Skip to content

链表

移除链表元素

我们要通过这道题好好理解虚拟头节点的作用,虚拟头节点可以避免对头节点的特殊处理,让我们可以把头节点看作和其他节点一样的节点,从而简化代码。

js
var removeElements = function (head, val) {
  let dummy = new ListNode();
  dummy.next = head;
  let cur = dummy;
  // 避免访问空指针
  while (cur !== null && cur.next !== null) {
    if (cur.next.val === val) {
      cur.next = cur.next.next;
      continue; // 这一步很关键,要及时截断,如果走到啦下一步,cur.next 就被忽略了(细品)
    }
    cur = cur.next;
  }
  return dummy.next;
};

设计链表

js
class ListNode {
  constructor(val = null, next = null) {
    this.val = val;
    this.next = next;
  }
}

var MyLinkedList = function () {
  this.head = null;
  this.size = 0;
};

/**
 * @param {number} index
 * @return {number}
 */
MyLinkedList.prototype.get = function (index) {
  if (index < 0 || index >= this.size) return -1;
  let cur = this.head;
  while (index) {
    cur = cur.next;
    index--;
  }
  return cur.val;
};

/**
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtHead = function (val) {
  const node = new ListNode(val);
  if (this.head === null) {
    this.head = node;
  } else {
    node.next = this.head;
    this.head = node;
  }
  this.size++;
};

/**
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtTail = function (val) {
  const node = new ListNode(val);
  let cur = this.head;
  if (this.head === null) {
    this.head = node;
    this.size++;
    return;
  }
  while (cur.next !== null) {
    cur = cur.next;
  }
  cur.next = node;
  this.size++;
};

/**
 * @param {number} index
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtIndex = function (index, val) {
  if (index === 0) {
    this.addAtHead(val);
    return;
  }
  if (index > this.size || index < 0) return;
  let dummy = new ListNode();
  let node = new ListNode(val);
  dummy.next = this.head;
  let cur = dummy;
  while (index) {
    index--;
    cur = cur.next;
  }
  node.next = cur.next;
  cur.next = node;
  this.size++;
};

/**
 * @param {number} index
 * @return {void}
 */
MyLinkedList.prototype.deleteAtIndex = function (index) {
  if (index < 0 || index >= this.size) return;
  let dummy = new ListNode();
  dummy.next = this.head;
  let cur = dummy;
  while (index) {
    cur = cur.next;
    index--;
  }
  cur.next = cur.next.next;
  if (cur === dummy) this.head = cur.next;
  this.size--;
};

反转链表

我们使用两个指针,一个指向前一个节点,一个指向当前节点,然后不断交换它们的指向,从而实现链表的反转。

js
var reverseList = function (head) {
  let prev = null;
  let cur = head;
  while (cur !== null) {
    let temp = cur.next;
    cur.next = prev;
    prev = cur;
    cur = temp;
  }
  return prev;
};

两两交换链表中的节点

js
var swapPairs = function (head) {
  let dummy = new ListNode();
  dummy.next = head;
  let cur = dummy;
  while (cur.next && cur.next.next) {
    let temp = cur.next;
    let temp2 = cur.next.next.next;
    cur.next = cur.next.next;
    cur.next.next = temp;
    cur.next.next.next = temp2; // 一开始忘了这一步,导致没有链接起来,可以思考一下递归和这个的区别
    cur = cur.next.next;
  }
  return dummy.next;
};

如果使用递归,这道题可以不用虚拟头节点,大家可以细品一下用不用虚拟节点的区别,还有一个需要注意的是 返回 temp2,此时 temp2 是新的头节点。

js
// 递归解法
var swapPairs = function (head) {
  if (!head || !head.next) return head;
  let temp1 = head;
  let temp2 = head.next;
  let temp3 = head.next.next;

  temp2.next = temp1;
  temp1.next = swapPairs(temp3);
  return temp2;
};

删除链表的倒数第 N 个节点

一开始写这道题的时候没有用虚拟节点,结果在删除头节点会出现空节点问题,所以这道题还是需要用虚拟节点,我觉得虚拟节点这个事情,当我们写的链表题多了,就可以比较灵活的运用了,我觉得要点就是看头节点是否需要特殊处理。

js
var removeNthFromEnd = function (head, n) {
  let dummy = new ListNode();
  dummy.next = head;
  let fast = dummy;
  let slow = dummy;
  while (n) {
    fast = fast.next;
    n--;
  }
  while (fast && fast.next !== null) {
    fast = fast.next;
    slow = slow.next;
  }
  slow.next = slow.next.next;
  return dummy.next;
};

链表相交

这道题的解法很巧妙,我们使用两个指针,一个指针指向 headA,一个指针指向 headB,然后不断移动指针,若有。

两个指针都走完了对方的路径,如果相交,那么两个指针一定会相遇,否则两个指针会同时走到 null

js
var getIntersectionNode = function (headA, headB) {
  let node1 = headA;
  let node2 = headB;
  while (node1 !== node2) {
    node1 = node1 ? node1.next : headB;
    node2 = node2 ? node2.next : headA;
  }
  return node1;
};

环形链表

我们使用两个指针,一个指针指向 head,一个指针指向 head.next,然后不断移动指针,如果两个指针相遇,那么就说明有环,否则没有环。

这道题是判断是否有环,为下一题做好了铺垫。

js
var hasCycle = function (head) {
  let slow = head;
  let fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) return true;
  }
  return false;
};

环形链表 II

这道理需要去推导数学证明,结论是当快慢指针相遇时,慢指针从 head 开始走,快指针从相遇点开始走,当它们再次相遇时,就是环的入口。

js
var detectCycle = function (head) {
  let slow = head;
  let fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (fast === slow) {
      slow = head;
      while (slow !== fast) {
        slow = slow.next;
        fast = fast.next;
      }
      return slow;
    }
  }
  return null;
};