解决动态规划(Dynamic Programming,简称DP)题目的一般思路可以分为以下几个步骤:
给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
dp[i][j]
表示字符串s[i...j]
是否为回文子串s[i]
和s[j]
字符不相同,显然s[i...j]
不是回文串s[i]
和s[j]
字符相同i
和j
是相邻的下标,即当j-i==1
时,显然s[i...j]
是回文串i
和j
不是相邻的下标,则s[i...j]
是否是回文串取决于s[i+1...j-1]
是否是回文串则状态转移方程:
if (s[i] == s[j]) {
dp[i][j] = j - i == 1 ? true : dp[i + 1][j - 1];
} else {
dp[i][j] = false;
}
dp[i][i]
初始化为true
(0<=idp[i][j]
取决于dp[i+1][j-1]
,所以状态是从长度较短的字符串向长度较长的字符串进行转移start
变量表示最长回文子串的起始下标,一个maxLen
变量表示最长回文子串的长度,然后每次找到一个回文子串的时候,判断该串的长度是否大于当前最长回文子串的长度,如果更长,就更新这两个标记着最长回文子串的变量的信息。最后返回s.substr(start, maxLen)
即为一个最长回文子串。class Solution {
public:
string longestPalindrome(string s) {
if (s.size() <= 1) {
return s;
}
int n = s.size();
// 定义状态:dp[i][j]表示s[i...j]是否为回文串
vector<vector<bool>> dp(n, vector<bool>(n, false));
// 初始化
for (int i = 0; i < n; ++i) {
dp[i][i] = true;
}
int start = 0; // 记录最长回文子串的起始下标
int maxLen = 1; // 记录最长回文子串的长度(最少一个字符本身就是回文串)
for (int len = 2; len <= n; ++len) { // 注意需要从长度较短的字符串向长度较长的字符串进行转移
for (int i = 0; i < n; ++i) {
int j = len + i - 1;
if (j >= n) {
break;
}
// 状态转移方程
if (s[i] == s[j]) {
dp[i][j] = j - i == 1 ? true : dp[i + 1][j - 1];
} else {
dp[i][j] = false;
}
// 更新最长回文子串信息
if (dp[i][j] && j - i + 1 > maxLen) {
start = i;
maxLen = j - i + 1;
}
}
}
return s.substr(start, maxLen);
}
};
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例 3:
输入:s = ""
输出:0
提示:
0 <= s.length <= 3 * 104
s[i] 为 '(' 或 ')'
dp[i]
代表以第i
个字符结尾的最长有效括号长度(i从0开始到n-1),例如(()
,就有dp[0]=0,dp[1]=0,dp[2]=2
;s[i]
是左括号(
,显然dp[i]=0
,因为左括号是不可能作为一串有效括号的结尾字符【例如字符串((
】s[i]
是右括号)
,则根据前一个字符是左括号还是右括号,需要分情况讨论s[i-1]
是左括号(
,则dp[i]=dp[i-2]+2
(i>=2),【例如字符串(())()
,dp[5]=dp[3]+2=4+2=6
】s[i-1]
是右括号)
,首先找到以第i-1
个字符结尾的最长有效括号的前面一个字符,即s[i-dp[i-1]-1]
,(
,那么dp[i]=dp[i-1]+2+dp[i-dp[i-1]-2]
,【例如字符串()(())
,dp[5]=dp[4]+2+dp[1]=2+2+2=6
】)
,那么dp[i]=0
(构不成有效括号串了)【例如)())
是一个非有效的字符串】状态转移方程简写后为:
if (s[i] == ')') {
if (s[i - 1] == '(') {
dp[i] = i >= 2 ? dp[i - 2] + 2 : 2;
} else if (s[i - 1] == ')' && i - dp[i - 1] - 1 >= 0 && s[i - dp[i - 1] - 1] == '(') {
dp[i] = i - dp[i - 1] - 2 >= 0 ? dp[i - dp[i - 1] - 2] + dp[i - 1] + 2 : dp[i - 1] + 2;
}
}
dp[i]=0
i
等于1
开始遍历,因为至少两个字符才能组成有效括号,所以dp[0]
一定是0
dp[i]
的过程同时更新maxLen
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.size();
vector<int> dp(n, 0);
int maxLen = 0;
for (int i = 1; i < n; ++i) {
if (s[i] == '(') {
continue;
}
if (s[i - 1] == '(') {
dp[i] = i >= 2 ? dp[i - 2] + 2 : 2;
} else if (s[i - 1] == ')' && i - dp[i - 1] - 1 >= 0 && s[i - dp[i - 1] - 1] == '(') {
dp[i] = i - dp[i - 1] - 2 >= 0 ? dp[i - dp[i - 1] - 2] + dp[i - 1] + 2 : dp[i - 1] + 2;
}
maxLen = max(maxLen, dp[i]);
}
return maxLen;
}
};
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
dp[i]
表示以第i个数结尾的数组的最大和dp[i] = max(dp[i - 1] + nums[i], nums[i]); (i>=1)
如果第i
个数加上以第i-1
个数结尾的数组的最大和比第i
个数本身还要小,那还不如不加。dp[0]=nums[0]
i=1
开始遍历到n-1
maxSum
,初始值为nums[0]
,后面的每轮遍历中都更新maxSum
的大小class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
// dp[i]表示以第i个数结尾的数组的最大连续子数组的和
vector<int> dp(n, 0);
// 初始化
dp[0] = nums[0];
int maxSum = nums[0];
for (int i = 1; i < n; ++i) {
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
maxSum = max(maxSum, dp[i]);
}
return maxSum;
}
dp[i]
只会用到dp[i-1]
的数据,所以可以直接使用一个变量sum
替代就好。class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int sum = nums[0];
int maxSum = sum;
for (int i = 1; i < n; ++i) {
sum = max(sum + nums[i], nums[i]);
maxSum = max(maxSum, sum);
}
return maxSum;
}
};