This commit is contained in:
programmercarl
2023-11-09 14:32:56 +08:00
parent 4dac467907
commit 2caee6fe9f
12 changed files with 618 additions and 1266 deletions

View File

@ -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)

View File

@ -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 {

View File

@ -4,30 +4,34 @@
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 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 = 2n = 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 <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
while (cin >> n >> m) {
vector<int> 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]
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">

View File

@ -0,0 +1,176 @@
<p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 替换数字
[卡码网题目链接](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<iostream>
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:
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>

View File

@ -0,0 +1,181 @@
<p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 右旋字符串
[卡码网题目链接](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<iostream>
#include<algorithm>
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<iostream>
#include<algorithm>
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<iostream>
#include<algorithm>
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:
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>

View File

@ -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 {

View File

@ -5,73 +5,92 @@
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 题目剑指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不能修改
首先扩充数组到每个空格替换成"%20"之后的大小
首先扩充数组到每个数字字符替换成 "number" 之后的大小
然后从后向前替换空格,也就是双指针法,过程如下:
例如 字符串 "a5b" 的长度为3那么 数字字符变成字符串 "number" 之后的字符串为 "anumberb" 长度为 8
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/20231030165201.png)
然后从后向前替换数字字符也就是双指针法过程如下i指向新长度的末尾j指向旧长度的末尾
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231030173058.png)
有同学问了为什么要从后向前填充从前向后填充不行么
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动
从前向后填充就是O(n^2)的算法了因为每次添加元素都要将添加元素之后的所有元素整体向后移动
**其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。**
**其实很多数组填充类的问题,其做饭都是先预先给数组扩容带填充后的大小,然后在从后向前进行操作。**
这么做有两个好处
1. 不用申请新数组
2. 从后向前填充元素避免了从前向后填充元素时每次添加元素都要将添加元素之后的所有元素向后移动的问题
时间复杂度空间复杂度均超过100%的用户。
<img src='https://code-thinking.cdn.bcebos.com/pics/剑指Offer05.替换空格.png' width=600> </img></div>
C++代码如下
```CPP
class Solution {
public:
string replaceSpace(string s) {
int count = 0; // 统计空格的个数
#include<iostream>
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::<Vec<char>>();
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::<String>()
}
}
```
<p align="center">

View File

@ -5,413 +5,174 @@
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
> 反转个字符串还有这么多用处?
# 右旋字符串
# 题目:剑指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代表需要旋转的字符串。
输出:输出共一行,为进行了右旋转操作后的字符串。
样例输入:
```
2
abcdefg
```
样例输出:
```
fgabcde
```
数据范围1 <= k < 10000, 1 <= s.length < 10000;
限制:
1 <= k < s.length <= 10000
## 思路
为了让本题更有意义提升一下本题难度**不能申请额外空间只能在本串上操作**。
不能使用额外空间的话模拟在本串操作要实现左旋转字符串的功能还是有点困难的
为了让本题更有意义提升一下本题难度:**不能申请额外空间只能在本串上操作**。 Java不能在字符串上修改所以使用java一定要开辟新空间
不能使用额外空间的话,模拟在本串操作要实现右旋转字符串的功能还是有点困难的。
那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过,使用整体反转+局部反转就可以实现反转单词顺序的目的。
这道题目也非常类似依然可以通过局部反转+整体反转 达到左旋转的目的
具体步骤为
本题中我们需要将字符串右移n位字符串相当于分成了两个部分如果n为2符串相当于分成了两个部分如图 length为字符串长度
1. 反转区间为前n的子串
2. 反转区间为n到末尾的子串
3. 反转整个字符串
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231106170143.png)
最后就可以达到左旋n的目的而不用定义新的字符串完全在本串上操作
例如 示例1中 输入字符串abcdefgn=2
右移n位 就是将第二段放在前面,第一段放在后面,先不考虑里面字符的顺序,是不是整体倒叙不就行了。如图:
如图
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231106171557.png)
<img src='https://code-thinking.cdn.bcebos.com/pics/剑指Offer58-II.左旋转字符串.png' width=600> </img></div>
此时第一段和第二段的顺序是我们想要的,但里面的字符位置被我们倒叙,那么此时我们在把 第一段和第二段里面的字符再倒叙一把,这样字符顺序不就正确了。 如果:
最终得到左旋2个单元的字符串cdefgab
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20231106172058.png)
思路明确之后,那么代码实现就很简单了
其实,思路就是 通过 整体倒叙,把两段子串顺序颠倒,两个段子串里的的字符在倒叙一把,**负负得正**,这样就不影响子串里面字符的顺序了。
C++代码如下:
整体代码如下:
```CPP
// 版本一
#include<iostream>
#include<algorithm>
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
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;
}
};
// 版本二
#include<iostream>
#include<algorithm>
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;
}
```
* 时间复杂度: O(n)
* 空间复杂度O(1)
是不是发现这代码也太简单了。
## 总结
此时我们已经反转好多次字符串了,来一起回顾一下吧。
## 拓展
在这篇文章[344.反转字符串](https://programmercarl.com/0344.反转字符串.html),第一次讲到反转一个字符串应该怎么做,使用了双指针法。
大家在做剑指offer的时候会发现 剑指offer的题目是左反转那么左反转和右反转 有什么区别呢?
然后发现[541. 反转字符串II](https://programmercarl.com/0541.反转字符串II.html)这里开始给反转加上了一些条件当需要固定规律一段一段去处理字符串的时候要想想在for循环的表达式上做做文章。
其实思路是一样一样的就是反转的区间不同而已。如果本题是左旋转n那么实现代码如下
后来在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。
```CPP
#include<iostream>
#include<algorithm>
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;
最后再讲到本题,本题则是先局部反转再 整体反转,与[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)类似,但是也是一种新的思路。
}
```
好了,反转字符串一共就介绍到这里,相信大家此时对反转字符串的常见操作已经很了解了。
大家可以感受一下 这份代码和 版本二的区别, 其实就是反转的区间不同而已。
## 题外话
那么左旋转的话,可以不可以先整体反转,例如想版本一的那样呢?
当然可以。
一些同学热衷于使用substr来做这道题。
其实使用substr 和 反转 时间复杂度是一样的 都是O(n)但是使用substr申请了额外空间所以空间复杂度是O(n)而反转方法的空间复杂度是O(1)。
**如果想让这套题目有意义,就不要申请额外空间。**
## 其他语言版本
### 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--;
}
}
}
```
```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]
```
### 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<char>, 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::<Vec<char>>();
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::<String>()
}
}
```

View File

@ -9,7 +9,7 @@
# 动态规划01背包理论基础
本题力扣上没有原题,大家可以去[卡码网第46题](https://kamacoder.com/problem.php?id=1046)去练习,题意是一样的。
本题力扣上没有原题,大家可以去[卡码网第46题](https://kamacoder.com/problempage.php?pid=1046)去练习,题意是一样的。
## 算法公开课

View File

@ -6,7 +6,7 @@
# 动态规划01背包理论基础滚动数组
本题力扣上没有原题,大家可以去[卡码网第46题](https://kamacoder.com/problem.php?id=1046)去练习
本题力扣上没有原题,大家可以去[卡码网第46题](https://kamacoder.com/problempage.php?pid=1046)去练习
## 算法公开课

View File

@ -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<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
vector<int> nums = {2, 3, 2};
int bagWeight = 10;
for (int i = 0; i < nums.size(); i++) {
while (nums[i] > 1) { // nums[i]保留到1把其他物品都展开
// 超时了
#include<iostream>
#include<vector>
using namespace std;
int main() {
int bagWeight,n;
cin >> bagWeight >> n;
vector<int> weight(n, 0);
vector<int> value(n, 0);
vector<int> 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<int> 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();
}
```
* 时间复杂度O(m × n × k)m物品种类个数n背包容量k单类物品数量
大家去提交之后,发现这个解法超时了,为什么呢,哪里耗时呢?
也有另一种实现方式就是把每种商品遍历的个数放在01背包里面在遍历一遍。
耗时就在 这段代码:
```CPP
for (int i = 0; i < n; i++) {
while (nums[i] > 1) { // 物品数量不是一的,都展开
weight.push_back(weight[i]);
value.push_back(value[i]);
nums[i]--;
}
}
```
如果物品数量很多的话C++中这种操作十分费时主要消耗在vector的动态底层扩容上。其实这里也可以优化先把 所有物品数量都计算好一起申请vector的空间。
这里也有另一种实现方式就是把每种商品遍历的个数放在01背包里面在遍历一遍。
代码如下:(详看注释)
```CPP
#include<iostream>
#include<vector>
using namespace std;
int main() {
int bagWeight,n;
cin >> bagWeight >> n;
vector<int> weight(n, 0);
vector<int> value(n, 0);
vector<int> 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<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
vector<int> nums = {2, 3, 2};
int bagWeight = 10;
vector<int> 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<Integer> weight = new ArrayList<>(Arrays.asList(1, 3, 4));
List<Integer> value = new ArrayList<>(Arrays.asList(15, 20, 30));
List<Integer> 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();
```

View File

@ -7,7 +7,7 @@
# 动态规划:完全背包理论基础
本题力扣上没有原题,大家可以去[卡码网第52题](https://kamacoder.com/problem.php?id=1052)去练习,题意是一样的。
本题力扣上没有原题,大家可以去[卡码网第52题](https://kamacoder.com/problempage.php?pid=1052)去练习,题意是一样的。
## 算法公开课