这5道408真题套路一模一样,搞懂双指针白送20分

四季读书网 2 0
这5道408真题套路一模一样,搞懂双指针白送20分

408 数据结构算法题,双指针法是当之无愧的「出题亲儿子」。

翻遍 2009 到 2024 的真题,每两三年就有一道双指针的大题。快慢指针、前后指针、三指针…… 换着花样考,但万变不离其宗。

今天这篇文章,我把历年真题中所有涉及双指针的大题全部拆一遍,从 2009 年的「链表倒数第 k 个结点」到 2020 年的「三元组最小距离」,五道经典真题,一题一题掰开揉碎了讲。

每道题都包含:题目还原 → 设计思想 → 完整代码 → 复杂度分析。建议收藏,考前反复看。


先搞清楚:什么是双指针法?

双指针法不是某个具体的算法,而是一类解题策略的总称。

核心思想很简单:用两个(或多个)指针在数据结构上按一定规则移动,在一次遍历内完成查找、重排、比对等操作。

在 408 真题中,双指针法主要有四种形态:

  • 同向快慢指针
    :两个指针同方向移动,一个快一个慢,保持固定间距
  • 长度差 + 同步指针
    :先让一个指针领先几步,然后同步移动
  • 前后夹逼双指针
    :一前一后向中间靠拢
  • 多指针扩展
    :三指针甚至更多,思想一脉相承

下面,按年份顺序逐一拆解。


2009 年第 42 题:单链表倒数第 k 个结点

题目还原

已知一个带有头结点的单链表,只给出了头指针 list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第 k 个位置上的结点。若查找成功,输出该结点的 data 值并返回 1;否则返回 0。

破题思路

这道题是同向快慢指针最经典的入门题。

想象两个人在同一条跑道上跑步:一个人先出发跑了 k 米,然后第二个人再出发。等第一个人跑到终点时,第二个人离终点刚好就是 k 米 —— 也就是倒数第 k 个位置。

核心步骤:

  1. 快指针 fast 和慢指针 slow 同时指向第一个数据结点
  2. 快指针先走 k 步。走不到 k 步就空了?说明链表长度不够,直接返回失败
  3. 快慢指针同步向后移动,直到快指针到末尾(NULL)
  4. 此时慢指针所在位置,就是倒数第 k 个结点

代码实现

typedef struct Node {     int data;     struct Node *link; } LinkNode, *LinkList;  int find_k(LinkList list, int k) {     if (list == NULL || k <= 0) {         return 0;  // 参数非法     }      LinkNode *fast = list->link;  // 快指针     LinkNode *slow = list->link;  // 慢指针      // 快指针先走 k 步     for (int i = 0; i < k; i++) {         if (fast == NULL) {             return 0;  // 链表长度不足 k         }         fast = fast->link;     }      // 快慢指针同步移动     while (fast != NULL) {         fast = fast->link;         slow = slow->link;     }      printf(「%d」, slow->data);     return 1; }

时间复杂度:O(n),仅遍历链表一次。空间复杂度:O(1),只用了两个指针。


2012 年第 42 题:两个链表的公共后缀

题目还原

两个单词的字母用单链表存储,若它们有相同的后缀(如 「loading」 和 「being」 共享 「ing」),则后缀部分的结点是共享的。给定两个链表的头指针 str1 和 str2,设计一个时间上尽可能高效的算法,找出共同后缀的起始位置。

破题思路

这题的 trick 在于:两个链表长度可能不同,但公共后缀部分的长度一定相同。

所以思路分两步:

  1. 对齐起点
    :先算两个链表的长度差 d,让长链表的指针先走 d 步
  2. 同步查找
    :两个指针同步向后移动,当它们指向同一个结点时,就是公共后缀的起点

这本质上是「长度差 + 同步指针」的变体,核心还是双指针思想。

代码实现

typedef struct Node {     char data;     struct Node *next; } LinkNode, *LinkList;  LinkNode* findCommon(LinkList str1, LinkList str2) {     int len1 = 0, len2 = 0;     LinkNode *p = str1->next;     LinkNode *q = str2->next;      // 计算两个链表的长度     while (p != NULL) { len1++; p = p->next; }     while (q != NULL) { len2++; q = q->next; }      // 长链表指针先走差值步     LinkNode *longp, *shortp;     int d;     if (len1 >= len2) {         longp = str1->next; shortp = str2->next;         d = len1 - len2;     } else {         longp = str2->next; shortp = str1->next;         d = len2 - len1;     }     while (d--) longp = longp->next;      // 同步遍历,寻找公共结点     while (longp != NULL && shortp != NULL) {         if (longp == shortp) return longp;         longp = longp->next;         shortp = shortp->next;     }     return NULL; }

时间复杂度:O(m + n)。空间复杂度:O(1)。


2019 年第 41 题:重排单链表

题目还原

线性表 L = (a₁, a₂, a₃, ..., aₙ₋₁, aₙ) 用带头结点的单链表存储。请设计一个空间复杂度 O(1) 的算法,将 L 重排为 L' = (a₁, aₙ, a₂, aₙ₋₁, a₃, aₙ₋₂, ...)。

简单说:第一个不动,最后一个插到第二个,倒数第二个插到第四个…… 首尾交替排列。

破题思路

这道题是双指针法的高阶综合应用,拆成三步:

  1. 快慢指针找中点
    :快指针走两步,慢指针走一步。快指针到末尾时,慢指针刚好在中点
  2. 逆置后半段
    :把中点之后的链表原地反转
  3. 交错合并
    :前半段和逆置后的后半段交替拼接

每一步都是经典算法,组合在一起就能解决复杂问题。

代码实现

typedef struct node {     int data;     struct node *next; } NODE;  void reorderList(NODE *head) {     if (head == NULL || head->next == NULL) return;      // 第一步:快慢指针找中点     NODE *slow = head, *fast = head;     while (fast->next != NULL && fast->next->next != NULL) {         slow = slow->next;       // 慢指针走一步         fast = fast->next->next; // 快指针走两步     }     NODE *mid = slow;  // mid 为前半段尾结点      // 第二步:逆置后半段链表(头插法)     NODE *p = mid->next;     mid->next = NULL;  // 断开前后两段     NODE *pre = NULL, *next;     while (p != NULL) {         next = p->next;         p->next = pre;         pre = p;         p = next;     }      // 第三步:交错合并两个子链表     NODE *p1 = head->next;  // 前半段头     NODE *p2 = pre;         // 逆置后后半段头     NODE *temp1, *temp2;     while (p1 != NULL && p2 != NULL) {         temp1 = p1->next; temp2 = p2->next;         p1->next = p2;         p2->next = temp1;         p1 = temp1; p2 = temp2;     } }

时间复杂度:O(n),找中点、逆置、合并各遍历一次。空间复杂度:O(1)。


2020 年第 41 题:三元组最小距离

题目还原

定义三元组 (a, b, c) 的距离 D = |a-b| + |b-c| + |c-a|。给定三个非空升序数组 S₁、S₂、S₃,求所有可能三元组中的最小距离。

例如 S₁ = {-1, 0, 9},S₂ = {-25, -10, 10, 11},S₃ = {2, 9, 17, 30, 41},最小距离为 2,对应三元组 (9, 10, 9)。

破题思路

这是双指针法的三指针扩展,思想一脉相承。

首先,距离公式可以化简:

D = |a-b| + |b-c| + |c-a| = 2 × (max(a,b,c) - min(a,b,c))

所以我们要做的,就是让最大值和最小值的差距尽可能小

策略:三个指针分别指向三个数组的当前位置。每次计算距离后,把最小值对应的指针往后移 —— 因为最小值拖后腿,得把它变大,才能缩小差距。

代码实现

#include <limits.h> #include <math.h>  int minDistance(int A[], int n1, int B[], int n2, int C[], int n3) {     int i = 0, j = 0, k = 0;     int minDist = INT_MAX;      while (i < n1 && j < n2 && k < n3) {         int a = A[i], b = B[j], c = C[k];          // 计算当前距离         int d = abs(a - b) + abs(b - c) + abs(c - a);         if (d < minDist) minDist = d;          // 移动最小值对应的指针         int minVal = a;         if (b < minVal) minVal = b;         if (c < minVal) minVal = c;          if (minVal == a) i++;         else if (minVal == b) j++;         else k++;     }     return minDist; }

时间复杂度:O(n₁ + n₂ + n₃)。空间复杂度:O(1)。


补充:2011 年第 42 题——两个有序数组的中位数

题目还原

两个等长的升序序列 A 和 B(各 n 个元素),求它们的所有元素合并后的中位数。

注意:本题的最优解是二分法(O(log n)),408 标准答案也这么给。但双指针法(O(n))是更直观的解法,在考场上可以作为保底方案。

双指针解法思路

两个指针 ij 分别指向 A、B 的起始位置。每次比较 A[i] 和 B[j],较小值对应的指针后移,同时计数。当数到第 n 个元素时(中位数位置),即为所求。

int findMedian(int A[], int B[], int n) {     int i = 0, j = 0, count = 0, result;     while (count < n) {         if (A[i] < B[j]) { result = A[i]; i++; }         else             { result = B[j]; j++; }         count++;     }     return result; }

五道真题一张表总结

年份
题目
双指针类型
核心思想
2009
链表倒数第 k 个结点
同向快慢指针
保持固定间距同步移动
2011
有序数组中位数
归并式双指针
类似归并排序的二路遍历
2012
链表公共后缀
长度差 + 同步指针
对齐起点后同步遍历
2019
重排单链表
快慢指针找中点
快 2 倍速找中点 + 逆置 + 合并
2020
三元组最小距离
三指针扩展
移动最小值指针缩小差距

三个备考建议

第一,套路比灵感重要。 双指针法考来考去就那几种套路,与其考场上灵光一现,不如提前把每种套路都练熟。

第二,背代码不如理解指针移动规则。 每道题问自己:为什么这个指针要这么移动?移动到什么条件停下?理解比记忆更可靠。

第三,408 算法题的评分是按步骤给分的。 即使写不出完整的代码,把设计思想写清楚、把关键步骤列出来,也能拿到大部分分数。千万不能空着。


双指针法是 408 数据结构性价比最高的考点之一。考频高、难度适中、套路固定。把这五道真题吃透,考场上一旦出现双指针题,就是你的送分题。

觉得有用的话,点个「在看」,转发给一起备考的研友。祝大家都能上岸理想的学校。

{embed:static}{/embed}

抱歉,评论功能暂时关闭!