leecode更新

This commit is contained in:
markilue 2022-11-22 13:55:35 +08:00
parent e12f7b1ea1
commit 85339939f6
3 changed files with 281 additions and 7 deletions

View File

@ -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]);
}
}

View File

@ -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=kn+k-1k=12m-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=kn+k-1k=12m-1组合数都是整数
ans = ans * x / y;
}
return (int) ans;
}
}

View File

@ -63,7 +63,7 @@ public class StrStr {
@Test @Test
public void test4() { public void test4() {
String haystack = "ababac"; String haystack = "abababc";
// String needle = "abbabbbabaa"; // String needle = "abbabbbabaa";
int[] next = getNext1(haystack); int[] next = getNext1(haystack);
System.out.println(Arrays.toString(next)); System.out.println(Arrays.toString(next));
@ -81,7 +81,7 @@ public class StrStr {
* @return * @return
*/ */
public int strStr(String haystack, String needle) { public int strStr(String haystack, String needle) {
int[] next = getNext(needle); int[] next = getNext1(needle);
int j = 0; int j = 0;
for (int i = 0; i < haystack.length(); ++i) { for (int i = 0; i < haystack.length(); ++i) {
@ -107,12 +107,9 @@ public class StrStr {
public int[] getNext(String needle) { public int[] getNext(String needle) {
int[] result = new int[needle.length()]; 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; int right = i;
for (int left = i - 1; left >= 0; left--) { for (int left = i - 1; left >= 0; left--) {
boolean flag = false; boolean flag = false;
@ -151,6 +148,8 @@ public class StrStr {
int k = -1; int k = -1;
for (int i = 1; i < needle.length(); ++i) { for (int i = 1; i < needle.length(); ++i) {
while (k != -1 && needle.charAt(k + 1) != needle.charAt(i)) { while (k != -1 && needle.charAt(k + 1) != needle.charAt(i)) {
//这里之所以可以直接使用k=next[k]去找到上一次相等的数,是因为next实际上就是最长前后缀相同
// 关键就在于这个前缀前缀意味着他是从头(索引0)开始算所以这个最长前后缀长度事实上可以直接代表上次相同的位置
k = next[k]; k = next[k];
} }
if (needle.charAt(k + 1) == needle.charAt(i)) { if (needle.charAt(k + 1) == needle.charAt(i)) {