一、单序列双指针
1.1 反转字符串
本质是相向双指针。
344. 反转字符串

c
void reverseString(char *s, int sSize)
{
int right = sSize - 1;
for (int left = 0; left < right; left++) {
char tmp = s[left];
s[left] = s[right];
s[right] = tmp;
right--;
}
}
1.2 相向双指针
两个指针 left=0, right=n−1,从数组的两端开始,向中间移动,这叫相向双指针。上面的滑动窗口相当于同向双指针。
2697. 字典序最小回文串

c
char *makeSmallestPalindrome(char *s)
{
int n = strlen(s);
int l = 0, r = n - 1;
while (l < r) {
if (s[l] != s[r]) {
// 统一改成更小的字符,保证字典序最小
char minCh = (s[l] < s[r]) ? s[l] : s[r];
s[l] = minCh;
s[r] = minCh;
}
l++;
r--;
}
return s;
}
1.3 同向双指针
两个指针的移动方向相同(都向右,或者都向左)。
611. 有效三角形的个数


c
// 升序排序
int cmp(const void* a, const void* b)
{
return *(int*)a - *(int*)b;
}
int triangleNumber(int* nums, int numsSize)
{
if (numsSize < 3) {
return 0;
}
qsort(nums, numsSize, sizeof(int), cmp);
int ans = 0;
for (int k = 2; k < numsSize; k++) {
int left = 0;
int right = k - 1;
while (left < right) {
if (nums[left] + nums[right] > nums[k]) {
// left 到 right-1 全部合法, 因为 nums[left] 本身是最小的
ans += right - left;
right--;
} else {
// 当前最小的左 + 当前最大的右都不够,说明这个 left 搭配任何小于 right 的数都不可能满足条件
// 只能 left++,换更大的左数试试。
left++;
}
}
}
return ans;
}
二、双序列双指针
什么是「双序列双指针」
两个独立有序序列,各用一个指针遍历,互相配合完成合并、筛选、插入、匹配操作,就是双序列双指针。
2.1 双指针
2109. 向字符串添加空格

本题两个序列:
序列 1:原字符串 s,指针 i(遍历原字符串每个字符下标)
序列 2:空格位置数组 spaces,指针 p(遍历要插入空格的原下标)
两个指针各自只往前走,不回头、不回退,属于典型双序列同向双指针
c
char *addSpaces(char *s, int *spaces, int spacesSize)
{
int len = strlen(s);
// 新字符串长度 = 原长度 + 空格个数 + 结束符
char *res = (char*)malloc(len + spacesSize + 1);
int idx = 0;
int p = 0; // spaces指针
for (int i = 0; i < len; i++) {
// 当前位置需要插入空格
if (p < spacesSize && i == spaces[p]) {
res[idx++] = ' ';
p++;
}
res[idx++] = s[i];
}
res[idx] = '\0';
return res;
}
int main() {
char s[] = "LeetcodeHelpsMeLearn";
int spaces[] = {8,13,15};
char* ans = addSpaces(s, spaces, 3);
printf("%s\n", ans);
free(ans);
return 0;
}
2.2 子序列
思路
两个指针 i(遍历子串s)、j(遍历母串t):
如果 si == tj:匹配成功,两个指针同时右移;
如果不相等:只移动母串指针 j;
最终如果 i 走完了s全部字符,说明是子序列,返回true,否则false。
392. 判断子序列

c
bool isSubsequence(char *s, char *t)
{
int i = 0, j = 0;
int lenS = strlen(s);
int lenT = strlen(t);
while (i < lenS && j < lenT) {
if (s[i] == t[j]) {
i++;
}
j++;
}
return i == lenS;
}
三、三指针
2367. 等差三元组的数目

核心观察(单调性)
因为数组递增:
如果差值偏小,只能把靠右的指针右移,放大差值;
如果差值偏大,只能把靠左的指针右移,缩小差值;
三个指针永远只往右走,没有回头
c
int arithmeticTriplets(int* nums, int numsSize, int diff)
{
if (numsSize < 3) return 0;
int ans = 0;
int i = 0, j = 1, k = 2;
while (k < numsSize) {
int d1 = nums[j] - nums[i];
int d2 = nums[k] - nums[j];
if (d1 == diff && d2 == diff) {
ans++;
i++;
j++;
k++;
} else if (d1 < diff) {
// 保证 j 不能追上 k
if (j + 1 < k) {
j++;
} else {
k++;
}
} else if (d2 < diff) {
k++;
} else {
i++;
}
}
return ans;
}
四、分组循环
适用场景:按照题目要求,数组会被分割成若干组,每一组的判断/处理逻辑是相同的。
核心思想:
外层循环负责遍历组之前的准备工作(记录开始位置),和遍历组之后的统计工作(更新答案最大值)。
内层循环负责遍历组,找出这一组最远在哪结束。
这个写法的好处是,各个逻辑块分工明确,也不需要特判最后一组(易错点)。以我的经验,这个写法是所有写法中最不容易出 bug 的,推荐大家记住。
485. 最大连续 1 的个数

c
int findMaxConsecutiveOnes(int *nums, int numsSize)
{
int maxLen = 0;
int i = 0;
// 外层循环:遍历所有分组
while (i < numsSize) {
// 内循环1:跳过所有0,找到连续1的起始位置
while (i < numsSize && nums[i] == 0) {
i++;
}
int start = i;
// 内循环2:遍历当前这一段连续的1
while (i < numsSize && nums[i] == 1) {
i++;
}
// 计算当前分组长度,更新最大值
int curLen = i - start;
if (curLen > maxLen) {
maxLen = curLen;
}
}
return maxLen;
}