300. 最长递增子序列

149 阅读3分钟

方法一:动态规划 O(N^2)

  • dp[i]表示以nums[i]为结尾的最长上升序列的长度
  • 从前缀中获得dp【i】
class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];//dp[i]表示以nums[i]为结尾的最长上升序列的长度
        Arrays.fill(dp, 1); //初始值设为1,即子序列为1个数字
        int max = 0;
        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);//遍历i之前的元素,滚动更新记录一个+1的最大值
                }
                
            }
            max = Math.max(max, dp[i]);
        }
        return max;
    }
}


  • 输出最长递增子序列
class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp, 1); //初始值设为1,即子序列为1个数字
        int max = 0;
        int maxLastIndex = 0;//最长递增序列的最后一个节点index
        Map<Integer, Integer> map = new HashMap<>();//<当前节点index,前序节点index>
        for (int i = 0; i < nums.length; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    // dp[i] = Math.max(dp[i], dp[j] + 1);
                    if (dp[j] + 1 > dp[i]) {
                        map.put(i, j);// i的前序节点为j,滚动更新
                        dp[i] = dp[j] + 1;
                    }
                }
            }   
            // max = Math.max(max, dp[i]);
            if (dp[i] > max) {
                max = dp[i];
                maxLastIndex = i; // 最长序列的最后一个节点index
            }
        }
        
        // 获取路径
        StringBuffer sb = new StringBuffer();
        sb.append(nums[maxLastIndex]);
        sb.append(",");
        while (map.get(maxLastIndex) != null) {
            sb.append(nums[map.get(maxLastIndex)]);
            sb.append(",");
            maxLastIndex = map.get(maxLastIndex);
        }
        System.out.println(sb.reverse().toString().substring(1, sb.length()));

        return max;
    }
}

方法二:贪心 + 二分查找 O(NlogN)

  • 贪心:如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。

  • 维护一个数组 arrarr[i]表示长度为 i 的最长上升子序列的末尾元素的最小值,arr一定是升序的。 注意,arr不一定为题目求的上升子序列的解。

  • 遍历nums,

    • 如果nums[i] 大于arr中所有元素,则扩容arr,加上nums[i]
    • 如果nums[i]arr大小范围中,则插入nums[i]到合适位置覆盖arr中的某个元素。由于arr是升序的,寻找合适位置的过程使用二分查找,复杂度为O(logN)
  • 遍历完nums后,arr长度即为所求。

  • 遍历插入替换元素的过程可以理解为覆盖卡牌: 简而言之:

  • 维护一个结果数组,如果当前元素比结果数组的值都大的的话,就追加在结果数组后面(相当于递增序列长度加了1)

  • 否则的话用当前元素覆盖掉第一个比它大的元素,表示长度为 i 的最长上升子序列的末尾元素的最小值还可以更小。

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        int[] arr = new int[n + 1];
        arr[1] = nums[0];
        int len = 1;
        for (int i = 1; i < n; ++i) {
            if (nums[i] > arr[len]) {
                len++;
                arr[len] = nums[i];
            } else { // 可以替换arr中的元素,替换哪一个呢?由于arr升序,所以用二分来确定位置。
               //找第一个比nums[i]大的值, 然后把它用numsi替换掉
                int low = 1, high = len;
                while (low <= high) {
                    int mid = (high + low) / 2;
                    if (arr[mid] < nums[i]) {
                        low = mid + 1;
                    } else {
                        high = mid - 1;
                    }
                }
                if (high == -1) {//没找到,arr中所有的数都比num[i]大
                    arr[0] = nums[i];
                } else {
                    arr[low] = nums[i];
                }

            }
        }
        return len;
    }
}
//nums升序有重复元素,找第一个>=target的数的index
class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] >= target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        int res = 0;
        if (left == nums.length) {//tar大于所有nums
           res = left;
        } else if (right == -1) {//tar小于所有nums
           res = 0;
        } else {//tar在nums里边,则一定能找到,找到时,nums[mid] = tar,循环最后一次时,left=right=mid一定还在tar的位置上,退出while后,right-1.所以用left
            res = left;//
        }
        return res;
    }
}

参考

www.bilibili.com/video/BV1Wf…