<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Leetcode on Yosgi Blog</title><link>https://siqi-liu.com/zh/categories/leetcode/</link><description>Recent content in Leetcode on Yosgi Blog</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Fri, 12 Jun 2026 04:28:42 +0000</lastBuildDate><atom:link href="https://siqi-liu.com/zh/categories/leetcode/index.xml" rel="self" type="application/rss+xml"/><item><title>1104-二叉树寻路</title><link>https://siqi-liu.com/zh/post/1104-%E4%BA%8C%E5%8F%89%E6%A0%91%E5%AF%BB%E8%B7%AF/</link><pubDate>Thu, 29 Jul 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/1104-%E4%BA%8C%E5%8F%89%E6%A0%91%E5%AF%BB%E8%B7%AF/</guid><description>第一时间想的是构造二叉树，然后利用每层对称的关系 ，求出父节点 var pathInZigZagTree = function(label) { let stack = [] for(let i = 1 ; i &lt;= label ; i ++) { let level = Math.floor(Math.log(i) / Math.log(2)) if (!stack[level]) { stack[level] = [] } if (level % 2 === 0) { stack[level].push(i) } else { stack[level].unshift(i) } } var level = Math.floor(Math.log(label) / Math.log(2)) let ans = [] while (level) { ans.push(label) level-- label = Math.floor(label / 2) let index = stack[level ].length - 1 - stack[level ].indexOf(label) label = stack[level][index] } ans.push(1) return ans.reverse() };很遗憾，超时了。 受到答案的启发，既然是对称的，同层数时，对称的数之和相同。 一层的首项为 2^ n ，末项是 2^(n + 1) - 1 可的对称数 2^ n + 2^(n + 1) - 1 - x 即 ( 1 &lt;&lt; row ) + ( 1 &lt;&lt; (row + 1) ) - 1 - label; 查找的末位一定是1，可做while 的结束 var pathInZigZagTree = function(label) { let row = Math.floor( Math.log(label) / Math.log(2) ) let ans = [] while(label!==1) { ans.unshift(label) row -- label = getReverse(Math.floor(label / 2),row) } ans.unshift(1) return ans };const getReverse = (label, row) =&gt; { return (1 &lt;&lt; row ) + (1 &lt;&lt; (row + 1) ) - 1 - label;}</description></item><item><title>反转链表2</title><link>https://siqi-liu.com/zh/post/%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A82/</link><pubDate>Fri, 23 Jul 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A82/</guid><description>考虑到头部也可能产生反转，所以需要一个虚拟头。 在遍历的过程中记录反转后的尾节点和之前的节点。 最后进行节点的链接处理。 var reverseBetween = function(head, m, n) { let noob = new ListNode(0) noob.next = head let cur = noob let index = 0 let A,B while (index &lt; m) { A = cur // A 记录反转子链的前一个节点 cur = cur.next index++ } B = cur // B 记录反转之后的尾节点 let pre = cur while (index &lt;= n) { let next = cur.next cur.next = pre pre = cur cur = next index++ } // 执行完后cur 指针在反转子链的下一个节点 B.next = cur A.next = pre return noob.next };</description></item><item><title>1893-检查是否区域内所有整数都被覆盖</title><link>https://siqi-liu.com/zh/post/1893-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8C%BA%E5%9F%9F%E5%86%85%E6%89%80%E6%9C%89%E6%95%B4%E6%95%B0%E9%83%BD%E8%A2%AB%E8%A6%86%E7%9B%96/</link><pubDate>Fri, 23 Jul 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/1893-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8C%BA%E5%9F%9F%E5%86%85%E6%89%80%E6%9C%89%E6%95%B4%E6%95%B0%E9%83%BD%E8%A2%AB%E8%A6%86%E7%9B%96/</guid><description>拿到没想到什么好方法，先暴力解 var isCovered = function(ranges, left, right) { for(let i = left ; i &lt;= right ; i ++) { let include = false for(let index = 0 ; index &lt; ranges.length ; index ++) { let [start,end] = ranges[index] if (i &gt;=start &amp;&amp; i &lt;=end) { include = true } } if (!include) return false } return true};思考优化，可以通过start ，end ，当 start &lt;= left 时， [left … end] 集合已经包含在内了，继续压缩 [end + 1 , right ] 直到集合里面没有元素 var isCovered = function(ranges, left, right) { ranges = ranges.sort((a,b) =&gt; a - b) for(let i = 0 ; i &lt; ranges.length;i++) { let [start,end] = ranges[i]; // 根据范围收缩左边界 if (start &lt;= left) { left = Math.max(end,right) } if (left &gt;= right) { return true } } return left &gt;= right };</description></item><item><title>138-复制带随机指针的链表</title><link>https://siqi-liu.com/zh/post/138-%E5%A4%8D%E5%88%B6%E5%B8%A6%E9%9A%8F%E6%9C%BA%E6%8C%87%E9%92%88%E7%9A%84%E9%93%BE%E8%A1%A8/</link><pubDate>Thu, 22 Jul 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/138-%E5%A4%8D%E5%88%B6%E5%B8%A6%E9%9A%8F%E6%9C%BA%E6%8C%87%E9%92%88%E7%9A%84%E9%93%BE%E8%A1%A8/</guid><description>拿到题目的想法是使用双指针指向新旧节点，再加上一个 map 用来映射旧节点和新节点。 递归 var listMap = new Map() var copyRandomList = function(head) { if(head === null) return head if (listMap.get(head)) { return listMap.get(head) } let newNode = new Node(head.val,null,null) listMap.set(head, newNode) newNode.next = copyRandomList(head.next) newNode.random = copyRandomList(head.random) return newNode };迭代 var copyRandomList = function(head) { if(head == null) return head var listMap = new Map() listMap.set(null,null) var cur = head while (cur!= null) { listMap.set(cur,new Node(cur.val,null,null)) cur = cur.next } cur = head while (cur!= null) { listMap.get(cur).next = listMap.get(cur.next) listMap.get(cur).random = listMap.get(cur.random) cur = cur.next } return listMap.get(head) };</description></item><item><title>684-冗余连接</title><link>https://siqi-liu.com/zh/post/684-%E5%86%97%E4%BD%99%E8%BF%9E%E6%8E%A5/</link><pubDate>Wed, 07 Jul 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/684-%E5%86%97%E4%BD%99%E8%BF%9E%E6%8E%A5/</guid><description>用时:15min 利用并查集模板即可 var findRedundantConnection = function(edges) { var n = edges.length var fa = new Array(n + 1) var find = function (x) { if (x != fa[x]) { fa[x] = find(fa[x]) } return fa[x] } for(let i = 1 ; i &lt;= n ; i ++) { fa[i] = i } for(let i = 0 ; i &lt; n ; i ++) { var [x,y] = edges[i] if (find(x) === find(y)) { return [x,y] } else { fa[find(x)] = find(y) } } };</description></item><item><title>1202-交换字符串中的元素</title><link>https://siqi-liu.com/zh/post/1202-%E4%BA%A4%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0/</link><pubDate>Tue, 06 Jul 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/1202-%E4%BA%A4%E6%8D%A2%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0/</guid><description>主要是用来学习并查集的思路，看了答案才下手的。 思路就是先对可交换的字符串进行分组，分组排序之后再组合起来 并查集就是用递归或者while循环实现find ， 然后 再用数组和下标的方式实现union var smallestStringWithSwaps = function(s, pairs) { var fa = [] var find = function (x) { if (x === fa[x]) { return x } else { return fa[x] = find(fa[x]) } } for (let i = 0; i &lt; s.length; i++) { fa[i] = i } for(let i = 0 ; i &lt; pairs.length ; i ++) { const [x,y] = pairs[i] fa[find(x)] = find(y) } var n = s.length // 计算分组后的map const vec = new Array(n).fill(0).map(() =&gt; new Array()); for (let i = 0; i &lt; n; i++) { fa[i] = find(i); vec[fa[i]].push(s[i]); } console.log(fa) // 对分组后的map 进行排序 for (let i = 0; i &lt; n; ++i) { if (vec[i].length &gt; 0) { vec[i].sort((a, b) =&gt; a.charCodeAt() - b.charCodeAt()); } } // 组合成ans const ans = new Array(n).fill(0); for (let i = 0; i &lt; n; ++i) { var group = fa[i] if ( group!= undefined &amp;&amp; vec[group].length) { // 取出每个组中序号最小的 ans[i] = vec[fa[i]].shift() } else { ans[i] = s[i] } } return ans.join('') };</description></item><item><title>778-水位上升的泳池中游泳</title><link>https://siqi-liu.com/zh/post/778-%E6%B0%B4%E4%BD%8D%E4%B8%8A%E5%8D%87%E7%9A%84%E6%B3%B3%E6%B1%A0%E4%B8%AD%E6%B8%B8%E6%B3%B3/</link><pubDate>Mon, 05 Jul 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/778-%E6%B0%B4%E4%BD%8D%E4%B8%8A%E5%8D%87%E7%9A%84%E6%B3%B3%E6%B1%A0%E4%B8%AD%E6%B8%B8%E6%B3%B3/</guid><description>用时：20min 首先能看出来是一个考察DFS的题目 var swimInWater = function(grid) { let row = grid.length; let col = grid[0].length; var step = 0 while(true) { for(let i = 0 ; i &lt; row ; i ++) { for(let j = 0 ; j &lt; col ; j ++){ grid[i][j] -- } } var visited = [] for(let i = 0 ; i &lt; row ; i ++) { let arr = [] for(let j = 0 ; j &lt; col ; j ++) { arr.push(false) } visited.push(arr) } var dfs = function (i,j) { if ( i&lt; 0 || j &lt; 0 || i &gt;= row || j &gt;= col) return false if (visited[i][j]) { return false } else { visited[i][j] = true } if (grid[i][j] &gt; 0) return false if (i === row - 1 &amp;&amp; j === col - 1 &amp;&amp; grid[i][j] &lt;= 0) return true return dfs(i+1,j) || dfs(i-1,j) || dfs(i,j-1)|| dfs(i,j + 1) } step++ if (dfs(0,0) === true) break } return step };想到我们不必从 1 开始尝试，可以用二分法找到最左插入点 var swimInWater = function(grid) { let row = grid.length; let col = grid[0].length; let right = -Infinity let left = Infinity for(let i = 0 ; i &lt; row ; i ++) { for(let j = 0 ; j &lt; col ; j ++ ){ let num = grid[i][j] right = Math.max(right,num) left = Math.min(left,num) } } let mid = left + Math.floor( (right - left) / 2 ) while(left &lt;= right) { mid = left + Math.floor( (right - left) / 2 ) var visited = [] var new_grid = [] for(let i = 0 ; i &lt; row ; i ++) { let arr = [] let _grid = [] for(let j = 0 ; j &lt; col ; j ++) { arr.push(false) _grid.push(grid[i][j] - mid) } visited.push(arr) new_grid.push(_grid) } var dfs = function (i,j) { if ( i &lt; 0 || j &lt; 0 || i &gt;= row || j &gt;= col) return false if (visited[i][j]) { return false } else { visited[i][j] = true } if (new_grid[i][j] &gt; 0) return false if (i === row - 1 &amp;&amp; j === col - 1 &amp;&amp; new_grid[i][j] &lt;= 0) return true return dfs(i+1,j) || dfs(i-1,j) || dfs(i,j-1)|| dfs(i,j + 1) } if(dfs(0,0)) { // 找到了备胎，继续看能不能更小 right = mid - 1 } else { left = mid + 1 } } return left };</description></item><item><title>475-供暖器</title><link>https://siqi-liu.com/zh/post/475-%E4%BE%9B%E6%9A%96%E5%99%A8/</link><pubDate>Thu, 10 Jun 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/475-%E4%BE%9B%E6%9A%96%E5%99%A8/</guid><description>首先想到的就是找到所有距离房屋最短距离的heater的距离 r ，然后返回最大的 r var findRadius = function(houses, heaters) { var max = 0 for(let i = 0 ; i &lt; houses.length ; i ++) { var house_position = houses[i] var r = Infinity for(let j = 0 ; j &lt; heaters.length ; j ++) { var heat_position = heaters[j] var reduce = Math.abs(house_position - heat_position) r = Math.min(r,reduce) } max = Math.max(r,max) } return max };既然这样，那么也可以利用二分法找到heater var findRadius = function(houses, heaters) { var r = 0 for(let i = 0 ; i &lt; houses.length ; i ++) { var house_position = houses[i] let left = 0 , right = heaters.length - 1 // 查找离 house_position 最近的 heat_position // 也就是查找 house_position 的最右插入点 while(left &lt;= right &amp;&amp; right &lt; heaters.length) { let mid = left + Math.floor((right - left) / 2) var heat_position = heaters[mid] // 找到的不大于，再往右边找 if(heat_position &lt;= house_position) { left = mid + 1 } else { right = mid - 1 } } // 这时候找到的插入点不一定是最近的,左边的比他小，所以需要比较一下 var R = Math.abs(heaters[right] - house_position) if(right &gt; 0) { R = Math.min( Math.abs( heaters[right - 1] - house_position) , R) } r = Math.max(r,R) } return r };</description></item><item><title>875-爱吃香蕉的珂珂</title><link>https://siqi-liu.com/zh/post/875-%E7%88%B1%E5%90%83%E9%A6%99%E8%95%89%E7%9A%84%E7%8F%82%E7%8F%82/</link><pubDate>Wed, 28 Apr 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/875-%E7%88%B1%E5%90%83%E9%A6%99%E8%95%89%E7%9A%84%E7%8F%82%E7%8F%82/</guid><description>用时：60min 拿到题目最快能想到的思路是枚举所有吃完香蕉的速度，找到能吃完香蕉的最小的速度。 枚举的速度范围是[1… 最大堆的香蕉],因为速度不会小于1，也没有速度比最大香蕉堆更快的必要 var minEatingSpeed = function(piles, h) { // 枚举所有可以吃完香蕉的速度,找到最小的 piles = piles.sort((a,b) =&gt; a - b) var maxSpeed = piles[piles.length - 1] var min = maxSpeed for(let i = 1; i &lt;= maxSpeed; i ++) { var H = 0 for(let j = 0 ; j &lt; piles.length; j ++) { H += Math.ceil(piles[j] / i) } if(H &lt;= h) { min = Math.min(min,i) } } return min };随即可以想到，[1… 最大速度]是一个单调区间，可以用二分法来解决 var minEatingSpeed = function(piles, h) { // 最小速度就是求下界l piles = piles.sort((a,b) =&gt; a - b) var maxSpeed = piles[piles.length - 1] var min = maxSpeed // 对速度区间 [1... maxSpeed] 做二分 var l = 1 ,r = maxSpeed while(l &lt;= r) { var mid = l + Math.floor ((r - l) / 2) var H = 0 for(let j = 0 ; j &lt; piles.length ; j ++) { H += Math.ceil(piles[j] / mid) } if ( H &lt;= h ) { // 符合，试试速度是否可以更小 r = mid - 1 } else { // 不符合，增加速度 l = mid + 1 } } return l };</description></item><item><title>295-数据流的中位数</title><link>https://siqi-liu.com/zh/post/295-%E6%95%B0%E6%8D%AE%E6%B5%81%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0/</link><pubDate>Wed, 24 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/295-%E6%95%B0%E6%8D%AE%E6%B5%81%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0/</guid><description>用时 ： 看了答案 既然是堆的练习，肯定是往堆的思路去靠拢了 由于我们要的仅仅是中位数，其实没有必要把所有的数都排序 可以用两个堆，一个最大堆一个最小堆 当总数为单数（即 最大堆 - 最小堆 个数 = 1），拿到最大堆的最大值 当总数为双数（即最大堆个数 = 最小堆） ，拿到最大堆最大值和最小堆最小值 const swap = function (arr,i,j) { [arr[i],arr[j]] = [arr[j],arr[i]] } class Heap { constructor() { this.count = 0 this.data = new Array(this.count + 1) } shiftUp(k) { // 把新的元素往上排 while(k&gt;=1) { let father = Math.floor(k / 2) if (this.data[k] &gt; this.data[father]) { swap(this.data,k,father) k = father } else { break } } } shiftDown(k) { while( k * 2 &lt;= this.count) { // 表示k 有孩子 let j = k if (k * 2 + 1 &lt;= this.count &amp;&amp; this.data[k * 2 + 1] &gt; this.data[k] &amp;&amp; this.data[k * 2 + 1] &gt; this.data[k * 2]) { j = k * 2 + 1 } else if (this.data[k * 2] &gt; this.data[k]) { j = k * 2 } else { break } swap(this.data,j,k) k = j } } size() { return this.count } isEmpty() { return this.count === 0 } insert(item) { this.data[++this.count] = item this.shiftUp(this.count) } extractMax() { if (this.count &lt;= 0) return let ret = this.data[1] swap(this.data,1,this.count--) this.shiftDown(1) return ret } } var MedianFinder = function() { this.maxHeap = new Heap(); this.minHeap = new Heap();};MedianFinder.prototype.addNum = function(num) { //[1] [] // [1,2] [] -&gt; [1] [2] // [1,3] [2] -&gt; [1,2] [3] // [1,2,4] [3] -&gt; [1,2] [3,4] // [1,2,5] [3,4] -&gt; [1,2,4] [4,5] var maxHeapsize = this.maxHeap.size() var minHeapsize = this.minHeap.size() this.maxHeap.insert(num) var max = this.maxHeap.extractMax() this.minHeap.insert(-max ) if (maxHeapsize === minHeapsize) { var min = Math.abs(this.minHeap.extractMax()) this.maxHeap.insert(min) } };MedianFinder.prototype.findMedian = function() { if (this.maxHeap.size() === this.minHeap.size()) { var max = this.maxHeap.extractMax() var min = Math.abs(this.minHeap.extractMax()) return (max + min) / 2 } else { return this.maxHeap.extractMax() } };需要注意的点</description></item><item><title>865-具有所有最深节点的最小子树</title><link>https://siqi-liu.com/zh/post/865-%E5%85%B7%E6%9C%89%E6%89%80%E6%9C%89%E6%9C%80%E6%B7%B1%E8%8A%82%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%90%E6%A0%91/</link><pubDate>Sun, 21 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/865-%E5%85%B7%E6%9C%89%E6%89%80%E6%9C%89%E6%9C%80%E6%B7%B1%E8%8A%82%E7%82%B9%E7%9A%84%E6%9C%80%E5%B0%8F%E5%AD%90%E6%A0%91/</guid><description>用时:60min 被题目的深度误导，认为计算深度需要由上至下。实际上可以由下至上。 如果左右子树高度相同，返回节点本身以及深度 如果左子树比较深，说明最小最深在左子树，返回左子树以及自己的深度 如果右子树比较深，说明最小深度在右边，返回右子树以及自身深度 var subtreeWithAllDeepest = function(root) { var dfs = function (root) { if (!root) return [root,0] var [left,ld] = dfs(root.left) var [right,rd] = dfs(root.right) if (ld &gt; rd) return [left, ld + 1] if (rd &gt; ld) return [right,rd + 1] return [root,ld + 1] } var res = dfs(root) return res[0] };</description></item><item><title>1046-最后一块石头的重量</title><link>https://siqi-liu.com/zh/post/1046-%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8F/</link><pubDate>Sun, 21 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/1046-%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8F/</guid><description>用时:10min 一道简单题应该不会让我手写最大堆吧 const swap = function (arr,i,j) { [arr[i],arr[j]] = [arr[j],arr[i]] } class MaxHeap { constructor() { this.count = 0 this.data = new Array(this.count + 1) } shiftUp(k) { // 把新的元素往上排 while(k&gt;=1) { let father = Math.floor(k / 2) if (this.data[k] &gt; this.data[father]) { swap(this.data,k,father) k = father } else { break } } } shiftDown(k) { while( k * 2 &lt;= this.count) { // 表示k 有孩子 let j = k if (k * 2 + 1 &lt;= this.count &amp;&amp; this.data[k * 2 + 1] &gt; this.data[k] &amp;&amp; this.data[k * 2 + 1] &gt; this.data[k * 2]) { j = k * 2 + 1 } else if (this.data[k * 2] &gt; this.data[k]) { j = k * 2 } else { break } swap(this.data,j,k) k = j } } size() { return this.count } isEmpty() { return this.count === 0 } insert(item) { this.data[++this.count] = item this.shiftUp(this.count) } extractMax() { if (this.count &lt; 0) return let ret = this.data[1] swap(this.data,1,this.count--) this.shiftDown(1) return ret } } var lastStoneWeight = function(stones) { var heap = new MaxHeap() for(let i = 0 ; i &lt; stones.length ; i++) { heap.insert(stones[i]) } while(heap.size() &gt; 1) { var s1 = heap.extractMax() var s2 = heap.extractMax() var reduce = Math.abs(s1 - s2) if (reduce) { heap.insert(reduce) } } if (heap.size() === 0) { return 0 } return heap.extractMax() };</description></item><item><title>783-二叉搜索树节点最小距离</title><link>https://siqi-liu.com/zh/post/783-%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%8A%82%E7%82%B9%E6%9C%80%E5%B0%8F%E8%B7%9D%E7%A6%BB/</link><pubDate>Thu, 18 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/783-%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%8A%82%E7%82%B9%E6%9C%80%E5%B0%8F%E8%B7%9D%E7%A6%BB/</guid><description>用时 ： 10min 利用前序遍历二叉树递增的性质解决 var minDiffInBST = function(root) { var min = Infinity var pre = null var dfs = function (root) { if (!root) return if (root.left) { dfs(root.left) } if (pre == null) { pre = root.val } else { console.log(pre,root.val) var reduce = Math.abs(pre - root.val) min = Math.min(min,reduce) pre = root.val } if (root.right) { dfs(root.right) } } dfs(root) return min };</description></item><item><title>814-二叉树剪枝</title><link>https://siqi-liu.com/zh/post/814-%E4%BA%8C%E5%8F%89%E6%A0%91%E5%89%AA%E6%9E%9D/</link><pubDate>Wed, 17 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/814-%E4%BA%8C%E5%8F%89%E6%A0%91%E5%89%AA%E6%9E%9D/</guid><description>用时：6min 很简单，中序遍历，计算两边子树是否存在 1 的值，没有就去掉。凡事左右子树或者自身包含 1 ，返回 true 。否则返回 false var pruneTree = function(root) { var DFS = function (root) { if (!root) return false var left = DFS(root.left) var right = DFS(root.right) if (!left) { root.left = null } if (!right) { root.right = null } if (left) { return true } if (right) { return true } if( root.val === 1) { return true } } var dummp = new TreeNode(1) dummp.left = root DFS(dummp) return dummp.left};一个需要注意的点是，root本身也有可能需要剪掉，所以增加了一个虚拟节点来作为父节点</description></item><item><title>1448-统计二叉树中好节点的数目</title><link>https://siqi-liu.com/zh/post/1448-%E7%BB%9F%E8%AE%A1%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%A5%BD%E8%8A%82%E7%82%B9%E7%9A%84%E6%95%B0%E7%9B%AE/</link><pubDate>Wed, 17 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/1448-%E7%BB%9F%E8%AE%A1%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%A5%BD%E8%8A%82%E7%82%B9%E7%9A%84%E6%95%B0%E7%9B%AE/</guid><description>用时： 偷偷看了答案 本来把问题想复杂了，想着维护一个最大堆，从上往下传递然后每次判断是否大于最大值 看了答案后发现只需要传递一个max，判断是否比max大，然后从下到上获取左右节点的值，返回左右节点和 加自身是否符合就好了 var goodNodes = function(root,max = root.val) { if (!root) return 0 var left = 0 max = Math.max(max,root.val) if (root.left) { left = goodNodes(root.left,max) } var right = 0 if (root.right) { right = goodNodes(root.right,max) } console.log(left,right,root.val) return (left + right) + (root.val &gt;= max ? 1 : 0) };</description></item><item><title>1325-删除给定值的叶子节点</title><link>https://siqi-liu.com/zh/post/1325-%E5%88%A0%E9%99%A4%E7%BB%99%E5%AE%9A%E5%80%BC%E7%9A%84%E5%8F%B6%E5%AD%90%E8%8A%82%E7%82%B9/</link><pubDate>Wed, 17 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/1325-%E5%88%A0%E9%99%A4%E7%BB%99%E5%AE%9A%E5%80%BC%E7%9A%84%E5%8F%B6%E5%AD%90%E8%8A%82%E7%82%B9/</guid><description>用时：5min 虚拟节点 + 后序遍历 就可以愉快的秒了 var removeLeafNodes = function(root, target) { var dfs = function (root) { if (root.left) { root.left = dfs(root.left) } if (root.right) { root.right = dfs(root.right) } if (!root.left &amp;&amp; !root.right) { if (root.val === target) { root = null } } return root } var dummy = new TreeNode(0) dummy.left = root dfs(dummy) return dummy.left};</description></item><item><title>1022-从根到叶的二进制数之和</title><link>https://siqi-liu.com/zh/post/1022-%E4%BB%8E%E6%A0%B9%E5%88%B0%E5%8F%B6%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%95%B0%E4%B9%8B%E5%92%8C/</link><pubDate>Wed, 17 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/1022-%E4%BB%8E%E6%A0%B9%E5%88%B0%E5%8F%B6%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%95%B0%E4%B9%8B%E5%92%8C/</guid><description>用时：10min 前序从上至下计算，没什么好说的 二进制转10 进制挺费劲的，我直接parseInt(path, 2) var sumRootToLeaf = function(root) { var ans = 0 var dfs = function (root,path) { if (!root) return path += root.val if (!root.left &amp;&amp; !root.right) { ans += parseInt(path, 2) } root.left &amp;&amp; dfs(root.left,path) root.right &amp;&amp; dfs(root.right,path) } dfs(root,'') return ans };</description></item><item><title>面试题-04-12-求和路径</title><link>https://siqi-liu.com/zh/post/%E9%9D%A2%E8%AF%95%E9%A2%98-04-12-%E6%B1%82%E5%92%8C%E8%B7%AF%E5%BE%84/</link><pubDate>Tue, 16 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/%E9%9D%A2%E8%AF%95%E9%A2%98-04-12-%E6%B1%82%E5%92%8C%E8%B7%AF%E5%BE%84/</guid><description>用时：20min 因为是求 ” 每个节点出发的为sum的路径” 很明显就想到了回溯，因为是二叉树，所以就双递归。 外层递归用来找到所有的节点，内层用来算节点的路径 var pathSum = function(root, sum) { var res = 0 var dfs = function (root,total,visited) { if (!root) return if (total + root.val == sum) { res++ } else if (total + root.val &gt; sum) { return } dfs(root.left,total + root.val,[...visited,root.val]) dfs(root.right,total + root.val,[...visited,root.val]) } var dfs_outer = function (root) { if (!root )return root.left &amp;&amp; dfs_outer(root.left) dfs(root,0,[]) root.right &amp;&amp; dfs_outer(root.right) } dfs_outer(root) return res };</description></item><item><title>563-二叉树的坡度</title><link>https://siqi-liu.com/zh/post/563-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%9D%A1%E5%BA%A6/</link><pubDate>Tue, 16 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/563-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%9D%A1%E5%BA%A6/</guid><description>用时：15min 用时：15min 拿到题的第一反应是是用递归，然后直接开始写了后发现 递归需要满足的条件是问题可以拆分成子问题，但是根据题意，我们需要求的是每个节点左右节点和的差值，这个 “左右节点和” 对于每个节点来说，都是不一样的问题 所以还是双递归 var findTilt = function(root) { var total = 0 var innerDFS = function (root) { if (!root) return 0 var left = 0 ,right = 0 if (root.left) { left = innerDFS(root.left) } if (root.right) { right = innerDFS(root.right) } return root.val + left + right } var outerDFS = function (root) { if (!root) return root.right &amp;&amp; outerDFS(root.right) total += Math.abs(innerDFS(root.left) - innerDFS(root.right)) root.left &amp;&amp; outerDFS(root.left) } outerDFS(root) return total };But!!!!!!!! 看了一眼答案我觉得自己还是拿衣服了 原来计算左右节点之和，与累积左右节点只差并不矛盾，一个递归就能解决问题。就是DFS的时候顺便两者一起做了，累计左右节点就是 return root.val + left + right 而计算差值是 total += Math.abs(left-right) var findTilt = function(root) { var total = 0 var DFS = function (root) { if (!root) return 0 var left = 0 ,right = 0 if (root.left) { left = DFS(root.left) } if (root.right) { right = DFS(root.right) } total += Math.abs(left-right) return root.val + left + right } DFS(root) return total };还是年轻了，明明做过类似的递归</description></item><item><title>129-求根节点到叶节点数字之和</title><link>https://siqi-liu.com/zh/post/129-%E6%B1%82%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E5%8F%B6%E8%8A%82%E7%82%B9%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C/</link><pubDate>Tue, 16 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/129-%E6%B1%82%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E5%8F%B6%E8%8A%82%E7%82%B9%E6%95%B0%E5%AD%97%E4%B9%8B%E5%92%8C/</guid><description>看题型是求从根节点出发的，首先想到的是自顶向下的DFS，带着参数向下传递，结束条件没有左右儿子 直接前序遍历走起，可以认为是前面的节点都处理好了 var sumNumbers = function(root) { var ans = 0 var DFS = function (root,path) { if (!root) return path += root.val if (!root.left &amp;&amp; !root.right) { ans += Number(path) } root.left &amp;&amp; DFS(root.left,path) DFS(root.right, path) } DFS(root,'') return ans };</description></item><item><title>124-二叉树中的最大路径和</title><link>https://siqi-liu.com/zh/post/124-%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C/</link><pubDate>Mon, 15 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/124-%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C/</guid><description>用时 ：30min 看懂题目的意思之后其实就能很快的做出来 题意是要求从任意节点出发的值，我们可以把每个节点的值求出来的过程中，选择最大的 又因为路径不能返回，可以知道我们出发的根节点的子节点的路径只能选择左或者右，或者都不要，结果为 x 求max 的时候再 把 x 和 左右都选的情况进行比较 var maxPathSum = function(root) { var max = -Infinity var DFS = function (root) { if (!root) return 0 var leftSum = DFS(root.left) var rightSum = DFS(root.right) var res = Math.max(leftSum + root.val ,rightSum + root.val ,root.val) max = Math.max(max,res,root.val + leftSum + rightSum) return res } DFS(root) return max };</description></item><item><title>99-恢复二叉搜索树</title><link>https://siqi-liu.com/zh/post/99-%E6%81%A2%E5%A4%8D%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91/</link><pubDate>Sat, 13 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/99-%E6%81%A2%E5%A4%8D%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91/</guid><description>用时：60min 思路还是中序遍历，使用pre缓存前一个节点，因为中序遍历是递增的，所以一定是先找到大的，再找到小的。 所以第一个出问题的是pre，第二个是root var recoverTree = function(root) { var left = null var right = null var pre = null // 一定是先找到大的，再找到小的，所以第一个出问题的是pre，第二个出问题的是root var DFS = function (root) { if (root.left) { DFS(root.left) } if (pre) { console.log(pre.val) if (pre.val &gt; root.val) { if(!left) { left = pre right = root } else { right = root } } pre = root } else { pre = root } if (root.right) { DFS(root.right) } } DFS(root) if (left &amp;&amp; right) { [left.val,right.val] = [right.val,left.val] } return root}</description></item><item><title>98-验证二叉搜索树</title><link>https://siqi-liu.com/zh/post/98-%E9%AA%8C%E8%AF%81%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91/</link><pubDate>Sat, 13 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/98-%E9%AA%8C%E8%AF%81%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91/</guid><description>用时 10min 直接用二叉搜索树中序遍历递增的性质就好了 中序遍历找到左孩子 pre 是否存在 不存在，赋值 ，找右孩子，返回 1 存在，是否比 val 小 是，非递增，返回 false 否，递增，赋值 pre ，返回 1 var isValidBST = function(root) { var preVal = null var DFS = function(root) { var left = true var right = true if (root.left) { left = DFS(root.left) } if (preVal !== null) { if (preVal &gt;= root.val) return false preVal = root.val } else { preVal = root.val } if (root.right) { right = DFS(root.right) } return left &amp;&amp; right } return DFS(root) };</description></item><item><title>662-二叉树最大宽度</title><link>https://siqi-liu.com/zh/post/662-%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6/</link><pubDate>Sat, 13 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/662-%E4%BA%8C%E5%8F%89%E6%A0%91%E6%9C%80%E5%A4%A7%E5%AE%BD%E5%BA%A6/</guid><description>用时：120min 一开始的思路是用BFS，出队时出一整队（后来知道这叫宽度遍历） 然后双指针找到左右节点。当左右节点都不存在时结束 var widthOfBinaryTree = function(root) { var que = [root] var max = 0 while(que.length) { var len = que.length var left = 0 var right = que.length - 1 while(!que[left] &amp;&amp; left &lt; len) { left ++ } while(!que[right] &amp;&amp; right &gt;= 0) { right -- } if (left &gt; right) { break } max = Math.max(max,right - left+ 1) for(let i = 0 ; i &lt; len ; i ++) { var node = que.shift() if (!node) { que.push(node) que.push(node) continue } node.left ? que.push(node.left) : que.push(null) node.right ? que.push(node.right) : que.push(null) } } return max };很快啊，提交显示执行超过时间限制，于是增加了一个储存序号的队列，和BFS出栈同步，用于计算左右距离 var widthOfBinaryTree = function(root) { var que = [root] var numQue = [1] var max = 0 while(que.length) { var len = que.length max = Math.max(numQue[numQue.length - 1] - numQue[0] + 1) for(let i = 0 ; i &lt; len ; i ++) { var node = que.shift() var val = numQue.shift() if (node.left) { que.push(node.left) numQue.push(val * 2) } if (node.right) { que.push(node.right) numQue.push(val * 2 + 1) } } } return max };很快啊，告诉我栈溢出了。我这才意识到问题的严重性（误 然后翻了翻答案 对每次的下表统一减去了当层的第一个数 var widthOfBinaryTree = function(root) { var que = [root] var numQue = [0] var max = 0 while(que.length) { var len = que.length var start = numQue[0] max = Math.max( numQue[numQue.length - 1] - start + 1,max) for(let i = 0 ; i &lt; len ; i ++) { var node = que.shift() var val = numQue.shift() - start if (node.left) { que.push(node.left) numQue.push(val * 2 + 1 ) } if (node.right) { que.push(node.right) numQue.push(val * 2 + 2 ) } } } return max };</description></item><item><title>222-完全二叉树的节点个数</title><link>https://siqi-liu.com/zh/post/222-%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E8%8A%82%E7%82%B9%E4%B8%AA%E6%95%B0/</link><pubDate>Sat, 13 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/222-%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E8%8A%82%E7%82%B9%E4%B8%AA%E6%95%B0/</guid><description>用时：80min 拿到题的第一个反应当然是DFS解决…. 但是这是一个完全二叉树，怎么说也应该把完全二叉树的性质用上才行 可以考察二叉树的左右子树深度 如果左深度 大于 右 ， 说明右边已经满了，满了的个数是 2 的 n 次方 - 1，加上root 是 2 的 n次方，继续递归左边 如果右深度大于左， 说明左边满了，同样，继续递归右边 至于怎么求二叉树的深度，可以从下往上递归 var count = function(root) { if (root === null) return 0 return Math.max(count(root.left),count(root.right)) + 1}那么总体的代码就是 var countNodes = function(root) { if (!root) { return 0 } var count = function(root) { if (root === null) return 0 return Math.max(count(root.left),count(root.right)) + 1 } var leftLevel = count(root.left) var rightLevel = count(root.right) if (leftLevel === rightLevel) { return Math.pow(2,leftLevel) + countNodes(root.right) } else { return Math.pow(2,rightLevel ) + countNodes(root.left) } };</description></item><item><title>669-修剪二叉搜索树</title><link>https://siqi-liu.com/zh/post/669-%E4%BF%AE%E5%89%AA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91/</link><pubDate>Thu, 11 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/669-%E4%BF%AE%E5%89%AA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91/</guid><description>用时 ：20min 与上一道题类似，多的是分了父节点在区间内和不在区间内的情况 var trimBST = function(root, low, high) { var add = function (root,node) { if (!node || !root) return null if (node.val &gt; root.val ) { if (!root.right) { root.right = node } else { add(root.right,node) } } if (node.val &lt; root.val) { if (!root.left) { root.left = node } else { add(root.left,node) } } } var walk = function (root) { if (!root) return root if (root.val &lt; low || root.val &gt; high) { var left = walk(root.left) var right = walk(root.right) if (!left &amp;&amp; !right) { root = null } else if (right &amp;&amp; left) { root = right add(root,left) } else if (left) { root = left } else { root = right } } else { root.left= walk(root.left) root.right = walk(root.right) } return root } return walk(root) };看了解析之后发现可以更简单，思路是如果大于右边界，就直接在左分支找； 如果小于左边界，就直接在右分支找。这样就省去了删除节点的步骤 var trimBST = function(root, low, high) { if(root == null) return null if (root.val &gt; high) return trimBST(root.left,low,high) if(root.val &lt; low) return trimBST(root.right,low,high) root.left = trimBST(root.left,low,height) root.right = trimBST(root.right,low,height) return root};</description></item><item><title>450-删除二叉搜索树中的节点</title><link>https://siqi-liu.com/zh/post/450-%E5%88%A0%E9%99%A4%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9/</link><pubDate>Thu, 11 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/450-%E5%88%A0%E9%99%A4%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9/</guid><description>用时:30min 题目分两个部分需要解决，搜索和删除 搜索部分就是找到节点，可以根据二叉搜索树的性质来找到需要的节点 删除分三种情况 只有左孩子，用左孩子代替 只有右孩子，用右孩子代替 都没有，则赋值为null （ 由于前面有递归赋值，所以这边可以成功 都有，则储存左孩子后，用右孩子代替，然后找到左孩子在右边的位置 var deleteNode = function(root, key) { function insert(root,node) { if (root.val &gt; node.val) { if (!root.left) { root.left = node } else { insert(root.left,node) } } else { if (!root.right) { root.right = node } else { insert(root.right,node) } } } function search(root) { if (!root) { return null } if (root.val &gt; key) { root.left = search(root.left) } else if (root.val &lt; key){ root.right = search(root.right) } else { if (!root.left &amp;&amp; !root.right) { root = null } else if (root.left &amp;&amp; root.right) { var left = root.left root = root.right insert(root,left) } else if (root.left) { root = root.left } else if (root.right) { root = root.right } } return root } return search(root) };需要注意的点 考虑节点不存在的情况 删除一个节点的方法，可以给左（右 节点赋值为递归的返回值</description></item><item><title>116-填充每个节点的下一个右侧节点指针</title><link>https://siqi-liu.com/zh/post/116-%E5%A1%AB%E5%85%85%E6%AF%8F%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%8F%B3%E4%BE%A7%E8%8A%82%E7%82%B9%E6%8C%87%E9%92%88/</link><pubDate>Wed, 10 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/116-%E5%A1%AB%E5%85%85%E6%AF%8F%E4%B8%AA%E8%8A%82%E7%82%B9%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%8F%B3%E4%BE%A7%E8%8A%82%E7%82%B9%E6%8C%87%E9%92%88/</guid><description>用时 ：25min 主要思路是利用栈，和普通的层序遍历不同的是每次都把栈清空 var connect = function(root) { if (!root) return null var stack = [root] while(stack.length) { var _stack = [...stack,null] stack = [] var pre = _stack.shift() while(_stack.length || pre) { pre.left &amp;&amp; stack.push(pre.left) pre.right &amp;&amp; stack.push(pre.right) pre.next = _stack.shift() pre = pre.next } } return root};很显然我使用的 On 的内存空间是不符合题目的常数空间的，考虑到每一层的链接可以依靠父节点，以链表的形式找到。如下代码 while(pre) { var cur = pre while(cur) { cur.left.next = cur.right cur.right.next = cur.next.left cur = cur.next } }剩下的需要考虑的有 pre需要保存为最左边的节点，以便于后面以链表的形式进行连接 第一层的next为空 最后一层没有左右节点</description></item><item><title>1008-前序遍历构造二叉搜索树</title><link>https://siqi-liu.com/zh/post/1008-%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91/</link><pubDate>Wed, 10 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/1008-%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91/</guid><description>用时 ： 没做出来 没做出来的主要原因是一直想着怎么用栈来做，写到后面看栈的答案看着看着发现递归最简单 递归 思路很简单，首先写出来怎么插入子节点 如果比父节点小，且父节点左为空，直接做左节点 如果比父节点大，且父节点右为空，直接做右节点 比父节点小，则父节点赋值为父节点的左节点，回到 1 比父节点大，则父节点赋值为父节点的右节点，回到1 遍历整个数组，一个个插入即可得到结果 var bstFromPreorder = function(preorder) { var add = function (node,val) { if (val &lt; node.val &amp;&amp; !node.left) { node.left = new TreeNode(val) } if (val &gt; node.val &amp;&amp; !node.right) { node.right = new TreeNode(val) } if (val &lt; node.val) { add(node.left,val) } if (val &gt; node.val) { add(node.right,val) } } var root = new TreeNode(preorder.shift()) for(let i = 0 ; i &lt; preorder.length ; i ++) { add(root,preorder[i]) } return root};</description></item><item><title>894-所有可能的满二叉树</title><link>https://siqi-liu.com/zh/post/894-%E6%89%80%E6%9C%89%E5%8F%AF%E8%83%BD%E7%9A%84%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91/</link><pubDate>Sun, 07 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/894-%E6%89%80%E6%9C%89%E5%8F%AF%E8%83%BD%E7%9A%84%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91/</guid><description>用时： 抄答案才做出来 思路很容易看出来是利用递归，但是coding才是真正的问题。 我们需要返回的是根节点，但是递归是从下至上的 尝试用动态规划解决，dp(n) 是 dp(x) 与 dp (n - x - 1) 结果的组合 var allPossibleFBT = function(n) { var dp = [] var buld = function (n) { for(let i = 1 ; i &lt;= n ; i ++ ) { if (i === 1) { dp[i] = new TreeNode(0) } else if (i % 2 === 0) { dp[i] = undefined } else { dp[i] = [] for(let left = 1; left &lt; i; left ++) { var leftNodes = dp[left] var rightNodes = dp[i - left - 1] for(let j = 0 ; j &lt; leftNodes.length ; j ++) { for(let k = 0 ; k &lt; rightNodes.length ; k ++) { var node = new TreeNode(0) node.left = leftNodes[j] node.right = rightNodes[k] dp[i].push(node) } } } } } } buld(n) return dp[n] };</description></item><item><title>297-二叉树的序列化与反序列化</title><link>https://siqi-liu.com/zh/post/297-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/</link><pubDate>Thu, 04 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/297-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/</guid><description>用时：30min 序列化很简单，使用BFS可以解决 反序列化需要用到二叉树的性质，即第 i 个节点的子节点分别为 (i + 1) * 2 - 1 和 ( i + 1) * 2, 流程 找到父节点，入栈，此时指针 i 在 父节点的 val 上 父节点出栈，找到父节点的左右节点，指针根据规则找到 两遍的值，左右节点入栈， i++ 重复 1 var serialize = function(root) { if (!root) return [] var que = [root] var res = [] while(que.length) { var cur = que.shift() if (cur) { res.push(cur.val); que.push(cur.left); que.push(cur.right); } else { res.push('null'); } } return res.join(',') };/** * Decodes your encoded data to tree. * * @param {string} data * @return {TreeNode} */var deserialize = function(data) { if (!data.length) return null var nodes = data.split(',') var i = 0 var root = new TreeNode(nodes[i]) var que = [root] while ( que.length) { var node = que.shift() var left = nodes[ (i + 1) * 2 - 1] var right = nodes[ (i + 1) * 2] if (left !== 'null') { node.left = new TreeNode(left) que.push(node.left) } else { node.left = null } if (right !== 'null') { node.right = new TreeNode(right) que.push(node.right) } else { node.right = null } i++ } return root};</description></item><item><title>1372-二叉树中的最长交错路径</title><link>https://siqi-liu.com/zh/post/1372-%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E4%BA%A4%E9%94%99%E8%B7%AF%E5%BE%84/</link><pubDate>Mon, 01 Mar 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/1372-%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E4%BA%A4%E9%94%99%E8%B7%AF%E5%BE%84/</guid><description>用时：25min 其实是比较常规的DFS类型题目，之所以用这么久是因为对边界值的判断出了问题。 从 root 出发时， 因为root 是可能没有子节点的，所以 len 值 为 0 从子节点出发，因为已经判断完字节点，算作当前节点到下一节点已经走一条长度为 1 的边 ， 所以是 1 var longestZigZag = function(root) { // true left false right var max = 0 var dfs = function (node,flag,len) { max = Math.max(len,max) if (flag) { node.right &amp;&amp; dfs(node.right,!flag,len + 1) node.left &amp;&amp; dfs(node.left,flag,1) } else { node.left &amp;&amp; dfs(node.left,!flag,len + 1) node.right &amp;&amp; dfs(node.right,flag,1) } } dfs(root,true,0) dfs(root,false,0) return max };</description></item><item><title>关于二叉树的总结</title><link>https://siqi-liu.com/zh/post/%E5%85%B3%E4%BA%8E%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%80%BB%E7%BB%93/</link><pubDate>Sun, 28 Feb 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/%E5%85%B3%E4%BA%8E%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%80%BB%E7%BB%93/</guid><description>为什么前中后序遍历用栈，而层序遍历使用队列 ？ 二叉树的前中后序遍历的迭代时，我们用栈来简化操作，因为它们都是DFS的递归结构，也就是从下到上处理，但是我写代码一定是从root节点开始，所以需要栈，而栈正好是先进先出的。这样我才可以把处理root放在最后。 层序遍历是BFS，由上至下。最先入队的root也是我想最先处理的。这就是为什么DFS 使用栈而BFS使用队列的原因。 DFS的算法流程与模板 首先将根节点放入stack中。 从stack中取出第一个节点，并检验它是否为目标。如果找到所有的节点，则结束搜寻并回传结果。否则将它某一个尚未检验过的直接子节点加入stack中。 重复步骤 2。 如果不存在未检测过的直接子节点。将上一级节点加入stack中。 重复步骤 2。 重复步骤 4。 若stack为空，表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传”找不到目标”。 function dfs(root) { if (满足特定条件）{ // 返回结果 or 退出搜索空间 } for (const child of root.children) { dfs(child) } }BFS 的算法流程与模板 首先将根节点放入队列中。 从队列中取出第一个节点，并检验它是否为目标。 如果找到目标，则结束搜索并回传结果。 否则将它所有尚未检验过的直接子节点加入队列中。 若队列为空，表示整张图都检查过了——亦即图中没有欲搜索的目标。结束搜索并回传”找不到目标”。 重复步骤 2。 function bfs(root) { var que = [root] while(que.length) { var node = que.shift() if (node 是我们要找到的) return node node.left &amp;&amp; que.push(node.left) node.right &amp;&amp; que.push(node.right) } }</description></item><item><title>102-二叉树的层序遍历</title><link>https://siqi-liu.com/zh/post/102-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86/</link><pubDate>Sun, 28 Feb 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/102-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86/</guid><description>102.二叉树的层序遍历 遍历本身很简单，需要考虑的问题是怎么标示每一层，这里可以用迭代和递归两种思路 迭代 入队 root ，再入队一个null 表示第 0 层的结束 出队 root ，入队左右节点 ， 如果出队的是null，说明一层结束，再入队一个 null 队列不为空，循环 2 ⚠️ 需要注意的点： 队列最后一项如果不做处理将会一直是null，因此需要增加结束条件 var levelOrder = function(root) { var que = [root,null] var res = [] var level = [] if (!root ) return [] while(que.length) { var node = que.shift() if (node) { node.left &amp;&amp; que.push(node.left) node.right &amp;&amp; que.push(node.right) level.push(node.val) } else { res.push([...level]) level = [] if(que.length) { que.push(null) } } } return res };递归 递归解法的重点是传递层数作为参数 var levelOrder = function(root) { var res = [] if (!root) return [] var walk = function (node,index) { if (!res[index]) res[index] = [] res[index].push(node.val) node.left &amp;&amp; walk(node.left,index + 1) node.right &amp;&amp; walk(node.right,index + 1) } walk(root,0) return res };步骤如代码所示 为所在的层数开辟一个数组空间 root 加入数组 如果存在左节点，或者右节点，继续到1，层数 + 1</description></item><item><title>双色标记法</title><link>https://siqi-liu.com/zh/post/%E5%8F%8C%E8%89%B2%E6%A0%87%E8%AE%B0%E6%B3%95/</link><pubDate>Sat, 27 Feb 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/%E5%8F%8C%E8%89%B2%E6%A0%87%E8%AE%B0%E6%B3%95/</guid><description>二叉树前中后序遍历总结 从前面的题目中可以看到，二叉树的前中后序遍历的递归方法是类似的，但是迭代的实现完全不同。 从垃圾回收的三色标记法得到启发，发现他们的共同点 节点入栈，标记为 0 （ 未访问 ） 节点出栈，若已经访问，出栈。若未访问，标记为已访问，入栈。 节点左右节点 继续到 1 这样，我们可以通过控制节点的入栈顺序，用相似的代码来迭代完成二叉树的前中后序遍历。需要额外做的是多开一个On的空间来保存节点的状态 拿后序遍历做例子 var postorderTraversal = function(root) { var stack = [[root,0]] var res = [] while(stack.length) { var [node,color] = stack.pop() if (color === 0) { // 在这里通过控制顺序就可以完成前中后序遍历 stack.push([node,1]) node.right &amp;&amp; stack.push([node.right,0]) node.left &amp;&amp; stack.push([node.left,0]) } else { res.push(node.val) } } return res };</description></item><item><title>145-二叉树的后序遍历</title><link>https://siqi-liu.com/zh/post/145-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/</link><pubDate>Fri, 26 Feb 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/145-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/</guid><description>145.二叉树的后序遍历 1h 中序遍历的顺序是 左 - 右 - 中 递归 用递归非常容易 var postorderTraversal = function(root) { var res = [] if (!root) return res var travel = function (node) { node.left &amp;&amp; travel(node.left) node.right &amp;&amp; travel(node.right) res.push(node.val) } travel(root) return res };迭代 根节点入栈 判断是否可以出栈，如果能，则记录自己为上一个出栈节点，出栈 不能出栈，分别将右节点和左节点入栈 重复第二个步骤 var postorderTraversal = function(root) { var stack = [root] var res = [] if (!root) { return res } var pre = root while(stack.length) { var node = stack[stack.length - 1] // 当节点的 左 / 右 节点是上一个被输出的节点，代表左右节点都已经被输出过（因为后序遍历根节点在最后） if ( (!node.left &amp;&amp; !node.right) || (node.left === pre || node.right === pre)) { // 只有在没有 左右节点，或者 左右节点都输出过时才可以输出 node = stack.pop() pre = node res.push(node.val) } else { if (node.right) { stack.push(node.right) } if(node.left) { stack.push(node.left) } } } return res };总结 虽然，做完了二叉树的前中后序遍历，但是我有预感用不了多久就会忘记，特别是中序遍历和后序遍历，我完全没有找到两者有什么共同点，所以随后会做一下总结。</description></item><item><title>94-二叉树的中序遍历</title><link>https://siqi-liu.com/zh/post/94-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86/</link><pubDate>Thu, 25 Feb 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/94-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86/</guid><description>94.二叉树的中序遍历 30min 中序遍历的顺序是 左 - 中 - 右 递归 用递归非常容易 var inorderTraversal = function(root) { var res = [] if (!root) { return res } var travel = function (node) { node.left &amp;&amp; travel(node.left) res.push(node.val) node.right &amp;&amp; travel(node.right) } travel(root) return res };迭代 迭代的步骤复杂一些，因为根节点不是先输出，所以需要保留根节点 根节点入栈，判断有没有左子节点，如果有，继续入栈，直到叶子结点 出栈，输出，判断是否有右子节点，有则入栈，继续执行2 var inorderTraversal = function(root) { var stack = [] var res = [] if (!root) return res stack.push(root) while(root.left) { stack.push(root.left) root = root.left } while(stack.length) { // 此时栈顶是树中最左的节点 var node = stack.pop() res.push(node.val) // 存在右节点，入栈后继续找左节点 if (node.right) { node = node.right stack.push(node) while(node.left) { stack.push(node.left) node = node.left } } } return res };</description></item><item><title>144-二叉树的前序遍历</title><link>https://siqi-liu.com/zh/post/144-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86/</link><pubDate>Thu, 25 Feb 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/144-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86/</guid><description>144.二叉树的前序遍历 10min 二叉树的前序遍历的顺序是中 - 左 - 右 先遍历完所有的根节点与左节点，然后处理右节点 可以看出来这是一个递归的行为，递归问题可以用栈来进行简化 迭代解法 var preorderTraversal = function(root) { if (!root) return null var stack = [root] var res = [] while(stack.length) { var node = stack.pop() res.push(node.val) node.right &amp;&amp; stack.push(node.right) node.left &amp;&amp; stack.push(node.left) } return res };递归解法 递归问题当然可以递归解决 var preorderTraversal = function(root) { var res = [] if (!root) return res var travel = function (node) { res.push(node.val) node.left &amp;&amp; travel(node.left) node.right &amp;&amp; travel(node.right) } travel(root) return res };</description></item><item><title>105-从前序遍历与中序遍历序列构造二叉树-1</title><link>https://siqi-liu.com/zh/post/105-%E4%BB%8E%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%E4%B8%8E%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91-1/</link><pubDate>Wed, 27 Jan 2021 00:00:00 +0000</pubDate><guid>https://siqi-liu.com/zh/post/105-%E4%BB%8E%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%E4%B8%8E%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91-1/</guid><description>递归 前序遍历：中-左-右 中序遍历：左-中-右 因此可以先从前序遍历找到根元素，再由中序确定左右子树的个数 var buildTree = function(preorder, inorder) { if (preorder.length === 0 || inorder.length=== 0) return null let nodeVal = preorder.shift() let node = new TreeNode(nodeVal) let index = inorder.indexOf(nodeVal) node.left = buildTree(preorder.slice(0,index), inorder.slice(0,index)) node.right = buildTree(preorder.slice(index),inorder.slice(index + 1)) return node };优化 slice 是很耗性能的，其实没有必要传递数组，函数传递指针即可 var buildTree = function(preorder, inorder) { var helper = function (p_start,p_end,i_start,i_end) { if (p_start &gt; p_end || i_start &gt; i_end ) return null let nodeVal = preorder[p_start] let node = new TreeNode(nodeVal) let index = inorder.indexOf(nodeVal) let left = index - i_start node.left = helper(p_start + 1 , p_start + left , i_start ,index - 1) node.right = helper(p_start + left + 1,p_end, index + 1,i_end) return node } return helper(0,preorder.length - 1,0,preorder.length - 1) };总结： 利用 前序和中序的性质，定位根节点，求出左右子树的个数。然后递归构建左右子树即可</description></item></channel></rss>