LeetCode 300. Longest Increasing Subsequence

Given an unsorted array of integers, find the length of longest increasing subsequence.

Example:

Input: [10,9,2,5,3,7,101,18]
Output: 4 
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. 

Note:

  • There may be more than one LIS combination, it is only necessary for you to return the length.
  • Your algorithm should run in O(n2) complexity.

Follow up: Could you improve it to O(n log n) time complexity?

非常经典的一道动态规划题目,常用解法有两个

1、暴力搜索

时间复杂度O(N^2),空间复杂度O(N),f[i]表示以num[i]结尾的上升子序列的长度,外层循环遍历整个序列,复杂度O(n),内层循环则从第一个数遍历到外层循环的数字(不含)为止,转移方程为f[i] = max(f[i],f[j]+1)代码如下:

 1 class Solution {
 2 public:
 3     int lengthOfLIS(vector<int>& nums) {
 4         if (nums.size() == 0)
 5             return 0;
 6         vector<int> v;
 7         int len = 1;
 8         for (int i = 0; i < nums.size(); i++)
 9         {
10             v.push_back(1);
11             for (int j = 0; j < i; j++)    //这里的可以是小于,也可以是小于等于
12                 if (nums[j] < nums[i])    //这里根据题目情况,如果要求严格单调上升,就必须是小于,否则应该是小于等于
13                     v[i] = max(v[i], v[j]+1);
14             if (v[i] > len)
15                 len = v[i];
16                     
17         }
18         return len;
19     }
20 };

2、二分搜索

时间复杂度O(N*logN),空间复杂度O(N),我们可以用一个数组,来记录当前已经形成的递增子序列,该数组一定是单调不减的,我们所希望的是末尾的数字尽量小,这样才会使得更多的数字有机会加入进来。外层循环需要遍历整个序列,内层循环则利用有序性进行二分查找,找到该数字在我们定义的数组中的位置,然后更新,最终返回该数组的长度即可,但是这么做要注意我们只保证长度是正确的,不保证该数组是一个最长上升子序列。

代码如下:

 1 class Solution {
 2 public:
 3     int lengthOfLIS(vector<int>& nums) {
 4         if (nums.size() == 0)
 5             return 0;
 6         vector<int> res;
 7         res.push_back(nums[0]);
 8         for (int i = 0; i < nums.size(); i++)
 9         {
10             int left = 0, right = res.size()-1;
11             if (nums[i] > res.back())
12                 res.push_back(nums[i]);
13             else
14             {
15                 while (left < right)
16                 {
17                     int mid = (left + right) / 2;
18                     if (res[mid] < nums[i])
19                         left = mid + 1;
20                     else
21                         right = mid;
22                 }
23                 res[right] = nums[i];
24             }
25         }
26         return res.size();
27     }
28 };

 易错点:6,7行,可以利用c++11新特性写成 res{nums[0]},但是千万别错写成res(nums[0])

另外,这道题的细节方面也值得注意,17行可以用移位运算代替除法提升速度,19行和21行是变形的二分查找,因为右边界搜索的速度放慢了,right = mid 而不是right = mid-1,这么做我的理解是“二分查找允许找不到返回-1,而我们这里一定会找到一个适合插入的位置,所以右边界搜索速度减慢”,当然你也许会问如果把左边界搜索速度放慢右边界不变可以吗?比如19行改为left = mid, 21行改为 rigth = mid - 1, 答案是不行,会陷入死循环,[10,9,2,5,3,7,101,18]就过不了。因为我们需要构造的是一个右紧密序列,也就是允许mid落在right上。
还有,15行必须为小于号,而不是小于等于,因为那样会陷入死循环,细节很多,需要注意!

参考链接:

https://www.cnblogs.com/lsgxeva/p/7787245.html

http://www.cnblogs.com/grandyang/p/4938187.html

https://www.cnblogs.com/GodA/p/5180560.html

https://blog.csdn.net/George__Yu/article/details/75896330

原文地址:https://www.cnblogs.com/dapeng-bupt/p/9746259.html