题目描述

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

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

题目描述中讲的是找到最长递增子序列的个数。
里面涉及到一个最长递增子序列的概念。
比如示例1
[10,9,2,5,3,7,101,18] 中有递增子序列[2,3,7,18] , [2,3,7,101] , [2,5,7,101] ,[10,101]...
其中最长的子序列长度为4
这个序列中的元素也会跳过其中的一些或者若干个元素。以满足最长

比如示例3
由于[7,7,7,7,7,7,7] 中没有[7,9]这样的元素。
所以[7]就是其中的一个最长递增子序列。说明此时递增是严格递增的。

思路

从注意中得到的信息

数组长度不超过2500

  1. 可能需要对自己的计算方法进行一定程度的加速,否则有可能超时。

实际操作

之前做过一些贪心的题目,可以通过把这些点都画出来的方式。来找一些波峰与波谷的规律。
[力扣300] 最长递增子序列 2021.9.20_#include波峰波谷图

尝试1

那从图上看。我需要做

  1. 由于第1个元素是1,所以我需要找比1严格递增的下一个元素。那就是3
  2. 再通过3找下一个严格递增的元素也就是4
  3. 再通过4长下一个严格递增的元素也就是5,但是此时5位于4元素之前,不能引入。
  4. 那只能找到7,所以大概思路就有如下。

选用std::map去保存数组中的值与index,由于有重复的值,所有可以用std::vector<int>它的value
由于map(内部实现是RB-Tree,是有序的)所以可以利用这个属性。

然后遍历整个数组。
通过当前这个元素找下一个严格比自己大的元素。跳到那个index再继续寻找。
写到这里,有一个反例不能使用这个方法。
[1,3,5,2,7] 由于2严格比1大1,所以通过这个方法找到的一定是2,但是如果跳到2,则会导致输出[1,2,7],则不符合题意。
所以此方法不行。

尝试2

由于是最长递增子序列,如何满足最长的这个属性呢?
那一定有:
最好整个序列都是递增的(尽可能找到更多的元素)
那让自己足够贪心,最好此元素的下一个元素就是递增的。

所以有:如果下一个元素比自己大的话,就计入序列。这样就有:
[0,1,3,5,7]
但有如下反例:
[0,1,7,8,9,2,3,4,5,6,10]
那么如果按照这个方法得到的是 [0,1,7,8,9,10]
但答案应该是[0,1,2,3,4,5,6,10]

所以程序在遍历的时候需要比如一下7,8,9与下一个低值2,3,4,5,6
由于2比1刚好大,所以 从1开始的子序列有两个选择,一个是7,8,9,一个是从2,后面开始,所以可以计数。
如果后面的最长递增子序列数量比7,8,9长的话,可以把前面进行替换。

那程序上如何实现呢?

  1. 可以通过什么来记录此最长序列的最长值 比如[0,1,7,8,9]为5
  2. 如果下一个元素比当前元素小,可以去前面找比当前元素严格递减的元素。
    [0,1,7..., 9 ,2] 由于 2 < 9 , 且 2-7 < 0 , 2-1 < 2-0,所以找到元素1
    此时当前最大元素为2,2前面的元素为1. 从2再继续向后找。
    如果此时找到序列尾部,或者遇到比当前元素值更小的元素时,则此段寻找完毕,与之前的最长元素进行一个比较。
    如果大于,则替换。

这样应为就可以找到最找递增子序列了。
目前没有想到反例,写代码试试看!

写代码的过程中发现前一个最长长度不是很好的表达。何况使用map去寻找不如一个一个去比对stack的栈顶方便。
所以修改为使用栈的方式。
有如下代码(其中有一些内容是帮助看结果正确与否的,所以实际提交时可以把它删去):

#include <vector>
#include <iostream>
#include <map>
#include <cstdio>
#include <stack>

using namespace std;

class Solution {
public:
    int lengthOfLIS(const vector<int> &nums) {
        if (nums.empty())
            return -1;

        std::stack<int> compare_stack;
        std::stack<int> pre_stack;

        for (int i = 0; i < nums.size(); i++) {
            if (compare_stack.empty()) {
                compare_stack.push(nums[i]);
                continue;
            }
            auto value = compare_stack.top();
            if (nums[i] > value) {
                compare_stack.push(nums[i]);
            } else {
                // 遇到比当前值小的数字。计数同时把栈copy一份到compare_stack处。
                // 再把比当前大的数字弹栈
                // 如果当前的compare_stack比原来的stack长的话,那么update pre_stack
                if (pre_stack.size() < compare_stack.size())
                    pre_stack = compare_stack;
                while (!compare_stack.empty()) {
                    auto top = compare_stack.top();
                    if (top >= nums[i]) {
                        compare_stack.pop();
                        continue;
                    }
                    break;
                }
            }
        }

        auto help_print = compare_stack.size() > pre_stack.size() ? compare_stack : pre_stack;
        while (!help_print.empty()) {
            printf("%d ", help_print.top());
            help_print.pop();
        }
        return std::max(pre_stack.size(), compare_stack.size());
    }
};

int main(void) {
    std::vector<int> test_case1{2, 2, 2, 2, 2};
    std::vector<int> test_case2{0, 1, 3, 5, 7};
    std::vector<int> test_case3{0, 1, 7, 8, 9, 2, 3, 4, 5, 6, 10};
    Solution s;
    for (auto &c : {test_case1, test_case2, test_case3}) {
        // printf("%d ", s.lengthOfLIS(c));
        s.lengthOfLIS(c);
        printf("-----------------\n");
    }
    return 0;
}

执行测试用例发现有错误
[0,1,0,3,2,3]

  1. 发现按照算法描述的应该是[0,2,3],结果并不是最长递增子序列
  2. 发现2没有写进入。发现程序中有bug,有else分支的while中,把比自己大的元素都弹出之后没有把nums[i]push进去
    应为在while后加一句compare_stack.push(nums[i]);

错误1又如何解决?
由于找到比前一个元素小的状态就会把当前栈清除会向后走。
这里就有一个问题,就是更新栈之后向后走的最长序列与原来向后走的最长序列谁会是最长的?
pre_stack的元素没有向后走显然不能与此compare_stack再继续比下去

所以无论如何,都应该从那个点向后走去

更新代码

这个时候可以用递归去思考问题。
一方面还沿用尝试2之前的思路,沿着新的路径走下去。一方面也沿着才路走下去。
[力扣300] 最长递增子序列 2021.9.20_#include_02

代码如下:

class Solution {
public:
    int lengthOfLIS(const vector<int> &nums) {
        if (nums.empty())
            return -1;

        std::size_t max_length                                    = 0;
        std::function<void(std::stack<int>, int)> find_max_length = [&](std::stack<int> curr_stack,
                                                                        int index) {
            if (index >= nums.size()) {
                max_length = std::max(max_length, curr_stack.size());
                return;
            }

            for (int i = index; i < nums.size(); i++) {
                if (curr_stack.empty() || curr_stack.top() < nums[i]) {
                    curr_stack.push(nums[i]);
                    continue;
                }
                auto curr_top_val = curr_stack.top();
                if (curr_top_val == nums[i]) {
                    continue;
                }

                int next_bigger_than_curr_top_index = nums.size();
                for (int next_bigger_index = i + 1; next_bigger_index < nums.size();
                     next_bigger_index++) {
                    if (curr_top_val < nums[next_bigger_index]) {
                        next_bigger_than_curr_top_index = next_bigger_index;
                        break;
                    }
                }
                // 分出一路找继续走下去
                find_max_length(curr_stack, next_bigger_than_curr_top_index);

                while (!curr_stack.empty()) {
                    auto val = curr_stack.top();
                    if (val >= nums[i]) {
                        curr_stack.pop();
                        continue;
                    }
                    break;
                }
                curr_stack.push(nums[i]);
            }
            max_length = std::max(max_length, curr_stack.size());
        };

        std::stack<int> cmp_stack;
        find_max_length(cmp_stack, 0);
        return max_length;
    }
};

这个代码有一个问题,那就是,后面的元素会被重复搜索。
比如
[1,11,12,13,2,3,4,21,22,15,16,17,23]
当13与2相比的时候去走2的路线然后13会从14开始找到21
另一条路线 2,3,4,21也会走到21,从同一个起点开始的后面的最大递归子序列的数量是一致的。

21这条路线到22和15相比的时候又进入到23
另一边进入到15的一条路上。

所以重复计算会导致速度很慢。
这里用这个code去挑战结果失败了。

由此想来,如果后面的计算很慢,那么我就先行计算后面的元素。
从后向前,后面的每一个元素都代表从这个元素开始可以获得的最大的子序列长度。

比如
[0,1,7,8,9,2,2,3,4,5,6]
6的最长子序列为1
如果此元素与后一个元素相等,则最长子序列值一样。
如果不相等,则遍历后面所有的比自己大的元素并选择其最大的子序列长度,在此基础之上+1

代码:

class Solution {
public:
    int lengthOfLIS(const vector<int> &nums) {
        if (nums.empty())
            return -1;

        std::map<int, int> max_subsequence_length;
        // index, max length
        max_subsequence_length.insert(std::make_pair(nums.size() - 1, 1));
        for (int i = nums.size() - 2; i >= 0; i--) {
            if (nums[i] == nums[i+1]) {
                max_subsequence_length[i] = max_subsequence_length[i + 1];
            }                
            else {
                int max_length = 0;
                for (int j = i + 1; j < nums.size(); j++) {
                    if (nums[i] < nums[j]) {
                        max_length = std::max(max_length, max_subsequence_length[j]);
                    }
                }
                max_subsequence_length[i] = max_length + 1;
            }
        }

        int max_length = 0;
        for (auto &p : max_subsequence_length) {
            max_length = std::max(max_length, p.second);
        }
        return max_length;
    }
};

这个可以勉强AC。