LeeCode动态规划
本质优雅的穷举,用数组存答案消重叠,
动态规划基本概念
1.定义
动态规划与分治法类似,其基本思想也是将待求问题分解成若干子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合与用动态规划求解的问题,经分解得到的子问题往往不是互相独立的,若使用分治法来解决这类问题,则分解得到的子问题数目太多,以至于最后解决原问题需要耗费指数时间,然而,不同子问题的数目常常只有多项式量级。在用分治法求解时,有些子问题被重复计算了许多次。如果能够保存已经解决子问题的答案,在需要的时候再找出这些已经求得的答案,这样就可以避免大量的重复运算,从而得到多项式时间算法。
为了达到上述目的,可以使用一个表来记录所有已经解决子问题的答案。不管子问题以后是否被用到,只要它被计算过,就将结果填入表中。这就是动态规划的基本思想。
2、无后效性
如果给定某一阶段的状态,则在这一阶段以后过程的发展不受这阶段以前各段状态的影响。即未来与过去无关。例如,一旦f(n)确定,“我们如何凑出f(n)”就再也用不着了。要求出f(15),只需要知道f(14),f(10),f(4)的值,而f(14),f(10),f(4)是如何算出来的,对之后的问题没有影响。
3、最优子结构
回顾我们对f(n)的定义:我们记“凑出n所需的最少钞票数量”为f(n)。f(n)的定义就已经蕴含了“最优”。利用w=14,10,4的最优解,我们即可算出w=15的最优
大问题最优解可以由小问题的最优解推出。
背包问题,根据选择写出状态转移逻辑
int dp[N+1][amount+1]
//BaseCase条件
if dp【0】【、、】=0 dp【、、】【0】=1
for i in【1、、N】:
for j in 【1、、amount】:
if判断是否要不要
若能装下,装不装
else 继承上一步状态
return dp【N】【amount】
Solution322零钱兑换
这是一个完全背包问题
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
class Solution {
public int coinChange(int[] coins, int amount) {
int max = Integer.MAX_VALUE;
int[] dp = new int[amount + 1];
//初始化dp数组为最大值
for (int j = 0; j < dp.length; j++) {
dp[j] = max;
}
//当金额为0时需要的硬币数目为0
dp[0] = 0;
for (int i = 0; i < coins.length; i++) {
//正序遍历:完全背包每个硬币可以选择多次
for (int j = coins[i]; j <= amount; j++) {
//只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要
if (dp[j - coins[i]] != max) {
//选择硬币数目最小的情况
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
}
return dp[amount] == max ? -1 : dp[amount];
}
}
Solution55跳跃游戏
给定一个非负整数数组
nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
class Solution {
public int jump(int[] nums) {
if(nums == null || nums.length == 0){
return 0;
}
int step = 0;
int cur = 0;
int next = 0;
for(int i = 0; i < nums.length; i++){
if(cur < i){
step++;
cur = next;
}
next = Math.max(next, i+nums[i]);
}
return step;
}
}
Solution45跳跃游戏Ⅱ
动态规划可以用贪心思想求更高的效率
给你一个非负整数数组 nums ,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。
以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点,这个范围内最小步数一定可以跳到,不用管具体是怎么跳的,不纠结于一步究竟跳一个单位还是两个单位。
//动态规化
class Solution {
public int jump(int[] nums) {
int[] dp = new int[nums.length];
int res = 0;
for (int i = 1; i < nums.length; ++i) {
while (i > res + nums[res]) {
res ++;
}
dp[i] = dp[res] + 1;
}
return dp[nums.length - 1];
}
}
//带贪心思想的动态规划
class Solution {
public int jump(int[] nums) {
if (nums == null || nums.length == 0 || nums.length == 1) {
return 0;
}
//记录跳跃的次数
int count=0;
//当前的覆盖最大区域
int curDistance = 0;
//最大的覆盖区域
int maxDistance = 0;
for (int i = 0; i < nums.length; i++) {
//在可覆盖区域内更新最大的覆盖区域
maxDistance = Math.max(maxDistance,i+nums[i]);
//说明当前一步,再跳一步就到达了末尾
if (maxDistance>=nums.length-1){
count++;
break;
}
//走到当前覆盖的最大区域时,更新下一步可达的最大区域
if (i==curDistance){
curDistance = maxDistance;
count++;
}
}
return count;
}
}
Solution62不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for(int i = 0;i < m; i++){
for(int j = 0; j < n; j++){
if (i == 0 || j == 0){
dp[i][j] = 1;
}else {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
}
return dp[m-1][n-1];
}
}
Solution64最小路径和
给定一个包含非负整数的
m x n
网格grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下或者向右移动一步。
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
// 状态定义:dp[i][j] 表示从 [0,0] 到 [i,j] 的最小路径和
int[][] dp = new int[m][n];
// 状态初始化
dp[0][0] = grid[0][0];
// 状态转移
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]);
}
}
}
// 返回结果
return dp[m - 1][n - 1];
}
}
Solution198打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
class Solution {
public int rob(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
dp[0] = nums[0];
if(n == 1){
return dp[0];
}
dp[1] = Math.max(nums[0],nums[1]);
for (int i = 2;i < n;i++){
dp[i] = Math.max(dp[i - 1],dp[i - 2] + nums[i]);
}
return dp[n - 1];
}
}
Solution213打家劫舍Ⅱ
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
环状的数组,可以分开讨论
class Solution {
public int rob(int[] nums) {
int len = nums.length;
if(len == 1){
return nums[0];
}
if(len == 2){
return Math.max(nums[0],nums[1]);
}
//偷一不偷尾
int[] dp1 = new int[len];
//偷尾不偷一
int[] dp2 = new int[len];
dp1[0] = nums[0];
dp1[1] = Math.max(nums[0], nums[1]);
dp2[1] = nums[1];
dp2[2] = Math.max(nums[1], nums[2]);
for(int i = 2; i < len - 1;i++){
dp1[i] = Math.max(dp1[i-1],dp1[i-2]+nums[i]);
}
for(int i = 3; i < len; i++){
dp2[i] = Math.max(dp2[i-1],dp2[i-2]+nums[i]);
}
return Math.max(dp1[len-2],dp2[len-1]);
}
}