LeetCode 剑指Offer 链表 by JavaScript

链表

本文描述了链表的基本概念和常用操作,并给出了几道常见的链表算法题来帮助读者理解链表。

基本概念

链表是数据元素的线性组合,但和数组不同的是,链表中的数据在内存中不是连续存储的。在一个单向链表中,每个节点指向后一个节点,这些节点前后相连并形成了一个序列。而在非循环双向链表中,除第一个和最后一个节点外,一个节点通常会和它的前后两个节点相连。此外,在实际使用双向链表时,通常会设置一个前哨节点作为表头,以方便对双向链表进行操作。
图1图2

常用操作

单链表

操作代码
// 构建单链表
function ListNode(val){
  this.val = val;
  this.next = null;  
}

// 向链表末尾添加节点
function add(head, val){
  let node = new ListNode(val);
  if(head == null){
    head = node;
  }else{
    let cur = head;
    while(cur.next != null){
      cur = cur.next;
    }
    cur.next = node;
  }
}

// 向链表表头前插节点
function prepend(head, val){
  let node = new ListNode(val);
  node.next = head;
  head = node;
}

// 按值搜索链表节点
function contains(head, val){
  let cur = head;
  while(cur != null){
    if(cur.val == val){
      return true;
    }
    cur = cur.next;
  }
  return false;
}

// 删除节点
function remove(head, val){
  if(head == null){
    return false;
  }
  let cur = head.next, front = head;
  while(cur != null && cur.val != val){
    front = cur;
    cur = cur.next;
  }
  if(cur != null){
    front.next = cur.next;
    cur = null;
    return true;
  }else{
    return false;
  }
}

// 遍历链表
function traverse(head){
  let cur = head;
  while(cur != null){
    console.log(cur.val);
    cur = cur.next;
  }
}

// 反向遍历
function reverseTraverse(head){
  let tail = null, cur = head;
  while(cur != null){
    let node = new ListNode(cur.val);
    node.next = tail;
    tail = node;
    cur = cur.next;
  }
  traverse(tail);
}

// 创建表头
let head = new ListNode(0);
复杂度
  1. 时间复杂度:
    ""
  2. 空间复杂度:O(n)

双向链表

操作代码
// 定义双链表
function ListNode(value){
  this.value = value;
  this.previous = null;
  this.next = null;
}

// 插入节点到前哨节点后
function addNode(head, value){
  let node = new ListNode(value);
  if(head.next == null){
    head.previous = node;
  }
  node.previous = head;
  node.next = head.next;
  head.next = node;
}

// 删除指定值节点
function deleteNode(head, value){
  let cur = head.next;
  while(cur != null){
    if(cur.value = value){
      cur.previous.next = cur.next;
      cur.next.previous = cur.previous;
    } 
    cur = cur.next;
  }
}

// 反向遍历
function reverseTraverse(head){
  let cur = head.previous;
  while(cur != null){
    console.log(cur.value);
    cur = cur.previous;
  }
}

//创建双链表对象 并将其作为前哨节点
let head = new ListNode("");
复杂度
  1. 时间复杂度:
    在这里插入图片描述
  2. 空间复杂度:O(n)

实战练习

  1. 剑指 Offer 06. 从尾到头打印链表
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {number[]}
 */
var reversePrint = function(head) {
  let arr = [];
  let cur=head;
  while(cur != null){
      arr.push(cur.val);
      cur=cur.next;
  }
  return arr.reverse();
};

/**题目分析
 * 反向打印链表,而题目给出的单向链表又只能从头访问,这和栈的思想类似,
 * 所以可以利用js数组模拟入栈出栈来解题。
 */
  1. 剑指 Offer 18. 删除链表的节点
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var deleteNode = function(head, val) {
  if(head.val == val){
      return head.next;
  }
  let node = head.next, front = head;
  while(node != null){
      if(node.val == val){
          front.next = node.next;
      }
      front = node;
      node = node.next;
  }
  return head;
};

/**题目分析
 * 很简单的一道题,就是单向链表的节点删除操作。
 */
  1. 剑指 Offer 22. 链表中倒数第k个节点.
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
 var getKthFromEnd = function(head, k) {
  let fast = slow = head
  while(fast != null){
      fast = fast.next;
      if(k-- <= 0){
          slow = slow.next;   
      }
  }
  return slow;
};

/**题解
 * 像这种找第几个节点的,基本都是快慢指针方法,
 * 即第一个节点负责遍历,走到末尾,第二个节点延迟行动,使得当遍历结束时,第二个节点正好指向目标节点。
 * 这里需要注意,遍历结束时,fast指针为null,而不是最后一个节点,所以应该是用后自减,使得快慢指针至少差一个节点位置。
 */
  1. 剑指 Offer 24. 反转链表
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
 var reverseList = function(head) {
  let reverseHead = null, cur = head;
  while(cur != null){
      const next = cur.next;
      cur.next = reverseHead;
      reverseHead = cur;
      cur = next;
  }
  return reverseHead;
};

/**题解
 * 反转链表,其实就是使用前插方法来重新构建一个新链表。
 */
  1. 剑指 Offer 35. 复杂链表的复制
/**
 * // Definition for a Node.
 * function Node(val, next, random) {
 *    this.val = val;
 *    this.next = next;
 *    this.random = random;
 * };
 */

/**
 * @param {Node} head
 * @return {Node}
 */
 var copyRandomList = function(head) {
  if(head == null){
      return null;
  }
  let copyArr = [], cur = head, index = 0;
  while(cur != null){
      let node = new Node(cur.val, null, null);
      copyArr.push(node);
      cur.val = index++;
      cur = cur.next;
  }
  let copyHead = copyArr[0];
  let copyCur = copyHead;
  cur = head;
  while(cur != null){
      copyCur.next = cur.next? copyArr[cur.next.val] : null;
      copyCur.random = cur.random? copyArr[cur.random.val] : null;
      cur = cur.next;
      copyCur = copyCur.next;
  }
  return copyHead;
};

/**题解
 * 一种思路是:先遍历链表,并使用映射表,将链表中的节点作为key,将对应的复制节点(复制值和next指针)作为值存储。
 * 之后再做一个遍历,使用哈希表获取random节点的复制节点,更新random指针。
 *
 * 另一种思路也是两次循环,但不需要映射表,用数组即可。在第一次循环时修改value值为其数组索引,之后根据value来更新next和random。
 */
  1. 剑指 Offer 52. 两个链表的第一个公共节点
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
 var getIntersectionNode = function(headA, headB) {
  let lenA = 0, lenB = 0;
  let curA = headA, curB = headB;
  while(curA != null){
      lenA++;
      curA = curA.next;
  }
  while(curB != null){
      lenB++;
      curB = curB.next;
  }
  curA = headA, curB = headB;
  if(lenA < lenB){
      [lenA, lenB] = [lenB, lenA];
      [curA, curB] = [curB, curA];
  }
  let gap = lenA - lenB;
  while(gap > 0){
      curA = curA.next;
      gap--;
  }
  while(curA!=null){
      if(curA == curB){
          return curA;
      }
      curA = curA.next;
      curB = curB.next;
  }
  return null;
};

/**题解
 * 这道题也是快慢指针的思路。当两个链表存在公共节点时,公共节点及后面的节点长度一致。
 * 所以先遍历得到两个链表的长度差,然后使用快慢指针遍历即可。
 */

总结

这几道比较简单,基本上都是一些链表的基本操作。一个值得注意的点是快慢指针(双指针)的使用,使用快慢指针能帮助减少时间\空间复杂度。

参考

[1] github-javascript-algorithms,单向链表的基本概念和基本操作
[2] github-javascript-algorithms,双向链表的基本概念和基本操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值