剑指offer - 代码的鲁棒性

1.链表中倒数第 k 个结点

问题描述:

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

解题思路 1:

因为要求链表倒数第 k 个节点,也就是求正数第 length - k 个节点。整体过程如下:

  • 链表又是个单链表,并且没有保存长度信息。所以需要循环一次计算 length。
  • 第二次循环找到第 length - k 个节点。

时间复杂度是 O(N),需要 2 次循环。

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/

function FindKthToTail(head, k) {
  // write code here
  let length = 0;
  let node = head;
  while (node) {
    length++;
    node = node.next;
  }
  if (k > length) {
    return null;
  }
  let offset = length - k;
  node = head;
  for (let i = 0; i < offset; i++) {
    node = node.next;
  }
  return node;
}

解题思路 2:

准备两个指针:left(慢)和 right(快)。整体过程如下:

  • right 先向右移动 k 位,此时 index(right) - index(left) = k
  • left 和 right 一起向右移动,直到 right 抵达边界
  • 由于 index(right) - index(left) = k,所以 index(left) = index(right) - k = length - k。也就是 left 指针移动到了倒数第 k 个位置

时间复杂度是 O(N),但仅需要遍历一次。空间复杂度是 O(1)

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/

function FindKthToTail(head, k) {
  let right = head;
  for (let i = 0; i < k; ++i) {
    if (right === null) {
      // 链表长度小于k
      return null;
    }
    right = right.next;
  }

  let left = head;
  while (right) {
    left = left.next;
    right = right.next;
  }

  return left;
}

2.反转链表

问题描述:

输入一个链表,反转链表后,输出新链表的表头。

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function ReverseList(pHead) {
  // write code here
  let prev = null; // 上一个节点
  var curr;
  while (pHead) {
    curr = pHead; // 当前节点
    pHead = pHead.next;
    curr.next = prev;
    prev = curr;
  }
  return prev;
}

3.合并两个排序的链表

问题描述:

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

解题思路:
设置一个“哨兵节点”叫 pHead,整体流程如下:

  • 如果 pHead1 和 pHead2,均没遍历完:

    • 如果 pHead1.val <= pHead2.val,那么当前 node 的 next 指向 pHead1。并且移动 pHead1 指针。
    • 否则,当前 node 的 next 指向 pHead2,移动 pHead2 指针。
    • 移动 node 指针
    • 继续循环
  • 否则,结束循环:

    • 如果 pHead1 未遍历完,node 的 next 指向 pHead1
    • 如果 pHead2 未遍历玩,node 的 next 指向 pHead2

时间复杂度是 O(N),空间复杂度是 O(1)。代码如下:

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function Merge(pHead1, pHead2) {
  // write code here
  if (!pHead1) {
    // 如果pHead1链表不存在,就返回pHead2
    return pHead2;
  }
  if (!pHead2) {
    // 如果pHead2链表不存在,就返回pHead1
    return pHead1;
  }

  let pHead = new ListNode(-1); // 新建一个‘哨兵节点’
  let node = pHead; // 头节点的指针

  while (pHead1 && pHead2) {
    // pHead1 和 pHead2 都存在时
    if (pHead1.val <= pHead2.val) {
      node.next = pHead1;
      pHead1 = pHead1.next;
    } else {
      node.next = pHead2;
      pHead2 = pHead2.next;
    }
    node = node.next;
  }

  if (pHead1) {
    // pHead1 链表长度大于 pHead2
    node.next = pHead1;
  }
  if (pHead2) {
    // pHead2 链表长度大于 pHead1
    node.next = pHead2;
  }
  return pHead.next; // pHead是-1,所以是pHead.next
}

4.树的子结构

问题描述:

输入两棵二叉树 A,B,判断 B 是不是 A 的子结构。(ps:我们约定空树不是任意一个树的子结构)

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */

function HasSubtree(pRoot1, pRoot2) {
  // 当pRoot2的根节点与pRoot1的根节点不同时就需要对pRoot1的左右子树进行遍历
  // write code here
  if (pRoot1 == null || pRoot2 == null) {
    // pRoot1 pRoot2 有一个为null,结构就为false
    return false;
  }
  return (
    judgeSubtree(pRoot1, pRoot2) || // pRoot2是不是pRoot1的子结构
    HasSubtree(pRoot1.left, pRoot2) || // pRoot1的左子树中有没有pRoot2
    HasSubtree(pRoot1.right, pRoot2) // pRoot1的右子树中有没有pRoot2
  );
}
function judgeSubtree(root1, root2) {
  // 对root2进行遍历判断root2 是不是root1 的子结构
  if (!root2) {
    return true;
  }
  if (!root1) {
    return false;
  }
  if (root1.val !== root2.val) {
    return judgeSubtree(root1.left, root2) || judgeSubtree(root1.right, root2);
  }
  return (
    judgeSubtree(root1.left, root2.left) &&
    judgeSubtree(root1.right, root2.right)
  );
}
posted @ 2020-04-16 21:51  木子呆头  阅读(120)  评论(0编辑  收藏  举报