1. 程式人生 > 實用技巧 >【LeetCode/LintCode】 題解丨位元組跳動高頻題:最長上升子序列

【LeetCode/LintCode】 題解丨位元組跳動高頻題:最長上升子序列

給定一個整數序列,找到最長上升子序列(LIS),返回LIS的長度。

線上評測地址:點選此處前往

說明

最長上升子序列的定義:

最長上升子序列問題是在一個無序的給定序列中找到一個儘可能長的由低到高排列的子序列,這種子序列不一定是連續的或者唯一的。

樣例 1:
	輸入:  [5,4,1,2,3]
	輸出:  3
	
	解釋:
	LIS 是 [1,2,3]


樣例 2:
	輸入: [4,2,4,5,3,7]
	輸出:  4
	
	解釋: 
	LIS 是 [2,4,5,7]

演算法:動態規劃(dp)

演算法思路

  • 因為所求為子序列,很容易想到一種線性動態規劃。
  • 對於求最長上升子序列,上升我們肯定需要知道當前階段最後一個元素為多少,最長我們還要知道當前我們的序列有多長。
  • 那麼我們就可以這樣定義狀態:設 dp[i] 表示以 nums[i] 為結尾的最長上升子序列的長度,為了保證元素單調遞增肯定只能從 i 前面且末尾元素比 nums[i] 小的狀態轉移過來

程式碼思路

  • 狀態轉移方程為

  • 每個位置初始值為 dp[i]=1(將自己作為一個序列)
  • 答案可以是任何階段中只要長度最長的那一個,所以我們邊轉移邊統計答案

複雜度分析

N表示為nums 的長度

  • 空間複雜度:O(N)
  • 時間複雜度:O(N^2)
 public class Solution {
    /**
     * @param nums: The integer array
     * @return: The length of LIS (longest increasing subsequence)
     */
    public int longestIncreasingSubsequence(int[] nums) {
        // dp[i]表示以nums[i]為結尾的最長上升子序列的長度
        int []dp = new int[nums.length];
        int ans = 0;
        for (int i = 0; i < nums.length; i++) {
            // 初始值為1
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i]) {
                    // 若nums[j] < nums[i]那麼可以接在該序列後,更新狀態
                    dp[i] = dp[i] > dp[j] + 1 ? dp[i] : dp[j] + 1;
                }
            }
            // 記錄所有狀態中的最大值
            if (dp[i] > ans) {
                ans = dp[i];
            }
        }
        return ans;
    }
}

優化演算法:動態規劃(dp)+二分優化

上面說過:我們肯定要知道當前階段最後一個元素為多少,還有當前的序列有多長。上面的方法是用前者做狀態,即元素是多少,那麼我們可不可以用後者,即序列長度做狀態呢?

演算法思路

  • 設 dp[i] 表示長度為 i 的最長上升子序列的末尾元素的最小值,顯然這個陣列的權值一定單調不降。
  • 於是我們按順序列舉陣列nums,每一次對dp陣列二分查詢,找到小於nums[i]的最大的 dp[j],並更新 dp[j+1]。

複雜度分析

  • 空間複雜度:O(N)
  • 時間複雜度:O(NlogN)
 public class Solution {
    /**
     * @param nums: An integer array
     * @return: The length of LIS (longest increasing subsequence)
     */
    public int longestIncreasingSubsequence(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        int[] lis = new int[nums.length + 1];
        lis[0] = Integer.MIN_VALUE;
        for (int i = 1; i <= nums.length; i++) {
            lis[i] = Integer.MAX_VALUE;
        }
        
        int longest = 0;
        for (int i = 0; i < nums.length; i++) {
            int index = firstGTE(lis, nums[i]);
            lis[index] = nums[i];
            longest = Math.max(longest, index);
        }
        
        return longest;
    }
    
    // GTE = Greater Than or Equal to
    private int firstGTE(int[] nums, int target) {
        int start = 0, end = nums.length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (nums[mid] >= target) {
                end = mid;
            } else {
                start = mid;
            }
        }
        if (nums[start] >= target) {
            return start;
        }
        return end;
    }
}

更多題解參見:九章演算法官網