前端面试高频算法题每日一练(Day1)

73 阅读2分钟

LeetCode 203. 移除链表元素

题目描述

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


示例

输入: head = [1,2,6,3,4,5,6], val = 6
输出: [1,2,3,4,5]

思路讲解

链表操作的常见套路是「虚拟头节点 dummy + 遍历指针」。

因为:

  • 删除节点时,如果被删的是第一个节点(头节点) ,处理会比较麻烦;

  • 所以我们可以在链表前加一个虚拟头节点 dummy,它的 next = head

  • 然后用一个指针 curdummy 开始遍历:

    • 每次检查 cur.next
    • 如果 cur.next.val === val,就删除它:cur.next = cur.next.next
    • 否则 cur = cur.next

最后返回 dummy.next(新的头结点)。


动态过程演示

例子:

head = 1263456
val = 6

过程(加虚拟头 dummy):

dummy → 1 → 2 → 6 → 3 → 4 → 5 → 6
↑
cur

cur.next.val = 16 → cur = cur.next
cur.next.val = 26 → cur = cur.next
cur.next.val = 6 = 6 → 删除该节点
链表变为:dummy → 1 → 2 → 3 → 4 → 5 → 6
继续往后直到结尾。

JavaScript 实现

function removeElements(head, val) {
  // 创建虚拟头节点
  let dummy = new ListNode(0);
  dummy.next = head;

  let cur = dummy;
  while (cur.next) {
    if (cur.next.val === val) {
      cur.next = cur.next.next; // 删除节点
    } else {
      cur = cur.next; // 移动指针
    }
  }

  return dummy.next;
}

时间与空间复杂度

项目复杂度
时间O(n)(遍历整个链表一次)
空间O(1)(只用了常数级额外空间)

LeetCode 206. 反转链表


思路(迭代法:三个指针)

反转链表其实就是让所有指针“掉头”:

例如原链表:

1234null

我们要得到:

null1234

核心想法

我们用三个指针:

  • prev:当前节点的前一个节点(初始为 null
  • curr:当前正在处理的节点(从 head 开始)
  • next:暂存 curr.next(因为指针反转后会丢失)

每次循环:

  1. 暂存下一个节点 next = curr.next
  2. 反转指针方向:curr.next = prev
  3. 前进一步:prev = curr
  4. 继续下一个:curr = next

直到 curr == nullprev 就是新链表头。


图解过程

初始状态:
null ← prev    curr → 1 → 2 → 3 → 4 → null

第一步:
next = 2
curr.next = prev   => 1 → null
prev = 1
curr = 2

第二步:
next = 3
curr.next = prev   => 21 → null
prev = 2
curr = 3

...

最终:
4 → 3 → 2 → 1 → null
返回 prev(即 4)

代码(JavaScript)

var reverseList = function(head) {
  let prev = null;
  let curr = head;

  while (curr) {
    let next = curr.next; // 暂存下一个节点
    curr.next = prev;     // 反转指针方向
    prev = curr;          // 前进
    curr = next;          // 继续
  }

  return prev; // 新头节点
};

示例

输入:

12345null

输出:

54321null

拓展:递归写法(更优雅)

递归思路:反转后半部分,让 head.next 指向自己。

var reverseList = function(head) {
  if (!head || !head.next) return head;
  let newHead = reverseList(head.next);
  head.next.next = head;
  head.next = null;
  return newHead;
};

思维图:

反转(123)
=>
反转(23)
=>
反转(3) → 返回 3

然后在回溯时:

2.next.next = 2
2.next = null

总结对比

方法思路空间优点
迭代法三指针依次翻转O(1)简洁高效
递归法从尾到头回溯反转O(n)更优雅但栈消耗大