二叉搜索树:
1. 剑指 Offer 36. 二叉搜索树与双向链表
题目描述:将二叉搜索树转换为双向循环链表
解题思路:
关键点1:二叉搜索树的中序遍历 DFS
关键点2:双向链表
关键点3:循环链表
代码:
var treeToDoublyList = function(root) { // 声明一个空的头结点 var head =null; // 初始化一个pre指向头结点 var pre=head; // 中序遍历二叉搜索树的每一个节点 function dfs(root){ if(root==null)return; // 遍历左子树 dfs(root.left); // root指向的是当前节点,如果pre为空代表此时指向头结点,需要将head指向当前节点(也就是头结点) if(pre==null){ head=root; }else{ // 前一个节点的后继为当前节点 pre.right=root; // 当前结点的前驱为前一个节点 root.left=pre; } // 移动前一个节点到当前节点 pre=root; // 遍历右子树 dfs(root.right); } // 注意树为空的情况 if(root==null)return null; dfs(root); // 更改头结点的指向 head.left=pre; pre.right=head; return head; };
题目描述:根据给定数组判断是否是二叉搜索树
解题思路:
关键点1:二叉搜索树的后续遍历的最后一个值是根节点 DFS
关键点2:找到比根节点小的最后一个元素,记录他的索引位置,以此为分界限,此节点向左为左子树,此节点向右(包括此节点为右子树),只有当左右子树也是二叉搜索树才是二叉搜索树
代码:
var verifyPostorder = function(postorder) { if(postorder.length==0)return true; return helper(postorder); }; function helper(arr){ if(arr.length<=1){ return true } var root = arr.pop(); var i=0; for(i=0;i<arr.length;i++){ if(arr[i]>root){ break; } } for(var j=i;j<arr.length;j++){ if(arr[j]<root){return false;} } return helper(arr.slice(0,i)) && helper(arr.slice(i,arr.length)); }
题目描述:根据给定的先序遍历和中序遍历数组还原二叉树
解题思路:
关键点1:前序遍历的首个元素即为根节点root
的值
关键点2:在中序遍历中搜索根节点root
的索引 ,可将中序遍历划分为[ 左子树 | 根节点 | 右子树 ]
关键点3:根据中序遍历中的左(右)子树的节点数量,可将前序遍历划分为[ 根节点 | 左子树 | 右子树 ]
关键点4:根据子树特点,我们可以通过同样的方法对左(右)子树进行划分,每轮可确认三个节点的关系 。此递推性质让我们联想到用 递归方法 处理
代码
var buildTree = function(preorder, inorder) { if(preorder.length==0 || inorder.length==0) return null; var root = new TreeNode(preorder[0]); var index=inorder.indexOf(root.val); var leftArr = inorder.slice(0,index); var preL = preorder.slice(1,index+1); var rightArr = inorder.slice(index+1); var preR = preorder.slice(index+1); root.left = buildTree(preL, leftArr); root.right = buildTree(preR, rightArr); return root; };
题目描述:广度优先搜索 BFS
解题思路:
关键点1:DFS广度优先搜索
关键点2:将每一层的节点存到数组中
关键点3:依次弹出每一个节点,将其左右子节点存入数组,将当前节点的值存到结果数组中
代码
var levelOrder = function(root) { if(root==null){ return []; } var curr=[root]; var result=[]; while(curr.length){ var m=curr.shift(); result.push(m.val) if(m.left){ curr.push(m.left); } if(m.right){ curr.push(m.right); } } return result; };
5.剑指 Offer 32 - III. 从上到下打印二叉树 III
题目描述:广度优先搜索 BFS
解题思路:
关键点1:BFS广度优先搜索
关键点2:将每一层的节点存到curr数组中
关键点3:遍历当前层节点数组,将当前节点的值存到currValue数组中,将当前节点的左右子节点存到nextNode节点数组中
关键点4:设置标志位,奇数层尾部插入数组,偶数层头部插入数组
代码
var levelOrder = function(root) { if(root==null) return []; var curr=[root]; // console.log(curr) var flag=1; var tmpValue=[]; var nextNode =[]; var result=[]; while(curr.length){ tmpValue=[]; nextNode=[]; for(var i=0;i<curr.length;i++){ if(flag==1){ tmpValue.push(curr[i].val); }else{ tmpValue.unshift(curr[i].val); } if(curr[i].left){ nextNode.push(curr[i].left); } if(curr[i].right){ nextNode.push(curr[i].right); } } flag *=-1; result.push(tmpValue); if(nextNode){ curr=nextNode; } } return result; };
题目描述:返回二叉树中和为某一值的所有路径的数组
解题思路:
关键点1:深度优先搜索、先序遍历
关键点2:当路径和为target并且到达叶子节点时存入结果数组,注意存入数组的浅拷贝,因为路径数组path会一直在发生变化
关键点3:注意回溯
代码
var pathSum = function(root, sum) { var res =[]; // 存放每条路径的数组 var path=[]; if(!root)return []; function helper(root,target){ if(root==null)return; path.push(root.val); target -= root.val; // 对于每一个节点都要判断,如果target减为0并且为叶子节点(左右子树均为空)的时候满足要求,就存入结果数组中 if(target==0 && root.left==null && root.right==null){ // 这里存放的是path的拷贝,否则path会随着元素的变化而变化 res.push([...path]); } helper(root.left,target); helper(root.right,target); // 注意这里一定要弹出当前元素,这是回溯算法关键的一点 path.pop(); } helper(root,sum); return res; }; };
题目描述:返回二叉树中和为某一值的所有路径的数组
解题思路:
关键点1:中序遍历
关键点2:如果是二叉搜索树,判断中序遍历数组是否为升序即可
关键点3:LeetCode中不要定义全局变量
代码
var isValidBST = function(root) { var res=[]; function inOrder(root){ if(root==null) return []; if(root.left){ inOrder(root.left); } res.push(root.val); if(root.right){ inOrder(root.right); } } inOrder(root); for (let i = 0; i < res.length - 1; i++) { if (res[i + 1] <= res[i]) return false; } return true; };
// 方法二 时间上更高效的方法,空间上不需要开辟新数组 // 也是中序遍历的方法 // 但是注意在LeetCode上提交的时候不要把 pre变量定义为全局变量 var isValidBST = function(root){ var pre=-Infinity; function helper(root){ if(root==null)return true; if(!helper(root.left)){ return false; } if(root.val<=pre){ return false; } pre=root.val; if(!helper(root.right)){ return false; } return true; } return helper(root); }
题目描述:给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种 (理解不够深刻)
解题思路:
关键点1:动态规划、递归
关键点2:我们可以遍历每个数字 ii,将该数字作为树根,将 1 cdots (i-1)1⋯(i−1) 序列作为左子树,将 (i+1) cdots n(i+1)⋯n 序列作为右子树。
接着我们可以按照同样的方式递归构建左子树和右子树
代码
var numTrees = function(n) { // 开辟一个数组,用0初始化 var dp= new Array(n+1).fill(0); // 初始化 dp[0]=1; dp[1]=1; // 分别以每个元素i为根节点值 for(var i=2;i<=n;i++){ for(var j=1;j<=i;j++){ dp[i] += dp[j-1]*dp[(i-j)]; } } return dp[n]; };
题目描述:如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 1 ,我们就称这棵二叉搜索树是 平衡的
解题思路:
关键点1:二叉树的重构、中序遍历
关键点2:中序遍历有序数组,然后奖盖有序数组重构为二叉平衡树
关键点3:重构的时候为保证左右子树高度差小于等于1,取数组中间值作为根节点,然后递归求得左右子树
代码
var balanceBST = function(root) { var res = []; function dfs(root){ if(root==null) return null; dfs(root.left); res.push(root.val); dfs(root.right); } dfs(root); function reBuild(arr){ if(arr.length==0){ return null; } if(arr.length==1){ return new TreeNode(arr[0]); } var mid=Math.floor(arr.length/2); var newRoot = new TreeNode(arr[mid]); newRoot.left = reBuild(arr.slice(0,mid)); newRoot.right = reBuild(arr.slice(mid+1)); return newRoot; } return reBuild(res); };
题目描述:二叉搜索树的巨大优势就是:在平均情况下,能够在 O(logN) 的时间内完成搜索和插入元素
解题思路:
关键点1:若val > node.val
,插入到右子树。
关键点2:若val < node.val
,插入到左子树。
代码
// 递归 var insertIntoBST = function(root, val) { if(root==null) return new TreeNode(val); if(root.val < val){ root.right = insertIntoBST(root.right,val); }else{ root.left = insertIntoBST(root.left,val); } return root; }; // 迭代 var insertIntoBST = function(root, val) { if(root==null) return new TreeNode(val); var parent = root,p=root; while(p!=null){ parent = p; p=p.val<val ? p.right:p.left; } if(parent.val<val){ parent.right = new TreeNode(val); }else{ parent.left = new TreeNode(val); } return root; };
题目描述:二叉搜索树的巨大优势就是:在平均情况下,能够在 O(logN) 的时间内完成搜索和插入元素
解题思路:分为三种情况:
1.如果要删除的节点在左子树,递归左子树
2.如果要删除的节点在右子树,递归右子树
3.要删除的节点为根节点:再分情况判断左右子树是否为空,当都不为空的时候,找到右子树上的最小节点,将根节点的值替换为这个最小值,然后删除这个最小节点
var deleteNode = function(root, key) { if(root==null){ return root; } // 要删除的节点在右子树 if(root.val<key){ root.right = deleteNode(root.right,key); }else if(root.val>key){ // 要删除的节点在左子树 root.left = deleteNode(root.left,key); }else{ // 要删除的节点为根节点 // 如果左右子树均为空 if(root.left==null && root.right==null){ root = null; } // 如果右子树为空 else if(root.right==null && root.left!=null){ root = root.left; } // 如果左子树为空 else if(root.left==null && root.right!=null){ root = root.right; }else { // 如果左右子树均不为空 var tmp = root.right; // 找到右子树上最小的数 while(tmp.left!==null){ tmp = tmp.left; } // 将根节点的值替换为这个最小的值 root.val=tmp.val; // 删除有字数上这个最小的值 root.right = deleteNode(root.right, root.val); } } return root; };