LeetCode算法练习——动态规划提高(一)

LeetCode5. 最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。例如对于字符串 “ababa”,如果我们已经知道 “bab”是回文串,那么 “ababa” 一定是回文串,这是因为它的首尾两个字母都是 “a”。

根据这样的思路,我们就可以用动态规划的方法解决本题。我们用 P(i, j)表示字符串 s 的第 i到 j个字母组成的串是否为回文串:

  • P(i, j) = true,如果子串 Si…Sj 是回文串
  • P(i, j) = false,其他情况

这里的「其它情况」包含两种可能性:

  • s[i, j] 本身不是一个回文串;
  • i > j,此时 s[i, j]本身不合法。

可以写出动态规划的状态转移方程:P(i, j)=P(i + 1,j − 1) ∧ (Si​ == Sj​)。也就是说,只有 s[i + 1 : j − 1]是回文串,并且 s 的第 i 和 j个字母相同时,s[i: j]才会是回文串。

之前的所有讨论是建立在子串长度大于 2 的前提之上的,我们还需要考虑动态规划中的边界条件,即子串的长度为 1 或 2。对于长度为 1 的子串,它显然是个回文串;对于长度为 2 的子串,只要它的两个字母相同,它就是一个回文串。因此我们就可以写出动态规划的边界条件:

  • P(i, i) = true
  • P(i, i + 1) = (Si ​== Si + 1​)​

根据这个思路,就可以完成动态规划了,最终的答案即为所有 P(i, j) = true 中 j − i + 1(即子串长度)的最大值。注意:在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。

class Solution {
public:
    //dp[i][j]存放的是[i,,,,j]是否是回文区间
    //dp[i][j]=dp[i+1][j-1]&&(s[i]==s[j])去除两端之间的子串是回文子串,且两端相等
    string longestPalindrome(string s) {
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
        int length = s.size();
        int maxLen = 0;
        string res;
        for(int k = 0; k < length; k++){
            for(int i = 0; i + k < length; i++){
                int j = i + k;
                if(k == 0)  dp[i][j] = true;                    //一个字母组成的是回文
                if((s[i] == s[j]) && (j - i <= 2 || dp[i + 1][j - 1])){
                    dp[i][j] = true;
                    if(j - i + 1 > maxLen){
                        res = s.substr(i, j - i + 1);
                        maxLen = j - i + 1;
                    }
                }  
            }
        }
        return res;
    }
};

 相似的题目还有LeetCode647. 回文子串,该题是统计回文子串的个数,做法类似,只不过当判定为回文子串时,计数器加1,去掉寻找最大子串的条件即可。

LeetCode322. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

示例 2:

输入: coins = [2], amount = 3
输出: -1

思路:

注意到这个问题有一个最优的子结构性质,这是解决动态规划问题的关键。最优解可以从其子问题的最优解构造出来。如何将问题分解成子问题?假设我们知道 F(S) ,即组成金额 S 最少的硬币数,最后一枚硬币的面值是 C。那么由于问题的最优子结构,转移方程应为:F(S) = F(S - C) + 1

但我们不知道最后一枚硬币的面值是多少,所以我们需要枚举每个硬币面额值,即遍历coins数组并选择其中的最小值。下列递推关系成立:

  •  F(S) = min_{_{i = 0,...,n-1}} F(S - C^{_{i}}) + 1 此时 S - C^{_{i}}\geq 0
  • F(S) = 0, S=0
  • F(S) = -1, n=0
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 - coins[i]] + 1, dp[j]);
            }
        }
        if(dp[amount] == INT_MAX)   return -1;
        return dp[amount];
    }
};

LeetCode518. 零钱兑换 II

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 1:

输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。

示例 3:

输入: amount = 10, coins = [10] 
输出: 1

此题沿用上一题的思想,只是不需要标记最值,状态转移方程为:dp[j] += dp[j - coins[i]];此题还可以用二维dp进行解决:

  • 状态:硬币,金额
  • 选择:当前硬币选或不选
  • dp数组含义:dp[i][j]表示前i个硬币可以达到j金额的组合数
  • 方程:dp[i][j]=dp[i−1][j]+dp[i][j−coins[i−1]](就是不选和选相加,注意选时的硬币下标仍为i,因为硬币个数无限,这个硬币上次仍然可以选)
  • base case:dp[i][0]=1,dp[0][j]=0,j≠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];
    }
    //二维dp
    /*int change(int amount, vector<int>& coins) {
        int n = coins.size();
        vector<vector<int>> dp(n + 1, vector<int>(amount + 1));
        //base case:
        for(int i = 0; i <= n; i++) {
            dp[i][0] = 1;
        }
        for(int i = 1; i <= amount; i++) {
            dp[0][i] = 0;
        }
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= amount; j++) {
                if(j - coins[i - 1] >= 0) 
                    dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]]; 
                    //完全背包,选的情况仍为i
                else dp[i][j] = dp[i - 1][j];
            }
        }
        return dp[n][amount];
    }*/
};

相似的题目还有LeetCode279. 完全平方数,只不过将coins数组里面的内容全部换成了完全平方数。

LeetCode343. 整数拆分 && 剑指 Offer 14- I. 剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。

示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

建立一维动态数组 dp:

  •     边界条件:dp[1] = dp[2] = 1,表示长度为 2 的绳子最大乘积为 1;
  •     状态转移方程:dp[i] = max(dp[i], max((i - j) * j, j * dp[i - j])),以下为LeetCode一题解给出的示意图:

class Solution {
public:
    int cuttingRope(int n) {
        vector<int> dp(n + 1,0);
        dp[1] = 1, dp[2] = 1;
        for(int i = 3; i <= n; i++){
            for(int j = 1; j < i; j++)
                dp[i] = max(dp[i], max((i - j) * j, j * dp[i - j])); 
        }
        return dp[n];
    }
};

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值