Skip to content

Commit 123c67e

Browse files
committed
dp,recursion and slide window
1 parent f54dcdc commit 123c67e

File tree

1 file changed

+91
-177
lines changed

1 file changed

+91
-177
lines changed

basic_algorithm/dp.md

Lines changed: 91 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,11 @@ int lengthOfLIS1(vector<int>& nums) {
375375

376376
#pragma region 复杂度O(n log n)
377377
// 题目给了提示,看到log n可以考虑往二分查找之类的上凑
378+
// 思路:保存最大长度及其末尾节点,通过判断是否大于末尾节点来决定是否能够组成子序列
379+
// 又,既然不能保证当前的"最大长度"会不会被其他组合反超
380+
// 干脆把整个过程,每个长度都保存下来,构成单调栈
381+
// 一方面通过栈顶来决定是否能够组合成更长的子序列
382+
// 另一方面通过查找,找出比当前节点小的第一个末尾进行更新,以确保其他组合的机会
378383
int lengthOfLIS(vector<int> &nums) {
379384
int size = nums.size();
380385
if (size < 2) {
@@ -413,53 +418,21 @@ int lengthOfLIS(vector<int> &nums) {
413418
414419
> 给定一个**非空**字符串  *s*  和一个包含**非空**单词列表的字典  *wordDict*,判定  *s*  是否可以被空格拆分为一个或多个在字典中出现的单词。
415420
416-
```go
417-
func wordBreak(s string, wordDict []string) bool {
418-
// f[i] 表示前i个字符是否可以被切分
419-
// f[i] = f[j] && s[j+1~i] in wordDict
420-
// f[0] = true
421-
// return f[len]
422-
423-
if len(s) == 0 {
424-
return true
425-
}
426-
f := make([]bool, len(s)+1)
427-
f[0] = true
428-
max,dict := maxLen(wordDict)
429-
for i := 1; i <= len(s); i++ {
430-
l := 0
431-
if i - max > 0 {
432-
l = i - max
433-
}
434-
for j := l; j < i; j++ {
435-
if f[j] && inDict(s[j:i],dict) {
436-
f[i] = true
437-
break
438-
}
439-
}
440-
}
441-
return f[len(s)]
442-
}
443-
444-
445-
446-
func maxLen(wordDict []string) (int,map[string]bool) {
447-
dict := make(map[string]bool)
448-
max := 0
449-
for _, v := range wordDict {
450-
dict[v] = true
451-
if len(v) > max {
452-
max = len(v)
453-
}
454-
}
455-
return max,dict
456-
}
457-
458-
func inDict(s string,dict map[string]bool) bool {
459-
_, ok := dict[s]
460-
return ok
421+
```c++
422+
bool wordBreak(string s, vector<string> &wordDict) {
423+
unordered_set<string> wordDictSet{wordDict.begin(), wordDict.end()};
424+
vector<bool> dp(s.size() + 1);
425+
dp[0] = true;
426+
for (int i = 1; i <= s.size(); ++i) {
427+
for (int j = 0; j < i; ++j) {
428+
if (dp[j] && wordDictSet.find(s.substr(j, i - j)) != wordDictSet.end()) {
429+
dp[i] = true;
430+
break;
431+
}
432+
}
433+
}
434+
return dp.back();
461435
}
462-
463436
```
464437

465438
小结
@@ -479,50 +452,23 @@ func inDict(s string,dict map[string]bool) bool {
479452
> 一个字符串的   子序列   是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
480453
> 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
481454
482-
```go
483-
func longestCommonSubsequence(a string, b string) int {
484-
// dp[i][j] a前i个和b前j个字符最长公共子序列
485-
// dp[m+1][n+1]
486-
// ' a d c e
487-
// ' 0 0 0 0 0
488-
// a 0 1 1 1 1
489-
// c 0 1 1 2 1
490-
//
491-
dp:=make([][]int,len(a)+1)
492-
for i:=0;i<=len(a);i++ {
493-
dp[i]=make([]int,len(b)+1)
494-
}
495-
for i:=1;i<=len(a);i++ {
496-
for j:=1;j<=len(b);j++ {
497-
// 相等取左上元素+1,否则取左或上的较大值
498-
if a[i-1]==b[j-1] {
499-
dp[i][j]=dp[i-1][j-1]+1
455+
```c++
456+
vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1));
457+
for (int i = 1; i <= text1.size(); ++i) {
458+
for (int j = 1; j <= text2.size(); ++j) {
459+
if (text1[i - 1] == text2[j - 1]) {
460+
dp[i][j] = dp[i - 1][j - 1] + 1;
500461
} else {
501-
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
462+
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
502463
}
503464
}
504465
}
505-
return dp[len(a)][len(b)]
506-
}
507-
func max(a,b int)int {
508-
if a>b{
509-
return a
510-
}
511-
return b
466+
return dp.back().back();
512467
}
513468
```
514469
515470
注意点
516471
517-
- go 切片初始化
518-
519-
```go
520-
dp:=make([][]int,len(a)+1)
521-
for i:=0;i<=len(a);i++ {
522-
dp[i]=make([]int,len(b)+1)
523-
}
524-
```
525-
526472
- 从 1 开始遍历到最大长度
527473
- 索引需要减一
528474
@@ -536,37 +482,30 @@ for i:=0;i<=len(a);i++ {
536482
537483
思路:和上题很类似,相等则不需要操作,否则取删除、插入、替换最小操作次数的值+1
538484
539-
```go
540-
func minDistance(word1 string, word2 string) int {
541-
// dp[i][j] 表示a字符串的前i个字符编辑为b字符串的前j个字符最少需要多少次操作
542-
// dp[i][j] = OR(dp[i-1][j-1],a[i]==b[j],min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1)
543-
dp:=make([][]int,len(word1)+1)
544-
for i:=0;i<len(dp);i++{
545-
dp[i]=make([]int,len(word2)+1)
546-
}
547-
for i:=0;i<len(dp);i++{
548-
dp[i][0]=i
549-
}
550-
for j:=0;j<len(dp[0]);j++{
551-
dp[0][j]=j
552-
}
553-
for i:=1;i<=len(word1);i++{
554-
for j:=1;j<=len(word2);j++{
555-
// 相等则不需要操作
556-
if word1[i-1]==word2[j-1] {
557-
dp[i][j]=dp[i-1][j-1]
558-
}else{ // 否则取删除、插入、替换最小操作次数的值+1
559-
dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1
485+
```c++
486+
int minDistance(string word1, string word2) {
487+
// 删除与插入操作是等价的,修改word1和修改word2也是等价的
488+
// 故,实际操作只有在三种:
489+
// 1. 在word1插入,dp[i][j] = dp[i][j - 1] + 1
490+
// 2. 在word2插入,dp[i][j] = dp[i - 1][j] + 1
491+
// 3. 在word1修改,dp[i][j] = dp[i - 1][j - 1] + 1
492+
vector<vector<int>> dp(word1.size(), vector<int>(word2.size()));
493+
for (int i = 0; i < word1.size(); ++i) {
494+
dp[i][0] = i;
495+
}
496+
for (int i = 0; i < word2.size(); ++i) {
497+
dp[0][i] = i;
498+
}
499+
for (int i = 1; i <= word1.size(); ++i) {
500+
for (int j = 1; j <= word2.size(); ++j) {
501+
if (word1[i - 1] == word2[j - 1]) {
502+
dp[i][j] = dp[i - 1][j - 1];
503+
} else {
504+
dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
560505
}
561506
}
562507
}
563-
return dp[len(word1)][len(word2)]
564-
}
565-
func min(a,b int)int{
566-
if a>b{
567-
return b
568-
}
569-
return a
508+
return dp.back().back();
570509
}
571510
```
572511

@@ -582,35 +521,23 @@ func min(a,b int)int{
582521
583522
思路:和其他 DP 不太一样,i 表示钱或者容量
584523

585-
```go
586-
func coinChange(coins []int, amount int) int {
587-
// 状态 dp[i]表示金额为i时,组成的最小硬币个数
588-
// 推导 dp[i] = min(dp[i-1], dp[i-2], dp[i-5])+1, 前提 i-coins[j] > 0
589-
// 初始化为最大值 dp[i]=amount+1
590-
// 返回值 dp[n] or dp[n]>amount =>-1
591-
dp:=make([]int,amount+1)
592-
for i:=0;i<=amount;i++{
593-
dp[i]=amount+1
594-
}
595-
dp[0]=0
596-
for i:=1;i<=amount;i++{
597-
for j:=0;j<len(coins);j++{
598-
if i-coins[j]>=0 {
599-
dp[i]=min(dp[i],dp[i-coins[j]]+1)
524+
```c++
525+
int coinChange(vector<int>& coins, int amount) {
526+
// 初始化为amount + 1,如果最后大于amount说明无解
527+
// dp[i]表示金额为i时,所需最少硬币个数
528+
// dp[i] = min(dp[i], dp[i - coin] + 1)
529+
// dp[i - coin],倒扣当前面额
530+
// 注意不要越界,i - coin >= 0
531+
vector<int> dp(amount + 1, amount + 1);
532+
dp[0] = 0;
533+
for (auto i = 1; i <= amount; ++i) {
534+
for (const auto &coin : coins) {
535+
if (i - coin >= 0) {
536+
dp[i] = min(dp[i], dp[i - coin] + 1);
600537
}
601538
}
602539
}
603-
if dp[amount] > amount {
604-
return -1
605-
}
606-
return dp[amount]
607-
608-
}
609-
func min(a,b int)int{
610-
if a>b{
611-
return b
612-
}
613-
return a
540+
return dp.back() > amount ? -1 : dp.back();
614541
}
615542
```
616543
@@ -622,34 +549,30 @@ func min(a,b int)int{
622549
623550
> 在 n 个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为 m,每个物品的大小为 A[i]
624551
625-
```go
626-
func backPack (m int, A []int) int {
552+
```c++
553+
int backPack(int m, vector<int> &A) {
627554
// write your code here
628-
// f[i][j] 前i个物品,是否能装j
629-
// f[i][j] =f[i-1][j] f[i-1][j-a[i] j>a[i]
630-
// f[0][0]=true f[...][0]=true
631-
// f[n][X]
632-
f:=make([][]bool,len(A)+1)
633-
for i:=0;i<=len(A);i++{
634-
f[i]=make([]bool,m+1)
635-
}
636-
f[0][0]=true
637-
for i:=1;i<=len(A);i++{
638-
for j:=0;j<=m;j++{
639-
f[i][j]=f[i-1][j]
640-
if j-A[i-1]>=0 && f[i-1][j-A[i-1]]{
641-
f[i][j]=true
555+
// dp[i][j] 前i个物品,是否能装j
556+
// dp[i - 1][j] == true? 则不需要第i个物品也能装满
557+
// dp[i - 1][j - A[i - 1]]? 腾出第i个物品的空间,剩下空间能否装满
558+
// dp[i][j] = dp[i - 1][j] or dp[i - 1][j - A[i - 1]]
559+
vector<vector<bool>> dp(A.size() + 1, vector<bool>(m + 1));
560+
dp[0][0] = true;
561+
for (int i = 1; i <= A.size(); ++i) {
562+
for (int j = 0; j <= m; ++j) {
563+
dp[i][j] = dp[i - 1][j];
564+
if (j - A[i - 1] >= 0 && dp[i - 1][j - A[i - 1]]) {
565+
dp[i][j] = true;
642566
}
643567
}
644568
}
645-
for i:=m;i>=0;i--{
646-
if f[len(A)][i] {
647-
return i
569+
for (int i = m; i >= 0; ++i) {
570+
if (dp[A.size()][i]) {
571+
return i;
648572
}
649573
}
650-
return 0
574+
return 0;
651575
}
652-
653576
```
654577

655578
### [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description)
@@ -659,31 +582,22 @@ func backPack (m int, A []int) int {
659582
660583
思路:f[i][j] 前 i 个物品,装入 j 背包 最大价值
661584

662-
```go
663-
func backPackII (m int, A []int, V []int) int {
585+
```c++
586+
int backPackII(int m, vector<int> &A, vector<int> &V) {
664587
// write your code here
665-
// f[i][j] 前i个物品,装入j背包 最大价值
666-
// f[i][j] =max(f[i-1][j] ,f[i-1][j-A[i]]+V[i]) 是否加入A[i]物品
667-
// f[0][0]=0 f[0][...]=0 f[...][0]=0
668-
f:=make([][]int,len(A)+1)
669-
for i:=0;i<len(A)+1;i++{
670-
f[i]=make([]int,m+1)
671-
}
672-
for i:=1;i<=len(A);i++{
673-
for j:=0;j<=m;j++{
674-
f[i][j]=f[i-1][j]
675-
if j-A[i-1] >= 0{
676-
f[i][j]=max(f[i-1][j],f[i-1][j-A[i-1]]+V[i-1])
588+
// dp[i][j] 前i个物品,装入j背包 最大价值
589+
vector<vector<int>> dp(A.size() + 1, vector<int>(V.size() + 1));
590+
for (int i = 1; i <= A.size(); ++i) {
591+
for (int j = 0; j <= m; ++j) {
592+
// 不选第i个物品,则dp[i][j] = dp[i - 1][j]
593+
// 选了第i个物品,则dp[i][j] = dp[i - 1][j - A[i - 1]],倒扣i的大小
594+
dp[i][j] = dp[i - 1][j];
595+
if (j - A[i - 1] >= 0) {
596+
dp[i][j] = max(dp[i][j], dp[i - 1][j - A[i - 1]] + V[i - 1]);
677597
}
678598
}
679599
}
680-
return f[len(A)][m]
681-
}
682-
func max(a,b int)int{
683-
if a>b{
684-
return a
685-
}
686-
return b
600+
return dp.back().back();
687601
}
688602
```
689603

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy