diff --git a/Week_01/id_25/LeetCode_42_025.java b/Week_01/id_25/LeetCode_42_025.java new file mode 100644 index 00000000..10eac6fe --- /dev/null +++ b/Week_01/id_25/LeetCode_42_025.java @@ -0,0 +1,169 @@ +package highFrequencyLeetcode.leetcode_42; + +import java.util.Stack; + +/** + * Hard + * (注解文档查看快捷键 选中类名或方法名 按ctrl + Q) + *
+ * 思维全过程记录方案:
+ * 1 背基础结构和算法 | 记录在课程笔记
+ * 2 看题 -> 悟题 思考过程 | 记录在wiki
+ * 3 悟题 -> 写题 实现难点 | 记录在代码注解
+ * 4 写题 -> 优化 多种解法 | 记录在leetcode提交 + *
+ * 题解方案topics: + * array、双指针、stack + * + * @author li tong + * @date 2019/6/3 10:18 + * @see Object + * @since 1.0 + */ +public class LeetCode_42_025 { + + public static void main(String[] args) { + int[] testcase = new int[]{2, 0, 2}; // 0, 2, 0 + System.out.println("FORCE=" + bruteForce(testcase)); + System.out.println(); + System.out.println("DP_ONE=" + dpOne(testcase)); + System.out.println(); + System.out.println("DP_TWO=" + dpTwo(testcase)); + System.out.println(); + System.out.println("STACK=" + stack(testcase)); + } + + /** + * 解法1 暴力求解
+ * 看题解,提取主干思路:
+ * 从左向右遍历整个下标 每个下标从左、右分别找出最高的杆子
+ * 自己学习 思考 实践时的难点(难点是如何突破的见wiki):
+ * 看了题解才知道居然要从左、右分头遍历,这个一开始是想不到的 + * + * @param columns + * @return + */ + public static int bruteForce(int[] columns) { + int result = 0, length = columns.length; + for (int i = 0; i < length; i++) { + // Caution 1 声明位置在for循环里面而不是外面 + int maxl = 0, maxr = 0; + for (int j = i; j < length; j++) { + maxr = Math.max(maxr, columns[j]); + } + for (int j = i; j >= 0; j--) { + maxl = Math.max(maxl, columns[j]); + } + result += Math.min(maxl, maxr) - columns[i]; + } + return result; + } + + /** + * 解法2 DP求解
+ * 看题解,提取主干思路:
+ * 在解法一的基础上 由于是求极值问题 考虑DP
+ * 自己学习 思考 实践时的难点(难点是如何突破的见wiki):
+ * 由于在暴力求解中DP递推方程已得出,现在需要思考
+ * 1 如何用dp的方式遍历 还是和暴力一样吗?
+ * 2 如何归并?
+ * 看完样例代码,发现遍历的顺序和暴力是有区别的,为什么?
+ * + * @param columns + * @return + */ + public static int dpOne(int[] columns) { + // Caution 1 if length == 0 + if (columns.length == 0) { + return 0; + } + int result = 0, length = columns.length; + int[] maxl = new int[columns.length]; + int[] maxr = new int[columns.length]; + maxl[0] = columns[0]; + // Caution 2 length - 1 + maxr[length - 1] = columns[length - 1]; + for (int i = 1; i < length; i++) { + // Caution 3 i - 1 i + 1 + maxl[i] = Math.max(maxl[i - 1], columns[i]); + } + // Caution 4 length - 2 + for (int i = length - 2; i >= 0; i--) { + maxr[i] = Math.max(maxr[i + 1], columns[i]); + } + for (int i = 1; i < columns.length - 1; i++) { + result += Math.min(maxl[i], maxr[i]) - columns[i]; + } + return result; + } + + /** + * 解法3 DP求解 单次遍历
+ * 看题解,提取主干思路:
+ * 双指针替换两次遍历
+ * 自己学习 思考 实践时的难点(难点是如何突破的见wiki):
+ * 虽然用dp存储了一些计算结果,但还是和暴力一样遍历了两次,如何减少循环次数?
+ * + * @param columns + * @return + */ + public static int dpTwo(int[] columns) { + int result = 0, left = 0, right = columns.length - 1; + int maxl = 0, maxr = 0; + while (left < right) { + if (columns[left] < columns[right]) { + if (columns[left] >= maxl) { + maxl = columns[left]; + } else { + result += (maxl - columns[left]); + } + ++left; + } else { + if (columns[right] >= maxr) { + maxr = columns[right]; + } else { + result += (maxr - columns[right]); + } + --right; + } + } + return result; + } + + /** + * 解法4 栈解法
+ * 看题解,提取主干思路:
+ * 使用栈标记低谷和两界
+ * 自己学习 思考 实践时的难点(难点是如何突破的见wiki):
+ * 1 很难想到
+ * 2 即便看了答案也是很难理解
+ *
+ * @param columns
+ * @return
+ */
+ public static int stack(int[] columns) {
+ int result = 0, cursor = 0, length = columns.length;
+ Stack
+ * 思维全过程记录方案:
+ * 1 背基础结构和算法 | 记录在课程笔记
+ * 2 看题 -> 悟题 思考过程 | 记录在wiki
+ * 3 悟题 -> 写题 实现难点 | 记录在代码注解
+ * 4 写题 -> 优化 多种解法 | 记录在leetcode提交
+ *
+ * 题解方案topics:
+ * array、dp
+ *
+ * @author li tong
+ * @date 2019/6/4 11:36
+ * @see Object
+ * @since 1.0
+ */
+public class LeetCode_62_025 {
+
+ /**
+ * 观察时间复杂度
+ */
+ private static int count = 0;
+
+ public static void main(String[] args) {
+ System.out.println(recursive(0, 0));
+ System.out.println(recursive(1, 1));
+ System.out.println(recursive(1, 2));
+ System.out.println(recursive(2, 2));
+ System.out.println(recursive(3, 2));
+ System.out.println(recursive(3, 3));
+ System.out.println("RECURSIVE=" + recursive(10, 10));
+ System.out.println();
+
+ System.out.println("RECURSIVE_MEM=" + recursiveMem(10, 10, new int[11][11]));
+ System.out.println();
+
+ System.out.println("TRY_DP_ONE=" + tryDPOne(10, 10));
+ System.out.println();
+
+ System.out.println("[LC PASS]DP_ONE=" + dpOne(51, 9));
+ System.out.println();
+
+ System.out.println("[LC PASS]DP_TWO=" + dpTwo(10, 10));
+ System.out.println();
+
+ System.out.println("[LC PASS]MATH=" + math(23, 12));
+ System.out.println("Count=" + count);
+ }
+
+ /**
+ * 解法1 递归
+ * 注:本解法时间复杂度高,无法通过leetcode(在较大参数时超时),
+ * 但是,是一种可以求得正解的解法和推理的重要思路,故保留方法
+ *
+ * 自己思考,主干思路:
+ * 脑图图像
+ * 1 1 1 1
+ * 1 2 3 4
+ * 1 3 6 10 15
+ * + + + +
+ * 3 6 10 15
+ * + 3 4 5
+ *
+ * 总结规律,发现:f(m,n) = f(m-1,n) + f(m,n-1)
+ *
+ * @param m 棋盘长度
+ * @param n 棋盘高度
+ * @return
+ */
+ public static int recursive(int m, int n) {
+ count++;
+ if (m < 1 || n < 1) {
+ return 0;
+ } else if (m == 1 || n == 1) {
+ return 1;
+ }
+ return recursive(m - 1, n) + recursive(m, n - 1);
+ }
+
+ /**
+ * 解法2 缓存递归
+ * 注:同样因为加了参数无法通过leetcode提交,但是可以求得正解,
+ * 也是承前启后的推理思路,同时温习老师介绍的缓存思想,故保留
+ *
+ * @param m
+ * @param n
+ * @param mem
+ * @return
+ */
+ public static int recursiveMem(int m, int n, int[][] mem) {
+ count++;
+ if (m < 1 || n < 1) {
+ return 0;
+ } else if (m == 1 || n == 1) {
+ return 1;
+ // if (mem[m][n] == 0)非常重要 表示只有=0 才递归计算 否则就走缓存
+ } else if (mem[m][n] == 0) {
+ mem[m][n] = recursiveMem(m - 1, n, mem) + recursiveMem(m, n - 1, mem);
+ }
+ return mem[m][n];
+ }
+
+ /**
+ * 解法3 伪DP
+ * 注:
+ * 1.没有缓存,无法通过leetcode提交,实质和解法1相同
+ * 2.是从递归过渡到DP的尝试过程,试图寻找DP方程,故保留
+ *
+ * @param m
+ * @param n
+ * @return
+ */
+ public static int tryDPOne(int m, int n) {
+ count++;
+ int[][] dp = new int[m + 1][n + 1];
+ if (m < 1 || n < 1) {
+ return 0;
+ } else if (m == 1 || n == 1) {
+ return 1;
+ // f22 = dp11 == 0 -> dp11 = f12 + f21 = 2
+ } else if (dp[m][n] == 0) {
+ dp[m][n] = tryDPOne(m - 1, n) + tryDPOne(m, n - 1);
+ }
+ return dp[m][n];
+ }
+
+ /**
+ * 解法4 伪DP 不可用
+ * 注:
+ * 1.没找到循环公式,结果错误
+ * 2.是从递归过渡到DP的尝试过程,试图寻找DP方程,故保留
+ * @param m
+ * @param n
+ * @return
+ */
+ public static int tryDPTwo(int m, int n) {
+ count++;
+ int[][] dp = new int[m + 1][n + 1];
+ if (m < 1 || n < 1) {
+ return 0;
+ } else if (m == 1 || n == 1) {
+ return 1;
+ // f22 = dp11 == 0 -> dp11 = dp01 + dp10
+ } else if (dp[m][n] == 0) {
+ dp[m][n] = dp[m - 1][n] + dp[m - 1][n - 1];
+ }
+ return dp[m][n];
+ }
+
+ /**
+ * 解法5 DP 已通过leetcode提交
+ * 自己思考结合题解提示,主干思路:
+ * 1.经过解法3、4的尝试,发现DP实现主路径
+ * 自己学习 思考 实践时的难点(难点是如何突破的见wiki):
+ * 1 从递归到DP,不知道for循环怎么写
+ * 2 循环内的判断条件
+ * @param m
+ * @param n
+ * @return
+ */
+ public static int dpOne(int m, int n) {
+ count++;
+ if (m == 0 || n == 0) {
+ return 0;
+ }
+ if (m == 1 || n == 1) {
+ return 1;
+ }
+ int[][] dp = new int[m + 1][n + 1];
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < n; j++) {
+ if (i == 0 || j == 0) {
+ dp[i][j] = 1;
+ } else {
+ // f22 = dp11 == 0 -> dp11 = dp01 + dp10
+ dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
+ }
+// System.out.print("i" + i);
+// System.out.print(", j" + j + ", ");
+// System.out.println(dp[i][j]);
+ }
+ }
+ return dp[m - 1][n - 1];
+ }
+
+ /**
+ * 解法6 DP优化 已通过leetcode提交
+ * 注:实际复杂度提升效果有限(leetcode已验证),只是代码层面看起来简洁了一些
+ * 看题解,提取主干思路:
+ * 将二维DP降为一维
+ * 自己学习 思考 实践时的难点(难点是如何突破的见wiki):
+ * 1.想不到降维
+ * 2.降维之后,这个一维DP代表的是什么东西?想了半天,画图,后面弄懂了
+ * 图像 斜角对称性
+ * 1 1 1 1
+ * 1 2 3 4
+ * 1 3 6 10 15
+ * + + + +
+ * 3 6 10 15
+ * + 3 4 5
+ *
+ * @param m
+ * @param n
+ * @return
+ */
+ public static int dpTwo(int m, int n) {
+ count++;
+ if (m == 0 || n == 0) {
+ return 0;
+ }
+ if (m == 1 || n == 1) {
+ return 1;
+ }
+ int[] dp = new int[n];
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < n; j++) {
+ if (i == 0 || j == 0) {
+ dp[j] = 1;
+ } else {
+ // 1 2 3 4 6 10 15 20 35 70
+ dp[j] += dp[j - 1];
+// System.out.print("i" + i);
+// System.out.print(", j" + j + ", ");
+// System.out.println(dp[j]);
+ }
+ }
+ }
+ return dp[n - 1];
+ }
+
+ /**
+ * 解法7 数学解法 已通过leetcode提交
+ * 看题解,提取主干思路:
+ * 发现数据规律:排列组合定理
+ * 自己学习 思考 实践时的难点(难点是如何突破的见wiki):
+ * 1.排列组合公式都忘了,因此把公式复习了一遍,并且做题验证
+ * 2.如何把公式套用到程序里
+ * 3.double int
+ * 图像
+ * 1 1 1 1
+ * 1 2 3 4
+ * 1 3 6 10 15
+ * 1 4 10 20 35
+ * · · · 15 35 70
+ *
+ * 公式 C(4, 3) = A(4,3)/ A(3,3) = 4*3*2 / 3*2*1 = 4
+ * 证明 abcd => abc abd acd bcd
+ * 公式 C(4, 2) = 4 * 3 / 2 = 6
+ * 证明 abcd => ab ac ad bc bd cd
+ *
+ * 证明 C5,3 = C5,5-3 = C5,2
+ * abcde
+ * C53 = abc abd abe acd ace ade + C43 = 10
+ * C52 = 5*4/2 = 10
+ *
+ * n > k
+ * C(n, k) = A(n,k) / A(k,k) = C(n, n-k) = A(n,n-k) / A(n-k,n-k)
+ * C52 = 5 * 4 / 2! = C53 = 5 * 4 * 3 / 3!
+ * = (n * n-1 * n-2 ... * n - k + 1) / k!
+ *
+ * @param m
+ * @param n
+ * @return
+ */
+ public static int math(int m, int n) {
+ // 总步数
+ int t = n + m - 2;
+ // 单单往下(或往右)的步数
+ int k = m - 1;
+ double res = 1;
+ for (int i = 1; i <= k; i++) {
+// System.out.print("i" + i);
+// System.out.print(", res=" + res + ", ");
+// System.out.print((t - k + i));
+ // C = ((t - k + 1) * (t - k + 2) * ... * t) / k!
+ res = res * (t - k + i) / i;
+// System.out.println(", res=" + res + ", ");
+ }
+ return (int)res;
+ }
+
+ // DFS
+
+}
diff --git a/Week_01/id_25/NOTE.md b/Week_01/id_25/NOTE.md
index 107ea7d6..832d00e0 100644
--- a/Week_01/id_25/NOTE.md
+++ b/Week_01/id_25/NOTE.md
@@ -1 +1,73 @@
# 学习笔记
+1 对学习方法的总结
+ 观察了其他组的同学和我们组的做法
+ 感觉我们组的方法还是比较科学的:我们自己建立了一套题库,并且组内优先review。
+
+2 自己学习态度的变化
+ 平时在休息或思考时,分配了更多的精力思考算法问题
+
+3 具体的算法学习思维
+ 3.1 抓主干
+ 3.2 总结规律 自己五分钟无解的 就先看答案
+ 我也把这一过程写在了每个题目的注解里面
+ * 思维全过程记录方案:
+ * 1 背基础结构和算法 | 记录在课程笔记
+ * 2 看题 -> 悟题 思考过程 | 记录在wiki
+ * 3 悟题 -> 写题 实现难点 | 记录在代码注解
+ * 4 写题 -> 优化 多种解法 | 记录在leetcode提交
+ 3.3 反复练习
+ 有思路的就不断优化 没思路的 就先背诵 然后理解 再默写 如此往复
+ 根据这个套路 我制定了一份基于斐波那契遗忘曲线的复习表
+ 一定不要忘记老师说的 五毒神掌
+ 
+
+4 在学习笔记里 主要提及宏观上的学习领悟
+ 关于具体的题解思路 都详尽的写在题目里的注解里了
+
+5 分享一下记忆曲线复习表 即是TaskView.html
+ 直接浏览器打开就能用 里面包含一段斐波那契递归函数呦
+
+6 Review
+ 总体感触 用什么语言的都有 精彩纷呈
+ 5.1 LeetCode_242_18.java
+ 优点
+ 1. 和我写题的方式很像,把每一种解法都写出来了,也就是一个类里面,有多个解法,尽可能的涵盖了各种角度
+ 缺点
+ 1. 没有注解
+ 尽管老师说要少些注解,但是由于我们是在练习、学习,必要的地方还是要加上注解,一个是给自己记忆,另一方面方便别的同学阅读,
+ 另外,即使方法体、代码段里没有注解,在方法签名上也应有详细的doc和注解,阐述题意、解法、思路。
+
+ 5.2 LeetCode_26_015.py
+ 优点
+ 1. 看了这个同学的readme,说自己不是科班,罗列了很多的学到的知识点,可以看出非常认真。
+ 缺点
+ 1. 同上,代码缺少必要的注解和说明
+ 2. 解法偏少,只有一种解法。建议:除了自己思考的解法以外,也把其他解法实现一遍。
+
+ 5.3 LeetCode_021_29.go
+ 优点
+ 1. 这个同学做了很多题。
+ 2. 代码格式整齐 清爽 有适当的注解
+ 3. 脑图画的很好
+ 缺点
+ 1. 没有题解doc
+
+ 5.4 LeetCode_1047_022.js
+ 优点
+ 1. 代码格式整齐 清爽
+ 2. 脑图画的很好
+ 缺点
+ 1. 解法偏少
+ 2. 没有题解doc
+
+ 5.5 Leetcode_189_8.java
+ * 最喜欢的是这个同学的提交
+ 优点
+ 1. readme里面分门别类,格式整齐、详细的介绍了题目、难点分析、解题思路、参考代码
+ 2. 脑图画的很好
+ 缺点
+ 1. 部分代码没有格式化
+ 举例:
+ 1. k%nums.length,符号和变量中间要有空格,改为k % nums.length
+ 2. nums[i+k],改为 nums[i + k]
+ 3. for (int i=0; i
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file