【LeetCode】动态规划 | 线性DP 序列DP(子序列)

博文声明:仅供本人学习交流使用,相关代码和资料已留下引用出处。

公共子序列

子序列定义:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 可以不连续

【参考:【你的衣服我扒了 - 《最长公共子序列》】动态规划 - 不相交的线 - 力扣(LeetCode)

一般这种求解 两个数组或者字符串 求最大或者最小 的题目都可以考虑动态规划,并且通常都定义
dp[i][j] 为 以 A[i], B[j] 结尾的 xxx

718. 最长重复子数组

1143.最长公共子序列 longest Common Subsequence LCS

1035. 不相交的线

516. 最长回文子序列

【参考:516. 最长回文子序列 - 力扣(LeetCode)

【参考:序列相关 DP 总结 - 知乎

回文就是从左到右遍历得到的序列与从右到左遍历得到的序列相同的字符串

因此,我们要找到回文子序列,可以通过求原字符串其反转字符串最长公共子序列的长度

只不过这个子序列一定是连续的字符串而已

class Solution {
    public int longestPalindromeSubseq(String s) {
        String t=new StringBuilder(s).reverse().toString();
        int m=s.length();
        int n=t.length();

        char[] sc=s.toCharArray();
        char[] tc=t.toCharArray();

        int[][] dp=new int[m+1][n+1];
        // base case
        // dp[i][0]=dp[0][j]=0

        for(int i=1;i<=m;i++)
            for(int j=1;j<=n;j++)
                if(sc[i-1]==tc[j-1]) // 注意下标
                    dp[i][j]=dp[i-1][j-1]+1;
                else
                    dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);

        return dp[m][n];      

    }
}

115. 不同的子序列 - hard

【参考:115. 不同的子序列 - 力扣(LeetCode)
【参考:序列相关 DP 总结 - 知乎

待理解

class Solution {
    public int numDistinct(String s, String t) {
        int m=s.length();
        int n=t.length();
        // m>=n

        char[] sc=s.toCharArray();
        char[] tc=t.toCharArray();

        int[][] dp=new int[m+1][n+1];
        // base case
        // dp[0][j]=0
        for(int i=0;i<=m;i++)
            dp[i][0]=1; // 空串也是子串

        for(int i=1;i<=m;i++)
            for(int j=1;j<=n;j++){
                if(sc[i-1]==tc[j-1]) // 注意下标
                    dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
                else
                     dp[i][j]=dp[i-1][j];
            }
        return dp[m][n];      

    }
}

522. 最长特殊序列 II - medium

【参考:522. 最长特殊序列 II - 力扣(LeetCode)
题目翻译:给出一个字符串数组,在里面找出字符串满足当前字符串不是字符串数组中其他字符串的子序列,返回满足条件的字符串中 最长的字符串的长度

class Solution {
    public int findLUSlength(String[] strs) {
        int ans = -1;
        int n = strs.length;
        // 两两比较
        for (int i = 0 ; i < n ; i++) {
            if (strs[i].length() < ans) continue; // 题目求最长的
            boolean flag = true;
            for (int j = 0 ; j < n ; j++) {
                if (i == j) continue; // 不和自己比较
                //判断strs[i]是否为strs[j]的子序列
                if (checkLCS(strs[i].toCharArray(), strs[j].toCharArray())) {
                    flag = false;
                }
            }
            if (flag) {
                ans = Math.max(ans, strs[i].length()); // 以前面的字符串为基准
            }
        }
        return ans;
    }
    // 判断strs[i]是否为strs[j]的子序列
    boolean checkLCS(char[] cs1, char[] cs2) {
        int n = cs1.length, m = cs2.length;
        if (n > m) return false;
        int[][] dp = new int[n + 1][m + 1];
        for (int i = 1 ; i <= n ; i++) {
            for (int j = 1 ; j <= m ; j++) {
                if (cs1[i - 1] == cs2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[n][m] == n; // 以前面的字符串为基准
    }
}
class Solution {
    public int findLUSlength(String[] strs) {
        int n = strs.length;
        int ans = -1;
        for (int i = 0; i < n; ++i) {
            boolean check = true;
            for (int j = 0; j < n; ++j) {
                if (i != j && isSubseq(strs[i], strs[j])) {
                    check = false;
                    break;
                }
            }
            if (check) {
                ans = Math.max(ans, strs[i].length());
            }
        }
        return ans;
    }
    // 双指针 判断s是否是t的子序列
    public boolean isSubseq(String s, String t) {        
        if (s.length() > t.length()) return false;
        int ptS = 0, ptT = 0;
        while (ptS < s.length() && ptT < t.length()) {
            if (s.charAt(ptS) == t.charAt(ptT)) {
                ++ptS;
                ++ptT;
            }else{
				++ptT;
			}            
        }
        return ptS == s.length();
    }
}

等差子序列

1218. 最长定差子序列

【参考:1218. 最长定差子序列 - 力扣(LeetCode)

模拟过程 【参考:【彤哥来刷题啦】动态规划 & 接近双百! - 最长定差子序列 - 力扣(LeetCode)

dp[x] 表示以 x 结尾的最长等差子序列的长度;

【参考:简单的dp - 最长定差子序列 - 力扣(LeetCode)

dp[i]来记录以数字 i 为结尾的最长等差子序列的长度

  • dp[i] = 1 // 表示在 i 之前没有出现等差子序列
  • dp[i] = dp[i - difference] + 1 // 表示在 i 之前出现了等差子序列,长度为 dp[i - difference], 而 i 也是满足这个等差序列的,所以等差序列的长度在此基础上加 1 就可以了
class Solution {
    public int longestSubsequence(int[] arr, int difference) {
        int res=1;
        Map<Integer,Integer> map=new HashMap<>();

        for(int i:arr){
        	// 查找上一个数字为 i-difference 结尾的最长等差子序列的长度
            int temp=map.getOrDefault(i-difference,0)+1; 
            map.put(i,temp);
            res=Math.max(res,temp);
        }
        return res;
    }
}
class Solution:
    def longestSubsequence(self, arr: List[int], difference: int) -> int:
        mydict=dict()
        res=1
        for x in arr:
            temp=mydict.get(x-difference,0)+1
            mydict[x]=temp
            res=max(res,temp)
        return res

上升子序列

300. 最长递增子序列 - medium

【参考:300. 最长递增子序列 - 力扣(LeetCode)

二分查找法

class Solution {
    public int lengthOfLIS(int[] nums) {
        int len = 0; // 二分查找数组的长度
        int n = nums.length;
        int[] top = new int[n];
        for (int i = 0; i < n; i++) {
            // 要处理的扑克牌
            int target = nums[i];
            /* 二分查找左侧边界 start */
            int left = 0, right = len-1;
            // 二分查找寻找最左侧的插入位置
            while (left <= right) {
                int mid = (left + right) / 2;
                if (top[mid] >= target)
                    right = mid-1;
                else
                    left = mid + 1;
            }
            if (left == len ) len++; // 检查出界情况
            /* 二分查找左侧边界 end */
            // left 就是插入位置
            top[left] = target; // 把这张牌放到牌堆顶
        }
        
        return len; // 牌堆数就是 LIS 长度
    }
}

354. 俄罗斯套娃信封问题 - hard

二维上升子序列

【参考:354. 俄罗斯套娃信封问题 - 力扣(LeetCode)

方法一:变成一维上升子序列,然后使用dp
【参考:俄罗斯套娃信封问题 - 俄罗斯套娃信封问题 - 力扣(LeetCode)

class Solution {
    public int maxEnvelopes(int[][] envelopes) {
        if (envelopes.length == 0) {
            return 0;
        }
        
        int n = envelopes.length;
        Arrays.sort(envelopes, new Comparator<int[]>() {
            public int compare(int[] e1, int[] e2) {
                if (e1[0] != e2[0]) {
                    return e1[0] - e2[0];
                } else {
                    return e2[1] - e1[1];
                }
            }
        });
        
		// 一维上升子序列
        int[] f = new int[n];
        Arrays.fill(f, 1);
        int ans = 1;
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (envelopes[j][1] < envelopes[i][1]) {
                    f[i] = Math.max(f[i], f[j] + 1);
                }
            }
            ans = Math.max(ans, f[i]);
        }
        return ans;
    }
}

【参考:动态规划设计:最长递增子序列 :: labuladong的算法小抄

方法二:变成一维上升子序列,然后使用二分查找

class Solution {
    public int maxEnvelopes(int[][] envelopes) {
        int n = envelopes.length;
        // 按宽度升序排列,如果宽度一样,则按高度降序排列
        Arrays.sort(envelopes, new Comparator<int[]>() 
        {
            public int compare(int[] a, int[] b) {
                return a[0] == b[0] ? 
                    b[1] - a[1] : a[0] - b[0];
            }
        });
        // 对高度数组寻找 LIS
        int[] height = new int[n];
        for (int i = 0; i < n; i++)
            height[i] = envelopes[i][1];

        return lengthOfLIS(height);
    }

    /* 返回 nums 中 LIS 的长度 */
    public int lengthOfLIS(int[] nums) {
        int len = 0; // 二分查找数组的长度
        int n = nums.length;
        int[] top = new int[n];
        for (int i = 0; i < n; i++) {
            // 要处理的扑克牌
            int target = nums[i];
            /* 二分查找左侧边界 start */
            int left = 0, right = len-1;
            // 二分查找寻找最左侧的插入位置
            while (left <= right) {
                int mid = (left + right) / 2;
                if (top[mid] >= target)
                    right = mid-1;
                else
                    left = mid + 1;
            }
            if (left == len ) len++; // 检查出界情况
            /* 二分查找左侧边界 end */
            // left 就是插入位置
            top[left] = target; // 把这张牌放到牌堆顶
        }
        
        return len; // 牌堆数就是 LIS 长度
    }
}
// 详细解析参见:
// https://labuladong.github.io/article/?qno=354

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值