leetcode 中另一个重要的解题思想–动态规划
动态规划是一种思想,不是具体的指某种算法,与分治法类似,通过拆分问题,定义问题状态和状态之间的关系,使问题能够以递推(分治)的方式去解决。
动态规划的特点:
- 局部解 - 全部解
- 局部解 - 最优解
动态规划的思路:
- 将大问题分解为子问题
- 状态转移方程
- 边界情况
这里用一个具体的例子说明,leetcode70.爬楼梯
题目描述:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
我们可以计算输入4:
示例 4:
输入: 4
输出: 5
解释: 有五种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 1 阶 + 2 阶
3. 1 阶 + 2 阶 + 1 阶
4. 2 阶 + 1 阶 + 1 阶
5. 2 阶 + 2 阶
那么规律就是
in = 1、out = 1
in = 2、out = 2
in = 3、out = 3 (1+2)
in = 4、out = 5(2+3)
…
in = n、out = in(n-1) + in(n-2)
这里可以看出,在第n个的值是由n-1 和 n-2的结果相加的(斐波那契数列),最后一个就是要得到的状态转换方程,所以思路就有了,大问题n是由于n-1和n-2解决的,3是由2和1解决的,就是累加嘛,状态转换方程:in = n、out = in(n-1) + in(n-2),边界条件 n,所以代码可以这样写:
public int climb(int n) {
int[] dp = new int[n+1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
这个例子比较直观,下面例子的思路也是动态规划,但是加了一些限制:
198.打家劫舍
题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你**在不触动警报装置的情况下,**能够偷窃到的最高金额
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
这里多了一个考虑的部分,相邻两个数不能同时取
- 如果数组只有两个数,那我们就得知道第一个与第二个比较,取大的;
- 如果数组只有三个数,那我们就得知道第一个加第三个的和与第二个比较,取大的;
- 如果数组只有四个数,那我们就得知道第四个加第二个之前的最大值的和与第三个之前的最大值比较,取大的
- …
代码如下:
public int rob(int[] nums) {
//异常情况
if (nums == null || nums.length == 0) {
return 0;
}
//特殊情况
if (nums.length == 1) {
return nums[0];
}
int[] dp = new int[nums.length];
//第一个默认最大
dp[0] = nums[0];
//每两个元素之间取大的
dp[1] = Math.max(nums[0], nums[1]);
//遍历
for (int i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[nums.length - 1];
}
leetcode中按照动态规划求解的题型代表还有如下:
- 279.完全平方数
- 309.最佳买卖股票时期含冷冻期
- 312.戳气球
- 337.打家劫舍Ⅲ