前端算法日记
前言
前端也是要刷算法的呀= =
选自 leetcode hot 100 和 剑指 offer
1.两数之和 【哈希表】
给定一个整数数组nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9
,返回 [0, 1]
。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
进阶:你可以想出一个时间复杂度小于O(n2)
的算法吗?
这道题对前端来说考察的应该是用 map 方法来操作数据的问题。
回顾一下 map 的三个方法map.get(key)
用于返回 key 对应的 value 值map.set(key,value)
设置 key 和 value 值map.has(key)
返回 key 是否存在
那么这道题的思路就是 先遍历数组 判断 target 和数字的差值是否在 map 里面,如果是则返回 key 和当前 i,否则存入 map
1 | var twoSum = function (nums, target) { |
其实不用到 map 这个 api 也是可以做的,并且用时还快一点
1 | var twoSum = function (nums, target) { |
2.两数相加 【链表】【链表合并】
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
这个就很类似于链表的合并,但是这个相较于合并特殊的一点在于它不能设置 val 为 0 而是要设置 next 为一个新的节点。
1 | var addTwoNumbers = function (l1, l2) { |
3.无重复字符的最长字串 【API】
思路就是用 include 方法循环判断是否存在这个字符 如果存在就删去,如果不存在就 push
然后判断 maxlength
1 | var lengthOfLongestSubstring = function (s) { |
4.寻找两个正序数组的中位数 【API】
思路就是合并数组 然后 sort 排序,然后判断奇偶来写。
1 | var findMedianSortedArrays = function (nums1, nums2) { |
19.删除链表的倒数第 N 个结点 【链表】【链表删除】
思路就是快慢指针,快指针先走 n 步,然后快慢一起遍历就能找到要删除节点的前驱节点和后继节点。
注意细节问题
1 | var removeNthFromEnd = function (head, n) { |
20.有效的括号 【栈】
思路 利用栈 先定义括号的数据结构,然后进站的是右括号 如果和出栈的对应 那么最后栈空
1 | var isValid = function (s) { |
21.合并两个有序链表 【链表】【链表合并】
思路
对链表进行穿针
穿针的步骤是把cur的下一位标志为当前最小val,然后让这个链表进位,最后cur进位
结束后多余出来的部分直接合并
1 | var mergeTwoLists = function (list1, list2) { |
11. 盛最多水的容器 【双指针】
看到涉及前后的问题 使用双指针 指向头尾
- 高度是相对小的指针的值 宽度是下标的差值
- 指针移动的条件是相对小的那边移动
1 | var maxArea = function (height) { |
15.三数之和 【双指针】
思路就是双指针 头指针和尾指针
- 先对数组进行排序
- 遍历数组,还有个 next 指针 为
i+1
- 头指针 next 指针 尾指针 用
while
来判断,终止条件是 next 等于尾指针
- 若它们代表的元素相加若等于 0,将三个指针代表的元素入数组,并将 next 指针指向下一位,如果下一位和上一位的数字相同则跳过 next 继续指向下一位
- 如果小于 0 next 指针++
- 如果大于 0 尾指针—
特别注意 如果前一项等于后一项 那么直接跳过当前项
代码
1 | /** |
16.最接近的三数之和 【双指针】
思路 和三数之和类似 多了个比较 最接近其实就是绝对值最小
代码
1 | /** |
18.四数之和 【双指针】
思路 和三数差不多 但是多了层循环
代码
1 | /** |
53.最大子数组和 【动态规划】
动态规划 但是是从前往后
正负收益 如果是相减是负收益 那么不要这个 取当前项
优化:不使用 dp 数组来维护这些变量 直接用一个 sum 变量来代替
1 | var maxSubArray = function (nums) { |
0 的地方是表示没有变化
70.爬楼梯 【动态规划】
动态规划 状态方程是f[n]= f[n-1]+f[n-2]
1 | var climbStairs = function (n) { |
94.二叉树的中序遍历 【二叉树】【中序遍历】
注意在函数里面递归你的二叉树
1 | var inorderTraversal = function (root) { |
101.对称二叉树 【二叉树】【递归】
左右节点都存在
左右节点相等
左节点的左等于右节点的右
左节点的右等于右键点的左
即为镜像
1 | var isSymmetric = function (root) { |
104. 二叉树的最大深度 【二叉树】【深度优先】
最大深度用深度优先遍历
然后注意判断的时机
代码
1 | var maxDepth = function (root) { |
121. 买卖股票的最佳时机 【动态规划】
先提出一个概念 收益 这里的话是前后元素的差值
这里涉及到一个问题 其实有时候不需要知道买进卖出的价格 我们只需要知道在这个过程中,收益的幅度,也就是收益为负的时候,刷新这个收益为 0,收益为正的时候,和最大收益比较并考虑是否刷新最大收益。
代码
1 | var maxProfit = function (prices) { |
136. 只出现一次的数字 【位运算】
利用异或的性质
- 0 异或任何数都返回那个数字
- 两个数字异或 相同 0 不同 1
代码
1 | var singleNumber = function (nums) { |
226. 翻转二叉树 【二叉树】
代码
1 | var invertTree = function (root) { |
141. 环形链表 【链表】【快慢指针】【环形链表】
快慢指针 如果是环形 快指针走两步 慢指针走一步 最后肯定会相遇
1 | var hasCycle = function (head) { |
155. 最小栈 【栈】
两个栈 一个用于存放最小的数据集合 一个用于普通存放
1 | var MinStack = function () { |
160. 相交链表 【链表】
连接 headA 和 headB 使得它们总路程相同
那么可以看 此时总路程相同 速度相同 如果有相交的点 那么最后肯定会到达该点
假设公共路径是 c 第一条公共路径之前是 a 第二条是 b
那么第一条路径就是 a+c+b+c
第二条就是 b+c+a+c
当第一条走过 a+c+b 的时候 第二条也走过了 b+c+a 此时下一步的 c 就是交点或者终点
1 | var getIntersectionNode = function(headA, headB) { |
448. 找到所有数组中消失的数字 【API】
思路 set 去重之后找到利用 set 的 has 特性 如果不存在该数字就把数组原地的nums[count]
修改为 i ,最后分割数组即可。
1 | var findDisappearedNumbers = function (nums) { |
169. 多数元素 【API】
code
1 | var majorityElement = function (nums) { |
283. 移动零 【】
code
1 | var moveZeroes = function (nums) { |
617. 合并二叉树 【二叉树】【合并】
code
1 | var mergeTrees = function (root1, root2) { |
543. 二叉树的直径 【二叉树】
代码
1 | var diameterOfBinaryTree = function (root) { |
461. 汉明距离 【API】
code
1 | var hammingDistance = function (x, y) { |
338. 比特位计数 【API】
code
1 | var countBits = function (n) { |
剑指 Offer 10- I. 斐波那契数列 【动态规划】
经典动态规划 注意栈溢出。
1 | var fib = function (n) { |
剑指 Offer 10- II. 青蛙跳台阶问题 【动态规划】
code
1 | var numWays = function (n) { |
剑指 Offer 11. 旋转数组的最小数字 【API】
代码
1 | var minArray = function (numbers) { |
剑指 Offer 15. 二进制中 1 的个数 【API】
code
1 | var hammingWeight = function (n) { |
剑指 Offer 17. 打印从 1 到最大的 n 位数 【】
code
1 | var printNumbers = function (n) { |
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 【双指针】
双指针 左指针维护奇数数组 右指针维护偶数数组
1 | var exchange = function (nums) { |
剑指 Offer 27. 二叉树的镜像 【二叉树】
反转二叉树
1 | var mirrorTree = function (root) { |
剑指 Offer 28. 对称的二叉树 【二叉树】
对称二叉树
1 | var isSymmetric = function (root) { |
剑指 Offer 40. 最小的 k 个数 【API】
代码
1 | var getLeastNumbers = function (arr, k) { |
剑指 Offer 42. 连续子数组的最大和 【动态规划】
收益和当前值比较 取最大 得到新收益
新收益和总收益比较 刷新总收益
注意初始化的时候总收益和收益都是第一个元素 避免只有一个元素的时候
1 | var maxSubArray = function (nums) { |
剑指 Offer 50. 第一个只出现一次的字符 【API】
巧用 indexOf 的第二个属性
从 xx 位置开始查找
所以如果查了第一次 发现第一次后面还有该数就说明重复
1 | var firstUniqChar = function (s) { |
剑指 Offer 39. 数组中出现次数超过一半的数字 【API】
中位数
1 | var majorityElement = function (nums) { |
剑指 Offer 53 - I. 在排序数组中查找数字 I 【API】
code
1 | var search = function (nums, target) { |
剑指 Offer 53 - II. 0 ~ n-1 中缺失的数字 【API】
code
1 | var missingNumber = function (nums) { |
剑指 Offer 57. 和为 s 的两个数字 【双指针】
code 双指针
1 | var twoSum = function (nums, target) { |
剑指 Offer 58 - I. 翻转单词顺序 【API】
注意条件
code
1 | var reverseWords = function (s) { |
剑指 Offer 57 - II. 和为 s 的连续正数序列 【双指针】
双指针 滑动窗口
初始化左右指针数值为 1,和为 0
循环条件 左指针小于目标值一半(比如说目标值 15 一半就是 7.5 左最大只能到 7)
当和小于目标值的时候 需要扩大右边界 此时和+=
右指针 右指针++
当和大于目标值 需要扩大左边界 此时和-=
左指针 左指针++
当和等于目标值 循环嵌入值
1 | var findContinuousSequence = function (target) { |
剑指 Offer 58 - II. 左旋转字符串 【API】
code
1 | var reverseLeftWords = function (s, n) { |
剑指 Offer 61. 扑克牌中的顺子
code
1 | var isStraight = function (nums) { |
剑指 Offer 62. 圆圈中最后剩下的数字 【约瑟夫环】
约瑟夫环
code
1 | var lastRemaining = function(n, m) { |
剑指 Offer 65. 不用加减乘除做加法 【位运算】
先异或计算进位,再位与进位
推导过程 eg 0001+0011=0100
1 | 0001^0011 = 0010 //2 |
所以代码是
1 | var add = function (a, b) { |
剑指 Offer 29. 顺时针打印矩阵 【API】【矩阵】
定义一个数组 res 为空
四个方向,一次循环 右下左上
第一步 右 res 直接 concat 目标数组 shift 的内容
第二步 下 循环遍历目标数组 删除目标数组中每一个数组的最后一位并加入 res
注意此时要判断数组是否还存在。
第三步 左 取出目标数组最后一行 pop 后反转加入 res 此时 res 需要 concat
注意此时要判断数组是否还存在。
第四步 上 剩余的最后一层到第一层
注意此时要判断数组是否还存在。
1 | var spiralOrder = function (matrix) { |
剑指 Offer 32 - II. 从上到下打印二叉树 II 【二叉树】
bfs + map 记录
1 | var levelOrder = function (root) { |
剑指 Offer 55 - I. 二叉树的深度 【二叉树】【bfs】
code bfs
1 | var maxDepth = function (root) { |
剑指 Offer 54. 二叉搜索树的第 k 大节点 【二叉树】【二叉搜索树】
中序遍历的结果是升序数组 反过来就是降序 找 k 个就行
1 | var kthLargest = function (root, k) { |
剑指 Offer 55 - II. 平衡二叉树 【二叉树】【平衡二叉树】
后序遍历,自下而上,如果遇到了不符合的情况直接剪枝返回-1
1 | var isBalanced = function (root) { |
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 【二叉树】【二叉搜索树】【最近公共祖先】
二叉搜索树的概念:
左子树的值恒小于根节点的值
右子树的值恒大于根节点的值
因此对于二叉搜索树的最近公共祖先有三种情况
- 如果左小于根 右大于根 则返回根节点
- 如果左右小于根 那么第一个符合值相等条件的就是公共祖先
- 如果左右大于根 那么第一个符合值相等条件的就是公共祖先
注意 传入的是节点 不是节点值
1 | var lowestCommonAncestor = function (root, p, q) { |
剑指 Offer 68 - II. 二叉树的最近公共祖先 【二叉树】【最近公共祖先】
isLeft 代表左树中有 q 或 p isRight 代表右树中有 q 或 p
isNode 表示此节点为 q 或 p
当左中右其中两个为真 那么此节点就是公共祖先
1 | var lowestCommonAncestor = function (root, p, q) { |
中等题
31. 下一个排列 【排列组合】
遍历空出最后两个位置
前后一对,定义 a 为 nums[i]
如果靠后 a 的那个数字小于 a
那么查看此时靠后 a 的数字是否是最后一个 如果是 直接交换它和 a
如果不是 则定义 k 等于 i+1 找到比 a 大的第一个数字 因为后面的数字都是降序排列 所以用一个 while 就能搞定 (while(a<nums[k+1]){k++})
然后交换 k 和 a
再将 a 后面的数字做升序排列
如果 i 已经是 0 了
那么 nums 反转
1 | var nextPermutation = function (nums) { |
153. 寻找旋转排序数组中的最小值 【双指针】
注意 mid 可以写成left+((right-left)>>1)
如果 left 值等于 right 值 说明升序 直接返回 nums0
乱序的情况下:
如果 mid 大于 left,right 就来到左边界
如果 mid 小于 left,left 就来到右边界
如果 mid》mid+1 说明 mid+1 是最小值
如果 mid《mid-1 说明 mid 是最小值
1 | var findMin = function (nums) { |
33. 搜索旋转排序数组 【双指针】
边界条件是 mid===target
如果 left《=mid
且 target》left 小于 mid 说明在左侧 right=mid-1 否则在右侧 left=mid+1
如果 left》mid
且 target》mid 小于 right 说明在右侧 left =mid+1 否则在左侧 right=mid-1
1 | var search = function (nums, target) { |
81. 搜索旋转排序数组 II 【双指针】
去重思路
如果 numsleft===numsmid 则++left continue
1 | var search = function (nums, target) { |
34. 在排序数组中查找元素的第一个和最后一个位置 【双指针】
找到之后判断前面和后面的数
1 | var searchRange = function (nums, target) { |
39. 组合总和 【排列组合】
回溯
1 | var combinationSum = function (nums, target) { |
46. 全排列 【排列组合】【回溯】
回溯 带 used 注意使用方法是new Array(nums.length).fill(false)
1 | var permute = function (nums) { |
48. 旋转图像 【矩阵】
先对角线交换然后反转。
1 | var rotate = function (matrix) { |
92. 反转链表 II 【链表】【反转链表】
截取反转再补上注意环
1 | var reverseBetween = function (head, left, right) { |
165. 比较版本号 【API】
1 | var compareVersion = function (version1, version2) { |
647. 回文子串 【回文】
我们要知道一个串是否是回文子串
从 i 开头 j 结尾开始
此时 charAt 的 i 和 j 相等 就得判断 i+1 和 j-1 是否是相等
或者说 当 j-i 小于等于 2 的时候 这个时候足够小 就可以认为它是回文子串。
所以得出 dp 的条件是dp[i][j] = (j-i<=2)||dp[i+1][j-1]
但是我们要计算dp[i+1][j-1]
直接从上到下是不行的,必须 i 从下到上,因此代码为
1 | var countSubstrings = function (s) { |
最长回文子串 【回文】
回文子串的做法加判断
res 初始化为 s0
如果 dp 为真且j-i+1>res.length
的情况下
返回res = s.slice(i,j+1)
1 | var longestPalindrome = function (s) { |
22. 括号生成 【DFS】
dfs 传入 l,r 和 str 初始的时候 l 和 r 都是 n str 是空串
前两者用于判断左右括号是否害存在
如果左大于 0 l-1 空串加入左括号
如果左小于右 那么右括号才能进组 r-1 加入右括号
结束的条件是当空串的长度为 2n 的时候
1 | var generateParenthesis = function (n) { |
55. 跳跃游戏 【贪心】【动态规划】
贪心,不必比较每次跳多少,而是是否超过 cover 的距离,如果大于 cover 刷新,如果 cover 已经超出长度则返回 true
跳跃的距离是nums[i]+i
1 | var canJump = function (nums) { |
105. 从前序与中序遍历序列构造二叉树 【二叉树】
如果中序遍历的长度不存在或者中序遍历的过程没有值直接返回 null
从前序遍历找到根节点(第一个或者 shift)
此时在中序中找到这个节点,然后新建树
根据中序遍历分类出左子树和右子树
之后左子树加入这个节点之前,右子树在该节点之后
注意新建节点:new TreeNode(xxx)
1 | var buildTree = function (preorder, inorder) { |
98. 验证二叉搜索树 【二叉树】【二叉搜索树】
如果中序遍历的时候左子树值小于根节点右子树值大于根节点即可。
1 | var isValidBST = function (root, min = -Infinity, max = Infinity) { |
49. 字母异位词分组 【哈希表】
哈希表
如何判断abc
和bca
相等关键在于"bca".split("").sort().toString() //a,b,c
用 map 维护 key 为上述字符串,value 为结果数组的结果集
最后返回[...map.values()]
即可
1 | var groupAnagrams = function (str) { |
56. 合并区间 【区间】
先对数组进行排序 注意这里是 sort 每个数组的第一位 避免出现[1,4],[0,4]
这样的比较 正确应该是比较[0,4],[1,4]
然后取数组的第一个作为基准 temp 之后遍历数组 如果 temp[1]比数组当前项 item 的[0]要大则有交集,
再取两者的右边界最大值为 temp[1]
如果没有交集 则 temp 入 res temp 和 item 交换
最后一个也要入 res
思路是对数组的第一个元素进行排序,排序之后的数组取出第一个数组为基准,如果基准数组的末尾元素大于等于下一个比较元素的头元素,说明产生了交集,然后开始合并,合并的第一个元素默认是基准数组的第一位(因为已经排序),第二个元素就是基准数组和下一个比较数组的末尾元素更大的部分。不产生交集,就将基准数组入栈,下一个比较数组作为基准。最后还要再入栈一次,因为先前入栈的都是基准数组。
1 | var merge = function (intervals) { |
62. 不同路径 【路径】【动态规划】
用一维数组维护数组
1 | 1 1 1 |
如下:
1 | 1 1 1 |
code
1 | let dp = new Array(m); |
78. 子集 【回溯】
回溯
传递空数组 path,长度 l,开始位置 start。
遍历数组传递 初始长度是 i,开始位置是 0 注意 i 要小于等于原数组长度(比如说 123 才能取到 123 否则只有 12 1 这种情况)
当数组长度等于长度的时候 push 到 res 数组里面然后返回(截至条件)
从 start 开始遍历数组 传递 concat 当前 numi 项,长度,和 i+1 作为下次的条件
1 | var subsets = function (nums) { |
79. 单词搜索 【DFS】
- 定二维数组 used 初始化 false
- 双重循环 dfs
- dfs 第一步判断越界情况
- 第二步标记存在或者不等于该单词 false
- 定义 res 传入 dfs 四个方向
- 成功回调 改变 used 状态
1 | var exist = function (board, word) { |
75. 颜色分类【双指针】
左右指针
1 | var sortColors = function (nums) { |
64. 最小路径和 【路径】
一维数组维护
例子
1 | 1 3 1 |
其实是一个一维数组的作用 upset = [0]
状态方程是:Math.min(left,top)+grid[i][j]
注意边界问题
1 | var minPathSum = function (grid) { |
96. 不同的二叉搜索树 【二叉树】【二叉搜索树】
code
1 | var numTrees = function (n) { |
114. 二叉树展开为链表 【二叉树】
code
1 | var flatten = function (root) { |
128. 最长连续序列 【哈希表】
哈希表 建立 set 遇到有 value1-1 就跳过 否则就 while 判断是否有 value+1 有就删除避免重复
code
1 | var longestConsecutive = function (nums) { |
142. 环形链表 II 【链表】【环形链表】
先找到 slow 和 fast 交点 然后 pre 开始出发 和 slow 香蕉则是起点。
1 | var detectCycle = function (head) { |
148. 排序链表 【链表】
code
1 | var sortList = function (head) { |
152. 乘积最大子数组 【】
定义 max,curMax 和 curMin 初始化为 nums0
从 1 开始遍历,curmax,curmin 都乘上 numsi
比较 curmax,curmin,numsi 三者最大值和最小值分别赋给 curmax,curmin
如果 max 小于 curmax 交换
返回 max
1 | var maxProduct = function (nums) { |
287. 寻找重复数 【哈希表】
哈希表
1 | var findDuplicate = function (nums) { |
剑指 Offer 07. 重建二叉树 【二叉树】
根节点是前序的第一个为 top
在中序中 indexof 该节点 p
新建树 tree 根是 top
用 dfs 不断将节点插入 treeleft 和 right 方法是 slice
1 | var buildTree = function (preorder, inorder) { |
剑指 Offer 04. 二维数组中的查找 【二分排序】【API】
方法 1 拍平然后 include
1 | var findNumberIn2DArray = function (matrix, target) { |
方法 2 对每项进行二分
1 | var findNumberIn2DArray = function (matrix, target) { |
剑指 Offer 13. 机器人的运动范围 【DFS】
初始化二维数组,未遍历的为 0
设置 dfs 传入 ij,判断越界,已经遍历则直接 return
否则打上标记 判断分割的数之和是否小于等于 k
如果是则 count++,往下和往右 dfs
1 | var movingCount = function (m, n, k) { |
208. 实现 Trie (前缀树) 【前缀树】
code
1 | var Trie = function () { |
406. 根据身高重建队列 【其他】
先用 sort 排序第一个数字从大到小,如果相同比较第二个数字按小到大
eg
1 | [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]] |
然后用 splice 在 item1 前面添加 item 进行排序
code
1 | var reconstructQueue = function (people) { |
238. 除自身以外数组的乘积 【其他】
eg
1 | pre 1 2 3 4 |
code
1 | var productExceptSelf = function (nums) { |
538. 把二叉搜索树转换为累加树 【二叉搜索树】【二叉树】
BST 中序遍历为升序列,逆向中序遍历为降序列。
使用一个 sum 变量记录累加和,从大到小(树从右到左)遍历一遍,每次加上结点本身的值即可。
1 | var convertBST = function (root) { |
236. 二叉树的最近公共祖先 【二叉树】【最近公共祖先】
新建根 broot dfs 初始化 count
count 增加的条件是当当前值等于 p 或 q 的时候
如果 broot 还不存在
如果此时左子树还有 递归左子树找相同 count+=dfs 左
如果 broot 还不在 就是说左边没有或者只有一个 那么 count+=dfs 右
如果 broot 还不在且 count 找到两个节点 那么回源 root 就是他的公共祖先
1 | var lowestCommonAncestor = function (root, p, q) { |
739. 每日温度 【单调栈】
单调栈
先看看一个单调栈的做法
1 | // 单调递增栈的实现 |
过程是
1 | [3][4][(4, 2)][6][(6, 4)][(6, 5)][(6, 5, 2)][(6, 5, 3)]; |
所以本题 弹出的时候取到 index 然后res[index] = i-index
注意栈放的是下标
1 | var dailyTemperatures = function (nums) { |
279. 完全平方数 【BFS】
bfs
注意 bfs 的技巧是 第一次 for 因为后续要带入新的 length,所以不能写i<xxx.length
而是要写len=xxx.length;i<len
1 | var numSquares = function (n) { |
347. 前 K 个高频元素 【哈希表】
设置 map 对每个元素出现的次数进行维护
然后使用解构和 sort 分类出最大次数的
最后利用 slice+map 返回前 k 高频
1 | var topKFrequent = function (nums, k) { |
二叉树专栏
99. 恢复二叉树 【二叉树】
本来一看,这不就是 BST 的合法性判断然后交换节点吗
于是就写出了这样的代码
1 | const isValidBST = (n,max,min){ |
这样大概过了 3/4 的用例吧,因为这样做前提的条件是交换的节点在根和左或者根和右,假如需要交换的节点是左右就不行了,比如 321,根据上面的方法转换出来的是 123 很明显就不对了。
所以换个思路,既然是 BST,那么 BST 的特性中,可以使用到的还有中序遍历。那么我们只要找到冲突的两个节点,并交换,就能解决问题了
那么出发点就到了怎么找到冲突的节点,我们知道,我们判断整个值是否升序在中序之中,可以用一个数组存取,最后按正常的思维捕获。也可以在遍历的过程中,获取上一次的值,来操作
比如 321 这个顺序,我们一看就知道 3 和 1 是冲突的,交换 3 和 1 即可。
那么我们就可以设置一个 prev 变量为空,如果第一次遍历的时候,prev 没有值,就把 3 给 prev
1 | let prev; |
如果 prev 已经有值了,就拿当前的值和 prev 比较,如果当前值小于 prev,说明这个顺序是降序的,而我们中序是升序的,很明显不符合,此时这个 prev 就是冲突节点,将它放到我们的 first 变量中记录下来。这个 n 就是第二个冲突的节点
1 | else{ |
这里需要注意一点,我们的第一个冲突节点是不能刷新的,第二个冲突节点是可以刷新的。所以我们在创建第一个节点的时候要加一层判断
1 | if (n.val < prev.val) { |
这样继续遍历,就能找到冲突节点 3 和冲突节点 1,交换就可。
总结:关键在于利用中序遍历的特性,去和上一次的值比较,存入第一个冲突节点,刷新第二个冲突节点
完整 code
1 | var recoverTree = function (root) { |
100. 相同的树 【二叉树】
一开始左这个题,emm 不就很简单吗
直接前序遍历开写,然后就发现了一点小问题
比如说[1,null,2]和[1,2]不是相同的树这件事
然后就改代码咯,不要去想一个节点空另外一个节点不是空的情况
如果两个节点都是空说明这两个节点相同,
如果两个节点都不为空,且值相同,且遍历左右子树的结果是 true 就返回真否则假
1 | var isSameTree = function (p, q) { |
当然这题还可以用别的方法,其实就是判断对象是否相等
1 | return JSON.stringify(p) === JSON.stringify(q); |
108. 将有序数组转为二叉搜索树 【二叉树】【二叉搜索树】
这题是关于 BST 的构造的一个题,因为数组是排序好的,所以中间的点就是根节点,然后递归构造即可。注意边界是 nums 为空的时候
1 | var sortedArrayToBST = function (nums) { |
109. 有序链表转二叉搜索树 【二叉树】
链表转数组,之后做法同上
1 | var sortedListToBST = function (head) { |
111. 二叉树最小深度 【二叉树】
求最小深度用 bfs,bfs 的核心思路是队列,可以想象成一个面,从一个点开始发散。
1 | var minDepth = function (root) { |
112. 路径总和 【二叉树】
总和使用 dfs 的方法,记得这里最好采用累加判断的方法
1 | var hasPathSum = function (root, targetSum) { |
113. 路径总和 II 【二叉树】
这道题由于要记录路径,要使用外部的 path 来记录路径,也就是要用到 dfs+回溯。
1 | var pathSum = function (root, targetSum) { |
117. 填充每个节点的下一个右侧节点指针 II 【二叉树】
这题给的是一颗二叉树不是完美二叉树所以不能像之前一样简单的递归了,现在使用 bfs 做层序遍历,所以其实这题的关键就在于 null 指针怎么填充,我们知道在每一层空的时候去插入 null,就是这个队列是空的时候,也就是 for 循环中它的 i+1 大于 len 的时候
1 | // 题目返回的是树,所以不需要数组存取 |
107. 二叉树的层序遍历 II 【二叉树】
这道题就是层序遍历翻转
1 | var levelOrderBottom = function (root) { |
103. 二叉树的锯齿形层序遍历 【二叉树】
加判断
1 | var zigzagLevelOrder = function (root) { |
144. 二叉树的前序遍历 【二叉树】
code
1 | var preorderTraversal = function (root) { |
145. 二叉树的后序遍历【二叉树】
code
1 | var postorderTraversal = function (root) { |
129. 求根节点到叶节点数字之和 【二叉树】
遍历加回溯,遍历一遍树记录节点,到叶子节点就开始解析累加,然后回溯,这里提供两种方法,一种是数组一种是字符串
1 | var sumNumbers = function (root) { |
数组
1 | var sumNumbers = function (root) { |
199. 二叉树的右视图 【二叉树】
基本思路 bfs 层序遍历 类似于那个填充右侧节点,相同的算法,在 i+1 大于等于 len 的节点添加即可
1 | var rightSideView = function (root) { |
二叉树的所有路径 【二叉树】
常规 dfs 题目 也可以采用数组的方式,最后在转为字符串即可
1 | var binaryTreePaths = function (root) { |
331. 验证二叉树的前序序列化 【二叉树】
code 反向 dfs
1 | var isValidSerialization = function (preorder) { |
404. 左叶子之和 【二叉树】
左叶子n.left&&!n.left.left&&!n.left.right
code
1 | var sumOfLeftLeaves = function (root) { |
429. N 叉树的层序遍历【二叉树】
回顾一下二叉树的层序遍历模板
1 | var levelOrder = function (root) { |
我们可以观察到类似的步骤 在处理左右的时候,我们现在拥有一颗多叉树,它是不分左右的,它的孩子是个数组,我们只需要处理数组即可。
1 | for (let j = 0; j < n.children.length; j++) { |
所以代码就是
1 | var levelOrder = function (root) { |
437. 路径总和 III 【二叉树】
用前序遍历加回溯的性质去做这个题,先拿到的
1 | var pathSum = function (root, targetSum) { |
88. 合并两个有序数组 【二叉树】
双指针逆序
code
1 | var merge = function (nums1, m, nums2, n) { |