class067 二维动态规划【算法】

简介: class067 二维动态规划【算法】

class067 二维动态规划

code1 64. 最小路径和

// 最小路径和

// 给定一个包含非负整数的 m x n 网格 grid

// 请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

// 说明:每次只能向下或者向右移动一步。

// 测试链接 : https://leetcode.cn/problems/minimum-path-sum/

dp[i][j]:从(0,0)到(i,j)最小路径 和

dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j]

第0行:dp[0][j-1]+grid[0][j]

第0列:dp[i-1][0]+grid[i-1][0]

code1 暴力递归

code2 记忆化搜索

code3 动态规划

code4 空间压缩

package class067;
// 最小路径和
// 给定一个包含非负整数的 m x n 网格 grid
// 请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
// 说明:每次只能向下或者向右移动一步。
// 测试链接 : https://leetcode.cn/problems/minimum-path-sum/
public class Code01_MinimumPathSum {
  // 暴力递归
  public static int minPathSum1(int[][] grid) {
    return f1(grid, grid.length - 1, grid[0].length - 1);
  }
  // 从(0,0)到(i,j)最小路径和
  // 一定每次只能向右或者向下
  public static int f1(int[][] grid, int i, int j) {
    if (i == 0 && j == 0) {
      return grid[0][0];
    }
    int up = Integer.MAX_VALUE;
    int left = Integer.MAX_VALUE;
    if (i - 1 >= 0) {
      up = f1(grid, i - 1, j);
    }
    if (j - 1 >= 0) {
      left = f1(grid, i, j - 1);
    }
    return grid[i][j] + Math.min(up, left);
  }
  // 记忆化搜索
  public static int minPathSum2(int[][] grid) {
    int n = grid.length;
    int m = grid[0].length;
    int[][] dp = new int[n][m];
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        dp[i][j] = -1;
      }
    }
    return f2(grid, grid.length - 1, grid[0].length - 1, dp);
  }
  public static int f2(int[][] grid, int i, int j, int[][] dp) {
    if (dp[i][j] != -1) {
      return dp[i][j];
    }
    int ans;
    if (i == 0 && j == 0) {
      ans = grid[0][0];
    } else {
      int up = Integer.MAX_VALUE;
      int left = Integer.MAX_VALUE;
      if (i - 1 >= 0) {
        up = f2(grid, i - 1, j, dp);
      }
      if (j - 1 >= 0) {
        left = f2(grid, i, j - 1, dp);
      }
      ans = grid[i][j] + Math.min(up, left);
    }
    dp[i][j] = ans;
    return ans;
  }
  // 严格位置依赖的动态规划
  public static int minPathSum3(int[][] grid) {
    int n = grid.length;
    int m = grid[0].length;
    int[][] dp = new int[n][m];
    dp[0][0] = grid[0][0];
    for (int i = 1; i < n; i++) {
      dp[i][0] = dp[i - 1][0] + grid[i][0];
    }
    for (int j = 1; j < m; j++) {
      dp[0][j] = dp[0][j - 1] + grid[0][j];
    }
    for (int i = 1; i < n; i++) {
      for (int j = 1; j < m; j++) {
        dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
      }
    }
    return dp[n - 1][m - 1];
  }
  // 严格位置依赖的动态规划 + 空间压缩技巧
  public static int minPathSum4(int[][] grid) {
    int n = grid.length;
    int m = grid[0].length;
    // 先让dp表,变成想象中的表的第0行的数据
    int[] dp = new int[m];
    dp[0] = grid[0][0];
    for (int j = 1; j < m; j++) {
      dp[j] = dp[j - 1] + grid[0][j];
    }
    for (int i = 1; i < n; i++) {
      // i = 1,dp表变成想象中二维表的第1行的数据
      // i = 2,dp表变成想象中二维表的第2行的数据
      // i = 3,dp表变成想象中二维表的第3行的数据
      // ...
      // i = n-1,dp表变成想象中二维表的第n-1行的数据
      dp[0] += grid[i][0];
      for (int j = 1; j < m; j++) {
        dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];
      }
    }
    return dp[m - 1];
  }
}

code2 79. 单词搜索

// 单词搜索(无法改成动态规划)

// 给定一个 m x n 二维字符网格 board 和一个字符串单词 word

// 如果 word 存在于网格中,返回 true ;否则,返回 false 。

// 单词必须按照字母顺序,通过相邻的单元格内的字母构成

// 其中"相邻"单元格是那些水平相邻或垂直相邻的单元格

// 同一个单元格内的字母不允许被重复使用

// 测试链接 : https://leetcode.cn/problems/word-search/

code 递归

package class067;
// 单词搜索(无法改成动态规划)
// 给定一个 m x n 二维字符网格 board 和一个字符串单词 word
// 如果 word 存在于网格中,返回 true ;否则,返回 false 。
// 单词必须按照字母顺序,通过相邻的单元格内的字母构成
// 其中"相邻"单元格是那些水平相邻或垂直相邻的单元格
// 同一个单元格内的字母不允许被重复使用
// 测试链接 : https://leetcode.cn/problems/word-search/
public class Code02_WordSearch {
  public static boolean exist(char[][] board, String word) {
    char[] w = word.toCharArray();
    for (int i = 0; i < board.length; i++) {
      for (int j = 0; j < board[0].length; j++) {
        if (f(board, i, j, w, 0)) {
          return true;
        }
      }
    }
    return false;
  }
  // 因为board会改其中的字符
  // 用来标记哪些字符无法再用
  // 带路径的递归无法改成动态规划或者说没必要
  // 从(i,j)出发,来到w[k],请问后续能不能把word走出来w[k...]
  public static boolean f(char[][] b, int i, int j, char[] w, int k) {
    if (k == w.length) {
      return true;
    }
    if (i < 0 || i == b.length || j < 0 || j == b[0].length || b[i][j] != w[k]) {
      return false;
    }
    // 不越界,b[i][j] == w[k]
    char tmp = b[i][j];
    b[i][j] = 0;
    boolean ans = f(b, i - 1, j, w, k + 1) 
        || f(b, i + 1, j, w, k + 1) 
        || f(b, i, j - 1, w, k + 1)
        || f(b, i, j + 1, w, k + 1);
    b[i][j] = tmp;
    return ans;
  }
}

code3 1143. 最长公共子序列

// 最长公共子序列

// 给定两个字符串text1和text2

// 返回这两个字符串的最长 公共子序列 的长度

// 如果不存在公共子序列,返回0

// 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列

// 测试链接 : https://leetcode.cn/problems/longest-common-subsequence/

dp[i][j]:text1[前i个]和text2[前j个]最长公共子序列的长度

dp[i-1][j-1]+1,text1[i-1]==text2[j-1]

max(dp[i-1][j],dp[i][j-1])

第0行 :0

第0列: 0

code1 递归

code2 递归

code3 记忆化搜索

code4 动态规划

code5 空间压缩

package class067;
// 最长公共子序列
// 给定两个字符串text1和text2
// 返回这两个字符串的最长 公共子序列 的长度
// 如果不存在公共子序列,返回0
// 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列
// 测试链接 : https://leetcode.cn/problems/longest-common-subsequence/
public class Code03_LongestCommonSubsequence {
  public static int longestCommonSubsequence1(String str1, String str2) {
    char[] s1 = str1.toCharArray();
    char[] s2 = str2.toCharArray();
    int n = s1.length;
    int m = s2.length;
    return f1(s1, s2, n - 1, m - 1);
  }
  // s1[0....i1]与s2[0....i2]最长公共子序列长度
  public static int f1(char[] s1, char[] s2, int i1, int i2) {
    if (i1 < 0 || i2 < 0) {
      return 0;
    }
    int p1 = f1(s1, s2, i1 - 1, i2 - 1);
    int p2 = f1(s1, s2, i1 - 1, i2);
    int p3 = f1(s1, s2, i1, i2 - 1);
    int p4 = s1[i1] == s2[i2] ? (p1 + 1) : 0;
    return Math.max(Math.max(p1, p2), Math.max(p3, p4));
  }
  // 为了避免很多边界讨论
  // 很多时候往往不用下标来定义尝试,而是用长度来定义尝试
  // 因为长度最短是0,而下标越界的话讨论起来就比较麻烦
  public static int longestCommonSubsequence2(String str1, String str2) {
    char[] s1 = str1.toCharArray();
    char[] s2 = str2.toCharArray();
    int n = s1.length;
    int m = s2.length;
    return f2(s1, s2, n, m);
  }
  // s1[前缀长度为len1]对应s2[前缀长度为len2]
  // 最长公共子序列长度
  public static int f2(char[] s1, char[] s2, int len1, int len2) {
    if (len1 == 0 || len2 == 0) {
      return 0;
    }
    int ans;
    if (s1[len1 - 1] == s2[len2 - 1]) {
      ans = f2(s1, s2, len1 - 1, len2 - 1) + 1;
    } else {
      ans = Math.max(f2(s1, s2, len1 - 1, len2), f2(s1, s2, len1, len2 - 1));
    }
    return ans;
  }
  // 记忆化搜索
  public static int longestCommonSubsequence3(String str1, String str2) {
    char[] s1 = str1.toCharArray();
    char[] s2 = str2.toCharArray();
    int n = s1.length;
    int m = s2.length;
    int[][] dp = new int[n + 1][m + 1];
    for (int i = 0; i <= n; i++) {
      for (int j = 0; j <= m; j++) {
        dp[i][j] = -1;
      }
    }
    return f3(s1, s2, n, m, dp);
  }
  public static int f3(char[] s1, char[] s2, int len1, int len2, int[][] dp) {
    if (len1 == 0 || len2 == 0) {
      return 0;
    }
    if (dp[len1][len2] != -1) {
      return dp[len1][len2];
    }
    int ans;
    if (s1[len1 - 1] == s2[len2 - 1]) {
      ans = f3(s1, s2, len1 - 1, len2 - 1, dp) + 1;
    } else {
      ans = Math.max(f3(s1, s2, len1 - 1, len2, dp), f3(s1, s2, len1, len2 - 1, dp));
    }
    dp[len1][len2] = ans;
    return ans;
  }
  // 严格位置依赖的动态规划
  public static int longestCommonSubsequence4(String str1, String str2) {
    char[] s1 = str1.toCharArray();
    char[] s2 = str2.toCharArray();
    int n = s1.length;
    int m = s2.length;
    int[][] dp = new int[n + 1][m + 1];
    for (int len1 = 1; len1 <= n; len1++) {
      for (int len2 = 1; len2 <= m; len2++) {
        if (s1[len1 - 1] == s2[len2 - 1]) {
          dp[len1][len2] = 1 + dp[len1 - 1][len2 - 1];
        } else {
          dp[len1][len2] = Math.max(dp[len1 - 1][len2], dp[len1][len2 - 1]);
        }
      }
    }
    return dp[n][m];
  }
  // 严格位置依赖的动态规划 + 空间压缩
  public static int longestCommonSubsequence5(String str1, String str2) {
    char[] s1, s2;
    if (str1.length() >= str2.length()) {
      s1 = str1.toCharArray();
      s2 = str2.toCharArray();
    } else {
      s1 = str2.toCharArray();
      s2 = str1.toCharArray();
    }
    int n = s1.length;
    int m = s2.length;
    int[] dp = new int[m + 1];
    for (int len1 = 1; len1 <= n; len1++) {
      int leftUp = 0, backup;
      for (int len2 = 1; len2 <= m; len2++) {
        backup = dp[len2];
        if (s1[len1 - 1] == s2[len2 - 1]) {
          dp[len2] = 1 + leftUp;
        } else {
          dp[len2] = Math.max(dp[len2], dp[len2 - 1]);
        }
        leftUp = backup;
      }
    }
    return dp[m];
  }
}

code4 516. 最长回文子序列

// 最长回文子序列

// 给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度

// 测试链接 : https://leetcode.cn/problems/longest-palindromic-subsequence/

dp[i][j]:从[i,j]字符中有最长回文子序列的长度

1,i=j

1/2,s[i]==s[j],i+1=j

2+dp[i+1][j-1],s[i]==s[j]

max(dp[i+1][j],dp[i][j-1])

从左到右,从下到上

code1 递归

code2 记忆化搜索

code3 动态规划

code4 空间压缩

package class067;
// 最长回文子序列
// 给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度
// 测试链接 : https://leetcode.cn/problems/longest-palindromic-subsequence/
public class Code04_LongestPalindromicSubsequence {
  // 最长回文子序列问题可以转化成最长公共子序列问题
  // 不过这里讲述区间动态规划的思路
  // 区间dp还会有单独的视频做详细讲述
  public static int longestPalindromeSubseq1(String str) {
    char[] s = str.toCharArray();
    int n = s.length;
    return f1(s, 0, n - 1);
  }
  // s[l...r]最长回文子序列长度
  // l <= r
  public static int f1(char[] s, int l, int r) {
    if (l == r) {
      return 1;
    }
    if (l + 1 == r) {
      return s[l] == s[r] ? 2 : 1;
    }
    if (s[l] == s[r]) {
      return 2 + f1(s, l + 1, r - 1);
    } else {
      return Math.max(f1(s, l + 1, r), f1(s, l, r - 1));
    }
  }
  public static int longestPalindromeSubseq2(String str) {
    char[] s = str.toCharArray();
    int n = s.length;
    int[][] dp = new int[n][n];
    return f2(s, 0, n - 1, dp);
  }
  public static int f2(char[] s, int l, int r, int[][] dp) {
    if (l == r) {
      return 1;
    }
    if (l + 1 == r) {
      return s[l] == s[r] ? 2 : 1;
    }
    if (dp[l][r] != 0) {
      return dp[l][r];
    }
    int ans;
    if (s[l] == s[r]) {
      ans = 2 + f2(s, l + 1, r - 1, dp);
    } else {
      ans = Math.max(f2(s, l + 1, r, dp), f2(s, l, r - 1, dp));
    }
    dp[l][r] = ans;
    return ans;
  }
  public static int longestPalindromeSubseq3(String str) {
    char[] s = str.toCharArray();
    int n = s.length;
    int[][] dp = new int[n][n];
    for (int l = n - 1; l >= 0; l--) {
      dp[l][l] = 1;
      if (l + 1 < n) {
        dp[l][l + 1] = s[l] == s[l + 1] ? 2 : 1;
      }
      for (int r = l + 2; r < n; r++) {
        if (s[l] == s[r]) {
          dp[l][r] = 2 + dp[l + 1][r - 1];
        } else {
          dp[l][r] = Math.max(dp[l + 1][r], dp[l][r - 1]);
        }
      }
    }
    return dp[0][n - 1];
  }
  public static int longestPalindromeSubseq4(String str) {
    char[] s = str.toCharArray();
    int n = s.length;
    int[] dp = new int[n];
    for (int l = n - 1, leftDown = 0, backup; l >= 0; l--) {
      // dp[l] : 想象中的dp[l][l]
      dp[l] = 1;
      if (l + 1 < n) {
        leftDown = dp[l + 1];
        // dp[l+1] : 想象中的dp[l][l+1]
        dp[l + 1] = s[l] == s[l + 1] ? 2 : 1;
      }
      for (int r = l + 2; r < n; r++) {
        backup = dp[r];
        if (s[l] == s[r]) {
          dp[r] = 2 + leftDown;
        } else {
          dp[r] = Math.max(dp[r], dp[r - 1]);
        }
        leftDown = backup;
      }
    }
    return dp[n - 1];
  }
}

code5 节点数为n高度不大于m的二叉树个数

// 节点数为n高度不大于m的二叉树个数

// 现在有n个节点,计算出有多少个不同结构的二叉树

// 满足节点个数为n且树的高度不超过m的方案

// 因为答案很大,所以答案需要模上1000000007后输出

// 测试链接 : https://www.nowcoder.com/practice/aaefe5896cce4204b276e213e725f3ea

// 请同学们务必参考如下代码中关于输入、输出的处理

// 这是输入输出处理效率很高的写法

// 提交以下所有代码,把主类名改成Main,可以直接通过

思路:就是头占1个,左右占[0,n-1]

dp[i][j]:节点数为i高度不大于j的二叉树个数

∑dp[k][j-1]*dp[i-k-1][j-1],(0<=k<=i)

package class067;
// 节点数为n高度不大于m的二叉树个数
// 现在有n个节点,计算出有多少个不同结构的二叉树
// 满足节点个数为n且树的高度不超过m的方案
// 因为答案很大,所以答案需要模上1000000007后输出
// 测试链接 : https://www.nowcoder.com/practice/aaefe5896cce4204b276e213e725f3ea
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下所有代码,把主类名改成Main,可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Code05_NodenHeightNotLargerThanm {
  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StreamTokenizer in = new StreamTokenizer(br);
    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    while (in.nextToken() != StreamTokenizer.TT_EOF) {
      int n = (int) in.nval;
      in.nextToken();
      int m = (int) in.nval;
      out.println(compute3(n, m));
    }
    out.flush();
    out.close();
    br.close();
  }
  public static int MAXN = 51;
  public static int MOD = 1000000007;
  // 记忆化搜索
  public static long[][] dp1 = new long[MAXN][MAXN];
  static {
    for (int i = 0; i < MAXN; i++) {
      for (int j = 0; j < MAXN; j++) {
        dp1[i][j] = -1;
      }
    }
  }
  // 二叉树节点数为n
  // 高度不能超过m
  // 结构数返回
  // 记忆化搜索
  public static int compute1(int n, int m) {
    if (n == 0) {
      return 1;
    }
    // n > 0
    if (m == 0) {
      return 0;
    }
    if (dp1[n][m] != -1) {
      return (int) dp1[n][m];
    }
    long ans = 0;
    // n个点,头占掉1个
    for (int k = 0; k < n; k++) {
      // 一共n个节点,头节点已经占用了1个名额
      // 如果左树占用k个,那么右树就占用i-k-1个
      ans = (ans + ((long) compute1(k, m - 1) * compute1(n - k - 1, m - 1)) % MOD) % MOD;
    }
    dp1[n][m] = ans;
    return (int) ans;
  }
  // 严格位置依赖的动态规划
  public static long[][] dp2 = new long[MAXN][MAXN];
  public static int compute2(int n, int m) {
    for (int j = 0; j <= m; j++) {
      dp2[0][j] = 1;
    }
    for (int i = 1; i <= n; i++) {
      for (int j = 1; j <= m; j++) {
        dp2[i][j] = 0;
        for (int k = 0; k < i; k++) {
          // 一共i个节点,头节点已经占用了1个名额
          // 如果左树占用k个,那么右树就占用i-k-1个
          dp2[i][j] = (dp2[i][j] + dp2[k][j - 1] * dp2[i - k - 1][j - 1] % MOD) % MOD;
        }
      }
    }
    return (int) dp2[n][m];
  }
  // 空间压缩
  public static long[] dp3 = new long[MAXN];
  public static int compute3(int n, int m) {
    dp3[0] = 1;
    for (int i = 1; i <= n; i++) {
      dp3[i] = 0;
    }
    for (int j = 1; j <= m; j++) {
      // 根据依赖,一定要先枚举列
      for (int i = n; i >= 1; i--) {
        // 再枚举行,而且i不需要到达0,i>=1即可
        dp3[i] = 0;
        for (int k = 0; k < i; k++) {
          // 枚举
          dp3[i] = (dp3[i] + dp3[k] * dp3[i - k - 1] % MOD) % MOD;
        }
      }
    }
    return (int) dp3[n];
  }
}

code6 329. 矩阵中的最长递增路径

// 矩阵中的最长递增路径

// 给定一个 m x n 整数矩阵 matrix ,找出其中 最长递增路径 的长度

// 对于每个单元格,你可以往上,下,左,右四个方向移动

// 你 不能 在 对角线 方向上移动或移动到 边界外(即不允许环绕)

// 测试链接 : https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/

dp[i][j]:从(i,j)出发到达的最长递增路径

max(dp[i-1][j],dp[i+1][j],dp[i][j-1],dp[i][j+1])+1,只有对应大才能走

code1 递归

code2 记忆化搜索

package class067;
// 矩阵中的最长递增路径
// 给定一个 m x n 整数矩阵 matrix ,找出其中 最长递增路径 的长度
// 对于每个单元格,你可以往上,下,左,右四个方向移动
// 你 不能 在 对角线 方向上移动或移动到 边界外(即不允许环绕)
// 测试链接 : https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/
public class Code06_LongestIncreasingPath {
  public static int longestIncreasingPath1(int[][] grid) {
    int ans = 0;
    for (int i = 0; i < grid.length; i++) {
      for (int j = 0; j < grid[0].length; j++) {
        ans = Math.max(ans, f1(grid, i, j));
      }
    }
    return ans;
  }
  // 从(i,j)出发,能走出来多长的递增路径,返回最长长度
  public static int f1(int[][] grid, int i, int j) {
    int next = 0;
    if (i > 0 && grid[i][j] < grid[i - 1][j]) {
      next = Math.max(next, f1(grid, i - 1, j));
    }
    if (i + 1 < grid.length && grid[i][j] < grid[i + 1][j]) {
      next = Math.max(next, f1(grid, i + 1, j));
    }
    if (j > 0 && grid[i][j] < grid[i][j - 1]) {
      next = Math.max(next, f1(grid, i, j - 1));
    }
    if (j + 1 < grid[0].length && grid[i][j] < grid[i][j + 1]) {
      next = Math.max(next, f1(grid, i, j + 1));
    }
    return next + 1;
  }
  public static int longestIncreasingPath2(int[][] grid) {
    int n = grid.length;
    int m = grid[0].length;
    int[][] dp = new int[n][m];
    int ans = 0;
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        ans = Math.max(ans, f2(grid, i, j, dp));
      }
    }
    return ans;
  }
  public static int f2(int[][] grid, int i, int j, int[][] dp) {
    if (dp[i][j] != 0) {
      return dp[i][j];
    }
    int next = 0;
    if (i > 0 && grid[i][j] < grid[i - 1][j]) {
      next = Math.max(next, f2(grid, i - 1, j, dp));
    }
    if (i + 1 < grid.length && grid[i][j] < grid[i + 1][j]) {
      next = Math.max(next, f2(grid, i + 1, j, dp));
    }
    if (j > 0 && grid[i][j] < grid[i][j - 1]) {
      next = Math.max(next, f2(grid, i, j - 1, dp));
    }
    if (j + 1 < grid[0].length && grid[i][j] < grid[i][j + 1]) {
      next = Math.max(next, f2(grid, i, j + 1, dp));
    }
    dp[i][j] = next + 1;
    return next + 1;
  }
}


相关文章
|
2天前
|
算法 数据安全/隐私保护 计算机视觉
基于二维CS-SCHT变换和LABS方法的水印嵌入和提取算法matlab仿真
该内容包括一个算法的运行展示和详细步骤,使用了MATLAB2022a。算法涉及水印嵌入和提取,利用LAB色彩空间可能用于隐藏水印。水印通过二维CS-SCHT变换、低频系数处理和特定解码策略来提取。代码段展示了水印置乱、图像处理(如噪声、旋转、剪切等攻击)以及水印的逆置乱和提取过程。最后,计算并保存了比特率,用于评估水印的稳健性。
|
3天前
|
机器学习/深度学习 存储 算法
数据结构与算法 动态规划(启发式搜索、遗传算法、强化学习待完善)
数据结构与算法 动态规划(启发式搜索、遗传算法、强化学习待完善)
11 1
|
24天前
|
算法
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
34 1
|
1月前
|
算法
算法系列--动态规划--特殊的状态表示--分析重复子问题(下)
算法系列--动态规划--特殊的状态表示--分析重复子问题(下)
15 0
|
1月前
|
算法
算法系列--动态规划--特殊的状态表示--分析重复子问题(上)
算法系列--动态规划--特殊的状态表示--分析重复子问题
18 0
|
1月前
|
算法
算法系列--动态规划--背包问题(5)--二维费用背包问题(下)
算法系列--动态规划--背包问题(5)--二维费用背包问题(下)
14 0
|
1月前
|
算法
算法系列--动态规划--背包问题(5)--二维费用背包问题(上)
算法系列--动态规划--背包问题(5)--二维费用背包问题(上)
21 0
|
1月前
|
算法
算法系列--动态规划--背包问题(4)--完全背包拓展题目(下)
算法系列--动态规划--背包问题(4)--完全背包拓展题目(下)
21 0
|
1月前
|
算法
算法系列--动态规划--背包问题(4)--完全背包拓展题目(上)
算法系列--动态规划--背包问题(4)--完全背包拓展题目(上)
20 0
|
1月前
|
算法
算法系列--动态规划--背包问题(3)--完全背包介绍(下)
算法系列--动态规划--背包问题(3)--完全背包介绍(下)
19 0