动态规划-基础篇-01
第九章 动态规划
动态规划五部曲
- 确定dp数组及下标的含义。
- 确定递推公式。
- 初始化dp数组。
- 确定遍历顺序。
- 举例推导dp数组。
509.斐波那契数
关键词:动态规划、边界处理
我们只要遍历n然后生成数即可,由于下一个值只与前两位的值有关,所以我们可以省略dp数组,直接使用两个变量存储值即可。
class Solution {
public int fib(int n) {
int a=0,b=1,temp=0;
if(n<2){ return n;}
for(int i=2;i<=n;i++){
temp = a+b;
a = b;
b = temp;
}
return b;
}
}
我的实现,使用递归
class Solution {
public int fib(int n) {
if(n==0 || n==1){
return n;
}
return fib(n-1) + fib(n-2);
}
}
70.爬楼梯
关键词:动态规划、边界处理
这题实际上也是斐波那契数的变种,第n阶楼梯是由第n-1阶楼梯走1步和第n-2阶楼梯走2步得到的。我们只需修改上一题的初值即可解决该题。
class Solution {
public int climbStairs(int n) {
int a=1,b=1,temp=0;
if(n<2){ return n;}
for(int i=2;i<=n;i++){
temp = a+b;
a = b;
b = temp;
}
return b;
}
}
我觉得这是一个排列问题,而不是组合问题。1和2在不同的位置算不同的答案。
dp[1] = 1; {[1]}
dp[2] = 2; {[1,1],[2]}
dp[3] = 3; {[1,1,1],[1,2],[2,1]}
dp[4] = 5; {[1,1,2],[2,2],[1,1,1,1],[1,2,1],[2,1,1]}
...
dp[i] = dp[i-1] + dp[i-2]
因为是排列问题,所以dp[i] = dp[i-1] + dp[i-2],dp[i]表示总和为i个台阶有多少种1或2的排列,使得排列的和等于i。解释上说dp[i]要么是dp[i-1]爬一节台阶上来,要么是dp[i-2]爬两节台阶上来。不妨换一种公式的写法:dp[i] = 1*dp[i-1] + 1*dp[i-2],1*dp[i-1]表示在dp[i-1]的各种排列的基础上,排列最后的位置放一个1,总共有1*dp[i-1]种排列结果;1*dp[i-2]表示在dp[i-2]的各种排列的基础上,排列最后的位置放一个2,总共有1*dp[i-2]种排列结果。dp[i-1] + dp[i-2]的排列肯定不存在重叠的情况,所以两种排列结果相加就是dp[i]。
同理,若每次可走台阶数:{1,2,3},则dp[i]=dp[i-1] + dp[i-2] + dp[i-3]
若此题问的是组合有多少种,那就不是动态规划的题了,即dp[i]可以独立计算,与dp[j](j<i)无关。
我的解答:
//不维护dp数组的两种方法
//循环,时间复杂度:O(n)
class Solution {
public int climbStairs(int n) {
int a=1,b=2;
int res = 0;
if(n<=2) return n;
for(int i = 3;i<=n;i++){
res = a + b;
a=b;
b=res;
}
return res;
}
}
//2.递归的方法:超出时间限制,时间复杂度:O(2^n)
class Solution {
public int climbStairs(int n) {
if(n==1 || n==2) return n;
return climbStairs(n-1) + climbStairs(n-2);
}
}
//使用循环维护dp数组
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n+1];
if(n<3) return n;
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++){
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
}
746.使用最小花费爬楼梯
关键词:动态规划、边界处理
和上一题一样,第n阶楼梯是由第n-1阶楼梯走1步和第n-2阶楼梯走2步得到的,只是这题增加了花费的概念。我们使用dp数组来记录每一步的花费,可以推导出这样递归公式dp[n+2] = Math.min(dp.get(i)+cost[i],dp.get(i+1)+cost[i+1]),确定好递归公式后,我们就需要为dp数组设定初值,由于可以选择在0或1的台阶开始,因此他们的初值为0,根据递归公式我么也可以知道应该从左到右进行遍历。
class Solution {
public int minCostClimbingStairs(int[] cost) {
List<Integer> dp = new ArrayList<Integer>();
dp.add(0);
dp.add(0);
for(int i=0;i<cost.length-1;i++){
dp.add(Math.min(dp.get(i)+cost[i],dp.get(i+1)+cost[i+1]));
}
return dp.get(dp.size()-1);
}
}
我的解答:
//使用循环,维护dp数组
class Solution {
public int minCostClimbingStairs(int[] cost) {
int[] dp = new int[cost.length+1];
dp[0]=0;
dp[1]=0;
for(int i=2;i<=cost.length;++i){
dp[i] = Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
return dp[cost.length];
}
}
//不维护dp数组
class Solution {
public int minCostClimbingStairs(int[] cost) {
int a=0,b=0,res=0; //初始化
for(int i=2;i<=cost.length;++i){
res = Math.min(b+cost[i-1],a+cost[i-2]);
a=b;
b=res;
}
return res;
}
}
62.不同路径
关键词:动态规划、二维dp数组、边界处理、数学
由于机器人每次只能向下移动或者向右移动一步,那么dp[i][j]的值就是左边的路线和加上上面的路线和,递推公式为dp[i][j] = dp[i][j-1]+dp[i-1][j];。显然最上面和最左边的会产生越界错误,我们需要为其赋初值,由于限制条件,因此初值为1。最后只需返回右下角的值即为不同路径的总数。或者使用高中数学的数论基础知识解决,机器人一定会走m+n-2步,即从m+n-2中挑出m-1步向下走不就行了吗?即(C^{m-1}_{m+n-2})。
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for(int i=0;i<n;i++){
dp[0][i] = 1;
}
for(int i=0;i<m;i++){
dp[i][0] = 1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j] = dp[i][j-1]+dp[i-1][j];
}
}
return dp[m-1][n-1];
}
}
我的解答:
经验总结:递归实现的动态规划很有可能超时,所以递归方法并不是首选方法。
//二叉树,遍历计算叶子节点(叶子节点当作终点)。起点作为二叉树的根节点,在任意位置都只有向下,向右两种运动方式,可以看作二叉树的左孩子和右孩子。判断是否为叶子节点的标志是其位置(i=m-1,j=n-1)。本题只是借用二叉树的思想,操作对象并不是节点。
//超时,重复计算,时间复杂度为O(2^(m + n - 1) - 1)。其思想和递归实现的动态规划相似:位置(i,j)的路径数=位置(i+1,j)路径数 + 位置(i,j+1)路径数。
//递归的底部是终点,从终点向起点逐步计算。
class Solution {
public int uniquePaths(int m, int n) {
return dfs(0, 0, m, n);
}
public int dfs(int i, int j, int m, int n){
if(i>=m || j>=n) return 0;
if(i==m-1 && j==n-1) return 1;
return dfs(i+1, j, m, n) + dfs(i, j+1, m, n);
}
}
//二维dp数组初始化边界,一维dp数组初始化开头。从起点向终点逐步计算。双重循环
//dp[i][j]表示从位置(i,j)到终点(m-1,n-1)的路径数。最终返回dp[0][0]
//dp[m-1][],dp[][n-1]都为1
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
///初始化
for(int i=0;i<n;++i) dp[m-1][i] = 1;
for(int j=0;j<m;++j) dp[j][n-1] = 1;
//双重循环
for(int i=m-2;i>=0;--i){
for(int j=n-2;j>=0;--j){
dp[i][j] = dp[i+1][j] + dp[i][j+1];
}
}
return dp[0][0];
}
}
//dp[i][j]表示从位置(0,0)到位置(i,j)的路径数。最终返回dp[m-1][n-1]
//dp[0][],dp[][0]都为1
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
///初始化
//双重循环
}
}

浙公网安备 33010602011771号