剑指 Offer——Day2链表(简单)

58 阅读3分钟

剑指 Offer 06. 从尾到头打印链表

方法一 递归

递归法,先递归至链表末端;回溯时,依次将节点值加入数组;

递归解析:

  1. 终止条件:head = null
  2. 递推工作:访问下一个节点head.next
  3. 回溯工作:将当前节点的值加入数组;

复杂度分析

  • 时间复杂度O(N):遍历链表,递归N次;
  • 空间复杂度O(N):系统递归需要O(N)的栈控件
var reversePrint = function(head) {
    const res = [];
    helper(head, res);
    return res;
};

var helper = function(head, res) {
    if (head === null) {
        return;
    }
    helper(head.next, res);//递归过程
    res.push(head.val);//回溯过程
}

方法二 辅助栈法

辅助栈法:借用栈的后进先出(LIFO)特性;

算法流程:

  1. 入栈:遍历链表,将各个节点值入栈
  2. 出栈:将各节点pop出栈,存储数组并返回

复杂度分析

  • 时间复杂度O(N):入栈和出栈共使用O(N)时间
  • 空间复杂度O(N):辅助栈和数组共使用O(N)的额外空间
var reversePrint = function(head) {
    const stack = [];
    let curr = head;
    while (curr) {
        stack.push(curr.val);
        curr = curr.next;
    }

    const res = [];
    while (stack.length !== 0) {
        res.push(stack.pop())
    }

    return res;
};

剑指 Offer 24. 反转链表

方法一 迭代(双指针)

遍历链表,并在访问各链表时修改next指向;

算法流程:

  1. 定义cur、prev
  2. 遍历链表,记cur.next为next,将当前cur.next 指向prev
  3. prev = cur、cur = next;

复杂度分析:

  • 时间复杂度O(N):遍历链表使用线性大小时间;
  • 空间复杂度O(1):变量 pre 和 cur 使用常数大小额外空间。
var reverseList = function(head) {
  let cur = head, prev = null;
  while (cur) {
      let next = cur.next;
      cur.next = prev;
      prev = cur;
      cur = next;
  }
  return prev;
}

方法二 递归

使用递归法遍历链表,当越过尾节点后终止递归,在回溯时修改各节点的next引用指向

recur(cur, pre) 递归函数:

  1. 终止条件:cur为空,返回prev节点,终止递归
  2. 递归后继节点:记录返回值,即反转链表的头节点为res;
  3. 修改当前节点cur引用指向前驱节点prev;
  4. 返回反转链表的头节点res

复杂度分析

  • 时间复杂度O(N):遍历链表使用线性大小的时间
  • 空间复杂度O(N):遍历链表的递归深度达到N,系统使用O(N)大小额外空间;
var reverseList = function(head) {
  return recur(head, null);
} 

var recur = function(cur, prev) {
  //递归终止条件
  if (cur === null) { 
    return prev;
  }

  let res = recur(cur.next, cur);
  cur.next = prev;
  return res;
}

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

复制链表很简单,本体难点在于random节点的指向。

方法一 借用哈希map

利用哈希表,构建原链表节点和新链表对应节点的键值映射关系,在遍历构建新链表的各节点的的next 和 random的引用指向;

算法流程

  1. 若头节点为空,返回null;
  2. 初始化:哈希表map,节点cur指向head
  3. 复制链表:建立新链表,并向map添加键值对(老节点是键,新节点是值);cur 遍历至原链表下一节点;
  4. 构建新链表的引用指向:构建新节点的nextrandom指向;cur 遍历至原链表下一节点;
  5. 返回值:新链表的头节点map(head)
function copyRandomList(head) {
  if (!head) {
    return null;
  }

  let cur = head;
  const map = new Map();
  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);
}

方法二 拼接+拆分