额外题目 | DP篇

本文记录的是刷题过程中的重要概念和笔记。如有侵权,请联系删除。

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不能直接由前一状态推出可以尝试自身交叉的组合作为各个状态在这里插入图片描述

  1. dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。i未起点,j为终点
  2. 确定递推公式

当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。

  1. dp数组如何初始化:FALSE
  2. 顺序:一定要从下到上,从左到右遍历,这样保证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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值