LeetCode 2569. Handling Sum Queries After Update【数组,线段树】困难

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

给你两个下标从 0 开始的数组 nums1 和 nums2 ,和一个二维数组 queries 表示一些操作。总共有 3 种类型的操作:

  1. 操作类型 1 为 queries[i] = [1, l, r] 。你需要将 nums1 从下标 l 到下标 r 的所有 0 反转成 1 或将 1 反转成 0 。l 和 r 下标都从 0 开始。
  2. 操作类型 2 为 queries[i] = [2, p, 0] 。对于 0 <= i < n 中的所有下标,令 nums2[i] = nums2[i] + nums1[i] * p 。
  3. 操作类型 3 为 queries[i] = [3, 0, 0] 。求 nums2 中所有元素的和。

请你返回一个数组,包含所有第三种操作类型的答案。

示例 1:

输入:nums1 = [1,0,1], nums2 = [0,0,0], queries = [[1,1,1],[2,1,0],[3,0,0]]
输出:[3]
解释:第一个操作后 nums1 变为 [1,1,1] 。第二个操作后,nums2 变成 [1,1,1] ,所以第三个操作的答案为 3 。所以返回 [3]

示例 2:

输入:nums1 = [1], nums2 = [5], queries = [[2,0,0],[3,0,0]]
输出:[5]
解释:第一个操作后,nums2 保持不变为 [5] ,所以第二个操作的答案是 5 。所以返回 [5] 。``

提示:

  • 1 <= nums1.length,nums2.length <= 10^5
  • nums1.length = nums2.length
  • 1 <= queries.length <= 10^5
  • queries[i].length = 3
  • 0 <= l <= r <= nums1.length - 1
  • 0 <= p <= 10^6
  • 0 <= nums1[i] <= 1
  • 0 <= nums2[i] <= 10^9

解法 线段树(区间更新和区间查询)

线段树是一个二叉树,每个结点保存数组 n u m s nums nums 在区间 [ s , e ] [s,e] [s,e] 的最小值、最大值或者总和等信息。线段树可以用树也可以用数组(堆式存储)来实现。对于数组实现,假设根结点的下标为 1 1 1 ,如果一个结点在数组的下标为 n o d e node node ,那么它的左子结点下标为 n o d e × 2 node\times 2 node×2 ,右子结点下标为 node × 2 + 1 \textit{node} \times 2 + 1 node×2+1 ,线段树可以在 O ( log ⁡ N ) O(\log N) O(logN) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作,关于线段树的详细描述可以参考「线段树」。

区间更新的线段树,需要借助「懒惰标记」来标记当前结点所在区间是否需要更新

建树 b u i l d build build 函数:

  1. 我们在结点 n o d e node node 保存数组 nums \textit{nums} nums 在区间 [ s , e ] [s,e] [s,e] 的总和。
  2. s = e s = e s=e 时,结点 n o d e node node 是叶子结点,它保存的值等于 n u m s [ s ] nums[s] nums[s]
  3. s < e s<e s<e 时,结点 n o d e node node 的左子结点保存区间 [ s , ⌊ s + e 2 ⌋ ] \Big [ s, \Big \lfloor \dfrac{s + e}{2} \Big \rfloor \Big ] [s,2s+e] 的总和,右子结点保存区间 [ ⌊ s + e 2 ⌋ + 1 , e ] \Big [ \Big \lfloor \dfrac{s + e}{2} \Big \rfloor + 1, e \Big ] [2s+e+1,e]
    的总和,那么结点 n o d e node node 保存的值等于它的两个子结点保存的值之和。

假设 n u m s nums nums 的大小为 n n n ,我们规定根结点 n o d e = 1 node=1 node=1 保存区间 [ 0 , n − 1 ] [0, n - 1] [0,n1] 的总和,然后自下而上递归地建树。

  • 区间修改 m o d i f y modify modify 函数:当要修改区间 n u m s [ l e f t ⋯ r i g h t ] nums[left⋯right] nums[leftright] 的值时,查看当前区间的结点此前是否已经「更新」过。如果更新过,那么通过 p u s h d o w n pushdown pushdown 函数将更新标记传递到子结点,对之前的操作进行更新,同时更新设置每个结点的懒标记 l a z y t a g lazytag lazytag后续该位置便可以认为无需进行更新操作
  • 区间查询 q u e r y query query 函数:给定区间 [ l e f t , r i g h t ] [left, right] [left,right] 时,也需要像区间更新操作一样,需要使用 p u s h d o w n pushdown pushdown 函数将更新标记往下传递到子结点,否则区间本身的数值实际上没有更新,懒标记只在区间修改或者区间查询时会往下传递,否则只是标记该区间需要更新。将区间 [ l e f t , r i g h t ] [left, right] [left,right] 拆成多个结点对应的区间。
    • 如果结点 n o d e node node 对应的区间与 [ l e f t , r i g h t ] [left, right] [left,right] 相同,可以直接返回该结点的值,即当前区间和。
    • 如果结点 n o d e node node 对应的区间与 [ l e f t , r i g h t ] [left, right] [left,right] 不同,设左子结点对应的区间的右端点为 m m m ,那么将区间 [ l e f t , r i g h t ] [left, right] [left,right] 沿点 m m m 拆成两个区间,分别向下传递懒标记,并计算左子结点和右子结点
    • 从根结点开始递归地拆分区间 [ l e f t , r i g h t ] [left, right] [left,right],边拆分边计算并返回最终结果即可。

本题目中含有三种操作:

  • 第一种操作是将给定区间 [ l e f t , r i g h t ] [left, right] [left,right] 内的所有数据进行反转,实际为是区间更新,此时我们可以利用线段树进行区间更新,此时需要用到「线段树的区间修改与懒惰标记」。
  • 第二种操作是唯一对 n u m s 2 nums_2 nums2 中的元素进行更新,此时 nums 2 ′ [ i ] = nums 2 [ i ] + nums 1 [ i ] × p \textit{nums}'_2[i] = \textit{nums}_2[i] + \textit{nums}_1[i] \times p nums2[i]=nums2[i]+nums1[i]×p设数组 nums 2 \textit{nums}_2 nums2 更新之前的和为 s u m sum sum ,更新之后的和为 s u m ′ sum′ sum 。再假设 n u m s 1 nums_1 nums1 中总共有 c c c 1 1 1 ,那么操作2相当于把 n u m s 2 nums_2 nums2 的元素和增加了 c × p c \times p c×p 。计算过程如下:
    sum ′ = ∑ i = 0 n − 1 nums 2 ′ [ i ] = ∑ i = 0 n − 1 ( nums 2 [ i ] + nums 1 [ i ] × p ) = ∑ i = 0 n − 1 nums 2 [ i ] + p × ∑ i = 0 n − 1 nums 1 [ i ] = sum + p × ∑ i = 0 n − 1 nums 1 [ i ] \begin{aligned} \textit{sum}' &= \sum\limits_{i=0}^{n-1}{\textit{nums}'_2[i]} \\ &= \sum\limits_{i=0}^{n-1}(\textit{nums}_2[i] + \textit{nums}_1[i] \times p) \\ &=\sum\limits_{i=0}^{n-1}\textit{nums}_2[i] + p \times \sum\limits_{i=0}^{n-1}\textit{nums}_1[i] \\ &= \textit{sum} + p \times \sum\limits_{i=0}^{n-1}\textit{nums}_1[i] \end{aligned} sum=i=0n1nums2[i]=i=0n1(nums2[i]+nums1[i]×p)=i=0n1nums2[i]+p×i=0n1nums1[i]=sum+p×i=0n1nums1[i]
    根据上述等式可以看到,每次执行操作二时,实际 n u m 2 num_2 num2 的和会加上 p p p n u m 1 num_1 num1 的元素之和,可在每次更新时维护数组 n u m 2 num_2 num2 的和。由于 n u m 1 num_1 num1 初始化时全部为 0 0 0 ,经过第一种操作时部分元素会进行反转,因此只需要用线段树维护区间内 1 1 1 的个数,每次进行区间查询即可得到数组 n u m 1 num_1 num1 的元素之和
  • 第三种操作是求数组 n u m 2 num_2 num2 的元素之和,此时返回操作二中维护的 n u m 2 num_2 num2 的和即可。

根据以上分析,建立区间更新的线段树,可以参考「线段树的区间修改与懒惰标记」,当遇到操作一时进行区间更新,遇到操作二时进行区间查询即可。

struct SegNode {int l, r, sum;bool lazyTag;SegNode() {l = r = sum = 0;lazyTag = false;}
};
class SegTree {
private:vector<SegNode> arr;
public:SegTree(vector<int>& nums) {int n = nums.size();arr = vector<SegNode>(n * 4 + 1);build(1, 0, n - 1, nums);}void build(int id, int l, int r, const vector<int>& nums) {arr[id].l = l;arr[id].r = r;arr[id].lazyTag = false;if (l == r) {arr[id].sum = nums[l];return;}int mid = (l + r) >> 1;build(2 * id, l, mid, nums);build(2 * id + 1, mid + 1, r, nums);arr[id].sum = arr[2 * id].sum + arr[2 * id + 1].sum;}// pushdown函数:下传懒标记,将当前区间的修改情况下放到其左右孩子结点void pushdown(int x) {if (arr[x].lazyTag) {arr[2 * x].lazyTag = !arr[2 * x].lazyTag;arr[2 * x].sum = arr[2 * x].r - arr[2 * x].l + 1 - arr[2 * x].sum; // 翻转后1的个数arr[2 * x + 1].lazyTag = !arr[2 * x + 1].lazyTag;arr[2 * x + 1].sum = arr[2 * x + 1].r - arr[2 * x + 1].l + 1 - arr[2 * x + 1].sum;arr[x].lazyTag = false;}}/** 区间修改 **/void modify(int id, int l, int r) {if (arr[id].l >= l && arr[id].r <= r) {arr[id].sum = (arr[id].r - arr[id].l + 1) - arr[id].sum;arr[id].lazyTag = !arr[id].lazyTag;return;}pushdown(id);int mid = (arr[id].l + arr[id].r) >> 1;if (arr[2 * id].r >= l) modify(2 * id, l, r);if (arr[2 * id + 1].l <= r) modify(2 * id + 1, l, r);arr[id].sum = arr[2 * id].sum + arr[2 * id + 1].sum;}/** 区间查询 **/int query(int id, int l, int r) {if (arr[id].l >= l && arr[id].r <= r) return arr[id].sum;if (arr[id].r < l || arr[id].l > r) return 0;pushdown(id);int ans = 0;if (arr[2 * id].r >= l) ans += query(2 * id, l, r);if (arr[2 * id + 1].l <= r) ans += query(2 * id + 1, l, r);return ans;}int sumRange(int left, int right) {return query(1, left, right);}void reverseRange(int left, int right) {modify(1, left, right);}
};
class Solution {
public:vector<long long> handleQuery(vector<int>& nums1, vector<int>& nums2, vector<vector<int>>& queries) {int n = nums1.size();int m = queries.size();SegTree tree(nums1);long long sum = accumulate(nums2.begin(), nums2.end(), 0LL);vector<long long> ans;for (int i = 0; i < m; ++i) {if (queries[i][0] == 1) {int l = queries[i][1];int r = queries[i][2];tree.reverseRange(l, r);} else if (queries[i][0] == 2) {sum += (long long) tree.sumRange(0, n - 1) * queries[i][1];} else if (queries[i][0] == 3) {ans.emplace_back(sum);}}return ans;}
};

复杂度分析:

  • 时间复杂度: O ( m log ⁡ n ) O(m \log n) O(mlogn) ,其中 m m m 表示操作 1 1 1 与操作 2 2 2 的次数之和, n n n 表示数组的长度。每次遇到操作类型 1 1 1 与操作类型 2 2 2 时需要更新线段树,线段树每次更新与查询的时间复杂度均为 O ( log ⁡ n ) O(\log n) O(logn) ,一共最多有 m m m 次操作,因此总的时间复杂度为 O ( m log ⁡ n ) O(m \log n) O(mlogn)
  • 空间复杂度: O ( C n ) O(Cn) O(Cn) ,其中 n n n 表示数组的长度。本题解中线段树采用堆式存储,假设当前数组的长度为 n n n ,由于线段树是一棵完全二叉树,此时该树的最大深度为 ⌈ log ⁡ n ⌉ \lceil \log n \rceil logn ,则其叶子结点的总数为 2 ⌈ log ⁡ n ⌉ 2^{\lceil \log n \rceil} 2logn ,该树中含有的结点总数为 2 ⌈ log ⁡ n ⌉ + 1 − 1 2^{\lceil \log n \rceil + 1} - 1 2logn+11 ,此时可以知道 2 ⌈ log ⁡ n ⌉ + 1 − 1 ≤ 2 log ⁡ n + 2 − 1 ≤ 4 n − 1 2^{\lceil \log n \rceil + 1} - 1 \le 2^{\log n + 2} - 1 \le 4n - 1 2logn+112logn+214n1 ,因此本题中取 C = 4 C=4 C=4 即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/10533.shtml

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

相关文章

Ansible的应用

Ansible简介 Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点&#xff0c;Pubbet和Saltstack能实现的功能&#xff0c;Ansible基本上都可以实现。 Ansible能批量配置、部署、管理上千台主机…

Io进、线程——进程的基础

进程的基础 进程是计算机中最基本的执行单位&#xff0c;是程序在操作系统中的一次执行过程。每个进程都有自己的地址空间、数据栈、程序计数器等&#xff0c;相互之间独立运行&#xff0c;互不干扰。进程间的通信通过特定的机制来实现&#xff0c;进程的创建和撤销由操作系统…

[AWD靶场搭建]

文章目录 [AWD靶场搭建]前言AWD平台搭建靶机搭建Cadinal添加靶机 连接Asteroid大屏默认ssh账号密码参考 [AWD靶场搭建] 前言 觉得好玩搭建了一下AWD靶场&#xff0c;使用了vidar-team编写的 Cardinal AWD平台搭建 这里我是在kali搭建的&#xff0c;所以我下载了这个压缩包&…

FANUC机器人SRVO-050碰撞检测报警和SRVO-053干扰值过大故障报警总结

FANUC机器人SRVO-050碰撞检测报警和SRVO-053干扰值过大故障报警总结 前面和大家分享了关于SRVO-050碰撞检测报警和SRVO-053干扰值过大的原因分析以及处理方法,感兴趣的朋友可以参考以下链接中的内容: FANUC机器人SRVO-050碰撞检测报警原因分析及处理对策

Java阶段五Day15

Java阶段五Day15 文章目录 Java阶段五Day15分层其他依赖dao-apidao-implinfrustructuredomainadaptermain 测试整合项目main前台师傅功能luban-front配置师傅相关表格ER图ER练习案例鲁班表格ER关系&#xff08;非常重要&#xff09; 前台师傅接口——师傅入驻adapterdomaininfr…

如何判断某个视频是深度伪造的?

目录 一、前言 二、仔细检查面部动作 三、声音可以提供线索 四、观察视频中人物的身体姿势 五、小心无意义的词语 深造伪造危险吗&#xff1f; 一、前言 制作深度伪造视频就像在Word文档中编辑文本一样简单。换句话说&#xff0c;您可以拍下任何人的视频&#xff0c;让他…

谷粒商城第六天-实现功能的前序工作(网关的配置 跨域配置)

目录 一、为什么要做这项工作 1.1 为什么要配置网关 1.2 为什么要使用网关统一配置跨域 二、网关配置 三、统一跨域配置 四、总结 一、为什么要做这项工作 1.1 为什么要配置网关 我们知道网关的作用其实主要就是进行路由的&#xff0c;也就是根据前端发送到网关的请求&…

【docker】docker部署nginx

目录 一、步骤二、示例 一、步骤 1.搜索nginx镜像 2.拉取nginx镜像 3.创建容器 4.测试nginx 二、示例 1.搜索nginx镜像 docker search nginx2.拉取nginx镜像 docker pull nginx3.创建容器&#xff0c;设置端口映射、目录映射 # 在root目录下创建nginx目录用于存储nginx数据…

Vue3解决:Mockjs 引入后并访问 404(Not Found) 的页面报错问题

1、问题描述&#xff1a; 其一、报错为&#xff1a; GET http://localhost:5173/list 404 (Not Found) ncaught (in promise) AxiosError {message: Request failed with status code 404, name: AxiosError, code: ERR_BAD_REQUEST, config: {…}, request: XMLHttpRequest,…

大采购3.0,打造企业采购数智化的韧性变革!

大采购3.0&#xff0c;深化采购全链路管控&#xff0c;聚焦数智化运营、智慧化监管、个性化需求适配、一体化协同、稳定可靠、安全可信、企业级服务七大核心基因&#xff0c;围绕采购管理、供应商全生命周期管理、人工智能深入应用、易用性、交付能力等方面进行了全面升级和优化…

MATLAB 估计点云法线 (31)

MATLAB 估计点云法线 (31) 一、算法介绍二、具体函数三、算法实现四、效果展示一、算法介绍 点云法线,可以看作点的周围点拟合平面的法线,点的法线是点云处理中非常重要的一个可以使用的特征,如地面和墙面点的法线方向存在十分明显的差异,通过设置一定的阈值,即可对二者…

.NET 8 Preview 5推出!

作者&#xff1a;Jiachen Jiang 排版&#xff1a;Alan Wang 我们很高兴与您分享 .NET 8 Preview 5 中的所有新功能和改进&#xff01;此版本是 Preview 4 版本的后续版本。在每月发布的版本中&#xff0c;您将看到更多新功能。.NET 6 和 7 用户可以密切关注此版本&#xff0c;而…

数据结构--图的遍历 BFS

数据结构–图的遍历 BFS 树的广度优先遍历 从 1 结点进行 b f s bfs bfs的顺序&#xff1a; 【1】 【2】【3】【4】 【4】【6】【7】【8】 图的广度优先遍历 从 2 号点开始 b f s bfs bfs的顺序&#xff1a; 【2】 【1】【6】 【5】【3】【7】 【4】【8】 树 vs 图 不存在“回…

系统学习Linux-MySQL服务基础(一)

一、MySQL服务概述 什么是数据库&#xff1f; 将大量数据保存起来&#xff0c;通过计算机加工而成的可以进行高效访问的数据集合 数据库是存储、管理和操作组织化数据的软件系统 数据库能干什么&#xff1f; 企业应用存放用户数据、管理企业数据金融行业存储分析客户的财务…

pear文件利用 (远程文件下载、生成配置文件、写配置文件) 从一道题看——CTFshow私教 web40

web40 考点&#xff1a;pear文件包含 pear是PHP的一个扩展 条件&#xff1a; 1 有文件包含点 2 开启了pear扩展 &#xff08;可以当他是一个框架&#xff09; 3 配置文件中register_argc_argv 设置为On,而默认为Off&#xff08;$_SERVER[‘argv’]生效&#xff09; 4 找到…

【力扣每日一题】2023.7.25 将数组和减半的最少操作次数

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码运行结果&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们一个数组&#xff0c;我们每次可以将任意一个元素减半&#xff0c;问我们操作几次之后才可以将整个数组的和减半&…

【cpolar内网穿透工具】

文章目录 cpolar内网穿透工具.md概述什么是cpolar&#xff1f;cpolar可以用在哪些场景&#xff1f; 1. 注册cpolar帐号1.1 访问官网站点&#xff1a;[https://www.cpolar.com](https://link.zhihu.com/?targethttps%3A//www.cpolar.com/) 1.2 注册帐号 2. 下载Windows版本cpol…

【ARM Cache 系列文章 2 -- Cache Coherence及内存顺序模学习】

文章目录 Cache Coherence 背景1.1 内存顺序模型简介(Memory Model)1.1.1 Normal Memory1.1.2 Device Memory 1.2 Cache 一致性问题解决方案1.2.1 Shareability 属性1.2.2 Non-Shareable 属性1.2.3 Inner-Shareable 属性1.2.4 Out-Shareable 属性 1.3 Shareability 和 PoC/PoU …

【干货防踩坑/全图文分步/Gitlab镜像仓库自动同步】Gitlab CE/EE镜像仓库的配置技巧(含ssh/密码两验证方法)

【干货踩坑】Gitlab CE/EE镜像仓库的配置技巧&#xff08;含ssh/密码两验证方法&#xff09; 众所周知&#xff0c;Gitlab是个好东西。为什么呢&#xff1f;GitHub没有的功能&#xff0c;他全有了。更何况还可以私有部署。这两天自己部署了Gitlab&#xff0c;然后想把自己的项…

2. Spring 的创建和使用

目录 1. 创建 Spring 项目 1.1 创建一个 maven 项目 1.2 添加 Spring 框架支持 1.3 添加启动类 2. 存储 Bean 对象 2.1 创建 Bean 2.2 将 Bean 注册到容器 3. 获取并使用 Bean 对象 3.1 创建 Spring 上下文 ApplicationContext 和 BeanFactory 的区别&#xff08;重点…