codetop标签动态规划大全C++讲解(一)!!动态规划刷穿地心!!学吐了家人们o(╥﹏╥)o

1.零钱兑换

给你 k 种面值的硬币,面值分别为 c1, c2 … ck,每种硬币的数量无限,再给一个总金额 amount,问你最少需要几枚硬币凑出这个金额,如果不可能凑出,算法返回 -1 。

dp[n]的意义为凑成总金额n,最少需要dp[n]枚硬币
amount怎么来的?撤回一个硬币+1来的,dp[i] = min(dp[i], dp[i - coin] + 1),其中是coin的原因是,到底撤回面值多少的硬币,还需要穷举
另外,要取min所以dp应该初始化为一个较大值,不能是INT_MAX,因为递推式是dp+1,会溢出

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, amount + 1);
        dp[0] = 0;
        for(int i = 0; i <= amount; i ++){
            for(int coin : coins){
                if(i - coin < 0) continue;
                dp[i] = min(dp[i], dp[i - coin] + 1);
            }
        }
        return dp[amount] == amount + 1 ? -1 : dp[amount];
    }
};

还有一种视作完全背包的写法,一样的初始化和递推,将硬币视为物品,面值视为重量和价值,背包容量是amount
其中dp[j - coins[i]] != INT_MAX必不可少,因为如果其为INT_MAX的话,后序计算dp会+1,就会溢出

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        for(int i = 0; i < coins.size(); i ++){//遍历物品
            for(int j = coins[i]; j <= amount; j ++){//遍历背包
                if(dp[j - coins[i]] != INT_MAX) dp[j] = min(dp[j], dp[j - coins[i]] + 1);
            }
        }
        if(dp[amount] == INT_MAX) return -1;
        return dp[amount];
    }
};

2.零钱兑换II

和零钱兑换一样,不一样的是这里要求返回硬币组合数。
可以视作完全背包做

如果求组合数就是外层for循环遍历物品,内层for遍历背包
如果求排列数就是外层for遍历背包,内层for循环遍历物品

这个题求的是组合,所以外层遍历背包,内存遍历物品。求组合数量的递推公式:dp[j]+=dp[j - coins[i]]
从这个递推式来看,初始化一定要有不为0的
dp[0] = 1,其他初始化为0

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount + 1, 0);
        dp[0] = 1;
        for(int i = 0; i < coins.size(); i ++){
            for(int j = coins[i]; j <= amount; j ++){
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }
};

3.面试题08.11.硬币

给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)

dp[i]的含义是i分有几种表示方法,由于5,1和1,5不用特别区分,所以求的是组合数,外层遍历物品,内层遍历背包容量

当前硬币有取或者不取,取得话就是dp[i - coin],不

class Solution {
public:
    const int mod = 1e9 + 7;
    int waysToChange(int n) {
        vector<int> dp(n + 1, 0);
        vector<int> coins = {1, 5, 10, 25};
        dp[0] = 1;
        for (int coin : coins) {
            for (int i = coin; i <= n; i++) {
                dp[i] = (dp[i] + dp[i - coin]) % mod;
            }
        }
        return dp[n];
    }
};

4.单词拆分

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
在这里插入图片描述
单词就是物品,字符串就是背包,因为物品可以重复填背包,所以这是个完全背包问题!
dp[i]:表示下标为0~i - 1的字符串可以被拆分为一个或多个在字典中出现的单词。
如果dp[j] == true且[j,i)出现在字典中了,那么dp[i] 也为 true,这句话记住基本就做出来了
本题求的是排列数,因为applepen是apple+pen的组合而不是pen+apple的组合

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        vector<bool> dp(s.size(), false);
        unordered_set<string> word(wordDict.begin(), wordDict.end());
        dp[0] = true;
        for(int i = 1; i <= s.size(); i ++){
            for(int j = 0; j < i; j ++){
                if(dp[j] && word.find(s.substr(j, i - j)) != word.end()) dp[i] = true;
            }
        }
        return dp[s.size()];
    }
};

5.最长递增子序列

输入一个无序的整数数组,请你找到其中最长的严格递增子序列的长度。
注意「子序列」和「子串」这两个名词的区别,子串一定是连续的,而子序列不一定是连续的。

dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度
那么base case就是dp[0] = 1
以下是完整代码,其中res初始化为 1 至关重要。
如果 res 初始化为 0,而数组是递减的,那么 res 将永远保持 0,因为没有任何 dp[j] 会大于 1,所以内部循环的条件 if(res < dp[j]) 永远不会满足。

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        //dp[i]是到nums[i]截止的
        vector<int> dp(nums.size() + 1, 1);
        int result = 1;
        for(int i = 1; i < nums.size(); i ++){
            for(int j = 0; j < i; j ++){
                if(nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
                if(result < dp[i]) result = dp[i];
            }
        }
        return result;
    }
};

6.最长递增子序列的个数

给定一个未排序的整数数组 nums , 返回最长递增子序列的个数 。
注意 这个数列必须是 严格 递增的。

dp[i]:最长递增序列的长度
cnt[i]:最长递增序列的个数
设nums的最长上升子序列的长度为maxLen,那么答案为所有满足dp[i] = maxLen的 i 对应的cnt[i]之和
我们从小到大计算 dp数组的值,在计算 dp[i]之前,我们已经计算出 dp[0…i−1]的值,则状态转移方程为:
dp[i]=max⁡(dp[j])+1,其中 0≤j<i 且 num[j]<num[i]
即考虑往 dp[0…i−1]中最长的上升子序列后面再加一个 nums[i]。
由于 dp[j] 代表 nums[0…j] 中以 nums[j] 结尾的最长上升子序列,所以如果能从 dp[j] 这个状态转移过来,那么 nums[i] 必然要大于 nums[j],才能将 nums[i] 放在 nums[j] 后面以形成更长的上升子序列。

对于 cnt[i],其等于所有满足 dp[j] + 1 = dp[i] 的 cnt[j]之和。在代码实现时,我们可以在计算 dp[i]的同时统计 cnt[i]的值。

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n), cnt(n);
        int ans = 0, maxLen = 0;
        for(int i = 0; i < n; ++i) {
            dp[i] = 1; // 初始化为1
            cnt[i] = 1; // 初始化为1
            for(int j = 0; j < i; ++j) {
                if(nums[i] > nums[j]) {
                    if(dp[j] + 1 > dp[i]) {  //相当于 dp[i] = max(dp[j]+1, dp[i]);
                        dp[i] = dp[j] + 1;
                        cnt[i] = cnt[j];
                    }else if(dp[j] + 1 == dp[i]){ //dp[j]+1 == dp[i] 有相同的长度
                        cnt[i] += cnt[j];
                    }
                }
            }
            if(dp[i] > maxLen) {
                maxLen = dp[i];
                ans = cnt[i];
            } else if(dp[i] == maxLen) {
                 ans += cnt[i];
            }
        }
        cout << ans << endl;
        return ans;
    }
};

7.得到山形数组的最少删除次数

我们定义 arr 是 山形数组 当且仅当它满足:

  • arr.length >= 3
  • 存在某个下标 i (从 0 开始) 满足 0 < i < arr.length - 1 且:

arr[0] < arr[1] < … < arr[i - 1] < arr[i]
arr[i] > arr[i + 1] > … > arr[arr.length - 1]

给你整数数组 nums​ ,请你返回将 nums 变成 山形状数组 的​ 最少 删除次数。

可以两部分处理这个问题:

  1. 寻找所有可能的递增序列的最长长度。
  2. 寻找所有可能的递减序列的最长长度。

1可以使用类似于最长递增子序列(LIS)的算法来实现。
2可以使用类似于最长递减子序列(LDS)的算法来实现。
对于每个位置 i,可以计算它作为山峰的情况下的最长山形子序列的长度,并求取最大的长度。然后,从总长度中减去这个最大长度就是最少删除次数。

class Solution {
public:
    int minimumMountainRemovals(vector<int>& nums) {
        int n = nums.size();
        if(n < 3) return 0;
        //记录上升子序列
        vector<int> dp(n, 1);
        for(int j = 1; j < n; j ++){
            for(int i = 0; i < j; i ++){
                if(nums[j] > nums[i]) dp[j] = max(dp[j], dp[i] + 1);
            }
        }
        //记录下降子序列
        vector<int> dp2(n, 1);
        for(int j = n - 2; j >= 0; j --){
            for(int i = n - 1; i > j; i --){
                if(nums[j] > nums[i]) dp2[j] = max(dp2[j], dp2[i] + 1);
            }
        }
        //找到了最长的山行子序列的长度
        int maxLen = 0;
        //以中心为基准
        for(int i = 1; i < n - 1; i ++){
            if(dp[i] > 1 && dp2[i] > 1) maxLen = max(maxLen, dp[i] + dp2[i] - 1);
        }
        return n - maxLen;
    }
};

8.最长公共子序列

text1和text2,返回最长处公共子序列的长度

dp[i][j]表示text1下标0~i-1和text2下标0 ~ j-1最长公共子序列的长度
所以dp的长度需要是len+1
text1[i - 1] == text2[j - 1],dp[i][j] = dp[i - 1][j - 1] + 1
text1[i - 1] != text2[j - 1],dp[i][j] = max(dp[i][j - 1],dp[i - 1][j])

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int len1 = text1.size();
        int len2 = text2.size();
        vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
        int res = 0;
        for(int i = 1; i <= len1; i ++){
            for(int j = 1; j <= len2; j ++){
                if(text1[i - 1] == text2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);

                if(res < dp[i][j]) res = dp[i][j];
            }
        }
        return res;

    }
};

9.最长重复子数组

给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。

dp[i][j] 的含义是,nums1以下标i - 1结尾的子数组和nums2以下标j - 1结尾的子数组重复的最长子数组长度

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        int len1 = nums1.size();
        int len2 = nums2.size();
        int result = 0;
        vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
        for(int i = 1; i <= len1; i ++){
            for(int j = 1; j <= len2; j ++){
                if(nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
                if(result < dp[i][j]) result = dp[i][j];
            }
        }
        return result;
    }
};

10.最长等差数列

给你一个整数数组 nums,返回 nums 中最长等差子序列的长度。

dp[i][j]:以nums[i]结尾的子序列中,差值是j的最长等差数列长度
nums[i] - nums[j] = diff
dp[i][diff] = dp[j][diff] + 1
res实时变化

另外,由于0 <= nums[i] <= 500,所以diff在-500到500之间,又由于索引不可为负数,所以diff应该加500,变成0到1000间

class Solution {
public:
    int longestArithSeqLength(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> dp(n, vector<int>(1001, 1));
        int res = dp[0][0];
        for(int i = 0; i < n; i ++){
            for(int j = 0; j < i; j ++){
                int diff = nums[i] - nums[j] + 500;
                dp[i][diff] = dp[j][diff] + 1;

                res = max(res, dp[i][diff]);
            }
        }
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值