树状数组练习题

这篇具有很好参考价值的文章主要介绍了树状数组练习题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

树状数组练习题

为什么大佬们一眼看出是树状数组呢?

不是一眼看出树状数组,而是要把 【找两个相邻的最小值之间还有几个数没删掉】 的问题转变成动态区间和问题,这个转化过程才是最重要的 至于你用什么数据结构求动态区间和是次要的,很多数据结构都能做,最常用的树状数组和线段树(这题比较特殊有序集合也能做),树状数组容易套板,码量少,常数还比线段树小。所以你才会看到大佬们清一色的树状数组。

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

难度中等602

给你一个数组 nums ,请你完成两类查询。

  1. 其中一类查询要求 更新 数组 nums 下标对应的值
  2. 另一类查询要求返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 ,其中 left <= right

实现 NumArray 类:

  • NumArray(int[] nums) 用整数数组 nums 初始化对象
  • void update(int index, int val)nums[index] 的值 更新val
  • int sumRange(int left, int right) 返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 (即,nums[left] + nums[left + 1], ..., nums[right]

示例 1:

输入:
["NumArray", "sumRange", "update", "sumRange"]
[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
输出:
[null, 9, null, 8]

解释:
NumArray numArray = new NumArray([1, 3, 5]);
numArray.sumRange(0, 2); // 返回 1 + 3 + 5 = 9
numArray.update(1, 2);   // nums = [1,2,5]
numArray.sumRange(0, 2); // 返回 1 + 2 + 5 = 8

提示:

  • 1 <= nums.length <= 3 * 104
  • -100 <= nums[i] <= 100
  • 0 <= index < nums.length
  • -100 <= val <= 100
  • 0 <= left <= right < nums.length
  • 调用 updatesumRange 方法次数不大于 3 * 104
class NumArray {

    BinaryIndexedTree t;
    int[] nums;

    public NumArray(int[] nums) {
        this.nums = nums;
        int n = nums.length;
        t = new BinaryIndexedTree(n+1);
        for(int i = 0; i < n; i++){
            t.add(i+1, nums[i]);
        }
    }
    
    public void update(int index, int val) {
        t.add(index+1, val - nums[index]); // val - nums[index] :变化量
        nums[index] = val;
    }
    
    public int sumRange(int left, int right) {
        return t.query(right+1) - t.query(left);
    }
}
// 模板
class BinaryIndexedTree{
    private int n;
    private int[] tree;

    public BinaryIndexedTree(int n){ // 初始化时,要传入n+1
        this.n = n;
        tree = new int[n];
    }
    // 将index位置加上val值 arr[i] += val(注意传进来的index要+1)
    public void add(int index, int val){
        while(index < n){
            tree[index] += val;
            index += index & -index;
        }
    }
    // 查询[1, index]的前缀和(注意传进来的index要+1)
    public int query(int index) {
        int s = 0;
        while (index > 0) {
            s += tree[index];
            index -= index & -index; // n&(~n+1) == n&(-n)
        }
        return s;
    }
	// 返回[left, right]之间的区间和(注意传进来的index要+1)
    public int RangeSum(int left, int right){
        return query(right) - query(left-1);
    }
}

1626. 无矛盾的最佳球队

难度中等179

假设你是球队的经理。对于即将到来的锦标赛,你想组合一支总体得分最高的球队。球队的得分是球队中所有球员的分数 总和

然而,球队中的矛盾会限制球员的发挥,所以必须选出一支 没有矛盾 的球队。如果一名年龄较小球员的分数 严格大于 一名年龄较大的球员,则存在矛盾。同龄球员之间不会发生矛盾。

给你两个列表 scoresages,其中每组 scores[i]ages[i] 表示第 i 名球员的分数和年龄。请你返回 所有可能的无矛盾球队中得分最高那支的分数

示例 1:

输入:scores = [1,3,5,10,15], ages = [1,2,3,4,5]
输出:34
解释:你可以选中所有球员。

示例 2:

输入:scores = [4,5,6,5], ages = [2,1,2,1]
输出:16
解释:最佳的选择是后 3 名球员。注意,你可以选中多个同龄球员。

示例 3:

输入:scores = [1,2,3,5], ages = [8,9,10,1]
输出:6
解释:最佳的选择是前 3 名球员。

提示:

  • 1 <= scores.length, ages.length <= 1000
  • scores.length == ages.length
  • 1 <= scores[i] <= 106
  • 1 <= ages[i] <= 1000

题解:注意更改了模板,类似线段树的区间修改,维护区间最大值(维护不超过当前球员年龄的球员的最大得分

class Solution {
    public int bestTeamScore(int[] scores, int[] ages) {
        int n = ages.length;
        int[][] arr = new int[n][2];
        for(int i = 0; i < n; i++){
            arr[i] = new int[]{scores[i], ages[i]};
        }
        // 我们可以将球员按照分数从小到大排序,如果分数相同,则按照年龄从小到大排序。
        Arrays.sort(arr, (a, b) -> a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]);
        int m = 0;
        for(int age : ages) m = Math.max(m, age);

        // 我们使用树状数组维护不超过当前球员年龄的球员的最大得分。
        BinaryIndexedTree tree = new BinaryIndexedTree(m+5);
        // 每一次,我们只需要在 O(logm) 的时间内找出不超过当前球员年龄的球员的最大得分,
        // 然后将当前球员的分数加到该得分上,即可更新当前球员年龄的最大得分。
        for(int[] x : arr){
            tree.add(x[1] + 1, x[0] + tree.query(x[1] + 1));
        }
        return tree.query(m+1);
    }
}

class BinaryIndexedTree{
    private int n;
    private int[] tree;

    public BinaryIndexedTree(int n){ // 初始化时,要传入n+1
        this.n = n;
        tree = new int[n];
    }
    // 将index位置加上val值 arr[i] += val(注意传进来的index要+1)
    public void add(int index, int val){
        while(index < n){
            tree[index] = Math.max(tree[index], val);
            index += index & -index;
        }
    }
    // 查询[1, index]的前缀和(注意传进来的index要+1)
    public int query(int index) {
        int s = 0;
        while (index > 0) {
            s = Math.max(tree[index], s);
            index -= index & -index; // n&(~n+1) == n&(-n)
        }
        return s;
    }
}

🎉6404. 将数组清空

难度困难2

给你一个包含若干 互不相同 整数的数组 nums ,你需要执行以下操作 直到****数组为空

  • 如果数组中第一个元素是当前数组中的 最小值 ,则删除它。
  • 否则,将第一个元素移动到数组的 末尾

请你返回需要多少个操作使 nums 为空。

示例 1:

输入:nums = [3,4,-1]
输出:5
Operation Array
1 [4, -1, 3]
2 [-1, 3, 4]
3 [3, 4]
4 [4]
5 []

示例 2:

输入:nums = [1,2,4,3]
输出:5
Operation Array
1 [2, 4, 3]
2 [4, 3]
3 [3, 4]
4 [4]
5 []

示例 3:

输入:nums = [1,2,3]
输出:3
Operation Array
1 [2, 3]
2 [3]
3 []

提示:

  • 1 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 中的元素 互不相同

题解:小羊肖恩 https://leetcode.cn/u/yawn_sean/

首先,我们肯定不能直接执行题目中给出的操作,否则时间复杂度将达到操作的数量。那么,我们应该在原数组中考虑删除操作和移动操作分别意味着什么。

我们考虑一个指针,在原数组中进行移动,且每次移动一个单位。那么“第一个元素移动到末尾”可以刻画为将该指针移动到下一个还在数组中的数字位置上,“删除”可以刻画为将某一个位置置空。

删除的位置顺序是预先给定的,对应位置的数字从小到大。

接下来我们考虑两次删除操作之间的间隔,相当于从某个位置移动到下一个最小值的位置。这两个位置在原数组中的下标是确定的, 因此我们只需要考虑这两点间有多少位置未被置空即可,这相当于一个区间求和,每次可以更新置空情况。 这可以通过线段树或树状数组等实现 (可以搜索相关知识)。具体逻辑可见代码注释。

时间复杂度为O(nlogn),来源于排序还有树状数组等结构的更新


为什么大佬们一眼看出是树状数组呢?

不是一眼看出树状数组,而是要把找两个相邻的最小值之间还有几个数没删掉的问题转变成动态区间和问题,这个转化过程才是最重要的,至于你用什么数据结构求动态区间和是次要的,很多数据结构都能做,最常用的树状数组和线段树(这题比较特殊有序集合也能做),树状数组容易套板,码量少,常数还比线段树小。所以你才会看到大佬们清一色的树状数组。

树状数组解法:

题解:https://leetcode.cn/problems/make-array-empty/solution/shu-zhuang-shu-zu-mo-ni-pythonjavacgo-by-ygvb/文章来源地址https://www.toymoban.com/news/detail-437043.html

class Solution:
    """
    转换:
    将第一个元素移动到数组的末尾 
        ==> 用 下标  来标记当前数组的第一个数是谁,然后不断往后移动
            举例 :2413 ==> 当前下标2 ==> 1324 ==>删除1 ==> 324 ==> 当前下标3
    这个转换是怎么想到的?技巧,相对关系【数字往后移 等价于 用一个下标来模拟】

    删除一个数后,下标自动向右移动一位(这一次操作是免费的)

    我们需要记录每一个数的位置,从小到大删除
    上一个删除的数的下标是 pre, 当前要删除的下标是 i
    因此我们需要思考【从pre移动到i,需要移动多少次?】
    1. 维护中间被删除的数字个数
    ==> 树状数组
        如果pre <= i, 移动次数 i-pre-query(pre,i) 【pre到i,再减去中间删除的个数】
        如果pre > i, 移动次数 n - pre-query(pre,n) + 1【n到1的那一步】 + (i-1) - query(1,i) 【pre到n,再从1到i】 
            简化一下: n - pre + i - (k - query(i, pre-1)) 【树状数组中一共有k个数】
    2. 

    """
    def countOperationsToEmptyArray(self, nums: List[int]) -> int:
        ans = n = len(nums) # 先把 n 计入答案
        t = BIT(n+1) # 树状数组下标从1开始
        pre = 1 # 上一个最小值的位置,初始为 1
        # 根据元素大小对下标进行排序,然后枚举 数的位置 从小到大删除每个数
        ids = sorted(range(n), key = lambda i: nums[i])
        for k, i in enumerate(ids):
            i += 1 # 下标i从1开始
            if pre <= i: # 从 pre 移动到 i,跳过已经删除的数
                ans += i - pre - t.query(pre, i)
            else:
                ans += n - pre + i - (k - t.query(i, pre-1))
            t.inc(i) # 删除i(添加到树状数组中)
            pre = i
        return ans

# 树状数组模板
class BIT:
    def __init__(self, n):
        self.tree = [0] * n

    # 将下标 i 上的数加一
    def inc(self, i: int) -> None:
        while i < len(self.tree):
            self.tree[i] += 1
            i += i & -i

    # 返回闭区间 [1, i] 的元素和
    def sum(self, i: int) -> int:
        res = 0
        while i > 0:
            res += self.tree[i]
            i &= i - 1
        return res

    # 返回闭区间 [left, right] 的元素和
    def query(self, left: int, right: int) -> int:
        return self.sum(right) - self.sum(left - 1)

到了这里,关于树状数组练习题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • Java练习题-用冒泡排序法实现数组排序

    Java练习题-用冒泡排序法实现数组排序

    ✅作者简介:CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1🏆 📃个人主页:hacker707的csdn博客 🔥系列专栏:Java练习题 💬个人格言:不断的翻越一座又一座的高山,那样的人生才是我想要的。这一马平川,一眼见底的活,我不想要,我的人生

    2024年02月08日
    浏览(11)
  • 【运维知识高级篇】34道Shell编程练习题及答案(从基础到实战:基础+计算+判断+循环+控制与数组+实战进阶)

    ​本篇文章几乎涵盖了绝大部分的Shell语法练习,用一个个实战练习,巩固Shell的学习,话不多说,直接开始。 练习1:按照时间生成文件\\\"2018-05-22.log\\\"将每天的磁盘使用状态写入到对应日期的文件 练习2:统计Nginx日志中每个IP的访问量有多少,日志格式如下 练习3:写一个脚本

    2024年02月14日
    浏览(11)
  • 【Java练习题汇总】《第一行代码JAVA》综合测试三,汇总Java练习题

    【Java练习题汇总】《第一行代码JAVA》综合测试三,汇总Java练习题

    线程的启动方法是( )。 A. run() B. start() C. begin() D. accept() Thread 类提供表示线程优先级的静态常量,代表普通优先级的静态常量是( )。 A. MAX_PRIORITY B. MIN_PRIORITY C. NORMAL_PRIORITY D. NORM_PRIORITY 设置线程优先级的方法是( )。 A. setPriority() B. getPriority() C. getName() D. setName() 下面 ( )方法是

    2024年02月14日
    浏览(19)
  • python文件练习题

    【问题描述】 从一个文本文件内读入任意多个学生的分数,求出最高分,最低分和平均分存入文件result.txt内。 【输入形式】 一个文件,文件中分数之间由换行隔开,输入的文件名为grade.txt。输入的分数都是整数。 【输出形式】 计算出grade.txt中所有分数的最高分,最低分和

    2024年02月03日
    浏览(11)
  • 蓝桥杯练习题(十)

    蓝桥杯练习题(十)

    本文主要是【算法】——蓝桥杯练习题(十)的文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是听风与他🥇 ☁️博客首页:CSDN主页听风与他 🌄每日一句:狠狠沉淀,顶峰相见 差分

    2024年01月21日
    浏览(12)
  • MySQL练习题(6)

    MySQL练习题(6)

    1、使用mysqldump命令备份数据库中的所有表   2、备份booksDB数据库中的books表 3、使用mysqldump备份booksDB和test数据库 4、使用mysqldump备份服务器中的所有数据库 5、使用mysql命令还原第二题导出的book表 6、进入数据库使用source命令还原第二题导出的book表 1、建立一个utf8编码的数据

    2024年02月16日
    浏览(12)
  • 蓝桥杯练习题(九)

    蓝桥杯练习题(九)

    本文主要是【算法】——蓝桥杯练习题(九)的文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是听风与他🥇 ☁️博客首页:CSDN主页听风与他 🌄每日一句:狠狠沉淀,顶峰相见

    2024年01月17日
    浏览(7)
  • 蓝桥杯练习题(十二)

    蓝桥杯练习题(十二)

    本文主要是【算法】——蓝桥杯练习题(十二)的文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是听风与他🥇 ☁️博客首页:CSDN主页听风与他 🌄每日一句:狠狠沉淀,顶峰相见

    2024年01月21日
    浏览(8)
  • 蓝桥杯练习题(八)

    蓝桥杯练习题(八)

    本文主要是【算法】——蓝桥杯练习题(八)的文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是听风与他🥇 ☁️博客首页:CSDN主页听风与他 🌄每日一句:狠狠沉淀,顶峰相见

    2024年01月17日
    浏览(9)
  • 练习题----顺序栈算法

    练习题----顺序栈算法

    ​输入一个包括 \\\'(\\\' 和 \\\')\\\' 的字符串string ,判断字符串是否有效。要求设计算法实现检查字符串是否有效,有效的字符串需满足以下条件: A. 左括号必须用相同类型的右括号闭合。 B. 左括号必须以正确的顺序闭合。 C. 每个右括号都有一个对应的相同类型的左括号。 ​该题需

    2024年04月26日
    浏览(11)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包