什么问题可以使用动态规划来解决?
1. leetcode509 斐波那契数
1.1 解题思路
1.2 代码实现1:动态规划
class Solution {
public int fib(int n) {
if (n <= 1)
return n;
// 1. 定义状态数组,dp[i] 表示的是数字 i 的斐波那契数
int[] dp = new int[n + 1];
// 2. 状态初始化
dp[0] = 0;
dp[1] = 1;
// 3. 状态转移
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
// 4. 返回最终需要的状态值
return dp[n];
}
}
1.3 代码实现2:🔴状态数组空间压缩
class Solution {
public int fib(int n) {
if (n <= 1)
return n;
int prev = 0;
int curr = 1;
for (int i = 2; i <= n; i++) {
int sum = prev + curr;
prev = curr;
curr = sum;
}
return curr;
}
}
2. leetcode322 零钱兑换
2.1 解题思路
填表法如下
2.2 代码实现:🔴动态规划
class Solution {
public int coinChange(int[] coins, int amount) {
if (amount < 0)
return -1;
if (amount == 0)
return 0;
// 1. 状态定义:dp[i] 表示凑齐总金额为 i 的时候需要的最小硬币数
int[] dp = new int[amount + 1];
// 2. 状态初始化
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
// 3. 状态转移
for (int target = 1; target <= amount; target++) {
for (int coin : coins) {
if (target >= coin && dp[target - coin] != Integer.MAX_VALUE) {
dp[target] = Math.min(dp[target], dp[target - coin] + 1);
}
}
}
// 4. 返回最终需要的状态值
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
}
3. leetcode64 最小路径和
3.1 解题思路
1. 回溯
2. 从终点到起始点
3. 从起始点到终点
4. 状态压缩
3.2 代码实现:从终点到起始点
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
// 1. 状态定义:dp[i][j] 表示从坐标(i, j)到右下角的最小路径和
int[][] dp = new int[m][n];
// 2. 状态初始化
dp[m - 1][n - 1] = grid[m - 1][n - 1];
// 3. 状态转移
for (int i = m - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
if (i == m - 1 && j != n - 1) {
// 最后一行
dp[i][j] = grid[i][j] + dp[i][j + 1];
} else if (i != m - 1 && j == n - 1) {
// 最后一列
dp[i][j] = grid[i][j] + dp[i + 1][j];
} else if (i != m - 1 && j != n - 1) {
dp[i][j] = grid[i][j] + Math.min(dp[i][j + 1], dp[i + 1][j]);
}
}
}
// 4. 返回结果
return dp[0][0];
}
}
3.3 代码实现:从起始点到终点
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
// 1. 状态定义:dp[i][j] 表示从 [0,0] 到 [i,j] 的最小路径和
int[][] dp = new int[m][n];
// 2. 状态初始化
dp[0][0] = grid[0][0];
// 3. 状态转移
for (int i = 0; i < m ; i++) {
for (int j = 0; j < n ; j++) {
if (i == 0 && j != 0) {
dp[i][j] = grid[i][j] + dp[i][j - 1];
} else if (i != 0 && j == 0) {
dp[i][j] = grid[i][j] + dp[i - 1][j];
} else if (i != 0 && j != 0) {
dp[i][j] = grid[i][j] + Math.min(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// 4. 返回结果
return dp[m - 1][n - 1];
}
}
3.4 代码实现:🔴空间复杂度O(1)
class Solution {
public int minPathSum(int[][] grid) {
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (i == 0 && j == 0) continue;
else if (i == 0) {
grid[i][j] = grid[i][j - 1] + grid[i][j];
} else if (j == 0) {
grid[i][j] = grid[i - 1][j] + grid[i][j];
} else {
grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];
}
}
}
return grid[grid.length - 1][grid[0].length - 1];
}
}
4. leetcode53. 最大子序和
4.1 解题思路
4.2 代码实现1:时间复杂度:O(n)、空间复杂度:O(n)
class Solution {
public int maxSubArray(int[] nums) {
// 1. 状态定义:dp[i] 表示以索引为 i 的元素结尾的最大子数组和
int[] dp = new int[nums.length];
// 2. 状态初始化
dp[0] = nums[0];
int maxSum = dp[0];
// 3. 状态转移
for (int i = 1; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
maxSum = Math.max(maxSum, dp[i]);
}
// 4. 返回最终需要的状态值
return maxSum;
}
}
4.3 代码实现2:🔴时间复杂度:O(n)、空间复杂度:O(1)
class Solution {
public int maxSubArray(int[] nums) {
int prevMaxSum = nums[0];
int maxSum = prevMaxSum;
for (int i = 1; i < nums.length; i++) {
prevMaxSum = Math.max(prevMaxSum + nums[i], nums[i]);
maxSum = Math.max(maxSum, prevMaxSum);
}
return maxSum;
}
}
5. leetcode647 回文子串
5.1 解题思路
【一列一列的去填】
5.2 代码实现:🔴时间复杂度:O(n²)
class Solution {
public int countSubstrings(String s) {
if (s == null || s.length() == 0)
return 0;
int res = 0;
// 1. 定义状态,dp[i][j] 表示子数组区间[i][j] 对应的子串是否是回文
boolean[][] dp = new boolean[s.length()][s.length()];
// 2. 状态初始化
for (int i = 0; i < s.length(); i++) {
dp[i][i] = true;
res++;
}
// 3. 状态转移 一列一列地进行转移
for (int j = 1; j < s.length(); j++) {
for (int i = 0; i < j; i++) {
boolean isEquals = s.charAt(i) == s.charAt(j);
if (j - i == 1) {
dp[i][j] = isEquals;
} else {
dp[i][j] = isEquals && dp[i + 1][j - 1];
}
if (dp[i][j])
res++;
}
}
// 4. 返回状态值
return res;
}
}
6. leetcode5 最长回文子串
6.1 解题思路
如题5
6.2 代码实现:🔴动态规划
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() == 0)
return "";
if (s.length() == 1)
return s;
boolean[][] dp = new boolean[s.length()][s.length()];
String res = s.charAt(0) + "";
for (int i = 0; i < s.length(); i++) {
dp[i][i] = true;
}
for (int j = 1; j < s.length(); j++) {
for (int i = 0; i < j; i++) {
boolean isEqual = s.charAt(i) == s.charAt(j);
if (j - i == 1) {
dp[i][j] = isEqual;
} else {
dp[i][j] = isEqual && dp[i + 1][j - 1];
}
if (dp[i][j] && j - i + 1 > res.length()) {
res = s.substring(i, j + 1);
}
}
}
return res;
}
}
6.3 代码实现:🔴中心扩展法
class Solution {
public String longestPalindrome(String s) {
int length = s.length();
// start表示最长回文串开始的位置
// maxLen表示最长回文串的长度
int start = 0;
int maxCount = 0;
for(int i = 0; i < length;){
//1. 判断剩余子串长度与最大回文子串
if((length - i) * 2 <= maxCount)
break;
int left = i;
int right = i;
//2. 如果有重复就包含进去
while(right < length - 1 && s.charAt(right) == s.charAt(right + 1))
right++;
// 下次再判断的时候从重复的下一个字符开始判断
i = right + 1;
//3. 向两边扩展
while(right < length - 1 && left > 0 && s.charAt(left - 1) == s.charAt(right + 1)){
left--;
right++;
}
if(right - left + 1 > maxCount){
start = left;
maxCount = right - left + 1;
}
}
return s.substring(start, start + maxCount);
}
}
7. leetcode131 分割回文串
7.1 解题思路
7.2 代码实现:🔴回溯 + 动态规划
class Solution {
public List<List<String>> partition(String s) {
List<List<String>> res = new ArrayList<>();
if (s == null || s.length() == 0)
return res;
// 1. 定义状态,dp[i][j] 表示子数组区间[i, j] 对应的子串是否是回文
boolean[][] dp = new boolean[s.length()][s.length()];
// 2. 状态初始化
for (int i = 0; i < s.length(); i++) {
dp[i][i] = true; // 一个字符,肯定是回文
}
// 3. 状态转移
for (int j = 1; j < s.length(); j++) {
for (int i = 0; i < j; i++) {
boolean isCharEqual = s.charAt(i) == s.charAt(j);
if (j - i == 1) {
// 只有两个字符
dp[i][j] = isCharEqual;
} else {
// 大于两个字符
dp[i][j] = isCharEqual && dp[i + 1][j - 1];
}
}
}
// 4. 返回状态值
backtrack(s, 0, new ArrayList<>(), res, dp);
return res;
}
private void backtrack(String s,
int startIndex,
List<String> path,
List<List<String>> res,
boolean[][] dp) {
if (startIndex == s.length()) {
res.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i < s.length(); i++) {
// [startIndex, endIndex]
// 如果不是回文串则进行剪枝
if (!dp[startIndex][i])
continue;
path.add(s.substring(startIndex, i + 1));
backtrack(s, i + 1, path, res, dp);
path.remove(path.size() - 1);
}
}
}
8. leetcode516 最长回文子序列
8.1 解题思路
8.2 代码实现:🔴横着往上走
class Solution {
public int longestPalindromeSubseq(String s) {
if (s == null || s.length() == 0)
return 0;
// 1. 状态定义:dp[i][j] 表示在区间 [i...j] 中最长回文子序列的长度
int[][] dp = new int[s.length()][s.length()];
// 2. 状态初始化
for (int i = 0; i < s.length(); i++) {
dp[i][i] = 1;
}
// 3. 状态转移
for (int i = s.length() - 2; i >= 0; i--) {
for (int j = i + 1; j < s.length(); j++) {
if (s.charAt(i) == s.charAt(j)) {
dp[i][j] = 2 + dp[i + 1][j - 1];
} else {
dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]);
}
}
}
// 4. 返回状态值
return dp[0][s.length() - 1];
}
}
8.3 代码实现:斜着往右走,控制步长
class Solution {
public int longestPalindromeSubseq(String s) {
if (s == null || s.length() == 0)
return 0;
// 1. 状态定义:dp[i][j] 表示在区间 [i...j] 中最长回文子序列的长度
int[][] dp = new int[s.length()][s.length()];
// 2. 状态初始化
for (int i = 0; i < s.length(); i++) {
dp[i][i] = 1;
}
// 3. 状态转移
for (int interval = 1; interval < s.length(); interval++) {
for (int i = 0; i < s.length(); i++) {
int j = i + interval;
if (j == s.length())
break;
if (s.charAt(i) == s.charAt(j)) {
dp[i][j] = 2 + dp[i + 1][j - 1];
} else {
dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]);
}
}
}
// 4. 返回状态值
return dp[0][s.length() - 1];
}
}
9. leetcode300 最长递增子序列
9.1 解题思路
9.2 代码实现:🔴动态规划
// 时间复杂度:O(N²)
// 空间复杂度:O(N)
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0)
return 0;
int maxLen = 1;
// 1. 状态定义:dp[i] 表示以索引为 i 的元素结尾的最长递增子序列的长度
int[] dp = new int[nums.length];
// 2. 状态初始化
Arrays.fill(dp, 1);
// 3. 状态转移
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(1 + dp[j], dp[i]);
maxLen = Math.max(maxLen,dp[i]);
}
}
}
// 4. 返回状态值
return maxLen;
}
}
10. leetcode1143 最长公共子序列
10.1 解题思路
10.2 代码实现1:🔴动态规划
// 时间复杂度:O(mn),其中m和n分别表是字符串text1和text2的长度
// 空间复杂度:O(mn)
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
if (text1 == null || text1.length() == 0
|| text2 == null || text2.length() == 0)
return 0;
int m = text1.length();
int n = text2.length();
// 1. 状态定义:dp[i][j] 表示字符串s前i个字符 和 字符串p前j个字符的最长公共子序列长度
int[][] dp = new int[m + 1][n + 1];
// 2. 状态初始化
// 3. 状态转移
for (int i = 1; i < m + 1; i++) {
for (int j = 1; j < n + 1; j++) {
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
dp[i][j] = 1 + dp[i - 1][j - 1];
} else {
dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
}
}
}
// 4. 返回状态值
return dp[m][n];
}
}
10.3 代码实现2:状态空间压缩为两行
10.4 代码实现2:状态空间压缩为一维
11. leetcode72 编辑距离
11.1 解题思路
11.2 代码实现🔴
// 时间复杂度:O(mn)
// 空间复杂度:O(mn)
class Solution {
public int minDistance(String word1, String word2) {
int word1Len = word1.length();
int word2Len = word2.length();
// 如果有一个字符串为空字符串,就可以提前返回另一个字符串的长度
if (word1Len * word2Len == 0) {
return word1Len + word2Len;
}
// 1. 状态定义:dp[i][j]表示word1前i个字符转换成word2前j个字符花的最少操作数
int[][] dp = new int[word1Len + 1][word2Len + 1];
// 2. 状态初始化
dp[0][0] = 0;
// 第一列
for (int i = 1; i < word1Len + 1; i++) {
dp[i][0] = i;
}
// 第一行
for (int j = 1; j < word2Len + 1; j++) {
dp[0][j] = j;
}
// 3. 状态转移
for (int i = 1; i < word1Len + 1; i++) {
for (int j = 1; j < word2Len + 1; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(1 + dp[i][j - 1],
Math.min(1 + dp[i - 1][j], 1 + dp[i - 1][j - 1]));
}
}
}
// 4. 返回状态值
return dp[word1Len][word2Len];
}
}
12. leetcode44 通配符匹配
12.1 解题思路
12.2 代码实现🔴
// 时间复杂度:O(mn)
// 空间复杂度:O(mn)
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
// 1. 状态定义:dp[i][j] 表示 s 的前 i 个字符和 p 的前 j 的字符是否匹配
boolean[][] dp = new boolean[m + 1][n + 1];
// 2. 状态初始化
dp[0][0] = true;
for (int i = 1; i <= m; i++) {
dp[i][0] = false;
}
for (int j = 1; j <= n; j++) {
if (dp[0][j - 1] == true && p.charAt(j - 1) == '*') {
dp[0][j] = true;
}
}
// 3. 状态转移
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s.charAt(i - 1) == p.charAt(j - 1)
|| p.charAt(j - 1) == '?') {
dp[i][j] = dp[i - 1][j - 1];
} else if (p.charAt(j - 1) == '*') {
dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
} else if (s.charAt(i - 1) != p.charAt(j - 1)) {
dp[i][j] = false;
}
}
}
// 4. 返回状态值
return dp[m][n];
}
}
13. leetcode486 预测赢家
13.1 解题思路