本文记录的是刷题过程中的重要概念和笔记。如有侵权,请联系删除。
5.最长回文子串 *
力扣题目链接(opens new window)
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
示例 3:
输入:s = “a”
输出:“a”
示例 4:
输入:s = “ac”
输出:“a”
思路
暴力
DP
DP不能直接由前一状态推出可以尝试自身交叉的组合作为各个状态
- dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。i未起点,j为终点
- 确定递推公式
当s[i]与s[j]不相等,那没啥好说的了,dp[i][j]一定是false。 当s[i]与s[j]相等时,这就复杂一些了,有如下三种情况
情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串
情况二:下标i 与 j相差为1,例如aa,也是文子串
情况三:下标:i 与j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是i+1 与 j-1区间,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。
- dp数组如何初始化:FALSE
- 顺序:一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的。
class Solution {
public:
string longestPalindrome(string s) {
vector<vector<bool>> dp(s.size(),vector<bool>(s.size(),false));
// for(int i=0;i<s.size();i++) dp[i][i]=true; // 初始化
int begin=0,end=0,res=0;
for(int i=s.size()-1;i>=0;i--){ //起点
for(int j=i;j<s.size();j++){ // 终点
if(s[i]==s[j]){
if(j-i<=1) dp[i][j]=true;
else dp[i][j]=dp[i+1][j-1];
if(dp[i][j] && res<j-i+1){
begin = i;
end=j;
res=j-i+1;
}
}
}
}
return s.substr(begin,end-begin+1);
}
};
时间复杂度:O(n^2)
空间复杂度:O(n^2)
- s.substr(begin,end-begin+1)
双指针
首先确定回文串,就是找中心然后想两边扩散看是不是对称的就可以了。
在遍历中心点的时候,要注意中心点有两种情况。
一个元素可以作为中心点,两个元素也可以作为中心点。
class Solution {
public:
int left = 0;
int right = 0;
int maxLength = 0;
string longestPalindrome(string s) {
int result = 0;
for (int i = 0; i < s.size(); i++) {
extend(s, i, i, s.size()); // 以i为中心
extend(s, i, i + 1, s.size()); // 以i和i+1为中心
}
return s.substr(left, maxLength);
}
void extend(const string& s, int i, int j, int n) {
while (i >= 0 && j < n && s[i] == s[j]) {
if (j - i + 1 > maxLength) {
left = i;
right = j;
maxLength = j - i + 1;
}
i--;
j++;
}
}
};
时间复杂度:O(n^2)
空间复杂度:O(1)
132. 分割回文串 II *
力扣题目链接(opens new window)
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。
返回符合要求的 最少分割次数 。
示例 1:
输入:s = “aab” 输出:1 解释:只需一次分割就可将 s 分割成 [“aa”,“b”] 这样两个回文子串。
示例 2: 输入:s = “a” 输出:0
示例 3: 输入:s = “ab” 输出:1
提示:
1 <= s.length <= 2000
s 仅由小写英文字母组成
思路
两重DP,第一重是上面的,不能直接吧=把上面的bool改成int记录最小切割次数,因为不能确定切割后要从哪开始作为前一个状态。
DP1
前一题
DP2
dp[i]:范围是[0, i]的回文子串,最少分割次数是dp[i]。
区间[j + 1, i]是回文子串,那么dp[i] 就等于 dp[j] + 1
所以最后递推公式为:dp[i] = min(dp[i], dp[j] + 1);
注意这里不是要 dp[j] + 1 和 dp[i]去比较,而是要在遍历j的过程中取最小的dp[i]!
dp[0]一定是0,长度为1的字符串最小分割次数就是0
非零下标的dp[i]初始化为一个最大数。INT_MAX可能会越界,可以选择s.size()。
根据递推公式:dp[i] = min(dp[i], dp[j] + 1);
j是在[0,i]之间,所以遍历i的for循环一定在外层,这里遍历j的for循环在内层才能通过 计算过的dp[j]数值推导出dp[i]。
class Solution {
public:
int minCut(string s) {
vector<vector<bool>> dp(s.size(),vector<bool>(s.size(),false));
int begin=0,end=0,res=0;
for(int i=s.size()-1;i>=0;i--){ //起点
for(int j=i;j<s.size();j++){ // 终点
if(s[i]==s[j]){
if(j-i<=1) dp[i][j]=true;
else dp[i][j]=dp[i+1][j-1];
}
}
}
vector<int> dp2(s.size(),s.size()); // 初始化的值为s.size(),也是可能的最大结果
for(int i=0;i<s.size();i++){
if(dp[0][i]){ // 如果本身可以是回文串,直接赋值为0,否则无论如何都最小是1
dp2[i]=0;
continue;
}
for(int j=0;j<i;j++){
if(dp[j+1][i]){ // j开始的第一个数
dp2[i]=min(dp2[i],dp2[j]+1);
}
}
}
return dp2.back();
}
};
673.最长递增子序列的个数
力扣题目链接(opens new window)
给定一个未排序的整数数组,找到最长递增子序列的个数。
示例 1:
输入: [1,3,5,4,7]
输出: 2
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。
示例 2:
输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。
思路
dp[i]:i之前(包括i)最长递增子序列的长度
count[i]:以nums[i]为结尾的字符串,最长递增子序列的个数为count[i]
class Solution {
public:
int findNumberOfLIS(vector<int>& nums) {
vector<vector<int>> record(nums.size(),vector(2,1));
if(nums.size()<=1) return nums.size();
// record[0][0]=1; // 长度
// record[0][1]=1; // 组数
for(int i=1;i<nums.size();i++){
for(int j=0;j<i;j++){
if(nums[i]>nums[j]){
if(record[i][0]<record[j][0]+1){
record[i][0]=record[j][0]+1;
record[i][1]=record[j][1];
} else if(record[i][0]==record[j][0]+1){
record[i][1]+=record[j][1];
}
}
}
}
int max=1,count=0;
for(int i=0;i<nums.size();i++){
if(max<record[i][0]){
max=record[i][0];
count=record[i][1];
}else if(max==record[i][0]){
count+=record[i][1];
}
}
return count;
}
};