从被追问到被点赞:我靠“哨兵+快慢指针”展示了面试官真正想看的代码思维

面试高频链表题精讲:从哨兵节点到快慢指针

在算法面试中,链表 是一类非常经典的数据结构题型。它不像数组那样支持随机访问,但正因如此,很多操作都需要我们对指针(引用)有精准的控制。本文将通过四道典型题目,带你系统掌握链表处理中的两大核心技巧:哨兵节点(Dummy Node)快慢指针(Fast & Slow Pointers)


一、删除链表中的指定节点:为什么需要哨兵节点?

题目描述

给定一个单链表的头节点 head 和一个值 val,删除链表中所有值等于 val 的节点,并返回新的头节点。

力扣:LCR 136. 删除链表的节点 - 力扣(LeetCode)

初步解法(不使用哨兵)

js 复制代码
function remove(head, val) {
  // 特殊处理头节点
  if (head && head.val === val) {
    return head.next;
  }
  let cur = head;
  while (cur.next) {
    if (cur.next.val === val) {
      cur.next = cur.next.next;
      break; // 假设只删第一个匹配项
    }
    cur = cur.next;
  }
  return head;
}

面试官反问:

"你为什么要单独判断头节点?能不能统一处理?"

这是一个非常关键的问题!因为头节点没有前驱节点,所以当我们想"让前一个节点跳过当前节点"时,头节点就成了特例。

引入哨兵节点(Dummy Node)

哨兵节点是一个人为添加的假节点 ,通常放在链表头部,其作用是消除边界条件的特殊处理

js 复制代码
function remove(head, val) {
  const dummy = new ListNode(0);
  dummy.next = head;
  let cur = dummy;
  while (cur.next) {
    if (cur.next.val === val) {
      cur.next = cur.next.next;
      break;
    }
    cur = cur.next;
  }
  return dummy.next; // 返回真正的头节点
}

优势

  • 不再需要单独判断头节点是否为待删节点;
  • 所有节点都变成了"中间节点",逻辑统一;
  • 即使链表为空或全被删完,也能安全返回。

二、反转链表:哨兵 + 头插法

题目描述

反转一个单链表。

力扣:206. 反转链表 - 力扣(LeetCode)

解法思路

我们可以利用哨兵节点作为新链表的头 ,然后遍历原链表,每次把当前节点插入到哨兵之后 ------这就是头插法

js 复制代码
function reverseList(head) {
  const dummy = new ListNode(0); // 哨兵,dummy.next 指向已反转部分的头
  let cur = head;
  while (cur) {
    const next = cur.next;       // 1. 保存下一个节点
    cur.next = dummy.next;       // 2. 当前节点指向已反转部分的头
    dummy.next = cur;            // 3. 更新反转部分的新头
    cur = next;                  // 移动到原链表下一节点
  }
  return dummy.next;
}

🔍 关键理解

  • dummy 始终不动,它的 next 指向当前已反转链表的头
  • 每轮操作把 cur 插到最前面,实现"头插";
  • 三步操作顺序不能乱,否则会断链。

💡 虽然这道题也可以用递归或双指针(prev/cur)实现,但哨兵+头插法提供了一种清晰、不易出错的迭代思路。


三、判断链表是否有环:从哈希表到快慢指针

题目描述

给定一个链表,判断其中是否存在环。

力扣:141. 环形链表 - 力扣(LeetCode)

解法一:哈希表(空间换时间)

js 复制代码
let hash = new Map();
let temp =head;
while(temp)
 {
   if(hash.has(temp))
    return true;
   else
    hash.set(temp,1);
  temp=temp.next;
 }
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

面试官追问:

"能不能不用额外空间?"

解法二:快慢指针(Floyd 判圈算法)

核心思想:如果链表有环,快指针(每次走两步)一定会在环内追上慢指针(每次走一步)。

js 复制代码
function hasCycle(head) {
  let slow = head;
  let fast = head;
  while (fast && fast.next) {
    slow = slow.next;         // 慢指针走1步
    fast = fast.next.next;    // 快指针走2步
    if (slow === fast) {      // 比较引用地址(同一内存)
      return true;
    }
  }
  return false;
}

优势

  • 空间复杂度 O(1);
  • 时间复杂度 O(n);
  • 是链表环检测的标准解法。

🌟 快慢指针不仅用于判环,还可用于找环入口、求中点、删除倒数第 N 个节点等。


四、删除链表的倒数第 N 个节点:哨兵 + 快慢指针的完美结合

题目描述

给你一个链表,删除倒数第 n 个节点,并返回链表头。

力扣:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

解法思路

  1. 使用哨兵节点避免头节点被删的边界问题;
  2. 快指针先走 n 步
  3. 然后快慢指针同步移动 ,当快指针到达末尾时,慢指针正好在倒数第 n 个节点的前一个
  4. 执行删除操作。
js 复制代码
const removeNthFromEnd = function(head, n) {
  const dummy = new ListNode(0);
  dummy.next = head;
  let fast = dummy;
  let slow = dummy;

  // 快指针先走 n 步
  for (let i = 0; i < n; i++) {
    fast = fast.next;
  }

  // 快慢指针一起走,直到 fast 到达最后一个节点
  while (fast.next) {
    fast = fast.next;
    slow = slow.next;
  }

  // 此时 slow 指向倒数第 n 个节点的前一个
  slow.next = slow.next.next;

  return dummy.next;
};

🎯 为什么需要哨兵?

  • 如果要删除的是头节点 (比如链表长度为 5,n=5),那么 slow 需要停在"头节点之前";
  • 没有哨兵的话,slow 无法指向 null 的前驱;
  • 哨兵确保 slow 始终有效,且 slow.next 可安全删除。

总结:链表面试两大法宝

技巧 适用场景 核心价值
哨兵节点(Dummy) 删除、插入、反转等涉及头节点的操作 消除边界条件,统一逻辑
快慢指针 判环、找中点、倒数第 N 个节点 O(1) 空间解决距离/位置问题

在实际面试中,先写出朴素解法(如哈希表) ,再根据面试官提示优化到快慢指针 ,能体现你的思维层次;而哨兵节点则是写出健壮、简洁代码的关键技巧。


希望这篇文章能帮你打通链表题的任督二脉!下次遇到链表题,先问自己两个问题:

  1. 要不要加哨兵? → 避免头节点特殊处理
  2. 能不能用快慢指针? → 解决"倒数"、"中点"、"环"等问题

祝你面试顺利,Offer 收割!🎉

相关推荐
千金裘换酒8 小时前
LeetCode 移动零元素 快慢指针
算法·leetcode·职场和发展
wm10439 小时前
机器学习第二讲 KNN算法
人工智能·算法·机器学习
NAGNIP9 小时前
一文搞懂机器学习线性代数基础知识!
算法
NAGNIP9 小时前
机器学习入门概述一览
算法
Hi_kenyon9 小时前
VUE3套用组件库快速开发(以Element Plus为例)二
开发语言·前端·javascript·vue.js
独自归家的兔9 小时前
Spring Cloud核心架构组件深度解析(原理+实战+面试高频)
spring cloud·面试·架构
iuu_star9 小时前
C语言数据结构-顺序查找、折半查找
c语言·数据结构·算法
Yzzz-F9 小时前
P1558 色板游戏 [线段树 + 二进制状态压缩 + 懒标记区间重置]
算法
漫随流水10 小时前
leetcode算法(515.在每个树行中找最大值)
数据结构·算法·leetcode·二叉树
EndingCoder10 小时前
Any、Unknown 和 Void:特殊类型的用法
前端·javascript·typescript