JS算法之复杂链表的复制及二叉搜索树与双向链表

349 阅读4分钟

这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战

复杂链表的复制

剑指Offer 35.复杂链表的复制

难度:中等

请实现copyRandomList函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个next指针指向下一个节点,还有一个random指针指向链表中的任意节点或者null

示例1:

img

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例2:

img

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例3:

img

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

示例4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:

  • -10000 <= Node.val <= 10000
  • Node.random为空(null)或指向链表中的节点
  • 节点数目不超过1000

题解

难点:本题主要是新增了random指针,在复制链表过程中需要新链表各节点的random引用指向,如下:(random无法确定指向)

var copyRandomList = function(head) {
  let cur = head;
  let dum = new Node(0), pre = dum;
  while(!cur){
    let node = new Node(cur.val); // 复制节点 cur
    prev.next = node;							// 新链表的 前驱节点->当前节点
    // prev.random = ???					// 无法确定
    cur = cur.next;								// 遍历下一节点
    pre = node;										// 保存当前节点
  }
  return dum.next;
};

法一 哈希表

利用哈希表的查询特点,构建原链表节点新链表对应节点的键值对映射关系,再遍历构建新链表各节点的nextrandom引用指向即可。

流程:

  1. 若头节点head为空,返回null。
  2. 初始化:新建哈希表,节点cur指向头节点。
  3. 复制链表
    1. 建立新节点,并向map添加键值对(原cur节点,新cur节点)。
    2. cur遍历至原链表下一节点。
  4. 构建新链表的引用指向
    1. 构建新节点next和random引用指向。
    2. cur 遍历至原链表下一节点。
  5. 返回值:新链表的头节点。
/**
 * // 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) return null;
  const map = new Map(); // 新建哈希表
  let cur = head;  
  while(cur){
    map.set(cur,new Node(cur.val))
    cur = cur.next;
  }
  cur = head;
  
  while(cur){
   	map.get(cur).next = cur.next ? map.get(cur.next) : null;
    map.get(cur).random = cur.random ? map.get(cur.random) : null;
    cur = cur.next;
  }
  return map.get(head);
};
  • 时间复杂度O(N):两轮遍历链表

  • 空间复杂度O(N):使用哈希表的额外空间

法二 拼接 + 拆分

构建旧节点1 -> 新节点1 -> 旧节点2 -> 新节点2 -> ...的拼接链表,如此可以方便旧节点的random指向节点的同时找到新对应新节点的random指向节点。

步骤:

  1. 复制各节点,并构建拼接链表
  2. 构建各新节点的random指向,当访问cur.random时,对应新节点cur.next的随机指向节点为cur.random.next。
  3. 拆分旧新链表,设置pre和cur分别指向旧链表和新链表的头节点,通过遍历,讲pre和cur两条链表拆分开。
  4. 返回新链表的头节点res。
var copyRandomList = function(head) {
  if(head === null) return null;
  // 1.复制各节点,并构建拼接链表
  while(cur){
    let tmp = new Node(cur.val);
    tmp.next = cur.next;
    cur.next = tmp;
    cur = tmp.next;
  }
  // 2.构建各新节点的random指向
  cur = head;
  while(cur){
    if(cur.random){
      cur.next.random = cur.random.next;
    }
    cur = cur.next.next;
  }
  // 3.拆分两链表
  cur = head.next;
  let pre = head, res = head.next;
  while(cur.next){
    pre.next = pre.next.next;
    cur.next = cur.next.next;
    pre = pre.next;
    cur = cur.next;
  }
  pre.next = null; // 处理旧链表表尾
  return res;	// 返回新链表头节点
}
  • 时间复杂度O(N):三轮遍历链表
  • 空间复杂度O(1):引用变量使用常数大小的空间。

二叉搜索树与双向链表

剑指Offer 36.二叉搜索树与双向链表

难度:中等

力扣地址:leetcode-cn.com/problems/er…

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

为了让您更好地理解问题,以下面的二叉搜索树为例:

img

我们希望将这个二叉搜索树转化成双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

下面展示了上面的二叉搜索树转换化成的链表。“head”表示指向链表中有最小元素的节点。

img

特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

题解

根据二叉搜索树的性质,可以推断出应该采用中序遍历。

法一 递归中序遍历

/**
 * @param {Node} root
 * @return {Node}
 */
var treeToDoublyList = function(root){
  if(!root) return;
  let head = null;
  let pre = head;
  inorder(root);
  head.left = pre;
  pre.right = head;
  return head;
 /**
 * @param {Node} node
 */
  function inorder(node){
    if(!node) return;
    // 遍历左子树
    inorder(node.left,pre);
    // 遍历当前根节点
    if(!pre){
      // 遍历到最小的节点(最左边的节点),此时节点就是双向链表的head
      head = node;
    }else{
      pre.right = node;
    }
    node.left = pre;
    pre = node;
    // 遍历右子树
    inorder(node.right,pre);
  }
}
  • 时间复杂度O(N)
  • 空间复杂度O(N)

法二 非递归中序遍历

利用栈来模拟递归过程

var treeToDoublyList = function(root) {
  if(!root) return;
  const stack = [];
  let node = root
  let pre = null;
  let head = null;
  while(stack.length || node){
    if(node){
      stack.push(node);
      node = node.left;
    }else{
      const top = stack.pop();
      if(!pre){
        head = top;
      }else{
        pre.right = top;
      }
      top.left = pre;
      pre = top;
      node = top.right;// 用于遍历右子树
    }
  }
  head.left = pre;
  pre.right = head;
  return head;
}

坚持每日一练!前端小萌新一枚,希望能点个哇~