首发于微信公众号《前端成长记》,写于 2019.12.06

背景

本文记录刷题过程中的整个思考过程,以供参考。主要内容涵盖:

  • 题目分析设想
  • 编写代码验证
  • 查阅他人解法
  • 思考总结

目录

Easy

100.相同的树

题目地址

题目描述

给定两个二叉树,编写一个函数来检验它们是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

示例:

  1. 输入: 1 1
  2. / \ / \
  3. 2 3 2 3
  4. [1,2,3], [1,2,3]
  5. 输出: true
  6. 输入: 1 1
  7. / \
  8. 2 2
  9. [1,2], [1,null,2]
  10. 输出: false
  11. 输入: 1 1
  12. / \ / \
  13. 2 1 1 2
  14. [1,2,1], [1,1,2]
  15. 输出: false

题目分析设想

题目直接说了是二叉树,而二叉树的遍历方式有两种:深度优先和广度优先,我就从这两个思路来作答。

编写代码验证

Ⅰ.深度优先

代码:

  1. /**
  2. * @param {TreeNode} p
  3. * @param {TreeNode} q
  4. * @return {boolean}
  5. */
  6. var isSameTree = function(p, q) {
  7. if (p === null && q === null) return true
  8. if (p === null || q === null) return false
  9. if (p.val !== q.val) return false
  10. return isSameTree(p.left, q.left) && isSameTree(p.right, q.right)
  11. };

结果:

  • 57/57 cases passed (52 ms)
  • Your runtime beats 98.81 % of javascript submissions
  • Your memory usage beats 16.66 % of javascript submissions (33.8 MB)
  • 时间复杂度 O(n)n 为节点个数

Ⅱ.广度优先

代码:

  1. /**
  2. * @param {TreeNode} p
  3. * @param {TreeNode} q
  4. * @return {boolean}
  5. */
  6. var isSameTree = function(p, q) {
  7. if (p === null && q === null) return true
  8. if (p === null || q === null) return false
  9. let pQ =[p] // 左侧比较队列
  10. let qQ =[q] // 右侧比较队列
  11. let res = true
  12. while(true) {
  13. if (!pQ.length || !qQ.length) {
  14. res = pQ.length === qQ.length
  15. break
  16. }
  17. // 当前比较节点
  18. let curP = pQ.shift()
  19. let curQ = qQ.shift()
  20. if ((curP && !curQ) || (!curP && curQ) || (curP && curQ && curP.val !== curQ.val)) {
  21. res = false
  22. break
  23. } else {
  24. let pL = curP ? curP.left : null
  25. let pR = curP ? curP.right : null
  26. if (pL || pR) { // 至少一个存在才有意义
  27. pQ.push(pL, pR) // 依次推入比较数组,实际上就是广度优先
  28. }
  29. let qL = curQ ? curQ.left : null
  30. let qR = curQ ? curQ.right : null
  31. if (qL || qR) { // 至少一个存在才有意义
  32. qQ.push(qL, qR) // 依次推入比较数组,实际上就是广度优先
  33. }
  34. }
  35. }
  36. return res
  37. };

结果:

  • 57/57 cases passed (64 ms)
  • Your runtime beats 73.27 % of javascript submissions
  • Your memory usage beats 15.53 % of javascript submissions (33.8 MB)
  • 时间复杂度 O(n)n 为节点个数

查阅他人解法

思路基本上都是这两种,未发现方向不同的解法。

思考总结

一般碰到二叉树的题,要么就深度遍历,要么就广度遍历。深度优先,也叫先序遍历。

101.对称二叉树

题目地址

题目描述

给定一个二叉树,检查它是否是镜像对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

示例:

  1. 1
  2. / \
  3. 2 2
  4. / \ / \
  5. 3 4 4 3

但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

  1. 1
  2. / \
  3. 2 2
  4. \ \
  5. 3 3

说明:

如果你可以运用递归和迭代两种方法解决这个问题,会很加分。

题目分析设想

还是一道二叉树的题,所以常规思路就是遍历操作,深度优先或广度优先都可。镜像对称可以观察到很明显的特点是有相同的根节点值,且每个树的右子树与另一个树的左字数对称相等。深度优先的方式,其实就是递归的思路,符合题目的说明。

编写代码验证

Ⅰ.深度优先

代码:

  1. /**
  2. * @param {TreeNode} root
  3. * @return {boolean}
  4. */
  5. var isSymmetric = function(root) {
  6. function isMirror (l, r) {
  7. if (l === null && r === null) return true
  8. if (l === null || r === null) return false
  9. return l.val === r.val && isMirror(l.left, r.right) && isMirror(l.right, r.left)
  10. }
  11. return isMirror(root, root)
  12. };

结果:

  • 195/195 cases passed (68 ms)
  • Your runtime beats 87.74 % of javascript submissions
  • Your memory usage beats 41.48 % of javascript submissions (35.5 MB)
  • 时间复杂度 O(n)n 为节点个数

Ⅱ.广度优先

代码:

  1. /**
  2. * @param {TreeNode} root
  3. * @return {boolean}
  4. */
  5. var isSymmetric = function(root) {
  6. if (root === null) return true
  7. // 初始队列
  8. let q = [root.left, root.right]
  9. // 依次将同级push进队列,每次取两个对称节点进行判断
  10. while(q.length) {
  11. let l = q.shift()
  12. let r = q.shift()
  13. if (l === null && r === null) continue
  14. if (l === null || r === null) return false
  15. if (l.val !== r.val) return false
  16. q.push(l.left, r.right, l.right, r.left)
  17. }
  18. return true
  19. };

结果:

  • 195/195 cases passed (64 ms)
  • Your runtime beats 94.88 % of javascript submissions
  • Your memory usage beats 28.3 % of javascript submissions (35.6 MB)
  • 时间复杂度 O(n)n 为节点个数

查阅他人解法

看到一个有意思的思路,将树按照左中右的顺序输入到数组,加上层数,该数组也是对称的。

Ⅰ.左中右顺序输出数组

代码:

  1. /**
  2. * @param {TreeNode} root
  3. * @return {boolean}
  4. */
  5. var isSymmetric = function(root) {
  6. if (root === null) return true
  7. // 输出数组
  8. let arr = []
  9. search(arr, root, 1);
  10. // 入参分别为输出,节点和层级
  11. function search(output, n, k) {
  12. if (n.left !== null) {
  13. search(output, n.left, k+1)
  14. }
  15. if (n.right !== null) {
  16. search(output, n.right, k + 1);
  17. }
  18. }
  19. //判断是否对称
  20. let i = 0, j = arr.length - 1
  21. while (i < j) {
  22. if (arr[i] != arr[j]) {
  23. return false
  24. }
  25. i++
  26. j--
  27. }
  28. return true
  29. };

结果:

  • 195/195 cases passed (72 ms)
  • Your runtime beats 76.3 % of javascript submissions
  • Your memory usage beats 6.11 % of javascript submissions (36.3 MB)
  • 时间复杂度 O(n)n 为节点个数

思考总结

这道题的大致解法都是遍历节点或者利用队列,只是在递归的细节上会有些差异。左中右输出数组的思路很清奇,虽然效率明显会更低下,但是不失为一种思路。

104.二叉树的最大深度

题目地址

题目描述

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:

给定二叉树 [3,9,20,null,null,15,7]

  1. 3
  2. / \
  3. 9 20
  4. / \
  5. 15 7

返回它的最大深度 3 。

题目分析设想

这道题最基本的思路就是计算出每条子节点的深度,再进行比较。为了提升效率,可以增加同级比对,去除不可能是最长节点的叶节点计算。

所以这里我就用以下几种思路来实现深度优先算法。

  • 递归,直接获取子树最大高度加 1
  • 利用队列,求深度转化为求有多少层

编写代码验证

Ⅰ.递归

代码:

  1. /**
  2. * @param {TreeNode} root
  3. * @return {number}
  4. */
  5. var maxDepth = function(root) {
  6. if (root === null) return 0
  7. // 左侧子树的最大高度
  8. let l = maxDepth(root.left)
  9. // 右侧子树的最大高度
  10. let r = maxDepth(root.right)
  11. return Math.max(l, r) + 1
  12. };

结果:

  • 39/39 cases passed (60 ms)
  • Your runtime beats 99 % of javascript submissions
  • Your memory usage beats 45.77 % of javascript submissions (37.1 MB)
  • 时间复杂度 O(n)n 为节点个数

Ⅱ.利用队列

代码:

  1. /**
  2. * @param {TreeNode} root
  3. * @return {number}
  4. */
  5. var maxDepth = function(root) {
  6. if (root === null) return 0
  7. // 队列
  8. let q = [root]
  9. let dep = 0
  10. while(q.length) {
  11. let size = q.length
  12. dep++
  13. while(size > 0) {
  14. let node = q.shift()
  15. if (node.left !== null) q.push(node.left)
  16. if (node.right !== null) q.push(node.right)
  17. size--
  18. }
  19. }
  20. return dep
  21. };

结果:

  • 39/39 cases passed (68 ms)
  • Your runtime beats 91.33 % of javascript submissions
  • Your memory usage beats 30.1 % of javascript submissions (37.2 MB)
  • 时间复杂度 O(n)n 为节点个数

查阅他人解法

这里看到一个用栈的角度来实现的,取栈高度的最大值,其他的基本都是循环的细节差异,大体思路一致。

Ⅰ.利用栈

代码:

  1. /**
  2. * @param {TreeNode} root
  3. * @return {number}
  4. */
  5. var maxDepth = function(root) {
  6. if (root === null) return 0
  7. // 栈
  8. let s = [{
  9. node: root,
  10. dep: 1
  11. }]
  12. let dep = 0
  13. while(s.length) {
  14. // 先进后出
  15. var cur = s.pop()
  16. if (cur.node !== null) {
  17. let curDep = cur.dep
  18. dep = Math.max(dep, curDep)
  19. if (cur.node.left !== null) s.push({node: cur.node.left, dep: curDep + 1})
  20. if (cur.node.right !== null) s.push({node: cur.node.right, dep: curDep + 1})
  21. }
  22. }
  23. return dep
  24. };

结果:

  • 39/39 cases passed (72 ms)
  • Your runtime beats 81.41 % of javascript submissions
  • Your memory usage beats 66.6 % of javascript submissions (37 MB)
  • 时间复杂度 O(n)n 为节点个数

思考总结

二叉树的操作,一般就是深度优先和广度优先,所以基本上就朝这两个方向上去解,然后进行优化就可以了。

107.二叉树的层次遍历II

题目地址

题目描述

给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

例如:

给定二叉树 [3,9,20,null,null,15,7],

  1. 3
  2. / \
  3. 9 20
  4. / \
  5. 15 7

返回其自底向上的层次遍历为:

  1. [
  2. [15,7],
  3. [9,20],
  4. [3]
  5. ]

题目分析设想

这道题在我看来还是两种方式,深度优先和广度优先。

  • 深度优先,记录下每个节点对应的层数后,按层数反向输出即可
  • 广度优先,记录下每层的节点后反向输出

编写代码验证

Ⅰ.深度优先

代码:

  1. /**
  2. * @param {TreeNode} root
  3. * @return {number[][]}
  4. */
  5. var levelOrderBottom = function(root) {
  6. // 当前层级标识
  7. let idx = 0
  8. let res = []
  9. function levelOrder(node, floor, arr) {
  10. if (node === null) return arr
  11. if(arr[floor]) {
  12. arr[floor].push(node.val)
  13. } else {
  14. arr[floor] = [node.val]
  15. }
  16. levelOrder(node.left, floor + 1, arr)
  17. levelOrder(node.right, floor + 1, arr)
  18. return arr
  19. }
  20. return levelOrder(root, idx, res).reverse()
  21. };

结果:

  • 34/34 cases passed (68 ms)
  • Your runtime beats 77.01 % of javascript submissions
  • Your memory usage beats 34.78 % of javascript submissions (34.7 MB)
  • 时间复杂度 O(n)n 为节点个数

Ⅱ.广度优先

代码:

  1. /**
  2. * @param {TreeNode} root
  3. * @return {number[][]}
  4. */
  5. var levelOrderBottom = function(root) {
  6. if (root === null) return []
  7. // 初始队列
  8. let q = [root]
  9. let res = []
  10. while(q.length) {
  11. // 当前层节点数量
  12. const count = q.length
  13. let curArr = []
  14. for(let i = 0; i < count;i++) {
  15. const node = q.shift()
  16. curArr.push(node.val)
  17. // 将子节点依次推入队列
  18. if (node.left) q.push(node.left)
  19. if (node.right ) q.push(node.right )
  20. }
  21. res.push(curArr)
  22. }
  23. return res.reverse()
  24. };

结果:

  • 34/34 cases passed (64 ms)
  • Your runtime beats 89.2 % of javascript submissions
  • Your memory usage beats 32.3 % of javascript submissions (34.7 MB)
  • 时间复杂度 O(n)n 为节点个数

查阅他人解法

没有看到什么特别的解法,主要都是按 BFS 和 DFS 来处理,要么迭代,要么递归等等。

这里就介绍下别的吧,在第一种解法中我们使用的是前序优先,当然用中序优先或后序优先也可以,下面代码可以说明区别:

  1. // 先序,顺序为 根 -> 左 -> 右
  2. function levelOrder(node, floor, arr) {
  3. if(arr[floor]) {
  4. arr[floor].push(node.val)
  5. } else {
  6. arr[floor] = [node.val]
  7. }
  8. levelOrder(node.left, floor + 1, arr)
  9. levelOrder(node.right, floor + 1, arr)
  10. return arr
  11. }
  12. // 中序,顺序为 左 -> 根 -> 右
  13. function levelOrder(node, floor, arr) {
  14. levelOrder(node.left, floor + 1, arr)
  15. if(arr[floor]) {
  16. arr[floor].push(node.val)
  17. } else {
  18. arr[floor] = [node.val]
  19. }
  20. levelOrder(node.right, floor + 1, arr)
  21. return arr
  22. }
  23. // 后序,顺序为 左 -> 右 -> 根
  24. function levelOrder(node, floor, arr) {
  25. levelOrder(node.left, floor + 1, arr)
  26. levelOrder(node.right, floor + 1, arr)
  27. if(arr[floor]) {
  28. arr[floor].push(node.val)
  29. } else {
  30. arr[floor] = [node.val]
  31. }
  32. return arr
  33. }

思考总结

二叉树的题目就根据情况在深度优先和广度优先中择优选择即可,基本不会有太大的问题。

108.将有序数组转换为二叉搜索树

题目地址

题目描述

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

  1. 给定有序数组: [-10,-3,0,5,9],
  2. 一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
  3. 0
  4. / \
  5. -3 9
  6. / /
  7. -10 5

题目分析设想

这里有两点要注意的:高度平衡二叉树要求每个节点的左右两个子树的高度差的绝对值不超过 1;而二叉搜索树要求左子树上所有节点值小于根节点,右子树上所有节点值大于根节点。

而题目给出的是一个有序的数组,所以可以直接考虑二分后进行处理,我这就直接递归作答:找到根节点,递归生成左右子树。

编写代码验证

Ⅰ.递归

代码:

  1. /**
  2. * @param {number[]} nums
  3. * @return {TreeNode}
  4. */
  5. var sortedArrayToBST = function(nums) {
  6. if (!nums.length) return null
  7. // 中位数,用偏移避免溢出
  8. const mid = nums.length >>> 1
  9. const root = new TreeNode(nums[mid])
  10. root.left = sortedArrayToBST(nums.slice(0, mid))
  11. root.right = sortedArrayToBST(nums.slice(mid + 1))
  12. return root
  13. };

结果:

  • 32/32 cases passed (80 ms)
  • Your runtime beats 70.72 % of javascript submissions
  • Your memory usage beats 29.79 % of javascript submissions (37.8 MB)
  • 时间复杂度 O(n)

查阅他人解法

这里看到另外一种解法,先创建一个平衡二叉树,然后中序遍历树同时遍历数组即可,因为中序遍历出来的刚好是有序数组。

Ⅰ.创建树后中序遍历数组赋值

代码:

  1. /**
  2. * @param {number[]} nums
  3. * @return {TreeNode}
  4. */
  5. var sortedArrayToBST = function(nums) {
  6. if (!nums.length) return null
  7. // 节点总数
  8. let len = nums.length
  9. let root = new TreeNode(-1);
  10. let q = [root]
  11. // 已经创建了根节点
  12. len--
  13. while(len) {
  14. const node = q.shift()
  15. // 左子树
  16. const l = new TreeNode(-1)
  17. q.push(l)
  18. node.left = l
  19. len--
  20. if (len) {
  21. // 右子树
  22. const r = new TreeNode(-1)
  23. q.push(r)
  24. node.right = r
  25. len--
  26. }
  27. }
  28. let i = 0
  29. inorder(root)
  30. function inorder(node) {
  31. if (node === null) return
  32. inorder(node.left)
  33. node.val = nums[i++]
  34. inorder(node.right)
  35. }
  36. return root
  37. };

结果:

  • 32/32 cases passed (72 ms)
  • Your runtime beats 93.4 % of javascript submissions
  • Your memory usage beats 24.12 % of javascript submissions (37.8 MB)
  • 时间复杂度 O(n)

思考总结

这里其实是个逆向思维,之前是二叉树输出数组,现在变成数组转成二叉树。刚好可以翻一下前序中序和后序的区别,这里中序就可以了。不过这道题我还是更推荐递归二分求解。

(完)


本文为原创文章,可能会更新知识点及修正错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验

如果能给您带去些许帮助,欢迎 ⭐️star 或 ✏️ fork

(转载请注明出处:https://chenjiahao.xyz)

【Leetcode 做题学算法周刊】第五期的更多相关文章

  1. 【Leetcode 做题学算法周刊】第二期

    首发于微信公众号<前端成长记>,写于 2019.11.05 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 20 ...

  2. 【Leetcode 做题学算法周刊】第四期

    首发于微信公众号<前端成长记>,写于 2019.11.21 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 67 ...

  3. 【Leetcode 做题学算法周刊】第一期

    首发于微信公众号<前端成长记>,写于 2019.10.28 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 1. ...

  4. 【Leetcode 做题学算法周刊】第三期

    首发于微信公众号<前端成长记>,写于 2019.11.13 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 35 ...

  5. 【Leetcode 做题学算法周刊】第六期

    首发于微信公众号<前端成长记>,写于 2019.12.15 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 11 ...

  6. 【Leetcode 做题学算法周刊】第七期

    首发于微信公众号<前端成长记>,写于 2020.01.15 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 12 ...

  7. 【Leetcode 做题学算法周刊】第八期

    首发于微信公众号<前端成长记>,写于 2020.05.07 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 15 ...

  8. LeetCode做题笔记之动态规划

    LeetCode之动态规划 时间有限只做了下面这几道:70.338.877.96.120.95.647,后续会继续更新 70:爬楼梯 先来道简单的练练手,一道经典的动态规划题目 可以采用动态规划的备忘 ...

  9. C#LeetCode刷题-贪心算法

    贪心算法篇 # 题名 刷题 通过率 难度 44 通配符匹配   17.8% 困难 45 跳跃游戏 II   25.5% 困难 55 跳跃游戏   30.6% 中等 122 买卖股票的最佳时机 II C ...

随机推荐

  1. 如何解决jpa 要求column 名称单词必须用下划线

    [转]:http://www.jeesns.cn/article/detail/6657 先引出轮子http://blog.csdn.net/54powerman/article/details/76 ...

  2. Android Jni开发,报com.android.ide.common.process.ProcessException: Error configuring 错误解决方案

    今天在练习JNI项目时,Android studio版本为:3.1.3,Gradle版本为4.4.由于Android studio 3.X弃用了 android.useDeprecatedNdk=tr ...

  3. 关于c++函数里面return的用法,关于调用的讲解

    与下面的图片对比一下 可以看见在int b = test();d的时候cout<<"hello";就被调用了: cout<<b;只是返回return a的值 ...

  4. Spring boot如何快速的配置多个Redis数据源

    简介 redis 多数据源主要的运用场景是在需要使用多个redis服务器或者使用多个redis库,本文采用的是fastdep依赖集成框架,快速集成Redis多数据源并集成lettuce连接池,只需引入 ...

  5. 鲲鹏性能优化十板斧(二)——CPU与内存子系统性能调优

    1.1 CPU与内存子系统性能调优简介 调优思路 性能优化的思路如下: l   如果CPU的利用率不高,说明资源没有充分利用,可以通过工具(如strace)查看应用程序阻塞在哪里,一般为磁盘,网络或应 ...

  6. Python不再为字符集编码发愁,使用chardet轻松解决你的困扰。

    欢迎添加华为云小助手微信(微信号:HWCloud002 或 HWCloud003),输入关键字"加群",加入华为云线上技术讨论群:输入关键字"最新活动",获取华 ...

  7. 产品vs程序员:你知道www是怎么来的吗?

    精彩回顾: 我是一个explorer的线程 我是一个杀毒软件线程 我是一个IE浏览器线程 比特宇宙-TCP/IP的诞生 Unix.Linux.Windows三大帝国集团发表<关于比特宇宙推进经贸 ...

  8. Unity3D for iOS初级教程:Part 3/3(上)

    转自:http://www.cnblogs.com/alongu3d/archive/2013/06/01/3111738.html 欢迎来到第三部分,这是Unity 3D for iOS初级系列教程 ...

  9. iOS开发-CoreMotion框架

    转自: CoreMotion是一个专门处理Motion的框架,其中包含了两个部分 加速度计和陀螺仪,在iOS4之前加速度计是由 UIAccelerometer 类 来负责采集数据,现在一般都是用Cor ...

  10. Spring Boot结合Mybatis

    pom文件: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http ...