树状数组(Binary Indexed Tree (B.I.T))

news/2024/11/17 23:47:12/

树状数组

树状数组 (Binary Indexed Tree(B.I.T), Fenwick Tree) 是一个查询和修改复杂度都为 log(n) 的数据结构。
「前缀和查询」与「单点更新」
直接前驱:c[i] 的直接前驱为 c[i - lowbid(i)],即 c[i] 左侧紧邻的子树的根。
直接后继:c[i] 的直接前驱为 c[i + lowbid(i)],即 c[i] 的父结点。
前驱:c[i] 左侧所有子树的根。
后继:c[i] 的所有祖先。

在这里插入图片描述

307. 区域和检索 - 数组可修改

单点更新,区间求和。

class NumArray {    int lowbit(int x) {return x & -x;}int query(int i) {int ans = 0;for (; i > 0; i -= lowbit(i)) ans += tree[i];return ans;}void add(int i, int u) {for (; i <= n; i += lowbit(i)) tree[i] += u;}int[] nums, tree;int n;public NumArray(int[] _nums) {nums = _nums;n = nums.length;tree = new int[n + 1];for (int i = 0; i < n; i++) add(i + 1, nums[i]);}public void update(int i, int val) {add(i + 1, val - nums[i]);nums[i] = val;}public int sumRange(int l, int r) {return query(r + 1) - query(l);}
}

时间复杂度:add 操作和 query 的复杂度都是 O(logn),因此构建数组的复杂度为 O(nlogn)。整体复杂度为 O(nlogn)
空间复杂度:O(n)

315. 计算右侧小于当前元素的个数

「离散化」:把原序列的值域映射到一个连续的整数区间,并保证它们的偏序关系不变。

  • 逆序遍历 nums 读取排名;
  • 先查询严格小于当前排名的「前缀和」,即严格小于当前排名的元素的个数,「前缀和查询」;
  • 给「当前排名」加 1,「单点更新」。
class Solution {public List<Integer> countSmaller(int[] nums) {List<Integer> res = new ArrayList();int n = nums.length;discrete(nums);BIT bit = new BIT(n);for (int i = n - 1; i >= 0; i--) {             int j = nums[i];bit.update(j + 1, 1);    res.add(bit.query(j));            }Collections.reverse(res);return res;}// 离散化 改变了原数组,偏序关系不变。void discrete(int[] nums) {int n = nums.length;int[] tmp = Arrays.copyOf(nums,  n);Arrays.sort(tmp);for (int i = 0; i < n; i++) {nums[i] = Arrays.binarySearch(tmp, nums[i]);}}// 数状数组模板class BIT {private int[] tree;private int n;public BIT(int n) {this.n = n;tree = new int[n + 1];}// 单点更新 public void update(int i, int delta) {            for (; i <= n; i += lowbit(i))  tree[i] += delta;}// 区间查询 前缀和public int query(int i) {int sum = 0;for (; i > 0; i -= lowbit(i)) sum += tree[i];return sum;}int lowbit(int x) {return x & (-x);}}
}

218. 天际线问题

327. 区间和的个数

树状数组(离散化)
由于区间和的定义是子数组的元素和,容易想到「前缀和」来快速求解。

对于每个 nums[i] 而言,需要统计以每个 nums[i] 为右端点的合法子数组个数(合法子数组是指区间和值范围为 [lower, upper] 的子数组)。

可以从前往后处理 nums,假设当前处理到位置 k,同时下标 [0, k] 的前缀和为 s,那么以 nums[k] 为右端点的合法子数组个数,等价于在下标 [0, k - 1] 中前缀和范围在 [s - upper, s - lower] 的数的个数。

需要使用一个数据结构来维护「遍历过程中的前缀和」,每遍历 nums[i] 需要往数据结构加一个数,同时每次需要查询值在某个范围内的数的个数。涉及的操作包括「单点修改」和「区间查询」,容易想到使用树状数组进行求解。

但值域的范围是巨大的(同时还有负数域),可以利用 nums 的长度为 105 来做离散化。需要考虑用到的数组都有哪些:

首先前缀和数组中的每一位 s 都需要被用到(添加到树状数组中);
同时对于每一位 nums[i](假设对应的前缀和为 s),我们都需要查询以其为右端点的合法子数组个数,即查询前缀和范围在 [s - upper, s - lower] 的数的个数。
因此对于前缀和数组中的每一位 s,我们用到的数有 s、s - upper 和 s - lower 三个数字,共有 1e51e5 个 ss,即最多共有 3×105 个不同数字被使用,我们可以对所有用到的数组进行排序编号(离散化),从而将值域大小控制在 3×105 范围内。

class Solution {int m;int[] tree = new int[100010 * 3];int lowbit(int x) {return x & -x;}void add(int x, int v) {for (int i = x; i <= m; i += lowbit(i)) tree[i] += v;}int query(int x) {int ans = 0;for (int i = x; i > 0; i -= lowbit(i)) ans += tree[i];return ans;}public int countRangeSum(int[] nums, int lower, int upper) {Set<Long> set = new HashSet<>();long s = 0;set.add(s);for (int i : nums) {s += i;set.add(s);set.add(s - lower);set.add(s - upper);}List<Long> list = new ArrayList<>(set);Collections.sort(list);Map<Long, Integer> map = new HashMap<>();for (long x : list) map.put(x, ++m);s = 0;int ans = 0;add(map.get(s), 1);for (int i : nums) {s += i;int a = map.get(s - lower), b = map.get(s - upper) - 1;ans += query(a) - query(b);add(map.get(s), 1);}return ans;}
}

406. 根据身高重建队列

493. 翻转对

673. 最长递增子序列的个数

1157. 子数组中占绝大多数的元素

1395. 统计作战单位数

class Solution {    public int numTeams(int[] rating) {int n = rating.length;discrete(rating);int ans = 0;BIT bit = new BIT(n);for (int i = 0; i < n; i++) {int x = rating[i];            int frontSmall = bit.query(x); // 前面比x小的个数int frontLarge = i - frontSmall; // 前面比x大的个数 int backSmall = x - frontSmall;int backLarge = n - 1 - i - backSmall;ans += frontSmall * backLarge + frontLarge * backSmall;bit.update(x + 1, 1); // 对应 tree 需要 + 1}return ans;}// 离散化 改变了原数组,偏序关系不变。void discrete(int[] nums) {int n = nums.length;int[] tmp = Arrays.copyOf(nums,  n);Arrays.sort(tmp);for (int i = 0; i < n; i++) {nums[i] = Arrays.binarySearch(tmp, nums[i]);}}// 数状数组模板class BIT {private int[] tree;private int n;public BIT(int n) {this.n = n;tree = new int[n + 1];}// 单点更新 public void update(int i, int delta) {            for (; i <= n; i += lowbit(i))  tree[i] += delta;}// 区间查询 前缀和public int query(int i) {int sum = 0;for (; i > 0; i -= lowbit(i)) sum += tree[i];return sum;}int lowbit(int x) {return x & (-x);}}
}

1409. 查询带键的排列

1505. 最多 K 次交换相邻数位后得到的最小整数

1649. 通过指令创建有序数组

1964. 找出到每个位置为止最长的有效障碍赛跑路线

2179. 统计数组中好三元组数目

2193. 得到回文串的最少操作次数

2250. 统计包含每个点的矩形数目

2286. 以组为单位订音乐会的门票

2407. 最长递增子序列 II

2424. 最长上传前缀

2426. 满足不等式的数对数目


http://www.ppmy.cn/news/18741.html

相关文章

promise对象

ES6 Promise 对象 1. Promise 的含义 Promise 是异步编程的一种解决方案&#xff0c;比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现&#xff0c;ES6 将其写进了语言标准&#xff0c;统一了用法&#xff0c;原生提供了 Promise 对象。 所谓…

论文投稿指南——中文核心期刊推荐(水利工程)

【前言】 🚀 想发论文怎么办?手把手教你论文如何投稿!那么,首先要搞懂投稿目标——论文期刊 🎄 在期刊论文的分布中,存在一种普遍现象:即对于某一特定的学科或专业来说,少数期刊所含的相关情报量很大,而多数期刊的情报量却很少;也就是说,世界上大量的科学论文集中…

String 与 StringBuffer 与 StringBuilder 各自的妙用

String 与 StringBuffer 与 StringBuilder 各自的妙用 每博一文案 我从未见过&#xff0c;一个早起&#xff0c;勤奋&#xff0c;谨慎&#xff0c;诚实的人&#xff0c;抱怨命运不好的。 最完美的状态&#xff0c;不是你从不失误&#xff0c;而是你从没放弃成长。没人能把你变…

第二章 IOC

1.IOC底层原理*什么是IOC&#xff1a;控制反转&#xff0c;把对象创建和对象之间的调用过程交给Spring进行管理*使用IOC的目的&#xff1a;降低耦合度*IOC底层原理&#xff1a;xml解析工厂模式反射*IOC思想基于IOC容器完成&#xff0c;IOC容器底层就是对象工厂*Spring提供IOC容…

【Python语言基础】——Python Lambda

Python语言基础——Python Lambda 文章目录 Python语言基础——Python Lambda一、Python Lambda一、Python Lambda lambda 函数是一种小的匿名函数。 lambda 函数可接受任意数量的参数,但只能有一个表达式。 语法 lambda arguments : expression 执行表达式并返回结果: 实例…

基于OpenCv的人脸识别,翻车了居然识别错误。

前言 我们身边的人脸识别有车站检票&#xff0c;监控人脸&#xff0c;无人超市&#xff0c;支付宝人脸支付&#xff0c;上班打卡&#xff0c;人脸解锁手机。 人脸检测是人脸识别系统组成的关键部分之一&#xff0c;其目的是检测出任意给定图片中的包含的一个或多个人脸&#xf…

Spring Batch 基本概念和运行示例

基本概念 Spring Batch是批处理框架。 作业(Job)是状态以及状态之间转换的集合。 作业里包含步骤&#xff08;Spring Bean&#xff09;&#xff0c;每一个步骤解耦到独立的处理器中&#xff0c;并负责自己的数据&#xff0c;把所需的业务逻辑应用到数据上&#xff0c;然后把数…

AlmaLinux 9 安装Kasm Workspaces

今天尝试一下AlmaLinux 9 安装Kasm Workspaces。 前提条件 安装了Docker和Docker Compose&#xff0c;已经最新版本要求&#xff0c; docker 18.06 docker compose 2.1.1 创建一个Swap分区 下面的步骤将创建一个2千兆字节&#xff08;2048MB&#xff09;的交换分区。请根据…