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

news2024/9/24 11:45:08

树状数组

树状数组 (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.coloradmin.cn/o/175928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

财务精度:BigInteger 与 BigDecimal

财务精度&#xff1a;BigInteger 与 BigDecimal 每博一文案 师父说: 人这一辈子&#xff0c;真地好难。 有些人&#xff0c;好着好着&#xff0c;忽然就变陌生了&#xff0c;有些手&#xff0c;牵着牵着&#xff0c;瞬间就放开了&#xff0c;有些路&#xff0c;走着走着&#…

算法练习笔记——栈的常用方法以及算法练习

栈学习常用方法介绍力扣练习力扣 20. 有效的括号力扣 32. 最长有效括号常用方法介绍 Stack<Character> characters new Stack<>();//判断栈是否为空boolean empty characters.empty();//将a压入栈底&#xff0c;同时也返回aCharacter push characters.push(a);/…

MYSQL中的常见知识问题(一)

1、MYSQL中redolog、binlog 、undolog的区别与作用。redolog&#xff1a;即重做日志&#xff0c;用来实现事物的一个持久性&#xff0c;由radiobuff和radiolog两部分组成。其中 radiobuff是一个缓冲&#xff0c;存放在内存里面&#xff1b;radiolog是文件&#xff0c;存放在磁盘…

基于粒子群优化和引力搜索混合优化算法改进的前馈神经网络(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【3-神经网络八股】北京大学TensorFlow2.0

课程地址&#xff1a;【北京大学】Tensorflow2.0_哔哩哔哩_bilibiliPython3.7和TensorFlow2.1六讲&#xff1a;神经网络计算&#xff1a;神经网络的计算过程&#xff0c;搭建第一个神经网络模型神经网络优化&#xff1a;神经网络的优化方法&#xff0c;掌握学习率、激活函数、损…

走进 HTML

文章目录01 什么是HTML&#xff1f;02 HTML的基本结构03 网页基本标签04 图像标签05 链接标签06 块元素和行内元素07 列表07 表格08 视频和音频09 页面结构10 iframe内联框架11 表单语法&#x1f449; 表单元素格式&#x1f449; 表单的应用&#x1f449; 表单初级验证01 什么是…

【Mysql】 数据库用户管理

【Mysql】 数据库用户管理 DCL:英文全称是Data Control Language(数据控制语言)&#xff0c;用来管理数据库用户、控制数据库的访问权限。 1. 管理用户 想要对数据库用户进行操作&#xff0c;我们首先得进入 mysql 数据库 use mysql1.1 查询用户 select * from user;该条命…

每日一问-ChapGPT-20230122-关于春节习俗

文章目录每日一问-ChapGPT系列起因每日一问-ChapGPT-20230116-关于春节习俗世界有哪些国家过春节中国各个地区过春节都有哪些习俗台湾的春节习俗有哪些新加坡过春节有哪些习俗初一到初七的传统习俗有哪些在热闹的节日里&#xff0c;自己无法融入氛围&#xff0c;是什么原因不喜…

【MySQL】第六部分 单行函数

【MySQL】第六部分 单行函数 文章目录【MySQL】第六部分 单行函数6. 单行函数6.1 常用的函数6.2 角度与弧度转换函数6.3 三角函数6.4 指数与对数6.5 进制转换6.6 字符串函数6.7 日期时间函数6.7.1 获取时间和日期6.7.2 日期与时间戳的转换6.7.3 获取月份、星期、星期数、天数等…

筑基二层 —— 图解函数递归、数组详解

目录 一.修炼必备 二.图解递归的执行过程 三.数组 3.1 一维数组 3.2 二维数组 3.3 数组的共同问题 一.修炼必备 1.入门必备&#xff1a;VS2019社区版&#xff0c;下载地址&#xff1a;Visual Studio 较旧的下载 - 2019、2017、2015 和以前的版本 (microsoft.com) 2.趁手武…

I.MX6ULL裸机开发笔记7:汇编点亮LED灯

一、vscode调整 加入汇编插件ARM vscode权限受限&#xff08;因为Ubuntu中的文件有的是root权限创建的&#xff0c;vscode以普通用户打开的话没有操作权限&#xff09;chmod 修改文件或者文件夹权限 二、编程步骤 使能GPIO时钟设置引脚复用位GPIO设置引脚属性&#xff08;上下…

深入跨域问题(3) - 利用 JSONP 解决跨域

目录 1.简单例子&#xff1a; 2.基本实现 3.JSONP 与 CORS 的对比 什么是跨域&#xff0c;在这篇文章内部就不再讲述了&#xff0c;本文主要着重于实现 JSONP 。 script 标签&#xff1a; 根据同源策略的限制&#xff0c;在 端口&#xff0c;域名&#xff0c;协议 这三者 …

AX7A200教程(3): DDR3突发读写

上一个章节我们新建工程&#xff0c;然后进行基本的初始化操作&#xff0c;这个章节将在上个工程的基础上进行突发读写因ddr3读写部分控制信号比较多&#xff0c;所以ddr3读写控制模块比较复杂&#xff0c;本章节着重于一个256位数据的突发读写&#xff0c;ddr读写控制模块暂不…

【JavaWeb】前端开发三剑客之CSS(下)

✨哈喽&#xff0c;进来的小伙伴们&#xff0c;你们好耶&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【JavaWeb】 ✈️✈️本篇内容:CSS的深度学习&#xff01; &#x1f680;&#x1f680;代码托管平台github&#xff1a;JavaWeb代码存放仓库&#xff01; ⛵⛵作者…

(考研湖科大教书匠计算机网络)第二章物理层-第一、二节:物理层基本概念和传输媒体

文章目录一&#xff1a;物理层概念二&#xff1a;物理层传输媒体&#xff08;1&#xff09;导引型传输媒体A&#xff1a;同轴电缆B&#xff1a; 双绞线C&#xff1a;光纤①&#xff1a;光纤通信②&#xff1a;光纤D&#xff1a;电力线&#xff08;2&#xff09;非导引型传输媒体…

day22 多线程02

1.线程池 1.1 线程状态介绍 当线程被创建并启动以后&#xff0c;它既不是一启动就进入了执行状态&#xff0c;也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢&#xff1f;Java中的线程 状态被定义在了java.lang.Thread.State…

[ESP][驱动]ST7701S RGB屏幕驱动

ST7701SForESP ST7701S ESP系列驱动&#xff0c;基于ESP-IDF5.0&#xff0c;ESP32S3编写。 本库只负责SPI的配置&#xff0c;SPI设置屏幕两方面。由于RGB库和图形库的配置无法解耦&#xff0c;具体使用的RGB库&#xff0c;图形库需要自行配置添加。 SPI的指令&#xff0c;地…

操作系统真相还原_第5章第2节:内存分页机制

文章目录分段机制分页机制一级页表二级页表启用分页机制的过程启用分页机制(二级页表)详解程序include.incmbr.sloader.s写入硬盘启动bochs执行分段机制 分页机制 一级页表 二级页表 启用分页机制的过程 1、准备好页目录项及页表 2、将页表地址写入控制寄存器cr3 3、寄存器cr0…

【ArcGIS微课1000例】0059:三种底图影像调色技巧案例教程

三种调整影像底图效果的技术,让你的图纸清新脱俗,做出的图更美观! 文章目录 方法一:影像源类型调整方法二:符号拉伸类型设置方法三:影像分析模块设置方法一:影像源类型调整 这种方法是最基础、最简单的一种方法,可以调整的内容有限。当大家发现导入了影像,然后影像图…

Spring Batch 基本概念和运行示例

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