diff --git a/README.md b/README.md index b7c03b0..6a5541f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,53 @@ # leetcode -## [Array](array.md) -## [String](string.md) -## [Linked List](linkedlist.md) +## 数组Array -##[Queue](queue.md) \ No newline at end of file + + +## 字符串String + + + +## 链表Linked List + + + +##队列Queue + + + +## 堆 + +[堆](heap/heap.md) + +- 堆的定义 +- 堆的算法 +- 堆的排序 +- 相关题目 + + + +## 优先队列 + + + +## 二叉树Binary Tree + +[二叉树](binaryTree/binaryTree.md) + +- 什么是二叉树 +- 二叉树的遍历 +- 递归解决二叉树问题 +- 相关题目 + + + +## 二叉搜索树Binary Search Tree + +[二叉搜索树](bst/bst.md) + + * 什么是二叉搜索树 + * 二叉搜索树的基本操作 + * 相关题目 \ No newline at end of file diff --git a/binaryTree/binaryTree.md b/binaryTree/binaryTree.md new file mode 100644 index 0000000..8bb3e5c --- /dev/null +++ b/binaryTree/binaryTree.md @@ -0,0 +1,1205 @@ +### 二叉树 + +#### 什么是二叉树 + +树是一种数据结构,树中的每个节点都包含一个键值和所有子节点的列表,对于二叉树来说,每个节点最多有两个子树结构,分别称为左子树和右子树。 + + + +#### 二叉树的遍历 + +根据访问root节点的先后顺序,可分为前序遍历,中序遍历和后序遍历。 + +**前序遍历** + +前序遍历是指先访问root节点,然后左子树,最后是右子树。 + +```go +// 前序遍历的伪代码 +// 递归版 +func preOrder(root *TreeNode) []int { + res := make([]int, 0) + if root != nil { + res = append(res, root.Val) + res = append(res, preOrder(root.Left)...) + res = append(res, preOrder(root.Right)...) + } + return res +} +``` + +**中序遍历** + +前序遍历是指访问左子树,然后root节点,最后是右子树。 + +```go +// 前序遍历的伪代码 +// 递归版 +func inOrder(root *TreeNode) []int { + res := make([]int, 0) + if root != nil { + res = append(res, inOrder(root.Left)...) + res = append(res, root.Val) + res = append(res, inOrder(root.Right)...) + } + return res +} +``` + +**后序遍历** + +前序遍历是指先访问左子树,然后右子树,最后是root节点。 + +```go +// 前序遍历的伪代码 +// 递归版 +func postOrder(root *TreeNode) []int { + res := make([]int, 0) + if root != nil { + res = append(res, postOrder(root.Left)...) + res = append(res, postOrder(root.Right)...) + res = append(res, root.Val) + } + return res +} +``` + +**层序遍历** + +层序遍历是指逐层遍历树的结构,也称为广度优先搜索,算法从一个根节点开始,先访问根节点,然后遍历其相邻的节点,其次遍历它的二级、三级节点。 + +借助栈数据结构,可以帮助我们实现层序遍历。 + +```go +// date 2020/03/21 +// 层序遍历 +// bfs广度优先搜索 +func levelOrder(root *TreeNode) [][]int { + res := make([][]int, 0) + stack := make([]*TreeNode, 0) + stack = append(stack, root) + for len(stack) != 0 { + lres, n := make([]int, 0), len(stack) + for i := 0; i < n; i++ { + if stack[i] != nil { + stack = append(stack, stack[i].Left) + stack = append(stack, stack[i].Right) + lres = append(lres, stack[i].Val) + } + } + stack = stack[n:] + if len(lres) > 0 { + res = append(res, lres) + } + } + return res +} +// dfs深度优先搜索 +func levelOrder(root *TreeNode) [][]int { + res := make([][]int, 0) + var dfs_ func(root *TreeNode, level int) + dfs_ = func(root *TreeNode, level int) { + if root == nil { return } + if level == len(res) { + res = append(res, make([]int, 0)) + } + res[level] = append(res[level], root.Val) + dfs_(root.Left, level+1) + dfs_(root.Right, level+1) + } + + dfs_(root, 0) + return res +} +``` + + + +#### 递归解决树的问题 + +递归通常是解决树的相关问题最有效和最常用的方法之一,分为**自顶向下**和**自底向上**两种。 + +**自顶向下**的解决方案 + +自顶向下意味着在每个递归层级,需要先计算一些值,然后递归调用并将这些值传递给子节点,视为前序遍历。参见题目【二叉树的最大深度】 + +**自底向上**的解决方案 + +自底向上意味着在每个递归层级,需要先对子节点递归调用函数,然后根据返回值和根节点本身得到答案。 + +**总结**: + +当遇到树的问题时,先思考一下两个问题: + +> 1.你能确定一些参数,从该节点自身解决出发寻找答案吗? +> +> 2.你可以使用这些参数和节点本身的值来决定什么应该是传递给它子节点的参数吗? + +如果答案是肯定的,那么可以尝试使用自顶向下的递归来解决问题。 + +当然也可以这样思考:对于树中的任意一个节点,如果你知道它子节点的结果,你能计算出该节点的答案吗?如果答案是肯定的,可以尝试使用自底向上的递归来解决问题。 + + + +#### 相关题目 + +- 从中序和后序遍历序列构造二叉树[前序和中序] +- 98 验证二叉搜索树【M】 +- 104 二叉树的最大深度【E】 +- 111 二叉树的最小深度【E】 +- 101 对称二叉树【E】 +- 156 上下翻转二叉树 +- 257 二叉树的所有路径 +- 270 最接近的二叉搜素树值【E】 +- 543 二叉树的直径【E】 +- 545 二叉树的边界【M】 +- 563 二叉树的坡度【E】 +- 617 合并二叉树【E】 +- 654 最大二叉树【M】 +- 655 输出二叉树【M】 +- 662 二叉树的最大宽度【M】 +- 814 二叉树剪枝【M】 +- 993 二叉树的堂兄弟节点 +- 1104 二叉树寻路【M】 +- 面试题27 二叉树的镜像 +- 路径总和 +- 不同的二叉搜索树 + +#### 从中序和后序遍历序列构造二叉树[前序和中序] + +思路分析 + +利用根节点在中序遍历的位置 + +```go +// 递归, 中序和后序 +func buildTree(inorder, postorder []int) *TreeNode { + if len(postorder) == 0 {return nil} + n := len(postorder) + root = &TreeNode{ + Val: postorder[n-1], + } + index := -1 + for i := 0; i < len(inorder); i++ { + if inorder[i] == root.Val { + index = i + break + } + } + if index >= 0 && index < n { + root.Left = buildTree(inorder[:index], postorder[:index]) + root.Right = buildTree(inorder[index+1:], postorder[index:n-1])) + } + return root +} +// 递归,前序和中序 +func buildTree(preorder, inorder []int) *TreeNode { + if 0 == len(preorder) {return nil} + n := len(preorder) + root := &TreeNode{ + Val: preorder[0], + } + index := -1 + for i := 0; i < len(inorder); i++ { + if inorder[i] == root.Val{ + index = i + break + } + } + if index >= 0 && index < n { + root.Left = buildTree(preorder[1:index+1], inorder[:index]) + root.Right = buildTree(preorder[index+1:], inorder[index+1:]) + } + return root +} +``` + +#### 98 验证二叉搜索树 + +题目要求:https://leetcode-cn.com/problems/validate-binary-search-tree/ + +算法分析: + +```go +// date 2020/03/21 +// 算法1:遍历其中序遍历序列,并判断其序列是否为递增 +func isValidBST(root *TreeNode) bool { + nums := inOrder(root) + for i := 0; i < len(nums)-1; i++ { + if nums[i] >= nums[i+1] { return false } + } + return true +} + +func inOrder(root *TreeNode) []int { + if root == nil { return []int{} } + res := make([]int, 0) + if root.Left != nil { res = append(res, inOrder(root.Left)...) } + res = append(res, root.Val) + if root.Right != nil { res = append(res, inOrder(root.Right)...) } + return res +} +// 算法2:递归 +// 时间复杂度O(N) +func isValidBST(root *TreeNode) bool { + if root == nil { return true } + var isValid func(root *TreeNode, min, max float64) bool + isValid = func(root *TreeNode, min, max float64) bool { + if root == nil { return true } + if float64(root.Val) <= min || float64(root.Val) >= max { return false } + return isValid(root.Left, min, float64(root.Val)) && isValid(root.Right, float64(root.Val), max) + } + return isValid(root, math.Inf(-1), math.Inf(0)) +} + +// 算法3 +// 其思想是只保留中序遍历的前继节点,判断中序遍历序列是否为递增序列 +// 时间复杂度O(N),空间复杂度O(1),最优解 +func isValidBST(root *TreeNode) bool { + if root == nil { return true } + queue := make([]*TreeNode, 0) + preValue := math.Inf(-1) + + for len(queue) != 0 || root != nil { + // 将左子树全部放入队列 + for root != nil { + queue = append(queue, root) + root = root.Left + } + // 去除最后一个节点 + root = queue[len(queue)-1] + queue = queue[:len(queue)-1] + // 判断其是否大于前继节点 + if float64(root.Val) <= preValue { return false } + // 更新前继节点和前继节点的值 + preValue = float64(root.Val) + root = root.Right + } + return true +} +``` + + + +#### 104 二叉树的最大深度 + +题目要求:给定一棵二叉树,找出其最大深度。 + +思路分析 + +算法:递归,递归调用分别得到左子树和右子树的深度,然后比较取最大值,并+1返回结果 + +```go +// 自底向上的思想 +// 递归 +func maxDepth(root *TreeNode) int { + if root == nil { return 0 } + l, r := maxDepth(root.Left), maxDepth(root.Right) + if l > r { return l+1 } + return r + 1 +} +// bfs广度优先搜索 +func maxDepth(root *TreeNode) int { + if root == nil {return 0} + queue := make([]*TreeNode, 0) + queue = append(queue, root) + depth, n := 0, 0 + for len(queue) != 0 { + n = len(queue) + for i := 0; i < n; i++ { + if queue[i].Left != nil { + queue = append(queue, queue[i].Left) + } + if queue[i].Right != nil { + queue = append(queue, queue[i].Right) + } + } + queue = queue[n:] + depth++ + } + return depth +} +// dfs深度优先搜索 +func maxDepth(root *TreeNode) int { + if root == nil { return 0 } + var dfs_ func(root *TreeNode, level int) int + dfs_ = func(root *TreeNode, level int) int { + if root == nil { return level } + if root.Left == nil && root.Right == nil { return level } + if root.Left == nil { return dfs_(root.Right, level+1) } + if root.Right == nil { return dfs_(root.Left, level+1) } + l, r := dfs_(root.Left, level+1), dfs_(root.Right, level+1) + if l > r { return l } + return r + } + return dfs_(root, 1) +} +``` + +#### 111 二叉树的最小深度 + +题}要求:https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/ + +算法分析 + +```go +// date 2020/03/21 +// 递归算法 +func minDepth(root *TreeNode) int { + if root == nil { return 0 } + if root.Left == nil { return 1 + minDepth(root.Right) } + if root.Right == nil { return 1 + minDepth(root.Left) } + l, r := minDepth(root.Left), minDepth(root.Right) + if l > r { return r+1 } + return l+1 +} +// bfs广度优先搜索 +func minDepth(root *TreeNode) int { + if root == nil { return 0 } + queue := make([]*TreeNode, 0) + queue = append(queue, root) + var res int + for len(queue) != 0 { + n := len(queue) + res += 1 + for i := 0; i < n; i++ { + if queue[i].Left == nil && queue[i].Right == nil { return res } + if queue[i].Left != nil { queue = append(queue, queue[i].Left) } + if queue[i].Right != nil { queue = append(queue, queue[i].Right) } + } + queue = queue[n:] + } + return res +} +// dfs深度优先搜索 +func minDepth(root *TreeNode) int { + if root == nil { return 0 } + var dfs_ func(root *TreeNode, level int) int + dfs_ = func(root *TreeNode, level int) int { + // 如果该节点为空或者为叶子节点,则直接返回 + if root == nil { return level } + if root.Left == nil && root.Right == nil { return level } + // 如果该节点的左子树为空,则在右子树中寻找结果 + if root.Left == nil { return dfs_(root.Right, level+1) } + // 如果该节点的右子树为空,则在左子树中寻找结果 + if root.Right == nil { return dfs_(root.Left, level+1) } + // 左子树和右子树均不为空,则返回两者较小值 + l := dfs_(root.Left, level+1) + r := dfs_(root.Right, level+1) + if l < r { return l } + return r + } + return dfs_(root, 1) +} +``` + + + +#### 对称二叉树 + +思路分析 + +算法1: + +如果一个树的左子树和右子树对称,那么这个树就是对称的。那么两个树在什么情况下互为镜像呢? + +> 如果两个树同时满足两个条件,则互为镜像。 +> +> 1.它们的根节点具有相同的值; +> +> 2.每个树的右子树和另一个树的左子树对称。 + +```go +// 算法一 +func isSymmetrics(root *TreeNode) bool { + return isMirror(root, root) +} +func isMirror(t1, t2 *TreeNode) bool { + if t1 == nil && t2 == nil {return true} + if t1 == nil || t2 == nil {return false} + return t1.Val == t2.Val && isMirror(t1.Left, t2.Right) && isMirror(t1.Right, t2.Left) +} +``` + +算法2:类似层序遍历,将子树的左右结点依次放入队列,通过判断队列中的连续的两个值是否相同来确认是否对称。 + +```go +// 算法二 +func isSymmetric(root *TreeNode) bool { + if root == nil {return true} + queue := make([]*TreeNode, 0) + queue = append(queue, root, root) + for 0 != len(queue) { + n := len(queue) + for i := 0; i < n; i += 2 { + t1, t2 := queue[i], queue[i+1] + if t1 == nil && t2 == nil {continue} + if t1 == nil || t2 == nil {return false} + if t1.Val != t2.Val {return false} + queue = append(queue, t1.Left, t2.Right, t1.Right, t2.Left) + } + queue = queue[n:] + } + return true +} +``` + +#### 105 从前序与中序遍历序列构造二叉树 + +算法分析:1)从中序序列中找到根节点,递归左右子树。 + +```go +// date 2020/02/26 +func buildTree(preorder []int, inorder []int) *TreeNode { + if len(preorder) == 0 { return nil } + root := &TreeNode{Val: preorder[0]} + index := 0 + for i, v := range inorder { + if v == preorder[0] { + index = i + break + } + } + root.Left = buildTree(preorder[1:index+1], inorder[:index]) + root.Right = buildTree(preorder[index+1:], inorder[index+1:]) + return root +} +``` + + + +#### 156 上下翻转二叉树 + +题目要求:给定一个二叉树,其中所有的右节点要么是具有兄弟节点(拥有相同父节点的左节点)的叶节点,要么为空,将此二叉树上下翻转并将它变成一棵树, 原来的右节点将转换成左叶节点。返回新的根。 + +算法分析: + +```go +// date 2020/02/25 +func upsideDownBinaryTree(root *TreeNode) *TreeNode { + var parent, parent_right *TreeNode + for root != nil { + // 0.保存当前left, right + root_left := root.Left + root.Left = parent_right // 重新构建root,其left为上一论的right + parent_right := root.Right // 保存right + root.Right = parent // 重新构建root,其right为上一论的root + parent = root + root = root_left + } + return parent +} +``` + +#### 199 二叉树的右视图 + +算法分析:层序遍历的最后一个节点值。 + +```go +// date 2020/02/26 +func rightSideView(root *TreeNode) []int { + res := make([]int, 0) + if root == nil { return res } + stack := make([]*TreeNode, 0) + stack = append(stack, root) + for len(stack) != 0 { + n := len(stack) + res = append(res, stack[0].Val) + for i := 0; i < n; i++ { + if stack[i].Right != nil { stack = append(stack, stack[i].Right) } + if stack[i].Left != nil { stack = append(stack, stack[i].Left) } + } + stack = stack[n:] + } + return res +} +``` + + + +#### 257 二叉树的所有路径 + +题目要求:给定一个二叉树,返回所有从根节点到叶子节点的路径。 + +算法分析: + +```go +// date 2020/02/23 +func binaryTreePaths(root *TreeNode) []string { + res := make([]string, 0) + if root == nil { return res } + if root.Left == nil && root.Right == nil { + res = append(res, fmt.Sprintf("%d", root.Val)) + return res + } else if root.Left != nil { + for _, v := range binaryTreePaths(root.Left) { + res = append(res, fmt.Sprintf("%d->%s", root.Val, v)) + } + } else if root.Right != nil { + for _, v := range binaryTreePaths(root.Right) { + res = append(res, fmt.Sprintf("%d->%s", root.Val, v)) + } + } + return res +} +``` + +```go +class MinStack { +public: + /** initialize your data structure here. */ + stack _stack; + int _min = INT_MAX; + MinStack() { + + } + + void push(int x) { + if(_min >= x){ + if(!_stack.empty()){ + _stack.push(_min); + } + _min = x; + } + _stack.push(x); + } + + void pop() { + if(_stack.empty()) + return; + if(_stack.size() == 1) + _min = INT_MAX; + else if(_min == _stack.top()){//下一个元素是下一个最小值 + _stack.pop(); + _min = _stack.top(); + } + _stack.pop(); + } + + int top() { + return _stack.top(); + } + + int getMin() { + return _min; + } +}; +``` + + + +#### 270 最接近的二叉搜索树值 + +题目要求:给定一个不为空的二叉搜索树和一个目标值 target,请在该二叉搜索树中找到最接近目标值 target 的数值。 + +算法分析:使用层序遍历,广度优先搜索。 + +```go +// date 2020/02/23 +func closestValue(root *TreeNode, target float64) int { + if root == nil { return 0 } + var res int + cur := math.Inf(1) + stack := make([]*TreeNode, 0) + stack = append(stack, root) + for len(stack) != 0 { + n := len(stack) + for i := 0; i < n; i++ { + if math.Abs(float64(stack[i].Val) - target) < cur { + cur = math.Abs(float64(stack[i].Val) - target) + res = stack[i].Val + } + if stack[i].Left != nil { stack = append(stack, stack[i].Left) } + if stack[i].Right != nil { stack = append(stack, stack[i].Right) } + } + stack = stack[n:] + } + return res +} +``` + +#### 538 把二叉树转换成累加树 + +算法:逆序的中序遍历,查找。 + +```go +// date 2020/02/26 +func convertBST(root *TreeNode) *TreeNode { + var sum int + decOrder(root, &sum) + return root +} + +func decOrder(root *TreeNode, sum *int) { + if root == nil { return } + decOrder(root.Right, sum) + *sum += root.Val + root.Val = *sum + decOrder(root.Left, sum) +} +``` + + + +#### 543 二叉树的直径 + +题目要求:给定一棵二叉树,返回其直径。 + +算法分析:左右子树深度和的最大值。 + +```go +// date 2020/02/23 +func diameterOfBinaryTree(root *TreeNode) int { + v1, _ := findDepth(root) + return v1 +} + +func findDepth(root *TreeNode) (int, int) { + if root == nil { return 0, 0 } + var v int + v1, l := findDepth(root.Left) + v2, r := findDepth(root.Right) + if v1 > v2 { + v = v1 + } else { + v = v2 + } + if l+r > v { v = l+r } + if l > r { return v, l+1} + return v, r+1 +} +``` + + + +#### 545 二叉树的边界 + +题目要求:给定一个二叉树,按逆时针返回其边界。 + +思路分析:分别求其左边界,右边界,和所有的叶子节点,用visited辅助去重,算法如下: + +```go +// date 2020/02/23 +func boundaryOfBinaryTree(root *TreeNode) []int { + if root == nil { return []int{} } + res := make([]int, 0) + left, right := make([]*TreeNode, 0), make([]*TreeNode, 0) + visited := make(map[*TreeNode]int, 0) + res = append(res, root.Val) + visited[root] = 1 + // find left node + pre := root.Left + for pre != nil { + left = append(left, pre) + visited[pre] = 1 + if pre.Left != nil { + pre = pre.Left + } else { + pre = pre.Right + } + } + // find right node + pre = root.Right + for pre != nil { + right = append(right, pre) + visited[pre] = 1 + if pre.Right != nil { + pre = pre.Right + } else { + pre = pre.Left + } + } + leafs := findLeafs(root) + // make res + for i := 0; i < len(left); i++ { + res = append(res, left[i].Val) + } + for i := 0; i < len(leafs); i++ { + if _, ok := visited[leafs[i]]; ok { continue } + res = append(res, ,leafs[i].Val) + } + for i := len(right) - 1; i >= 0; i-- { + res = append(res, right[i].Val) + } + return res +} + +func findLeafs(root *TreeNode) []*TreeNode { + res := make([]*TreeNode, 0) + if root == nil { return res } + if root.Left == nil && root.Right == nil { + res = append(res, root) + return res + } + res = append(res, findLeafs(root.Left)...) + res = append(res, findLeafs(root.Right)...) + return res +} +``` + + + +#### 563 二叉树的坡度 + +题目要求:给定一棵二叉树,返回其坡度。 + +算法一:根据定义,递归计算。 + +```go +func findTilt(root *TreeNode) int { + if root == nil || root.Left == nil && root.Right == nil { return 0 } + left := sum(root.Left) + right := sum(root.Right) + var res int + if left > right { + res = left - right + } else { + res = right - left + } + return res + findTilt(root.Left) + findTilt(root.Right) +} + +func sum(root *TreeNode) int { + if root == nil { return 0 } + return sum(root.Left) + sum(root.Right) + root.Val +} +``` + +#### 617 合并二叉树 + +给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。 + +你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。 + +```go +// date 2020/02/23 +// 递归 +func mergeTrees(t1, t2 *TreeNode) *TreeNode { + if t1 == nil { return t2 } + if t2 == nil { return t1 } + t1.Val += t2.Val + t1.Left = mergeTrees(t1.Left, t2.Left) + t1.Right = mergeTrees(t1.Right, t2.Right) + return t1 +} +``` + +#### 654 最大二叉树 + +题目要求: + +给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下: + +二叉树的根是数组中的最大元素。 + 左子树是通过数组中最大值左边部分构造出的最大二叉树。 + 右子树是通过数组中最大值右边部分构造出的最大二叉树。 + 通过给定的数组构建最大二叉树,并且输出这个树的根节点。 + +算法分析: + +找到数组中的最大值,构建根节点,然后递归调用。 + +```go +// date 2020/02/25 +// 递归版 +func constructMaximumBinaryTree(nums []int) *TreeNode { + if len(nums) == 0 { return nil } + if len(nums) == 1 { return &TreeNode{Val: nums[0]} } + p := 0 + for i, v := range nums { + if v > nums[p] { p = i } + } + root := &TreeNode{Val: nums[p]} + root.Left = constructMaximumBinaryTree(nums[:p]) + root.Right = constructMaximumBinaryTree(nums[p+1:]) + return root +} +``` + +#### 655 输出二叉树 + +题目要求:将二叉树输出到m*n的二维字符串数组中。 + +算法思路:先构建好res,然后逐层填充。 + +```go +// date 2020/02/25 +func printTree(root *TreeNode) [][]string { + depth := findDepth(root) + length := 1 << depth - 1 + res := make([][]string, depth) + for i := 0; i < depth; i++ { + res[i] = make([]string, length) + } + fill(res, root, 0, 0, length) +} + +func fill(res [][]string, t *TreeNode, i, l, r int) { + if t == nil { return } + res[i][(l+r)/2] = fmt.Sprintf("%d", root.Val) + fill(res, t.Left, i+1, l, (l+r)/2) + fill(res, t.Right, i+1, (l+r+1)/2, r) +} + +func findDepth(root *TreeNode) int { + if root == nil { return 0 } + l, r := findDepth(root.Left), findDepth(root.Right) + if l > r { return l+1 } + return r+1 +} +``` + +#### 662 二叉树的最大宽度 + +题目要求:给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。 + +每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。 + 链接:https://leetcode-cn.com/problems/maximum-width-of-binary-tree + +算法分析:使用栈stack逐层遍历,计算每一层的宽度,注意全空节点的时候要返回。时间复杂度O(n),空间复杂度O(logn)。 + +```go +// date 2020/02/26 +func widthOfBinaryTree(root *TreeNode) int { + res := 0 + stack := make([]*TreeNode, 0) + stack = append(stack, root) + var n, i, j int + var isAllNil bool + for 0 != len(stack) { + i, j, n = 0, len(stack)-1, len(stack) + // 去掉每一层两头的空节点 + for i < j && stack[i] == nil { i++ } + for i < j && stack[j] == nil { j-- } + // 计算结果 + if j-i+1 > res { res = j-i+1 } + // 添加下一层 + isAllNil = true + for ; i <= j; i++ { + if stack[i] != nil { + stack = append(stack, stack[i].Left, stack[i].Right) + isAllNil = false + } else { + stack = append(stack, nil, nil) + } + } + if isAllNil { break } + stack = stack[n:] + } + return res +} +``` + + + +#### 669 修剪二叉搜索树 + +题目要求:给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。 + +算法分析: + +```go +// date 2020/02/25 +func trimBST(root *TreeNode, L, R int) *TreeNode { + if root == nil { return nil } + if root.Val < L { return trimBST(root.Right, L, R) } + if root.Val > R { return trimBST(root.Left, L, R) } + root.Left = trimBST(root.Left, L, R) + root.Right = trimBST(root.Right, L, R) + return root +} +``` + +#### 814 二叉树剪枝 + +题目要求:给定一个二叉树,每个节点的值要么是0,要么是1。返回移除了所有不包括1的子树的原二叉树。 + +算法分析: + +使用containOne()函数判断节点以及节点的左右子树是否包含1。 + +```go +// date 2020/02/25 +func pruneTree(root *TreeNode) *TreeNode { + if containsOne(root) { return root } + return nil +} + +func containsOne(root *TreeNode) bool { + if root == nil { return false } + l, r := containsOne(root.Left), containsOne(root.Right) + if !l { root.Left = nil } + if !r { root.Right = nil } + return root.Val == 1 || l || r +} +``` + +#### 993 二叉树的堂兄弟节点 + +题目要求:给定一个二叉树和两个节点元素值,判断两个节点值是否是堂兄弟节点。 + +堂兄弟节点的定义:两个节点的深度一样,但父节点不一样。 + +算法分析: + +使用findFatherAndDepth函数递归找到每个节点的父节点和深度,并进行比较。 + +```go +// date 2020/02/25 +func isCousins(root *TreeNode, x, y int) bool { + xf, xd := findFatherAndDepth(root, x) + yf, yd := findFatherAndDepth(root, y) + return xd == yd && xf != yf +} +func findFatherAndDepth(root *TreeNode, x int) (*TreeNode, int) { + if root == nil || root.Val == x { return nil, 0 } + if root.Left != nil && root.Left.Val == x { return root, 1 } + if root.Right != nil && root.Right.Val == y { return root, 1 } + l, lv := findFatherAndDepth(root.Left, x) + r, rv := findFatherAndDepth(root.Right, x) + if l != nil { return l, lv+1 } + return r, rv+1 +} +``` + + + +#### 1104 二叉树寻路 + +题目要求:https://leetcode-cn.com/problems/path-in-zigzag-labelled-binary-tree/ + +算法分析: + +```go +// date 2020/02/25 +func pathInZigZagTree(label int) []int { + res := make([]int, 0) + for label != 1 { + res = append(res, label) + label >>= 1 + y := highBit(label) + lable = ^(label-y)+y + } + res = append(res, 1) + i, j := 0, len(res)-1 + for i < j { + t := res[i] + res[i] = res[j] + res[j] = t + i++ + j-- + } + return res +}label = label ^(1 << (label.bit_length() - 1)) - 1 + +func bitLen(x int) int { + res := 0 + for x > 0 { + x >>= 1 + res++ + } + return res +} +``` + + + +#### 面试题27 二叉树的镜像 + +题目要求:给定一棵二叉树,返回其镜像二叉树。 + +```go +// date 2020/02/23 +func mirrorTree(root *TreeNode) *TreeNode { + if root == nil { return root } + left := root.Left + root.Left = root.Right + root.Right = left + root.Left = mirrorTree(root.Left) + root.Right = mirrorTree(root.Right) + return root +} +``` + + + +#### 路径总和 + +思路分析 + +算法1:自顶向下的递归思想 + +```go +// 自顶向下递归 +func hasPathSum(root *TreeNode, sum int) bool { + if root == nil {return false} + sum -= root.Val + if root.Left == nil && root.Right == nil {return sum == 0} + return hasPathSum(root.Left, sum) || hasPathSum(root.Right, sum) +} +``` + +算法2:迭代,利用queue保存当前结果,类型层序遍历。 + +```go +func hasPathSum(root *TreeNode, sum int) bool { + if root == nil {return false} + queue, csum := make([]*TreeNode, 0), make([]int, 0) + queue = append(queue, root) + csum = append(csum, sum - root.Val) + for 0 != len(queue) { + n := len(queue) + for i := 0; i < n; i++ { + if queue[i].Left == nil && queue[i].Right == nil && csum[i] == 0 {return true} + if queue[i].Left != nil { + queue = append(queue, queue[i].Left) + csum = append(csum, csum[i] - queue[i].Left.Val) + } + if queue[i].Right != nil { + queue = append(queue, queue[i].Right) + csum = append(csum, csum[i] - queue[i].Right.Val) + } + } + queue = queue[n:] + csum = csum[n:] + } + return false +} +``` + +#### 不同的二叉搜索树 II + +题目要求:给定一个整数n,生成所有由1...n为节点所组成的二叉搜索树。 + +思路分析 + +题目看着很难,实际利用递归思想没有那么难。 + +从序列`1..n`取出数字`1` 作为当前树的根节点,那么就有i-1个元素用来构造左子树,二另外的i+1..n个元素用来构造右子树。最后我们将得到G(i-1)棵不同的左子树,G(n-i)棵不同的右子树,其中G为卡特兰数。 + +这样就可以以i为根节点,和两个可能的左右子树序列,遍历一遍左右子树序列,将左右子树和根节点链接起来。 + +```go +func generateTrees(n int) []*TreeNode { + if n == 0 { return nil } + return generateTreesWithNode(1, n) +} + +func generateTreesWithNode(start end int) []*TreeNode { + res := make([]*TreeNode, 0) + if start > end { + // 表示空树 + res = append(res, nil) + return res + } + for i := start; i <= end; i++ { + left := generateTreesWithNode(start, i-1) + right := generateTreesWithNode(i+1, end) + for _, l := range left { + for _, r := range right { + res = append(res, &TreeNode{ + Val: i, + Left: l, + Right: r, + }) + } + } + } + return res + } +``` + + + +#### 101 平衡二叉树 + +题目要求:给定一个二叉树,判断其是否为高度平衡二叉树。【一个二叉树的每个节点的左右两个子树的高度差的绝对值不超过1,视为高度平衡二叉树】 + +算法一:递归 + +```go +// date 2020/02/18 +func isBalanced(root *TreeNode) bool { + if root == nil { return true } + if !isBalanced(root.Left) || !isBalanced(root.Right) { return false } + if Math.Abs(float64(getHeight(root.Left)-getHeight(root.Right))) > float64(1) { return false } + return true +} +func getHeight(root *TreeNode) int { + if root == nil { return 0 } + if root.Left == nil && root.Right == nil { return 1 } + l, r := getHeight(root.Left), getHeight(root.Right) + if l > r { return l+1 } + return r+1 +} +``` + +#### 703 数据流中第K大元素 + +解题思路: + +0。使用数据构建一个容量为k的小顶堆。 + +1。如果小顶堆堆满后,向其增加元素,若大于堆顶元素,则重新建堆,否则掉弃。 + +2。堆顶元素即为第k大元素。 + +```go +// date 2020/02/19 +type KthLargest struct { + minHeap []int + size +} + +func Constructor(k int, nums []int) KthLargest { + res := KthLargest{ + minHeap: make([]int, k), + } + for _, v := range nums { + res.Add(v) + } +} + +func (this *KthLargest) Add(val int) int { + if this.size < len(this.minHeap) { + this.size++ + this.minHeap[this.size-1] = val + if this.size == len(this.minHeap) { + this.makeMinHeap() + } + } else if this.minHeap[0] < val { + this.minHeap[0] = val + this.minHeapify(0) + } + return this.minHeap[0] +} + +func (this *KthLargest) makeMinHeap() { + // 为什么最后一个根节点是n >> 1 - 1 + // 长度为n,最后一个叶子节点的索引是n-1; left = i >> 1 + 1; right = i >> 1 + 2 + // 父节点是 n >> 1 - 1 + for i := len(this.minHeap) >> 1 - 1; i >= 0; i-- { + this.minHeapify(i) + } +} + +func (this *KthLargest) minHeapify(i int) { + if i > len(this.minHeap) {return} + temp, n := this.minHeap[i], len(this.minHeap) + left := i << 1 + 1 + right := i << 1 + 2 + for left < n { + right = left + 1 + // 找到left, right中较小的那个 + if right < n && this.minHeap[left] > this.minHeap[right] { left++ } + // left, right 均小于root否和小顶堆,跳出 + if this.minHeap[left] >= this.minHeap[i] { break } + this.minHeap[i] = this.minHeap[left] + this.minHeap[left] = temp + // left发生变化,继续调整 + i = left + left = i << 1 + 1 + } +} +``` + diff --git a/bst.md b/bst.md new file mode 100644 index 0000000..5550ebf --- /dev/null +++ b/bst.md @@ -0,0 +1,532 @@ +# 二叉搜索树 + +### 什么是二叉搜索树 + +顾名思义,二叉搜索树是以一棵二叉树来组织的,其满足一下条件:x为二叉树的一个节点,如果y是x左子树的一个节点,那么`y.Val <= x.Val`;如果y是x右子树的一个节点,则`y.Val >= x.Val` + +根据二叉树的性质可得知,二叉搜索树的中序遍历为升序序列,相反如果交换左右子树的遍历顺序亦可得到降序序列。 + +```go +// inorder伪代码 +func inOrder(root *TreeNode) []int { + res := make([]int, 0) + if root != nil { + res = append(res, inOrder(root.Left)...) + res = append(res, root.Val) + res = append(res, inOrder(root.Right)...) + } + return res +} + +func decOrder(root *TreeNode) []int { + res := make([]int, 0) + if root != nil { + res = append(res, decOrder(root.Right)...) + res = append(res, root.Val) + res = append(res, decOrder(root.Left)...) + } + return res +} +``` + +### 二叉搜索树的基本操作 + +二叉搜索树的基本操作包括查询,最小关键字元素,最大关键字元素,前继节点,后继节点,插入和删除,这些基本操作所花费的时间与二叉搜索树的高度成正比。 + +**查询** + +如果二叉搜索树的高度为h,则查询的时间复杂度为O(h)。 + +```go +// 查询伪代码 +// 递归版 +func searchBST(root *TreeNode, key int) *TreeNode { + if root == nil || root.Val == key { return root } + if root.Val < key { + return searchBST(root.Right, key) + } + return searchBST(root.Left, key) +} +// 迭代版 +func searchBST(root *TreeNode, key int) *TreeNode { + for root != nil && root.Val != key { + if root.Val < key { + root = root.Right + } else { + root = root.Left + } + } + return root +} +``` + +**最大关键字元素** + +如果二叉搜素树的高度为h,则最大关键字元素的操作时间复杂度为O(h)。 + +```go +// 最大关键字元素的伪代码 +func maximumBST(root *TreeNode) *TreeNode { + for root.Right != nil { root = root.Right } + return root +} +// 递归版 +func maximumBST(root *TeeNode) *TreeNode { + if root.Right != nil { + return maximumBST(root.Right) + } + return root +} +``` + +**最小关键字元素** + +如果二叉搜素树的高度为h,则最大关键字元素的操作时间复杂度为O(h)。 + +```go +// 最小关键字元素的伪代码 +func minimumBST(root *TreeNode) *TreeNode { + for root.Left != nil { root = root.Left } + return root +} +// 递归版 +func minimumBST(root *TreeNode) *TreeNode { + if root.Left != nil { + return minimum(root.Left) + } + return root +} +``` + +**前继节点** + +前继节点是指给定一个节点x,如果存在x的前继节点则返回前继节点,否则返回nil。 + +如果二叉搜素树的高度为h,则查找前继节点的操作时间复杂度为O(h)。 + +```go +func predecessorBST(x *TreeNode) *TreeNode { + // x节点存在左子树,则返回左子树中最大的节点 + if x.Left != nil { + return maximum(root.Left) + } + // 如果x节点没有左子树,则找到其父节点,且父节点的右子树包含x + y := x.Present + for y != nil && x == y.Left { + x = y + y = y.Present + } + return y +} +``` + +**后继节点** + +后继节点是指给定一个节点x,如果存在x的后继节点则返回后继节点,否则返回nil。 + +如果二叉搜素树的高度为h,则查找后继节点的操作时间复杂度为O(h)。 + +```go +// 后继节点的伪代码 +func successorBST(x *TreeNode) *TreeNode { + // x节点存在右子树,则返回右子树中最小的节点 + if x.Right != nil { + return minimum(root.Right) + } + // 如果x节点没有右子树,则找到其父节点,且父节点的左子树包含x + y := x.Present + for y != nil && x == y.Right { + x = y + y = y.Present + } + return y +} +``` + +**插入** + +插入和删除会引起二叉搜索树所表示的动态集合的变化。一定要修改数据结构来反映这种变化,单修改要保持二叉搜索树的性质不变。 + +```go +// x.Val = val, x.Right = x.Left = x.Present = nil +// 插入的伪代码 +func insertBST(root, x *TreeNode) { + var y *TreeNode + for root != nil { + y = root + if x.Val < root.Val { + root = root.Left + } else { + root = root.Right + } + } + x.Present = y + if y == nil { // empty tree + root = x + } else if x.Val < y.Val { + y.Left = x + } else { + y.Right = x + } +} +// 没有父指针 +// 递归版 +func insertBST(root, x *TreeNode) *TreeNode { + if root == nil { + root = x + return root + } + if root.Val < x.Val { + return insertBST(root.Right) + } + return insertBST(root.Left) +} +// 迭代版 +func insertBST(root, x *TreeNode) *TreeNode { + if root == nil { + root == x + return root + } + pre, head := root, root + for head != nil { + pre = head + if root.Val < x.Val { + head = head.Right + } else { + head = head.Left + } + } + if y.Val < x.Val { + y.Right = x + } else { + y.Left = x + } + return root +} +``` + +**删除** + +从一棵二叉搜索树中删除一个节点x的整个策略分为三个情况,分别是: + +1)如果x没有孩子节点,那么直接删除即可; + +2)如果x只有一个孩子节点,那么将该孩子节点提升到x即可; + +3)如果x有两个孩子,那么需要找到x的后继节点y(一定存在x的右子树中),让y占据x的位置,那么x原来的右子树部分称为y的右子树,x的左子树称为y的新的左子树。 + +```go +func deleteBST(root *TreeNode, key int) *TreeNode{ + if root == nil { return root } + if root.Val < key { + return deleteBST(root.Right, key) + } else if root.Val > key { + return deleteBST(root.Left, key) + } else { + // no child node + if root.Left == nil && root.Right == nil { + root = nil + } else if root.Right != nil { + root.Val = postNodeVal(root.Right) + root.Right = deleteBST(root.Right, root.Val) + } else { + root.Val = preNodeVal(root.Left) + root.Left = deleteBST(root.Left, root.Val) + } + } + return root +} + +// find post node val in right +func postNodeVal(root *TreeNode) int { + for root.Left != nil { root = root.Left } + return root.Val +} +// find pre node val in left +func preNodeVal(root *TreeNode) int { + for root.Right != nil { root = root.Right } + return root.Val +} +``` + +### 相关题目 + +* 98 验证二叉搜索树【M】 +* 108 将有序数组转换为二叉搜索树【M】 +* 235 二叉搜素树的最近公共祖先 +* 450 删除二叉搜索树中的节点 +* 1038 从二叉搜索树到更大和树【M】 +* 1214 查找两棵二叉搜索树之和【M】 +* 面试题 17.12 BiNode【E】 +* 面试题54 二叉搜索树的第K大节点 + +#### 98 验证二叉搜索树 + +题目要求:给定一个二叉树,判断其是否为二叉搜索树,其规则如下:1)节点的左子树只包含小于当前节点的数;2)节点的右子树只包含大于当前节点的数。 + +思路分析:因为要判断当前的节点的值,需要传入上下界。math包中无穷大,无穷小的处理。 + +算法一:递归 + +```go +// date 2020/02/17 +func isValidBST(root *TreeNode) bool { + return isOk(root, math.Inf(-1), math.Inf(0)) +} + +func isOk(root *TreeNode, left, right float64) bool { + if root == nil {return true} + val := float64(root.Val) + if val <= left || val >= right { return false } + if !isOk(root.Left, left, val) { return false } + if !isOK(root.Right, val, right) { return false } + return true +} +``` + +算法二:利用中序遍历序列,判断是否为二叉搜索树;不用保存中序遍历序列,因为压栈进去的值就可以判断。 + +```go +// date 2020/02/17 +func isValidBST(root *TreeNode) bool { + stack := make([]*TreeNode, 0) + inorder = math.Inf(-1) + for len(stack) != 0 || root != nil { + // 将当前节点的所有左子树压入栈 + for root != nil { + stack = append(stack, root) + root = root.Left + } + // 取出栈顶元素 + root = stack[len(stack)-1] + stack = stack[:len(stack)-1] + // 如果栈顶元素小于或等于中序遍历序列,则返回false + if float64(root.Val) <= inorder { return false } + // 更新中序遍历序列的最后一个值 + inorder = float64(root.Val) + // 查看当前节点的右子树 + root = root.Right + } + return false +} +``` + +#### 108 将有序数组转换为二叉搜索树 + +题目要求:给定一个升序数据,返回一棵高度平衡二叉树。 + +思路分析: + +二叉搜索树的定义: + +1)若任意节点的左子树不为空,则左子树上所有的节点值均小于它的根节点值; + +2)若任意节点的右子树不为空,则右子树上所有的节点值均大于它的根节点值; + +3)任意节点的左,右子树均为二叉搜索树; + +4)没有键值相等的点。 + +如何构造一棵树,可拆分成无数个这样的子问题:构造树的每个节点,以及节点之间的关系。对于每个节点来说,都需要: + +1)选取节点; + +2)构造该节点的左子树; + +3)构造该节点的右子树。 + +算法一:对于升序序列,可以选择中间的节点作为根节点,然后递归调用。因为每个节点只遍历一遍,所以时间复杂度为O(n);因为递归调用,空间复杂度为O(log(n))。 + +```go +//date 2020/02/20 +// 算法一:递归版 +func sortedArrayToBST(nums []int) *TreeNode { + if len(nums) == 0 { return nil } + // 选择根节点 + mid := len(nums) >> 1 + // 构造左子树和右子树 + left := nums[:mid] + right := nums[mid+1:] + + return &TreeNode{ + Val: nums[mid], + Left: sortedArrayToBST(left), + Right: sortedArrayToBST(right), + } +} +``` + + + +#### 235 二叉搜索树的最近公共祖先 + +题目要求:给定一个二叉搜素树和两个节点,返回其最近公共祖先。 + +算法:递归,并充分利用二叉搜索树的性质`left.Val < root.Val < right.Val`。 + +```go +// date 2020/02/18 +func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { + if root == nil || root == p || root == q {return root } + if p == nil { return q } + if q == nil { return p } + if root.Val > q.Val && root.Val > p.Val { return lowestCommonAncestor(root.Left) } + if root.Val < q.Val && root.Val < p.Val { return lowestCommonAncestor(root.Right) } + return root +} +``` + + + +#### 450 删除二叉搜索树中的节点 + +题目要求:给定一个二叉搜索树和key,删除key节点,并返回树的根节点 + +算法一:二叉搜索树的中序遍历序列是个升序序列,则欲要删除的节点需要用其前驱节点或后驱节点来代替。 + +1)如果为叶子节点,则直接删除; + +2)如果不是叶子节点且有右子树,则用后驱节点代替。后驱节点为右子树中的较低位置; + +3)如果不是叶子节点且有左子树,则用前驱节点代替。前驱节点为左子树中的较高位置; + +```go +func deleteBST(root *TreeNode, key int) *TreeNode{ + if root == nil { return root } + if root.Val < key { + return deleteBST(root.Right, key) + } else if root.Val > key { + return deleteBST(root.Left, key) + } else { + // no child node + if root.Left == nil && root.Right == nil { + root = nil + } else if root.Right != nil { + root.Val = postNodeVal(root.Right) + root.Right = deleteBST(root.Right, root.Val) + } else { + root.Val = preNodeVal(root.Left) + root.Left = deleteBST(root.Left, root.Val) + } + } + return root +} + +// find post node val in right +func postNodeVal(root *TreeNode) int { + for root.Left != nil { root = root.Left } + return root.Val +} +// find pre node val in left +func preNodeVal(root *TreeNode) int { + for root.Right != nil { root = root.Right } + return root.Val +} +``` + +#### 1038 从二叉搜索树到更大和树 + +题目要求:给出二叉**搜索**树的根节点,该二叉树的节点值各不相同,修改二叉树,使每个节点 `node` 的新值等于原树中大于或等于 `node.val` 的值之和 + +思路分析: + +`root.Val = root.Val + root.Right.Val` + +`root.Left.Val = root.Left.Val + root.Val` + +首先得到二叉搜索树的中序遍历序列的逆序列,然后从序列的第一个开始,将累加值记录到当前树的节点上。 + +```go +// date 2020/02/23 +func bsttoGST(root *TreeNode) *TreeNode{ + array := decOrder(root) + val := 0 + for i := 0; i < len(array); i++ { + val += array[i] + setNewVal(root, array[i], val) + } +} +func decOrder(root *TreeNode) []int { + res := make([]int, 0) + if root != nil { + res = append(res, decOrder(root.Right)...) + res = append(res, root.Val) + res = append(res, decOrder(root.Left)...) + } + return res +} + +func setNewVal(root *TreeNode, key, val int) { + if root != nil { + if root.Val == key { + root.Val = val + } else if root.Val > key { + setNewVal(root.Left, key, val) + } else { + setNewVal(root.Right, key, val) + } + } +} +``` + +#### 面试题 BiNode + +题目要求:将一颗二叉搜索树转换成单链表,Right指针指向下一个节点。 + +思路分析: + +要求返回头节点,所以先转换left。 + +```go +// date 2020/02/19 +func convertBiNode(root *TreeNode) *TreeNode { + if root == nil { return nil } + // 左子树不为空,需要转换 + if root.Left != nil { + // 得到左子树序列,并找到最后一个节点 + left := convertBiNode(root.Left) + pre := left + for pre.Right != nil { pre = pre.Right } + // 将最后一个节点right指向root + pre.Right = root + root.Left = nil + // 得到右子树序列 + root.Right = convertBiNode(root.Right) + return left + } + root.Right = convertBiNode(root.Right) + return root +} +``` + +#### 面试题54 二叉搜索树的第K大节点 + +题目要求:给定一个二叉搜索树,找出其中第k大节点。 + +算法一:二叉搜索树的中序遍历为升序序列,按照Right->root->Left的顺序遍历可得到降序序列,然后返回第k节点。 + +```go +// date 2020/02/18 +func kthLargest(root *TreeNode, k int) int { + if root == nil || k < 0 { return -1 } + nums := decOrder(root) + if len(nums) < k {return -1} + return nums[k-1] +} + +// right->root->left +func decOrder(root *TreeNode) []int { + if root == nil { return []int{} } + res := make([]int, 0) + if root.Right != nil { + res = append(res, decOrder(root.Right)...) + } + res = append(res, root.Val) + if root.Left != nil { + res = append(res, decOrder(root.Left)...) + } + return res +} +``` + +#### \ No newline at end of file diff --git a/bst/bst.md b/bst/bst.md new file mode 100644 index 0000000..5550ebf --- /dev/null +++ b/bst/bst.md @@ -0,0 +1,532 @@ +# 二叉搜索树 + +### 什么是二叉搜索树 + +顾名思义,二叉搜索树是以一棵二叉树来组织的,其满足一下条件:x为二叉树的一个节点,如果y是x左子树的一个节点,那么`y.Val <= x.Val`;如果y是x右子树的一个节点,则`y.Val >= x.Val` + +根据二叉树的性质可得知,二叉搜索树的中序遍历为升序序列,相反如果交换左右子树的遍历顺序亦可得到降序序列。 + +```go +// inorder伪代码 +func inOrder(root *TreeNode) []int { + res := make([]int, 0) + if root != nil { + res = append(res, inOrder(root.Left)...) + res = append(res, root.Val) + res = append(res, inOrder(root.Right)...) + } + return res +} + +func decOrder(root *TreeNode) []int { + res := make([]int, 0) + if root != nil { + res = append(res, decOrder(root.Right)...) + res = append(res, root.Val) + res = append(res, decOrder(root.Left)...) + } + return res +} +``` + +### 二叉搜索树的基本操作 + +二叉搜索树的基本操作包括查询,最小关键字元素,最大关键字元素,前继节点,后继节点,插入和删除,这些基本操作所花费的时间与二叉搜索树的高度成正比。 + +**查询** + +如果二叉搜索树的高度为h,则查询的时间复杂度为O(h)。 + +```go +// 查询伪代码 +// 递归版 +func searchBST(root *TreeNode, key int) *TreeNode { + if root == nil || root.Val == key { return root } + if root.Val < key { + return searchBST(root.Right, key) + } + return searchBST(root.Left, key) +} +// 迭代版 +func searchBST(root *TreeNode, key int) *TreeNode { + for root != nil && root.Val != key { + if root.Val < key { + root = root.Right + } else { + root = root.Left + } + } + return root +} +``` + +**最大关键字元素** + +如果二叉搜素树的高度为h,则最大关键字元素的操作时间复杂度为O(h)。 + +```go +// 最大关键字元素的伪代码 +func maximumBST(root *TreeNode) *TreeNode { + for root.Right != nil { root = root.Right } + return root +} +// 递归版 +func maximumBST(root *TeeNode) *TreeNode { + if root.Right != nil { + return maximumBST(root.Right) + } + return root +} +``` + +**最小关键字元素** + +如果二叉搜素树的高度为h,则最大关键字元素的操作时间复杂度为O(h)。 + +```go +// 最小关键字元素的伪代码 +func minimumBST(root *TreeNode) *TreeNode { + for root.Left != nil { root = root.Left } + return root +} +// 递归版 +func minimumBST(root *TreeNode) *TreeNode { + if root.Left != nil { + return minimum(root.Left) + } + return root +} +``` + +**前继节点** + +前继节点是指给定一个节点x,如果存在x的前继节点则返回前继节点,否则返回nil。 + +如果二叉搜素树的高度为h,则查找前继节点的操作时间复杂度为O(h)。 + +```go +func predecessorBST(x *TreeNode) *TreeNode { + // x节点存在左子树,则返回左子树中最大的节点 + if x.Left != nil { + return maximum(root.Left) + } + // 如果x节点没有左子树,则找到其父节点,且父节点的右子树包含x + y := x.Present + for y != nil && x == y.Left { + x = y + y = y.Present + } + return y +} +``` + +**后继节点** + +后继节点是指给定一个节点x,如果存在x的后继节点则返回后继节点,否则返回nil。 + +如果二叉搜素树的高度为h,则查找后继节点的操作时间复杂度为O(h)。 + +```go +// 后继节点的伪代码 +func successorBST(x *TreeNode) *TreeNode { + // x节点存在右子树,则返回右子树中最小的节点 + if x.Right != nil { + return minimum(root.Right) + } + // 如果x节点没有右子树,则找到其父节点,且父节点的左子树包含x + y := x.Present + for y != nil && x == y.Right { + x = y + y = y.Present + } + return y +} +``` + +**插入** + +插入和删除会引起二叉搜索树所表示的动态集合的变化。一定要修改数据结构来反映这种变化,单修改要保持二叉搜索树的性质不变。 + +```go +// x.Val = val, x.Right = x.Left = x.Present = nil +// 插入的伪代码 +func insertBST(root, x *TreeNode) { + var y *TreeNode + for root != nil { + y = root + if x.Val < root.Val { + root = root.Left + } else { + root = root.Right + } + } + x.Present = y + if y == nil { // empty tree + root = x + } else if x.Val < y.Val { + y.Left = x + } else { + y.Right = x + } +} +// 没有父指针 +// 递归版 +func insertBST(root, x *TreeNode) *TreeNode { + if root == nil { + root = x + return root + } + if root.Val < x.Val { + return insertBST(root.Right) + } + return insertBST(root.Left) +} +// 迭代版 +func insertBST(root, x *TreeNode) *TreeNode { + if root == nil { + root == x + return root + } + pre, head := root, root + for head != nil { + pre = head + if root.Val < x.Val { + head = head.Right + } else { + head = head.Left + } + } + if y.Val < x.Val { + y.Right = x + } else { + y.Left = x + } + return root +} +``` + +**删除** + +从一棵二叉搜索树中删除一个节点x的整个策略分为三个情况,分别是: + +1)如果x没有孩子节点,那么直接删除即可; + +2)如果x只有一个孩子节点,那么将该孩子节点提升到x即可; + +3)如果x有两个孩子,那么需要找到x的后继节点y(一定存在x的右子树中),让y占据x的位置,那么x原来的右子树部分称为y的右子树,x的左子树称为y的新的左子树。 + +```go +func deleteBST(root *TreeNode, key int) *TreeNode{ + if root == nil { return root } + if root.Val < key { + return deleteBST(root.Right, key) + } else if root.Val > key { + return deleteBST(root.Left, key) + } else { + // no child node + if root.Left == nil && root.Right == nil { + root = nil + } else if root.Right != nil { + root.Val = postNodeVal(root.Right) + root.Right = deleteBST(root.Right, root.Val) + } else { + root.Val = preNodeVal(root.Left) + root.Left = deleteBST(root.Left, root.Val) + } + } + return root +} + +// find post node val in right +func postNodeVal(root *TreeNode) int { + for root.Left != nil { root = root.Left } + return root.Val +} +// find pre node val in left +func preNodeVal(root *TreeNode) int { + for root.Right != nil { root = root.Right } + return root.Val +} +``` + +### 相关题目 + +* 98 验证二叉搜索树【M】 +* 108 将有序数组转换为二叉搜索树【M】 +* 235 二叉搜素树的最近公共祖先 +* 450 删除二叉搜索树中的节点 +* 1038 从二叉搜索树到更大和树【M】 +* 1214 查找两棵二叉搜索树之和【M】 +* 面试题 17.12 BiNode【E】 +* 面试题54 二叉搜索树的第K大节点 + +#### 98 验证二叉搜索树 + +题目要求:给定一个二叉树,判断其是否为二叉搜索树,其规则如下:1)节点的左子树只包含小于当前节点的数;2)节点的右子树只包含大于当前节点的数。 + +思路分析:因为要判断当前的节点的值,需要传入上下界。math包中无穷大,无穷小的处理。 + +算法一:递归 + +```go +// date 2020/02/17 +func isValidBST(root *TreeNode) bool { + return isOk(root, math.Inf(-1), math.Inf(0)) +} + +func isOk(root *TreeNode, left, right float64) bool { + if root == nil {return true} + val := float64(root.Val) + if val <= left || val >= right { return false } + if !isOk(root.Left, left, val) { return false } + if !isOK(root.Right, val, right) { return false } + return true +} +``` + +算法二:利用中序遍历序列,判断是否为二叉搜索树;不用保存中序遍历序列,因为压栈进去的值就可以判断。 + +```go +// date 2020/02/17 +func isValidBST(root *TreeNode) bool { + stack := make([]*TreeNode, 0) + inorder = math.Inf(-1) + for len(stack) != 0 || root != nil { + // 将当前节点的所有左子树压入栈 + for root != nil { + stack = append(stack, root) + root = root.Left + } + // 取出栈顶元素 + root = stack[len(stack)-1] + stack = stack[:len(stack)-1] + // 如果栈顶元素小于或等于中序遍历序列,则返回false + if float64(root.Val) <= inorder { return false } + // 更新中序遍历序列的最后一个值 + inorder = float64(root.Val) + // 查看当前节点的右子树 + root = root.Right + } + return false +} +``` + +#### 108 将有序数组转换为二叉搜索树 + +题目要求:给定一个升序数据,返回一棵高度平衡二叉树。 + +思路分析: + +二叉搜索树的定义: + +1)若任意节点的左子树不为空,则左子树上所有的节点值均小于它的根节点值; + +2)若任意节点的右子树不为空,则右子树上所有的节点值均大于它的根节点值; + +3)任意节点的左,右子树均为二叉搜索树; + +4)没有键值相等的点。 + +如何构造一棵树,可拆分成无数个这样的子问题:构造树的每个节点,以及节点之间的关系。对于每个节点来说,都需要: + +1)选取节点; + +2)构造该节点的左子树; + +3)构造该节点的右子树。 + +算法一:对于升序序列,可以选择中间的节点作为根节点,然后递归调用。因为每个节点只遍历一遍,所以时间复杂度为O(n);因为递归调用,空间复杂度为O(log(n))。 + +```go +//date 2020/02/20 +// 算法一:递归版 +func sortedArrayToBST(nums []int) *TreeNode { + if len(nums) == 0 { return nil } + // 选择根节点 + mid := len(nums) >> 1 + // 构造左子树和右子树 + left := nums[:mid] + right := nums[mid+1:] + + return &TreeNode{ + Val: nums[mid], + Left: sortedArrayToBST(left), + Right: sortedArrayToBST(right), + } +} +``` + + + +#### 235 二叉搜索树的最近公共祖先 + +题目要求:给定一个二叉搜素树和两个节点,返回其最近公共祖先。 + +算法:递归,并充分利用二叉搜索树的性质`left.Val < root.Val < right.Val`。 + +```go +// date 2020/02/18 +func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { + if root == nil || root == p || root == q {return root } + if p == nil { return q } + if q == nil { return p } + if root.Val > q.Val && root.Val > p.Val { return lowestCommonAncestor(root.Left) } + if root.Val < q.Val && root.Val < p.Val { return lowestCommonAncestor(root.Right) } + return root +} +``` + + + +#### 450 删除二叉搜索树中的节点 + +题目要求:给定一个二叉搜索树和key,删除key节点,并返回树的根节点 + +算法一:二叉搜索树的中序遍历序列是个升序序列,则欲要删除的节点需要用其前驱节点或后驱节点来代替。 + +1)如果为叶子节点,则直接删除; + +2)如果不是叶子节点且有右子树,则用后驱节点代替。后驱节点为右子树中的较低位置; + +3)如果不是叶子节点且有左子树,则用前驱节点代替。前驱节点为左子树中的较高位置; + +```go +func deleteBST(root *TreeNode, key int) *TreeNode{ + if root == nil { return root } + if root.Val < key { + return deleteBST(root.Right, key) + } else if root.Val > key { + return deleteBST(root.Left, key) + } else { + // no child node + if root.Left == nil && root.Right == nil { + root = nil + } else if root.Right != nil { + root.Val = postNodeVal(root.Right) + root.Right = deleteBST(root.Right, root.Val) + } else { + root.Val = preNodeVal(root.Left) + root.Left = deleteBST(root.Left, root.Val) + } + } + return root +} + +// find post node val in right +func postNodeVal(root *TreeNode) int { + for root.Left != nil { root = root.Left } + return root.Val +} +// find pre node val in left +func preNodeVal(root *TreeNode) int { + for root.Right != nil { root = root.Right } + return root.Val +} +``` + +#### 1038 从二叉搜索树到更大和树 + +题目要求:给出二叉**搜索**树的根节点,该二叉树的节点值各不相同,修改二叉树,使每个节点 `node` 的新值等于原树中大于或等于 `node.val` 的值之和 + +思路分析: + +`root.Val = root.Val + root.Right.Val` + +`root.Left.Val = root.Left.Val + root.Val` + +首先得到二叉搜索树的中序遍历序列的逆序列,然后从序列的第一个开始,将累加值记录到当前树的节点上。 + +```go +// date 2020/02/23 +func bsttoGST(root *TreeNode) *TreeNode{ + array := decOrder(root) + val := 0 + for i := 0; i < len(array); i++ { + val += array[i] + setNewVal(root, array[i], val) + } +} +func decOrder(root *TreeNode) []int { + res := make([]int, 0) + if root != nil { + res = append(res, decOrder(root.Right)...) + res = append(res, root.Val) + res = append(res, decOrder(root.Left)...) + } + return res +} + +func setNewVal(root *TreeNode, key, val int) { + if root != nil { + if root.Val == key { + root.Val = val + } else if root.Val > key { + setNewVal(root.Left, key, val) + } else { + setNewVal(root.Right, key, val) + } + } +} +``` + +#### 面试题 BiNode + +题目要求:将一颗二叉搜索树转换成单链表,Right指针指向下一个节点。 + +思路分析: + +要求返回头节点,所以先转换left。 + +```go +// date 2020/02/19 +func convertBiNode(root *TreeNode) *TreeNode { + if root == nil { return nil } + // 左子树不为空,需要转换 + if root.Left != nil { + // 得到左子树序列,并找到最后一个节点 + left := convertBiNode(root.Left) + pre := left + for pre.Right != nil { pre = pre.Right } + // 将最后一个节点right指向root + pre.Right = root + root.Left = nil + // 得到右子树序列 + root.Right = convertBiNode(root.Right) + return left + } + root.Right = convertBiNode(root.Right) + return root +} +``` + +#### 面试题54 二叉搜索树的第K大节点 + +题目要求:给定一个二叉搜索树,找出其中第k大节点。 + +算法一:二叉搜索树的中序遍历为升序序列,按照Right->root->Left的顺序遍历可得到降序序列,然后返回第k节点。 + +```go +// date 2020/02/18 +func kthLargest(root *TreeNode, k int) int { + if root == nil || k < 0 { return -1 } + nums := decOrder(root) + if len(nums) < k {return -1} + return nums[k-1] +} + +// right->root->left +func decOrder(root *TreeNode) []int { + if root == nil { return []int{} } + res := make([]int, 0) + if root.Right != nil { + res = append(res, decOrder(root.Right)...) + } + res = append(res, root.Val) + if root.Left != nil { + res = append(res, decOrder(root.Left)...) + } + return res +} +``` + +#### \ No newline at end of file diff --git a/heap/heap.md b/heap/heap.md new file mode 100644 index 0000000..7f5aac3 --- /dev/null +++ b/heap/heap.md @@ -0,0 +1,302 @@ +## 堆 + +### 堆的定义 + +数据结构二叉堆能够很好地实现有线队列的基本操作。在二叉堆的数组中,每个元素都要保证大于等于另两个特定位置的元素。 + +> 当一棵二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序。 + +二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级存储。如果不使用数组的第一个位置,那么父结点`i`所对应的的左右子结点分别为`2i`和`2i+i`。结点`i`的父节点`i/2`。 + +如果使用数组的第一个位置,那么父结点`i`对应的左右子结点分别为`2i+1`和`2i+2`,结点`i`对应的父结点为`i/2 -1`。 + + + +### 堆的算法 + +堆的操作会首先进行一些简单的改动,打破堆的状态,然后再遍历堆并按照要求将堆的状态恢复,这个过程称为堆的有序化(reheapifying)。 + +堆的有序化有两种策略,分别是自下而上的堆有序化(上浮,swim)和自上而下的堆有序化(下沉,sink),其伪代码分别如下: + +```go +// date 2020/03/01 +// 自下而上swim +func swim(k int) { + for k > 1 && less(/2, i) { + swap(k/2, k) + k = k / 2 + } +} +// 自上而下sink +func sink(k int) { + l := 2 * k + r := l + 1 + for l < n { + r = l + 1 + if r < n && less(l, r) { l++ } + if !less(k, l) { break } + swap(k, l) + k = l + l = 2 * k + } +} +``` + +**插入元素** + +将新元素插入到数组的末尾,并使用自下而上的堆有序化将新元素上浮到合适的位置。时间复杂度O(logN) + +**删除元素** + +将数组顶端的元素删除,将数组最后一个元素放到顶端,并使用自上而下的堆有序化将这个元素下沉到合适的位置。时间复杂度O(logN) + +### 堆排序 + +基本思路:先构造堆,然后将堆顶元素与数组的最后一个元素交换,然后对N-1的堆重新堆有序化。伪代码如下: + +```go +// date 2020/03/01 +func heapSort(nums []int) []int { + makeMaxHeap(nums) + n := len(nums) + for n > 0 { + swap(nums, 0, n-1) + n-- + sink(nums, 0, n) + } +} + +// 构造堆 +func makeMaxHeap(nums []int) { + for i := len(nums) >> 1 - 1; i >= 0; i-- { sink(nums, 0, len(nums)) } +} + +func sink(nums []int, start, end int) { + if start > end { return } + l := start >> 1 + 1 + temp := nums[start] + for l < end { + r := l + 1 + if r < end && nums[r] > nums[l] { l++ } + if nums[start] > nums[l] { break } + nums[start] = nums[l] + nums[l] = temp + start = l + l = start >> 1 + 1 + } +} +``` + +### 相关题目 + +- 912 排序数组【M】 +- 1046 最后一块石头的重量 +- 215 数组中第K个最大元素 +- 面试题40 最小的K个数 +- 面试题17.09 第K个数 + +#### 912 排序数组 + +题目要求:https://leetcode-cn.com/problems/sort-an-array/ + +思路分析: + +算法一:堆排序。时间复杂度O(NlogN)。 + +> 将N个元素排序,堆排序只需少于(2NlgN+2N)次比较,以及一半次数的交换。 + +```go +// date 2020/03/01 +func sortArray(nums []int) []int { + makeMaxHeap(nums) + n := len(nums) + for n > 0 { + swap(nums, 0, n-1) + n-- + maxHeapify(nums, 0, n) + } + return nums +} +// 堆排序 +// 构建大顶堆 +func makeMaxHeap(nums []int) { + for i := len(nums) >> 1 - 1; i >= 0; i-- { + maxHeapify(nums, i, len(nums)) + } +} +// 自上而下的堆有序化 +func maxHeapify(nums []int, start, end int) { + if start > end { return } + temp, l, r := nums[start], start << 1 + 1, start << 1 + 2 + for l < end { + r = l + 1 + if r < end && nums[r] > nums[l] { l++ } + if nums[start] > nums[l] { break } + nums[start] = nums[l] + nums[l] = temp + start = l + l = start << 1 + 1 + } +} +// swap +func swap(nums []int, i, j int) { + t := nums[i] + nums[i] = nums[j] + nums[j] = t +} +``` + + + +#### 1046 最后一块石头的重量 + +题目要求:https://leetcode-cn.com/problems/last-stone-weight/ + +思路分析: + +```go +// date 2020/02/29 +func lastStoneWeight(stones []int) int { + if len(stones) == 1 { return stones[0] } + sort.Ints(stones) + n := len(stones) + for stones[n-2] != 0 { + stones[n-1] -= stones[n-2] + stones[n-2] = 0 + sort.Ints(stones) + } + return stones[n-1] +} +``` + +#### 215 数组中第K个最大元素 + +题目要求:https://leetcode-cn.com/problems/kth-largest-element-in-an-array/ + +思路分析: + +算法一:排序,返回第k个元素 + +算法二:小顶堆 + +```go +// date 2020/02/29 +// 算法二 使用小顶堆 +func findKthLargest(nums []int, k int) int { + res, size := make([]int, k), 0 + for _, v := range nums { + if size < k { + res[size] = v + size++ + if size == k { + makeMinHeap(res) + } + } else if v > res[0] { + res[0] = v + minHeapify(res, 0) + } + } + return res[0] +} +// 建立小顶堆,堆顶元素即为结果 +func makeMinHeap(nums []int) { + for i := len(nums) >> 1 - 1; i >= 0; i-- { + minHeapify(nums, i) + } +} +func minHeapify(nums []int, i int) { + if i > len(nums) { return } + temp, n := nums[i], len(nums) + l, r := i << 1 + 1, i << 1 + 2 + for l < n { + r = l+1 + if r < n && nums[r] < nums[l] { l++ } + if nums[i] < nums[l] { break } + nums[i] = nums[l] + nums[l] = temp + i = l + l = i << 1 + 1 + } +} +``` + +#### 面试题40 最小的k个数 + +题目要求:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/ + +思路分析: + +1)注意边界条件k等于零 + +2)建立大顶堆的结果集 + +3)遍历数组,如果小于堆顶元素,则替换,并调整堆 + +```go +// date 2020/02/29 +func getLeastNumbers(arr []int, k int) []int { + if k == 0 { return []int{} } + res, size := make([]int, k), 0 + for _, v := range arr { + if size < k { + res[size] = v + size++ + if size == k { + makeMaxHeap(res) + } + } else if v < res[0] { + res[0] = v + maxHeapify(res, 0) + } + } + return res +} +// 实现大顶堆的两个函数 +func makeMaxHeap(nums []int) { + for i := len(nums) >> 1 - 1; i >= 0; i-- { + maxHeapify(nums, i) + } +} +func maxHeapify(nums []int, i int) { + if i > len(nums) { return } + temp, n := nums[i], len(nums) + l, r := i << 1 + 1, i << 1 + 2 + for l < n { + r = l+1 + if r < n && nums[r] > nums[l] { l++ } + if nums[i] > nums[l] { break } + nums[i] = nums[l] + nums[l] = temp + i = l + l = i << 1 + 1 + } +} +``` + +#### 面试题17.09 第k个数 + +题目要求:https://leetcode-cn.com/problems/get-kth-magic-number-lcci/ + +思路分析:动态规划 + +```go +// date 2020/02/29 +func getKthMagicNumber(k int) int { + l1, l2, l3 := 0, 0, 0 + res := make([]int, k) + res[0] = 1 + for i := 1; i < k; i++ { + res[i] = min(res[l1] * 3, min(res[l2] * 5, res[l3] * 7)) + if res[i] == res[l1] * 3 { l1++ } + if res[i] == res[l2] * 5 { l2++ } + if res[i] == res[l3] * 7 { l3++ } + } + return res[k-1] +} + +func min(x, y int) int { + if x < y { return x } + return y +} +``` +