問題描述
-
Given an unsorted array of integers, find the length of longest increasing subsequence.
For example,
Given [10, 9, 2, 5, 3, 7, 101, 18],
The longest increasing subsequence is [2, 3, 7, 101], therefore the length is 4. Note that there may be more than one LIS combination, it is only necessary for you to return the length.
- Your algorithm should run in O(n^2) complexity.
- Follow up: Could you improve it to O(n log n) time complexity?
- 位址
問題分析
-
方法1 :暴力遞歸
如果用暴力遞歸的話,若求 [i ~ end] 的最長上升子序列的長度,對nums[i]有要或不要兩種決策,如果nums[i] 大于上一個值,那麼這兩種決策中的最大值便即為所求。如果改寫動态規劃的話,有種最優子結構的意思。
時間複雜度 :
O(2^N)
-
方法2 :記憶化搜尋
可以用記憶化搜尋來優化上述遞歸過程,為了節省空間,可以用
來代替遞歸中preIndex
preValue
的作用。
時間複雜度:
O(N^2)
-
方法3 :動态規劃
如果換一個思路,用子數組那種經典套路,求以某一位置結尾或者開頭的最優解,然後最終的最優解一定在上述最優中選優。
用
表示必須以length[i]
結束的最長子序列的長度。那麼可以根據nums[i]
來得到length[0],length[1]...length[i - 1]
,具體見實作。最終最長子序列的長度一定是length[i]
length[i]
中的最大值
當然,該題也可以用
表示必須以length[i]
nums[i]
開頭的最長子序列的長度,從右向左更新即可。
時間複雜度:
O(N^2)
-
方法4 :二分查找法
首先明确二分查找的特點:如果待查找元素
不在已排序數組中,那麼查找結束後,最終num
停在剛剛大于left
的位置,num
停在剛剛小于right
num
的位置。
然後過程是這樣的:
- 對于以上四種方法,LeetCode discuss 中都有提到。
經驗教訓
- 對比方法1與方法3來看,如果我們狀态選取的不一樣,最終算法時間複雜度也不一樣*最優子結構與子數組常用套路*的一種決策,
- 二分查找後
與left
的性質。right
代碼實作
- 暴力遞歸(TLE)
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == ) {
return ;
}
return maxLenOfLIS(nums, , Integer.MIN_VALUE);
}
//傳回[i ~ end] 的最長遞增子序列:要不要nums[i]
public int maxLenOfLIS(int[] nums, int i, int preValue) {
if (i== nums.length) {
return ;
}
int maxLen = ;
if (nums[i] > preValue) {
maxLen = + maxLenOfLIS(nums, i + , nums[i]);
}
maxLen = Math.max(maxLen, maxLenOfLIS(nums, i + , preValue));
return maxLen;
}
- 記憶化搜尋
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == ) {
return ;
}
int[][] memory = new int[nums.length][nums.length + ];
return maxLenOfLIS(nums, , -, memory);
}
public int maxLenOfLIS(int[] nums, int i, int preIndex,int[][] memory) {
if (i == nums.length) {
return ;
}
if (memory[i][preIndex + ] > ) {
return memory[i][preIndex + ];
}
int maxLen = ;
if (preIndex == - || nums[i] > nums[preIndex] ) {
maxLen = + maxLenOfLIS(nums, i + , i, memory);
}
maxLen = Math.max(maxLen, maxLenOfLIS(nums, i + , preIndex, memory));
memory[i][preIndex + ] = maxLen;
return maxLen;
}
- 動态規劃
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == ) {
return ;
}
int[] length = new int[nums.length];
int maxLen = ;
//length[i] :表示必須以nums[i]結束的最長子序列的長度
for (int i = ; i < nums.length; ++i) {
//初始化為 1
length[i] = ;
for (int j = ; j < i; ++j) {
//周遊前面元素,看能否更新length[i]
if (nums[i] > nums[j]) {
length[i] = Math.max(length[i], length[j] + );
}
}
//更新最大長度
maxLen = Math.max(length[i], maxLen);
}
return maxLen;
}
- 二分查找
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length ==) {
return;
}
int[] lis = new int[nums.length];
//LIS的長度
int len =;
for (int num : nums) {
//利用二分法,最終left停在剛好比num大的位置
int left =;
int right = len -;
while(left <= right) {
int mid = left + (right - left) /;
if (num > lis[mid]) {
left = mid +;
}else {
right = mid -;
}
}
//用num替代該元素
lis[left] = num;
//看是否是新增加了元素,若是,則更新len
len = left == len ? len + : len;
}
return len;
}