Maximum Segment Sum After Removals
Maximum Segment Sum After Removals
You are given two 0-indexed integer arrays and , both of length . For the query, the element in at the index is removed, splitting into different segments.
A segment is a contiguous sequence of positive integers in . A segment sum is the sum of every element in a segment.
Return an integer array , of length , where is the maximum segment sum after applying the removal.
Note: The same index will not be removed more than once.
Example 1:
Input: nums = [1,2,5,6,1], removeQueries = [0,3,2,4,1] Output: [14,7,2,2,0] Explanation: Using 0 to indicate a removed element, the answer is as follows: Query 1: Remove the 0th element, nums becomes [0,2,5,6,1] and the maximum segment sum is 14 for segment [2,5,6,1]. Query 2: Remove the 3rd element, nums becomes [0,2,5,0,1] and the maximum segment sum is 7 for segment [2,5]. Query 3: Remove the 2nd element, nums becomes [0,2,0,0,1] and the maximum segment sum is 2 for segment [2]. Query 4: Remove the 4th element, nums becomes [0,2,0,0,0] and the maximum segment sum is 2 for segment [2]. Query 5: Remove the 1st element, nums becomes [0,0,0,0,0] and the maximum segment sum is 0, since there are no segments. Finally, we return [14,7,2,2,0].
Example 2:
Input: nums = [3,2,11,1], removeQueries = [3,2,1,0] Output: [16,5,3,0] Explanation: Using 0 to indicate a removed element, the answer is as follows: Query 1: Remove the 3rd element, nums becomes [3,2,11,0] and the maximum segment sum is 16 for segment [3,2,11]. Query 2: Remove the 2nd element, nums becomes [3,2,0,0] and the maximum segment sum is 5 for segment [3,2]. Query 3: Remove the 1st element, nums becomes [3,0,0,0] and the maximum segment sum is 3 for segment [3]. Query 4: Remove the 0th element, nums becomes [0,0,0,0] and the maximum segment sum is 0, since there are no segments. Finally, we return [16,5,3,0].
Constraints:
All the values of are unique.
解题思路
这题可以正着做(在线做法),用平衡树(这里用STL的set和multiset实现)。翻着做(离线做法),用并查集。
比赛的时候是正着做,思路有了并且正确,但死在不熟悉STL和边界情况上了。
正着做就是用一个集合set来维护若干组区间,每一次操作就相当于将某个区间裂开成两个区间(也有可能是一个区间)。先根据当前要删除的下标找到覆盖这个下标的区间,这里可以通过set::lower_bound()来实现(这里有个边界要处理,当时找了半天bug都没找到,解析在代码下方)。然后把区间裂成和(当然也可能只有一个区间),同时把区间删除。同时还要开一个multiset来维护set中各个区间的总和,multiset::rbegin()就是所有区间总和的最大值,这里是把multiset当作堆来使用(当时比赛的时候想到用优先队列来维护,但优先队列不支持删除操作,所以还写了些其他东西来补助这个操作,写得很乱)。
AC代码如下:
1 class Solution { 2 public: 3 vector<long long> maximumSegmentSum(vector<int>& nums, vector<int>& removeQueries) { 4 int n = nums.size(); 5 vector<long long> s(n + 1); // 前缀和数组 6 for (int i = 1; i <= n; i++) { 7 s[i] = s[i - 1] + nums[i - 1]; 8 } 9 10 set<pair<int, int>> st; // 维护区间 11 st.insert({1, n}); 12 multiset<long long> mst; // 维护各个区间的总和,可以得到最大值 13 mst.insert(s[n]); 14 vector<long long> ans; 15 16 for (int i = 0; i < n; i++) { 17 int x = removeQueries[i] + 1; 18 auto it = --st.upper_bound({x, n + 1}); // 找到可以覆盖x的区间。{x, n+1}的第二维相当于正无穷,找到左端点严格大于x的区间,那么前一个区间的左端点就一定<=x 19 20 if (x >= it->first) { // 裂成左边部分的区间(left, x-1) 21 st.insert({x + 1, it->second}); 22 mst.insert(s[it->second] - s[x]); 23 } 24 if (x <= it->second) { // 裂成右边部分的区间(x+1, right) 25 st.insert({it->first, x - 1}); 26 mst.insert(s[x - 1] - s[it->first - 1]); 27 } 28 29 mst.erase(mst.find(s[it->second] - s[it->first - 1])); // 删去mst中区间(left, right)的值,这里要删除迭代器,如果直接删除对应的值,就会把所有相同的值都删除而不是只删这一个 30 st.erase(it); // 删除区间(left right) 31 32 ans.push_back(*mst.rbegin()); 33 } 34 35 return ans; 36 } 37 };
这里记录一个bug。就是在上面代码中的行upper_bound()操作,一开始我传入的值是,然后在跑某组数据时发生执行出错。原因就是,如果此时set中只有一个区间,而我传入的是,也就是找到严格大于的pair,可以发现第一维,第二维,因此有,因此就会返回区间的迭代器,而这个迭代器就是st.begin(),如果再执行迭代器减操作,就会越界报错。因此当我们要查找覆盖的区间时,应该把第二维赋值为正无穷(这里下标最大不超过,因此可以用来表示正无穷),这样即使pair的第一维相等,也会强制往后比较,这样就避免了这种边界情况。
更新:另一种并查集的实现方法,灵感来源于题目Subarray With Elements Greater Than Varying Threshold
AC代码如下:
1 class Solution { 2 public: 3 vector<int> fa; 4 vector<long long> sum; 5 6 int find(int x) { 7 return fa[x] == x ? fa[x] : fa[x] = find(fa[x]); 8 } 9 10 vector<long long> maximumSegmentSum(vector<int>& nums, vector<int>& removeQueries) { 11 int n = nums.size(); 12 for (int i = 0; i <= n ; i++) { 13 fa.push_back(i); 14 sum.push_back(0); 15 } 16 17 vector<long long> ans; 18 multiset<long long> mst; 19 mst.insert(0); 20 for (int i = n - 1; i >= 0; i--) { 21 ans.push_back(*mst.rbegin()); 22 int x = removeQueries[i]; 23 if (sum[find(x)]) mst.erase(mst.find(sum[find(x)])); 24 if (x + 1 < n && sum[find(x + 1)]) mst.erase(mst.find(sum[find(x + 1)])); 25 sum[find(x + 1)] += sum[x] + nums[x]; 26 fa[x] = find(x + 1); 27 mst.insert(sum[find(x + 1)]); 28 } 29 30 reverse(ans.begin(), ans.end()); 31 return ans; 32 } 33 };
反着做就是一开始没有任何数,然后每次都往数轴上加一个数,如果可以合并的话就合并成一个更大的区间,最后整个数组填满了数形成一个与原来一样的的区间。可以发现执行过程就是区间不断合并的过程(维护连通性),因此可以用并查集。合并后还需要求新区间的总和,然后用这个总和来更新最大值。
AC代码如下:
1 class Solution { 2 public: 3 vector<int> fa; 4 vector<long long> sum; 5 6 int find(int x) { 7 return fa[x] == x ? fa[x] : fa[x] = find(fa[x]); 8 } 9 10 vector<long long> maximumSegmentSum(vector<int>& nums, vector<int>& removeQueries) { 11 int n = nums.size(); 12 for (int i = 0; i < n; i++) { 13 fa.push_back(i); 14 sum.push_back(nums[i]); 15 } 16 17 vector<bool> vis(n); // vis[i] == false表示第i个数还没有被加进来 18 vector<long long> ans; 19 long long maxs = 0; 20 for (int i = n - 1; i >= 0; i--) { 21 ans.push_back(maxs); 22 int x = removeQueries[i]; 23 vis[x] = true; // x这个位置的数被加进来 24 if (x - 1 >= 0 && vis[x - 1]) { // 合并x-1位置的连通块 25 sum[x] += sum[find(x - 1)]; 26 fa[find(x - 1)] = find(x); 27 } 28 if (x + 1 < n && vis[x + 1]) { // 合并x+1位置的连通块 29 sum[x] += sum[find(x + 1)]; 30 fa[find(x + 1)] = x; 31 } 32 maxs = max(maxs, sum[x]); 33 } 34 35 reverse(ans.begin(), ans.end()); 36 return ans; 37 } 38 };
参考资料
力扣第85场双周赛:https://www.bilibili.com/video/BV1u14y1t771
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16610329.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效