【LeetCode】300. 最长上升子序列

一、题目

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
  • 你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

二、解决

1、暴力+剪枝

思路: 暂略。
代码: 暂略。
时间复杂度: O ( 2 n ) O(2^n) O(2n)
空间复杂度: O ( l o g n ) O(logn) O(logn)

2、递归+记忆化

思路: 暂略。

代码:

class Solution {
    
    
    public int lengthOfLIS(int[] nums) {
    
    
        if(nums.length == 0) return 0;
        int[] memo = new int[nums.length];
        Arrays.fill(memo, -1);
        int max = 1;
        for(int i = 0;i<nums.length;i++) {
    
    
            max = Math.max(max, helper(nums, i, memo));
        }
        return max;
    }
    
    protected int helper(int[] nums, int index, int[] memo) {
    
    
        if( index == 0) return 1;
        if(memo[index] != -1) return memo[index];
        int longest = 1;
        for(int i = index-1;i>=0;i--) {
    
    
            if(nums[i] < nums[index]) {
    
    
                longest = Math.max(longest, helper(nums, i, memo)+1);
            }
        }
        memo[index] = longest;
        return longest;
    }
}

3、动态规划

思路:

1、状态定义:
DP[i] -- 从头到i的元素,最长子序列的长度

2、状态方程
max{
    
    DP[0], DP[1],...,DP[n-1]}

for i : 0~n-1
    DP[i] = Max {
    
    DP[j]} + 1 } j=[0,1,2,...,n-1]] 且 a[j]<a[i]

for i : 0-n-1
    j: 0-n-1
       DP[i] = Max {
    
    DP[j]} + 1 } j=[0,1,2,...,n-1]] 且 a[j]<a[i]
  
3、例1: 
10, 9, 2, 5, 3, 7, 101, 18

10 
9
2
2, 5
2, 3
2, 3, 7
2, 3, 7, 101
2, 3, 7, 1825, 7, 1, 2, 3

5
5, 7
1, 7
1, 2
1, 2, 3

代码:

class Solution {
    
    
    public int lengthOfLIS(int[] nums) {
    
    
        if(nums.length == 0) return 0;
        int[] dp = new int[nums.length];
        int res = 0;
        Arrays.fill(dp, 1);
        for(int i = 0; i < nums.length; i++) {
    
    
            for(int j = 0; j < i; j++) {
    
    
                if(nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
            }
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n ) O(n) O(n)

4、二分

思路:

1、状态定义:
DP[i] -- 从头到i的元素,最长子序列的长度

2、状态方程
max{
    
    DP[0], DP[1],...,DP[n-1]}

for i : 0~n-1
    DP[i] = Max {
    
    DP[j]} + 1 } j=[0,1,2,...,n-1]] 且 a[j]<a[i]

for i : 0-n-1
    for j: 0-n-1
        DP[i] = Max {
    
    DP[j]} + 1 } j=[0,1,2,...,n-1]] 且 a[j]<a[i]
原思路分析:
for i: 0-n-1  n
    for j: 0-i-1 --> 优化为二分查找

新思路过程:
a : 维护数组 LIS
b : for i : 0-n-1
        插入到LIS  // 二分查找实现

c: LIS。size() / length

代码-版本1:

class Solution {
    
    
    public int lengthOfLIS(int[] nums) {
    
    
        int[] tails = new int[nums.length];
        int size = 0;
        for (int x : nums) {
    
    
            int i = 0, j = size;
            while (i != j) {
    
    
                int m = (i + j) / 2;
                if (tails[m] < x)
                    i = m + 1;
                else
                    j = m;
            }
            tails[i] = x;
            if (i == size) ++size;
        }
        return size;
    }
}

代码-版本2:

    public int lengthOfLIS(int[] nums) {
    
    
        if (nums == null || nums.length == 0) {
    
    
            return 0;
        }
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int len = 0;
        for (int i = 1; i < nums.length; i++) {
    
    
            int pos = binarySearch(dp,len,nums[i]);
            if (nums[i] < dp[pos]) dp[pos] = nums[i];
            if (pos > len) {
    
    
                len = pos;
                dp[len] = nums[i];
            }
        }
        return len+1;
    }
    private int binarySearch(int[] dp, int len, int val) {
    
    
        int left = 0;
        int right = len;
        while(left+1 < right) {
    
    
            int mid = left + (right-left)/2;
            if (dp[mid] == val) {
    
    
                return mid;
            } else {
    
    
                if (dp[mid] < val) {
    
    
                    left = mid;
                } else {
    
    
                    right = mid;
                }
            }
        }
        if (dp[right] < val) return len+1;
        else if (dp[left] >= val) return left;
        else return right;
    }
}

代码-版本3:

public class Solution {
    
    
    public int lengthOfLIS(int[] nums) {
    
                
        int[] dp = new int[nums.length];
        int len = 0;

        for(int x : nums) {
    
    
            int i = Arrays.binarySearch(dp, 0, len, x);
            if(i < 0) i = -(i + 1);
            dp[i] = x;
            if(i == len) len++;
        }

        return len;
    }
}

代码-版本3-备选:

class Solution {
    
    
    public int lengthOfLIS(int[] nums) {
    
    
        if(nums == null || nums.length == 0) {
    
    
            return 0;
        }
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int len = 0;
        for(int i = 1; i < nums.length; i++) {
    
    
            if(nums[i] > dp[len]) {
    
    
                dp[++len] = nums[i];
            }
            else {
    
    
                int index = search(dp, len, nums[i]);
                dp[index] = nums[i];
            }
        }
        return len + 1;
    }

    private int search(int[] dp, int len, int val) {
    
    
        int start = 0;
        while(start <= len) {
    
    
            int mid = start + (len - start) / 2;
            if(dp[mid] == val) {
    
    
                return mid;
            }
            else if(dp[mid] < val) {
    
    
                start = mid + 1;
            }
            else {
    
    
                len = mid - 1;
            }
        }
        return start;
    }
}

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)

5、TreeSet

思路: TreeSet由红黑树实现,其删除与添加均为log(n)时间复杂度。
代码:

class Solution {
    
    
    public int lengthOfLIS(int[] nums) {
    
    
        TreeSet<Integer> set = new TreeSet<>();
        for(int i : nums) {
    
    
            Integer ceil = set.ceiling(i);  // ceiling(i)--返回>=i的数,不存在返回null
            if(null != ceil) {
    
    
                set.remove(ceil);
            }
            set.add(i);
        }
        return set.size();
    }
}

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)

三、参考

1、最长上升子序列
2、Java/Python Binary search O(nlogn) time with explanation
3、Fast Java Binary Search Solution with detailed explanation
4、Share Java DP solution
5、Java Short nlogn TreeSet Solution
6、Recursion with memoisation O(n) space
7、Java recursive backtracking solution, who can help to analyze the complexity?
8、Java solution with Backtracking, 14ms
9、O(nlogn) Clean and easy Java DP + Binary Search solution with detailed explanation
10、java.util.TreeSet.ceiling()方法实例

猜你喜欢

转载自blog.csdn.net/HeavenDan/article/details/109100911