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

更新动态规划部分:从 “买卖股票的最佳时机IV” 到 “最长公共子序列”
This commit is contained in:
程序员Carl
2022-12-31 18:51:24 +08:00
committed by GitHub
4 changed files with 88 additions and 101 deletions

View File

@ -323,40 +323,6 @@ func max(a, b int) int {
}
```
```go
func maxProfit(k int, prices []int) int {
if len(prices)==0{
return 0
}
dp:=make([][]int,len(prices))
for i:=0;i<len(prices);i++{
dp[i]=make([]int,2*k+1)
}
for i:=1;i<len(dp[0]);i++{
if i%2!=0{
dp[0][i]=-prices[0]
}
}
for i:=1;i<len(prices);i++{
dp[i][0]=dp[i-1][0]
for j:=1;j<len(dp[0]);j++{
if j%2!=0{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]-prices[i])
}else {
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]+prices[i])
}
}
}
return dp[len(prices)-1][2*k]
}
func max(a,b int)int{
if a>b{
return a
}
return b
}
```
Javascript:
```javascript

View File

@ -36,9 +36,9 @@
首先通过本题大家要明确什么是子序列,“子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序”。
本题也是代码随想录中子序列问题的第一题,如果没接触过这种题目的话,本题还是很难的,甚至想暴力去搜索也不知道怎么搜。
子序列问题是动态规划解决的经典问题当前下标i的递增子序列长度其实和i之前的下表j的子序列长度有关系又是什么样的关系呢。
子序列问题是动态规划解决的经典问题当前下标i的递增子序列长度其实和i之前的下表j的子序列长度有关系那又是什么样的关系呢。
接下来,我们依然用动规五部曲来分析详细一波:
接下来,我们依然用动规五部曲来详细分析一波:
1. dp[i]的定义
@ -46,7 +46,7 @@
**dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度**
为什么一定表示 “以nums[i]结尾的最长递增子序” ,因为我们在 做 递增比较的时候,如果比较 nums[j] 和 nums[i] 的大小那么两个递增子序列一定分别以nums[j]为结尾 和 nums[i]为结尾, 要不然这个比较就没有意义了,不是尾部元素的比较那么 如算递增呢。
为什么一定表示 “以nums[i]结尾的最长递增子序” ,因为我们在 做 递增比较的时候,如果比较 nums[j] 和 nums[i] 的大小那么两个递增子序列一定分别以nums[j]为结尾 和 nums[i]为结尾, 要不然这个比较就没有意义了,不是尾部元素的比较那么 如算递增呢。
2. 状态转移方程
@ -155,31 +155,6 @@ class Solution:
```
Go
```go
func lengthOfLIS(nums []int ) int {
dp := []int{}
for _, num := range nums {
if len(dp) ==0 || dp[len(dp) - 1] < num {
dp = append(dp, num)
} else {
l, r := 0, len(dp) - 1
pos := r
for l <= r {
mid := (l + r) >> 1
if dp[mid] >= num {
pos = mid;
r = mid - 1
} else {
l = mid + 1
}
}
dp[pos] = num
}//二分查找
}
return len(dp)
}
```
```go
// 动态规划求解
func lengthOfLIS(nums []int) int {
@ -212,21 +187,29 @@ func max(x, y int) int {
return y
}
```
Rust:
```rust
pub fn length_of_lis(nums: Vec<i32>) -> i32 {
let mut dp = vec![1; nums.len() + 1];
let mut result = 1;
for i in 1..nums.len() {
for j in 0..i {
if nums[j] < nums[i] {
dp[i] = dp[i].max(dp[j] + 1);
}
result = result.max(dp[i]);
}
}
result
贪心+二分 优化
```go
func lengthOfLIS(nums []int ) int {
dp := []int{}
for _, num := range nums {
if len(dp) == 0 || dp[len(dp) - 1] < num {
dp = append(dp, num)
} else {
l, r := 0, len(dp) - 1
pos := r
for l <= r {
mid := (l + r) >> 1
if dp[mid] >= num {
pos = mid;
r = mid - 1
} else {
l = mid + 1
}
}
dp[pos] = num
}//二分查找
}
return len(dp)
}
```
@ -270,6 +253,22 @@ function lengthOfLIS(nums: number[]): number {
};
```
Rust:
```rust
pub fn length_of_lis(nums: Vec<i32>) -> i32 {
let mut dp = vec![1; nums.len() + 1];
let mut result = 1;
for i in 1..nums.len() {
for j in 0..i {
if nums[j] < nums[i] {
dp[i] = dp[i].max(dp[j] + 1);
}
result = result.max(dp[i]);
}
}
result
}
```

View File

@ -28,7 +28,7 @@
注意题目中说的子数组其实就是连续子序列
要求两个数组中最长重复子数组如果是暴力的解法 需要先两层for循环确定两个数组起始位置然后来一个循环可以是for或者while来从两个起始位置开始比较取得重复子数组的长度
要求两个数组中最长重复子数组如果是暴力的解法 只需要先两层for循环确定两个数组起始位置然后来一个循环可以是for或者while来从两个起始位置开始比较取得重复子数组的长度
本题其实是动规解决的经典题目我们只要想到 用二维数组可以记录两个字符串的所有比较情况这样就比较好推 递推公式了
动规五部曲分析如下
@ -163,7 +163,7 @@ public:
当然可以就是实现起来麻烦一些
如果定义 dp[i][j] 以下标i为结尾的A和以下标j 为结尾的B那么 第一行和第一列毕竟要行初始化如果nums1[i] nums2[0] 相同的话对应的 dp[i][0]就要初始为1 因为此时最长重复子数组为1 nums2[j] nums1[0]相同的话同理
如果定义 dp[i][j] 以下标i为结尾的A和以下标j 为结尾的B那么 第一行和第一列毕竟要行初始化如果nums1[i] nums2[0] 相同的话对应的 dp[i][0]就要初始为1 因为此时最长重复子数组为1 nums2[j] nums1[0]相同的话同理
所以代码如下
@ -298,6 +298,29 @@ func findLength(A []int, B []int) int {
}
return res
}
// 滚动数组
func findLength(nums1 []int, nums2 []int) int {
n, m, res := len(nums1), len(nums2), 0
dp := make([]int, m+1)
for i := 1; i <= n; i++ {
for j := m; j >= 1; j-- {
if nums1[i-1] == nums2[j-1] {
dp[j] = dp[j-1] + 1
} else {
dp[j] = 0 // 注意这里不相等要赋值为0供下一层使用
}
res = max(res, dp[j])
}
}
return res
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
```
JavaScript

View File

@ -49,7 +49,7 @@ dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符
有同学会问:为什么要定义长度为[0, i - 1]的字符串text1定义为长度为[0, i]的字符串text1不香么
这样定义是为了后面代码实现方便,如果非要定义为长度为[0, i]的字符串text1也可以我在 [动态规划718. 最长重复子数组](https://programmercarl.com/0718.最长重复子数组.html) 中的「拓展」里 详细讲解了区别所在其实就是简化了dp数组第一行和第一列的初始化逻辑。
这样定义是为了后面代码实现方便,如果非要定义为长度为[0, i]的字符串text1也可以我在 [动态规划718. 最长重复子数组](https://programmercarl.com/0718.最长重复子数组.html) 中的「拓展」里 详细讲解了区别所在其实就是简化了dp数组第一行和第一列的初始化逻辑。
2. 确定递推公式
@ -240,27 +240,6 @@ func max(a,b int)int {
```
Rust:
```rust
pub fn longest_common_subsequence(text1: String, text2: String) -> i32 {
let (n, m) = (text1.len(), text2.len());
let (s1, s2) = (text1.as_bytes(), text2.as_bytes());
let mut dp = vec![0; m + 1];
let mut last = vec![0; m + 1];
for i in 1..=n {
dp.swap_with_slice(&mut last);
for j in 1..=m {
dp[j] = if s1[i - 1] == s2[j - 1] {
last[j - 1] + 1
} else {
last[j].max(dp[j - 1])
};
}
}
dp[m]
}
```
Javascript
```javascript
const longestCommonSubsequence = (text1, text2) => {
@ -304,6 +283,26 @@ function longestCommonSubsequence(text1: string, text2: string): number {
};
```
Rust:
```rust
pub fn longest_common_subsequence(text1: String, text2: String) -> i32 {
let (n, m) = (text1.len(), text2.len());
let (s1, s2) = (text1.as_bytes(), text2.as_bytes());
let mut dp = vec![0; m + 1];
let mut last = vec![0; m + 1];
for i in 1..=n {
dp.swap_with_slice(&mut last);
for j in 1..=m {
dp[j] = if s1[i - 1] == s2[j - 1] {
last[j - 1] + 1
} else {
last[j].max(dp[j - 1])
};
}
}
dp[m]
}
```