提示:DDU,供自己复习使用。欢迎大家前来讨论~
字符串Part01
学习字符串的基础操作。
一、基础知识学习
时间复杂度的计算: 执行次数最多的代码。
下面是两个计算的例子:
-
一层循环
解题思路:
1.循环趟数t和每轮循环i的变化值
2.t与i的关系
3.循环停止条件
4.联立解方程
5.得到结果i = n*n while(i!=1){ i=i/2 }
t = log2 (n)
-
两层循环
1.写出外层循环中i的变化值
2.写出内层语句的执行次数(利用求和公式(2+4+6+…+2n))也就是O(n^2)
for(i=1;i<=n;i++){ for(j=1;j<=2*i;j++){ m++; } }
二、题目
题目一:344.反字符串
思路:
不给另外的数组分配额外的空间,要求原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
使用双指针法,依次交换就可以了。(不要使用库函数reverse)
细节:
如果库函数仅仅是 解题过程中的一小部分,并且已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。
字符串也是一种数组,所以元素在内存中是连续分布,这就决定了反转链表和反转字符串方式上还是有所差异的。字符串的反转,要比链表简单一些。
class Solution {
public:
void reverseString(vector<char>& s) {
for (int i = 0, j = s.size() - 1; i < s.size() / 2; i++, j--) {
swap(s[i], s[j]); //这里使用了函数,也可以使用中间变量进行两个数的交换
}
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(1)
题目二: 541.反转字符串II
解题思路:
1.不需要i++,而是使用i += 2k。剪枝操作。
2.剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符。
3.剩余字符少于 k 个,则将剩余字符全部反转。
class Solution {
public:
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i += (2 * k)) {
// 1. 每隔 2k 个字符的前 k 个字符进行反转
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
if (i + k <= s.size()) {
reverse(s.begin() + i, s.begin() + i + k);
} else {
// 3. 剩余字符少于 k 个,则将剩余字符全部反转。
// reverse接收的是一个迭代器
reverse(s.begin() + i, s.end());
}
}
return s;
}
};
题目三:替换数字
解题思路:
- 预处理与扩展: 首先,我们对原始数组进行预处理,计算出替换后所需的新数组大小。这是通过将每个数字字符转换为"number"这一特定字符串的长度来实现的。随后,我们扩充原始数组,确保它有足够的空间来容纳替换后的所有元素。
- 逆序填充: 接下来,我们采用逆序策略,从数组的末尾开始向前填充。这种方法避免了在填充过程中需要不断移动后续元素的低效操作。通过从后向前逐步替换,我们可以确保每个数字字符都被正确且高效地转换为"number"。
细节:
-
字符串和数组是一样的,在内存中是连续存在的。
-
在==数组填充==问题中,选择从后向前填充而非从前向后,是一种优化策略,它能够显著提高算法的效率。
从前向后填充就是O( n 2 n^2 n2)的算法了,因为每次添加元素都要将添加元素之后的所有元素整体向后移动。
其实很多数组填充类的问题,其做法都是先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这么做有两个好处:
- 不用申请新数组。
- 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
#include <iostream>
using namespace std;
int main() {
string s;
while (cin >> s) {
int sOldIndex = s.size() - 1;
int count = 0; // 统计数字的个数
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 sNewIndex = s.size() - 1;
// 从后往前将数字替换为"number"
while (sOldIndex >= 0) {
if (s[sOldIndex] >= '0' && s[sOldIndex] <= '9') {
s[sNewIndex--] = 'r';
s[sNewIndex--] = 'e';
s[sNewIndex--] = 'b';
s[sNewIndex--] = 'm';
s[sNewIndex--] = 'u';
s[sNewIndex--] = 'n';
} else {
s[sNewIndex--] = s[sOldIndex];
}
sOldIndex--;
}
cout << s << endl;
}
}
- 时间复杂度: O(n)
- 空间复杂度: O(1)
总结
- 字符串的基础题目,尽量不是关键的步骤下才可以使用库函数。
- 巩固双指针的操作。
- 逆序填充,避免增加时间复杂度。