Merge pull request #1827 from juguagua/leetcode-modify-the-code-of-the-dp

更新动态规划部分:从"爬楼梯" 至 “整数拆分”
This commit is contained in:
程序员Carl
2022-12-20 10:58:20 +08:00
committed by GitHub
3 changed files with 90 additions and 90 deletions

View File

@ -53,7 +53,7 @@ dp[i] 爬到第i层楼梯有dp[i]种方法
2. 确定递推公式
可以推出dp[i]呢?
可以推出dp[i]呢?
从dp[i]的定义可以看出dp[i] 可以有两个方向推出来。
@ -73,7 +73,7 @@ dp[i] 爬到第i层楼梯有dp[i]种方法
在回顾一下dp[i]的定义爬到第i层楼梯有dp[i]中方法。
那么i为0dp[i]应该是多少呢,这个可以有很多解释,但基本是直接奔着答案去解释的。
那么i为0dp[i]应该是多少呢,这个可以有很多解释,但基本是直接奔着答案去解释的。
例如强行安慰自己爬到第0层也有一种方法什么都不做也就是一种方法即dp[0] = 1相当于直接站在楼顶。
@ -91,7 +91,7 @@ dp[i] 爬到第i层楼梯有dp[i]种方法
我相信dp[1] = 1dp[2] = 2这个初始化大家应该都没有争议的。
所以我的原则是不考虑dp[0]如初始化只初始化dp[1] = 1dp[2] = 2然后从i = 3开始递推这样才符合dp[i]的定义。
所以我的原则是不考虑dp[0]如初始化只初始化dp[1] = 1dp[2] = 2然后从i = 3开始递推这样才符合dp[i]的定义。
4. 确定遍历顺序
@ -163,7 +163,7 @@ public:
这道题目还可以继续深化,就是一步一个台阶,两个台阶,三个台阶,直到 m个台阶有多少种方法爬到n阶楼顶。
这又有难度了,这其实是一个完全背包问题,但力扣上没有这种题目,所以后续我在讲解背包问题的时候,今天这道题还会从背包问题的角度上来再讲一遍。
这又有难度了,这其实是一个完全背包问题,但力扣上没有这种题目,所以后续我在讲解背包问题的时候,今天这道题还会从背包问题的角度上来再讲一遍。
这里我先给出我的实现代码:
@ -194,7 +194,7 @@ public:
这一连套问下来,候选人算法能力如何,面试官心里就有数了。
**其实大厂面试最喜欢问题就是这种简单题,然后慢慢变化,在小细节上考察候选人**
**其实大厂面试最喜欢问题就是这种简单题,然后慢慢变化,在小细节上考察候选人**
@ -255,37 +255,37 @@ class Solution {
class Solution:
def climbStairs(self, n: int) -> int:
# dp[i] 为第 i 阶楼梯有多少种方法爬到楼顶
dp=[0]*(n+1)
dp[0]=1
dp[1]=1
for i in range(2,n+1):
dp[i]=dp[i-1]+dp[i-2]
dp = [0]*(n+1)
dp[0] = 1
dp[1] = 1
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
# 空间复杂度为O(1)版本
class Solution:
def climbStairs(self, n: int) -> int:
dp=[0]*(n+1)
dp[0]=1
dp[1]=1
dp = [0]*(n+1)
dp[0] = 1
dp[1] = 1
for i in range(2,n+1):
tmp=dp[0]+dp[1]
dp[0]=dp[1]
dp[1]=tmp
tmp = dp[0] + dp[1]
dp[0] = dp[1]
dp[1] = tmp
return dp[1]
```
### Go
```Go
func climbStairs(n int) int {
if n==1{
if n == 1 {
return 1
}
dp:=make([]int,n+1)
dp[1]=1
dp[2]=2
for i:=3;i<=n;i++{
dp[i]=dp[i-1]+dp[i-2]
dp := make([]int, n+1)
dp[1] = 1
dp[2] = 2
for i := 3; i <= n; i++ {
dp[i] = dp[i-1] + dp[i-2]
}
return dp[n]
}

View File

@ -35,7 +35,7 @@
dp[i]分拆数字i可以得到的最大乘积为dp[i]。
dp[i]的定义贯彻整个解题过程下面哪一步想不懂了就想想dp[i]究竟表示的是啥!
dp[i]的定义贯彻整个解题过程下面哪一步想不懂了就想想dp[i]究竟表示的是啥!
2. 确定递推公式
@ -93,7 +93,7 @@ for (int i = 3; i <= n ; i++) {
}
}
```
注意 枚举j的时候是从1开始的。从0开始的话那么让拆分一个数拆个0的话,那么求最大乘积没有意义了。
注意 枚举j的时候是从1开始的。从0开始的话那么让拆分一个数拆个0求最大乘积没有意义了。
j的结束条件是 j < i - 1 其实 j < i 也是可以的不过可以节省一步例如让j = i - 1的话其实在 j = 1的时候这一步就已经拆出来了重复计算所以 j < i - 1
@ -109,15 +109,15 @@ for (int i = 3; i <= n ; i++) {
}
```
因为拆分一个数n 使之乘积最大那么一定是拆分m个近似相同的子数相乘才是最大的
因为拆分一个数n 使之乘积最大那么一定是拆分m个近似相同的子数相乘才是最大的
例如 6 拆成 3 * 3 10 拆成 3 * 3 * 4 100的话 也是拆成m个近似数组的子数 相乘才是最大的
只不过我们不知道m究竟是多少而已但可以明确的是m一定大于等于2既然m大于等于也就是 最差也应该是拆成两个相同的 可能是最大值
只不过我们不知道m究竟是多少而已但可以明确的是m一定大于等于2既然m大于等于2也就是 最差也应该是拆成两个相同的 可能是最大值
那么 j 遍历只需要遍历到 n/2 就可以后面就没有必要遍历了一定不是最大值
至于 拆分一个数n 使之乘积最大那么一定是拆分m个近似相同的子数相乘才是最大的 这个我就不去做数学证明了感兴趣的同学可以自己证明
至于 拆分一个数n 使之乘积最大那么一定是拆分m个近似相同的子数相乘才是最大的 这个我就不去做数学证明了感兴趣的同学可以自己证明
5. 举例推导dp数组
@ -221,14 +221,14 @@ public:
class Solution {
public int integerBreak(int n) {
//dp[i] 为正整数 i 拆分后的结果的最大乘积
int[]dp=new int[n+1];
dp[2]=1;
for(int i=3;i<=n;i++){
for(int j=1;j<=i-j;j++){
int[] dp = new int[n+1];
dp[2] = 1;
for(int i = 3; i <= n; i++) {
for(int j = 1; j <= i-j; j++) {
// 这里的 j 其实最大值为 i-j,再大只不过是重复而已,
//并且,在本题中,我们分析 dp[0], dp[1]都是无意义的,
//j 最大到 i-j,就不会用到 dp[0]与dp[1]
dp[i]=Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
dp[i] = Math.max(dp[i], Math.max(j*(i-j), j*dp[i-j]));
// j * (i - j) 是单纯的把整数 i 拆分为两个数 也就是 i,i-j ,再相乘
//而j * dp[i - j]是将 i 拆分成两个以及两个以上的个数,再相乘。
}
@ -254,7 +254,7 @@ class Solution:
```
### Go
```golang
```go
func integerBreak(n int) int {
/**
动态五部曲
@ -264,40 +264,25 @@ func integerBreak(n int) int {
4.确定遍历顺序
5.打印dp
**/
dp:=make([]int,n+1)
dp[1]=1
dp[2]=1
for i:=3;i<n+1;i++{
for j:=1;j<i-1;j++{
dp := make([]int, n+1)
dp[1] = 1
dp[2] = 1
for i := 3; i < n+1; i++ {
for j := 1; j < i-1; j++ {
// i可以差分为i-j和j。由于需要最大值故需要通过j遍历所有存在的值取其中最大的值作为当前i的最大值在求最大值的时候一个是j与i-j相乘一个是j与dp[i-j].
dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]))
dp[i] = max(dp[i], max(j*(i-j), j*dp[i-j]))
}
}
return dp[n]
}
func max(a,b int) int{
if a>b{
func max(a, b int) int{
if a > b {
return a
}
return b
}
```
### Rust
```rust
pub fn integer_break(n: i32) -> i32 {
let n = n as usize;
let mut dp = vec![0; n + 1];
dp[2] = 1;
for i in 3..=n {
for j in 1..i-1 {
dp[i] = dp[i].max((i - j) * j).max(dp[i - j] * j);
}
}
dp[n] as i32
}
```
### Javascript
```Javascript
var integerBreak = function(n) {
@ -313,6 +298,21 @@ var integerBreak = function(n) {
};
```
### Rust
```rust
pub fn integer_break(n: i32) -> i32 {
let n = n as usize;
let mut dp = vec![0; n + 1];
dp[2] = 1;
for i in 3..=n {
for j in 1..i-1 {
dp[i] = dp[i].max((i - j) * j).max(dp[i - j] * j);
}
}
dp[n] as i32
}
```
### TypeScript
```typescript

View File

@ -83,7 +83,7 @@ dp[i - 2] 跳到 dp[i] 需要花费 dp[i - 2] + cost[i - 2]。
那么 dp[0] 应该是多少呢? 根据dp数组的定义到达第0台阶所花费的最小体力为dp[0]那么有同学可能想那dp[0] 应该是 cost[0],例如 cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] 的话dp[0] 就是 cost[0] 应该是1。
这里就要说名了,本题力扣为什么改题意,而且修改题意之后 就清晰很多的原因了。
这里就要说本题力扣为什么改题意,而且修改题意之后 就清晰很多的原因了。
新题目描述中明确说了 “你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。” 也就是说 从 到达 第 0 个台阶是不花费的,但从 第0 个台阶 往上跳的话,需要花费 cost[0]。
@ -101,7 +101,7 @@ dp[i - 2] 跳到 dp[i] 需要花费 dp[i - 2] + cost[i - 2]。
> **但是稍稍有点难度的动态规划,其遍历顺序并不容易确定下来**。
> 例如01背包都知道两个for循环一个for遍历物品嵌套一个for遍历背包容量那么为什么不是一个for遍历背包容量嵌套一个for遍历物品呢 以及在使用一维dp数组的时候遍历背包容量为什么要倒序呢
**这些都遍历顺序息息相关。当然背包问题后续「代码随想录」都会重点讲解的!**
**这些都遍历顺序息息相关。当然背包问题后续「代码随想录」都会重点讲解的!**
5. 举例推导dp数组
@ -182,7 +182,7 @@ public:
## 总结
大家可以发现这道题目相对于 昨天的[动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html)难了一点,但整体思路是一样。
大家可以发现这道题目相对于 昨天的[动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html)难了一点,但整体思路是一样
从[动态规划:斐波那契数](https://programmercarl.com/0509.斐波那契数.html)到 [动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html)再到今天这道题目,录友们感受到循序渐进的梯度了嘛。
@ -243,43 +243,43 @@ class Solution {
```python
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
dp = [0] * (len(cost))
dp[0] = cost[0]
dp[1] = cost[1]
for i in range(2, len(cost)):
dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]
return min(dp[len(cost) - 1], dp[len(cost) - 2])
dp = [0] * (len(cost) + 1)
dp[0] = 0
dp[1] = 0
for i in range(2, len(cost) + 1):
dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i-2])
return dp[len(cost)]
```
### Go
```Go
func minCostClimbingStairs(cost []int) int {
dp := make([]int, len(cost))
dp[0], dp[1] = cost[0], cost[1]
for i := 2; i < len(cost); i++ {
dp[i] = min(dp[i-1], dp[i-2]) + cost[i]
}
return min(dp[len(cost)-1], dp[len(cost)-2])
f := make([]int, len(cost) + 1)
f[0], f[1] = 0, 0
for i := 2; i <= len(cost); i++ {
f[i] = min(f[i-1] + cost[i-1], f[i-2] + cost[i-2])
}
return f[len(cost)]
}
func min(a, b int) int {
if a < b {
return a
}
return b
if a < b {
return a
}
return b
}
```
### Javascript
```Javascript
var minCostClimbingStairs = function(cost) {
const dp = [ cost[0], cost[1] ]
for (let i = 2; i < cost.length; ++i) {
dp[i] = Math.min(dp[i -1] + cost[i], dp[i - 2] + cost[i])
const n = cost.length;
const dp = new Array(n + 1);
dp[0] = dp[1] = 0;
for (let i = 2; i <= n; ++i) {
dp[i] = Math.min(dp[i -1] + cost[i - 1], dp[i - 2] + cost[i - 2])
}
return Math.min(dp[cost.length - 1], dp[cost.length - 2])
return dp[n]
};
```
@ -289,19 +289,19 @@ var minCostClimbingStairs = function(cost) {
function minCostClimbingStairs(cost: number[]): number {
/**
dp[i]: 走到第i阶需要花费的最少金钱
dp[0]: cost[0];
dp[1]: cost[1];
dp[0]: 0;
dp[1]: 0;
...
dp[i]: min(dp[i - 1], dp[i - 2]) + cost[i];
dp[i]: min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
*/
const dp: number[] = [];
const length: number = cost.length;
dp[0] = cost[0];
dp[1] = cost[1];
const dp = [];
const length = cost.length;
dp[0] = 0;
dp[1] = 0;
for (let i = 2; i <= length; i++) {
dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];
dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
}
return Math.min(dp[length - 1], dp[length - 2]);
return dp[length];
};
```