Leetcode刷题指南之DP动态规划

Leetcode刷题指南之DP动态规划


前言

记录关于Leetcode上DP相关的题目和题解及个人解析

DP背包

参考秒懂算法的讲解
题目:
4个物品,背包总容量是8,背包最多能装入最高价值是多少?

物品编号1234
物品体积2345
物品价值3456

在这里插入图片描述

在这里插入图片描述

代码实现:

public class backPackage {
	int n,m;
	int []value;
	int []weight;
	int [][]F;
	public void zeroOne() {
		//初始化,第0行都是0
		for(int j=0;j<=m;j++) {
			F[0][j]=0;
		}
		for(int i=1;i<=n;i++) {
			F[i][0]=0;
		}
		//关键
		for(int i=1;i<=n;i++) {
			for(int j=0;j<=m;j++) {
				//背包的容量为j时
				if(j>=weight[i]) {//如果背包容量j大于第i个物体的容量,即放得下该物体
					//考虑是把第i个物品放进去  和  不放第i个物品  哪个最终价值大
					F[i][j]=Math.max(F[i-1][j-weight[i]]+value[i], F[i-1][j]); 			//放第i个物品则价值为前i-1个物品在(当前容量扣掉第i个物品的容量的容量)的最佳价值+当前物品的价值和当前容量的前i-1个物品的最佳价值比较。
				}
				
				else {//容量不够,不放入背包
					F[i][j]=F[i-1][j];
				}
			}
		}
		

	}
	public void printRes() {//回溯,最终背包放了哪几个编号物品
		boolean[] isAdd=new boolean[n+1];
		int count=0;
		int j=m;
		for(int i=n;i>=1;i--) {
			if(F[i][j]==F[i-1][j])
				isAdd[i]=false;
			else {
				isAdd[i]=true;
				j=j-weight[i];
			}
		}
		for(int i=1;i<=n;i++) {
			for(int t=1;t<=m;t++) {
				System.out.printf(F[i][t]+" ");
			}
			System.out.println();
		}
		for(int i=0;i<=n;i++) {
			if(isAdd[i]==true) {
				System.out.printf("编号%d被放入",i);
			}
		}}
	public void init() {
		Scanner scanner=new Scanner(System.in);
		n=scanner.nextInt();
		m=scanner.nextInt();
		weight=new int[n+1];
		value=new int[n+1];
		F=new int[n+1][m+1];
		for(int i=1;i<=n;i++) {
			weight[i]=scanner.nextInt();
		}
		for(int i=1;i<=n;i++) {
			value[i]=scanner.nextInt();
		}
	}
	public static void main(String[] args) {
		backPackage bp=new backPackage();
		bp.init();
		bp.zeroOne();
		bp.printRes();
	}

}

Leetcode139. Word Break

public class Solution {
    public boolean wordBreak(String s, Set<String> dict) {
        

        boolean[] f = new boolean[s.length() + 1];
        f[0] = true;

        for(int i = 1; i <= s.length(); i++){
            for(String str: dict){
                if(str.length() <= i){
                    if(f[i - str.length()]){
                        if(s.substring(i-str.length(), i).equals(str)){
                            f[i] = true;
                            break;
                        }
                    }
                }
            }
        }
        /*
        //Second DP
        for(int i=1; i <= s.length(); i++){
            for(int j=0; j < i; j++){
                if(f[j] && dict.contains(s.substring(j, i))){
                    f[i] = true;
                    break;
                }
            }
        }
        */

        return f[s.length()];
    }

}


完全背包解法:

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        int n = s.length();
        boolean [] dp = new boolean [n+1];
        dp[0]=true;

             for(int i=1;i<=n;i++){
              for(String word:wordDict){//求解顺序完全背包问题对物品的迭代应该放在里层
                     int w = word.length();
                 if(i>=w&&word.equals(s.substring(i-w,i))){
                     dp[i]=dp[i]||dp[i-w];
                 }
             }
         }
        return dp[n];
    }

}

Leetcode198 House Robber I

Example 1:

Input: nums = [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
             Total amount you can rob = 1 + 3 = 4.

Example 2:

Input: nums = [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).
             Total amount you can rob = 2 + 9 + 1 = 12.

典型的动态规划题目
题目:给你一个数组,你要返回一个序列值的和。
这个序列里面的数满足:1.互不相邻2.值的和最大

方法一:用动态规划的思路解:算法时间复杂度是O(n²)
思路:定义一个长度为nums.length的dp数组。来存当前i的与前面不相邻的数的最大和。后面直接调用。就是动态规划的思想

代码:

class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        if(n<2){            
        return n==0?0:nums[0];
        }
        int []dp=new int[n];   
        dp[0]=nums[0];
        dp[1]=nums[1];
        for(int i=2;i<n;i++){
            for(int j=i-2;j>=0;j--){
                dp[i]=Math.max(dp[j]+nums[i],dp[i]);
            }
        }
        int max=dp[0];
        for(int x:dp){
           if(x>max)
               max=x;
        }
        return max;
    }

}


方法二:优化为0/1背包问题O(n)

Success

Details

Runtime: 0 ms, faster than 100.00% of Java online submissions for House Robber.

Memory Usage: 36.6 MB, less than 90.17% of Java online submissions for House Robber.

思路:

转化为0/1背包问题 dp(i,0)表示第i个数不放进去,0i-1个数的满足不相邻条件的最大和,因为当前数不放,那么前一个数有两种选择,我们选值大那个【max(前一个数放,前一个数不放)】,dp(i,1)表示第i个数放进去,0i个数满足不相邻条件的最大和,第i个数放,那么前一个数得不放。

class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        int [][]dp=new int[n+1][2];
        dp[0][0]=0;
        dp[0][1]=0;
        for(int i=1;i<=n;i++){
            dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]);
            dp[i][1]=nums[i-1]+dp[i-1][0];
        }
    return Math.max(dp[n][0],dp[n][1]);
    }

}

方法三:优化为Space O(1)

class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        int pre0=0;
        int pre1=0;
        for(int i=1;i<=n;i++){
            int temp=pre0;
            pre0=Math.max(pre0,pre1);
            pre1=nums[i-1]+temp;
        }
    return Math.max(pre0,pre1);
    }

}

Leetcode213 House Robber II

与Leetcode198 House Robber I不同的题目要求是

第一个数和最后一个数也是邻居

Example 1:

Input: [2,3,2]
Output: 3
Explanation: You cannot rob house 1 (money = 2) and then rob house 3 (money = 2),
             because they are adjacent houses.

Example 2:

Input: [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
             Total amount you can rob = 1 + 3 = 4.

解:那么就比较分别从0n-1,1n的哪个的不相邻之和最大。

class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        if(n==0)
            return 0;
        if(n==1)
            return nums[0];
        int [][]dp=new int[2][n];
        dp[0][0]=0;
        dp[1][0]=nums[0];
        for(int i=1;i<n-1;i++){
            dp[0][i]=Math.max(dp[1][i-1],dp[0][i-1]);//不放当前
            dp[1][i]=nums[i]+dp[0][i-1];    //放当前
        }
        

        int max=Math.max(dp[0][n-2],dp[1][n-2]);
        dp[0][1]=0;
        dp[1][1]=nums[1];
         for(int i=2;i<n;i++){
            dp[0][i]=Math.max(dp[1][i-1],dp[0][i-1]);//不放当前
            dp[1][i]=nums[i]+dp[0][i-1];    //放当前
        }
        max=Math.max(max,dp[0][n-1]);
        return Math.max(max,dp[1][n-1]);
    }

}

Leetcode322. Coin Change

解:

dp表格

dp(i,j)表示第0-i个硬币中凑出钱数是j的最少硬币个数。

j i01234567891011
0000000000000
111234567891011
2111223344556
5111221223323

状态转移方程:

dp(i,j)=min(dp(i-1,j),dp(i,nums[i-1])+dp(i,j-nums[i-1]));

class Solution {
    public int coinChange(int[] nums, int amount) {
        int n=nums.length;
        int m=amount;
        if(n==0||m==0)
            return 0;
        int [][]dp=new int[n+1][m+1];
        for(int i=0;i<=n;i++){
            dp[i][0]=1;
        }
        for(int i=0;i<=n;i++){
            for(int j=1;j<=m;j++){
               dp[i][j]=999999; 
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                dp[i][j]=dp[i-1][j];
                if(j>nums[i-1])
                dp[i][j]=Math.min(dp[i][j],dp[i][j-nums[i-1]]+dp[i][nums[i-1]]);
                else if(j==nums[i-1])
                    dp[i][j]=1;
            }
        }
        return dp[n][m]==999999?-1:dp[n][m];     
    }
}

空间优化:

class Solution {
    public int coinChange(int[] nums, int amount) {
        int n=nums.length;
        int m=amount;
        if(n==0||m==0)
            return 0;
        int []dp=new int[m+1];
        

            dp[0]=1;
            for(int j=1;j<=m;j++){
               dp[j]=999999; 
            }
        
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(j>nums[i-1])
                dp[j]=Math.min(dp[j],dp[j-nums[i-1]]+dp[nums[i-1]]);
                else if(j==nums[i-1])
                    dp[j]=1;
            }
        }
        return dp[m]==999999?-1:dp[m];
       
    }

}

再优化:

1.每个coin循环j直接从当前coin开始,因为当前coin之前已经得出最小了

2.dp[coin]=1是恒定的,所以dp[j-coin]+dp[coin]=dp[j-coin]+1

class Solution {
    public int coinChange(int[] nums, int amount) {
        int n=nums.length;
        int m=amount;
        if(n==0||m==0)
            return 0;
        int []dp=new int[m+1];
        

            dp[0]=0;
            for(int j=1;j<=m;j++){
               dp[j]=99999; 
            }
    
            for(int coin:nums){
            for(int j=coin;j<=m;j++){
                if(dp[j-coin]!=99999)
                dp[j]=Math.min(dp[j],dp[j-coin]+1);
            }
        }
        return dp[m]==99999?-1:dp[m];
       
    }

}

Leetcode518.Coin Change ||

Input: amount = 5, coins = [1, 2, 5]
Output: 4
Explanation: there are four ways to make up the amount:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

Example 2:

Input: amount = 3, coins = [2]
Output: 0
Explanation: the amount of 3 cannot be made up just with coins of 2.

Example 3:

Input: amount = 10, coins = [10] 
Output: 1
class Solution {
    public int change(int amount, int[] coins) {
        int []dp=new int[amount+1];
        int n=coins.length;
        dp[0]=1;
        for(int i=1;i<=n;i++){
            int coin=coins[i-1];
            for(int j=coin;j<=amount;j++){
                dp[j]+=dp[j-coin];
            }
        }
        return dp[amount];
    }
}

Leetcode377 Combination Sum IV

比较:

377 的组合数字之间是有顺序的(对于每个目标,每个数字在不同的顺序中可以重复出现,所以以target的顺序为准来遍历)

518的组合数字是无顺序的(对于每个目标,这个数字在第一次出现和第二次出现是一样的,所以以coins的顺序为准来遍历比较好)

Example:

nums = [1, 2, 3]
target = 4

The possible combination ways are:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

Note that different sequences are counted as different combinations.

Therefore the output is 7.
class Solution {
    public int combinationSum4(int[] nums, int target) {
       

        int []dp=new int[target+1];
        dp[0]=1;
            for(int j=1;j<=target;j++){
                for(int x:nums){
                if(j>=x){
                    dp[j]+=dp[j-x];
                }
            }
        }
        return dp[target];
    }

}

Leetcode416 Partition Equal Subset Sum

题目:一个数组能否划分成 两个和相等的序列

Input: [1, 5, 11, 5]

Output: true

Explanation: The array can be partitioned as [1, 5, 5] and [11].

转化为0/1完全背包问题

img

可以看到,背包容量为0时,总有合法值。因为只要不放就是合法的。

容量为1时,由于没有体积为1的物品,所以v=1的所有状态都是非法的。取值为负无穷。

V=2时,由于物品4的体积为2,所以前4件 和 前5件,都有合法值。

以此类推。

class Solution {
    public boolean canPartition(int[] nums) {
        int sum=0;
        for(int x:nums){
            sum+=x;
        }
        if(sum%2!=0){//奇数 两个相同的数相加是偶数
            return false;
        }
        sum/=2;
        int n=nums.length;
        int m=sum;
        boolean [][]dp=new boolean[n+1][m+1];
        

        for(int i=0;i<=n;i++){
            dp[i][0]=true;
        }
        for(int j=1;j<=m;j++){
            dp[0][j]=false;
            }
        int curindex=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                curindex=i-1;
                dp[i][j]=dp[i-1][j];
                if(j>=nums[curindex]){
                    dp[i][j]=(dp[i][j]||dp[i-1][j-nums[curindex]]);
                }
            }
        }
        return dp[n][m];
    }

}

空间优化:

class Solution {
    public boolean canPartition(int[] nums) {
        int n=nums.length;
        int sum=0;
        for(int x:nums){
            sum+=x;
         }
        if(sum%2!=0)
            return false;
        sum/=2;
    

        boolean []dp=new boolean[sum+1];
        dp[0]=true;
        for(int i=1;i<=n;i++){
            for(int j=sum;j>=0;j--){
                if(j>=nums[i-1])
                dp[j]=(dp[j]||dp[j-nums[i-1]]);
            }
        }
        return dp[sum];
    }

}

Leetcode1143. 最长公共子序列Longest Common Subsequence

Example 1:

Input: text1 = "abcde", text2 = "ace" 
Output: 3  
Explanation: The longest common subsequence is "ace" and its length is 3.

Example 2:

Input: text1 = "abc", text2 = "abc"
Output: 3
Explanation: The longest common subsequence is "abc" and its length is 3.

最长公共子序列(Longest Common Subsequence,简称 LCS)是一道非常经典的面试题目,也是一道老典型动态规划了,原型是“0-1背包问题”。使用“动态规划”解决问题的思路是“以空间换时间”,即我们常说的,填表格!!!(这波呀,老动态规划了)
动态规划:
动态规划方法并不是什么高大上的方法可以直接解决问题,而是让我们去寻找原始问题(或者和原始问题差不多)的最初的样子,通过“状态转移方程”记录每一步求解的结果,一步一步解决,最后迎娶白富美走上人生巅峰。
一般来讲,使用动态规划有5个步骤:
1)定义状态
2)思考初始化
3)思考输出
4)思考状态转移方程(这一步是最难的)
5)考虑状态压缩(即优化)

3.理性分析

第一步,一定要明确 dp 数组的含义。

对于字符串text1和text2,一般都要构建这样一个dp表。text1=“abcde”,text2=“acez”

为了方便理解,这里我们约定字符串的索引从1开始。其中dp[i][j]的含义是:对于s1[1,i]和s2[1,j],它们两者的LCS长度为dp[i][j]。比如dp[2][3]=1,即s1=“ab”,s2=“ace”,它们的LCS长度为1。

第二步,思考初始化

我们让索引为0的行和列分别表示当s1为空 or s2为空,因此第一行和第一列都应该初始化为0。

第三步,思考输出

我们的输出应该为表格中的最后一格,即dp[text1.length()][text2.length()]

第四步,思考状态转移方程(这一步是最难的,希望读者细品)

这道题要求我们求text1和text2的最长公共子序列,那么对于text1和text2中的每一个字符,他们都有两种命运:在LCS中,或者不在LCS中。
text1=“abcde”,text2=“acez”,LCS=“ace”
我们可以注意到,如果某个字符在LCS中,那么他一定存在于 text1 和 text2 中,因此我们需要用两个指针 i 和 j 分别指向 text1 和 text2 ,如果我们发现 text1[i] == text2[j] ,那么这个字符一定在LCS中,即 dp[i][j] = dp[i-1][j-1] + 1 ;
那么如果 text1[i] != text2[j] 呢?就会有三种情况:
1)s1[i]字符在LCS中,s2[j]不在LCS中,比如 text1[3](c)和 text2[4](z)比较。此时z不在LCS中。那么此时的LCS应该等价于去 掉text2[4] 这个字符的LCS。说明 ”text1=abc,text2=acez” 和 最长子序列应该和 ”text1=abc,text2=ace” 的最长子序列相等,
即 dp[3][4]=dp[3][3] == dp[i][j] = dp[i][j-1]
2)s1[i]字符不在LCS中,s2[j]在LCS中,比如 text1[4] = d 和 text2[2] = c 比较,此时d不在LCS中。说明" text1 = abcd 和 text2 = ac " 的最长子序列和 " text1=abc,text2 = ac " 相等,即dp[4][2] = dp[3][2],即 dp[i][j] = dp[i-1][j] 。
3)两个字符都不在LCS中。显然dp[i][j] = dp[i-1][j-1]。(这里不在多述,如有疑问欢迎在评论区提
因此,我们得到了当 text1[i] != text2[j] 的状态转移方程,即
dp[i][j] = max (dp[i-1][j] , dp[i][j-1] , dp[i-1][j-1])

第五步:压缩状态状态方程(即优化)

dp[i-1][j] = max( dp[i-1][j-1],dp[i-2][j] )
即dp[i-1][j] >= dp[i-1][j-1]
同理可得dp[i][j-1] >= dp[i-1][j-1]
也就是说我们在求 dp[i][j] 的时候,不需要再比较 dp[i-1][j-] 了,因为在求前面的 dp[i-1][j] 和 dp[i][j-1] 时,已经比较过了。因此我们的状态转移方程可以进一步优化为:
在这里插入图片描述

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
       

        int n=text1.length();
        int m=text2.length();
        if(n==0||m==0)
            return 0;
        int [][]dp=new int[n+1][m+1];
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(text1.charAt(i-1)==text2.charAt(j-1))
                    dp[i][j]=dp[i-1][j-1]+1;
                else{
                    dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[n][m];
    }

}

DP最大子序列和

Leetcode53. Maximum Subarray

求最大子序列和的问题

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

解析:

一个res数组,若res[i]不加res[i-1]还更大,那res[i]独美

class Solution {
    public int maxSubArray(int[] nums) {
        int n=nums.length;
        int []res=new int[n];
        for(int i=0;i<n;i++){
            res[i]=nums[i];
        }
        for(int i=1;i<n;i++){
            if(res[i]+res[i-1]>res[i]){
                res[i]=res[i-1]+res[i];
            }
        }
        int max=res[0];
        for(int x:res){
            if(x>max){
                max=x;
            }
        }
        return max;
    }
}

Leetcode121 买卖股票最佳时机

题目:给一个数组为股票在每天的价格。让买股票,然后再卖出去。求最大收益

思路转换:求最大子序列之和->优雅(dp),此题是Leetcode53的变式
如[7,1,5,3,6,4]
nums[-6,4,-2,3,2]每相邻两天的差价
最大子序列之和的序列为[4,-2,3] 和为5

求最大子序列的思想
两个记录:一开始i=0(1)max=nums[0]
(2)res=nums[0]
i=1:(1)max=Math.max(max+nums[i],nums[i]);
在长度为2的序列之间找出最大子序列之和。即nums[i-1],nums[i],nums[i-1]+nums[i]之间找最大值
(2)res=Math.max(max,res);
找出前面的最大之后把前面前面的序列看成一个整体。即求i+1时候的最大子序列之和与i=1时候的方法一样,求出前面的就能求出后面的,像数学归纳一样。

代码:(赞赞赞!优雅)

class Solution {
    public int maxProfit(int[] pri) {
        int max=0;
        int res=0;
        for(int i=1;i<pri.length;i++){
            max=Math.max(max+pri[i]-pri[i-1],pri[i]-pri[i-1]);
            res=Math.max(max,res);
        }
        return res;
    }
}

我写的代码:

class Solution {
    public int maxProfit(int[] prices) {
        int n=prices.length;
        if(n==0)
            return 0;
        int []dp=new int[n];
        for(int i=1;i<n;i++){
            dp[i-1]=prices[i]-prices[i-1];
        }
        for(int i=1;i<n-1;i++){
            if(dp[i-1]+dp[i]>dp[i]){
                dp[i]=dp[i-1]+dp[i];
            }
        }
        int max=dp[0];
        for(int x:dp){
            if(x>max){
                max=x;
            }
        }
        return max;
    }
}

Leetcode718 最长公共子数组

题意:给一个数组A,一个数组B,找出它们的最长公共子数组

不要和最长公共子序列Leetcode1143搞混了。这里的子数组意思是是要连续的数组

dp(i,j)代表的不是从i~j的最长重复子数组,所以这道题最佳方法其实不是用dp.

Example 1:

Input:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
Output: 3
Explanation: 
The repeated subarray with maximum length is [3, 2, 1].

dp做法:

47231
410000
702000
000000
200100
300020
100003
class Solution {
    public int findLength(int[] A, int[] B) {
        int n=A.length;
        int m=B.length;
        int [][]dp=new int[n+1][m+1];
        int max=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(A[i-1]==B[j-1])
                    dp[i][j]=dp[i-1][j-1]+1;
                max=Math.max(max,dp[i][j]);
            }
        } 
        return max;
    }
}

最长上升序列Leetcode300

Given an unsorted array of integers, find the length of longest increasing subsequence.

Example:

Input: [10,9,2,5,3,7,101,18]
Output: 4 
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. 
class Solution {
    public int lengthOfLIS(int[] nums) {
        int n=nums.length;
        if(n==0)
            return 0;
        int []dp=new int[n];
        dp[0]=1;
        for(int i=1;i<n;i++){
            for(int j=i;j>=1;j--){
                if(nums[i]>nums[j-1]){
                    dp[i]=Math.max(dp[i],dp[j-1]);
                }
            }
            dp[i]+=1;
        }
        int max=dp[0];
        for(int i=0;i<n;i++){
            if(dp[i]>max)
                max=dp[i];
        }
        return max;
    }
}

求路径

Leetcode120三角形最小路径和

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]

The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

Runtime: 1 ms, faster than 98.29% of Java online submissions for Triangle.

Memory Usage: 39.6 MB, less than 60.21% of Java online submissions for Triangle.

解析:

自己做出来的,做动态规划的时候

1.理解题目意思

每一步只能走向下一行相邻的数。求从顶到底的最小路径和。

2.dp的含义

dp[j]表示从第n行当前第i行的每个结点的最小路径和

为什么从n向上呢?因为从底向上就是直接回溯,不然你先从上往下每一步走相邻最小,最终结果不一定是最小。

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int res=0;
        int n=triangle.size();
        List<Integer> lastlist=triangle.get(n-1);
        int m=lastlist.size();
        int []dp=new int[m];
        int cnt=0;
        for(int x:lastlist){
            dp[cnt++]=x;
        }
        for(int i=n-2;i>=0;i--){
            List<Integer> list=triangle.get(i);
            int k=list.size();
            for(int j=0;j<k;j++){
                dp[j]=Math.min(dp[j],dp[j+1])+list.get(j);
            }
        }
        return dp[0];
    }
}

Leetcode931. Minimum Falling Path Sum(最小下降序列和)

Example 1:

Input: [[1,2,3],[4,5,6],[7,8,9]]
Output: 12
Explanation: 
The possible falling paths are:
  • [1,4,7], [1,4,8], [1,5,7], [1,5,8], [1,5,9]
  • [2,4,7], [2,4,8], [2,5,7], [2,5,8], [2,5,9], [2,6,8], [2,6,9]
  • [3,5,7], [3,5,8], [3,5,9], [3,6,8], [3,6,9]

The falling path with the smallest sum is [1,4,7], so the answer is 12.

自己根据三角形最小路径和还有找规律做的

自底向上

class Solution {
    public int minFallingPathSum(int[][] A) {
        int n=A.length;
        int m=A[0].length;
        int [][]dp=new int[n][m];
        for(int j=0;j<m;j++){
            dp[n-1][j]=A[n-1][j];
        }
    
        for(int i=n-2;i>=0;i--){    
            for(int j=0;j<m;j++){
                if(j-1>=0)
                    dp[i][j-1]=Math.min(dp[i][j-1],dp[i+1][j]+A[i][j-1]);
                if(j==0){
                    dp[i][j]=dp[i+1][j]+A[i][j];
                }
                if(j+1<m){
                    dp[i][j+1]=Math.min(dp[i+1][j],dp[i+1][j+1])+A[i][j+1];
                }
            }
        }
        int min=dp[0][0];
        for(int j=0;j<n;j++){
            if(dp[0][j]<min)
                min=dp[0][j];
        }
        return min;
    }

}

更高效的方法:

class Solution {
    public int minFallingPathSum(int[][] A) {
     //dps[i][j]代表从i这个数到j层的最大值,
        //从下往上找
        //然后判断(dps[0][A[0].length-1])第一行的每个值到最下面一层中的最小值。
        int [][]dps=new int [A.length][A[0].length];
        for(int j=0;j<A[0].length;j++)
        {
            dps[A.length-1][j]=A[A.length-1][j];
        }
        for(int i=A.length-2;i>=0;i--)
        {
            for(int j=0;j<A[0].length;j++)
            {
             int a1=Integer.MAX_VALUE;
                int a2=Integer.MAX_VALUE;
                int a3=Integer.MAX_VALUE;
                if(j-1>=0)
                    a1=dps[i+1][j-1];
                if(j+1<A[0].length)
                    a2=dps[i+1][j+1];
                a3=dps[i+1][j];
                int a4=Math.min(a2,a3);
                dps[i][j]=Math.min(a1,a4)+A[i][j];
                }
            }
         int min=Integer.MAX_VALUE;
        for(int j=0;j<A[0].length;j++){
            if(dps[0][j]<min)
            {
                min=dps[0][j];
            }
        }
        return min;
        }
       
       }

Leetcde64. Minimum Path Sum

求从左上角到右下角的最小路径,每一步只能往右或往下。

Input:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
Output: 7
Explanation: Because the path 1→3→1→1→1 minimizes the sum.

1.当时左上角时grid[0][0]不变

2.当是在左上角的右边一行时,右边+=其左一位的数

3.当是在左上角下边一列时,下边+=其上一位方的数

4.当是其它位置的时候,该位置+=min(上,左);

class Solution {
    public int minPathSum(int[][] grid) {
        int n=grid.length;
        int m=grid[0].length;
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(i==0&&j!=0){
                  grid[i][j]=grid[i][j]+grid[i][j-1];
                }
                else if(i!=0&&j==0){
                    grid[i][j]=grid[i][j]+grid[i-1][j];
                }
                else if(i!=0&&j!=0){
                    grid[i][j]+=Math.min(grid[i-1][j],grid[i][j-1]);
                }
                else
                {
                    grid[i][j]=grid[i][j];
                }    

            }
        }
         return grid[n-1][m-1];
}
}

Leetcode62. Unique Paths

题:求从左上角到右下角的不同路径总数目,每一步只能→↓

解析:

除了第一行和第一列以外的每个点接收到的来向都是上点和左点。

所以我们先初始化第一行和第一列都为1

然后把除这之外的每个结点都是=上+左

class Solution {
    public int uniquePaths(int m, int n) {
        int [][]dp=new int[m][n];
        for(int i=0;i<m;i++){
            dp[i][0]=1;
        }
        for(int j=0;j<n;j++){
             dp[0][j]=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];
    }
}

Leetcode63. Unique Paths II

在Leetcode62的基础上加入了障碍物。

Input:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
Output: 2
Explanation:
There is one obstacle in the middle of the 3x3 grid above.
There are two ways to reach the bottom-right corner:
1. Right -> Right -> Down -> Down
2. Down -> Down -> Right -> Right
class Solution {
    public int uniquePathsWithObstacles(int[][] Grid) {
        int n=Grid.length;
        int m=Grid[0].length;
        if(n==0||Grid[0][0]==1)
            return 0;
        Grid[0][0]=1;
        for(int j=1;j<m;j++){
            if(Grid[0][j]==1){
                 Grid[0][j]=0;
            }
            else
            Grid[0][j]+=Grid[0][j-1];
        }
        for(int i=1;i<n;i++){
            if(Grid[i][0]==1){
                Grid[i][0]=0;
            }
            else
            Grid[i][0]+=Grid[i-1][0];
        }
        for(int i=1;i<n;i++){
            for(int j=1;j<m;j++){
                if(Grid[i][j]==1){
                   Grid[i][j]=0;
                }
                else
                    Grid[i][j]=Grid[i-1][j]+Grid[i][j-1];
            }
        }
        return Grid[n-1][m-1];
    }
}

Leetcode 70青蛙跳台阶


青蛙跳台阶 step1-2。给一个n,求有几种跳法。
典型的dp Fibonacci数列思想转换
比如跳3个台阶
res(3)=res(2)+1+res(1)+2;
res(4)=res(3)+1+res(2)+2;
所以由此可得第n个台阶为n-1个和n-2个台阶的跳法之和

完全的Fibonacci数列

代码:

class Solution {
    public int climbStairs(int n) {
        if(n<=2){
            return n==1?1:2;
        }
        int two_befor=1;
        int one_befor=2;
        int res=0;
        for(int i=2;i<n;i++){
            res=two_befor+one_befor;
            two_befor=one_befor;//之后前2步=当前的前1步
            one_befor=res;//之后的前1步=当前的结果
        }
        return res;
    }
}

Leetcode746. Min Cost Climbing Stairs

题目:从cost[0]或者cost[1]开始走一步或者两步,求最后花费最少。

Example 1:

Input: cost = [10, 15, 20]
Output: 15
Explanation: Cheapest is start on cost[1], pay that cost and go to the top.

Example 2:

Input: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
Output: 6
Explanation: Cheapest is start on cost[0], and only step on 1s, skipping cost[3].
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n=cost.length;
        int []dp=new int[n];
      

        dp[0]=cost[0];
        dp[1]=cost[1];
        for(int i=2;i<n;i++){
            dp[i]=Math.min(dp[i-1],dp[i-2])+cost[i];
        }
        return Math.min(dp[n-2],dp[n-1]);
    }

}

Leetcode96. Unique Binary Search Trees

Example:

Input: 3
Output: 5
Explanation:
Given n = 3, there are a total of 5 unique BST's:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

大概是这个意思:

  1. 给出的n代表有n个节点,1,2,3,4,5,……n,这些节点组成的不同形态的二叉查找树,是说中序遍历这些树,得到的序列就是 1,2,3,4,5,……n。
  2. 根据二叉查找树可以知道,某根节点x,它的左子树的值全<=x(当然本题不存在等于的情况),它的右子树的值全>=x,所以,当它的根节点是 1 的时候,左子树个数为 0 ,右子树的个数为 n-1, 当它的根节点为 2 的时候, 左子树个数为 1, 右子树的个数为 n-2……
  3. 还有一个规律,就是这棵树的不同形态的二叉查找树的个数,就是根节点的 左子树的个数*右子树的个数,想想还是很容易理解的,就是左边的所有情况乘以右边的所有情况,知道这个规律就好做啦。
  4. 动态规划,从前到后计算出当有i个节点时,它有多少种不同形态的树。nums[i] += nums[j] * nums[i-1-j] (初始j==0,每做完一步j++)。(这里i-1-j 减掉的 1 代表是根节点占了一个位置)

当节点个数为0时有一种形态的树(也就是空树吧),当节点个数为1时有一种形态的树,之后就可以向下继续计算节点为2,3,4,5,……n。

class Solution {
    public int numTrees(int n) {
    int dp[]=new int[n+1];
        dp[0]=1;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            for(int j=0;j<i;j++){
                dp[i]+=dp[j]*dp[i-1-j];
            }
        }
        
        return dp[n];
    }
}

Leetcode486预测赢家

Leetcode486. Predict the Winner

题意:

从一个数组中,玩家1先从数组头/尾拿一个数。拿了之后玩家2再剩下的数组中的头/尾拿一个数。以此往复,直到所有数拿完了。判断玩家1赢的条件是,玩家1拿的所有的数的和大于玩家2拿的所有数的和。

Example 1:

Input: [1, 5, 2]
Output: False
Explanation: Initially, player 1 can choose between 1 and 2. If he chooses 2 (or 1), then player 2 can choose from 1 (or 2) and 5. If player 2 chooses 5, then player 1 will be left with 1 (or 2). So, final score of player 1 is 1 + 2 = 3, and player 2 is 5. Hence, player 1 will never be the winner and you need to return False.

Example 2:

Input: [1, 5, 233, 7]
Output: True
Explanation: Player 1 first chooses 1. Then player 2 have to choose between 5 and 7. No matter which number player 2 choose, player 1 can choose 233.Finally, player 1 has more score (234) than player 2 (12), so you need to return True representing player1 can win.

解:

dp(left,right)表示从left~right的序列中玩家1拿的数的和对玩家2的数的和的差。

因此,每一次拿头还是拿尾,取决与谁的差值更大。

class Solution {
    public boolean PredictTheWinner(int[] nums) {
        int n=nums.length;
        int [][]dp=new int[n][n];
        dp[n-1][n-1]=nums[n-1];
        for(int left=n-2;left>=0;left--){
            for(int right=left;right<n;right++){
                if(left==right)
                    dp[left][right]=nums[left];
                else
                    dp[left][right]=Math.max(nums[left]-dp[left+1][right],nums[right]-dp[left][right-1]);
            }
        }
        return dp[0][n-1]>=0?true:false;
    }
}

Leetcode650. 2 Keys Keyboard

题意:

初始给一个A,若要想得到n个A最少需要多少步

只有两个键:

复制,只能全部复制当前有的A

粘贴:粘贴到当前有的A的后面。

Input: 3
Output: 3
Explanation:
Intitally, we have one character 'A'.
In step 1, we use Copy All operation.
In step 2, we use Paste operation to get 'AA'.
In step 3, we use Paste operation to get 'AAA'.

DP:

class Solution {
    public int minSteps(int n) {
        int[] dp=new int[n+1];
        if(n==0||n==1)
            return 0;
        dp[1]=1;
        for(int i=1;i<=n;i++){
            dp[i]=i;
            for(int j=i-1;j>1;j--){
                if(i%j==0){
                    dp[i]=Math.min(dp[i],dp[j]+(i-j)/j+1);
                    break;
                }
            }
        }
        return dp[n];
    }
}

Leetcode338. Counting Bits计算二进制中1的个数

题目给一个数字num.求0<=i<=num的每个i的二进制的位数。

分析:

整数只有奇数和偶数

偶数 dp[i]=dp[i/2];因为2*n 这样一个数 那么 2n的二进制中知识在n的二进制多加了一个0,1的个数没变

容易知:奇数dp[i]=dp[i-1]+1//或者是dp[i]=dp[i/2]+1

class Solution {
    public int[] countBits(int num) {
        int []dp=new int[num+1];
        for(int i=1;i<=num;i++){
            dp[i]=dp[i/2];
            if(i%2==1){
                dp[i]++;
        }
    }
    return dp;       
}
}

Leetcode714 股票买卖最佳时机II

题意:卖股票前必须得有股票,需要交手续费。手中持有股票为0,才能买股票。

Input: prices = [1, 3, 2, 8, 4, 9], fee = 2
Output: 8
Explanation: The maximum profit can be achieved by:
Buying at prices[0] = 1Selling at prices[3] = 8Buying at prices[4] = 4Selling at prices[5] = 9The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8.

dp(0,i)表示第0~i天的没有股票的最大收益

没有股票如何比较最大收益:

1)把之前i-1持有的股票在第i天卖出

2)持续之前i-1没有股票状态

比较以上二者哪个的钱多

dp(1,i)表示第0~i天的持有股票的最大收益

持有股票如何比较最大收益:

1)持续之前i-1有股票的状态

2)之前i-1没有股票状态剩的钱在第i天买股票

比较以上二者哪个的钱多。

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int n=prices.length;
        int [][]dp=new int[2][n];
        dp[0][0]=0;
        dp[1][0]=-prices[0];
        for(int i=1;i<n;i++){
            dp[0][i]=Math.max(dp[0][i-1],prices[i]+dp[1][i-1]-fee);
            dp[1][i]=Math.max(dp[1][i-1],dp[0][i-1]-prices[i]);
        }
        return Math.max(dp[0][n-1],dp[1][n-1]);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值