高级数据结构1—初识树状数组—快速求得前缀和和修改某一元素值

本文介绍了树状数组的基本原理,包括其在快速求前缀和、修改元素值方面的优势,以及在实际问题如楼兰图腾、区间和检索等中的应用实例。通过理解区间元素个数的计算和父节点查找,掌握如何使用树状数组优化nlogn复杂度问题。

- 本人的LeetCode账号:魔术师的徒弟,欢迎关注获取每日一题题解,快来一起刷题呀~

  • 本人Gitee账号:路由器,欢迎关注获取博客内容源码。

  树状数组和其他的高级数据结构不同,它非常的好写,同时解决问题也比较局限,所以树状数组的题目的难度主要集中在思考而非代码。

一、基本原理

  树状数组可以解决两个操作:快速的求前缀和修改某一个数,这两个操作都是O(logn)的。

  这两个操作如果我们直接来操作:

  • 存原数组,前缀和O(N),修改一个数O(1)
  • 维护前缀和,前缀和O(1),修改一个数O(N)

  有一种鱼和熊掌不可兼得的感觉,但是我们的题目中时间复杂度一般取决于最糟糕的时间复杂度,所以如果有n次查询,那么复杂度会达到O(n^2)。树状数组有一个折中的思想,它让这两个操作的时间复杂度都变成了O(logn),这样总时间复杂度就是O(nlogn),就会快很多了。

  它是一种基于二进制的方法来解决这个问题的。

  假设我们有一个数x,其二进制表示为:
x = 2 i k + 2 i k − 1 + . . . + 2 i 1 i k > = i k − 1 > = . . . > = i 1 x = 2^{i_k} + 2^{i_{k - 1}} + ...+2^{i_{1}}\\ i_{k}>=i_{k-1}>=...>=i_1 x=2ik+2ik1+...+2i1ik>=ik1>=...>=i1
  假设我们想求的是下标为1~x的总和,那么我们可以把1~x这个区间划分成k部分:
( x − 2 i 1 , x ] ( x − 2 i 1 − 2 i 2 , x − 2 i 1 ] . . . ( 0 , x − 2 i 1 − 2 i 2 − . . . − 2 i k − 1 ] (x-2^{i_1},x]\\(x-2^{i_1}-2^{i_2}, x-2^{i_1}]\\...\\(0,x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}}] (x2i1,x](x2i12i2,x2i1]...(0,x2i12i2...2ik1]
  这样就把下标为1~x这个区间划分成了logx份,这样如果算1~x的总和,只需要求logx个区间的和就能算出来了。

  这个思想就是让我们在logn的时间复杂度中使用前缀和的思想。

  下面来看看区间中元素的个数和区间右端点有什么关系:
( x − 2 i 1 , x ] , 元 素 个 数 2 1 i 个 ( x − 2 i 1 − 2 i 2 , x − 2 i 1 ] , 元 素 个 数 2 2 i 个 . . . ( 0 , x − 2 i 1 − 2 i 2 − . . . − 2 i k − 1 ] , 元 素 个 数 2 k i 个 (x-2^{i_1},x], 元素个数2^i_1个\\ (x-2^{i_1}-2^{i_2}, x-2^{i_1}],元素个数2^i_2个\\ ...\\ (0,x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}}],元素个数2^i_k个 (x2i1,x],21i(x2i12i2,

在Java中,数组的长度是固定的,初始化后不能直接插入元素。不过,可以通过创建新数组的方式来实现插入元素的效果。以下是具体的实现步骤代码示例: ### 实现思路 1. 创建一个新数组,长度比原数组大1。 2. 将原数组的元素复制到新数组中。 3. 在指定位置插入新元素。 ### 代码示例 ```java public class ArrayInsertion { public static void main(String[] args) { // 初始化长度为5的数组 int[] arr = {1, 2, 3, 4, 5}; // 要插入的元素 int elementToInsert = 6; // 要插入的位置 int insertIndex = 2; // 调用插入元素的方法 int[] newArr = insertElement(arr, elementToInsert, insertIndex); // 输出新数组 for (int num : newArr) { System.out.print(num + " "); } } public static int[] insertElement(int[] arr, int element, int index) { // 创建一个新数组,长度比原数组大1 int[] newArr = new int[arr.length + 1]; // 将原数组的元素复制到新数组中 for (int i = 0; i < index; i++) { newArr[i] = arr[i]; } // 在指定位置插入新元素 newArr[index] = element; // 将原数组剩余的元素复制到新数组中 for (int i = index; i < arr.length; i++) { newArr[i + 1] = arr[i]; } return newArr; } } ``` ### 代码解释 1. **创建新数组**:`int[] newArr = new int[arr.length + 1];` 创建一个长度比原数组大1的新数组。 2. **复制元素**:使用 `for` 循环将原数组的元素复制到新数组中。 3. **插入元素**:`newArr[index] = element;` 在指定位置插入新元素。 4. **复制剩余元素**:使用 `for` 循环将原数组剩余的元素复制到新数组中。 ### 复杂度分析 - **时间复杂度**:$O(n)$,其中 $n$ 是原数组的长度。 - **空间复杂度**:$O(n+1)$,主要用于创建新数组。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值