Leetcode300. 最长递增子序列(CPP)

一、题目

300. 最长递增子序列
给定一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

法一:动态规划

  • 模式识别1:看到最长字眼,首先考虑使用动态规划的思想解题
  • 模式识别2:指纹最优解,不问具体解,考虑使用动态规划,不能使用回溯算法来搜索具体的解
  • 题型:最值型+坐标型动态规划

1.1 确定状态

  • 最后一步:在最优策略中,最长严格递增子序列一定存在着最后一个元素nums[i]。
    虽然我们不知道是哪个字符,但是肯定有最后一个元素。
    那么,有了最后一个字符之后,我们发现这里存在着两种情况(考虑序列长度大于2的条件下):

  • 第一种情况:最优策略中的最后一个元素 nums[i] 小于前面一个元素 nums[i - 1],这时最值取决于前面一个元素 nums[i - 1] 结尾的序列,这种情况不属于递增,可以直接跳过。

  • 第二种情况:大于,这时候由于题目不要求子序列是连续的,因此我们需要枚举最后一个字符前面的所有元素进行比较,得到最长的递增子序列,然后加上 nums[i]。

  • 由此,我们发现:

  • 需要求以 nums[i - 1] 结尾的序列的最长递增子序列

  • 而原问题是求以 nums[i] 结尾的序列的最长递增子序列

  • 问题规模缩小了

  • 状态定义:设 f[i] 是以 nums[i] 结尾的序列的最长递增子序列的长度。

  • 如何设定状态?根据题目中的要求,如果是求长度,那么我们需要将状态的宾语设定为长度。

1.2 转移方程

f[i] = max( f[i], f[j] + 1) for j in [0, i) 且 nums[i] > nums[j]

1.3 初始条件和边界情况

  • 初始条件:每一个元素都可以单独作为一个子序列,这时候的长度为1。
  • 边界条件:情况2中 0 <= j < i,而且 nums[i] > nums[j],满足单调性。

1.4 计算顺序

  • 从小到大计算:f[0], f[1], ……, f[n -1]
  • 答案:最终的答案不一定是f[n - 1],因为我们不确定最优策略中的最后一个元素是哪个
  • 因此答案为全局最大值:max(f[0], f[1], ……, f[n -1])

1.5 编程

1.5.1 参考1

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        int result = 1;
        // 定义并初始化状态方程
        vector<int> f(n, 1);
        for (int i = 0; i < n; ++i) {
            // f[i] = 1;
            // 在子问题中,只有最后一个元素与前面的每一个元素都进行对比
            // 这里和674题的连续不同:仅仅需要比较前一个元素就能确保最长
            for (int j = 0; j < i; ++j) {
                if (nums[j] < nums[i]) {
                    f[i] = max(f[i], f[j] + 1);
                }
            }
            if (f[i] > result) {
                result = f[i];
            }
        }
        return result; 
    }
};

1.5.2 参考2:利用C++的函数求最大值

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n, 1);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (nums[j] < nums[i]) {
                    f[i] = max(f[i], f[j] + 1);
                }
            }
        }
        return *max_element(f.begin(), f.end()); 
    }
};

1.6 运行结果

Leetcode300. 最长递增子序列(CPP)_第1张图片

法二:贪心 + 二分查找

2.1 题目分析

Leetcode300. 最长递增子序列(CPP)_第2张图片
Leetcode300. 最长递增子序列(CPP)_第3张图片

2.2 编程

2.2.1 参考1–替换策略1

  • 在 tail 数组中进行二分查找,找到第一个比nums[i] 小的数 tail[k] ,并更新tail[k+1] = nums[i]。
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int len = 1, n = (int)nums.size();
        if (n == 0) {
            return 0;
        }
        vector<int> d(n + 1, 0);
        d[len] = nums[0];
        for (int i = 1; i < n; ++i) {
            if (nums[i] > d[len]) {
                d[++len] = nums[i];
            } else {
                int l = 1, r = len, pos = 0; // 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将 pos 设为 0
                while (l <= r) {
                    int mid = (l + r) >> 1;
                    if (d[mid] < nums[i]) {
                        pos = mid;
                        l = mid + 1;
                    } else {
                        r = mid - 1;
                    }
                }
                d[pos + 1] = nums[i];
            }
        }
        return len;
    }
};
// 作者:LeetCode-Solution
// 链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-by-// leetcode-soluti/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2.2.2 参考2–替换策略2

  • 用 nums[i] 覆盖掉大于它的元素中最小的那个。
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> res(1);
        res[0] = nums[0];
        for(int i = 1; i < nums.size(); ++i) {
            if (nums[i] > res[res.size() - 1]) {
                res.emplace_back(nums[i]);
            }
            // 使用二分查找
            else {
                int left = 0, right = res.size() - 1;
                while (left < right) {
                    // int mid = (left + right) >> 1;
                    int mid = (left + right) / 2;
                    if (res[mid] < nums[i]) {
                        left = mid + 1;
                    }
                    else {
                        right = mid;
                    }
                }
                // 用当前元素覆盖掉 >= 它的元素中最小的那个。
                res[left] = nums[i];
            }
        }
        return res.size();
    }
};

2.2.3 参考3:借助C++的lower_bound(查找左边界,右边界)函数

  • lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> res;
        for(auto n : nums) {
            auto lb = lower_bound(res.begin(), res.end(), n);
            if(lb == res.end()) {
                res.push_back(n);
            }
            else {
                res[lb - res.begin()] = n;
            }
        }
        return res.size();
    }
};

2.3 运行结果

1
Leetcode300. 最长递增子序列(CPP)_第4张图片

2
Leetcode300. 最长递增子序列(CPP)_第5张图片
3
Leetcode300. 最长递增子序列(CPP)_第6张图片

你可能感兴趣的:(#,动态规划,动态规划,c++,算法)