剑指Offer(JavaScript版)

数组

1. 数组中重复的数字

  • 题目
    • 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字
  • 解题思路
    • 数组排序后,相等的数就会相邻
    • 直接循环比较,遇到相等的就返回该值
  • 代码实现
var findRepeatNumber = function(nums) {
    
    
    nums.sort();
    for(var i = 0; i < nums.length-1; i++) {
    
    
        if(nums[i] == nums[i+1])
            return nums[i]
    }
};

2. 二维数组中的查找

  • 题目
    • 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
  • 解题思路
    • 数组降维后再次查找
  • 代码实现
var findNumberIn2DArray = function(matrix, target) {
    
    
    return matrix.flat(Infinity).includes(target)
};

3. 顺时针打印矩阵

  • 题目
    • 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
  • 解题思路
    • 一层层向里处理,按顺时针依次遍历:上、右、下、左层
    • 不再形成“环”了,就会剩下一行或一列,然后单独判断
  • 代码实现
var spiralOrder = function (matrix) {
    
    
  if (matrix.length === 0) return []
  const res = []
  let top = 0, bottom = matrix.length - 1, left = 0, right = matrix[0].length - 1
  while (top < bottom && left < right) {
    
    
    for (let i = left; i < right; i++) res.push(matrix[top][i])   // 上层
    for (let i = top; i < bottom; i++) res.push(matrix[i][right]) // 右层
    for (let i = right; i > left; i--) res.push(matrix[bottom][i])// 下层
    for (let i = bottom; i > top; i--) res.push(matrix[i][left])  // 左层
    right--
    top++
    bottom--
    left++  // 四个边界同时收缩,进入内层
  }
  if (top === bottom) // 剩下一行,从左到右依次添加
    for (let i = left; i <= right; i++) res.push(matrix[top][i])
  else if (left === right) // 剩下一列,从上到下依次添加
    for (let i = top; i <= bottom; i++) res.push(matrix[i][left])
  return res
};

4. 在排序数组中查找数字I

  • 题目
    • 统计一个数字在排序数组中出现的次数
  • 解题思路
    • 从数组左侧向右遍历,遇到目标数字 target,停止,记录下标 left
    • 从数组右侧向左遍历,遇到目标数字 target,停止,记录下标 right
    • 如果 right 小于 left,那么说明没出现,返回 0;否则返回 right - left + 1
  • 代码实现
var search = function(nums, target) {
    
    
    if (!nums.length) return 0;

    let left = 0,
        right = nums.length - 1;
    while (nums[left] !== target && left < nums.length) {
    
    
        ++left;
    }
    while (nums[right] !== target && right >= 0) {
    
    
        --right;
    }

    return left <= right ? right - left + 1 : 0;
};

5. 0~n-1中缺失的数字

  • 题目

    • 一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
  • 解题思路

    • 二分查找
      • left 指向 0,right 指向最后一个元素
      • 计算中间坐标 mid:
        • 如果mid = nums[mid],说明[0, mid]范围内不缺失数字,left 更新为 mid + 1
        • 如果mid < nums[mid],说明[mid, right]范围内不缺失数字,right 更新为 mid - 1
      • 检查 left 是否小于等于 mid,若成立,返回第 2 步;否则,向下执行
      • 返回 left 即可
  • 代码实现

var missingNumber = function(nums) {
    
    
    let left = 0,
        right = nums.length - 1;
    while (left <= right) {
    
    
        let mid = Math.floor((left + right) / 2);
        if (mid === nums[mid]) {
    
    
            left = mid + 1;
        } else if (mid < nums[mid]) {
    
    
            right = mid - 1;
        }
    }

    return left;
};

1. 用两个栈实现队列

  • 题目

    • 用两个栈实现一个队列
    • 队列的声明如下,请实现它的两个函数 appendTaildeleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能
    • (若队列中没有元素,deleteHead 操作返回 -1 )
  • 解题思路

    • 利用双栈实现序列倒置
      • 假设 stack1=[1, 2, 3] 、 stack2=[] ,循环出栈 stack1 并将出栈元素进栈 stack2 ,循环结束后, stack1=[] 、 stack2=[3, 2, 1] ,即实现元素的倒置
    • 当需要删除队首元素时,仅仅需要 stack2 出栈即可
    • 当 stack2 为空时,出队就需要将 stack1 元素倒置倒 stack2 , stack2 再出队即可
    • 如果 stack1 也为空,即队列中没有元素,返回 -1
  • 代码实现

var CQueue = function() {
    
    
    this.stack1 = [];
    this.stack2 = [];
};

CQueue.prototype.appendTail = function(value) {
    
    
    this.stack1.push(value)
};

CQueue.prototype.deleteHead = function() {
    
    
    if(this.stack2.length){
    
    
        return this.stack2.pop()
    }

    if(!this.stack1.length) return -1

    while(this.stack1.length){
    
    
        this.stack2.push(this.stack1.pop())
    }
    return this.stack2.pop()
};

2. 包含min函数的栈

  • 题目
    • 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中
    • 调用 minpushpop 的时间复杂度都是 O(1)
  • 解题思路
    • 暴力求解
    • ...this.itemsES6扩展运算符语法,主要运用于函数的调用
  • 代码实现
var MinStack = function() {
    
    
    this.items = []
};

MinStack.prototype.push = function(x) {
    
    
    this.items.push(x)
};

MinStack.prototype.pop = function() {
    
    
    this.items.pop()
};

MinStack.prototype.top = function() {
    
    
    return this.items[this.items.length - 1]
};

MinStack.prototype.min = function() {
    
    
    return Math.min(...this.items)
};

4. 队列的最大值

  • 题目
    • 请定义一个队列并实现函数 max_value 得到队列里的最大值
    • 要求函数max_valuepush_backpop_front 的均摊时间复杂度都是O(1)
    • 若队列为空,pop_frontmax_value 需要返回 -1
  • 解题思路
    • 使用两个队列
      • 一个队列 queue 用于存放所有元素,另一个辅助队列 dequeue 用来存放当前 queue 中的最大值。
    • push 操作:
      • 将元素放入 queue 中
      • 检查元素是否大于 dequeue 队尾元素
      • 如果大于,那么队尾元素出队;直到不再满足大于条件
    • pop 操作:
      • 如果 queue 的队首元素等于 dequeue 的队首元素,那么 dequeue 队首元素需要出队
      • queue 队首元素需要出队
  • 代码实现
var MaxQueue = function() {
    
    
    this.queue = [];
    this.dequeue = [];
};

MaxQueue.prototype.max_value = function() {
    
    
    return this.dequeue.length ? this.dequeue[0] : -1;
};

MaxQueue.prototype.push_back = function(value) {
    
    
    this.queue.push(value);
    while (
        this.dequeue.length &&
        value > this.dequeue[this.dequeue.length - 1]
    ) {
    
    
        this.dequeue.pop();
    }
    this.dequeue.push(value);
};

MaxQueue.prototype.pop_front = function() {
    
    
    if (!this.dequeue.length) {
    
    
        return -1;
    }
    if (this.queue[0] === this.dequeue[0]) {
    
    
        this.dequeue.shift();
    }
    return this.queue.shift();
};

队列

1. 滑动窗口的最大值

  • 题目
    • 给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值
  • 解题思路
    • 暴力求解
    • 直接移动这个滑动窗口,每次统计窗口中的最大值即可
  • 代码实现
var maxSlidingWindow = function(nums, k) {
    
    
    if (k <= 1) return nums;
    const res = [];
    for (let i = 0; i < nums.length - k + 1; ++i) {
    
    
        res.push(Math.max(...nums.slice(i, i + k)));
    }
    return res;
};

链表

1. 从尾到头打印链表

  • 题目
    • 输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)
  • 解题思路
    • 使用数组 result 保存结果
    • 使用 while 循环遍历链表
    • 使用 unshift 方法将每次循环的节点值存到数组中,指向下一个节点
  • 代码实现
var reversePrint = function(head) {
    
    
  const result = []
  
  while(head !== null) {
    
    
    result.unshift(head.val)
    head = head.next
  }
  
  return result
};

2. 删除链表的节点

  • 题目
    • 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。
  • 解题思路
    • 判断要删除的元素是否处于链表的开头
      • 如果是开头元素
        • 直接返回head.next
      • 如果不是开头元素
        • 先找到要删除的元素在链表中所在的位置index
        • 在利用循环找到要删除的元素current和它的前一个元素previous
        • 再让previous.next = current.next即可
  • 代码实现
var deleteNode = function(head, val) {
    
    

    if(val == head.val){
    
    
        return head.next;
    }
    else{
    
    
        
        let pre = head;
        let index=0;

        while(pre){
    
    
            pre = pre.next;
            index++;
            if(pre.val == val){
    
    
                break;
            }
        };

        let previous;
        let current = head;
        for(let j=0;j<index;j++){
    
    
            previous = current;
            current = current.next;
        }
        previous.next = current.next;

        return head;
    }

};

3. 链表中倒数第k个节点

  • 题目
    • 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点
  • 解题思路
    • 设置一个指针res,初始值指向head
    • 用head指针遍历链表,遍历k个结点后(含第一个结点),res跟着往后遍历,当遍历结束时,res即为结果
    • 若链表长度小于k,res没有发生变化,返回head
  • 代码实现
var getKthFromEnd = function(head, k) {
    
    
    let res = head;
    while (head.next) {
    
    
        k--;
        if (k <= 0) {
    
    
            res = res.next;
        } 
        head = head.next;
    }
    return res;
};

4. 反转链表

  • 题目
    • 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点
  • 解题思路
    • 利用3个变量在循环过程中记录最后3种信息
    • cur游标,一直往后循环,最后会为null
    • prev记录前一个节点
    • oldNext,变更方向时,需要先用oldNext记住改变前的next节点,否则无法向后循环
  • 代码实现
var reverseList = function(head) {
    
    
    var prev = null,cur=head,temp;
    while(cur){
    
    
        temp = cur.next;//修改前先记住下一个节点
        cur.next = prev; //改别指向,第一个节点prev是null,
        prev = cur; //记录前一个节点,供下次循环使用
        cur = temp; // cur通过temp指向下一节点
    }
    return prev;//cur会多循环直到null
};

5. 复杂链表的复制

  • 题目
    • 请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null
  • 解题思路
    • 创建一个 Map 对象存储创建过的节点
    • 在递归函数里创建新的节点,把这个节点加入到 Map 里面去
    • 使用递归函数设置设个节点的 next 和 random 指向,返回复制的节点
    • 调用递归函数
  • 代码实现
var copyRandomList = function(head) {
    
    
  const visited = new Map()

  function dfs(head) {
    
    
    if(head === null) return null
    if(visited.has(head)) return visited.get(head)

    const copy = new Node(head.val)
    visited.set(head, copy)
    copy.next = dfs(head.next)
    copy.random = dfs(head.random)
    return copy
  }
  
  return dfs(head)
};

6. 两个链表的第一个公共节点

  • 题目
    • 输入两个链表,找出它们的第一个公共节点
  • 解题思路
    • 开辟哈希表 map。key 是节点,value 是 boolean,代表节点是否出现过
    • 对 list1 进行遍历,设置 map[节点]=true
    • 对 list2 进行遍历,如果节点在 map 中出现过,那么说明这是两个链表的公共节点,返回
  • 代码实现
var getIntersectionNode = function(headA, headB) {
    
    
    const map = new Map();
    let node = headA;
    while (node) {
    
    
        map.set(node, true);
        node = node.next;
    }

    node = headB;
    while (node) {
    
    
        if (map.has(node)) return node;
        node = node.next;
    }
    return null;
};

哈希表

1. 最长不含重复字符的子字符串

  • 题目
    • 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度
  • 解题思路
    • 准备 2 个指针 i、j,i 指向窗口左边,j 指向右边
    • 指针每次可以向前“滑动”一个位置,它们之间的区域就是“窗口”
  • 整体流程
    • 准备哈希表 map。key 是 char,value 是 boolean,代表字符 char 是否出现在滑动窗口内
    • i 和 j 初始化为 0,结果 ans 初始化为 0
    • 检查s[j]是否出现过:
    • 没有出现过,扩大窗口:记录s[j],指针 j 向右滑动一格,更新 ans
    • 出现过,缩小窗口:指针 i 向右移动一格,map[s[i]]更新为 false
    • 如果 i 和 j 没有越界,回到 step3,否则返回 ans
  • 代码实现
var lengthOfLongestSubstring = function(s) {
    
    
    const length = s.length;
    const map = {
    
    }; // char => boolean 代表着char是否在目前的窗口内
    let i = 0,
        j = 0;
    let ans = 0;
    while (i < length && j < length) {
    
    
        if (!map[s[j]]) {
    
    
            ans = Math.max(j - i + 1, ans);
            map[s[j]] = true;
            ++j;
        } else {
    
    
            // 如果char重复,那么缩小滑动窗口,并更新对应的map
            map[s[i]] = false;
            ++i;
        }
    }

    return ans;
};

2. 第一个只出现一次的字符

  • 题目
    • 在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母
  • 解题思路
    • 使用lastIndexOf和indexOf
    • 从前面找和从后面找,找到的下标一致,说明是唯一的
  • 代码实现
var firstUniqChar = function(s) {
    
    
    if (s == '') return " ";
    for (let i = 0 ; i < s.length; i++) {
    
    
        if (s.lastIndexOf(s[i]) === s.indexOf(s[i])) {
    
    
            return s[i]
        }
    }
    return " ";
};

1. 重建二叉树

  • 题目
    • 输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字
  • 解题思路
    • 因为前序遍历的第一个元素就是当前二叉树的根节点
    • 那么,这个值就可以将中序遍历分成 2 个部分
    • 最后,根据左右子树,继续递归即可
  • 代码实现
var buildTree = function(preorder, inorder) {
    
    
    if (!preorder.length || !inorder.length) {
    
    
        return null;
    }

    const rootVal = preorder[0];
    const node = new TreeNode(rootVal);

    let i = 0; // i有两个含义,一个是根节点在中序遍历结果中的下标,另一个是当前左子树的节点个数
    for (; i < inorder.length; ++i) {
    
    
        if (inorder[i] === rootVal) {
    
    
            break;
        }
    }

    node.left = buildTree(preorder.slice(1, i + 1), inorder.slice(0, i));
    node.right = buildTree(preorder.slice(i + 1), inorder.slice(i + 1));
    return node;
};

2. 树的子结构

  • 题目

    • 输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
    • B是A的子结构, 即 A中有出现和B相同的结构和节点值
  • 解题思路

    • isSubStructure 的职能:判断 B 是否是 A 的子结构。是,返回 true;否则,尝试 A 的左右子树
    • isSubTree 的职能:封装“判断 B 是否是 A 的子结构”的具体逻辑。
  • 代码实现

var isSubStructure = function(A, B) {
    
    
    // 题目约定:约定空树不是任意一个树的子结构
    if (!A || !B) {
    
    
        return false;
    }

    return (
        isSubTree(A, B) ||
        isSubStructure(A.left, B) ||
        isSubStructure(A.right, B)
    );
};

function isSubTree(pRoot1, pRoot2) {
    
    
    // B树遍历完了,说明B是A的子结构
    if (!pRoot2) {
    
    
        return true;
    }
    // A遍历完了,但是B还没有遍历完,那么B肯定不是A的子结构
    if (!pRoot1) {
    
    
        return false;
    }

    if (pRoot1.val !== pRoot2.val) {
    
    
        return false;
    }

    return (
        isSubTree(pRoot1.left, pRoot2.left) &&
        isSubTree(pRoot1.right, pRoot2.right)
    );
}

3. 二叉树的镜像

  • 题目
    • 请完成一个函数,输入一个二叉树,该函数输出它的镜像
  • 解题思路
    • 从上到下,依次交换每个节点的左右节点
  • 代码实现
var mirrorTree = function(root) {
    
    
    if (!root) {
    
    
        return null;
    }
    // 交换当前节点的左右节点
    const leftCopy = root.left;
    root.left = root.right;
    root.right = leftCopy;

    // 对左右子树做相同操作
    mirrorTree(root.left);
    mirrorTree(root.right);

    return root;
};

4. 对称的二叉树

  • 题目
    • 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的
  • 解题思路
    • 从根节点开始的左右子树分别相等
    • 左子树的左节点=右子树的右节点
    • 左子树的右节点=右子树的左节点
  • 代码实现
var isSymmetric = function(root) {
    
    
  if(root==null) return true;
  return dfs(root.left,root.right);

  function dfs(p,q){
    
    
      if(!p || !q) return !p&&!q;
      if(p.val!==q.val) return false;
      return dfs(p.left,q.right) && dfs(p.right,q.left);
  }
};

5. 序列化二叉树

  • 题目

    • 请实现两个函数,分别用来序列化反序列化二叉树
  • 解题思路

    • 使用广度优先(BFS)遍历所有节点(包括空节点)
  • 序列化流程如下

    • 初始化字符串 res
    • 初始化队列 queue,将 root 放入队列
    • 检查队列是否为空:
      • 队列不为空:取出队首节点,如果节点为 null,那么 res 更新为 res + ‘#,’;如果节点不是 null,那么res 更新为 res + val,并且将节点的左右节点依次加入 queue。继续循环
      • 队列为空:结束循环
    • 返回"[" + res + “]”
  • 反序列化流程如下

    • 去掉字符串 res 前后的[和],并将其按照,逗号切分得到数组 nodes
    • 初始化队列 queue,放入 nodes 的第一个值对应的节点,nodes 弹出第一个值
    • 检查队列是否为空:
      • 队列不为空。从 queue 取出队首元素。从 nodes 中取出第一个值和第二值,依次处理。继续循环
      • 队列为空。结束循环
    • 返回根节点
  • 反序列化函数的设计关键是:数组 nodes 取出元素的顺序和原二叉树层序遍历的顺序是对应的

  • 代码实现

// 序列化
var serialize = function(root) {
    
    
    if (!root) {
    
    
        return "[]";
    }

    let res = "";
    let node = root;
    const queue = [node];
    while (queue.length) {
    
    
        const front = queue.shift();
        if (front) {
    
    
            res += `${
      
      front.val},`;
            queue.push(front.left);
            queue.push(front.right);
        } else {
    
    
            res += "#,";
        }
    }

    res = res.substring(0, res.length - 1); // 去掉最后一个逗号

    return `[${
      
      res}]`;
};


// 反序列化
var deserialize = function(data) {
    
    
    if (data.length <= 2) {
    
    
        return null;
    }

    const nodes = data.substring(1, data.length - 1).split(",");
    const root = new TreeNode(parseInt(nodes[0]));
    nodes.shift();

    const queue = [root];
    while (queue.length) {
    
    
        const node = queue.shift();
        // 第一个是左节点,节点为空,直接跳过
        const leftVal = nodes.shift();
        if (leftVal !== "#") {
    
    
            node.left = new TreeNode(leftVal);
            queue.push(node.left);
        }
        // 第二个是右节点,节点为空,直接跳过
        const rightVal = nodes.shift();
        if (rightVal !== "#") {
    
    
            node.right = new TreeNode(rightVal);
            queue.push(node.right);
        }
    }

    return root;
};

6. 二叉搜索树的第 k 大节点

  • 题目
    • 给定一棵二叉搜索树,请找出其中第k大的节点
  • 解题思路
    • 中序遍历的顺序是左根右,是单调递增的序列;求第k大的数就是就倒数第k个元素的值
    • 最终返回的倒数第k个元素就是arr.length-1-k+1 ,即以arr.length-k为下标的元素
  • 代码实现
var kthLargest = function(root, k) {
    
    
   //中序遍历的模板代码
   let arr=[];
   function dfs(node){
    
    
     if(!node) return;
     dfs(node.left);
     arr.push(node.val);
     dfs(node.right);
   }
   dfs(root);
   return arr[arr.length-k];
};

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

  • 题目

    • 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先
    • 最近公共祖先的定义
      • 对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)
  • 解题思路

    • 由于lowestCommonAncestor(root, p, q)的功能是找出以root为根节点的两个节点p和q的最近公共祖先。 我们考虑:
      • 如果p和q分别在root两侧,那么root就是最近公共祖先。我们使用(root.val - p.val) * (root.val - q.val) <= 0来判断即可
      • 否则的话(p和q在某一颗子树上),我们继续判断p是否在左子树。如果在(p在q一定也在),我们返回递归去左子树找。如果不在我们返回递归去右子树找
  • 代码实现

var lowestCommonAncestor = function(root, p, q) {
    
    
    if ((root.val - p.val) * (root.val - q.val) <= 0) return root
    if (p.val < root.val) return lowestCommonAncestor(root.left, p, q)
    return lowestCommonAncestor(root.right, p, q)
};

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

  • 题目
    • 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先
    • 最近公共祖先的定义
      • 对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)
  • 解题思路
    • 由于lowestCommonAncestor(root, p, q)的功能是找出以root为根节点的两个节点p和q的最近公共祖先。 我们考虑:
      • 如果p和q分别是root的左右节点,那么root就是我们要找的最近公共祖先
      • 如果root是None,说明我们在这条寻址线路没有找到,我们返回None表示没找到
      • 我们继续在左右子树执行相同的逻辑。
      • 如果左子树没找到,说明在右子树,我们返回lowestCommonAncestor(root.right, p , q)
      • 如果右子树没找到,说明在左子树,我们返回lowestCommonAncestor(root.left, p , q)
      • 如果左子树和右子树分别找到一个,我们返回root
  • 代码实现
var lowestCommonAncestor = function(root, p, q) {
    
    
  if (!root || root === p || root === q) return root;
  const left = lowestCommonAncestor(root.left, p, q);
  const right = lowestCommonAncestor(root.right, p, q);
  if (!left) return right; // 左子树找不到,返回右子树
  if (!right) return left; // 右子树找不到,返回左子树
  return root;
};

排序

1. 把数组排成最小的数

  • 题目
    • 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个
  • 解题思路
    • 在 js 中,可以通过参数将自定义的「排序依据」作为函数传入 sort 中,这个函数的逻辑是:
      • 如果 a + b < b + a,说明 ab 比 ba 小,a 应该在 b 前面,返回-1
      • 如果 a + b > b + a,说明 ab 比 ba 大,a 应该在 b 后面,返回 1
      • 如果相等,返回 0
  • 代码实现
var minNumber = function(nums) {
    
    
    nums.sort((a, b) => {
    
    
        const s1 = a + "" + b;
        const s2 = b + "" + a;

        if (s1 < s2) return -1;
        if (s1 > s2) return 1;
        return 0;
    });
    return nums.join("");
};

广度优先搜索

1. 从上到下打印二叉树 I
  • 题目

    • 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印
  • 解题思路

    • 将 root 放入队列
    • 取出队首元素,将 val 放入返回的数组中
    • 检查队首元素的子节点,若不为空,则将子节点放入队列
    • 检查队列是否为空,为空,结束并返回数组;不为空,回到第二步
  • 代码实现

var levelOrder = function(root) {
    
    
    if (!root) {
    
    
        return [];
    }

    const data = [];
    const queue = [root];
    while (queue.length) {
    
    
        const first = queue.shift();
        data.push(first.val);
        first.left && queue.push(first.left);
        first.right && queue.push(first.right);
    }

    return data;
};
2. 从上到下打印二叉树 II
  • 题目

    • 从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行
  • 解题思路

    • 初始化 queue,用于存储当前层的节点
    • 检查 queue 是否为空
      • 如果不为空:依次遍历当前 queue 内的所有节点,检查每个节点的左右子节点,将不为空的子节点放入 queue,继续循环
      • 如果为空:跳出循环
  • 代码实现

var levelOrder = function(root) {
    
    
    if (!root) return [];
    const queue = [root];
    const res = []; // 存放遍历结果
    let level = 0; // 代表当前层数
    while (queue.length) {
    
    
        res[level] = []; // 第level层的遍历结果

        let levelNum = queue.length; // 第level层的节点数量
        while (levelNum--) {
    
    
            const front = queue.shift();
            res[level].push(front.val);
            if (front.left) queue.push(front.left);
            if (front.right) queue.push(front.right);
        }

        level++;
    }
    return res;
};
3. 从上到下打印二叉树 III
  • 题目

    • 请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推
  • 解题思路

    • 借助 level 变量标记层数,当 level 为偶数的时候,镜像翻转遍历结果
  • 代码实现

var levelOrder = function(root) {
    
    
    if (!root) return [];
    const queue = [root];
    const res = [];
    let level = 0; // 代表当前层数
    while (queue.length) {
    
    
        res[level] = []; // 第level层的遍历结果

        let levelNum = queue.length; // 第level层的节点数量
        while (levelNum--) {
    
    
            const front = queue.shift();
            res[level].push(front.val);
            if (front.left) queue.push(front.left);
            if (front.right) queue.push(front.right);
        }
        // 行号是偶数时,翻转当前层的遍历结果
        if (level % 2) {
    
    
            res[level].reverse();
        }

        level++;
    }
    return res;
};

深度优先搜索

1. 二叉树的深度
  • 题目

    • 输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度
  • 解题思路

    • 遍历左子树和右子树取最大深度+1
  • 代码实现

var maxDepth = function(root) {
    
    
    if(!root) return 0;
    return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
};
2. 平衡二叉树
  • 题目

    • 输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树
  • 解题思路

    • 首先获取平衡二叉树的深度,判断左右子树的深度之差是不是<=1,并且需要判断这棵二叉树是不是同时具有左右子树
    • Math.abs(x)=|x| 取某个数的绝对值
  • 代码实现

var isBalanced = function(root) {
    
    
  //获取深度
  function getHeight(root){
    
    
    if(!root) return 0;
    return Math.max(getHeight(root.left),getHeight(root.right))+1;
  }
  
  if(!root) return true;
  //平衡二叉树的判断条件
  return isBalanced(root.left) && isBalanced(root.right)
  && Math.abs(getHeight(root.left)-getHeight(root.right))<=1;
};
3. 二叉树中和为某一值的路径
  • 题目

    • 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径
  • 解题思路

    • 先序遍历+回溯
  • 代码实现

var pathSum = function(root, sum) {
    
    
   var path=[], //保存路径
        res=[];  //保存路经的数组
    /*辅助函数---增加参数列表,用来实现对res,path的引用值的传递,因为res,path为数组,是对象范畴
      本题目中需要根据条件,回溯更新路径path直到符合条件.
    */
    var resuc = function (root, sum, res, path) {
    
    
        if (root) {
    
                        
            //单个节点要做的事
            path.push(root.val);
            if (!root.left && !root.right && sum-root.val == 0) {
    
    
                res.push([...path]);
            }

            //左右子节点递归调用
            resuc(root.left, sum - root.val,res, path);
            resuc(root.right, sum - root.val, res, path);
            path.pop();   //回溯先序遍历一条路径结束,不符合条件时,将最后一个数弹出如5,4,4,7-->5,4,4,-2。
        }
        return res;
    }
    return resuc(root, sum, res, path); 
};

总结

  • 在力扣上看了《剑指Offer》,看大佬们用JavaScript刷题的思路,自己总结一下,以后有时间会更深一步学习一下算法,到时再更新

猜你喜欢

转载自blog.csdn.net/qq_43645678/article/details/106965740