开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第32天,点击查看活动详情 这也是第44篇文章
前言
这篇文章要讨论的主题是动态规划(dp)。
实际上,dp是一个很宽泛的概念,我也知道在自己这篇文章中的有限篇幅里根本不可能描摹出它的全貌,所涉及的甚至只是一些入门的皮毛,但是也还是想写篇文章说明一下这种思想的重要性,以及说明它其实也没有人们想象中的那么难。
接下来就以两道题作为例子。
剑指 Offer 47. 礼物的最大价值
题目
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
思路
要做dp的题目,关键在于确定其状态转移方程。在这里每一次选择,要么拿礼物,要么不拿礼物,看哪种选择的收益更大,就选择哪种。
代码实现
二维dp
class Solution {
public int maxValue(int[][] grid) {
int m=grid.length, n=grid[0].length;
for(int i=0;i<m;i++) {
for(int j=0;j<n;j++) {
if(i==0 && j==0) continue;
if(i==0) grid[i][j] += grid[i][j-1] ;
else if(j==0) grid[i][j] += grid[i- 1][j];
else grid[i][j]+=Math.max(grid[i][j-1], grid[i-1][j]);
}
}
return grid[m-1][n-1];
}
}
这种写法还可以被进一步简化(展平)为一维dp:
class Solution {
public int maxValue(int[][] grid) {
int n=grid.length;
int m=grid[0].length;
int [] dp=new int [m+1];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
dp[j]=grid[i-1][j-1]+Math.max(dp[j],dp[j-1]);
}
}
return dp[m];
}
}
另一道很相似的题目是最低票价。
983. 最低票价
题目
在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为
days的数组给出。每一项是一个从1到365的整数。火车票有 三种不同的销售方式 :
- 一张 为期一天 的通行证售价为
costs[0]美元;- 一张 为期七天 的通行证售价为
costs[1]美元;- 一张 为期三十天 的通行证售价为
costs[2]美元。通行证允许数天无限制的旅行。 例如,如果我们在第
2天获得一张 为期 7 天 的通行证,那么我们可以连着旅行 7 天:第2天、第3天、第4天、第5天、第6天、第7天和第8天。返回 你想要完成在给定的列表
days中列出的每一天的旅行所需要的最低消费 。
思路
状态转移方程为:
代码实现
class Solution {
ArrayList<Integer> list;
int [] costs;
Integer [] mem;
public int mincostTickets(int[] days, int[] costs) {
Arrays.sort(days);
list=(ArrayList<Integer>)Arrays.stream(days).boxed().collect(Collectors.toList());
this.costs=costs;
mem=new Integer [366];
return dp(1);
}
public int dp(int i){
if(i>365) return 0;
if(mem[i]!=null) return mem[i];
if(list.contains(i)){
mem[i]=Math.min((costs[0]+dp(i+1)),Math.min((costs[1]+dp(i+7)),costs[2]+dp(i+30)));
}
else mem[i]=dp(i+1);
return mem[i];
}
}