链表

概述

用一组任意存储的单位来存储线性表的数据元素。一个对象存储着本身的值和下一个元素的地址。

链表结果

链表与JS

上面的图用js表示

const list = {
  value: 5,
  next: {
    value: 1,
    next: {
      value: 9,
      next: null
    }
  }
}

练习1: 链表中环的入口节点

给一个链表,若其中包含环,请找出该链表的环的入口节点,否则,输出null。

function entryNodeOfLoop (list) {
  if (!list || !list.next) return null;

  // 判断链表中是否有环
  let p1 = list.next, 
      p2 = list.next.next;
  while (p1 !== p2) {
    if (p2 === null || p2.next === null) return null;
    p1 = p1.next;
    p2 = p2.next.next;
  }

  // 获取环的长度
  let length = 1, temp = p1;
  p1 = p1.next;
  while (temp !== p1) {
    p1 = p1.next;
    length++;
  }

  // 查询环的入口
  p1 = p2 = list;
  while (length-- > 0) {
    p1 = p1.next; 
  }

  while (p1 !== p2) {
    p1 = p1.next;
    p2 = p2.next;
  }
  return p1;
}

// 测试一下
var node1 = {
  value: 1,
  next: null
}
var node2 = {
  value: 2,
  next: null
}
var node3 = {
  value: 3,
  next: null
}
var node4 = {
  value: 4,
  next: null
}
var node5 = {
  value: 5,
  next: null
}
var node6 = {
  value: 6,
  next: null
}
var node7 = {
  value: 7,
  next: null
}
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node6;
node6.next = node7;
node7.next = node3;

entryNodeOfLoop(node1);

练习2: 链表转换为数组

输入一个链表,按链表值从尾到头的顺序返回一个ArrayList

输入 2 -> 3 -> 4

输出 [4, 3, 2]
function transformListToArray(node) {
  if (!node || !node.next) return [];
  let array = [];
  while (node) {
    array.unshift(node.val);
    node = node.next;
  }
  return array;
}


// 测试一下
const list = {
  val: 2,
  next: {
    val: 3,
    next: {
      val: 4,
      next: null
    }
  }
}
transformListToArray(list)
// 输出 [4, 3, 2]

练习3:反转链表

输入一个链表,返回其反转后的链表。

输入 2 -> 3 -> 4

输出 4 -> 3 -> 2
function reverse(list) {
  let currentNode = null,
  head = list;
  while (list && list.next) {
    currentNode = list.next;
    list.next = currentNode.next;
    currentNode.next = head;
    head = currentNode;
  }
  return head;
}

// 测试一下
var list = {
  value: 2,
  next: {
    value: 3,
    next: {
      value: 4,
      next: null
    }
  }
}
reverse(list)

练习4: 合并链表

输入数组,转成两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调递增规则。

输入:  1 -> 2 -> 43 -> 5 -> 6
输出:  1 -> 2 -> 3 -> 4 -> 5 -> 6
function ListNode(value) {
  this.value = value;
  this.next = null;
}

// 将数组转换成链表,已把数组进行排序
function transformArrayToList(array) {
  if (!Array.isArray(array)) return false;

  // 先把数组进行排序
  array = array.sort();
  let list = new ListNode(null),
      node = list;
  
  while (array.length > 0) {
    let value = array.shift();
    node.next = new ListNode(value);
    node = node.next;
  }
  return list.next;
}

// 比较两个节点
function mergeList(l1, l2) {
  if (!l1) return l2;
  if (!l2) return l1;

  let node = null;
  if (l1.value < l2.value) {
    node = l1;
    node.next = mergeList(l1.next, l2);
  } else if (l1.value > l2.value) {
    node = l2;
    node.next = mergeList(l1, l2.next);
  } else if (l1.value === l2.value) {
    node = l1;
    node.next = mergeList(l1.next, l2.next);
  }
  return node;
}

const l1 = transformArrayToList([1, 2, 4, 3]);
const l2 = transformArrayToList([3, 5, 6]);
const linkedList = mergeList(l1, l2) 

练习5: 链表倒数第k个节点

输入一个链表,输出该链表中倒数第k个结点。

解题思路:可以用快慢指针

输入 5 -> 4 -> 3 -> 2 , 2

输出 3
/*
* 获取链表中的倒数第k个节点
* @params {ListNode} list
* @params {Number} k
* @returns {Node}
*/
function findKthToTail(list, k) {
  if (!list)  return null;

  let fast = list,
      slow = list,
      i = 1;

  while (fast.next) {
    i++;
    fast = fast.next;
    if (i > k) {
      slow = slow.next;
    }
  }
  return (k <= i) && slow;
}

function ListNode(value) {
  this.value = value;
  this.next = null;
}

function transformArrayToList(array) {
  if (!Array.isArray(array)) return false;

  let list = new ListNode(null),
      node = list;
  while(array.length > 0) {
    let value = array.shift();
    node.next = new ListNode(value);
    node = node.next
  }
  return list.next;
}


let list = transformArrayToList([5, 4, 3, 2])
findKthToTail(list, 2); // { val: 3, next: {...} }
findKthToTail(list, 5); // false

练习6: 两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共结点。

// 获取链表的长度
function getLinkedListLength(list) {
  if (!list || !list.next) return false;
  let length = 1, current = list.next;
  while(current) {
    length++;
    current = current.next
  }
  return length;
}

function findFirstCommonNode(l1, l2) {
  if (!l1 || !l2) return null;
  let len1 = getLinkedListLength(l1),
     len2 = getLinkedListLength(l2);
  let diff, long, short;

  if (len1 > len2) {
    long = l1,
    short = l2,
    diff = len1 - len2;
  } else {
    long = l2,
    short = l1,
    diff = len2 - len1;
  }

  while(diff-- >0) {
    long = long.next;
  }

  while (long) {
    if (long === short) {
      return long;
    }
    long = long.next
    short = short.next
  }
  return null;
}


// 测试一下

let common = {
  val: 100,
  next: {
    val: 101, 
    next: {
      val: 102,
      next: null
    }
  }
}

let l1 = {
  val: 1,
  next: {
    val: 2,
    next: {
      val: 3,
      next: common
    }
  }
};

let l2 = {
  val: 5, 
  next: {
    val: 6, 
    next: common
  }
}

findFirstCommonNode(l1, l2); // { val: 100, next: { val: 101, next: { val: 102, next: null } } }

练习7: 删除链表的倒数第k个节点

给定一个链表,删除链表的倒数第 k 个节点,并且返回链表的头结点。

给定一个链表: 1->2->3->4->5, 和 k = 2.

当删除了倒数第二个节点后,链表变为 1-> 2 -> 3 ->5.
function removeKthFromTail(list, k) {
  if (!list) return false;
  let length = getLinkedListLength(list);

  if (length < k) return false;
  let idx = length - k;
  let result = new ListNode(null);
      result.next = list;
  let  pre = result,
      cur = pre.next;
  
  for (let i = 0; i < idx; i++) {
    pre = cur;
    cur = pre.next;
  }
  pre.next = cur.next;
  return result.next;
}

练习8: 回文链表

请判断一个链表是否为回文链表。

示例:

输入 1 -> 2 -> 1

输出 false


输入 1 -> 2 -> 2 -> 1

输出 true

function isPalindrome(head) {
  if (head === null) return true;

  let fast = head, slow = head;
  let stack = [];

  while(fast.next && fast.next.next) {
    stack.push(slow.val);
    slow = slow.next;
    fast = fast.next.next;
  }

  // 奇数
  if (fast.next) {
    stack.push(slow.val);
  }

  while ((slow = slow.next)) {
    if (slow.val !== stack.pop()) {
      return false;
    }
  }
  return true;
}

练习9: 旋转链表

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

输入: 0 -> 1 -> 2 -> NULL, k = 4
输出: 2 -> 0 -> 1 -> NULL
解释:
向右旋转 1 : 2 -> 0 -> 1 -> NULL
向右旋转 2 : 1 -> 2 -> 0 -> NULL
向右旋转 3 : 0 -> 1 -> 2 -> NULL
向右旋转 4 : 2 -> 0 -> 1 -> NULL
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var rotateRight = function(head, k) {
  if (!head || k === 0) return head;
  let length = 1, temp = head;
  while (temp.next) {
    length++;
    temp = temp.next;
  }
  
  k = k % length;
  if (k === 0) return head;

  // 形成环
  temp.next = head;
  let result = head;
  for (let i = 0; i < length - k; i++) {
    temp = temp.next;
    result = result.next;
  }
  temp.next = null;
  return result;
};

练习10: 删除链表中的重复元素

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

🌰 1:

输入: 1->1->2
输出: 1->2

🌰 2:

输入: 1->1->2
输出: 1->2

!! 注意,现已是排序链表

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
function deleteDuplicates(head) {
  let current = head;
  while (current && current.next) {
    if (current.val !== current.next.val) {
      current = current.next;
    } else {
      current.next = current.next.next;
    }
  }
  return head;
}

练习11: 移除链表元素

删除链表中等于给定值 val 的所有节点。

🌰 1:

输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
function removeElements(head, val) {
  let result = new ListNode(null);
  result.next = head;
  let current = result;
  while (current.next) {
    if (current.next.val === val) {
      current.next = current.next.next;
    } else {
      current = current.next;
    }
  }
  return result.next;
}

练习12: 反转链表

反转一个单链表。

🌰 1:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
function reverseList (head) {
  let prev = null;
  let current = head;
  while(current) {
    let temp = current.next;
    current.next = prev;
    prev = current;
    current = temp;
  }
  return prev;
};

练习13: 移除重复节点

编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。

🌰 1:

输入:[1, 2, 3, 3, 2, 1]
输出:[1, 2, 3]

🌰 2:

输入:[1, 1, 1, 1, 2]
输出:[1, 2]
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
function removeDuplicateNodes (head) {
  if (head === null) return head;
  let list = new ListNode(null),
      map = new Map(),
      prev = list,
      current = head;
  list.next = head;

  while (current) {
    if (map.has(current.val)) {
      prev.next = current.next;
      current = current.next;
    } else {
      map.set(current.val);
      prev = prev.next;
      current = current.next;
    }
  }
  return list.next;
}
Last Updated:
Contributors: kk