面试被问到线段树,已经这么卷了吗?

本文介绍了线段树这种数据结构,用于解决动态数据场景下的区间查询问题。相比于前缀和数组,线段树在保证O(1)查询效率的同时,能更好地处理数据的修改,其时间复杂度为O(logn)。文章通过实例讲解了线段树的逻辑定义、物理实现、基本操作(建树、查询、更新)以及解题框架,并与前缀和数组进行了对比。
摘要由CSDN通过智能技术生成

在这篇文章里,我将介绍一种高效的区间查询数据结构 —— 线段树(Segment Tree)。如果能帮上忙,请务必点赞加关注,这真的对我非常重要。


目录

前置知识

这篇文章的内容会涉及以下前置 / 相关知识,贴心的我都帮你准备好了,请享用~

=


1. 前缀和数组的缺点

上一篇文章,我们使用了「前缀和 + 差分」技巧解决了 303. 区域和检索 - 数组不可变 【题解】 。简单来说,我们开辟了一个前缀和数组,存储「元素所有前驱节点的和」。利用这个前缀和数组,可以很快计算出区间 [i, j] 的和: nums[i,j]=preSum[j+1]−preSum[i]nums[i, j] = preSum[j + 1] - preSum[i]nums[i,j]=preSum[j+1]−preSum[i]

参考代码:

class NumArray(nums: IntArray) {
    private val sum = IntArray(nums.size + 1) { 0 }

    init {
        for (index in nums.indices) {
            sum[index + 1] = sum[index] + nums[index]
        }
    }

    fun sumRange(i: Int, j: Int): Int {
        return sum[j + 1] - sum[i] // 注意加一
    }
}
复制代码

此时,区间查询的时间复杂度是O(1)O(1)O(1),空间复杂度是O(n)O(n)O(n),总体不错。但是,正如前言提到的,目前我们只考虑了静态数据的场景,如果数据是可修改的会怎么样?

我们需要修正前缀和数组了,例如:将num[2]num[2]num[2]更新为 10,那么preSum[3,...,7]preSum[3,...,7]preSum[3,...,7] 都需要更新,这个更新操作的时间复杂度为O(n)O(n)O(n)。要是一次更新操作影响倒是不大,但如果更新操作很频繁,算法的均摊时间复杂度就劣化了。为了解决动态数据的场景,就出现了 “线段树” 这种数据结构,它和其他数据结构的复杂度对比如下表:

数据结构 构建结构 区间更新 区间查询 空间复杂度
遍历,不使用数据结构 O(1) O(1) O(n) O(1)
前缀和数组 O(n) O(n) O(1) O(n)
线段树 O(n) O(lgn) O(lgn) O(4*n)或O(2*n)
树状数组 O(n) O(lgn) O(lgn) O(n)

可以看到「前缀和数组」的优势是O(1)O(1)O(1)查询,但不适合动态数据的场景,而线段树似乎学会了中庸之道,线段树平衡了「区间查询」和「单点更新」两种操作的时间复杂度。它是怎么做到的呢?


2. 什么是线段树?

这是因为前缀和数组是线性逻辑结构,修改操作一定需要花费线性时间。为了使得修改操作优于线性时间,那么一定需要构建非线性逻辑结构

2.1 线段树的逻辑定义

一般的二叉树节点上存储的是一个值,而线段树上的节点存储的是一个区间[L,R][L, R][L,R] 上的聚合信息(例如最大值 / 最小值 / 和),并且子节点的区间合并后正好等同于父节点的区间。例如,对于父节点的区间是 [L,R][L, R][L,R],那么左子节点的区间是 [L,(L+R)/2][L, (L+R)/2][L,(L+R)/2],右子节点的区间是 [(L+R)/2,R][(L+R)/2, R][(L+R)/2,R]。叶子节点也是一个区间,不过区间端点 L==RL == RL==R,是一个单点区间。

—— 图片引用自 www.jianshu.com/p/4d9da6745… —— yo1ooo 著

从线段树的逻辑定义可以看出:线段树(Segment Tree)本质上是一棵平衡二叉搜索树,也就是说它同时具备二叉搜索树和平衡二叉树的性质:

  • 二叉搜索树:任意节点的左子树上的节点值都小于根节点的值,右子树上的节点值都大于根节点的值;

  • 平衡二叉树(Balance Tree):任意节点的左右子树高度差不大于 1。

2.2 线段树的物理实现

通常,一个二叉树的物理实现可以基于数组,也可以基于链表。不过,因为线段树本身也

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值