给定一个字符串 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)=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, 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> dp(s.size(), vector(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,去掉寻找最大子串的条件即可。
给定不同面额的硬币 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。那么由于问题的最优子结构,转移方程应为:
但我们不知道最后一枚硬币的面值是多少,所以我们需要枚举每个硬币面额值,即遍历coins数组并选择其中的最小值。下列递推关系成立:
class Solution {
public:
int coinChange(vector& coins, int amount) {
vector 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];
}
};
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 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进行解决:
class Solution {
public:
int change(int amount, vector& coins) {
vector 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& coins) {
int n = coins.size();
vector> dp(n + 1, vector(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数组里面的内容全部换成了完全平方数。
给你一根长度为 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:
class Solution {
public:
int cuttingRope(int n) {
vector 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];
}
};