leecode更新
This commit is contained in:
parent
e12f7b1ea1
commit
85339939f6
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue