动态规划
最优化原理:指的最优策略具有这样的性质:不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简单来说就是一个最优策略的子策略也是必须是最优的,而所有子问题的局部最优解将导致整个问题的全局最优。如果一个问题能满足最优化原理,就称其具有最优子结构性质。
这是判断问题能否使用动态规划解决的先决条件,如果一个问题不能满足最优化原理,那么这个问题就不适合用动态规划来求解。
问题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]; }

浙公网安备 33010602011771号