在阅读本篇文章之前,希望你已经完成了代码随想录中的:
数组部分、字符串部分、双指针法非链表的部分。
1.双指针法总结
在出现两个场景的时候,我们可以考虑使用双指针法。
1.1 需要原地对数组或者字符串进行修改的时候
54. 替换数字(第八期模拟笔试) (kamacoder.com)
26. 删除有序数组中的重复项 - 力扣(LeetCode)
像这种题目都是要求你原地修改数组的(所以说机考几乎不可能出现,因为谁知道你机考输出的是原地修改的还是另起一个数组;但需要担心面试问到)
对于双指针需要从前面开始还是从后面开始,取决于:你更新完数组之后,数组的长度是变长了还是变短了。如果更新完数据之后数组长度变短,就从0开始;如果更新完数据之后数组长度变长,那就从nums.size() - 1(要先resize出新的长度)开始。
1.2 减少for循环的次数
当你在考虑使用多层for循环暴力求解的时候,就可以考虑使用双指针来减少一个for了。
当你想使用左右双指针来解决问题的时候,需要满足:
1. 需要在一串数据之中需要找到两个值,目标是它们相加等于某个值。
2. 这串数据是有序的。
1.3 双指针法在链表中使用
等到链表篇再讲
当然双指针的用法还有很多很杂的,这边就不多讲了,全靠做题时能否自己悟出来了
2. 去重逻辑
顺带一提,对于上面什么什么几数之和这种要求去重的题目,有一个公式去重法。
对于for循环的话,可以这样去重:
for (int i = 0; i < nums.size(); i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for (int j = i + 1; j < nums.size(); j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
for (int k = j + 1; k < nums.size(); k++) {
if (k > j + 1 && nums[k] == nums[k - 1]) {
continue;
}
// 以此类推
}
}
}
对于双指针的话,可以这样去重:
int left = 0;
int right = nums.size() - 1;
while (left < right) {
if (left > 0 && nums[left] == nums[left - 1]) {
left++;
continue;
}
if (right < nums.size() - 1 && nums[right] == nums[right + 1]) {
right++;
continue;
}
// 你想写的主体内容
}
对于回溯算法的话,可以这样去重(看不明白也没关系,以后到回溯的时候还会再说一遍)
void function(vector<int> &nums, int start)
{
for (int i = start; i < nums.size(); i++) {
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
// 你想写的函数体
function(nums, i + 1);
}
}
总而言之,去重的核心逻辑就是:
当指针>指针的起始位置,且nums[指针] = nums[指针-1](如果是从右往左移的指针则反过来)那就跳过当前循环。
3. 前缀和
58. 区间和 | 代码随想录 (programmercarl.com)
44. 开发商购买土地 | 代码随想录 (programmercarl.com)
本身前缀和的题目并不难以理解,本质上就是用一个新数组把知道当前遍历的值的和都记下来,然后需要取某一段之和的时候就将新数组相减。
前缀和后面还可能会在动态规划、哈希表等情况遇见,到时候再说。
4. KMP算法
这位更是重量级,机考不会考的,散了吧(面试也应该不会问吧)
严格来说这玩意也能算双指针,也能叫前缀,但要理解起来非常抽象
vector<int> prefix(needle.size(), 0);
int j = 0;
for (int i = 1; i < needle.size(); i++) {
while (j > 0 && needle[i] != needle[j]) {
j = prefix[j - 1];
}
if (needle[i] == needle[j]) {
j++;
}
prefix[i] = j;
}
上面这一部是先找到prefix(或者叫next)数组,这个数组记录的是:到当前下标,多少个后缀与前缀是重复的。
比如aba的结果就是001,abab的结果就是0012,abcab的结果就是00012。
j = 0;
for (int i = 0; i < haystack.size(); i++) {
while (j > 0 && haystack[i] != needle[j]) {
j = prefix[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
if (j == needle.size()) {
return i - j + 1;
}
}
}
return -1;
然后在我们逐个比对的时候,当我们发现不匹配的时候,我们再去看看当前不匹配的位置的prefix有没有值,如果有值的话就说明,直到这里的后缀和某一段前缀是重复的;因为有一段后缀和haystack已经匹配了,那么一定又会有那么一段前缀和haystack匹配,这一段前缀的匹配就可以被省略掉。
我知道上面这段文字很抽象,要是你看不懂的话要不你来找我,我给你打语音()