算法Day52 | 300.最长递增子序列,674. 最长连续递增序列,718. 最长重复子数组

Day52

    • 300.最长递增子序列
      • 题目的进阶内容
    • 674. 最长连续递增序列
    • 718. 最长重复子数组

300.最长递增子序列

题目链接:300.最长递增子序列
dp数组:以nums[i]为结尾的最长递增子序列的长度

递推公式
if (nums[j] < nums[i]) dp[i] = max(dp[i], dp[j] + 1) max是因为取的是最长子序列

初始化
dp[i] = 1,每个至少为1

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> dp(nums.size(), 1);
        int res = 1;//当nums.size == 1时,输出1
        for (int i = 1; i < nums.size(); ++i) {
            for (int j = 0; j < i; ++j) {
                if (nums[i] > nums[j])
                    dp[i] = max(dp[i], dp[j] + 1);
            }
            res = max(res, dp[i]);
        }
        return res;
    }
};

由于dp数组是满足 if (nums[i] > nums[j])条件时,才会进行递推。就是说dp数组不一定是单调递增的,dp.back()不是所求的最大值。因此需要额外的变量res来存储最大值。

题目的进阶内容

时间复杂度降低到 O ( n × log ⁡ n ) O(n \times \log n) O(n×logn)。看到 log ⁡ n \log n logn,想到二分法
通过修改j的遍历方式,将复杂度由 O ( n × n ) O(n\times n) O(n×n) 修改成 O ( n × log ⁡ n ) O(n \times \log n) O(n×logn)
通过二分法来维护一个数组tails

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> tails(nums.size(), 0);
        int res = 0;
        for (auto& num : nums) {
            int l = 0, r = res;//双指针
            while (l < r) {//通过二分法找出第一个大于等于num的位置l
                int m = (l + r) / 2;
                tails[m] < num ? l = m + 1 : r = m;
            }
            tails[l] = num;
            if (res == r)
                ++res;
        }
        return res;
    }
};

时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn) 的算法通常是指具有二分查找特性的算法。以下是一些常见的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn) 的算法:

二分查找:在有序数组中查找指定元素的位置。每次将数组分成两半,然后根据目标元素与中间元素的关系,在前半部分或后半部分继续查找。每次查找都将数据量减半,因此时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

二叉搜索树的查找操作:在二叉搜索树中查找指定元素的位置。每次比较目标元素与当前节点的值,根据大小关系向左或向右子树继续查找,每次查找都可以将数据量缩小一半,因此时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

堆排序中的堆操作:在堆排序算法中,建堆和调整堆都具有时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)的特性。堆是一种完全二叉树结构,根据堆的性质,可以通过比较父节点与子节点的值进行插入、删除和堆化等操作,每次操作都可以将数据量减半,因此时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)


674. 最长连续递增序列

题目链接:674. 最长连续递增序列
要求连续,只需比较nums[i]nums[i - 1]即可。
上一题不是连续,因此要比较num[i]nums[j]比较

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        vector<int> dp(nums.size(), 1);
        int res = 1;//当nums.size == 1时,输出1
        for (int i = 1; i < nums.size(); ++i) {
            if (nums[i] > nums[i - 1])
                dp[i] = dp[i - 1] + 1;
            res = max(res, dp[i]);
        }
        return res;
    }
};

718. 最长重复子数组

题目链接:718. 最长重复子数组

二维dp数组
i-1为结尾和j-1为结尾的最长重复子数组的长度为dp[i][j]
不使用i为结尾和j为结尾的最长重复子数组的长度为dp[i][j],是因为在初始化部分,会对dp[0][j]dp[i][0]通过遍历来赋值,增加代码量。

递推公式
if(nums[i - 1] == nums[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1

初始化
dp[i][0]和dp[0][j]无意义,因此dp[i][j]都初始化为零

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
        int res = 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;
                }
                res = max(res, dp[i][j]);
            }
        }
        return res;
    }
};

可以看到递推公式dp[i][j] = dp[i - 1][j - 1] + 1;,只用到了dp[i-1][j-1],而没用到dp[i][j-1]dp[i-1][j],因此可以压缩为一维数组,有

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<int> dp(nums2.size() + 1, 0);
        int res = 0;
        for (int i = 1; i <= nums1.size(); ++i) {
            for (int j = nums2.size(); j > 0; --j) {//从后往前遍历,防止重复
                if (nums1[i - 1] == nums2[j - 1]) {
                    dp[j] = dp[j - 1] + 1;
                } else {
                    dp[j] = 0;//恢复数组的值,以达到压缩数组的效果
                }
                res = max(res, dp[j]);
            }
        }
        return res;
    }
};

你可能感兴趣的:(刷题日志,算法,leetcode,c++,动态规划,数据结构)