阅读 28

二叉搜索树专题

1. 介绍

二叉搜索树是一种特殊的二叉树,初衷是为了方便查找,满足以下性质:

  • 非空左子树的所有键值都小于根节点的键值
  • 非空右子树的所有键值都大于根节点的键值
  • 左右子树也分布是二叉搜索树
  • 没有键值相等的节点

由于这个性质,二叉搜索树有一个重要结论:中序遍历二叉搜索树,得到的结果一定是有序数组
同样由于性质,二叉搜索树有一个最大的弊端:可能退化成链表,为了防止这种情况出现,又设计出二叉平衡树

2. 基本操作

1. 查找

由于二叉搜索树的特性,在二叉搜索树上查找的过程就像做二分查找一样:

  • 若 X 小于根值,在左子树继续查找
  • 若 X 大于根值,在右子树继续查找
  • 若 X 等于根植,找到返回

其模板如下:

func find(root *TreeNode, val int) *TreeNode {
	if root == nil {
		return nil
	}
	if root.Val < val {
		return find(root.Right, val)
	} else if root.Val > val {
		return find(root.Left, val)
	} else {
		return root
	}
}
复制代码

2. 查找极值

由于二叉搜索树的特性,极值肯定存在于:

  • 最大值一定在树的最右子树
  • 最小值一定在树的最左子树

// 如果有右子树,就去右子树找,再没有右子树了,就是当前这个节点
func findMax(root *TreeNode) *TreeNode {
	if root == nil {
		return nil
	} else if root.Right != nil {
		return findMax(root.Right)
	} else {
		return root
	}
}

// 如果有左子树,就去左子树找,再没有左子树了,就是当前这个节点
func findMin(root *TreeNode) *TreeNode {
	if root == nil {
		return nil
	} else if root.Left != nil {
		return findMin(root.Left)
	} else {
		return root
	}
}
复制代码

3. 插入

为了维护二叉搜索树的特性,需要找到合适位置插入:

  • 当 X 等于空,在该点构造节点
  • 当 X 大于当前值,在右子树查找合适位置插入
  • 当 X 小于当前值,在左子树查找合适位置插入
  • 当 X 等于当前值,啥也不干

其模板如下:

func insert(root *TreeNode, val int) *TreeNode {
	if root == nil {
		root = &TreeNode{Val: val}
	} else {
		if root.Val > val {
			root.Left = insert(root.Left, val)
		} else if root.Val < val {
			root.Right = insert(root.Right, val)
		}
	}
	return root
}
复制代码

4. 删除

为了维护二叉搜索树的特性,删除有三种情况:

  1. 要删除的是叶节点,直接删除这个节点
  2. 要删除的节点只有一个子树,用子树代替被删除节点
  3. 要删除的节点有两个子树,用右子树中的最小值左子树的最大值替代被删除节点

其模板如下:

func delete(root *TreeNode, val int) *TreeNode {
	if root == nil {
		return nil
	}
	if root.Val < val {
		root.Right = delete(root.Right, val)
	} else if root.Val > val {
		root.Left = delete(root.Left, val)
	} else {
		if root.Left == nil && root.Right == nil {
			root = nil
		} else if root.Left == nil {
			root = root.Right
		} else if root.Right == nil {
			root = root.Left
		} else if root.Left != nil && root.Right != nil {
			// 找到右子树的最小值
			minRoot := findMin(root.Right)
			root.Val = minRoot.Val
			// 删除右子树的最小值
			root.Right = delete(root.Right, root.Val)
		}
	}
	return root
}
复制代码

3. 常见考点

1. 中序有序

二叉搜素树中序是有序数组,一定要优先考虑是否能中序解决

4. 常见题目

1. 二叉搜索树的属性

通常利用二叉搜索树的性质,前一个元素和当前元素做比较,其模板为:

var pre *TreeNode
func dfs(root *TreeNode) {
    if root == nil {
        return
    }
    dfs(root.Left)
    (逻辑写这里面)
    pre = root
    dfs(root.Right)
}
复制代码

98. 验证二叉搜索树

中序遍历,遍历过程中比较前一个值是否比当前值小

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var pre *TreeNode
func isValidBST(root *TreeNode) bool {
    pre = nil
    return dfs(root)
}
func dfs(root *TreeNode) bool {
    if root == nil {
        return true
    }
    left := dfs(root.Left)
    if pre != nil && pre.Val >= root.Val {
        return false
    }
    pre = root
    right := dfs(root.Right)
    return left && right
}
复制代码

501. 二叉搜索树中的众数

(暴力法)遍历二叉树的过程中使用 map 记录频率,最后输出最高频率对应的值

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var m map[int]int
func findMode(root *TreeNode) []int {
    m = make(map[int]int)
    dfs(root)
    res := []int{}
    maxCount := 0
    for _, v := range m {
        if v > maxCount {
            maxCount = v
        }
    }
    for k, v := range m {
        if v == maxCount {
            res = append(res, k)
        }
    }
    return res
}
func dfs(root *TreeNode) {
    if root == nil {
        return
    }
    dfs(root.Left)
    m[root.Val]++
    dfs(root.Right)
}
复制代码

利用二叉树的性质,中序遍历的过程中:

  • 当 pre 与当前值相等,计数 + 1
  • 当 pre 与当前树不等,计数 = 1

记录最大计数

  • 当当前计数与最大计数相等,将当前结果加入结果集
  • 当当前计数大于最大计数,清除当前结果集,将当前结果加入结果集
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var pre *TreeNode
var count int
var maxCount int
var res []int
func findMode(root *TreeNode) []int {
    pre = nil
    count = 0
    maxCount = 0
    res = make([]int, 0)
    dfs(root)
    return res
}
func dfs(root *TreeNode) {
    if root == nil {
        return 
    }
    dfs(root.Left)
    if pre == nil || pre.Val != root.Val {
        count = 1
    } else {
        count++
    }
    if count == maxCount {
        res = append(res, root.Val)
    }
    if count > maxCount {
        maxCount = count
        res = make([]int, 0)
        res = append(res, root.Val)
    }
    pre = root
    dfs(root.Right)
}
复制代码

783. 二叉搜索树节点最小距离

照着模板算最小距离就行

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var pre *TreeNode
var minDiff int
func minDiffInBST(root *TreeNode) int {
    minDiff = math.MaxInt32
    pre = nil
    dfs(root)
    return minDiff
}
func dfs(root *TreeNode) {
    if root == nil {
        return 
    }
    dfs(root.Left)
    if pre != nil && minDiff > root.Val - pre.Val {
        minDiff = root.Val - pre.Val
    }
    pre = root
    dfs(root.Right)
}
复制代码

700. 二叉搜索树中的搜索

前面有提到过的搜索,和要搜索的值进行比较,选择不同路径走

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func searchBST(root *TreeNode, val int) *TreeNode {
    if root == nil {
        return root
    } else if root.Val > val {
        return searchBST(root.Left, val)
    } else if root.Val < val {
        return searchBST(root.Right, val)
    }
    return root
}
复制代码

1038. 把二叉搜索树转换为累加树

中序拿到有序数组,累加和后再次中序加上去

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var res []int
var prefixSum []int
var count int
func bstToGst(root *TreeNode) *TreeNode {
    res = []int{}
    dfs(root)
    prefixSum = make([]int, len(res))
    prefixSum[len(prefixSum)-1] = res[len(res)-1]
    for i := len(prefixSum)-2; i >= 0; i-- {
        prefixSum[i] = prefixSum[i+1] + res[i]
    }
    count = 0
    redfs(root)
    return root
}
func redfs(root *TreeNode) {
    if root == nil {
        return
    }
    redfs(root.Left)
    root.Val = prefixSum[count]
    count++
    redfs(root.Right)
}

func dfs(root *TreeNode) {
    if root == nil {
        return
    }
    dfs(root.Left)
    res = append(res, root.Val)
    dfs(root.Right)
}
复制代码

倒着中序,遍历一个累加一个

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var pre int
func bstToGst(root *TreeNode) *TreeNode {
    pre = 0
    dfs(root)
    return root
}
func dfs(root *TreeNode) {
    if root == nil {
        return
    }
    dfs(root.Right)
    root.Val += pre
    pre = root.Val
    dfs(root.Left)
}
复制代码

235. 二叉搜索树的最近公共祖先

二叉搜索树遍历,公共祖先的值肯定在区间 [p,q] 或 [q,p] 中

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val   int
 *     Left  *TreeNode
 *     Right *TreeNode
 * }
 */
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
	if root == nil {
        return root
    }
    // 比两边都大了,要往左找
    if root.Val > p.Val && root.Val > q.Val {
        return lowestCommonAncestor(root.Left, p, q)
    }
    // 比两边都小了,要往右找
    if root.Val < p.Val && root.Val < q.Val {
        return lowestCommonAncestor(root.Right, p, q)
    }
    // 确实在 [p,q] 或 [q,p] 区间
    return root
}
复制代码

2. 二叉搜索树的修改

701. 二叉搜索树中的插入操作

如果比当前节点值大,往右边插;如果比当前节点值小,往左边插

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func insertIntoBST(root *TreeNode, val int) *TreeNode {
    if root == nil {
        root = &TreeNode{Val: val}
    } else {
        if root.Val < val {
            root.Right = insertIntoBST(root.Right, val)
        } else if root.Val > val {
            root.Left = insertIntoBST(root.Left, val)
        }
    }
    return root
}
复制代码

450. 删除二叉搜索树中的节点

三种情况:

  • 被删除节点无子树,直接删除
  • 被删除节点有一个子树,使用子树当作当前节点
  • 被删除节点有两个子树,选择右子树最大值替代当前节点,再删除被选择的节点
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func deleteNode(root *TreeNode, key int) *TreeNode {
    if root == nil {
        return root
    } else 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 == nil && root.Right == nil {
            root = nil 
        } else if root.Left == nil {  // 有一个子树
            root = root.Right
        } else if root.Right == nil {  // 有一个子树
            root = root.Left
        } else {   // 有两个子树
            node := findMin(root.Right)
            root.Val = node.Val
            root.Right = deleteNode(root.Right, node.Val)
        }
    }
    return root
}

func findMin(root *TreeNode) *TreeNode {
    if root == nil {
        return root
    } else if root.Left != nil {
        return findMin(root.Left)
    } else {
        return root
    }
}
复制代码

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

要求平衡,乍一看好像要二叉平衡树,其实不需要的,只需要挑最中间的作为根,建出来的树自然满足题意

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func sortedArrayToBST(nums []int) *TreeNode {
    if len(nums) == 0 {
        return nil
    }
    rootIdx := len(nums)/2
    rootVal := nums[rootIdx]
    
    root := &TreeNode{Val: rootVal}
    root.Left = sortedArrayToBST(nums[:rootIdx])
    root.Right = sortedArrayToBST(nums[rootIdx+1:])
    return root 
}
复制代码

669. 修剪二叉搜索树

前序遍历,遍历过程中如果发现有节点在范围外,就不返回这个节点,而是返回可能存在返回内的节点(比如小了返回右儿子)

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func trimBST(root *TreeNode, low int, high int) *TreeNode {
    if root == nil {
        return nil
    }
    if root.Val < low {
        return trimBST(root.Right, low, high)
    } else if root.Val > high {
        return trimBST(root.Left, low, high)
    }
    root.Left = trimBST(root.Left, low, high)
    root.Right = trimBST(root.Right, low, high)
    return root
}
复制代码
文章分类
后端
文章标签