代码随想录算法训练营day41

0 阅读7分钟

300.最长递增子序列 难点解析: 1.明确dp数组的具体意义,包括下标和存储内容,这里的下标指遍历到的数组下标,存储内容为以该下标为结尾的最长子序列长度 2.确定递推迭代公式,注意,这里是需要通过比较取较大值的 3.在充分理解1的情况下,设计result记录最长递增子序列。 4.注意j的迭代循环范围是不能超过i的

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        //采用动态规划方式进行递推迭代
        //剪支
        if(nums.size()==0) return 0;
        //设计dp数组,并初始化,这里注意,数组元素均应当初始化为1;
        vector<int> dp(nums.size(),1);//下标i为遍历到的数组长度,内容存储子序列长度
         //以 nums [i] 为最后一个元素的最长递增子序列长度(这是 LIS 动态规划的核心定义);
        int result=1;
        //进行双循环迭代,外层循环为整数数组,内层循环为子序列长度i
        for(int i=1;i<nums.size();i++){
            for(int j=0;j<i;j++){//注意这里只能遍历到i-1!!!
                if(nums[j]<nums[i]) dp[i]=max(dp[i],dp[j]+1);
            }
            //存储最大子序列长度
            if(dp[i]>result) result=dp[i];
        }
        return result;
    }
};

674.最长连续递增序列 采用贪心算法最快

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        if(nums.size()<=1) return nums.size();
        int result=1;
        int tmp=1;
        for(int i=1;i<nums.size();i++){
            if(nums[i]>nums[i-1]){
                tmp++;
                if(tmp>result) result=tmp;
            }
            else
            tmp=1;
        }
        return result;
    }
};

下面展示采用动态规划算法

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        if(nums.size()<=1) return nums.size();
        int result=1;
       //设计dp数组并初始化,均初始化为1
       vector<int> dp(nums.size(),1);
       //执行递推公式
       for(int i=1;i<nums.size();i++){
        if(nums[i]>nums[i-1]) dp[i]=dp[i-1]+1;
        if(result<dp[i]) result=dp[i];
       }
        return result;
    }
};

718.最长重复子数组 难点解析; 1.如何设计dp数组,注意这里有两个基础对比条件,分别是A,B数组,建议设计二维数组方便理解,同时注意为了简便运算,应当将存储内容设计为包括i-1,j-1的最长数组长度 2.边界问题,因为我们设计存储内容为i-1,j-1,所以我们开始循环应当从1开始,并且我们结束应当为nums1.size()和nums2.size(),所以我们的循环结束边界应当为nusm1.size();

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        //采用动态规划得出最长重复子数组
        //排除意外情况或者剪支处理
        if(nums1.size()==0||nums2.size()==0) return 0;
        //设计dp数组,考虑到为两个整数数组,需要两个下标表示具体含义,内容存储为i-1,j-1时的最长子序列长度
        vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
        //已经初始化为0;设计result存储结果
        int result=0;
        //双层循环遍历
        for(int i=1;i<=nums1.size();i++){//注意这里的边界必须到nums1.size(),因为我们设计的时nums1.size()规模的数组
            for(int j=1;j<=nums2.size();j++){
                if(nums1[i-1]==nums2[j-1]) dp[i][j]=dp[i-1][j-1]+1;//递推公式
                //存储最大长度
                if(result<dp[i][j]) result=dp[i][j];
            }
        }
        return result;
    }
};

滚动数组版本,注意,滚动数组的设计包括两个下标具备相同的变化时可以应用


class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
        vector<int> dp(vector<int>(B.size() + 1, 0));
        int result = 0;
        for (int i = 1; i <= A.size(); i++) {
            for (int j = B.size(); j > 0; j--) {
                if (A[i - 1] == B[j - 1]) {
                    dp[j] = dp[j - 1] + 1;
                } else dp[j] = 0; // 注意这里不相等的时候要有赋0的操作
                if (dp[j] > result) result = dp[j];
            }
        }
        return result;
    }
};

前面讲了 dp数组为什么定义:以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。

我就定义dp[i][j]为 以下标i为结尾的A,和以下标j 为结尾的B,最长重复子数组长度。不行么?

当然可以,就是实现起来麻烦一些。

如果定义 dp[i][j]为 以下标i为结尾的A,和以下标j 为结尾的B,那么 第一行和第一列毕竟要进行初始化,如果nums1[i] 与 nums2[0] 相同的话,对应的 dp[i][0]就要初始为1, 因为此时最长重复子数组为1。 nums2[j] 与 nums1[0]相同的话,同理。

1143.最长公共子序列,区别在于不相同时增加了不同取值判断

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        //采用动态规划
        //设计dp数组,并初始化为0,为了方便后序编写代码,dp[i][j]数组存储的时i-1,j-1长度的公共子序列
        vector<vector<int>> dp(text1.size()+1,vector<int>(text2.size()+1,0));
        for(int i=1;i<=text1.size();i++){
            for(int j=1;j<=text2.size();j++){
            //设计递推公式
            if(text1[i-1]==text2[j-1]) dp[i][j]=dp[i-1][j-1]+1;
            else dp[i][j]=max(dp[i][j-1],dp[i-1][j]);//取较大值
            }
        }
        return dp[text1.size()][text2.size()];
    }
};

1035.不相交的线 这里的难点在于理解题意:不相交的情况应当满足i,j之间的相对顺序不变,因此可以当作是求最大公共子序列长度

class Solution {
public:
    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
        for (int i = 1; i <= nums1.size(); i++) {
            for (int j = 1; j <= nums2.size(); j++) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[nums1.size()][nums2.size()];
    }
};

53.最大子序和 贪心算法

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        //贪心算法
        int result = INT32_MIN;
        int count = 0;
        for (int i = 0; i < nums.size(); i++) {
            count += nums[i];
            if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
                result = count;
            }
            if (count <= 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
        }
        return result;
        
    }
};

动态规划算法

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        //动态规划算法
       vector<int> dp(nums.size(),0);
       //初始化
       dp[0]=nums[0];
       int result=dp[0];
       //执行递推迭代
       for(int i=1;i<nums.size();i++){
        //求较大值
        dp[i]=max(dp[i-1]+nums[i],nums[i]);
        //记录比较最大结果
        if(result<dp[i]) result=dp[i];
       }
        return result;   
    }
};

392.判断子序列,与最长公共子序列没有什么特别大区别,甚至更加简单

class Solution {
public:
    bool isSubsequence(string s, string t) {
        //注意,这里基本等同于最长公共子序列变体,唯一区别在于并不需要相互取最大值比较
         vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
        for (int i = 1; i <= s.size(); i++) {
            for (int j = 1; j <= t.size(); j++) {
                if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = dp[i][j - 1];
            }
        }
        if (dp[s.size()][t.size()] == s.size()) return true;
        return false;
    }
};

115.不同的子序列 1.本题难点在于求子序列的个数,因此如何设计状态转移递推公式最重要 2.初始化必须慎重考虑。每次当初始化的时候,都要回顾一下dp[i][j]的定义,不要凭感觉初始化。

dp[i][0]表示什么呢?

dp[i][0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。

那么dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。

再来看dp[0][j],dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。

那么dp[0][j]一定都是0,s如论如何也变成不了t。

最后就要看一个特殊位置了,即:dp[0][0] 应该是多少。

dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t。

class Solution {
public:
    int numDistinct(string s, string t) {
        //采用动态规划的方式进行迭代,同时,题设使用的是字符,所以使用vector的时候应当使用unint64_t模板
        vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1));
        //在递推迭代公式的基础上,应当对最上行和最右列初始化
        for (int i = 0; i < s.size(); i++) dp[i][0] = 1;
        for (int j = 1; j < t.size(); j++) dp[0][j] = 0;
        for (int i = 1; i <= s.size(); i++) {
            for (int j = 1; j <= t.size(); j++) {
                if (s[i - 1] == t[j - 1]) {//注意状态转移公式的特殊性,这里求得是个数,当满足条件时,不仅是在原有匹配基础上增加,还有不匹配的增加
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[s.size()][t.size()];
        
    }
};