前言
Leetcode算法系列:https://leetcode-cn.com/study-plan/algorithms/?progress=njjhkd2
简单总结一下动态规划相关的算法题:
关于动态规划的介绍见博客:https://blog.csdn.net/Little_ant_/article/details/124231043
一、爬楼梯
题目链接:https://leetcode-cn.com/problems/climbing-stairs/
题目描述:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
DP
此问题的状态可以描述为,f(n) :爬到第 n 阶可以采用的方法个数。根据题意可知:由于每次可以爬 1 个或 2 个台阶,所以想要爬到第 n 阶需要先到达 n-1 阶 或 先到达 n-2 阶,这两种情况都可以到达第 n 阶,在第 n-1 阶只能在爬 1 个台阶,在第 n-2 阶只能爬 2 个台阶(在此时爬 1 个台阶的情况已经包含在 f(n-1) 中了)。
状态转移方程为:f(n)=f(n-1)+f(n-2),等于 f(n-1) 和 f(n-2) 的方法数之和。
参考算法如下(自底而上):
public int climbStairs(int n) {
int[] dp=new int[n+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;i++)
dp[i]=dp[i-1]+dp[i-2];
return dp[n];
}
参考算法如下(自顶而下):
int[] dp;
public int climbStairs(int n) {
dp=new int[n+1];
dp[0]=1;
dp[1]=1;
return solve(n);
}
private int solve(int n){
if(dp[n]==0)
dp[n]=solve(n-2)+solve(n-1);
return dp[n];
}
二、打家劫舍
题目链接:https://leetcode-cn.com/problems/house-robber/
题目描述:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
DP:
假设有 n 间屋子,在偷到 n 间屋子所能得到的最大金额取决于一个限制条件,即是否在第 n-1 间屋子偷东西。如果偷的话:那么在 n 间屋子偷到的最大金额等于在前 n-1 间屋子里偷到的最大金额;否则等于在前 n-2 间屋子中偷到的最大金额加上第 n 间屋子中的金额。
所以状态描述为,f(n):一直到偷到第 n 间屋子所能得到的最大金额。
状态转移方程为:f(n)=max(f(n-1),f(n-2)+第 n 间屋子的金额)。
参考算法如下:
public int rob(int[] nums) {
int len=nums.length;
int[] dp=new int[len+1];
dp[1]=nums[0];
for(int i=2;i<dp.length;i++)
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i-1]);
return dp[dp.length-1];
}
三、三角形最小路径和
题目链接:https://leetcode-cn.com/problems/triangle/
题目描述:
给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。
DP:
最初将状态描述为,f(n):在第 n 行能够得到的最小路径和。认为在第 n 行能够得到的最小路径和与第 n-1 行得到最小路径和的位置有关,即从第 n-1 行中最小路径和的位置出发来判断第 n 行的最小路径和以及位置。然而这样做是有问题的,没有考虑到第 n 行中其他位置的元素(因为从上一行某个位置出发,那么总有在这一行中判断不了的位置)。
如果说,想直接得到某一行的最小路径和是将问题想的过于简单了,那么就稍微复杂一些,直接求出在每一行中的每一个位置的最小路径和,从一维扩展到二维。
所以将状态描述为,f(i,j):在位置(i,j)处的最小路径和。
状态转移方程为:f(i,j)=min(f(i-1,j),f(i-1,j-1))。
参考算法如下:
public int minimumTotal(List<List<Integer>> triangle) {
int n=triangle.size(); // triangle 一共有 n 个数组,最后一个数组的元素个数也为 n;
int[][] dp=new int[n][n];
dp[0][0]=triangle.get(0).get(0);
for(int i=1;i<n;i++){ // i 表示第几行,且第 i 行有 i+1 个数。
dp[i][0]=dp[i-1][0]+triangle.get(i).get(0);
for(int j=1;j<i;j++)
dp[i][j]=Math.min(dp[i-1][j-1],dp[i-1][j])+triangle.get(i).get(j);
dp[i][i]=dp[i-1][i-1]+triangle.get(i).get(i);
}
int res=Integer.MAX_VALUE;
for(int k=0;k<n;k++)
res=Math.min(res,dp[n-1][k]);
return res;
}
上面的算法可以很简单的转换为空间复杂度为 O(n) 的解法,用两个一维数组交替更新就可以了:参考算法如下:
// 上面的算法发现,其实每一次更新时,只用到了上面一个数组。所以可以用两个数组来回交替。
public int minimumTotal(List<List<Integer>> triangle) {
int n=triangle.size();
int index1=0,index2=0;// index2一直为当前要写入的数组,从 index1 中更新值。
int[][] dp=new int[2][n];
dp[index2][0]=triangle.get(0).get(0);
for(int i=1;i<n;i++){
index2=i%2;
index1=index2==1?0:1;
dp[index2][0]=dp[index1][0]+triangle.get(i).get(0);
for(int j=1;j<i;j++)
dp[index2][j]=Math.min(dp[index1][j-1],dp[index1][j])+triangle.get(i).get(j);
dp[index2][i]=dp[index1][i-1]+triangle.get(i).get(i);
}
int res=Integer.MAX_VALUE;
for(int k=0;k<n;k++)
res=Math.min(res,dp[index2][k]); //注意:当只有一个数时,index2必须初始化为0
return res;
}
总结
完