动态规划

 

最优化原理:指的最优策略具有这样的性质:不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简单来说就是一个最优策略的子策略也是必须是最优的,而所有子问题的局部最优解将导致整个问题的全局最优。如果一个问题能满足最优化原理,就称其具有最优子结构性质

 

这是判断问题能否使用动态规划解决的先决条件,如果一个问题不能满足最优化原理,那么这个问题就不适合用动态规划来求解。

 

问题1:换硬币

问题2:不同的路径

问题3.1:连续子数组的最大和

问题3.2:连续子数组的最大和,输出该子数组

问题4:乘积最大子数组

问题5:跳跃游戏

问题6:三角形最小路径和

问题7:最长回文子串

问题8:最长公共子序列

 

问题1:换硬币

给出不同面额的硬币以及一个总金额. 写一个方法来计算给出的总金额可以换取的最少的硬币数量. 如果已有硬币的任意组合均无法与总金额面额相等, 那么返回 -1.

样例

样例1

输入:
[1, 2, 5]
11
输出: 3
解释: 11 = 5 + 5 + 1

样例2

输入: 
[2]
3
输出: -1

注意事项

你可以假设每种硬币均有无数个
总金额不会超过10000

public int coinChange(int[] coins, int amount) {
        // write your code here
        /*这里为什么是 amount+1 ,原因是我们需要使用到 arr[amount],为啥需要使用 arr[amount],
        是因为我们根据 类似 f(27) = min( f(27-2)+1, f(27-5)+1, f(27-27)+1 ),然后这里包括 f(27)*/
        int[] arr = new int[amount + 1];
        int length = coins.length;
        /*设置初始值*/
        arr[0] = 0;
        int i, j;
        for (i = 1; i <= amount; i++) {
            /*这里为什么把每个数变成无穷大,试想,那些 f(-1) f(-1)肯定是不合法的,然后还有另一种情况是,
             类似给定2 5 7 ,想这种f(1) f(3) 之类的肯定是找不到答案的,我们把这种找不到答案的和那些不合法的,
             设置他们为无穷大,然后后面的值就会正常运行 例如 :f(8)=f(2)+f(5)+f(1),这里有一个f(1),由于
              f(1) 在前面已经是无穷大,然后相应的 f(8) 也就会无穷大*/
            arr[i] = Integer.MAX_VALUE;
            for (j = 0; j < length; j++) {
                if (i >= coins[j] && arr[i - coins[j]] != Integer.MAX_VALUE) {
                    arr[i] = Math.min(arr[i - coins[j]]+1, arr[i]);
                }
            }
        }
        if (arr[amount] == Integer.MAX_VALUE) {
            return -1;
        }{
            return arr[amount];
        }

 问题2:不同的路径

有一个机器人的位于一个 m × n 个网格左上角。

机器人每一时刻只能向下或者向右移动一步。机器人试图达到网格的右下角。

问有多少条不同的路径?

样例

样例 1:

输入: n = 1, m = 3
输出: 1    
解释: 只有一条通往目标位置的路径。

样例 2:

输入:  n = 3, m = 3
输出: 6    
解释:
    D : Down
    R : Right
    1) DDRR
    2) DRDR
    3) DRRD
    4) RRDD
    5) RDRD
    6) RDDR

注意事项

n和m均不超过100
且答案保证在32位整数可表示范围内。

public int uniquePaths(int m, int n) {
        // write your code here
        int[][] arr = new int[m][n];
        int i, j;
        for (i = 0; i < m; i++) {
            for (j = 0; j < n; j++) {
                /*这里为什么在第一列或第一行设置为 arr[i][j] = 1,
                 因为只能向下或者向右移动,导致在第一列或第一行只有一种情况,
                 这个也是作为初始条件*/
                if (i == 0 || j == 0) {
                    arr[i][j] = 1;
                }else {
                    arr[i][j] = arr[i - 1][j] + arr[i][j - 1];
                }
            }
        }
        return arr[m - 1][n - 1];
    }

问题3.1:连续子数组的最大和
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

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

动态规划解析:
状态定义: 设动态规划列表 dp ,dp[i]代表以元素 nums[i] 为结尾的连续子数组最大和。

为何定义最大和 dp[i] 中必须包含元素 nums[i]:保证 dp[i] 递推到 dp[i+1] 的正确性;如果不包含 nums[i] ,递推时则不满足题目的 连续子数组 要求。
转移方程: 若 ddp[i−1]≤0 ,说明 dp[i - 1]对 dp[i] 产生负贡献,即 dp[i−1]+nums[i] 还不如 nums[i] 本身大。

当 dp[i - 1] > 0dp[i−1]>0 时:执行 dp[i] = dp[i-1] + nums[i];
当 dp[i−1] ≤ 0 时:执行 dp[i] = nums[i] ;
初始状态: dp[0] = nums[0],即以 nums[0]结尾的连续子数组最大和为 nums[0] 。

返回值: 返回 dp 列表中的最大值,代表全局最大值。

public int maxSubArray(int[] nums) {
        // write your code here
        int length = nums.length;
        int max = nums[0];
        for (int i = 1; i < length; i++) {
            nums[i] = Math.max(nums[i - 1] + nums[i], nums[i]);
            if (nums[i] >= max) {
                max = nums[i];
            }
        }
        return max;
    }

问题3.2:连续子数组的最大和,输出该子数组

public int[] maxSubArrayPrint(int[] nums) {
        // write your code here
        int[][] arr = new int[nums.length][2];
        int max = nums[0];
        int j = 0;
        arr[0][0] = nums[0];
        arr[0][1] = 1;
        for (int i = 1; i < nums.length; i++) {
            arr[i][1] = 1;
            arr[i][0] = arr[i - 1][0] + nums[i];
            if (arr[i][0] >= nums[i]) {
                arr[i][1] = arr[i - 1][1] + 1;
            }
            arr[i][0] = Math.max(arr[i][0], nums[i]);
            if (arr[i][0] >= max) {
                max = arr[i][0];
                j = i;
            }
        }
        int length = arr[j][1];
        int[] list = new int[length];
        for (int i = length - 1; i >= 0; i--) {
            list[i] = nums[j--];
        }
        return list;
    }

问题4:乘积最大子数组
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

示例 1:

输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:

输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

动态规划:

public int maxProduct(int[] nums) {
        //write your code here
        int length = nums.length;
        int[] maxArr = new int[length];
        int[] minArr = new int[length];
        int max = nums[0];
        maxArr[0] = nums[0];
        minArr[0] = nums[0];
        for (int i = 1; i < length; i++) {
            maxArr[i] = Math.max(Math.max(maxArr[i - 1] * nums[i], minArr[i - 1] * nums[i]), nums[i]);
            minArr[i] = Math.min(Math.min(maxArr[i - 1] * nums[i], minArr[i - 1] * nums[i]), nums[i]);
            if (maxArr[i] >= max) {
                max = maxArr[i];
            }
        }
        return max;
    }

问题5:跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:

输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。

示例 2:

输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
public boolean canJump(int[] A) {
        // write your code here
        int length = A.length;
        boolean[] arr = new boolean[length];
        arr[0] = true;
        for (int j = 1; j < length; j++) {
            arr[j] = false;
            /*这个 for 循环是为了证明是否能跳到第 j 个位置上*/
            for (int i = 0; i < j; i++) {
                if (arr[i] && i + A[i] >= j) {
                    arr[j] = true;
                    break; // break 只能跳出一层循环
                }
            }
        }
        return arr[length - 1];
    }

 问题6:三角形最小路径和

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。
例如,给定三角形:

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)    

思路:

我们用 f[i][j] 表示从三角形顶部走到位置 (i, j) 的最小路径和。这里的位置 (i, j) 指的是三角形中第 i 行第 j 列(均从 0 开始编号)的位置。
由于每一步只能移动到下一行「相邻的节点」上,因此要想走到位置 (i,j),上一步就只能在位置 (i - 1, j - 1) 或者位置 (i - 1, j)。
我们在这两个位置中选择一个路径和较小的来进行转移,
状态转移方程为: f[i][j]
= min(f[i-1][j-1], f[i-1][j]) + c[i][j] 其中 c[i][j] 表示位置 (i,j) 对应的元素值。 注意第 i 行有 i+1 个元素,它们对应的 j 的范围为 [0, i]。当 j=0 或 j=i 时,上述状态转移方程中有一些项是没有意义的。
例如当 j=0 时,f[i-1][j-1] 没有意义,
因此状态转移方程为: f[i][
0] = f[i-1][0] + c[i][0] 即当我们在第 i 行的最左侧时,我们只能从第 i-1 行的最左侧移动过来。
当 j=i 时,f[i-1][j] 没有意义,因此状态转移方程为: f[i][i] = f[i-1][i-1] + c[i][i] 即当我们在第 i 行的最右侧时,我们只能从第 i-1 行的最右侧移动过来。 最终的答案即为 f[n-1][0] 到 f[n-1][n-1] 中的最小值,其中 n 是三角形的行数。
public int minimumTotal(List<List<Integer>> triangle) {
        int row = triangle.size();   //有几行
        int[][] arr = new int[row][row];
        arr[0][0] = triangle.get(0).get(0);
        int i, j;
        for (i = 1; i < row; i++) {
            arr[i][0] = arr[i - 1][0] + +triangle.get(i).get(0);
            for (j = 1; j < i; j++) {
                arr[i][j] = Math.min(arr[i - 1][j - 1], arr[i - 1][j]) + triangle.get(i).get(j);
            }
            arr[i][i] = arr[i - 1][i - 1] + triangle.get(i).get(i);
        }
        int min = arr[row - 1][0];
        for (i = 1; i < arr[row - 1].length; i++) {
            min = Math.min(min, arr[row - 1][i]);
        }
        return min;
    }

问题7:最长回文字串

 

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

思路

public String longestPalindrome(String s) {
        int length = s.length();
        boolean[][] dp = new boolean[length][length];
        int len, i, j;
        String str = "";
        for (len = 0; len < length; len++) { //  len加上 1,即 len+1 代表子串的长度
            for (i = 0; i + len < length; i++) {
                j = i + len;
                if (len == 0) {  //  len==0时,代表字串长度是 1
                    dp[i][j] = true;
                } else if (len == 1) {
                    dp[i][j] = s.charAt(i) == s.charAt(j);
                } else {
                    dp[i][j] = dp[i + 1][j - 1] && (s.charAt(i) == s.charAt(j));
                }
                if (dp[i][j] && (len + 1) > str.length()) {
                    str = s.substring(i, j + 1);
                }
            }
        }
        return str;
    }

 问题8:最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(

也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
若这两个字符串没有公共子序列,则返回 0。

示例 1:

输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace",它的长度为 3。

示例 2:

输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc",它的长度为 3。

示例 3:

输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0。

 

思路:

https://leetcode-cn.com/problems/longest-common-subsequence/solution/dong-tai-gui-hua-zhi-zui-chang-gong-gong-zi-xu-lie/

public int longestCommonSubsequence(String text1, String text2) {
        int len1 = text1.length();
        int len2 = text2.length();
        int[][] arr = new int[len1 + 1][len2 + 1];
        int i, j;
        for (i = 1; i < len1 + 1; i++) {
            for (j = 1; j < len2 + 1; j++) {
                if (text2.charAt(j - 1) == text1.charAt(i - 1)) {
                    arr[i][j] = arr[i - 1][j - 1] + 1;
                } else {
                    arr[i][j] = Math.max(arr[i - 1][j], arr[i][j] = arr[i][j - 1]);
                }
            }
        }
        return arr[len1][len2];
    }

 

 

 

posted @ 2020-11-07 21:28  王余阳  阅读(249)  评论(0)    收藏  举报