• 算法通关村第19关【黄金】| 继续盘点高频动态规划dp问题


    回文串专题

    1.最长回文串

    思路:

    确定dp:dp[i][j]子串是否是回文串

    确定递推公式:

    例如:aa|cbc|aa dp[2][4] = dp[3][3] true

    • 如果s[i] == s[j] 那么 dp[i][j] = dp[i+1][j-1]
    • 否则dp[i][j] = false

    确定初始化:dp[i][i] = true,一个字母都是回文

    确定遍历顺序:子串从长度2开始一直到len长度,从小到大。i从小到大,不可以更换顺序

    两个一组从头遍历到尾,三个一组从头遍历到尾,456....len个一组遍历到尾

    1. class Solution {
    2. public String longestPalindrome(String s) {
    3. int len = s.length();
    4. if (len < 2) {
    5. return s;
    6. }
    7. int maxLen = 1;
    8. int begin = 0;
    9. // dp[i][j] 表示 s[i..j] 是否是回文串
    10. boolean[][] dp = new boolean[len][len];
    11. // 初始化:所有长度为 1 的子串都是回文串
    12. for (int i = 0; i < len; i++) {
    13. dp[i][i] = true;
    14. }
    15. for(int l = 2;l<=len;l++){
    16. for(int i = 0;i
    17. int j = l + i - 1;
    18. if(j>=len){
    19. break;
    20. }
    21. dp[i][j] = false;
    22. if(s.charAt(i) == s.charAt(j)){
    23. if(j-i<3){
    24. dp[i][j] = true;
    25. }else{
    26. dp[i][j] = dp[i+1][j-1];
    27. }
    28. if(dp[i][j]&&j-i+1>maxLen){
    29. maxLen = j-i+1;
    30. begin = i;
    31. }
    32. }
    33. }
    34. }
    35. return s.substring(begin, begin + maxLen);
    36. }
    37. }

    2. 分割回文串II

    思路:

    确定dp:dp[i][j]子串是否是回文串(上一问方法),f[i]第i个字符最少的分割次数

    确定递推公式:

    • 如果dp[0][i]为true说明当前整个字符串就是回文串不需要分割,那么 f[i] = 0
    • 否则dp[0][i]为false说明当前字符串需要进行分割,对当前字符串进行遍历,从当前字符串第二个字符开始(第一个字母自身就是回文),是回文子串就分割,f[i] = Math.min(f[i],f[j]+1)一直分割找到最小分割次数

    确定初始化:f[i] = Integer.MAX_VALUE

    确定遍历顺序:从前往后

    1. class Solution {
    2. public int minCut(String s) {
    3. int len = s.length();
    4. boolean[][] dp = new boolean[len][len];
    5. for(int i = 0;i
    6. dp[i][i] = true;
    7. }
    8. //标记回文子串
    9. for(int l = 2;l<=len;l++){
    10. for(int i = 0;i
    11. int j = l + i - 1;
    12. if(j>=len){
    13. break;
    14. }
    15. dp[i][j] = false;
    16. if(s.charAt(i) == s.charAt(j)){
    17. if(j-i<3){
    18. dp[i][j] = true;
    19. }else{
    20. dp[i][j] = dp[i+1][j-1];
    21. }
    22. }
    23. }
    24. }
    25. //分割次数
    26. int[] f = new int[len];
    27. for(int i = 0;i
    28. f[i] = Integer.MAX_VALUE;
    29. }
    30. for(int j = 0;j
    31. if(dp[0][j]){
    32. f[j] = 0;
    33. }else{
    34. for(int i = 0;i
    35. if(dp[i+1][j]){
    36. f[j] = Math.min(f[j],f[i] + 1);
    37. }
    38. }
    39. }
    40. }
    41. return f[len-1];
    42. }
    43. }

    双串典型问题

    1.最长公共子序列

    思路:

    确定dp:dp[i][j] text1前i个字符和text2前j个字符最长公共子序列

    确定递推公式:

    • 如果s[i] == s[j],dp[i][j] = dp[i-1][j-1]+1
    • 否则,dp[i][j] = Max(dp[i-1][j],dp[i-1][j-1],dp[i][j-1])

    确定初始化:默认都是0

    确定遍历顺序:从前往后

    1. class Solution {
    2. public int longestCommonSubsequence(String text1, String text2) {
    3. int len1 = text1.length();
    4. int len2 = text2.length();
    5. int[][] dp =new int[len1+1][len2+1];
    6. char[] s1 = text1.toCharArray();
    7. char[] s2 = text2.toCharArray();
    8. for(int i = 0;i
    9. for(int j = 0;j
    10. if(s1[i] == s2[j]){
    11. dp[i+1][j+1] = dp[i][j] + 1;
    12. }else{
    13. dp[i+1][j+1] = Math.max(dp[i][j+1],dp[i+1][j]);
    14. }
    15. }
    16. }
    17. return dp[len1][len2];
    18. }
    19. }

    2.编辑距离

    思路:

    p[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]

    相等不相等两种情况:

    if (word1[i - 1] == word2[j - 1])

    那么就不用编辑,dp[i][j] = dp[i - 1][j - 1];

    if (word1[i - 1] != word2[j - 1])

    • 操作一:word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 再加上一个操作。即 dp[i][j] = dp[i - 1][j] + 1;
    • 操作二:word2删除一个元素,那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 再加上一个操作。即 dp[i][j] = dp[i][j - 1] + 1;
    • 操作三:替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同。就是i-2和j-2的最近编辑距离 再加上一个操作。因为此时是替换不是增删就是操作左上角元素
    1. class Solution {
    2. public int minDistance(String word1, String word2) {
    3. char[] s1 = word1.toCharArray();
    4. char[] s2 = word2.toCharArray();
    5. int len1 = s1.length;
    6. int len2 = s2.length;
    7. int[][] dp = new int[len1+1][len2+1];
    8. for(int i = 0;i<=len1;i++){
    9. dp[i][0] = i;
    10. }
    11. for(int i = 0;i<=len2;i++){
    12. dp[0][i] = i;
    13. }
    14. for(int i = 0;i
    15. for(int j = 0;j
    16. if(s1[i] == s2[j]){
    17. dp[i+1][j+1] = dp[i][j];
    18. }else{
    19. dp[i+1][j+1] = Math.min(dp[i][j],Math.min(dp[i][j+1],dp[i+1][j]))+1;
    20. }
    21. }
    22. }
    23. return dp[len1][len2];
    24. }
    25. }

     3.正则表达式匹配

    思路:

    确定dp:dp[i][j] s前i个字符和p前j个字符是否能匹配

    确定递推公式:

    • 如果s[i] == p[j],dp[i][j] = dp[i-1][j-1]
    • 如果p[j] == '.',dp[i][j] = dp[i-1][j-1]
    • 如果p[j] == '*'
      • 如果s[i] == p[j-1]||p[j-1] == '.' 匹配0个dp[i][j] = dp[i][j-2],匹配1个dp[i][j] = dp[i][j-1],匹配多个dp[i][j] = dp[i-1][j]
      • 否则,匹配0个dp[i][j] = dp[i][j-2]

    确定初始化:dp[0][0]=true 空串匹配

    确定遍历顺序:从前往后

    1. class Solution {
    2. public boolean isMatch(String s, String p) {
    3. int lens = s.length();
    4. int lenp = p.length();
    5. char[] sc = s.toCharArray();
    6. char[] pc = p.toCharArray();
    7. boolean[][] dp = new boolean[lens+1][lenp+1];
    8. dp[0][0] = true;
    9. for(int i = 0;i<=lens;i++){
    10. for(int j = 1;j<=lenp;j++){
    11. //从1开始防止越界
    12. if(i>0&&(pc[j-1] == '.'||pc[j-1] == sc[i-1])){
    13. dp[i][j] = dp[i-1][j-1];
    14. }else if(pc[j-1] == '*'){
    15. if(i>0&&(pc[j-2] == sc[i-1]||pc[j-2] == '.')){
    16. //匹配0/1、多个,需要加上匹配0个,a ..* .*丢弃
    17. dp[i][j]=dp[i][j-1]|dp[i-1][j]|dp[i][j-2];
    18. }else{
    19. dp[i][j] = dp[i][j-2];
    20. }
    21. }
    22. }
    23. }
    24. return dp[lens][lenp];
    25. }
    26. }

    乘积最大子数组

    思路:

    确定dp:

    dp[i]第i个字符最大乘积dp[i]=max(dp[i-1]*nums[i],nums[i]),这样忽略了负负得正的情况

    max[i]当前子数组对应的最大乘积

    min[i]当前子数组对应的最小乘积

    确定递推公式: 

    三种情况取最大和最小

    max[i] = Math.max(max[i-1]*nums[i],Math.max(min[i-1]*nums[i],nums[i]));

    确定初始化:

    • max[0] = nums[0];
    • min[0] = nums[0];
    • int res = nums[0];

    确定遍历顺序:从前往后

    1. class Solution {
    2. public int maxProduct(int[] nums) {
    3. int[] max = new int[nums.length];
    4. int[] min = new int[nums.length];
    5. max[0] = nums[0];
    6. min[0] = nums[0];
    7. int res = nums[0];
    8. for(int i = 1;i
    9. max[i] = Math.max(max[i-1]*nums[i],Math.max(min[i-1]*nums[i],nums[i]));
    10. min[i] = Math.min(max[i-1]*nums[i],Math.min(min[i-1]*nums[i],nums[i]));
    11. if(max[i]>res){
    12. res = max[i];
    13. }
    14. }
    15. return res;
    16. }
    17. }

    股票专题

    1.买卖股票最佳时机

    思路:prices[i] - min就是最大差价

    1. class Solution {
    2. public int maxProfit(int[] prices) {
    3. int min = prices[0];
    4. int gap = 0;
    5. int res = 0;
    6. for(int i = 1;i
    7. if(prices[i]
    8. min = prices[i];
    9. }
    10. gap = Math.max(gap,prices[i]-min);
    11. }
    12. return gap;
    13. }
    14. }

    2.买卖股票的最佳时机II

    思路:

    确定dp:

    dp[i][0]不持有当前股票所有资金

    dp[i][1]持有当前股票所有资金

    确定递推公式:

    • dp[i][0] = Math.max(dp[i-1][1] + prices[i],dp[i-1][0]); 前一天持有当天卖出/前一天也不持有
    • dp[i][1] = Math.max(dp[i-1][0]-prices[i] ,dp[i-1][1]); 前一天不持有当天买入/前一天也持有

    确定初始化:dp[0][0]=0 dp[0][1] = -prices[0]当天买入

    确定遍历顺序:从前往后

    1. class Solution {
    2. public int maxProfit(int[] prices) {
    3. int[][] dp = new int[prices.length][2];
    4. dp[0][0] = 0;
    5. dp[0][1] = -prices[0];
    6. for(int i = 1;i
    7. dp[i][0] = Math.max(dp[i-1][1] + prices[i],dp[i-1][0]);
    8. dp[i][1] = Math.max(dp[i-1][0]-prices[i] ,dp[i-1][1]);
    9. }
    10. return dp[prices.length-1][0];
    11. }
    12. }

    3.买卖股票最佳时机III

    思路:

    确定dp:

    dp[i][0][1]不持有当前股票所有资金,并且处于或者已完成第一次交易

    dp[i][1][2]持有当前股票所有资金,并且处于或者已经完成第二次交易

    确定递推公式:

    • dp[i][0][1] = Math.max(dp[i-1][0][1],dp[i-1][1][1]+prices[i]);继续不持有/第一次卖出
    • dp[i][1][1] = Math.max(dp[i-1][1][1],-prices[i]);继续持有/买入
    • dp[i][0][2] = Math.max(dp[i-1][0][2],dp[i-1][1][2] + prices[i]);继续不持有/第二次卖出
    • dp[i][1][2] = Math.max(dp[i-1][0][1]-prices[i],dp[i-1][1][2]);第二次买入/继续持有

    确定初始化:dp[0][1][1]=-prices[0] dp[0][1][2] = -prices[0]持有状态说明买入

    确定遍历顺序:从前往后

    1. class Solution {
    2. public int maxProfit(int[] prices) {
    3. int[][][] dp = new int[prices.length][2][3];
    4. dp[0][0][0] = 0;
    5. dp[0][1][1] = -prices[0];
    6. dp[0][1][2] = -prices[0];
    7. for(int i = 1;i
    8. dp[i][0][1] = Math.max(dp[i-1][0][1],dp[i-1][1][1]+prices[i]);
    9. dp[i][1][1] = Math.max(dp[i-1][1][1],-prices[i]);
    10. dp[i][0][2] = Math.max(dp[i-1][0][2],dp[i-1][1][2] + prices[i]);
    11. dp[i][1][2] = Math.max(dp[i-1][0][1]-prices[i],dp[i-1][1][2]);
    12. }
    13. return Math.max(dp[prices.length-1][0][1],dp[prices.length-1][0][2]);
    14. }
    15. }

    打家劫舍

    思路:

    确定dp:dp[i]到当前房屋所能获得的最大金额

    确定递推公式:dp[i] = Math.max(dp[i-1],dp[i-2] + nums[i]);偷当前房屋和不偷当前房屋

    确定初始化:dp[0] = nums[0] dp[1] = max(nums[0],nums[1])

    确定遍历顺序:从前往后

    1. class Solution {
    2. public int rob(int[] nums) {
    3. if(nums.length == 1){
    4. return nums[0];
    5. }
    6. if(nums.length == 0){
    7. return 0;
    8. }
    9. int[] dp = new int[nums.length];
    10. dp[0] = nums[0];
    11. dp[1] = Math.max(nums[0],nums[1]);
    12. for(int i = 2;i
    13. dp[i] = Math.max(dp[i-1],dp[i-2] + nums[i]);
    14. }
    15. return dp[nums.length-1];
    16. }
    17. }

     

  • 相关阅读:
    C语言-指针详解速成
    什么是分卷压缩
    React事件绑定的方式有哪些?区别?
    NOIP1998-2018 CSP-S2 2019 2021提高组解题报告与视频
    LFS在VMware Fusion 中启动时两种报错的解决办法
    太戈编程第1628、1629、1630、1631题
    Vue 打包成桌面应用 vue 打包桌面应用 vue部署为桌面应用 vue部署桌面应用 vue 桌面应用
    STM32开发时HardFault错误的排查
    火星探测器背后的人工智能:从原理到实战的强化学习
    Git 回滚命令笔记
  • 原文地址:https://blog.csdn.net/Candy___i/article/details/134019354