马上面临秋招,笔者也刷过不少题了,但是动态规划一直是笔者心中的痛点,大厂的笔试中动态规划已成标配,为了圆大厂梦,笔者决定沉下心,认真研究一下动态规划问题,近日看到leetcode一位大佬将动态规划问题划分为五类,私以为很受用,加上一些个人见解,将其写成博客,如有什么错误还请各位看官指正。
1、到达目标的最大/最小路径
1.1 问题描述
通常这类问题都具有如下的描述:
给定一个目标,求到达目标所需要的最大/最小代价、最大/最小和或最大/最小路径数
1.2 解题方法
以路径问题为例,通常在所有可能的路径中,最大/最小路径数都由前一个最大/最小路径加上当前的路径数来得出,我们可以列出状态转移方程:
routes[i] = min(route[i-1],routes[i-2],routes[i-3]...routes[i-k])+cost[i]
通常,解决这类问题的代码都是如下形式:
for(int i=1;i<=target;i++){
for(int j=0;j<ways.size(),j++){
dp[i]=min(dp[i],dp[i-ways[j]])+cost[i];
}
}
1.3 leetcode原题解析
以下是笔者搜集的leetcode此类问题,附上笔者的代码,在这里不讨论代码的优化问题。
746. Min Cost Climbing Stairs (Easy)
/**
*爬阶梯问题,类似于青蛙跳台阶,只不过这里加了一个cost,解题方法还是没变
*每一阶的最小代价都由前一个最小代价决定
*/
//解决方案
class Solution {
public int minCostClimbingStairs(int[] cost) {
int [] dp = new int[cost.length+1];
dp[0] = cost[0];
dp[1] = cost[1];
for (int i = 2;i<=cost.length;i++){
dp[i]=Math.min(dp[i-1],dp[i-2])+(i==cost.length?0:cost[i]);
}
return dp[cost.length];
}
}
64. Minimum Path Sum (Medium)
/**
*求到达目标点的最小和,先假设只有一行或只有一列,那么只有一条路径
*再考虑其他情况,每次只能往右或往下移
*/
//解决方案
class Solution {
public int minPathSum(int[][] grid) {
int row = grid.length;
int col = grid[0].length;
for (int i =1;i<row;i++){
grid[i][0]+=grid[i-1][0];
}
for (int j =1;j<col;j++){
grid[0][j]+=grid[0][j-1];
}
for (int i =1;i<row;i++){
for (int j =1;j<col;j++){
grid[i][j]=Math.min(grid[i-1][j],grid[i][j-1])+grid[i][j];
}
}
return grid[row-1][col-1];
}
}
322. Coin Change (Medium)
/**
*要求组成amount数量的最少硬币数,要先将0~amount之间的最少硬币数表示出来
*先用一个足够大的数字来填充dp数组(这里我用的10000),如果能换,就选小的替换掉10000
*如果能换,只要将之前的最少硬币数加1就行了,例如10变成15只需要加一个面值为5的硬币,只加了一个硬币
*/
//解决方案
class Solution {
public int coinChange(int[] coins, int amount) {
if (amount==0){
return 0;
}
int [] dp = new int[amount+1];
Arrays.fill(dp, 10000);
dp[0] = 0;
for (int j = 1;j<=amount;j++){
for (int coin : coins) {
if (coin <= j) {
dp[j] = Math.min(dp[j], (dp[j - coin]) + 1);
}
}
}
return dp[amount]==10000?-1:dp[amount];
}
}
931. Minimum Falling Path Sum (Medium)
/**
*每一个最小的path都由前一个最小path加上当前path值得到
*当j处于边界时,它的前面状态只有2个,所以需要加判断
*/
//解决方案
class Solution {
public int minFallingPathSum(int[][] A) {
int row = A.length;
int col = A[0].length;
for (int i=1;i<row;i++){
for (int j =0;j<col;j++){
if (j==0){
A[i][j] = Math.min(A[i-1][j],A[i-1][j+1])+A[i][j];
}
else if (j==col-1){
A[i][j] = Math.min(A[i-1][j],A[i-1][j-1])+A[i][j];
}
else{
A[i][j] = Math.min(Math.min(A[i-1][j-1],A[i-1][j]),A[i-1][j+1])+A[i][j];
}
}
}
Arrays.sort(A[row-1]);
return A[row-1][0];
}
}
983. Minimum Cost For Tickets (Medium)
/**
*先利用一个hashset将需要旅行的日子存储起来,当需要旅行时,判断当天的dp最小值
*取出需要旅行的最大日期,就是target,如果某一天不需要旅行,那它的花费就等于前一天的花费,因为不需要花钱
*/
//解决方案
class Solution {
public int mincostTickets(int[] days, int[] costs) {
int max_day = days[days.length-1];
int [] dp = new int[max_day+1];
dp[0] = 0;
HashSet<Integer> hashSet = new HashSet<Integer>();
for (int day : days) {
hashSet.add(day);
}
for (int i =1;i<=max_day;i++){
if (hashSet.contains(i)){
int minSevendays = i>=7?i-7:0;
int minthirtydays = i>=30?i-30:0;
dp[i] = Math.min(costs[0]+dp[i-1],Math.min(costs[1]+dp[minSevendays],costs[2]+dp[minthirtydays]));
}
else {
dp[i]=dp[i-1];
}
}
return dp[max_day];
}
}
279. Perfect Squares (Medium)
/**
*和找零钱的思路一样,区别是要先构造出coins
*/
//解决方案
class Solution {
public int numSquares(int n) {
int i = 0;
int [] dp = new int[n+1];
Arrays.fill(dp,10000);
dp[0] = 0;
ArrayList<Integer> arr = new ArrayList<>();
while (i*i<=n){
arr.add(i*i);
i++;
}
for (int j=1;j<=n;j++){
for (int coin:arr){
if (coin<=j){
dp[j] = Math.min(dp[j],dp[j-coin]+1);
}
}
}
return dp[n];
}
}
1049. Last Stone Weight II (Medium)
/**
*可以将问题转化成0-1背包问题,尽可能地装背包,使其weight最接近sum/2即可
*注意石头的重量不能超过sum/2
*/
//解决方案
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum = 0;
for (int stone : stones) {
sum += stone;
}
int [] dp = new int[sum/2+1];
int maxsize = sum/2;
for (int curstone : stones) {
for (int j = maxsize; j >= curstone; j--) {
dp[j] = Math.max(dp[j], dp[j - curstone] + curstone);
}
}
return sum-2*dp[sum/2];
}
}
120. Triangle (Medium)
/**
*这个问题与931瀑布问题类似,区别是它不是一个标准的矩形,而是有个三角形
*不难发现,当j处于临界值时,它只能来源于dp[i-1][j],当它不是临界值时,它可能来源于
*两个状态:dp[i-1][j-1]和dp[i-1][j],最后根据最后一行的dp[i]排序取最小值
*/
//解决方案
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
for (int i = 1;i<triangle.size();i++){
for (int j=0;j<triangle.get(i).size();j++){
if(j==0){
triangle.get(i).set(j,triangle.get(i-1).get(0)+triangle.get(i).get(j));
}
else if (j==triangle.get(i).size()-1){
triangle.get(i).set(j,triangle.get(i-1).get(j-1)+triangle.get(i).get(j));
}
else {
triangle.get(i).set(j,Math.min(triangle.get(i-1).get(j-1),triangle.get(i-1).get(j))+triangle.get(i).get(j));
}
}
}
triangle.get(triangle.size()-1).sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
return triangle.get(triangle.size() - 1).get(0);
}
}
474. Ones and Zeroes (Medium)
/**
*与找零钱问题类似
*这里求的是最大值,当前dp的最大值为当前的最大值和前一状态的最大值+1中的较大值
*/
//解决方案
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int len=strs.length;
int dp[][]=new int [m+1][n+1];
for(int i=0; i<len; i++)
{
int ones=0,zeros=0;
for(int j=0; j<strs[i].length(); j++)
{
if(strs[i].charAt(j)=='0')
zeros++;
else
ones++;
}
for(int k=m; k>=zeros; k--)
{
for(int l=n; l>=ones; l--)
{
dp[k][l]=Math.max(dp[k][l], dp[k-zeros][l-ones]+1);
}
}
}
return dp[m][n];
}
}
221. Maximal Square (Medium)
/**
*当一个点不为0,它的左边、左上、上边值都不为0时,它们就可以构成一个正方形,正方形的边长为4个值的最小值,以此类推
*char[i][j]-48是为了得到int类型的0和1
*/
//解决方案
class Solution {
public int maximalSquare(char[][] matrix) {
if (matrix.length<=1){
if (matrix.length==0){
return 0;
}
else {
for(int j =0;j<matrix[0].length;j++){
if (matrix[0][j]=='1'){
return 1;
}
}
return 0;
}
}
int max = 0;
int [][] tmp_matrix = new int[matrix.length][matrix[0].length];
for(int i =0 ;i<matrix.length;i++){
for(int j =0;j<matrix[0].length;j++){
tmp_matrix[i][j]= matrix[i][j]-48;
}
}
for (int i =1;i<tmp_matrix.length;i++)
{
for (int j =1;j<tmp_matrix[0].length;j++){
if (tmp_matrix[i][j]!=0){
if(tmp_matrix[i][j-1]!=0 & tmp_matrix[i-1][j-1]!=0 & tmp_matrix[i-1][j]!=0){
tmp_matrix[i][j]=Math.min(Math.min(tmp_matrix[i][j-1],tmp_matrix[i-1][j-1]),tmp_matrix[i-1][j])+1;
max = Math.max(tmp_matrix[i][j], max);
}
}
}
}
return max*max;
}
}
1.4 挑战极限
以下三个题为hard难度,笔者看到难度就已经放弃了(笑哭),但是为了内容的完整性和为大佬铺路,这里还是列出来,感兴趣的同学可以挑战一下。