动态规划

·  阅读 179

image.png image.png image.png 什么问题可以使用动态规划来解决? image.png image.png

1. leetcode509 斐波那契数

1.1 解题思路

image.png image.png

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 解题思路

image.png 填表法如下 image.png

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. 回溯 image.png image.png 2. 从终点到起始点 image.png 3. 从起始点到终点 image.png 4. 状态压缩 image.png

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 解题思路

image.png image.png image.png image.png image.png

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 解题思路

image.png 【一列一列的去填】 image.png

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 解题思路

image.png image.png

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 解题思路

image.png image.png

8.2 代码实现:🔴横着往上走

image.png image.png

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 代码实现:斜着往右走,控制步长

image.png

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 解题思路

image.png

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 解题思路

image.png image.png image.png

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:状态空间压缩为两行

image.png

10.4 代码实现2:状态空间压缩为一维

image.png

11. leetcode72 编辑距离

11.1 解题思路

image.png image.png image.png image.png

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 解题思路

image.png image.png

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 解题思路

image.png image.png

13.2 代码实现

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改