目录
什么是动态规划
怎么用动态规划
动态规划经典例题
斐波那契数
题目描述
思路
代码
爬楼梯
题目描述
思路
代码
不同路径
题目描述
思路
例题
不同路径Ⅱ
打家劫舍
打家劫舍Ⅱ
买卖股票的最佳时期
买卖股票的最佳时期Ⅱ
使用最小花费爬楼梯
不同的二叉搜索树
整数拆分
什么是动态规划
动态规划(Dynamic Programming),简称DP,是通过把原问题分解成相对简单的子问题的方式求解复杂问题的方法。
简单来说,就是给定一个问题,我们把它拆成一个个子问题,直到子问题可以直接解决。然后把子问题的答案保存起来,以减少重复计算,再根据子问题答案反推,得出原问题解的一种方法。
核心思想:拆分子问题,记住过往,减少重复计算。
适用情况:有重叠子问题和最优子结构性质的问题。
怎么用动态规划
①确定dp数组以及下标的含义;
②确定递推公式;
③初始化dp数组;
④确定遍历顺序;
⑤举例推导dp数组。
动态规划经典例题
斐波那契数
力扣题目链接509. 斐波那契数 - 力扣(LeetCode) (leetcode-cn.com)
题目描述
思路
1、确定dp数组以及下标的含义;
dp[i]定义为:第i个数的斐波那契数值为dp[i]
2、确定递推公式;
状态转移方程(题中已给):
dp[i] = dp[i-1] + dp[i-2];
3、初始化dp数组;
dp[0] = 0;
dp[1] = 1;
4、确定遍历顺序;
由状态转移方程可以看出dp[i]依赖dp[i-1]和dp[i-2],所以遍历顺序是从前到后遍历的。
5、举例推导dp数组。
举例当n=10时,dp数组应该是[0,1,1,2,3,5,8,13,21,34,55]
写完代码后如果结果不对,把dp数组打印出来对照一下。
代码
var fib = function(n) {
let dp = [];
dp[0] = 0;
dp[1] = 1;
for(let i=2;i<=n;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
};
爬楼梯
力扣题目链接70. 爬楼梯 - 力扣(LeetCode) (leetcode-cn.com)
题目描述
思路
1、确定dp数组以及下标的含义;
定义一个一维数组记录不同楼层的状态,
dp[i]定义为:爬到第i+1层楼梯,由dp[i]种方法。
2、确定递推公式;
一次可以爬1或2个台阶,则dp[i]可以从两个方向推出来:
dp[i-1]:爬到第i层楼梯,共有dp[i-1]种方法,再一次爬1个台阶就是dp[i]了;
dp[i-2]:爬到第i-1层楼梯,共有dp[i-1]种方法,再一次爬2个台阶就是dp[i]了。
那么可以推出:dp[i]就是dp[i-1]与dp[i-2]的和。
状态转移方程:
dp[i] = dp[i-1] + dp[i-2];
3、初始化dp数组;
dp[0] = 1; //爬到第1层楼梯,1种方法
dp[1] = 2;
4、确定遍历顺序;
由状态转移方程可以看出dp[i]依赖dp[i-1]和dp[i-2],所以遍历顺序是从前到后遍历的。
5、举例推导dp数组。
举例当n=5时,dp数组应该是[1,2,3,5,8]
写完代码后如果结果不对,把dp数组打印出来对照一下。
代码
var climbStairs = function(n) {
let dp = [];
dp[0] = 1;
dp[1] = 1;
for(let i=2;i<=n;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
};
不同路径
力扣题目链接62. 不同路径 - 力扣(LeetCode) (leetcode-cn.com)
题目描述
思路
1、确定dp数组以及下标的含义;
定义二维数组dp;
dp[i][j]:表示从(0,0)触发,到(i,j)有dp[i][j]条不同路径
2、确定递推公式;
因为机器人每次只能向下或向右走一路,状态转移方程:
dp[i][j] = dp[i-1][j] + dp[i][j-1];
3、初始化dp数组;
dp[i][0]全部初始化为1;
dp[0][j]全部初始化为1;
4、确定遍历顺序;
由状态转移方程可以看出dp[i]是从上边和左边推导而来,所以遍历顺序从左到右从上到下遍历。
5、举例推导dp数组。
举例当m=3,n=7时,dp数组应该是如下图所示。
同样,写完代码后如果结果不对,把dp数组打印出来对照一下。
代码
var uniquePaths = function(m, n) {
let dp = new Array(m).fill(1).map(()=>new Array(n).fill(1));
for(let i=1;i<m;i++){
for(let j=1;j<n;j++){
dp[i][j] = dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
};
例题
不同路径Ⅱ
力扣题目链接63. 不同路径 II - 力扣(LeetCode) (leetcode-cn.com)
题目描述
代码
var uniquePathsWithObstacles = function(obstacleGrid) {
let m = obstacleGrid.length,n = obstacleGrid[0].length;
let dp = new Array(m).fill(0).map(()=>new Array(n).fill(0));
for(let i=0;i<m&&obstacleGrid[i][0]===0;i++){
dp[i][0] = 1;
}
for(let i=0;i<n&&obstacleGrid[0][i]===0;i++){
dp[0][i] = 1;
}
for(let i=1;i<m;i++){
for(let j=1;j<n;j++){
if(obstacleGrid[i][j]===0)
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
};
打家劫舍
力扣题目链接198. 打家劫舍 - 力扣(LeetCode) (leetcode-cn.com)
题目描述
代码
var rob = function(nums) {
let rob = [];
rob[0] = nums[0];
rob[1] = Math.max(nums[0],nums[1]);
for(let i=2;i<nums.length;i++){
rob[i] = Math.max(rob[i-2]+nums[i],rob[i-1]);
}
return rob[nums.length-1];
};
打家劫舍Ⅱ
力扣题目链接213. 打家劫舍 II - 力扣(LeetCode) (leetcode-cn.com)
题目描述
代码
var rob = function(nums) {
if(nums.length<2){
return nums;
}
//不偷最后一家
let rob1 = [],rob2 = [];
rob1[0] = nums[0];
rob1[1] = Math.max(nums[0],nums[1]);
for(let i=2;i<nums.length-1;i++){
rob1[i] = Math.max(rob1[i-2]+nums[i],rob1[i-1]);
}
//不偷第一家
rob2[1] = nums[1];
rob2[2] = Math.max(nums[1],nums[2]);
for(let i=3;i<nums.length;i++){
rob2[i] = Math.max(rob2[i-2]+nums[i],rob2[i-1]);
}
//比较两种情况的偷窃金额
return Math.max(rob1[nums.length-2],rob2[nums.length-1]);
};
买卖股票的最佳时期
力扣题目链接121. 买卖股票的最佳时机 - 力扣(LeetCode) (leetcode-cn.com)
题目描述
代码
var maxProfit = function(prices) {
// 动态规划
let res = 0;
let pre = 0;
for(let i=1;i<prices.length;i++){
let diff = prices[i]-prices[i-1];
pre = Math.max(pre+diff,0);
res = Math.max(res,pre);
}
return res;
};
买卖股票的最佳时期Ⅱ
力扣题目链接122. 买卖股票的最佳时机 II - 力扣(LeetCode) (leetcode-cn.com)
题目描述
代码
var maxProfit = function(prices) {
// 动态规划
let dp = new Array(prices.length).fill([0,0]);
dp[0] = [-prices[0],0];
for(let i=1;i<prices.length;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[prices.length-1][1];
// 贪心
// let profit = 0;
// for(let i=1;i<prices.length;i++){
// profit += Math.max(0,prices[i]-prices[i-1]);
// }
// return profit;
};
使用最小花费爬楼梯
力扣题目链接746. 使用最小花费爬楼梯 - 力扣(LeetCode) (leetcode-cn.com)
题目描述
代码
var minCostClimbingStairs = function(cost) {
//第0个阶梯最小花费是cost[0],第1个阶梯最小花费是cost[1]
for(let i=2;i<cost.length;i++){
cost[i] = Math.min(cost[i-2],cost[i-1])+cost[i];
}
return Math.min(cost[cost.length-2],cost[cost.length-1]);
};
不同的二叉搜索树
力扣题目链接96. 不同的二叉搜索树 - 力扣(LeetCode) (leetcode-cn.com)
题目描述
代码
var numTrees = function(n) {
// 卡特兰数,动态规划
let dp = new Array(n+1).fill(0);
dp[0] = 1;
dp[1] = 1;
for(let i=2;i<n+1;i++){
for(let j=1;j<i+1;j++){
dp[i] += dp[j-1]*dp[i-j];
}
}
return dp[n];
};
整数拆分
力扣题目链接343. 整数拆分 - 力扣(LeetCode) (leetcode-cn.com)
题目描述
代码
var integerBreak = function(n) {
let dp = new Array(n+1).fill(0);
dp[1] = 1;
dp[2] = 1;
for(let i=3;i<=n;i++){
// 对于数字i,它可以拆分成两部分——j,i-j
for(let j=0;j<=i-j;j++){
// 选择可拆可不拆,拆就是dp[i-1],不拆就是i-j
dp[i] = Math.max(dp[i],j*dp[i-j],(i-j)*j);
}
}
return dp[n];
};