动态规划问题题集

本文作者分享了在LeetCode刷题过程中遇到的动态规划问题及其解决方案,包括01背包问题、目标和、最后一块石头的重量、盈利计划等经典题目。通过题意转化和状态转移方程,详细解释了如何构建动态规划模型并优化解法,如滚动数组减少空间复杂度。文章旨在帮助读者提升动态规划问题的解决能力。
摘要由CSDN通过智能技术生成

动态规划问题是算法里一个比较难的部分了,每次遇到动态规划的问题都感觉很不好解决。目前的认知是读到一个题我知道应当用动态规划的方式来解决,但要上手去写总感觉无从下手。这一篇文章用来记录我在LeetCode刷题时遇到的动态规划问题及解答的方法。

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        //可以视作背包问题来求解
        //一般的背包问题,需要考虑物品的价值、重量
        //对于此题,子集的数目即总的价值,0/1都代表重量(两个维度)
        //dp[i][j] 含义: 最多有i个0和j个1的strs的最⼤⼦集的⼤⼩为dp[i][j]
        int[][]dp=new int[m+1][n+1];
        for(String s:strs){
            int zeros=0;
            int ones=0;
            for(char c:s.toCharArray()){
                if(c=='0') zeros++;
                else ones++;
            }
        //从后向前填表是为了上一轮的结果不被覆盖
        //从可用的部分去减该字符串包含的
        //如果选当前字符串dp[i][j]=dp[i-zeros][j-ones]+1
        //如果不选当前字符串dp[i][j]=dp[i][j]
        for(int i=m;i>=zeros;i--){
            for(int j=n;j>=ones;j--){
            	//状态转移方程
                dp[i][j]=Math.max(dp[i][j],dp[i-zeros][j-ones]+1);
                }
            }
        }
        return dp[m][n];
    }
}

其中还包含了一个dp常用知识点,滚动数组,把本来三维的dp数组降到了二维。官方的三维解法是dp[i][j][k]代表前 i 个字符串中,使用 j 个 0 和 k 个 1 的情况下最多可以得到的字符串数量。

class Solution {
     int count=0;
    public int findTargetSumWays(int[] nums, int target) {
        backTrace(nums,target,0,0);
        return count;
    }
    //对每个数组加符号有两种 正或负
    public void backTrace(int[] nums,int target,int index,int sum){
        if(index==nums.length){
             if(sum==target){
                count++;
                }
        }
        else{
            backTrace(nums,target,index+1,sum+nums[index]);
            backTrace(nums,target,index+1,sum-nums[index]);
        }
    }
}

动态规划方法:首先分析题意,对题目所求进行转化。记数组的元素和为sum,添加-号的元素之和为neg,则其余添加+的元素之和为sum−neg,得到的表达式的结果为(sum-neg)-neg=target.即neg=(sum-target)/2.
则sum-targe必须为偶数,遇到非偶数的情况可以提前返回。
令dp[i][j]表示在前i个元素中进行选取,使元素和为j的方案数。

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum=0;
        for(int i:nums)
            sum+=i;
        int neg=sum-target;
        //不符合要求时提前退出
        if(neg<0||neg%2!=0) return 0;
        int n = nums.length;
        neg = neg / 2;
        int[][] dp = new int[n + 1][neg + 1];
        //不选取任何数字 使结果为0的方案有一种
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++) {
            int num = nums[i - 1];
            for (int j = 0; j <= neg; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= num) {
                    dp[i][j] += dp[i - 1][j - num];
                }
            }
        }
        return dp[n][neg];
    }
}
class Solution {
    public int lastStoneWeightII(int[] stones) {
        int n = stones.length;
        int sum = 0;
        for (int i : stones) sum += i;
        int t = sum / 2;
        int[][] dp = new int[n + 1][t + 1];
        for (int i = 1; i <= n; i++) {
            int x = stones[i - 1];
            for (int j = 0; j <= t; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= x) dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - x] + x);
            }
        }
        //这里需要减去两个dp[n][t]的道理类似于目标和这道题的neg
        return Math.abs(sum - dp[n][t] - dp[n][t]);
    }
}

还有另一个大佬写的解法看上去可能会清晰一些


class Solution {
    public int lastStoneWeightII(int[] stones) {
        int n = stones.length;
        int sum = 0;
        for(int num:stones){
            sum += num;
        }
        //背包容量上限为石头总重量的一半
        int dp[][] = new int[n+1][sum/2+1];
        for(int i=1;i<=n;i++){
            for(int j=0;j<=sum/2;j++){
                //当我能把这块石头放进背包时,我会比较放或不放,选择最大值
                if(j>=stones[i-1]){
                    dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-stones[i-1]] + stones[i-1]);
                } else {
                //这块石头放不进背包时,只能跳过
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        //dp[n][sum/2]的最大值为sum/2,因此最理想的结果为0
        return sum-dp[n][sum/2]*2;
    }
}

class Solution {
    public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
        int mod=(int)1e9+7;
        //总员工数n和minProfit是边界条件
        //仍然视作背包问题 最多装n个物品 保证其价值最小为minProfit (两个限制)
        // 前i个任务 选人最大为j 得到的最小利润k
        int[][][]dp=new int[group.length+1][n+1][minProfit+1];
        //任何工作都不选 则结果为0 做一个预处理
        for(int i=0;i<=n;i++)dp[0][i][0]=1;
        for(int i=1;i<=group.length;i++){
            int gro=group[i-1];
            int pro=profit[i-1];
            for(int j=0;j<=n;j++){
                for(int k=0;k<=minProfit;k++){
                    //不选
                    dp[i][j][k]=dp[i-1][j][k];
                    if(j>=gro){
                        int u=Math.max(k-pro,0);
                        dp[i][j][k]+=dp[i-1][j-gro][u];
                        dp[i][j][k]%=mod;
                    }

                }
            }
        }
        return dp[group.length][n][minProfit];

    }
}

class Solution {
    public int change(int amount, int[] coins) {
        //完全背包 每个物品不限制取用次数
        //不限制体积的情况下求能凑成amount的方案数
        //dp[i]表示金额为i情况下的方案数目
        int[] dp=new int[amount+1];
        //边界初始化
        dp[0]=1;
        for(int coin:coins){
            for(int i=0;i<=amount;i++){
                if(i>=coin)
                dp[i]=dp[i]+dp[i-coin];
            }
        }
        return dp[amount];
    }
}
class Solution {
    public int numSquares(int n) {
        //类似于完全背包 每个完全平方数不限制选用次数
        //dp[i]表示容量为i时的数字个数
        int[] dp=new int[n+1];
        for(int i=0;i<=n;i++)dp[i]=i;//最多由i个1组成当前数字
        for(int i=0;i<=n;i++){
            for(int j=1;j*j<=i;j++){
                dp[i]=Math.min(dp[i-j*j]+1,dp[i]);
            }
        }
        return dp[n];
    }
}

Leetcode139 单词拆分

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        //dp[i]表示字符串以i结尾时是否可拆分
        boolean [] dp=new boolean[s.length()+1];
        dp[0]=true;
        for(int i=1;i<=s.length();i++){
            for(int j=0;j<i;j++){
                if(dp[j]&&wordDict.contains(s.substring(j,i))){
                    dp[i]=true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }
}

【不定期更新】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值