diff --git a/README.md b/README.md index 60890f228a..c1296ed3db 100644 --- a/README.md +++ b/README.md @@ -152,9 +152,9 @@ 1. [字符串:344.反转字符串](./problems/0344.反转字符串.md) 2. [字符串:541.反转字符串II](./problems/0541.反转字符串II.md) -3. [字符串:替换空格](./problems/剑指Offer05.替换空格.md) +3. [字符串:替换数字](./problems/kama54.替换数字.md) 4. [字符串:151.翻转字符串里的单词](./problems/0151.翻转字符串里的单词.md) -5. [字符串:左旋转字符串](./problems/剑指Offer58-II.左旋转字符串.md) +5. [字符串:右旋字符串](./problems/kama55.右旋字符串.md) 6. [帮你把KMP算法学个通透](./problems/0028.实现strStr.md) 8. [字符串:459.重复的子字符串](./problems/0459.重复的子字符串.md) 9. [字符串:总结篇!](./problems/字符串总结.md) @@ -165,7 +165,7 @@ 1. [数组:27.移除元素](./problems/0027.移除元素.md) 2. [字符串:344.反转字符串](./problems/0344.反转字符串.md) -3. [字符串:替换空格](./problems/剑指Offer05.替换空格.md) +3. [字符串:替换数字](./problems/kama54.替换数字.md) 4. [字符串:151.翻转字符串里的单词](./problems/0151.翻转字符串里的单词.md) 5. [链表:206.翻转链表](./problems/0206.翻转链表.md) 6. [链表:19.删除链表的倒数第 N 个结点](./problems/0019.删除链表的倒数第N个节点.md) diff --git "a/problems/0070.\347\210\254\346\245\274\346\242\257.md" "b/problems/0070.\347\210\254\346\245\274\346\242\257.md" index 1a1f7e31b5..7a59221dc6 100644 --- "a/problems/0070.\347\210\254\346\245\274\346\242\257.md" +++ "b/problems/0070.\347\210\254\346\245\274\346\242\257.md" @@ -165,9 +165,12 @@ public: 这道题目还可以继续深化,就是一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。 -这又有难度了,这其实是一个完全背包问题,但力扣上没有这种题目,所以后续我在讲解背包问题的时候,今天这道题还会从背包问题的角度上来再讲一遍。 如果想提前看一下,可以看这篇:[70.爬楼梯完全背包版本](https://programmercarl.com/0070.%E7%88%AC%E6%A5%BC%E6%A2%AF%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%E7%89%88%E6%9C%AC.html) +这又有难度了,这其实是一个完全背包问题,但力扣上没有这种题目,大家可以去卡码网去做一下 [57. 爬楼梯](https://kamacoder.com/problempage.php?pid=1067) -这里我先给出我的实现代码: + +所以后续我在讲解背包问题的时候,今天这道题还会从背包问题的角度上来再讲一遍。 如果想提前看一下,可以看这篇:[70.爬楼梯完全背包版本](https://programmercarl.com/0070.%E7%88%AC%E6%A5%BC%E6%A2%AF%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%E7%89%88%E6%9C%AC.html) + +这里我先给出本题的代码: ```CPP class Solution { diff --git "a/problems/0070.\347\210\254\346\245\274\346\242\257\345\256\214\345\205\250\350\203\214\345\214\205\347\211\210\346\234\254.md" "b/problems/0070.\347\210\254\346\245\274\346\242\257\345\256\214\345\205\250\350\203\214\345\214\205\347\211\210\346\234\254.md" index 4ca7a3710b..0da1ebeca5 100644 --- "a/problems/0070.\347\210\254\346\245\274\346\242\257\345\256\214\345\205\250\350\203\214\345\214\205\347\211\210\346\234\254.md" +++ "b/problems/0070.\347\210\254\346\245\274\346\242\257\345\256\214\345\205\250\350\203\214\345\214\205\347\211\210\346\234\254.md" @@ -4,30 +4,34 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-# 70. 爬楼梯 +# 70. 爬楼梯(进阶版) -[力扣题目链接](https://leetcode.cn/problems/climbing-stairs/) +[卡码网:57. 爬楼梯](https://kamacoder.com/problempage.php?pid=1067) -假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 +假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 -每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? +每次你可以爬至多m (1 <= m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢? -注意:给定 n 是一个正整数。 +注意:给定 n 是一个正整数。 -示例 1: -输入: 2 -输出: 2 -解释: 有两种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 -2. 2 阶 +输入描述:输入共一行,包含两个正整数,分别表示n, m + +输出描述:输出一个整数,表示爬到楼顶的方法数。 + +输入示例:3 2 + +输出示例:3 + +提示: + +当 m = 2,n = 3 时,n = 3 这表示一共有三个台阶,m = 2 代表你每次可以爬一个台阶或者两个台阶。 + +此时你有三种方法可以爬到楼顶。 + +* 1 阶 + 1 阶 + 1 阶段 +* 1 阶 + 2 阶 +* 2 阶 + 1 阶 -示例 2: -输入: 3 -输出: 3 -解释: 有三种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 + 1 阶 -2. 1 阶 + 2 阶 -3. 2 阶 + 1 阶 ## 思路 @@ -35,11 +39,13 @@ **这次终于讲到了背包问题,我选择带录友们再爬一次楼梯!** -这道题目 我们在[动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html) 中已经讲过一次了,原题其实是一道简单动规的题目。 +这道题目 我们在[动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html) 中已经讲过一次了,这次我又给本题加点料,力扣上没有原题,所以可以在卡码网[57. 爬楼梯](https://kamacoder.com/problempage.php?pid=1067)上来刷这道题目。 -既然这么简单为什么还要讲呢,其实本题稍加改动就是一道面试好题。 +我们之前做的 爬楼梯 是只能至多爬两个台阶。 -**改为:一步一个台阶,两个台阶,三个台阶,.......,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?** +这次**改为:一步一个台阶,两个台阶,三个台阶,.......,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?** + +这又有难度了,这其实是一个完全背包问题。 1阶,2阶,.... m阶就是物品,楼顶就是背包。 @@ -86,27 +92,31 @@ 以上分析完毕,C++代码如下: ```CPP -class Solution { -public: - int climbStairs(int n) { +#include +#include +using namespace std; +int main() { + int n, m; + while (cin >> n >> m) { vector dp(n + 1, 0); dp[0] = 1; - for (int i = 1; i <= n; i++) { // 遍历背包 - for (int j = 1; j <= m; j++) { // 遍历物品 + for (int i = 1; i <= n; i++) { // 遍历物品 + for (int j = 1; j <= m; j++) { // 遍历背包 if (i - j >= 0) dp[i] += dp[i - j]; } } - return dp[n]; + cout << dp[n] << endl; } -}; +} ``` -* 时间复杂度: O(nm) +* 时间复杂度: O(n * m) * 空间复杂度: O(n) +代码中m表示最多可以爬m个台阶,代码中把m改成2就是 力扣:70.爬楼梯的解题思路。 +**当然注意 力扣是 核心代码模式,卡码网是ACM模式** -代码中m表示最多可以爬m个台阶,代码中把m改成2就是本题70.爬楼梯可以AC的代码了。 ## 总结 @@ -129,123 +139,22 @@ public: ### Java: -```java -class Solution { - public int climbStairs(int n) { - int[] dp = new int[n + 1]; - int m = 2; //有兩個物品:itme1重量爲一,item2重量爲二 - dp[0] = 1; - - for (int i = 1; i <= n; i++) { // 遍历背包 - for (int j = 1; j <= m; j++) { //遍历物品 - if (i >= j) //當前的背包容量 大於 物品重量的時候,我們才需要記錄當前的這個裝得方法(方法數+) - dp[i] += dp[i - j]; - } - } - - return dp[n]; - } -} -``` ### Python3: -```python -class Solution: - def climbStairs(self, n: int) -> int: - dp = [0]*(n + 1) - dp[0] = 1 - m = 2 - # 遍历背包 - for j in range(n + 1): - # 遍历物品 - for step in range(1, m + 1): - if j >= step: - dp[j] += dp[j - step] - return dp[n] -``` ### Go: -```go -func climbStairs(n int) int { - //定义 - dp := make([]int, n+1) - //初始化 - dp[0] = 1 - // 本题物品只有两个1,2 - m := 2 - // 遍历顺序 - for j := 1; j <= n; j++ { //先遍历背包 - for i := 1; i <= m; i++ { //再遍历物品 - if j >= i { - dp[j] += dp[j-i] - } - //fmt.Println(dp) - } - } - return dp[n] -} -``` ### JavaScript: -```javascript -var climbStairs = function(n) { - const dp = new Array(n + 1).fill(0); - const m = 2; - dp[0] = 1; - for(let i = 1; i <= n; i++){ - for(let j = 1; j <= m; j++){ - if(i >= j) { - dp[i] += dp[i - j]; - } - } - } - return dp[n]; -}; -``` ### TypeScript: -```typescript -function climbStairs(n: number): number { - const m: number = 2; // 本题m为2 - const dp: number[] = new Array(n + 1).fill(0); - dp[0] = 1; - // 遍历背包 - for (let i = 1; i <= n; i++) { - // 遍历物品 - for (let j = 1; j <= m; j++) { - if (j <= i) { - dp[i] += dp[i - j]; - } - } - } - return dp[n]; -}; -``` ### Rust: -```rust -impl Solution { - pub fn climb_stairs(n: i32) -> i32 { - let (n, m) = (n as usize, 2); - let mut dp = vec![0; n + 1]; - dp[0] = 1; - for i in 1..=n { - for j in 1..=m { - if i >= j { - dp[i] += dp[i - j]; - } - } - } - dp[n] - } -} -```

diff --git "a/problems/kama54.\346\233\277\346\215\242\346\225\260\345\255\227.md" "b/problems/kama54.\346\233\277\346\215\242\346\225\260\345\255\227.md" new file mode 100644 index 0000000000..80c87951aa --- /dev/null +++ "b/problems/kama54.\346\233\277\346\215\242\346\225\260\345\255\227.md" @@ -0,0 +1,176 @@ + +

+ + + +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ + +# 替换数字 + +[卡码网题目链接](https://kamacoder.com/problempage.php?pid=1064) + +给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。 + +例如,对于输入字符串 "a1b2c3",函数应该将其转换为 "anumberbnumbercnumber"。 + +对于输入字符串 "a5b",函数应该将其转换为 "anumberb" + +输入:一个字符串 s,s 仅包含小写字母和数字字符。 + +输出:打印一个新的字符串,其中每个数字字符都被替换为了number + +样例输入:a1b2c3 + +样例输出:anumberbnumbercnumber + +数据范围:1 <= s.length < 10000。 + +## 思路 + +如果想把这道题目做到极致,就不要只用额外的辅助空间了! (不过使用Java刷题的录友,一定要使用辅助空间,因为Java里的string不能修改) + +首先扩充数组到每个数字字符替换成 "number" 之后的大小。 + +例如 字符串 "a5b" 的长度为3,那么 将 数字字符变成字符串 "number" 之后的字符串为 "anumberb" 长度为 8。 + +如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231030165201.png) + +然后从后向前替换数字字符,也就是双指针法,过程如下:i指向新长度的末尾,j指向旧长度的末尾。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231030173058.png) + +有同学问了,为什么要从后向前填充,从前向后填充不行么? + +从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素整体向后移动。 + +**其实很多数组填充类的问题,其做饭都是先预先给数组扩容带填充后的大小,然后在从后向前进行操作。** + +这么做有两个好处: + +1. 不用申请新数组。 +2. 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。 + +C++代码如下: + +```CPP +#include +using namespace std; +int main() { + string s; + while (cin >> s) { + int count = 0; // 统计数字的个数 + int sOldSize = s.size(); + for (int i = 0; i < s.size(); i++) { + if (s[i] >= '0' && s[i] <= '9') { + count++; + } + } + // 扩充字符串s的大小,也就是每个空格替换成"number"之后的大小 + s.resize(s.size() + count * 5); + int sNewSize = s.size(); + // 从后先前将空格替换为"number" + for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) { + if (s[j] > '9' || s[j] < '0') { + s[i] = s[j]; + } else { + s[i] = 'r'; + s[i - 1] = 'e'; + s[i - 2] = 'b'; + s[i - 3] = 'm'; + s[i - 4] = 'u'; + s[i - 5] = 'n'; + i -= 5; + } + } + cout << s << endl; + } +} + + +``` + +* 时间复杂度:O(n) +* 空间复杂度:O(1) + +此时算上本题,我们已经做了七道双指针相关的题目了分别是: + +* [27.移除元素](https://programmercarl.com/0027.移除元素.html) +* [15.三数之和](https://programmercarl.com/0015.三数之和.html) +* [18.四数之和](https://programmercarl.com/0018.四数之和.html) +* [206.翻转链表](https://programmercarl.com/0206.翻转链表.html) +* [142.环形链表II](https://programmercarl.com/0142.环形链表II.html) +* [344.反转字符串](https://programmercarl.com/0344.反转字符串.html) + +## 拓展 + +这里也给大家拓展一下字符串和数组有什么差别, + +字符串是若干字符组成的有限序列,也可以理解为是一个字符数组,但是很多语言对字符串做了特殊的规定,接下来我来说一说C/C++中的字符串。 + +在C语言中,把一个字符串存入一个数组时,也把结束符 '\0'存入数组,并以此作为该字符串是否结束的标志。 + +例如这段代码: + +``` +char a[5] = "asd"; +for (int i = 0; a[i] != '\0'; i++) { +} +``` + +在C++中,提供一个string类,string类会提供 size接口,可以用来判断string类字符串是否结束,就不用'\0'来判断是否结束。 + +例如这段代码: + +``` +string a = "asd"; +for (int i = 0; i < a.size(); i++) { +} +``` + +那么vector< char > 和 string 又有什么区别呢? + +其实在基本操作上没有区别,但是 string提供更多的字符串处理的相关接口,例如string 重载了+,而vector却没有。 + +所以想处理字符串,我们还是会定义一个string类型。 + + +## 其他语言版本 + +### C: + +### Java: + + +### Go: + + + +### python: + +### JavaScript: + + +### TypeScript: + + +### Swift: + + +### Scala: + + +### PHP: + + +### Rust: + + + +

+ + + + diff --git "a/problems/kama55.\345\217\263\346\227\213\345\255\227\347\254\246\344\270\262.md" "b/problems/kama55.\345\217\263\346\227\213\345\255\227\347\254\246\344\270\262.md" new file mode 100644 index 0000000000..afd29d7376 --- /dev/null +++ "b/problems/kama55.\345\217\263\346\227\213\345\255\227\347\254\246\344\270\262.md" @@ -0,0 +1,181 @@ + +

+ + + +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ + +# 右旋字符串 + +[卡码网题目链接](https://kamacoder.com/problempage.php?pid=1065) + +字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。 + +例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。 + +输入:输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。 + +输出:输出共一行,为进行了右旋转操作后的字符串。 + +样例输入: + +``` +2 +abcdefg +``` + +样例输出: + +``` +fgabcde +``` + +数据范围:1 <= k < 10000, 1 <= s.length < 10000; + + +## 思路 + +为了让本题更有意义,提升一下本题难度:**不能申请额外空间,只能在本串上操作**。 (Java不能在字符串上修改,所以使用java一定要开辟新空间) + +不能使用额外空间的话,模拟在本串操作要实现右旋转字符串的功能还是有点困难的。 + +那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过,使用整体反转+局部反转就可以实现反转单词顺序的目的。 + + +本题中,我们需要将字符串右移n位,字符串相当于分成了两个部分,如果n为2,符串相当于分成了两个部分,如图: (length为字符串长度) + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231106170143.png) + + +右移n位, 就是将第二段放在前面,第一段放在后面,先不考虑里面字符的顺序,是不是整体倒叙不就行了。如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231106171557.png) + +此时第一段和第二段的顺序是我们想要的,但里面的字符位置被我们倒叙,那么此时我们在把 第一段和第二段里面的字符再倒叙一把,这样字符顺序不就正确了。 如果: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231106172058.png) + +其实,思路就是 通过 整体倒叙,把两段子串顺序颠倒,两个段子串里的的字符在倒叙一把,**负负得正**,这样就不影响子串里面字符的顺序了。 + +整体代码如下: + +```CPP +// 版本一 +#include +#include +using namespace std; +int main() { + int n; + string s; + cin >> n; + cin >> s; + int len = s.size(); //获取长度 + + reverse(s.begin(), s.end()); // 整体反转 + reverse(s.begin(), s.begin() + n); // 先反转前一段,长度n + reverse(s.begin() + n, s.end()); // 再反转后一段 + + cout << s << endl; + +} +``` + +那么整体反正的操作放在下面,先局部反转行不行? + +可以的,不过,要记得 控制好 局部反转的长度,如果先局部反转,那么先反转的子串长度就是 len - n,如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231106172534.png) + +代码如下: + +```CPP +// 版本二 +#include +#include +using namespace std; +int main() { + int n; + string s; + cin >> n; + cin >> s; + int len = s.size(); //获取长度 + reverse(s.begin(), s.begin() + len - n); // 先反转前一段,长度len-n ,注意这里是和版本一的区别 + reverse(s.begin() + len - n, s.end()); // 再反转后一段 + reverse(s.begin(), s.end()); // 整体反转 + cout << s << endl; + +} +``` + + +## 拓展 + +大家在做剑指offer的时候,会发现 剑指offer的题目是左反转,那么左反转和右反转 有什么区别呢? + +其实思路是一样一样的,就是反转的区间不同而已。如果本题是左旋转n,那么实现代码如下: + +```CPP +#include +#include +using namespace std; +int main() { + int n; + string s; + cin >> n; + cin >> s; + int len = s.size(); //获取长度 + reverse(s.begin(), s.begin() + n); // 反转第一段长度为n + reverse(s.begin() + n, s.end()); // 反转第二段长度为len-n + reverse(s.begin(), s.end()); // 整体反转 + cout << s << endl; + +} +``` + +大家可以感受一下 这份代码和 版本二的区别, 其实就是反转的区间不同而已。 + +那么左旋转的话,可以不可以先整体反转,例如想版本一的那样呢? + +当然可以。 + + + + +## 其他语言版本 + +### Java: + + + +### Python: + + +### Go: + + +### JavaScript: + + +### TypeScript: + + +### Swift: + + + +### PHP: + + +### Scala: + + +### Rust: + + + + +

+ + + diff --git "a/problems/\344\272\214\345\217\211\346\240\221\344\270\255\351\200\222\345\275\222\345\270\246\347\235\200\345\233\236\346\272\257.md" "b/problems/\344\272\214\345\217\211\346\240\221\344\270\255\351\200\222\345\275\222\345\270\246\347\235\200\345\233\236\346\272\257.md" index 9bf7405d06..67570bc8d6 100644 --- "a/problems/\344\272\214\345\217\211\346\240\221\344\270\255\351\200\222\345\275\222\345\270\246\347\235\200\345\233\236\346\272\257.md" +++ "b/problems/\344\272\214\345\217\211\346\240\221\344\270\255\351\200\222\345\275\222\345\270\246\347\235\200\345\233\236\346\272\257.md" @@ -621,7 +621,7 @@ func _binaryTreePaths3(_ root: TreeNode, res: inout [String], paths: inout [Int] > 100.相同的树 -```rsut +```rust use std::cell::RefCell; use std::rc::Rc; impl Solution { diff --git "a/problems/\345\211\221\346\214\207Offer05.\346\233\277\346\215\242\347\251\272\346\240\274.md" "b/problems/\345\211\221\346\214\207Offer05.\346\233\277\346\215\242\347\251\272\346\240\274.md" index fed08a5346..040be29983 100644 --- "a/problems/\345\211\221\346\214\207Offer05.\346\233\277\346\215\242\347\251\272\346\240\274.md" +++ "b/problems/\345\211\221\346\214\207Offer05.\346\233\277\346\215\242\347\251\272\346\240\274.md" @@ -5,73 +5,92 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-# 题目:剑指Offer 05.替换空格 +# 替换数字 -[力扣题目链接](https://leetcode.cn/problems/ti-huan-kong-ge-lcof/) +力扣已经将剑指offer题目下架,所以我在卡码网上给大家提供类似的题目来练习 -请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 +[卡码网题目链接](https://kamacoder.com/problempage.php?pid=1064) -示例 1: -输入:s = "We are happy." -输出:"We%20are%20happy." +给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。 + +例如,对于输入字符串 "a1b2c3",函数应该将其转换为 "anumberbnumbercnumber"。 + +对于输入字符串 "a5b",函数应该将其转换为 "anumberb" + +输入:一个字符串 s,s 仅包含小写字母和数字字符。 + +输出:打印一个新的字符串,其中每个数字字符都被替换为了number + +样例输入:a1b2c3 + +样例输出:anumberbnumbercnumber + +数据范围:1 <= s.length < 10000。 ## 思路 -如果想把这道题目做到极致,就不要只用额外的辅助空间了! +如果想把这道题目做到极致,就不要只用额外的辅助空间了! (不过使用Java刷题的录友,一定要使用辅助空间,因为Java里的string不能修改) + +首先扩充数组到每个数字字符替换成 "number" 之后的大小。 + +例如 字符串 "a5b" 的长度为3,那么 将 数字字符变成字符串 "number" 之后的字符串为 "anumberb" 长度为 8。 -首先扩充数组到每个空格替换成"%20"之后的大小。 +如图: -然后从后向前替换空格,也就是双指针法,过程如下: +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231030165201.png) -i指向新长度的末尾,j指向旧长度的末尾。 +然后从后向前替换数字字符,也就是双指针法,过程如下:i指向新长度的末尾,j指向旧长度的末尾。 -![替换空格](https://code-thinking.cdn.bcebos.com/gifs/%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC.gif) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231030173058.png) 有同学问了,为什么要从后向前填充,从前向后填充不行么? -从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。 +从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素整体向后移动。 -**其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。** +**其实很多数组填充类的问题,其做饭都是先预先给数组扩容带填充后的大小,然后在从后向前进行操作。** 这么做有两个好处: 1. 不用申请新数组。 2. 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。 -时间复杂度,空间复杂度均超过100%的用户。 - - - C++代码如下: ```CPP -class Solution { -public: - string replaceSpace(string s) { - int count = 0; // 统计空格的个数 +#include +using namespace std; +int main() { + string s; + while (cin >> s) { + int count = 0; // 统计数字的个数 int sOldSize = s.size(); for (int i = 0; i < s.size(); i++) { - if (s[i] == ' ') { + if (s[i] >= '0' && s[i] <= '9') { count++; } } - // 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小 - s.resize(s.size() + count * 2); + // 扩充字符串s的大小,也就是每个空格替换成"number"之后的大小 + s.resize(s.size() + count * 5); int sNewSize = s.size(); - // 从后先前将空格替换为"%20" + // 从后先前将空格替换为"number" for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) { - if (s[j] != ' ') { + if (s[j] > '9' || s[j] < '0') { s[i] = s[j]; } else { - s[i] = '0'; - s[i - 1] = '2'; - s[i - 2] = '%'; - i -= 2; + s[i] = 'r'; + s[i - 1] = 'e'; + s[i - 2] = 'b'; + s[i - 3] = 'm'; + s[i - 4] = 'u'; + s[i - 5] = 'n'; + i -= 5; } } - return s; + cout << s << endl; } -}; +} + + ``` * 时间复杂度:O(n) @@ -123,442 +142,32 @@ for (int i = 0; i < a.size(); i++) { ### C: -```C -char* replaceSpace(char* s){ - //统计空格数量 - int count = 0; - int len = strlen(s); - for (int i = 0; i < len; i++) { - if (s[i] == ' ') { - count++; - } - } - - //为新数组分配空间 - int newLen = len + count * 2; - char* result = malloc(sizeof(char) * newLen + 1); - //填充新数组并替换空格 - for (int i = len - 1, j = newLen - 1; i >= 0; i--, j--) { - if (s[i] != ' ') { - result[j] = s[i]; - } else { - result[j--] = '0'; - result[j--] = '2'; - result[j] = '%'; - } - } - result[newLen] = '\0'; - - return result; -} -``` - ### Java: -```Java -//使用一个新的对象,复制 str,复制的过程对其判断,是空格则替换,否则直接复制,类似于数组复制 -public static String replaceSpace(String s) { - if (s == null) { - return null; - } - //选用 StringBuilder 单线程使用,比较快,选不选都行 - StringBuilder sb = new StringBuilder(); - //使用 sb 逐个复制 s ,碰到空格则替换,否则直接复制 - for (int i = 0; i < s.length(); i++) { - //s.charAt(i) 为 char 类型,为了比较需要将其转为和 " " 相同的字符串类型 - //if (" ".equals(String.valueOf(s.charAt(i)))){} - if (s.charAt(i) == ' ') { - sb.append("%20"); - } else { - sb.append(s.charAt(i)); - } - } - return sb.toString(); - } - -//方式二:双指针法 -public String replaceSpace(String s) { - if(s == null || s.length() == 0){ - return s; - } - //扩充空间,空格数量2倍 - StringBuilder str = new StringBuilder(); - for (int i = 0; i < s.length(); i++) { - if(s.charAt(i) == ' '){ - str.append(" "); - } - } - //若是没有空格直接返回 - if(str.length() == 0){ - return s; - } - //有空格情况 定义两个指针 - int left = s.length() - 1;//左指针:指向原始字符串最后一个位置 - s += str.toString(); - int right = s.length()-1;//右指针:指向扩展字符串的最后一个位置 - char[] chars = s.toCharArray(); - while(left>=0){ - if(chars[left] == ' '){ - chars[right--] = '0'; - chars[right--] = '2'; - chars[right] = '%'; - }else{ - chars[right] = chars[left]; - } - left--; - right--; - } - return new String(chars); -} -``` ### Go: -```go -// 遍历添加 -func replaceSpace(s string) string { - b := []byte(s) - result := make([]byte, 0) - for i := 0; i < len(b); i++ { - if b[i] == ' ' { - result = append(result, []byte("%20")...) - } else { - result = append(result, b[i]) - } - } - return string(result) -} - -// 原地修改 -func replaceSpace(s string) string { - b := []byte(s) - length := len(b) - spaceCount := 0 - // 计算空格数量 - for _, v := range b { - if v == ' ' { - spaceCount++ - } - } - // 扩展原有切片 - resizeCount := spaceCount * 2 - tmp := make([]byte, resizeCount) - b = append(b, tmp...) - i := length - 1 - j := len(b) - 1 - for i >= 0 { - if b[i] != ' ' { - b[j] = b[i] - i-- - j-- - } else { - b[j] = '0' - b[j-1] = '2' - b[j-2] = '%' - i-- - j = j - 3 - } - } - return string(b) -} -``` - ### python: -因为字符串是不可变类型,所以操作字符串需要将其转换为列表,因此空间复杂度不可能为O(1) - -(版本一)转换成列表,并且添加相匹配的空间,然后进行填充 -```python -class Solution: - def replaceSpace(self, s: str) -> str: - counter = s.count(' ') - - res = list(s) - # 每碰到一个空格就多拓展两个格子,1 + 2 = 3个位置存’%20‘ - res.extend([' '] * counter * 2) - - # 原始字符串的末尾,拓展后的末尾 - left, right = len(s) - 1, len(res) - 1 - - while left >= 0: - if res[left] != ' ': - res[right] = res[left] - right -= 1 - else: - # [right - 2, right), 左闭右开 - res[right - 2: right + 1] = '%20' - right -= 3 - left -= 1 - return ''.join(res) - -``` -(版本二)添加空列表,添加匹配的结果 -```python -class Solution: - def replaceSpace(self, s: str) -> str: - res = [] - for i in range(len(s)): - if s[i] == ' ': - res.append('%20') - else: - res.append(s[i]) - return ''.join(res) -``` -(版本三)使用切片 -```python -class Solution: - def replaceSpace(self, s: str) -> str: - n = len(s) - for e, i in enumerate(s[::-1]): - print(i, e) - if i == " ": - s = s[: n - (e + 1)] + "%20" + s[n - e:] - print("") - return s -``` -(版本四)使用join + split -```python -class Solution: - def replaceSpace(self, s: str) -> str: - return "%20".join(s.split(" ")) -``` -(版本五)使用replace -```python -class Solution: - def replaceSpace(self, s: str) -> str: - return s.replace(' ', '%20') -``` ### JavaScript: -```js -/** - * @param {string} s - * @return {string} - */ - var replaceSpace = function(s) { - // 字符串转为数组 - const strArr = Array.from(s); - let count = 0; - - // 计算空格数量 - for(let i = 0; i < strArr.length; i++) { - if (strArr[i] === ' ') { - count++; - } - } - - let left = strArr.length - 1; - let right = strArr.length + count * 2 - 1; - - while(left >= 0) { - if (strArr[left] === ' ') { - strArr[right--] = '0'; - strArr[right--] = '2'; - strArr[right--] = '%'; - left--; - } else { - strArr[right--] = strArr[left--]; - } - } - - // 数组转字符串 - return strArr.join(''); -}; -``` ### TypeScript: -```typescript -function replaceSpace(s: string): string { - let arr: string[] = s.split(''); - let spaceNum: number = 0; - let oldLength: number = arr.length; - for (let i = 0; i < oldLength; i++) { - if (arr[i] === ' ') { - spaceNum++; - } - } - arr.length = oldLength + 2 * spaceNum; - let cur: number = oldLength - 1; - for (let i = arr.length - 1; i >= 0; i--, cur--) { - if (arr[cur] !== ' ') { - arr[i] = arr[cur] - } else { - arr[i] = '0'; - arr[--i] = '2'; - arr[--i] = '%'; - } - } - return arr.join(''); -}; -``` ### Swift: -```swift -func replaceSpace(_ s: String) -> String { - var strArr = Array(s) - var count = 0 - - // 统计空格的个数 - for i in strArr { - if i == " " { - count += 1 - } - } - // left 指向旧数组的最后一个元素 - var left = strArr.count - 1 - // right 指向扩容后数组的最后一个元素(这里还没对数组进行实际上的扩容) - var right = strArr.count + count * 2 - 1 - - // 实际对数组扩容 - for _ in 0..<(count * 2) { - strArr.append(" ") - } - - while left < right { - if strArr[left] == " " { - strArr[right] = "0" - strArr[right - 1] = "2" - strArr[right - 2] = "%" - left -= 1 - right -= 3 - } else { - strArr[right] = strArr[left] - left -= 1 - right -= 1 - } - } - - return String(strArr) -} -``` ### Scala: -方式一: 双指针 -```scala -object Solution { - def replaceSpace(s: String): String = { - var count = 0 - s.foreach(c => if (c == ' ') count += 1) // 统计空格的数量 - val sOldSize = s.length // 旧数组字符串长度 - val sNewSize = s.length + count * 2 // 新数组字符串长度 - val res = new Array[Char](sNewSize) // 新数组 - var index = sNewSize - 1 // 新数组索引 - // 逆序遍历 - for (i <- (0 until sOldSize).reverse) { - if (s(i) == ' ') { - res(index) = '0' - index -= 1 - res(index) = '2' - index -= 1 - res(index) = '%' - } else { - res(index) = s(i) - } - index -= 1 - } - res.mkString - } -} -``` -方式二: 使用一个集合,遇到空格就添加%20 -```scala -object Solution { - import scala.collection.mutable.ListBuffer - def replaceSpace(s: String): String = { - val res: ListBuffer[Char] = ListBuffer[Char]() - for (i <- s.indices) { - if (s(i) == ' ') { - res += '%' - res += '2' - res += '0' - }else{ - res += s(i) - } - } - res.mkString - } -} -``` -方式三: 使用map -```scala -object Solution { - def replaceSpace(s: String): String = { - s.map(c => if(c == ' ') "%20" else c).mkString - } - } -``` ### PHP: -```php -function replaceSpace($s){ - $sLen = strlen($s); - $moreLen = $this->spaceLen($s) * 2; - - $head = $sLen - 1; - $tail = $sLen + $moreLen - 1; - - $s = $s . str_repeat(' ', $moreLen); - while ($head != $tail) { - if ($s[$head] == ' ') { - $s[$tail--] = '0'; - $s[$tail--] = '2'; - $s[$tail] = '%'; - } else { - $s[$tail] = $s[$head]; - } - $head--; - $tail--; - } - return $s; -} -// 统计空格个数 -function spaceLen($s){ - $count = 0; - for ($i = 0; $i < strlen($s); $i++) { - if ($s[$i] == ' ') { - $count++; - } - } - return $count; -} -``` ### Rust: -```Rust -impl Solution { - pub fn replace_space(s: String) -> String { - let mut len: usize = s.len(); - let mut s = s.chars().collect::>(); - let mut count = 0; - for i in &s { - if i.is_ascii_whitespace() { - count += 1; - } - } - let mut new_len = len + count * 2; - s.resize(new_len, ' '); - while len < new_len { - len -= 1; - new_len -= 1; - if s[len].is_ascii_whitespace() { - s[new_len] = '0'; - s[new_len - 1] = '2'; - s[new_len - 2] = '%'; - new_len -= 2; - } - else { s[new_len] = s[len] } - } - s.iter().collect::() - } -} -```

diff --git "a/problems/\345\211\221\346\214\207Offer58-II.\345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" "b/problems/\345\211\221\346\214\207Offer58-II.\345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" index 43a0fe0849..138cf3a88d 100644 --- "a/problems/\345\211\221\346\214\207Offer58-II.\345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" +++ "b/problems/\345\211\221\346\214\207Offer58-II.\345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" @@ -5,413 +5,174 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-> 反转个字符串还有这么多用处? +# 右旋字符串 -# 题目:剑指Offer58-II.左旋转字符串 +力扣已经将剑指offer题目下架,所以在卡码网上给大家提供类似的题目来练习 -[力扣题目链接](https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/) +[卡码网题目链接](https://kamacoder.com/problempage.php?pid=1065) -字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。 +字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。 -示例 1: -输入: s = "abcdefg", k = 2 -输出: "cdefgab" +例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。 -示例 2: -输入: s = "lrloseumgh", k = 6 -输出: "umghlrlose" +输入:输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。 -限制: -1 <= k < s.length <= 10000 +输出:输出共一行,为进行了右旋转操作后的字符串。 -## 思路 +样例输入: -为了让本题更有意义,提升一下本题难度:**不能申请额外空间,只能在本串上操作**。 +``` +2 +abcdefg +``` -不能使用额外空间的话,模拟在本串操作要实现左旋转字符串的功能还是有点困难的。 +样例输出: +``` +fgabcde +``` -那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过,使用整体反转+局部反转就可以实现反转单词顺序的目的。 +数据范围:1 <= k < 10000, 1 <= s.length < 10000; -这道题目也非常类似,依然可以通过局部反转+整体反转 达到左旋转的目的。 -具体步骤为: +## 思路 -1. 反转区间为前n的子串 -2. 反转区间为n到末尾的子串 -3. 反转整个字符串 +为了让本题更有意义,提升一下本题难度:**不能申请额外空间,只能在本串上操作**。 (Java不能在字符串上修改,所以使用java一定要开辟新空间) -最后就可以达到左旋n的目的,而不用定义新的字符串,完全在本串上操作。 +不能使用额外空间的话,模拟在本串操作要实现右旋转字符串的功能还是有点困难的。 -例如 :示例1中 输入:字符串abcdefg,n=2 +那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过,使用整体反转+局部反转就可以实现反转单词顺序的目的。 -如图: - +本题中,我们需要将字符串右移n位,字符串相当于分成了两个部分,如果n为2,符串相当于分成了两个部分,如图: (length为字符串长度) -最终得到左旋2个单元的字符串:cdefgab +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231106170143.png) -思路明确之后,那么代码实现就很简单了 -C++代码如下: +右移n位, 就是将第二段放在前面,第一段放在后面,先不考虑里面字符的顺序,是不是整体倒叙不就行了。如图: -```CPP -class Solution { -public: - string reverseLeftWords(string s, int n) { - reverse(s.begin(), s.begin() + n); - reverse(s.begin() + n, s.end()); - reverse(s.begin(), s.end()); - return s; - } -}; -``` -* 时间复杂度: O(n) -* 空间复杂度:O(1) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231106171557.png) -是不是发现这代码也太简单了。 +此时第一段和第二段的顺序是我们想要的,但里面的字符位置被我们倒叙,那么此时我们在把 第一段和第二段里面的字符再倒叙一把,这样字符顺序不就正确了。 如果: -## 总结 +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231106172058.png) +其实,思路就是 通过 整体倒叙,把两段子串顺序颠倒,两个段子串里的的字符在倒叙一把,**负负得正**,这样就不影响子串里面字符的顺序了。 -此时我们已经反转好多次字符串了,来一起回顾一下吧。 +整体代码如下: -在这篇文章[344.反转字符串](https://programmercarl.com/0344.反转字符串.html),第一次讲到反转一个字符串应该怎么做,使用了双指针法。 +```CPP +// 版本一 +#include +#include +using namespace std; +int main() { + int n; + string s; + cin >> n; + cin >> s; + int len = s.size(); //获取长度 -然后发现[541. 反转字符串II](https://programmercarl.com/0541.反转字符串II.html),这里开始给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在for循环的表达式上做做文章。 + reverse(s.begin(), s.end()); // 整体反转 + reverse(s.begin(), s.begin() + n); // 先反转前一段,长度n + reverse(s.begin() + n, s.end()); // 再反转后一段 -后来在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。 + cout << s << endl; -最后再讲到本题,本题则是先局部反转再 整体反转,与[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)类似,但是也是一种新的思路。 +} +``` -好了,反转字符串一共就介绍到这里,相信大家此时对反转字符串的常见操作已经很了解了。 +那么整体反正的操作放在下面,先局部反转行不行? -## 题外话 +可以的,不过,要记得 控制好 局部反转的长度,如果先局部反转,那么先反转的子串长度就是 len - n,如图: -一些同学热衷于使用substr,来做这道题。 -其实使用substr 和 反转 时间复杂度是一样的 ,都是O(n),但是使用substr申请了额外空间,所以空间复杂度是O(n),而反转方法的空间复杂度是O(1)。 +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231106172534.png) -**如果想让这套题目有意义,就不要申请额外空间。** +代码如下: +```CPP +// 版本二 +#include +#include +using namespace std; +int main() { + int n; + string s; + cin >> n; + cin >> s; + int len = s.size(); //获取长度 + reverse(s.begin(), s.begin() + len - n); // 先反转前一段,长度len-n ,注意这里是和版本一的区别 + reverse(s.begin() + len - n, s.end()); // 再反转后一段 + reverse(s.begin(), s.end()); // 整体反转 + cout << s << endl; -## 其他语言版本 +} +``` -### Java: -```java -class Solution { - public String reverseLeftWords(String s, int n) { - int len=s.length(); - StringBuilder sb=new StringBuilder(s); - reverseString(sb,0,n-1); - reverseString(sb,n,len-1); - return sb.reverse().toString(); - } - public void reverseString(StringBuilder sb, int start, int end) { - while (start < end) { - char temp = sb.charAt(start); - sb.setCharAt(start, sb.charAt(end)); - sb.setCharAt(end, temp); - start++; - end--; - } - } +## 拓展 + +大家在做剑指offer的时候,会发现 剑指offer的题目是左反转,那么左反转和右反转 有什么区别呢? + +其实思路是一样一样的,就是反转的区间不同而已。如果本题是左旋转n,那么实现代码如下: + +```CPP +#include +#include +using namespace std; +int main() { + int n; + string s; + cin >> n; + cin >> s; + int len = s.size(); //获取长度 + reverse(s.begin(), s.begin() + n); // 反转第一段长度为n + reverse(s.begin() + n, s.end()); // 反转第二段长度为len-n + reverse(s.begin(), s.end()); // 整体反转 + cout << s << endl; + } ``` -```java -// 解法二 -// 空间复杂度:O(n)。String 的 toCharArray() 方法底层会 new 一个和原字符串相同大小的 char 数组 -// 思路为:先整个字符串反转,再反转前面的,最后反转后面 n 个 -class Solution { - public String reverseLeftWords(String s, int n) { - char[] chars = s.toCharArray(); - reverse(chars, 0, chars.length - 1); - reverse(chars, 0, chars.length - 1 - n); - reverse(chars, chars.length - n, chars.length - 1); - return new String(chars); - } - - public void reverse(char[] chars, int left, int right) { - while (left < right) { - chars[left] ^= chars[right]; - chars[right] ^= chars[left]; - chars[left] ^= chars[right]; - left++; - right--; - } - } -``` +大家可以感受一下 这份代码和 版本二的区别, 其实就是反转的区间不同而已。 -### Python: -(版本一)使用切片 +那么左旋转的话,可以不可以先整体反转,例如想版本一的那样呢? -```python -class Solution: - def reverseLeftWords(self, s: str, n: int) -> str: - return s[n:] + s[:n] -``` -(版本二)使用reversed + join - -```python -class Solution: - def reverseLeftWords(self, s: str, n: int) -> str: - s = list(s) - s[0:n] = list(reversed(s[0:n])) - s[n:] = list(reversed(s[n:])) - s.reverse() - - return "".join(s) +当然可以。 -``` -(版本三)自定义reversed函数 - -```python -class Solution: - def reverseLeftWords(self, s: str, n: int) -> str: - s_list = list(s) - - self.reverse(s_list, 0, n - 1) - self.reverse(s_list, n, len(s_list) - 1) - self.reverse(s_list, 0, len(s_list) - 1) - - return ''.join(s_list) - - def reverse(self, s, start, end): - while start < end: - s[start], s[end] = s[end], s[start] - start += 1 - end -= 1 -``` -(版本四)使用 模 +下标 -```python 3 -class Solution: - def reverseLeftWords(self, s: str, n: int) -> str: - new_s = '' - for i in range(len(s)): - j = (i+n)%len(s) - new_s = new_s + s[j] - return new_s -``` -(版本五)使用 模 + 切片 - -```python 3 -class Solution: - def reverseLeftWords(self, s: str, n: int) -> str: - l = len(s) - # 复制输入字符串与它自己连接 - s = s + s - - # 计算旋转字符串的起始索引 - k = n % (l * 2) - - # 从连接的字符串中提取旋转后的字符串并返回 - return s[k : k + l] +## 其他语言版本 + +### Java: + + + +### Python: -``` ### Go: -```go -func reverseLeftWords(s string, n int) string { - b := []byte(s) - // 1. 反转前n个字符 - // 2. 反转第n到end字符 - // 3. 反转整个字符 - reverse(b, 0, n-1) - reverse(b, n, len(b)-1) - reverse(b, 0, len(b)-1) - return string(b) -} -// 切片是引用传递 -func reverse(b []byte, left, right int){ - for left < right{ - b[left], b[right] = b[right],b[left] - left++ - right-- - } -} -``` ### JavaScript: -```javascript -var reverseLeftWords = function(s, n) { - const length = s.length; - let i = 0; - while (i < length - n) { - s = s[length - 1] + s; - i++; - } - return s.slice(0, length); -}; -``` - -版本二(在原字符串上操作): - -```js -/** - * @param {string} s - * @param {number} n - * @return {string} - */ -var reverseLeftWords = function (s, n) { - /** Utils */ - function reverseWords(strArr, start, end) { - let temp; - while (start < end) { - temp = strArr[start]; - strArr[start] = strArr[end]; - strArr[end] = temp; - start++; - end--; - } - } - /** Main code */ - let strArr = s.split(''); - let length = strArr.length; - reverseWords(strArr, 0, length - 1); - reverseWords(strArr, 0, length - n - 1); - reverseWords(strArr, length - n, length - 1); - return strArr.join(''); -}; -``` ### TypeScript: -```typescript -function reverseLeftWords(s: string, n: number): string { - /** Utils */ - function reverseWords(strArr: string[], start: number, end: number): void { - let temp: string; - while (start < end) { - temp = strArr[start]; - strArr[start] = strArr[end]; - strArr[end] = temp; - start++; - end--; - } - } - /** Main code */ - let strArr: string[] = s.split(''); - let length: number = strArr.length; - reverseWords(strArr, 0, length - 1); - reverseWords(strArr, 0, length - n - 1); - reverseWords(strArr, length - n, length - 1); - return strArr.join(''); -}; -``` -方法二: -```typescript -// 拼接两个字符串,截取符合要求的部分 -function reverseLeftWords(s: string, n: number): string { - return (s+s).slice(n,s.length+n); -}; -``` ### Swift: -```swift -func reverseLeftWords(_ s: String, _ n: Int) -> String { - var ch = Array(s) - let len = ch.count - // 反转区间[0, n - 1] - reverseString(&ch, startIndex: 0, endIndex: n - 1) - // 反转区间[n, len - 1] - reverseString(&ch, startIndex: n, endIndex: len - 1) - // 反转区间[0, len - 1],也就是整个字符串反转 - reverseString(&ch, startIndex: 0, endIndex: len - 1) - return String(ch) -} - -func reverseString(_ s: inout [Character], startIndex: Int, endIndex: Int) { - var start = startIndex - var end = endIndex - while start < end { - (s[start], s[end]) = (s[end], s[start]) - start += 1 - end -= 1 - } -} -``` ### PHP: -```php -function reverseLeftWords($s, $n) { - $this->reverse($s,0,$n-1); //反转区间为前n的子串 - $this->reverse($s,$n,strlen($s)-1); //反转区间为n到末尾的子串 - $this->reverse($s,0,strlen($s)-1); //反转整个字符串 - return $s; -} - -// 按指定进行翻转 【array、string都可】 -function reverse(&$s, $start, $end) { - for ($i = $start, $j = $end; $i < $j; $i++, $j--) { - $tmp = $s[$i]; - $s[$i] = $s[$j]; - $s[$j] = $tmp; - } -} -``` ### Scala: -```scala -object Solution { - def reverseLeftWords(s: String, n: Int): String = { - var str = s.toCharArray // 转换为Array - // abcdefg => ba cdefg - reverseString(str, 0, n - 1) - // ba cdefg => ba gfedc - reverseString(str, n, str.length - 1) - // ba gfedc => cdefgab - reverseString(str, 0, str.length - 1) - // 最终返回,return关键字可以省略 - new String(str) - } - // 翻转字符串 - def reverseString(s: Array[Char], start: Int, end: Int): Unit = { - var (left, right) = (start, end) - while (left < right) { - var tmp = s(left) - s(left) = s(right) - s(right) = tmp - left += 1 - right -= 1 - } - } -} -``` ### Rust: -```Rust -impl Solution { - pub fn reverse(s: &mut Vec, mut begin: usize, mut end: usize){ - while begin < end { - let temp = s[begin]; - s[begin] = s[end]; - s[end] = temp; - begin += 1; - end -= 1; - } - } - pub fn reverse_left_words(s: String, n: i32) -> String { - let len = s.len(); - let mut s = s.chars().collect::>(); - let n = n as usize; - Self::reverse(&mut s, 0, n - 1); - Self::reverse(&mut s, n, len - 1); - Self::reverse(&mut s, 0, len - 1); - s.iter().collect::() - } -} -``` diff --git "a/problems/\350\203\214\345\214\205\347\220\206\350\256\272\345\237\272\347\241\20001\350\203\214\345\214\205-1.md" "b/problems/\350\203\214\345\214\205\347\220\206\350\256\272\345\237\272\347\241\20001\350\203\214\345\214\205-1.md" index bc5f2e5d67..09fb97a9d8 100644 --- "a/problems/\350\203\214\345\214\205\347\220\206\350\256\272\345\237\272\347\241\20001\350\203\214\345\214\205-1.md" +++ "b/problems/\350\203\214\345\214\205\347\220\206\350\256\272\345\237\272\347\241\20001\350\203\214\345\214\205-1.md" @@ -9,7 +9,7 @@ # 动态规划:01背包理论基础 -本题力扣上没有原题,大家可以去[卡码网第46题](https://kamacoder.com/problem.php?id=1046)去练习,题意是一样的。 +本题力扣上没有原题,大家可以去[卡码网第46题](https://kamacoder.com/problempage.php?pid=1046)去练习,题意是一样的。 ## 算法公开课 diff --git "a/problems/\350\203\214\345\214\205\347\220\206\350\256\272\345\237\272\347\241\20001\350\203\214\345\214\205-2.md" "b/problems/\350\203\214\345\214\205\347\220\206\350\256\272\345\237\272\347\241\20001\350\203\214\345\214\205-2.md" index 0b4f8450c2..674166a199 100644 --- "a/problems/\350\203\214\345\214\205\347\220\206\350\256\272\345\237\272\347\241\20001\350\203\214\345\214\205-2.md" +++ "b/problems/\350\203\214\345\214\205\347\220\206\350\256\272\345\237\272\347\241\20001\350\203\214\345\214\205-2.md" @@ -6,7 +6,7 @@ # 动态规划:01背包理论基础(滚动数组) -本题力扣上没有原题,大家可以去[卡码网第46题](https://kamacoder.com/problem.php?id=1046)去练习 +本题力扣上没有原题,大家可以去[卡码网第46题](https://kamacoder.com/problempage.php?pid=1046)去练习 ## 算法公开课 diff --git "a/problems/\350\203\214\345\214\205\351\227\256\351\242\230\347\220\206\350\256\272\345\237\272\347\241\200\345\244\232\351\207\215\350\203\214\345\214\205.md" "b/problems/\350\203\214\345\214\205\351\227\256\351\242\230\347\220\206\350\256\272\345\237\272\347\241\200\345\244\232\351\207\215\350\203\214\345\214\205.md" index 50c2e5bf8c..b19b52731a 100644 --- "a/problems/\350\203\214\345\214\205\351\227\256\351\242\230\347\220\206\350\256\272\345\237\272\347\241\200\345\244\232\351\207\215\350\203\214\345\214\205.md" +++ "b/problems/\350\203\214\345\214\205\351\227\256\351\242\230\347\220\206\350\256\272\345\237\272\347\241\200\345\244\232\351\207\215\350\203\214\345\214\205.md" @@ -7,6 +7,8 @@ # 动态规划:关于多重背包,你该了解这些! +本题力扣上没有原题,大家可以去[卡码网第56题](https://kamacoder.com/problempage.php?pid=1066)去练习,题意是一样的。 + 之前我们已经系统的讲解了01背包和完全背包,如果没有看过的录友,建议先把如下三篇文章仔细阅读一波。 * [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html) @@ -53,79 +55,96 @@ 毫无区别,这就转成了一个01背包问题了,且每个物品只用一次。 -这种方式来实现多重背包的代码如下: + +练习题目:[卡码网第56题,多重背包](https://kamacoder.com/problempage.php?pid=1066) + +代码如下: ```CPP -void test_multi_pack() { - vector weight = {1, 3, 4}; - vector value = {15, 20, 30}; - vector nums = {2, 3, 2}; - int bagWeight = 10; - for (int i = 0; i < nums.size(); i++) { - while (nums[i] > 1) { // nums[i]保留到1,把其他物品都展开 +// 超时了 +#include +#include +using namespace std; +int main() { + int bagWeight,n; + cin >> bagWeight >> n; + vector weight(n, 0); + vector value(n, 0); + vector nums(n, 0); + for (int i = 0; i < n; i++) cin >> weight[i]; + for (int i = 0; i < n; i++) cin >> value[i]; + for (int i = 0; i < n; i++) cin >> nums[i]; + + for (int i = 0; i < n; i++) { + while (nums[i] > 1) { // 物品数量不是一的,都展开 weight.push_back(weight[i]); value.push_back(value[i]); nums[i]--; } } - + vector dp(bagWeight + 1, 0); - for(int i = 0; i < weight.size(); i++) { // 遍历物品 + for(int i = 0; i < weight.size(); i++) { // 遍历物品,注意此时的物品数量不是n for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } - for (int j = 0; j <= bagWeight; j++) { - cout << dp[j] << " "; - } - cout << endl; } cout << dp[bagWeight] << endl; - -} -int main() { - test_multi_pack(); } +``` + +大家去提交之后,发现这个解法超时了,为什么呢,哪里耗时呢? + +耗时就在 这段代码: +```CPP +for (int i = 0; i < n; i++) { + while (nums[i] > 1) { // 物品数量不是一的,都展开 + weight.push_back(weight[i]); + value.push_back(value[i]); + nums[i]--; + } +} ``` -* 时间复杂度:O(m × n × k),m:物品种类个数,n背包容量,k单类物品数量 +如果物品数量很多的话,C++中,这种操作十分费时,主要消耗在vector的动态底层扩容上。(其实这里也可以优化,先把 所有物品数量都计算好,一起申请vector的空间。 + -也有另一种实现方式,就是把每种商品遍历的个数放在01背包里面在遍历一遍。 +这里也有另一种实现方式,就是把每种商品遍历的个数放在01背包里面在遍历一遍。 代码如下:(详看注释) +```CPP +#include +#include +using namespace std; +int main() { + int bagWeight,n; + cin >> bagWeight >> n; + vector weight(n, 0); + vector value(n, 0); + vector nums(n, 0); + for (int i = 0; i < n; i++) cin >> weight[i]; + for (int i = 0; i < n; i++) cin >> value[i]; + for (int i = 0; i < n; i++) cin >> nums[i]; -```CPP -void test_multi_pack() { - vector weight = {1, 3, 4}; - vector value = {15, 20, 30}; - vector nums = {2, 3, 2}; - int bagWeight = 10; vector dp(bagWeight + 1, 0); - - for(int i = 0; i < weight.size(); i++) { // 遍历物品 + for(int i = 0; i < n; i++) { // 遍历物品 for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量 // 以上为01背包,然后加一个遍历个数 for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) { // 遍历个数 dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]); } } - // 打印一下dp数组 - for (int j = 0; j <= bagWeight; j++) { - cout << dp[j] << " "; - } - cout << endl; } + cout << dp[bagWeight] << endl; } -int main() { - test_multi_pack(); -} ``` -* 时间复杂度:O(m × n × k),m:物品种类个数,n背包容量,k单类物品数量 +时间复杂度:O(m × n × k),m:物品种类个数,n背包容量,k单类物品数量 从代码里可以看出是01背包里面在加一个for循环遍历一个每种商品的数量。 和01背包还是如出一辙的。 @@ -146,320 +165,14 @@ int main() { ### Java: -```Java -public void testMultiPack1(){ - // 版本一:改变物品数量为01背包格式 - List weight = new ArrayList<>(Arrays.asList(1, 3, 4)); - List value = new ArrayList<>(Arrays.asList(15, 20, 30)); - List nums = new ArrayList<>(Arrays.asList(2, 3, 2)); - int bagWeight = 10; - - for (int i = 0; i < nums.size(); i++) { - while (nums.get(i) > 1) { // 把物品展开为i - weight.add(weight.get(i)); - value.add(value.get(i)); - nums.set(i, nums.get(i) - 1); - } - } - - int[] dp = new int[bagWeight + 1]; - for(int i = 0; i < weight.size(); i++) { // 遍历物品 - for(int j = bagWeight; j >= weight.get(i); j--) { // 遍历背包容量 - dp[j] = Math.max(dp[j], dp[j - weight.get(i)] + value.get(i)); - } - System.out.println(Arrays.toString(dp)); - } -} - -public void testMultiPack2(){ - // 版本二:改变遍历个数 - int[] weight = new int[] {1, 3, 4}; - int[] value = new int[] {15, 20, 30}; - int[] nums = new int[] {2, 3, 2}; - int bagWeight = 10; - - int[] dp = new int[bagWeight + 1]; - for(int i = 0; i < weight.length; i++) { // 遍历物品 - for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量 - // 以上为01背包,然后加一个遍历个数 - for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) { // 遍历个数 - dp[j] = Math.max(dp[j], dp[j - k * weight[i]] + k * value[i]); - } - System.out.println(Arrays.toString(dp)); - } - } -} -``` ### Python: -改变物品数量为01背包格式(无参版) -```python -def test_multi_pack(): - weight = [1, 3, 4] - value = [15, 20, 30] - nums = [2, 3, 2] - bagWeight = 10 - - # 将数量大于1的物品展开 - for i in range(len(nums)): - while nums[i] > 1: - weight.append(weight[i]) - value.append(value[i]) - nums[i] -= 1 - - dp = [0] * (bagWeight + 1) - for i in range(len(weight)): # 遍历物品 - for j in range(bagWeight, weight[i] - 1, -1): # 遍历背包容量 - dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) - for j in range(bagWeight + 1): - print(dp[j], end=" ") - print() - - print(dp[bagWeight]) - - -test_multi_pack() - -``` - - -改变遍历个数(无参版) -```python -def test_multi_pack(): - weight = [1, 3, 4] - value = [15, 20, 30] - nums = [2, 3, 2] - bagWeight = 10 - dp = [0] * (bagWeight + 1) - - for i in range(len(weight)): # 遍历物品 - for j in range(bagWeight, weight[i] - 1, -1): # 遍历背包容量 - # 以上为01背包,然后加一个遍历个数 - for k in range(1, nums[i] + 1): # 遍历个数 - if j - k * weight[i] >= 0: - dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]) - - # 打印一下dp数组 - for j in range(bagWeight + 1): - print(dp[j], end=" ") - print() - - print(dp[bagWeight]) - - -test_multi_pack() - -``` - - -改变物品数量为01背包格式(有参版) -```python -def test_multi_pack(weight, value, nums, bagWeight): - # 将数量大于1的物品展开 - for i in range(len(nums)): - while nums[i] > 1: - weight.append(weight[i]) - value.append(value[i]) - nums[i] -= 1 - - dp = [0] * (bagWeight + 1) - for i in range(len(weight)): # 遍历物品 - for j in range(bagWeight, weight[i] - 1, -1): # 遍历背包容量 - dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) - for j in range(bagWeight + 1): - print(dp[j], end=" ") - print() - - print(dp[bagWeight]) - - - - -if __name__ == "__main__": - weight = [1, 3, 4] - value = [15, 20, 30] - nums = [2, 3, 2] - bagWeight = 10 - test_multi_pack(weight, value, nums, bagWeight) -``` - - -改变遍历个数(有参版) -```python -def test_multi_pack(weight, value, nums, bagWeight): - dp = [0] * (bagWeight + 1) - - for i in range(len(weight)): # 遍历物品 - for j in range(bagWeight, weight[i] - 1, -1): # 遍历背包容量 - # 以上为01背包,然后加一个遍历个数 - for k in range(1, nums[i] + 1): # 遍历个数 - if j - k * weight[i] >= 0: - dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]) - - # 使用 join 函数打印 dp 数组 - print(' '.join(str(dp[j]) for j in range(bagWeight + 1))) - - print(dp[bagWeight]) - - - - - -if __name__ == "__main__": - weight = [1, 3, 4] - value = [15, 20, 30] - nums = [2, 3, 2] - bagWeight = 10 - test_multi_pack(weight, value, nums, bagWeight) - -``` ### Go: -```go -package theory - -import "log" - -// 多重背包可以化解为 01 背包 -func multiplePack(weight, value, nums []int, bagWeight int) int { - - for i := 0; i < len(nums); i++ { - for nums[i] > 1 { - weight = append(weight, weight[i]) - value = append(value, value[i]) - nums[i]-- - } - } - log.Println(weight) - log.Println(value) - - res := make([]int, bagWeight+1) - for i := 0; i < len(weight); i++ { - for j := bagWeight; j >= weight[i]; j-- { - res[j] = getMax(res[j], res[j-weight[i]]+value[i]) - } - log.Println(res) - } - - return res[bagWeight] -} -``` - -> 单元测试 - -```go -package theory - -import "testing" - -func Test_multiplePack(t *testing.T) { - type args struct { - weight []int - value []int - nums []int - bagWeight int - } - tests := []struct { - name string - args args - want int - }{ - { - name: "one", - args: args{ - weight: []int{1, 3, 4}, - value: []int{15, 20, 30}, - nums: []int{2, 3, 2}, - bagWeight: 10, - }, - want: 90, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := multiplePack(tt.args.weight, tt.args.value, tt.args.nums, tt.args.bagWeight); got != tt.want { - t.Errorf("multiplePack() = %v, want %v", got, tt.want) - } - }) - } -} -``` - -> 输出 - -``` -=== RUN Test_multiplePack -=== RUN Test_multiplePack/one -2022/03/02 21:09:05 [1 3 4 1 3 3 4] -2022/03/02 21:09:05 [15 20 30 15 20 20 30] -2022/03/02 21:09:05 [0 15 15 15 15 15 15 15 15 15 15] -2022/03/02 21:09:05 [0 15 15 20 35 35 35 35 35 35 35] -2022/03/02 21:09:05 [0 15 15 20 35 45 45 50 65 65 65] -2022/03/02 21:09:05 [0 15 30 30 35 50 60 60 65 80 80] -2022/03/02 21:09:05 [0 15 30 30 35 50 60 60 70 80 80] -2022/03/02 21:09:05 [0 15 30 30 35 50 60 60 70 80 80] -2022/03/02 21:09:05 [0 15 30 30 35 50 60 60 70 80 90] ---- PASS: Test_multiplePack (0.00s) - --- PASS: Test_multiplePack/one (0.00s) -PASS -``` ### TypeScript: -> 版本一(改变数据源): - -```typescript -function testMultiPack() { - const bagSize: number = 10; - const weightArr: number[] = [1, 3, 4], - valueArr: number[] = [15, 20, 30], - amountArr: number[] = [2, 3, 2]; - for (let i = 0, length = amountArr.length; i < length; i++) { - while (amountArr[i] > 1) { - weightArr.push(weightArr[i]); - valueArr.push(valueArr[i]); - amountArr[i]--; - } - } - const goodsNum: number = weightArr.length; - const dp: number[] = new Array(bagSize + 1).fill(0); - // 遍历物品 - for (let i = 0; i < goodsNum; i++) { - // 遍历背包容量 - for (let j = bagSize; j >= weightArr[i]; j--) { - dp[j] = Math.max(dp[j], dp[j - weightArr[i]] + valueArr[i]); - } - } - console.log(dp); -} -testMultiPack(); -``` - -> 版本二(改变遍历方式): - -```typescript -function testMultiPack() { - const bagSize: number = 10; - const weightArr: number[] = [1, 3, 4], - valueArr: number[] = [15, 20, 30], - amountArr: number[] = [2, 3, 2]; - const goodsNum: number = weightArr.length; - const dp: number[] = new Array(bagSize + 1).fill(0); - // 遍历物品 - for (let i = 0; i < goodsNum; i++) { - // 遍历物品个数 - for (let j = 0; j < amountArr[i]; j++) { - // 遍历背包容量 - for (let k = bagSize; k >= weightArr[i]; k--) { - dp[k] = Math.max(dp[k], dp[k - weightArr[i]] + valueArr[i]); - } - } - } - console.log(dp); -} -testMultiPack(); -``` diff --git "a/problems/\350\203\214\345\214\205\351\227\256\351\242\230\347\220\206\350\256\272\345\237\272\347\241\200\345\256\214\345\205\250\350\203\214\345\214\205.md" "b/problems/\350\203\214\345\214\205\351\227\256\351\242\230\347\220\206\350\256\272\345\237\272\347\241\200\345\256\214\345\205\250\350\203\214\345\214\205.md" index c6c856978a..92e944cc49 100644 --- "a/problems/\350\203\214\345\214\205\351\227\256\351\242\230\347\220\206\350\256\272\345\237\272\347\241\200\345\256\214\345\205\250\350\203\214\345\214\205.md" +++ "b/problems/\350\203\214\345\214\205\351\227\256\351\242\230\347\220\206\350\256\272\345\237\272\347\241\200\345\256\214\345\205\250\350\203\214\345\214\205.md" @@ -7,7 +7,7 @@ # 动态规划:完全背包理论基础 -本题力扣上没有原题,大家可以去[卡码网第52题](https://kamacoder.com/problem.php?id=1052)去练习,题意是一样的。 +本题力扣上没有原题,大家可以去[卡码网第52题](https://kamacoder.com/problempage.php?pid=1052)去练习,题意是一样的。 ## 算法公开课