剑指offer

90 阅读2分钟

链表

从尾到头打印链表

输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。
如输入{1,2,3}的链表如下图:
返回一个数组为[3,2,1]

image.png

输入:{1,2,3}
返回值:[3,2,1]
function printListFromTailToHead(head)
{
    let res = [];
    while(head){
        res.unshift(head.val)
        head = head.next
    }
    return res
}

反转链表

image.png

输入: {1,2,3}
返回值:{3,2,1}
function ReverseList(head)
{
    let temp = new ListNode()
    let next = null;
    while(head){
        next = head.next;
        head.next = temp.next;
        temp.next = head;
        head = next
    }
    return temp.next
}

合并两个排序的链表

image.png

function Merge(pHead1, pHead2)
{
    if(pHead1 === null) return pHead2;
    if(pHead2 === null) return pHead1;
    if(pHead1.val < pHead2.val){
        pHead1.next = Merge(pHead1.next,pHead2)
        return pHead1
    }else{
        pHead2.next = Merge(pHead1,pHead2.next)
        return pHead2
    }
}

两个链表的第一个公共结点

例如,输入{1,2,3},{4,5},{6,7}时,两个无环的单向链表的结构如下图所示:

image.png

输入:{1,2,3},{4,5},{6,7}
输出:{6}
function FindFirstCommonNode(pHead1, pHead2)
{
    if(!pHead1 || !pHead2) return null;
    let p1 = pHead1;
    let p2 = pHead2;
    while(p1 != p2){
        p1 = p1 === null ? pHead2 : p1.next;
        p2 = p2 === null ? pHead1 : p2.next;
    }
    return p1
}

链表中倒数最后k个结点

例如输入{1,2,3,4,5},2时,对应的链表结构如下图所示: image.png 其中蓝色部分为该链表的最后2个结点,所以返回倒数第2个结点(也即结点值为4的结点)即可,系统会打印后面所有的节点来比较。

输入:{1,2,3,4,5},2
返回值:{4,5}

注意:1.快指针走k步的时候注意判断fast是否为null

function FindKthToTail( pHead ,  k ) {
    let fast = pHead;
    let slow = pHead;
    for(let i = 0; i < k; i++){
        if(fast === null) return null;
        fast = fast.next;
    }
    while(fast != null){
        fast = fast.next;
        slow = slow.next;
    }
    return slow
}

复杂链表的复制 hard

输入:{1,2,3,4,5,3,5,#,2,#}
返回值:{1,2,3,4,5,3,5,#,2,#}

删除链表中重复的结点(重复的也删除,感觉不会有人这样考)

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5  处理后为 1->2->5 image.png

输入:{1,2,3,3,4,4,5}
返回值:{1,2,5}
function deleteDuplication(pHead) {
  root.next = pHead;
  let r = root;

  while (r.next != null && r.next.next != null) {
    if (r.next.val == r.next.next.val) {
      var temp = r.next.val;
      while (r.next != null && r.next.val == temp) r.next = r.next.next;
    } else {
      r = r.next;
    }
  }

  return root.next;
}

删除排序链表中的重复元素(保留一个重复的)

image.png

输入: head = [1,1,2]
输出: [1,2]
var deleteDuplicates = function(head) {
    let cur = head;
    while(cur && cur.next) {
        if(cur.val == cur.next.val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }
    return head;
};

删除链表的节点

输入: {2,5,1,9},5
输出: {2,1,9}
function deleteNode( head ,  val ) {
    if(head === null ){return head}
    head.next = deleteNode(head.next, val)
    return head.val === val ? head.next : head
}

反转链表 II

给你单链表的头指针 head 和两个整数 leftright ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

image.png

输入: head = [1,2,3,4,5], left = 2, right = 4
输出: [1,4,3,2,5]
var reverseBetween = function(head, left, right) {
    let temp = new ListNode();
    temp.next = head;
    let pre = temp;
    for(let i = 0; i < left - 1; i++){
        pre = pre.next;
    }
    let cur = pre.next;
    for(let i = 0; i < right - left; i++){
        const next = cur.next;
        cur.next = next.next;
        next.next = pre.next;
        pre.next = next;
    }
    return temp.next;
};

24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

image.png

输入: head = [1,2,3,4]
输出: [2,1,4,3]
var swapPairs = function(head) {
    let temp = new ListNode();
    temp.next = head;
    let cur = temp;
    while(cur.next != null && cur.next.next!=null){
        let n1 = cur.next;
        let n2 = cur.next.next;
        n1.next = n2.next;
        n2.next = n1;
        cur.next = n2;
        cur = n1;
    }
    return temp.next
};

二叉树

二叉树的深度

function TreeDepth(pRoot)
{
    if(!pRoot) return 0;
    let left = TreeDepth(pRoot.left);
    let right = TreeDepth(pRoot.right);
    return left>right?left+1:right+1
}

按之字形顺序打印二叉树(记住层序遍历)

给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替)
image.png
先求二叉树的层序遍历,再对数组下标对2求余数,余数不为0 就把数组reverse();返回arr。

function Print(pRoot) {
  const arr = [];
  das(pRoot, arr, 0);
  for (i = 0; i < arr.length; i++) {
    if (i % 2) {
      arr[i].reverse();
    }
  }
  return arr;
}
function das(root, arr, level) {
  // 层序遍历
  if (!root) return;
  if (!arr[level]) {
    arr[level] = [];
  }
  arr[level].push(root.val);
  das(root.left, arr, level + 1);
  das(root.right, arr, level + 1);
}

二叉树前中后层序遍历

//前序遍历:
var preorderTraversal = function(root, res = []) {
    if (!root) return res;
    res.push(root.val);
    preorderTraversal(root.left, res)
    preorderTraversal(root.right, res)
    return res;
};

//中序遍历:
var inorderTraversal = function(root, res = []) {
    if (!root) return res;
    inorderTraversal(root.left, res);
    res.push(root.val);
    inorderTraversal(root.right, res);
    return res;
};

//后序遍历:
var postorderTraversal = function(root, res = []) {
    if (!root) return res;
    postorderTraversal(root.left, res);
    postorderTraversal(root.right, res);
    res.push(root.val);
    return res;
};
// 层序遍历
function das(root, arr, level) {
  if (!root) return;
  if (!arr[level]) {
    arr[level] = [];
  }
  arr[level].push(root.val);
  das(root.left, arr, level + 1);
  das(root.right, arr, level + 1);
}

98. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下: 节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树 image.png

输入: root = [2,1,3]
输出: true

思路:利用二叉搜索树的性质,每个节点都大于它左子树所有节点,小于右子树上所有节点,并且每个节点左右子树不为空,那它的左右子树也是二叉搜索树。我们可以递归验证每个节点的左右子树。

function helper(root,lower,higher){
    if(root === null) return true;
    if(root.val <= lower || root.val >= higher){
        return false;
    }
    return helper(root.left,lower,root.val) && helper(root.right,root.val,higher)
}
var isValidBST = function(root) {
    return helper(root,-Infinity,Infinity)

};

235. 二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

image.png

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6

分为三种情况,1.root节点大于p并且大于q,说明p和q都在root的左子树, 2.root节点小于p并且小于q,说明p和q都在root的右子树,3.其他情况比如root等于p或q,说明root就是公共祖先,前两种情况直接递归左右子树,第3种情况直接返回root

var lowestCommonAncestor = function(root, p, q) {
    if(root === null) return root
    if(root.val > p.val && root.val > q.val){
        let left = lowestCommonAncestor(root.left , p, q)
        return left !== null && left
    } 
    if(root.val < p.val && root.val < q.val){
        let right = lowestCommonAncestor(root.right, p, q)
        return right !== null && right
    }
    return root
};

236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

image.png

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3

分四种情况,1.root是null或者root等于p或q,说明root是p,q的公共祖先,2.递归左右子树,如果左右子树递归函数返回的都不为空,则root就是p,q的公共祖先,3.左子树递归函数返回的值为空,则p,q都在右子树,4.右子树递归函数返回的值为空,则p,q都在左子树 image.png

 function travel(root,p,q){
     // 确定递归终止条件
     if(root === null || root === p || root === q) return root
     // 确定递归单层逻辑
     let left = travel(root.left, p, q)
     let right = travel(root.right, p, q)
     //如果在某一个节点的左右子树都能找到p和q说明这个节点就是公共祖先
     if(left !== null && right !== null) return root
     if(left === null) return right //如果左子树没找到就说明p,q都在右子树
     if(right === null) return left //如果右子树没找到就说明p,q都在左子树
 }
var lowestCommonAncestor = function(root, p, q) {
    return travel(root,p,q)
};

112. 路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。

image.png

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。

递归左右子树,不断让sum减去当前节点的值。左右子树有一个返回true就找到了一条这样的路径

var hasPathSum = function(root, targetSum) {
    if(root === null) return false
    // 遇到叶子结点
    if(root.left === null && root.right === null){
        return targetSum - root.val === 0
    }
    // 遍历左右子树
    return hasPathSum(root.left, targetSum-root.val) || hasPathSum(root.right, targetSum-root.val)
};

257. 二叉树的所有路径 (easy)

给你一个二叉树的根节点 `root` ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。

image.png

输入: root = [1,2,3,null,5]
输出: ["1->2->5","1->3"]

递归左右子树,直到叶子节点,递归的过程中不断透传path,递归的每一层连接上当前节点的值

var binaryTreePaths = function(root) {
    let arr = []
    function dfs(root, path){
        if(root){
            path += root.val.toString()
            // 如果是叶子结点就把结果放进去
            if(root.left === null && root.right === null){
                arr.push(path)
            }else{
                path += '->'
                dfs(root.left,path)
                dfs(root.right,path)
            }
        }
    }
    dfs(root,'')
    return arr
};