diff --git a/problems/0188.买卖股票的最佳时机IV.md b/problems/0188.买卖股票的最佳时机IV.md index 23a8f54a..695fac35 100644 --- a/problems/0188.买卖股票的最佳时机IV.md +++ b/problems/0188.买卖股票的最佳时机IV.md @@ -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;ib{ - return a - } - return b -} -``` - Javascript: ```javascript diff --git a/problems/0300.最长上升子序列.md b/problems/0300.最长上升子序列.md index 42efe31d..f4fe1c31 100644 --- a/problems/0300.最长上升子序列.md +++ b/problems/0300.最长上升子序列.md @@ -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 { - 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 { + 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 +} +``` diff --git a/problems/0718.最长重复子数组.md b/problems/0718.最长重复子数组.md index 37f6cbfe..0658f9c8 100644 --- a/problems/0718.最长重复子数组.md +++ b/problems/0718.最长重复子数组.md @@ -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: diff --git a/problems/1143.最长公共子序列.md b/problems/1143.最长公共子序列.md index b655b5cd..f2e6e7e2 100644 --- a/problems/1143.最长公共子序列.md +++ b/problems/1143.最长公共子序列.md @@ -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] +} +```