題目
最長上升子序列
給定一個整數序列,找到最長上升子序列(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