From 85339939f6d5079f1661b0965a17bfb874e6d527 Mon Sep 17 00:00:00 2001 From: markilue <745518019@qq.com> Date: Tue, 22 Nov 2022 13:55:35 +0800 Subject: [PATCH] =?UTF-8?q?leecode=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dynamic/T03_MinCostClimbingStairs.java | 76 +++++++ .../leecode/dynamic/T04_UniquePaths.java | 199 ++++++++++++++++++ .../com/markilue/leecode/string/StrStr.java | 13 +- 3 files changed, 281 insertions(+), 7 deletions(-) create mode 100644 Leecode/src/main/java/com/markilue/leecode/dynamic/T03_MinCostClimbingStairs.java create mode 100644 Leecode/src/main/java/com/markilue/leecode/dynamic/T04_UniquePaths.java diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T03_MinCostClimbingStairs.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T03_MinCostClimbingStairs.java new file mode 100644 index 0000000..7634dd7 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T03_MinCostClimbingStairs.java @@ -0,0 +1,76 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: markilue + * @CreateTime: 2022-11-22 09:53 + * @Description: + * TODO 力扣746题 使用最小花费爬楼梯: + * 给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。 + * 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 + * 请你计算并返回达到楼梯顶部的最低花费。 + * @Version: 1.0 + */ +public class T03_MinCostClimbingStairs { + + @Test + public void test(){ + int[] cost = {10, 15, 20}; + int[] cost1= {1, 100, 1, 1, 1, 100, 1, 1, 100, 1}; + System.out.println(minCostClimbingStairs(cost)); + } + + + /** + * 自己尝试使用动态规划法:同样是爬楼梯,能爬一到两层 + * TODO 递归五部曲如下: + * (1)确定dp数组及下标含义:dp[i]是i楼要花费的钱是dp[i] + * (2)确定递推公式:状态转移方程 dp[i] = min(dp[i - 1]+cost[i-1], dp[i - 2]+cost[i-2]) + * (3)dp数组如何初始化:dp[0] = cost[0], ,dp[1] = cost[1] + * (4)确定遍历顺序:从递归公式dp[i] = min(dp[i - 1]+cost[i-1], dp[i - 2]+cost[i-2]);中可以看出,dp[i]是依赖 dp[i - 1] 和dp[i - 2],那么遍历的顺序一定是从前到后遍历的 + * (5)举例推导dp数组:当cost[] ={1,100,1,1,1,100,1,1,100,1}当N为10的时候,dp数组应该是如下的数列:1 100,1,2,... + * 速度超过7.57%,内存超过45.15% 1ms + * 将for循环出来的条件改为<=后 速度击败100%,内存击败65.13% + * @param cost + * @return + */ + public int minCostClimbingStairs(int[] cost) { + + int[] dp =new int[2]; + + for (int i = 2; i <= cost.length; i++) { + int now =Math.min(dp[0]+cost[i-2],dp[1]+cost[i-1]); + dp[0]=dp[1]; + dp[1]=now; + } + + + return dp[1]; + } + + + /** + * 代码随想录解法:他的理解是第一步需要消耗体力,最后一步需要消耗体力,所以他的解法初始化dp[0]和dp[1] + * 理解可以如下:本人的思路是爬上这一层需要的体力,代码随想录的方法是爬过这一层需要的体力 + * @param cost + * @return + */ + public int minCostClimbingStairs1(int[] cost) { + + int[] dp =new int[2]; + dp[0]=cost[0]; + dp[1]=cost[1]; + + for (int i = 2; i < cost.length; i++) { + int now =Math.min(dp[0],dp[1])+cost[i]; + dp[0]=dp[1]; + dp[1]=now; + } + + + return Math.min(dp[0],dp[1]); + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T04_UniquePaths.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T04_UniquePaths.java new file mode 100644 index 0000000..47102d5 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T04_UniquePaths.java @@ -0,0 +1,199 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: markilue + * @CreateTime: 2022-11-22 11:18 + * @Description: + * TODO 力扣62题 不同路径: + * 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 + * 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 + * 问总共有多少条不同的路径? + * @Version: 1.0 + */ +public class T04_UniquePaths { + + @Test + public void test(){ + //测试阶乘方法和组合方法 +// System.out.println(factorial(8,6)); +// System.out.println(combination(2,8)); + System.out.println(combination(9,18)); + System.out.println("=========================="); + //测试实际题目 + int m = 10, n = 10; + System.out.println(uniquePaths3(m,n)); + } + + /** + * 自己思路简化:这里似乎可以不用动态规划法,直接使用数学方法就可以完成: + * 因为要走的步数和要向右走的数都是确定的,排序就行了,给定m,n时,方法数是C(N-1,N+M-2),所以定义一个求组合数目的方法即可 + * 这种方法理论上来说应该是对的,但是由于精度限制,无论是换成long还是上,在阶乘计算时n一旦太大就会超出精度范围,但是这是无法避免的,所以这种方法无法通过 + * @param m + * @param n + * @return + */ + public int uniquePaths(int m, int n) { + return combination(Math.min(m,n)-1,m+n-2); + } + /** + * 求组合数 + * c(n,sum) + */ + public int combination(int n,int sum){ + if(n==0){ + return 1; + } + return factorial(sum,sum-n+1)/factorial(n,1); + } + /** + * 求一个数的阶乘 + */ + public int factorial(int m,int threshold){ + if(m==threshold){ + return threshold; + } + return m*factorial(m-1,threshold); + } + + + /** + * 自己思路动态规划法: + * TODO 递归五部曲如下: + * (1)确定dp数组及下标含义:dp[i][j]是到第i,j位置的方法数 + * (2)确定递推公式:状态转移方程 dp[i][j] =dp[i-1][j]+dp[i][j-1] 因为 dp[i][j]可以从dp[i-1][j]向右走一步,也可以dp[i][j-1]向下走一步 + * (3)dp数组如何初始化:dp[0][j] = 1, ,dp[i][0] = 1 + * (4)确定遍历顺序:从递归公式dp[i][j] =dp[i-1][j]+dp[i][j-1];中可以看出,dp[i]是依赖 dp[i - 1] 和dp[j - 2],那么遍历的顺序是从前到后,从上到下遍历的 + * (5)举例推导dp数组:当N为10的时候,dp数组应该是如下的数列: + * 速度超过100%,内存超过85.31% 0ms,但空间复杂度为O(n*m) + * @param m + * @param n + * @return + */ + public int uniquePaths1(int m, int n) { + + int[][] dp=new int[m][n]; + + for (int i = 0; i < m; i++) { + dp[i][0]=1; + } + for (int i = 0; i < n; i++) { + dp[0][i]=1; + } + + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + dp[i][j]=dp[i-1][j]+dp[i][j-1]; + } + } + return dp[m-1][n-1]; + } + + + /** + * 代码随想录的深度优先搜索法:由于这种方式类似于二叉树,需要不断的遍历全部的左右枝,所以时间复杂度较高 + * 时间复杂度O(2^(M+N+1)-1) + * 在m=19,n=13时,超出了时间限制 + * @param m + * @param n + * @return + */ + public int uniquePaths2(int m, int n) { + + return dfs(1,1,m,n); + } + + /** + * + * @param i 当前的位置i + * @param j 当前的位置j + * @param m 目标位置m + * @param n 目标位置n + * @return + */ + public int dfs(int i,int j,int m,int n){ + if(i>m||j>n) return 0; //越界了找不到 + if(i==m&&j==n) return 1; //刚好找到目标 + + return dfs(i+1,j,m,n)+dfs(i,j+1,m,n); + } + + + /** + * 代码随想录动态规划法:与本人思路一致,这里只写出他对于空间复杂度的优化 + * 实际上是使用dp[i]记录上一排的数字即可 + * 速度超过100%,内存超过41.12% + * @param m + * @param n + * @return + */ + public int uniquePaths3(int m, int n) { + + int[] dp=new int[n]; + + for (int i = 0; i < n; i++) { + dp[i]=1; + } + + for (int j = 1; j < m; j++) { + for (int i = 1; i < n; i++) { + dp[i]+=dp[i-1]; + } + } + return dp[n-1]; + } + + + /** + * 代码随想录数论法:与本人排列组合法思路一致,但是他使用随时除的方式避免精度超过限制 + * 时间复杂度O(M),空间复杂度O(1) + * 速度超过100%,内存超过77% + * @param m + * @param n + * @return + */ + public int uniquePaths4(int m, int n) { + long numerator=1;//分子 + int denominator=m-1;//分母 + int count =m-1; + int t=m+n-2; + //因为count--是后续计算的,所以这里是大于等于1 + while (count-- >=1){ + numerator*=(t--); + //这里采用能除尽的时候就除来避免精度超过限制,但是好像指标不治本? + while (denominator!=0&&numerator%denominator==0){ + numerator/=denominator; + denominator--; + } + } + + return (int)numerator; + + + } + + + /** + * 官方数论法:与本人排列组合法思路一致,但是他使用随时除的方式避免精度超过限制,关键在于他能保证每次除都是整数 + * 因为其实每一步的ans=ans*x/y=(k,n+k-1),k=1,2…m-1,组合数都是整数 + * 时间复杂度O(M),空间复杂度O(1) + * 速度超过100%,内存超过77% + * @param m + * @param n + * @return + */ + public int uniquePaths5(int m, int n) { + long ans = 1; + for (int x = n, y = 1; y < m; ++x, ++y) { + //TODO 重点:因为其实每一步的ans=ans*x/y=(k,n+k-1),k=1,2…m-1,组合数都是整数 + ans = ans * x / y; + } + return (int) ans; + } + + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/string/StrStr.java b/Leecode/src/main/java/com/markilue/leecode/string/StrStr.java index c880ea3..b0b46fb 100644 --- a/Leecode/src/main/java/com/markilue/leecode/string/StrStr.java +++ b/Leecode/src/main/java/com/markilue/leecode/string/StrStr.java @@ -63,7 +63,7 @@ public class StrStr { @Test public void test4() { - String haystack = "ababac"; + String haystack = "abababc"; // String needle = "abbabbbabaa"; int[] next = getNext1(haystack); System.out.println(Arrays.toString(next)); @@ -81,7 +81,7 @@ public class StrStr { * @return */ public int strStr(String haystack, String needle) { - int[] next = getNext(needle); + int[] next = getNext1(needle); int j = 0; for (int i = 0; i < haystack.length(); ++i) { @@ -107,12 +107,9 @@ public class StrStr { public int[] getNext(String needle) { int[] result = new int[needle.length()]; + result[0]=-1; + for (int i = 1; i < needle.length(); i++) { - for (int i = 0; i < needle.length(); i++) { - result[i] = -1; - if (i == 0) { - continue; - } int right = i; for (int left = i - 1; left >= 0; left--) { boolean flag = false; @@ -151,6 +148,8 @@ public class StrStr { int k = -1; for (int i = 1; i < needle.length(); ++i) { while (k != -1 && needle.charAt(k + 1) != needle.charAt(i)) { + //这里之所以可以直接使用k=next[k]去找到上一次相等的数,是因为next实际上就是最长前后缀相同, + // 关键就在于这个前缀,前缀意味着他是从头(索引0)开始算,所以这个最长前后缀长度事实上可以直接代表上次相同的位置 k = next[k]; } if (needle.charAt(k + 1) == needle.charAt(i)) {