Sunday, April 9, 2017

300. Longest Increasing Subsequence


Version #1 primitive DP
O(n^2)
We define T[i] as the longest increasing subsequence whose maximum number is nums[i]
T[i] = for every j < i, maximum T[j] + 1 where nums[j] < nums[i]

一刷
42.69%

public class Solution {
    public int lengthOfLIS(int[] nums) {
        // write your code here
         if (nums == null || nums.length == 0) return 0;
         int[] count = new int[nums.length];
         count[0] = 1;
         int max = 1;
         for (int curr = 0; curr < nums.length; curr++) {
             count[curr] = 1;
             for (int prev = 0; prev < curr; prev++) {
                 if (nums[prev] < nums[curr]) {
                     count[curr] = Math.max(count[curr], count[prev] + 1);
                 }
             }
             max = Math.max(max, count[curr]);
         }
         return max;
   
    }
}
二刷
49.00 %
class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int longest = 1;
        // dp[i] -> LIS end with index i
        // for all a < i && nums[a] < nums[i], dp[i] = Max(dp[a]) + 1
        int[] dp = new int[nums.length];
        dp[0] = 1;
        for (int end = 1; end < nums.length; end++) {
            int max = 0;
            for (int start = end - 1; start >= 0; start--) {
                if (nums[start] < nums[end]) {
                    max = Math.max(dp[start], max);
                }
            }
            dp[end] = max + 1;
            longest = Math.max(longest, dp[end]);
        }
        return longest;
    }
}

Version #2 DP with Binary Search
Time O(nlogn)
九章讲解
89.86%
一刷
思路很简单不知道为什么网上的所有讲解都讲的特别不清楚。。。
就是维护一个minEnd array
index代表的是subsequence length
value代表的是所有能够组成当前valid subsequence的可能性中那个最小的end point
譬如给一个array  5 2 3 6 4
当遍历到6的时候,所有能够使subsequence为2的结果有 5 6,2 3,2 6
这里的3 就是minimum end point
为什么要保留这个值呢?因为对于所有后来的数,凡是大于其他possible end point的一定大于minimum,但是大于minimum的却不一定大于其他possible end point
所以这个minimum保证了最大的后续sequence 的potential
当current point 大于当前最大length的minEnd时,证明subarray可以被expand,于是length++
若小于等于当前最大length,则之前的某一个midEnd一定可以被更新

【注意】这里写了一个bug就是binarySearch找的是firstLargerIndex但是没考虑有duplicates的情况
若考虑duplicates则应该是第一个大于等于target的index


public class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        //countIndex -> length - 1
        int[] minEnd = new int[nums.length];
        int len = 0;
        minEnd[0] = nums[0];
        for (int curr = 1; curr < nums.length; curr++) {
            if (nums[curr] < minEnd[0]) {
                minEnd[0] = nums[curr];
            } else if (nums[curr] > minEnd[len]) {
                len++;
                minEnd[len] = nums[curr];
            } else {
                int firstLargerIndex = binarySearch(minEnd, len, nums[curr]);
                minEnd[firstLargerIndex] = nums[curr];
            }
        }
        return len + 1;
    }
    //search between [0,len]
    //Consider the case with duplicates
    private int binarySearch(int[] minEnd, int len, int target) {
        if (len == 0) return 0;
        int start = 0, end = len;
        int mid;
        while (start + 1 < end) {
            mid = start + (end - start) / 2;
            if (minEnd[mid] > target) end = mid;
            else start = mid;
        }
        if (minEnd[start] >= target) return start;
        return end;
    }
}

二刷
不如一刷写的好
应该找到第一个大于等于的值

94.34 %
class Solution {
    public int lengthOfLIS(int[] nums) {
        // We have some repeat work when search for previous nums
        // Given an array 2, 5, 3 ...
        // We can either choose 2, 3 to construct an array of length 2, or choose 2, 5 to construct an array of length 2
        // We are always using the smaller one
        if (nums == null || nums.length == 0) {
            return 0;
        }
        // minEnd[i] -> minimum end number to form a LIS of length i
        // Find the 1st index greater than current number, replace by the current number
        // If not found, increase max length
        int[] minEnd = new int[nums.length];
        minEnd[0] = nums[0];
        int max = 0;
        //[4,10,4,3,8,9]
        //   max    0    1     2    3    4    5
        // minEnd   4    10
        for (int i = 1; i < nums.length; i++) {
            int start = 0;
            int end = max;
            while (start < end) {
                int mid = start + (end - start) / 2;
                if (minEnd[mid] <= nums[i]) {
                    start = mid + 1;
                } else {
                    end = mid;
                }
            }
            if (minEnd[start] >= nums[i] && (start == 0 || minEnd[start - 1] < nums[i])) {
                minEnd[start] = nums[i];
            } else if (minEnd[start] < nums[i]) {
                max++;
                minEnd[max] = nums[i];
            }
        }
        return max + 1;
    }
}

三刷 06/2022
Version #1 Naiive DP
Time O(N^2)
Space O(N)
Runtime: 68 ms, faster than 57.46% of Java online submissions for Longest Increasing Subsequence.
Memory Usage: 44.7 MB, less than 30.22% of Java online submissions for Longest Increasing Subsequence.
class Solution {
    public int lengthOfLIS(int[] nums) {
        // dp[i] - longest increasing subsequence ending with nums[i]
        int[] dp = new int[nums.length];
        int globalMax = 0;
        for (int end = 0; end < nums.length; end++) {
            int prevMax = 0;
            for (int prev = end - 1; prev >= 0; prev--) {
                if (nums[prev] < nums[end]) {
                    prevMax = Math.max(prevMax, dp[prev]);
                }
            }
            dp[end] = prevMax + 1;
            globalMax = Math.max(globalMax, dp[end]);
        }
        return globalMax;
    }
}

Version #2 Binary Search
注意相等的情況是需要非常注意的
一定是找到第一个大于等于num的minEnd,而不是大于的
因为如果两个数相等是不能接在一起的
Time O(NlogN)
Space O(N)
Runtime: 7 ms, faster than 80.35% of Java online submissions for Longest Increasing Subsequence.
Memory Usage: 44 MB, less than 70.52% of Java online submissions for Longest Increasing Subsequence.
class Solution {
    public int lengthOfLIS(int[] nums) {
        // We keep a minEnd array represents the minimum end number that could be to construct a  sequence with that length
        // The minEnd array is increasing
        // Given a number, we need to see if it can replace one of the minEnd - find the first index that is larger than or equals to the number, and replace that number
        int[] minEnd = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            minEnd[i] = Integer.MAX_VALUE;
        }
        int max = 0;
        for (int i = 0; i < nums.length; i++) {
            int p = insertPosition(minEnd, nums[i]);
            minEnd[p] = nums[i];
            max = Math.max(max, p + 1);
        }
        return max;
    }
    
    private int insertPosition(int[] minEnd, int num) {
        int start = 0, end = minEnd.length - 1;
        while (start < end) {
            int mid = start + (end - start) / 2;
            if (minEnd[mid] < num) {
                start = mid + 1;
            } else {
                end = mid;
            }
        }
        return start;
    }
}

No comments:

Post a Comment