文章

树(Tree)

  • 是一个一个一个无环图罢了(悲~

树的遍历

  • 树的遍历可以参考图的遍历,其他稍微有点特殊的另外给出:

DFS(先序,中序,后序)

  • 先序pre-order144. Binary Tree Preorder Traversal

    1
    2
    3
    4
    5
    6
    7
    
    func preOrder(node *TreeNode) {
        if node != nil {
            ...
            preOrder(node.Left)
            preOrder(node.Right)
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    func preOrder(node *TreeNode) {
        stack := make([]*TreeNode, 0)
        ptr := root
        for ptr != nil || len(stack) != 0 {
            if ptr != nil {
                ...
                stack = append(stack, ptr)
                ptr = ptr.Left
            }  else {
                ptr = stack[len(stack)-1].Right
                stack = stack[:len(stack)-1]
            }
        }
    }
    
  • 中序in-order94. Binary Tree Inorder Traversal

    1
    2
    3
    4
    5
    6
    7
    
    func inOrder(node *TreeNode) {
        if node != nil {
            preOrder(node.Left)
            ...
            preOrder(node.Right)
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    func inOrder(node *TreeNode) {
        stack := make([]*TreeNode, 0)
        ptr := root
        for ptr != nil || len(stack) != 0 {
            if ptr != nil {
                stack = append(stack, ptr)
                ptr = ptr.Left
            }  else {
                ptr = stack[len(stack)-1].Right
                ...
                stack = stack[:len(stack)-1]
            }
        }
    }
    
  • 后序post-order145. Binary Tree Postorder Traversal

    1
    2
    3
    4
    5
    6
    7
    
    func preOrder(node *TreeNode) {
        if node != nil {
            ...
            preOrder(node.Left)
            preOrder(node.Right)
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    func inOrder(node *TreeNode) {
        stack := make([]*TreeNode, 0)
        visited := make([]bool, 0)
        res := make([]*TreeNode, 0)
        ptr := root
        stack = append(stack, ptr)
        visited = append(visited, false)
          
        for len(stack) != 0 {
            ptr, v = stack[len(stack)-1], visited[len(stack)-1]
            stack, visited = stack[:len(stack)-1], visited[:len(stack)-1]
            if v {
                res = append(res, ptr)
            }  else {
                stack = append(stack, ptr)
        		visited = append(visited, true)
                if ptr.Right != nil {
                    stack = append(stack, ptr.Right)
    	    		visited = append(visited, false)
                }
                if ptr.Left != nil {
                    stack = append(stack, ptr.Left)
    	    		visited = append(visited, false)
                }
            }
        }
    }
    
  • 树的遍历题很多都是可以通过递归来解决,经典的是 DFS

树的遍历例题

IDLeetCode 题号描述思路
1199. Binary Tree Right Side View树的右视图层序遍历 BFS,每次添加层末尾元素
2124. Binary Tree Maximum Path Sum无向树中权最大的路径DFS 更新和返回值分开,更新当前节点 + 左子树递归 + 右子树递归,返回当前节点 + 子树递归最大值
398. Validate Binary Search Tree验证是否是二叉搜索树要注意整棵子树和根节点的大小关系
4297. Serialize and Deserialize Binary Tree二叉树的编码和解码DFS + 按序遍历
5863. All Nodes Distance K in Binary Tree找距离某节点长度为k的所有点DFS ,通过记录父节点统一 DFS 的过程,通过哈希表排除重复节点
314. Binary Tree Vertical Order Traversal366 Find Leaves of Binary TreeLowest Common Ancestor系列428 Serialize and Deserialize N-ary Tree

线段树(Segment Tree)

参考

  1. https://leetcode.cn/problems/range-sum-query-mutable/solution/by-lfool-v3x9/
  • 一颗线段树长如下图所示,其本质是用于范围统计($\sum^{r}_{i=l}$)的一个树状结构,采用二分思想;其叶子节点存放着单点信息,而其他非叶节点则存放着范围信息。这就意味着所有范围操作都能递归进行,代码简单(但不直白)。

img

  • 常见的范围统计操作无外乎:$sum$ , $max$ ,$min$ ,$avg$ 此类。

已有数组→线段树(离散化树状数组)

  • 当给定一个数组时,我们需要在这个基础上“长”出一颗线段树:(自底向上

img

  • 这种线段树的索引构思很巧妙,我们不难发现,最底层的节点二进制最低位为 $1$,倒数第二层的最低位为 $10$,以此类推,我们就能得到递推公式:i = i + (i & -i)。以题307. Range Sum Query - Mutable为例:

  • 数据结构

    1
    2
    3
    4
    5
    
    type NumArray struct {
    	nums []int
    	tree []int
    	n    int
    }
    
  • 添加更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    func (na *NumArray) add(index int, val int) {
    	for i := index + 1; i <= na.n; i += lowBit(i) {
    		na.tree[i] += val
    	}
    }
      
    func lowbit(i int) int {
    	return i & -i
    }
      
    func (na *NumArray) update(index int, val int) {
    	na.add(index, val-na.nums[index])
    	na.nums[index] = val
    }
    
  • 范围查询(这一步区别于标准线段树,他每个节点的和等于从 $1$ 号结点开始到当前节点的数据和,所以这里的 $left$ 其实是 $left+1-1$)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    func (na *NumArray) query(i int) (res int) {
    	for ; i > 0; i -= lowBit(i) {
    		res += na.tree[i]
    	}
    	return
    }
      
    func (na *NumArray) sumRange(left int, right int) int {
    	return na.query(right+1) - na.query(left)
    }
    

动态开线段树

  • 堆式储存的情况下,需要给线段树开 $4n$ 大小的数组(这个数据是 $log_2$ 累加算出来的)。为了节省空间,我们可以不一次性建好树,而是:

    1. 在最初只建立一个根结点代表整个区间;
    2. 当我们需要访问某个子区间时,才建立代表这个区间的子结点;这样我们不再使用 $2p$ 和 $2p+1$ 代表 $p$ 结点的儿子,而是用 $ls$ 和 $rs$ 记录儿子的编号。总之,动态开点线段树的核心思想就是:结点只有在有需要的时候才被创建

    单次操作的时间复杂度是不变的,为 $O(log\,n)$ 。由于每次操作都有可能创建并访问全新的一系列结点,因此 $m$ 次单点操作后结点的数量规模是 $O(m\,log\,n)$。最多也只需要 $2n-1$ 个结点,没有浪费。

  • 懒惰标记优化:更新时线段树讲究向下创建节点,但是现在我不了,对于已有分段我不创建,而是使用懒惰标记表示一次或者多次更新,而放到后面再做操作。

  • map 版本:(以 729. My Calendar I 为题)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    
    type MyCalendar struct {
    	tree, lazy map[int]bool
    }
      
    func Constructor() MyCalendar {
    	return MyCalendar{
    		tree: map[int]bool{},
    		lazy: map[int]bool{},
    	}
    }
      
    func (c *MyCalendar) query(start, end int, left, right int, idx int) bool {
    	if right < start || left > end {
    		return false
    	}
    	if c.lazy[idx] {
    		return true
    	}
    	if start <= left && right <= end {
    		return c.tree[idx]
    	}
    	mid := (left + right) >> 1
    	return c.query(start, end, left, mid, idx*2) ||
    		c.query(start, end, mid+1, right, idx*2+1)
    }
      
    func (c *MyCalendar) update(start, end int, left, right int, idx int) {
    	if right < start || left > end {
    		return
    	}
    	if start <= left && right <= end {
    		c.tree[idx] = true
    		c.lazy[idx] = true
    	} else {
    		mid := (left + right) >> 1
    		c.update(start, end, left, mid, idx*2)
    		c.update(start, end, mid+1, right, idx*2+1)
    		c.tree[idx] = true
    		if c.lazy[2*idx] && c.lazy[2*idx+1] {
    			c.lazy[idx] = true
    		}
    	}
    }
      
    func (c *MyCalendar) Book(start int, end int) bool {
    	if c.query(start, end-1, 0, 1e9, 1) {
    		return false
    	}
    	c.update(start, end-1, 0, 1e9, 1)
    	return true
    }
    
  • tree 版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    
    type node struct {
    	l, r        int
    	left, right *node
    	lazy        bool
    	val         bool
    }
      
    type MyCalendar struct {
    	root *node
    }
      
    func Constructor() MyCalendar {
    	return MyCalendar{&node{l: 0, r: 1e9, lazy: false, val: false}}
    }
      
    func query(cur *node, start, end int) bool {
    	if start > cur.r || end < cur.l {
    		return false
    	}
    	if start <= cur.l && cur.r <= end {
    		return cur.val
    	}
      
    	lazyCreate(cur)
    	return query(cur.left, start, end) || query(cur.right, start, end)
    }
      
    func update(cur *node, start, end int) {
    	if start > cur.r || end < cur.l {
    		return
    	}
    	if start <= cur.l && cur.r <= end {
    		cur.lazy = true
    		cur.val = true
    		return
    	}
      
    	lazyCreate(cur)
    	update(cur.left, start, end)
    	update(cur.right, start, end)
    	cur.val = cur.left.val || cur.right.val
    }
      
    func lazyCreate(cur *node) {
    	mid := cur.l + (cur.r-cur.l)>>1
    	if cur.left == nil {
    		cur.left = &node{l: cur.l, r: mid}
    	}
    	if cur.right == nil {
    		cur.right = &node{l: mid + 1, r: cur.r}
    	}
    	if !cur.lazy {
    		return
    	}
    	cur.left.lazy = cur.lazy
    	cur.right.lazy = cur.lazy
    	cur.left.val = cur.left.val || cur.lazy
    	cur.right.val = cur.right.val || cur.lazy
    	cur.lazy = false
    }
      
    func (c *MyCalendar) Book(start int, end int) bool {
    	if query(c.root, start, end-1) {
    		return false
    	}
    	update(c.root, start, end-1)
    	return true
    }
    

线段树例题

IDLeetCode 题号描述思路
1307. Range Sum Query - Mutable区域检索和更新从给定的数组生成线段树
2729. My Calendar I日程预定列表线段树 + 懒惰标记
3731. My Calendar II日程预定列表(可重复预定)线段树 + 懒惰标记,注意更新方式
4732. My Calendar III日程预定列表(计算最大重复度)LeetCode不会解释例子可以不解释嗷,万能模板:线段树 + 懒惰标记
52407. Longest Increasing Subsequence II最长递增子序列,但是有间隔限制动态开树的线段树优化找的过程,原本还有一维DP的,结果发现不需要,我是傻逼
6715. Range Module范围查询线段树
7315. Count of Smaller Numbers After Self判断后序数列中有几个比当前数少离散化树状数组 + 相对顺序排序(压缩,不然会吃 超时
8775. Global and Local Inversions全局倒置和局部倒置模拟 + 线段树统计
715. Range 模块699. 掉落的方块933. 最近的请求次数

字典树

  • 多叉树的特殊用例,每一个单词作为一个节点,搜索过程仍为 $O(n)$ 的复杂度,但是可以减少初始队列的大小,适合做类似于字典查找的工作,一般用于带字符串查找的题目。

  • 字典树初始化

    1
    2
    3
    4
    5
    6
    
    type node struct {
        isEnd bool
        nextMap map[byte]*node
    }
      
    var root = &node{...}
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    func insert(root *node, word string) {
        cur := root
        for i := 0; i < len(word); i++ {
            if cur.nextMap[word[i]] == nil {
                cur.nextMap[word[i]] = &node{...}
            }
            cur = cur.nextMap[word[i]]
        }
        cur.isEnd = true
    }
    
  • 字典树查找

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    func search(root *node, word string) bool {
        cur := root
        for i := 0; i < len(word); i++ {
            if cur.nextMap[word[i]] == nil {
                return false
            }
            cur = cur.nextMap[word[i]]
        }
        return cur.isEnd
    }
    

字典树列题

IDLeetCode 题号描述思路
1745. Prefix and Suffix Search给定前缀和后缀查找单词变形的字典树
本文由作者按照 CC BY 4.0 进行授权