题目
最长上升子序列
给定一个整数序列,找到最长上升子序列(LIS),返回LIS的长度。
给出[5,4,1,2,3],这个LIS是[1,2,3],返回 3
给出[4,2,4,5,3,7],这个LIS是[4,4,5,7],返回 4
要求时间复杂度为O(n^2) 或者O(nlogn)
最长上升子序列的定义:
- 最长上升子序列问题是在一个无序的给定序列中找到一个尽可能长的由低到高排列的子序列,这种子序列不一定是连续的或者唯一的。
- https://en.wikipedia.org/wiki/Longest_common_subsequence_problem
解题
下面是很久前复制网上的程序
public class Solution { /** * @param nums: The integer array * @return: The length of LIS (longest increasing subsequence) */ public int longestIncreasingSubsequence(int[] nums) { // write your code here int numlen = nums.length; if(numlen ==0 ) return 0; int d[] = new int[numlen]; // 以nums[i] 结束的最长升序子序列的长度 默认值是 1 在全部逆序的情况, // for(int i = 0 ;i< numlen ; i++) // d[i] = 1; d[0] = 1; int dmax = 1; for(int i = 1 ; i< numlen;i++){ int subdmax = 0; // 这里记录的是以i结束的升序子序列中,去除第i个元素的长度,显然默认是0 for(int j = 0; j< i ;j++){ if(nums[j] <= nums[i]){ subdmax = Math.max(d[j],subdmax);// 求出i所在升序子序列中,去除第i个元素后,最长的升序子序列长度 } } d[i] = subdmax + 1; dmax = Math.max(dmax,d[i]); } return dmax; } }
下面写下自己的理解
求的最长子序列的数不是连续的,数之间是有间隔的,但是他们是升序的,或者说是非递减的序列。
前面做过一些关于动态规划的题目,都是喜欢定义一个数组,A,A[i] 表示到达当前 i 位置 的某种 意义,如:最大值个数,最小值个数,长度等等。
这里也是定义一个数组sublongest sublongest[i] 表示到i 位置的最长升序子序列的长度。
到达第i 个元素的最长子序列长度是 sublongest[i] ,则到达第 i + 1 个元素的最长子序列长度 sublongest[i + 1 ] = nums[j] <= nums[i] 时候的最长子序列长度 + 1
数组中最大值就是答案了。
int sublong = 0; for(int j=0;j<i;j++){ // nums[i] 前面有几个比自己小的数 比自己小的那个数到自己就是一个递增序列 // sublongest[j] j 这个下标对应 nums[j] 这个元素的, if(nums[j] <= nums[i]){ sublong = Math.max(sublongest[j],sublong); } } sublongest[i] = sublong + 1;
JAVA
public class Solution { /** * @param nums: The integer array * @return: The length of LIS (longest increasing subsequence) */ public int longestIncreasingSubsequence(int[] nums) { // write your code here if(nums.length ==0 || nums == null) return 0; int len = nums.length; // 记录到当前位置最长升序序列的长度 int sublongest[] = new int[len]; sublongest[0] = 1; int longest = Integer.MIN_VALUE; for(int i = 1;i<len;i++){ int sublong = 0; for(int j=0;j<i;j++){ // nums[i] 前面有几个比自己小的数 比自己小的那个数到自己就是一个递增序列 // sublongest[j] j 这个下标对应 nums[j] 这个元素的, if(nums[j] <= nums[i]){ sublong = Math.max(sublongest[j],sublong); } } sublongest[i] = sublong + 1; longest = Math.max(sublongest[i],longest); } return longest; } }
Python
class Solution: """ @param nums: The integer array @return: The length of LIS (longest increasing subsequence) """ def longestIncreasingSubsequence(self, nums): # write your code here if nums == None or len(nums)==0: return 0 l = len(nums) sublongest = [0 for i in range(l)] sublongest[0] = 1 longest = -1 for i in range(1,l): sublong = 0 for j in range(0,i): if nums[j] <= nums[i]: sublong = max(sublongest[j],sublong) sublongest[i] = sublong + 1 longest = max(sublongest[i],longest) return longest
上面的时间复杂度都是O(N2),在LeetCode discuss 看到可以利用二分法,时间复杂度是O(NlogN) 同时空间复杂度是O(N)
这里是定义一个List数组,存储这个升序子序列,并且还是动态变化的,对于新来的元素,通过二分查找,插入到这个list数组中,当大于list数组最后一个元素的时候直接在最后插入,如果在list数组中间位置,就直接在中间位置插入,为什么?说明中间位置额那个数比
需要插入的数字大,我们找的是最长的升序子序列,比他大的当然需要被小的替代了,由于list数组是动态变化的,最后list数组的大小就是最长升序子序列,并且其存储的数就是这个升序子序列,上面的方法对这个升序序列不好存储的。同时中间状态的list数组也是原数
组中间位置的最长升序子序列。下面程序很好理解的。
Java
public class Solution { /** * @param nums: The integer array * @return: The length of LIS (longest increasing subsequence) */ public int longestIncreasingSubsequence(int[] nums) { int len = nums.length; if(nums == null || len ==0) return 0; ArrayList<Integer> dp = new ArrayList<Integer>(); for(int i=0;i<len ;i++){ if(dp.isEmpty() || dp.get(dp.size() - 1) <= nums[i]) dp.add(nums[i]); else{ int index = findFirstLargeEqual(dp,nums[i]); dp.set(index,nums[i]);// 用指定的元素替代此列表中指定位置上的元素。 } } return dp.size(); } public int findFirstLargeEqual(ArrayList<Integer> list,int num){ int left = 0; int right = list.size() - 1; while(left < right){ int mid = (left + right)/2; if(list.get(mid) <= num) left = mid + 1; else right = mid; } return left; } }
Python
class Solution: """ @param nums: The integer array @return: The length of LIS (longest increasing subsequence) """ def longestIncreasingSubsequence(self, nums): # write your code here if nums == None or len(nums) ==0: return 0 lst = list() for i in range(len(nums)): if len(lst) == 0 or lst[len(lst) - 1] <= nums[i]: lst.append(nums[i]) else: index = self.findFirstLargeEqual(lst,nums[i]) lst[index] = nums[i] return len(lst) def findFirstLargeEqual(self,lst,target): left = 0 right = len(lst) -1 while left < right: mid = (left + right)/2 if lst[mid] <= target: left = mid + 1 else: right = mid return left