• Java-算法-动态规划-附一


    前置条件

            见Java-算法-动态规划

    例一~例三

            见 Java-算法-动态规划

    例四. leetcode62 不同路径

    一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

    机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?

    (1)递归

    以3,3为例给出递归的所有可能性

    1. class Solution {
    2. public int uniquePaths(int m, int n) {
    3. return dfs(m,n);
    4. }
    5. public int dfs(int m, int n){
    6. if(m == 1 && n == 1){
    7. return 1;
    8. }
    9. if(m == 1 && n >1){
    10. return dfs(m,n-1);
    11. }
    12. if(n == 1 && m > 1){
    13. return dfs(m-1,n);
    14. }
    15. return dfs(m-1,n)+dfs(m,n-1);
    16. }
    17. }

    (2)记忆化搜索

    显然有重复的递归操作,加上记忆化搜索

    1. class Solution {
    2. public int uniquePaths(int m, int n) {
    3. int[][] cache = new int[m+1][n+1];
    4. return dfs(m,n,cache);
    5. }
    6. public int dfs(int m, int n, int[][] cache){
    7. if(cache[m][n] != 0){
    8. return cache[m][n];
    9. }
    10. if(m == 1 && n == 1){
    11. return 1;
    12. }
    13. if(m == 1 && n >1){
    14. return dfs(m,n-1,cache);
    15. }
    16. if(n == 1 && m > 1){
    17. return dfs(m-1,n,cache);
    18. }
    19. int num = dfs(m-1,n,cache)+dfs(m,n-1,cache);
    20. cache[m][n] = num;
    21. return num;
    22. }
    23. }

    (3)动态规划

    根据递推出动态规划表格

    1. class Solution {
    2. public int uniquePaths(int m, int n) {
    3. int[][] dp = new int[m+1][n+1];
    4. dp[0][0] = 1;
    5. for(int i = 0; i < m; i ++){
    6. for(int j = 0; j < n; j++){
    7. if(i == 0 || j == 0){
    8. dp[i][j] = 1;
    9. }
    10. else{
    11. dp[i][j] = dp[i-1][j]+dp[i][j-1];
    12. }
    13. }
    14. }
    15. return dp[m-1][n-1];
    16. }
    17. }

    例五. leetcode53. 最大子数组和

    给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。

    输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
    输出:6
    解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

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

    本题总结【1】:

    • dp[i]:表示以 nums[i] 结尾的连续子数组的最大和。
    • 以 1结尾的连续子数组有 [-2,1] 和 [1] ,其中 [-2,1] 就是在「子问题 1」的后面加上 1 得到。-2 + 1 = -1 < 1−2+1=−1<1 ,因此「子问题 2」 的答案是 1。
    • 如果编号为 i 的子问题的结果是负数或者 0 ,那么编号为 i + 1 的子问题就可以把编号为 i 的子问题的结果舍弃掉,一个数 a 加上负数的结果比 a 更小;一个数 a 加上 00 的结果不会比 a 更大
    • 而子问题的定义必须以一个数结尾,因此如果子问题 i 的结果是负数或者 0,那么子问题 i + 1 的答案就是以 nums[i] 结尾的那个数。
    • 根据状态的定义,由于 nums[i] 一定会被选取,并且以 nums[i] 结尾的连续子数组与以 nums[i - 1] 结尾的连续子数组只相差一个元素 nums[i] 。

    • 假设数组 nums 的值全都严格大于 0,那么一定有 dp[i] = dp[i - 1] + nums[i]。

    • 可是 dp[i - 1] 有可能是负数,于是分类讨论:如果 dp[i - 1] > 0,那么可以把 nums[i] 直接接在 dp[i - 1] 表示的那个数组的后面,得到和更大的连续子数组;如果 dp[i - 1] <= 0,那么 nums[i] 加上前面的数 dp[i - 1] 以后值不会变大。于是 dp[i] 「另起炉灶」,此时单独的一个 nums[i] 的值,就是 dp[i]。

    • dp[0] 根据定义,只有 1 个数,一定以 nums[0] 结尾,因此 dp[0] = nums[0]。

    • 这里状态的定义不是题目中的问题的定义,不能直接将最后一个状态返回回去

    状态转移方程:

    dp[i] = \begin{cases} dp[i-1]+nums[i] & \text{ if } dp[i-1]>0 \\ nums[i]& \text{ if } dp[i-1]<=0 \end{cases}

    或者是

    dp[i] = Math.max(dp[i-1]+nums[i],nums[i])

    例六. leetcode63 不同路径II

    一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

    机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。

    现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

    网格中的障碍物和空位置分别用 1 和 0 来表示。

    输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
    输出:2
    解释:3x3 网格的正中间有一个障碍物。
    从左上角到右下角一共有 2 条不同的路径:
    1. 向右 -> 向右 -> 向下 -> 向下
    2. 向下 -> 向下 -> 向右 -> 向右

    1. class Solution {
    2. public int uniquePathsWithObstacles(int[][] obs) {
    3. int col = obs.length;
    4. int row = obs[0].length;
    5. int[][] dp = new int[col+1][row+1];
    6. for(int i = 0; i < col; i++){
    7. if(obs[i][0] == 1){
    8. break;
    9. }
    10. else{
    11. dp[i][0] = 1;
    12. }
    13. }
    14. for(int i = 0; i < row; i++){
    15. if(obs[0][i] == 1){
    16. break;
    17. }
    18. else{
    19. dp[0][i] = 1;
    20. }
    21. }
    22. for(int i = 1; i < col; i++){
    23. for(int j = 1; j < row; j++){
    24. if(obs[i][j] == 1){
    25. dp[i][j] = 0;
    26. }
    27. else{
    28. dp[i][j] = dp[i-1][j] + dp[i][j-1];
    29. }
    30. }
    31. }
    32. return dp[col-1][row-1];
    33. }
    34. }

    本题目小结:(1)基本思想和例四一样,要注意的是处理石头那边

                         (2)遇见石头dp[i][j]就是0,但是,第一行第一列注意,一旦遇见石头后面都是0

    例七. 474. 一和零

    给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

    请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。

    如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。


    输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
    输出:4
    解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
    其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

    1. class Solution {
    2. public int findMaxForm(String[] strs, int m, int n) {
    3. int len = strs.length;
    4. int[][] cnt = new int[len][2];
    5. for(int i = 0; i < len; i++){
    6. String temp = strs[i];
    7. int num0 = 0;
    8. int num1 = 0;
    9. for(int j = 0; j < temp.length(); j++){
    10. if(temp.charAt(j) == '0'){
    11. num0++;
    12. }
    13. else{
    14. num1++;
    15. }
    16. }
    17. cnt[i][0] = num0;
    18. cnt[i][1] = num1;
    19. }
    20. int[][] dp = new int[m+1][n+1];
    21. for(int k = 0; k < len; k++){
    22. for(int i = m; i >= cnt[k][0]; i--){
    23. for(int j = n; j >= cnt[k][1]; j--){
    24. dp[i][j] = Math.max(dp[i][j],dp[i-cnt[k][0]][j-cnt[k][1]]+1);
    25. }
    26. }
    27. }
    28. return dp[m][n];
    29. }
    30. }

    本题目小结:(1)待写

                         (2)待写

     

    例八. leetcode494 目标和

    给你一个整数数组 nums 和一个整数 target 。

    向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

    例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
    返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
    输入:nums = [1,1,1,1,1], target = 3
    输出:5
    解释:一共有 5 种方法让最终目标和为 3 。
    -1 + 1 + 1 + 1 + 1 = 3
    +1 - 1 + 1 + 1 + 1 = 3
    +1 + 1 - 1 + 1 + 1 = 3
    +1 + 1 + 1 - 1 + 1 = 3
    +1 + 1 + 1 + 1 - 1 = 3

    1. class Solution {
    2. public int findTargetSumWays(int[] nums, int target) {
    3. int sum = 0;
    4. for(int i : nums) sum += i;
    5. int len = nums.length;
    6. int capacity = target > 0 ? sum-target : sum + target;
    7. if(capacity < 0 ||capacity % 2 != 0) return 0;
    8. capacity /= 2;
    9. int[] dp = new int[capacity+1];
    10. dp[0] = 1;
    11. for(int i = 0; i < len; i++){
    12. for(int j = capacity; j >= nums[i]; j--){
    13. dp[j] = dp[j] + dp[j-nums[i]];
    14. }
    15. }
    16. return dp[capacity];
    17. }
    18. }

    本题目小结:(1)待写

                         (2)待写

     

    参考来源:【1】leetcode liweiwei1419 经典动态规划问题(理解「无后效性」

  • 相关阅读:
    【Hack The Box】linux练习-- Sense
    吴恩达《机器学习》1-4:无监督学习
    c语言进阶篇:指针(三)
    Spring Security(七) ——跨域配置
    5进程创建FORK
    解决Kibana初始化失败报错: Unable to connect to Elasticsearch
    OceanBase存储层代码解读(四):宏块的垃圾回收和坏块检查
    【快速幂】
    华为数通方向HCIP-DataCom H12-831题库(单选题:201-220)
    高并发面试:线程池的七大参数?手写一个线程池?
  • 原文地址:https://blog.csdn.net/weixin_45532984/article/details/124342754