动态规划(含0-1背包)

矩阵路径

64 最小路径和

https://leetcode-cn.com/problems/minimum-path-sum/

1处先判断j==0是因为如果判断先判断i==0时若j也为0,在dp[j]=dp[j-1];会出现数组越界。

class Solution {
    public int minPathSum(int[][] grid) {
        if(grid==null || grid.length==0)
            return 0;
        int m=grid.length;
        int n=grid[0].length;
        int dp[]=new int[n];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(j==0){//1
                    dp[j]=dp[j];
                }else if(i==0){
                    dp[j]=dp[j-1];
                }else{
                    dp[j]=Math.min(dp[j-1],dp[j]);
                }
                dp[j]=dp[j]+grid[i][j];
            }
        }
        return dp[n-1];
    }
}

62 不同路径

https://leetcode-cn.com/problems/unique-paths/
方法一:此处从1开始是因为当i为0或当j为0时为1

class Solution {
    public int uniquePaths(int m, int n) {
        int dp[]=new int[n]; Arrays.fill(dp, 1);
        Arrays.fill(dp, 1);
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                dp[j]=dp[j]+dp[j-1];
            }
        }
        return dp[n-1];
    }
}

方法二:可以看做是m+n-2次中选择m-1次往下。

class Solution {
    public int uniquePaths(int m, int n) {
        int S=m+n-2;
        int D=m-1;
        long result=1;
        for(int i=1;i<=D;i++){
            result=result*(S-D+i)/i;
        }
        return (int)result;
    }
}

数组区间

303 区域和检索 - 数组不可变

https://leetcode-cn.com/problems/range-sum-query-immutable/

class NumArray {
    int sums[];

    public NumArray(int[] nums) {
        sums=new int[nums.length+1];
        for(int i=0;i<nums.length;i++){
            sums[i+1]=sums[i]+nums[i];
        }
    }
    
    public int sumRange(int i, int j) {
        return sums[j+1]-sums[i];
    }
}

分割整数

343 整数拆分

https://leetcode-cn.com/problems/integer-break/
方法一:

class Solution {
    public int integerBreak(int n) {
        int dp[]=new int[n+1];
        dp[1]=1;
        for(int i=2;i<=n;i++){
            for(int j=1;j<=i-1;j++){
                dp[i]=Math.max(dp[i],Math.max(dp[i-j]*j,j*(i-j)));
            }
        }
        return dp[n];
    }
}

方法二:
根据题目设定的条件整数n的取值范围为: 2<=n<=58
分析一下:
当n=2时: n=1+1; result = 1*1=1
当n=3时: 可以拆分为: 1+2 或者 1+1+1,但是显然拆分为 1+2,所获得的乘积最大
当n=4时:可以拆分为: 1+3 或者 2+2,但是显然拆分为 2+2,所获得的乘积最大
当n=5时:可以拆分为:2+3,所获得乘积最大
当n=6时:可以拆分为:3+3,所获得乘积最大
当n=7时:可以拆分为:3+4,所获得乘积最大
当n=8时:可以拆分为:3+3+2,所获得乘积最大
当n=9时:可以拆分为:3+3+3,所获得乘积最大
当n=10时:可以拆分为:3+3+4,所获得乘积最大
通过观察上述内容,我们可以发现从n=5开始,拆分的结果都有数字3。
之后的数字,例如11,可以先拆出来1个3,然后再看余下的8如何拆分。

class Solution {
    public int integerBreak(int n) {
        if(n==1 || n==4)
            return n;
        if(n==2 || n==3)
            return n-1;
        int result=1;
        while(n>4){
            result*=3;
            n-=3;
        }
        return result*n;
    }
}

91. 解码方法

https://leetcode-cn.com/problems/decode-ways/

class Solution {
    public int numDecodings(String s) {
        if(s==null||s.length()==0||s.charAt(0)=='0')
            return 0;
        int dp[]=new int[s.length()+1];
        dp[0]= 1;
        for(int i=1;i<s.length();i++){
            int cur=s.charAt(i)-'0';
            int pre=s.charAt(i-1)-'0';
            if(cur==0){
                if(pre<1 || pre>2){
                    return 0;
                }
                dp[i]=i>=2?dp[i-2]:1;
            }else{
                if (pre==1 || pre==2 && cur<=6) {
                    dp[i] = i>=2?dp[i - 2]+dp[i-1]:2;
                }else{
                    dp[i]=dp[i-1];
                }                
            }
        }
        return dp[s.length()-1];
    }
}

最长递增子序列

300 最长上升子序列

https://leetcode-cn.com/problems/longest-increasing-subsequence/
tails[i]:以i结尾的最长递增子序列。

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums==null || nums.length==0){
            return 0;
        }
        int tails[]=new int[nums.length];
        tails[0]=nums[0];
        int len=1;
        for(int i=1;i<nums.length;i++){
            if(nums[i]>tails[len-1]){
                tails[len++]=nums[i];
            }else{
                int temp=findFirst(tails,len,nums[i]);
                tails[temp]=nums[i];
            }
        }
        return len;
    }
    
    //二分查找,第一个大于等于des的数组索引
    public int findFirst(int []nums,int len, int des){
        int l=0;
        int h=len-1;
        while(l<=h){
            int m=l+(h-l)/2;
            if(nums[m]>=des){
                h=m-1;
            }else{
                l=m+1;
            }
        }
        return l;
    }
}

376 摆动序列

https://leetcode-cn.com/problems/wiggle-subsequence/

class Solution {
    public int wiggleMaxLength(int[] nums) {
        if(nums==null || nums.length==0)
            return 0;
        int up=1;
        int down=1;
        for(int i=1;i<nums.length;i++){
            if(nums[i]-nums[i-1]>0){
                up=down+1;
            }else if(nums[i]-nums[i-1]<0){
                down=up+1;
            }
        }
        return Math.max(down,up);
    }
}

0-1 背包

416 分割等和子集

https://leetcode-cn.com/problems/partition-equal-subset-sum/
可以看成一个背包大小为 sum/2 的 0-1 背包问题。dp[0]=true;//初始条件

class Solution {
    public boolean canPartition(int[] nums) {
        if(nums==null || nums.length==0)
            return false;
        int sum=0;
        for(int i=0;i<nums.length;i++){
            sum+=nums[i];
        }
        if(sum%2!=0)
            return false;
        int des=sum/2;
        boolean dp[]=new boolean[des+1];
        dp[0]=true;//初始条件
        for(int num:nums){
            for(int i=des;i>=num;i--){//0-1 背包一个物品只能用一次,故从后往前,先计算dp[i]再计算 dp[i-num]
                dp[i]=dp[i] || dp[i-num];
            }
        }
        return dp[des];
    }
}

494 目标和

https://leetcode-cn.com/problems/target-sum/
该问题可以转换为 Subset Sum 问题,从而使用 0-1 背包的方法来求解。

可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:

  sum(P) - sum(N) = target
  sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)
  2 * sum(P) = target + sum(nums)
class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        if(nums==null || nums.length==0)
            return 0;
        int sum=0;
        for(int i=0;i<nums.length;i++){
            sum+=nums[i];
        }
        if (sum < S || (sum + S) % 2 == 1) {
            return 0;
        }
        int des=(S+sum)/2;
        int dp[]=new int[des+1];
        dp[0]=1;
        for(int num:nums){
            for(int i=des;i>=num;i--){
                dp[i]+=dp[i-num];
            }
        }
        return dp[des];
    }
}

474 一和零

https://leetcode-cn.com/problems/
这是一个多维费用的 0-1 背包问题,有两个背包大小,0 的数量和 1 的数量。

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        if(strs==null || strs.length==0)
            return 0;
        int dp[][]=new int[m+1][n+1];
        for(String str:strs){
            char ch[]=str.toCharArray();
            int count_0=0;
            int count_1=0;
            for(int i=0;i<ch.length;i++){
                if(ch[i]-'0'==0){
                    count_0++;
                }else if(ch[i]-'0'==1){
                    count_1++;
                }
            }
            for(int i=m;i>=count_0;i--){
                for(int j=n;j>=count_1;j--){
                    dp[i][j]=Math.max(dp[i][j],dp[i-count_0][j-count_1]+1);
                }
            }
        }
        return dp[m][n];
    }
}

322 零钱兑换

https://leetcode-cn.com/problems/coin-change/
方法一:因为硬币可以重复使用,因此这是一个完全背包问题。完全背包只需要将 0-1 背包中逆序遍历 dp 数组改为正序遍历即可。

class Solution {
    public int coinChange(int[] coins, int amount) {
        if(coins==null || coins.length==0 ){
            return -1;
        }
        if(amount==0)
            return 0;
        int dp[]=new int[amount+1];
        for(int coin:coins){
            for(int i=coin;i<=amount;i++){
                if(i==coin){
                    dp[i]=1;
                }else if(dp[i]==0 && dp[i-coin]!=0){
                    dp[i]=dp[i-coin]+1;
                }else if(dp[i-coin]!=0){
                    dp[i]=Math.min(dp[i],dp[i-coin]+1);
                }
            }
        }
        return dp[amount]==0?-1:dp[amount];
    }
}

方法二:dfs+剪枝,比dp效果好

class Solution {
    public int coinChange(int[] coins, int amount) {
        Arrays.sort(coins);
        recursion(coins, amount, 0, coins.length - 1);
        return minCount == Integer.MAX_VALUE ? -1 : minCount;
    }
    int minCount = Integer.MAX_VALUE;
    /**
     * 1、按金额从大到小,从多到少(排序,用余数一步到位)
     * 2、count + amount / coins[index] >= minCount 预判低于最优解,终止递归(可以返回最优解,不过提升有限,意义不大)
     * 3、能整除即可返回
     */
    void recursion(int[] coins, int amount, int count, int index) {
        if (index < 0 || count + amount / coins[index] >= minCount) return;
        if (amount % coins[index] == 0) {
            minCount = Math.min(minCount, count + amount / coins[index]);
            return;
        }
        for (int i = amount / coins[index]; i >= 0; i--) {
            recursion(coins, amount - i * coins[index], count + i, index - 1);
        }
    }
}

类似完全背包问题还有 518 零钱兑换 II

139 单词拆分

https://leetcode-cn.com/problems/word-break/
dict 中的单词没有使用次数的限制,因此这是一个完全背包问题。该问题涉及到字典中单词的使用顺序,因此可理解为涉及顺序的完全背包问题。

dp[i]表示字符串s的前i个字符能否拆分成wordDict

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        // 可以类比于背包问题
        int n = s.length();
        // memo[i] 表示 s 中以 i - 1 结尾的字符串是否可被 wordDict 拆分
        boolean[] memo = new boolean[n + 1];
        memo[0] = true;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < i; j++) {
                if (memo[j] && wordDict.contains(s.substring(j, i))) {
                    memo[i] = true;
                    break;
                }
            }
        }
        return memo[n];
    }
}

377 组合总和 Ⅳ

https://leetcode-cn.com/problems/combination-sum-iv/
顺序的完全背包。

class Solution {
    public int combinationSum4(int[] nums, int target) {
        if(nums==null||nums.length==0)
            return 0;
        int dp[]=new int[target+1];
        Arrays.sort(nums);
        dp[0]=1;
        for(int i=1;i<=target;i++){
            for(int num:nums){
                if(i>=num){
                    dp[i]=dp[i]+dp[i-num];
                }else{
                    break;
                }                
            }
        }
        return dp[target];
    }
}

股票交易

参考1

309 最佳买卖股票时机含冷冻期

https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/
在这里插入图片描述

class Solution {
    public int maxProfit(int[] prices) {
        if(prices==null || prices.length==0)
            return 0;
        int len=prices.length;
        int s0[]=new int[len];
        int s1[]=new int[len];
        int s2[]=new int[len];
        s0[0]=0;
        s1[0]=-prices[0];
        s2[0] = 0;
        for(int i=1;i<prices.length;i++){
            s0[i]=Math.max(s0[i-1],s2[i-1]);
            s1[i]=Math.max(s1[i-1],s0[i-1]-prices[i]);
            s2[i]=s1[i-1]+prices[i];
        }
        return Math.max(s0[len-1],s2[len-1]);
    }
}

714 买卖股票的最佳时机含手续费

https://leetcode-cn.com/problems/coin-change/
在这里插入图片描述

class Solution {
    public int maxProfit(int[] prices, int fee) {
        if(prices==null||prices.length==0)
            return 0;
        int len=prices.length;
        int s0[]=new int[len];
        int s1[]=new int[len];
        s0[0]=0;
        s1[0]=-prices[0];
        for(int i=1;i<len;i++){
            s0[i]=Math.max(s0[i-1],s1[i-1]+prices[i]-fee);
            s1[i]=Math.max(s1[i-1],s0[i-1]-prices[i]);
        }
        return Math.max(s0[len-1],s1[len-1]);
    }
}

字符串编码

583 两个字符串的删除操作

https://leetcode-cn.com/problems/delete-operation-for-two-strings/
可以转换为求两个字符串的最长公共子序列问题。

class Solution {
    public int minDistance(String word1, String word2) {
        
        int m=word1.length();
        int n=word2.length();
        int dp[][]=new int[m+1][n+1];
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(word1.charAt(i-1)==word2.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 m+n-2*dp[m][n];
    }
}

72 编辑距离

https://leetcode-cn.com/problems/edit-distance/
思想可参考:https://www.jianshu.com/p/a617d20162cf
假设序列S和T的长度分别为m和n, 两者的编辑距离表示为dp[m][n]. 则对序列进行操作时存在以下几种情况:

  • a, 当S和T的末尾字符相等时, 对末尾字符不需要进行上述定义操作中(亦即"编辑")的任何一个, 也就是不需要增加计数. 则满足条件: dp[m][n] = dp[m - 1][n - 1]
  • b, 当S和T的末尾字符不相等时, 则需要对两者之一的末尾进行编辑, 相应的计数会增加1.
  • b1, 对S的末尾进行修改, 以使之与T相等, 则此时dp[m][n] = dp[m - 1][n - 1] + 1
  • b2, 删除S末尾的元素, 使S与T相等, 则此时dp[m][n] = dp[m - 1][n] + 1
  • b3, 在S的末尾添加T的尾元素, 使S和T相等, 则此时S的长度变为m+1, 但是此时S和T的末尾元素已经相等, 只需要比较S的前m个元素与T的前n-1个元素, 所以满足dp[m][n] = dp[m][n - 1] + 1;
public int minDistance(String word1, String word2) {
    if (word1 == null || word2 == null) {
        return 0;
    }
    int m = word1.length(), n = word2.length();
    int[][] dp = new int[m + 1][n + 1];
    for (int i = 1; i <= m; i++) {
        dp[i][0] = i;
    }
    for (int i = 1; i <= n; i++) {
        dp[0][i] = i;
    }
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j])) + 1;
            }
        }
    }
    return dp[m][n];
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值