算法问题——动态规划算法理解

动态规划的就是的将原问题拆解成若干子问题,同时保存子问题的答案,使得每个子问题只求解一次 ,最终获得原问题的答案。

动态规划是一种自下而上的一种的思考:就是这个问题是由于前一个问题+加上当前的问题的而得到的结果。F(i)=x+F(i-x)这样的一种的表达。这样的是一种递归的调用,这样的会占用系统的栈空间的,所以在很多时候是最好不采用这样的一种方式。而记忆化搜索是一种的自上而下的一种的方法来实现的。F(i)+x=F(i+x)的这样的一种表现形式。用小数据问题解决大数据问题。采用的是的数组来记录好当前的转态的一种的。

算法问题——动态规划算法理解_第1张图片

70. 爬楼梯

算法问题——动态规划算法理解_第2张图片

    /**
     * 递归函数
     *
     * @param n
     * @return
     */
    public int test(int n) {
        return calway(n);
    }

    private int calway(int n) {
        if (n == 1 || n == 2) {
            return n;
        }
        return calway(n - 1) + calway(n - 2);
    }
 /**
     * 记忆化递归的思想
     *
     * @param n
     * @return
     */
    public int solution(int n) {
        //这个是记忆的数组 记录是的每一个台阶的方法
        int[] memeo = new int[n + 1];
        return climbstairs1(n, memeo);
    }

    private int climbstairs1(int n, int[] memeo) {
        if (memeo[n] > 0) {
            return memeo[n];
        }
        if (n == 1 || n == 2) {
            memeo[n] = n;
        } else {
            memeo[n] = climbstairs1(n - 1, memeo) + climbstairs1(n - 2, memeo);
        }
        return memeo[n];
    }

120. 三角形最小路径和

算法问题——动态规划算法理解_第3张图片

状态的转移方程:dp[i][j]=Math.min(dp[i-1][j-1],dp[i-1][j])+tragle[i][j]

边界的dp[i][0]=dp[i-1][0]+trangle[i][0]

边界的dp[i][j]=dp[i-1][i-1]=trangle[i][i]

/**
 * Copyright (C), 2018-2020
 * FileName: 最小路径和64
 * Author:   xjl
 * Date:     2020/9/7 12:27
 * Description:
 */
package 动态规划问题集合;

import org.junit.Test;

/**
 * 写出这个转态的转移方程式
 * dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+dp[i][j]
 */
public class 最小路径和64 {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        if (grid == null || m == 0 || n == 0) {
            return 0;
        }
        //1 设置一个数组
        int[][] dp = new int[m][n];

        //3 确定的边界的时候
        dp[0][0] = grid[0][0];
        //当列为0的时候的边界
        for (int i = 1; i < m; i++) {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        //当行为0的时候的边界
        for (int i = 1; i < n; i++) {
            dp[0][i] = dp[0][i - 1] + grid[0][i];
        }

        //2 状态转移方程
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }
        //结果是最后的
        return dp[m - 1][n - 1];
    }

    @Test
    public void test() {
        int[][] array = {
    {1, 3, 1}, {1, 5, 1}, {4, 2, 1}};
        int i = minPathSum(array);
        System.out.println(i);
    }
}

120. 三角形最大路径和||

64. 最小路径和

/**
 * Copyright (C), 2018-2020
 * FileName: 最小路径和64
 * Author:   xjl
 * Date:     2020/9/7 12:27
 * Description:
 */
package 动态规划问题集合;

/**
 * 写出这个转态的转移方程式 dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+dp[i][j]
 */
public class 最小路径和64 {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        if (grid == null || m == 0 || n == 0) {
            return 0;
        }
        //1 设置一个数组
        int[][] dp = new int[m][n];

        //3 确定的边界的时候
        dp[0][0] = grid[0][0];
        //当列为0的时候的边界
        for (int i = 1; i < m; i++) {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        //当行为0的时候的边界
        for (int i = 1; i < n; i++) {
            dp[0][i] = dp[0][i - 1] + grid[0][i];
        }

        //2 状态转移方程
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }
        //结果是最后的
        return dp[m - 1][n - 1];
    }
}

343. 整数拆分

算法问题——动态规划算法理解_第4张图片

算法问题——动态规划算法理解_第5张图片

/**
 * Copyright (C), 2018-2020
 * FileName: 整数拆分343
 * Author:   xjl
 * Date:     2020/9/7 14:04
 * Description:
 */
package 动态规划问题集合;

public class 整数拆分343 {
    /**
     * 分割两个部分 这是一个递归问题 时间hi超过限制  可能需要采用的记忆化递归的算法
     *
     * @param n
     * @return
     */
    static int[] array;

    public static int integerBreak(int n) {
        array = new int[n + 1];
        return test(n);
    }

    /**
     * 这个超过时间限制 也是使用了记忆化搜索的
     *
     * @param n
     * @return
     */
    public static int test(int n) {
        if (n == 1) {
            return 1;
        }
        if (array[n] != 0) {
            return array[n];
        }
        int res = -1;
        //能分割多少的中的方法嗯?
        for (int i = 1; i <= n - 1; i++) {
            res = Math.max(res, Math.max(i * (n - i), i * test(n - i)));
        }
        return res;
    }

    /**
     * 记忆化搜索
     *
     * @param n
     * @return
     */
    public static int integerBreak1(int n) {
        array = new int[n + 1];
        array[1] = 1;
        for (int i = 2; i <= n; i++) {
            //求解array[i] 将 i 进行分割 j i-j这的两个
            for (int j = 1; j <= i - 1; j++) {
                // j+(i-j)
                //计算 i的分割的最大的值保留  array[i - j]前面已经计算出来了所以是
                array[i] = Math.max(Math.max(j * (i - j), j * array[i - j]), array[i]);
            }
        }
        return array[n];
    }

    public static void main(String[] args) {
        int i = integerBreak1(30);
        System.out.println(i);
    }
}

279. 完全平方数

正如上述贪心算法的复杂性分析种提到的,调用堆栈的轨迹形成一颗 N 元树,其中每个结点代表 is_divided_by(n, count) 函数的调用。基于上述想法,我们可以把原来的问题重新表述如下:

给定一个 N 元树,其中每个节点表示数字 n 的余数减去一个完全平方数的组合,我们的任务是在树中找到一个节点,该节点满足两个条件:

(1) 节点的值(即余数)也是一个完全平方数。
(2) 在满足条件(1)的所有节点中,节点和根之间的距离应该最小。

算法问题——动态规划算法理解_第6张图片

在前面的方法3中,由于我们执行调用的贪心策略,我们实际上是从上到下逐层构造 N 元树。我们以 BFS(广度优先搜索)的方式遍历它。在 N 元树的每一级,我们都在枚举相同大小的组合。

遍历的顺序是 BFS,而不是 DFS(深度优先搜索),这是因为在用尽固定数量的完全平方数分解数字 n 的所有可能性之前,我们不会探索任何需要更多元素的潜在组合。

//这样的问题还有一个特点就是都是1--n的一个效果。    
public int numSquares1(int n) {
        int[] dp = new int[n + 1]; // 默认初始化值都为0
        for (int i = 1; i <= n; i++) {
            dp[i] = i; // 最坏的情况就是每次+1 就是采用的是的全部是1的这样的方式
            for (int j = 1; i - j * j >= 0; j++) {
                //每次去看这个j表示的是的平方数 一定是小于这i的
                dp[i] = Math.min(dp[i], dp[i - j * j] + 1); // 动态转移方程
            }
        }
        return dp[n];
    }
class Solution {
  public int numSquares(int n) {

    ArrayList square_nums = new ArrayList();
    for (int i = 1; i * i <= n; ++i) {
      square_nums.add(i * i);
    }

    Set queue = new HashSet();
    queue.add(n);

    int level = 0;
    while (queue.size() > 0) {
      level += 1;
      Set next_queue = new HashSet();

      for (Integer remainder : queue) {
        for (Integer square : square_nums) {
          if (remainder.equals(square)) {
            return level;
          } else if (remainder < square) {
            break;
          } else {
            next_queue.add(remainder - square);
          }
        }
      }
      queue = next_queue;
    }
    return level;
  }
}

 91. 解码方法

算法问题——动态规划算法理解_第7张图片

"12321"的解码数 = "1232"的解码数 + "123"的解码数,当然也要考虑后1、2位能否构成合法的字母。

/**
 * Copyright (C), 2018-2020
 * FileName: 解码方法91
 * Author:   xjl
 * Date:     2020/9/8 14:04
 * Description:
 */
package 动态规划问题集合;

import java.util.HashMap;

public class 解码方法91 {

    public int numDecodings(String s) {
        return getAns(s, 0);
    }

    private int getAns(String s, int start) {
        //划分到了最后返回 1
        if (start == s.length()) {
            return 1;
        }
        //开头是 0,0 不对应任何字母,直接返回 0
        if (s.charAt(start) == '0') {
            return 0;
        }
        //得到第一种的划分的解码方式
        int ans1 = getAns(s, start + 1);
        int ans2 = 0;
        //判断前两个数字是不是小于等于 26 的
        if (start < s.length() - 1) {
            int ten = (s.charAt(start) - '0') * 10;
            int one = s.charAt(start + 1) - '0';
            if (ten + one <= 26) {
                ans2 = getAns(s, start + 2);
            }
        }
        return ans1 + ans2;
    }

    public int numDecodings2(String s) {
        int len = s.length();
        int[] dp = new int[len + 1];
        //将递归法的结束条件初始化为 1
        dp[len] = 1;
        //最后一个数字不等于 0 就初始化为 1
        if (s.charAt(len - 1) != '0') {
            dp[len - 1] = 1;
        }
        for (int i = len - 2; i >= 0; i--) {
            //当前数字时 0 ,直接跳过,0 不代表任何字母
            if (s.charAt(i) == '0') {
                continue;
            }
            int ans1 = dp[i + 1];
            //判断两个字母组成的数字是否小于等于 26
            int ans2 = 0;
            int ten = (s.charAt(i) - '0') * 10;
            int one = s.charAt(i + 1) - '0';
            if (ten + one <= 26) {
                ans2 = dp[i + 2];
            }
            dp[i] = ans1 + ans2;
        }
        return dp[0];
    }

    public int numDecodings3(String s) {
        HashMap memoization = new HashMap<>();
        return getAns(s, 0, memoization);
    }

    private int getAns(String s, int start, HashMap memoization) {
        //表示的是的s的长度 到了最后的一个分割位置
        if (start == s.length()) {
            return 1;
        }
        //如果还第一个位置为0 表示的是的0
        if (s.charAt(start) == '0') {
            return 0;
        }
        //判断之前是否计算过
        int m = memoization.getOrDefault(start, -1);
        if (m != -1) {
            return m;
        }
        int ans1 = getAns(s, start + 1, memoization);
        int ans2 = 0;
        if (start < s.length() - 1) {
            int ten = (s.charAt(start) - '0') * 10;
            int one = s.charAt(start + 1) - '0';
            if (ten + one <= 26) {
                ans2 = getAns(s, start + 2, memoization);
            }
        }
        //将结果保存
        memoization.put(start, ans1 + ans2);
        return ans1 + ans2;
    }
}

62. 不同路径

算法问题——动态规划算法理解_第8张图片

public int uniquePaths(int m, int n) {
        //d[i][j]: start->(i,j) 一共有多少个uniqueunique paths
        //d[i][j]=dp[i-1][j]+d[i][j-1]
        //d[0][0]=1

        int[][] dp=new int[m][n];
        dp[0][0]=1;
        for(int i=0;i0){
                    dp[i][j]+=dp[i-1][j];
                }
                if(j>0){
                    dp[i][j]+=dp[i][j-1];
                }
            }
        }
        return dp[m-1][n-1];
    }

63. 不同路径 II

算法问题——动态规划算法理解_第9张图片

 /**
     * 一种的直接从(0,0)开始的计算  然后按照这个每一行的计算
     * @param obstacleGrid
     * @return
     */
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int[][] dp = new int[m][n];

        dp[0][0] = 1;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (obstacleGrid[i][j] == 1) {
                    dp[i][j] = 0;
                } else {
                    if (j > 0) {
                        dp[i][j] += dp[i][j - 1];
                    }
                    if (i > 0) {
                        dp[i][j] += dp[i - 1][j];
                    }
                }
            }
        }
        return dp[m - 1][n - 1];
    }
/**
*一种的方法是:开始的把两边的开始    * 开始的从(1,1) 开始的的时候计算
*/
class Solution {
    public int uniquePathsWithObstacles(int[][] ob) {
        int m = ob.length;
        int n = ob[0].length;
        if(m == 0 || n == 0) return 0;
        // `ob[i][j] == 1`表示无障碍物,`dp[i][j] = dp[i - 1][j] + d[i][j - 1]`
        int[][] dp = new int[m][n];
        // 初始化
        // 第0行和第0列分别遍历,赋值为1;
        // 如果碰到障碍物,从这个点之后全为0,因为碰到障碍物,之后都不可达了。
        for(int i = 0; i < m; i++) {
            if(ob[i][0] == 1) break;
            else dp[i][0] = 1;
        }
        for(int i = 0; i < n; i++) {
            if(ob[0][i] == 1) break;
            else dp[0][i] = 1;
        }

        for(int i = 1; i < m; i++){
            for(int j = 1; j < n; j++){
                // `ob[i][j] == 1`表示有障碍物,直接令`dp[i][j] = 0`;
                if(ob[i][j] == 1) dp[i][j] = 0;
                // `ob[i][j] == 0`表示无障碍物,`dp[i][j] = dp[i - 1][j] + d[i][j - 1]`
                else dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[m - 1][n - 1];
    }
}

198. 打家劫舍

算法问题——动态规划算法理解_第10张图片

算法问题——动态规划算法理解_第11张图片

 

首先考虑最简单的情况。如果只有一间房屋,则偷窃该房屋,可以偷窃到最高总金额。如果只有两间房屋,则由于两间房屋相邻,不能同时偷窃,只能偷窃其中的一间房屋,因此选择其中金额较高的房屋进行偷窃,可以偷窃到最高总金额。

如果房屋数量大于两间,应该如何计算能够偷窃到的最高总金额呢?对于第 k (k>2)k~(k>2)k (k>2) 间房屋,有两个选项:

    偷窃第 kkk 间房屋,那么就不能偷窃第 k−1k-1k−1 间房屋,偷窃总金额为前 k−2k-2k−2 间房屋的最高总金额与第 kkk 间房屋的金额之和。

    不偷窃第 kkk 间房屋,偷窃总金额为前 k−1k-1k−1 间房屋的最高总金额。

在两个选项中选择偷窃总金额较大的选项,该选项对应的偷窃总金额即为前 kkk 间房屋能偷窃到的最高总金额。

用 dp[i]dp[i]dp[i] 表示前 iii 间房屋能偷窃到的最高总金额,那么就有如下的状态转移方程:

算法问题——动态规划算法理解_第12张图片

class Solution {
    public int rob(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int length = nums.length;
        if (length == 1) {
            return nums[0];
        }
        int[] dp = new int[length];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);
        for (int i = 2; i < length; i++) {
            dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[length - 1];
    }
}
/**
 * Copyright (C), 2018-2020
 * FileName: 打劫1
 * Author:   xjl
 * Date:     2020/9/8 15:58
 * Description:
 */
package 动态规划问题集合;

import java.util.Arrays;

public class 打劫1 {
    public static int rob(int[] nums) {
        return testrob(nums, 0);
    }

    /**
     * 考虑的是的nums[index…… nums.size()]这个范围的所有的房子
     *
     * @param nums
     * @param index
     * @return
     */
    public static int testrob(int[] nums, int index) {
        if (index >= nums.length) {
            return 0;
        }
        //开始抢劫这个房子以后的所有房子
        int res = 0;
        for (int i = index; i < nums.length; i++) {
            res = Math.max(res, nums[i] + testrob(nums, index + 2));
        }
        return res;
    }

    /**
     * 采用的是记忆化搜索的方法来实现
     * @param nums
     * @param index
     * @return
     */
    static int[] memo;

    public static int rob1(int[] nums) {
        memo = new int[nums.length];
        Arrays.fill(memo, -1);
        return testrob2(nums, 0);
    }

    public static int testrob2(int[] nums, int index) {
        if (index >= nums.length) {
            return 0;
        }
        //判断时候有值 如果有值就不需要计算了  如果是没有就需要的是的计算
        if (memo[index] != -1) {
            return memo[index];
        }
        //开始抢劫这个房子以后的所有房子
        int res = 0;
        for (int i = index; i < nums.length; i++) {
            res = Math.max(res, nums[i] + testrob2(nums, i + 2));
        }
        //每一次将这个保留这个转态
        memo[index] = res;
        return res;
    }
}
public int rob4(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        // 子问题:
        // f(k) = 偷 [0..k) 房间中的最大金额
        // f(0) = 0
        // f(1) = nums[0]
        // f(k) = max{ rob(k-1), nums[k-1] + rob(k-2) }
        int N = nums.length;
        //表示的是的最大的数字
        int[] dp = new int[N + 1];
        dp[0] = 0;
        dp[1] = nums[0];
        for (int k = 2; k <= N; k++) {
            dp[k] = Math.max(dp[k - 1], nums[k - 1] + dp[k - 2]);
        }
        return dp[N];
    }

213. 打家劫舍 II

环状排列意味着第一个房子和最后一个房子中只能选择一个偷窃,因此可以把此环状排列房间问题约化为两个单排排列房间子问题:

    在不偷窃第一个房子的情况下(即 nums[1:]nums[1:]nums[1:]),最大金额是 p1,p1​ ;
    在不偷窃最后一个房子的情况下(即 nums[:n−1]nums[:n-1]nums[:n−1]),最大金额是 p2,p2​ 。

综合偷窃最大金额 为以上两种情况的较大值,即 max(p1,p2) 。

算法问题——动态规划算法理解_第13张图片

import java.util.Arrays;
public class 打劫2 {

    public int rob(int[] nums) {
        if (nums.length == 0) return 0;
        if (nums.length == 1) return nums[0];
        return Math.max(rob4(Arrays.copyOfRange(nums, 0, nums.length - 1)),rob4(Arrays.copyOfRange(nums, 1, nums.length)));
    }

    public int rob4(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        // 子问题:
        // f(k) = 偷 [0..k) 房间中的最大金额
        // f(0) = 0
        // f(1) = nums[0]
        // f(k) = max{ rob(k-1), nums[k-1] + rob(k-2) }
        int N = nums.length;
        //表示的是的最大的数字
        int[] dp = new int[N + 1];
        dp[0] = 0;
        dp[1] = nums[0];
        for (int k = 2; k <= N; k++) {
            dp[k] = Math.max(dp[k - 1], nums[k - 1] + dp[k - 2]);
        }
        return dp[N];
    }

}

337. 打家劫舍 III

/**
 * Copyright (C), 2018-2020
 * FileName: 打劫3
 * Author:   xjl
 * Date:     2020/9/13 13:41
 * Description:
 */
package 动态规划问题集合;

public class 打劫3 {

    public class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int x) {
            val = x;
        }
    }

    // 树的后序遍历

    public int rob(TreeNode root) {
        int[] res = dfs(root);
        return Math.max(res[0], res[1]);
    }

    private int[] dfs(TreeNode node) {
        if (node == null) {
            return new int[]{0, 0};
        }
        // 分类讨论的标准是:当前结点偷或者不偷
        // 由于需要后序遍历,所以先计算左右子结点,然后计算当前结点的状态值
        int[] left = dfs(node.left);
        int[] right = dfs(node.right);
        // dp[0]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点不偷
        // dp[1]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点偷
        int[] dp = new int[2];
        dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        dp[1] = node.val + left[0] + right[0];
        return dp;
    }
}

309. 最佳买卖股票时机含冷冻期

 

剑指 Offer 63. 股票的最大利润

 

901. 股票价格跨度

 

121. 买卖股票的最佳时机

 

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

 

123. 买卖股票的最佳时机 III

 

188. 买卖股票的最佳时机 IV

 

你可能感兴趣的:(数据结构与算法,算法)