LeetCode-动态规划总结(二)

最长递增子序列

已知一个序列 {S1, S2,…,Sn},取出若干数组成新的序列 {Si1, Si2,…, Sim},其中 i1、i2 … im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 子序列

如果在子序列中,当下标 ix > iy 时,Six > Siy,称子序列为原序列的一个 递增子序列

定义一个数组 dp 存储最长递增子序列的长度,dp[n] 表示以 Sn 结尾的序列的最长递增子序列长度。对于一个递增子序列 {Si1, Si2,…,Sim},如果 im < n 并且 Sim < Sn,此时 {Si1, Si2,…, Sim, Sn} 为一个递增子序列,递增子序列的长度增加 1。满足上述条件的递增子序列中,长度最长的那个递增子序列就是要找的,在长度最长的递增子序列上加上 Sn 就构成了以 Sn 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | Si < Sn && i < n} 。

因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列,此时 {Sn} 就构成了递增子序列,需要对前面的求解方程做修改,令 dp[n] 最小为 1,即:


对于一个长度为 N 的序列,最长递增子序列并不一定会以 SN 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,max{ dp[i] | 1 <= i <= N} 即为所求。

最长递增子序列

300. Longest Increasing Subsequence (Medium)

class Solution {
    public int lengthOfLIS(int[] nums) {
        if (null == nums || 0 == nums.length) {
            return 0;
        }
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= i - 1; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            max = Math.max(max, dp[i]);
        }
        return max == Integer.MIN_VALUE ? 1 : max;
    }
}

以上解法的时间复杂度为 O(N2),可以使用二分查找将时间复杂度降低为 O(NlogN)。

定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素。对于一个元素 x,

  • 如果它大于 tails 数组所有的值,那么把它添加到 tails 后面,表示最长递增子序列长度加 1;
  • 如果 tails[i-1] < x <= tails[i],那么更新 tails[i] = x。

例如对于数组 [4,3,6,5],有:

tails      len      num
[]         0        4
[4]        1        3
[3]        1        6
[3,6]      2        5
[3,5]      2        null

可以看出 tails 数组保持有序,因此在查找 Si 位于 tails 数组的位置时就可以使用二分查找。

class Solution {
    public int lengthOfLIS(int[] nums) {
        if (null == nums || 0 == nums.length) {
            return 0;
        }
        int[] tail = new int[nums.length];
        int len = 0;
        for (int num : nums) {
            int index = binarySearch(tail, len, num);
            tail[index] = num;
            if (index == len) {
                len++;
            }
        }
        return len;
    }
    public int binarySearch(int[] tail, int len, int target) {
        int left = 0, right = len;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (tail[mid] == target) {
                return mid;
            } else if (tail[mid] > target) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}

一组整数对能够构成的最长链

646. Maximum Length of Pair Chain (Medium)

Input: [[1,2], [2,3], [3,4]]
Output: 2
Explanation: The longest chain is [1,2] -> [3,4]

题目描述:对于 (a, b) 和 (c, d) ,如果 b < c,则它们可以构成一条链。

class Solution {
    public int findLongestChain(int[][] pairs) {
        if (null == pairs || 0 == pairs.length) {
            return 0;
        }
        int m = pairs.length;
        int n = pairs[0].length;
        int[] dp = new int[m];
        Arrays.fill(dp, 1);
        int max = Integer.MIN_VALUE;
        Arrays.sort(pairs, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0] - o2[0];
            }
        });
        for (int i = 0; i < m; i++) {
            int c = pairs[i][0];
            for (int j = 0; j <= i - 1; j++) {
                int b = pairs[j][1];
                if (c > b) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            max = Math.max(max, dp[i]);
        }
        return max == Integer.MIN_VALUE ? 1 : max;
    }
}

最长摆动子序列

376. Wiggle Subsequence (Medium)

Input: [1,7,4,9,2,5]
Output: 6
The entire sequence is a wiggle sequence.

Input: [1,17,5,10,13,15,10,5,16,8]
Output: 7
There are several subsequences that achieve this length. One is [1,17,10,13,10,16,8].

Input: [1,2,3,4,5,6,7,8,9]
Output: 2

要求:使用 O(N) 时间复杂度求解。

class Solution {
    public int wiggleMaxLength(int[] nums) {
        if (null == nums || 0 == nums.length) {
            return 0;
        }
        int n = nums.length;
        int up = 1, down = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] < nums[i - 1]) {
                down = up + 1;
            } else if (nums[i] > nums[i - 1]){
                up = down + 1;
            }
        }
        return Math.max(up, down);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值