Hard

题目描述

给你两个下标从 0 开始的整数数组 numsremoveQueries ,两者长度都为 n 。对于第 i 个查询,nums 中位于下标 removeQueries[i] 处的元素被删除,将 nums 分割成不同的线段。

线段nums 中连续的 正整数 序列。线段和 是线段中每个元素的和。

请你返回一个长度为 n 的整数数组 answer ,其中 answer[i] 是第 i 次删除之后的 最大线段和

注意: 相同的下标不会被删除超过一次。

示例 1:

输入:nums = [1,2,5,6,1], removeQueries = [0,3,2,4,1]
输出:[14,7,2,2,0]
解释:用 0 表示被删除的元素,答案如下所示:
查询 1 :删除第 0 个元素,nums 变为 [0,2,5,6,1] ,最大线段和为 14 ,对应线段 [2,5,6,1] 。
查询 2 :删除第 3 个元素,nums 变为 [0,2,5,0,1] ,最大线段和为 7 ,对应线段 [2,5] 。
查询 3 :删除第 2 个元素,nums 变为 [0,2,0,0,1] ,最大线段和为 2 ,对应线段 [2] 。
查询 4 :删除第 4 个元素,nums 变为 [0,2,0,0,0] ,最大线段和为 2 ,对应线段 [2] 。
查询 5 :删除第 1 个元素,nums 变为 [0,0,0,0,0] ,最大线段和为 0 ,因为没有任何线段。
最后返回 [14,7,2,2,0] 。

示例 2:

输入:nums = [3,2,11,1], removeQueries = [3,2,1,0]
输出:[16,5,3,0]
解释:用 0 表示被删除的元素,答案如下所示:
查询 1 :删除第 3 个元素,nums 变为 [3,2,11,0] ,最大线段和为 16 ,对应线段 [3,2,11] 。
查询 2 :删除第 2 个元素,nums 变为 [3,2,0,0] ,最大线段和为 5 ,对应线段 [3,2] 。
查询 3 :删除第 1 个元素,nums 变为 [3,0,0,0] ,最大线段和为 3 ,对应线段 [3] 。
查询 4 :删除第 0 个元素,nums 变为 [0,0,0,0] ,最大线段和为 0 ,因为没有任何线段。
最后返回 [16,5,3,0] 。

提示:

  • n == nums.length == removeQueries.length
  • 1 <= n <= 10^5
  • 1 <= nums[i] <= 10^9
  • 0 <= removeQueries[i] < n
  • removeQueries 的所有值 互不相同

解题思路

这道题的关键思路是逆向思维。与其模拟删除过程,我们可以逆向考虑元素添加的过程。

核心思路:

  1. 逆向处理:将删除操作逆向看作添加操作。从最后状态(所有元素都被删除)开始,逆向执行 removeQueries,相当于依次添加元素。

  2. 并查集维护连通性:使用并查集来维护连续线段。每当添加一个元素时,检查其左右相邻位置是否已存在元素,如果存在则合并成更大的线段。

  3. 动态维护最大值:使用优先队列(最大堆)来维护当前所有线段的和,堆顶即为最大线段和。

  4. 处理无效线段:由于线段会被合并,堆中可能存在已失效的线段。我们在取堆顶时需要验证该线段是否仍然有效。

算法流程:

  • 逆向遍历 removeQueries,依次"添加"元素
  • 对于每个添加的元素,计算其所在新线段的和
  • 检查左右相邻是否有元素,如果有则合并线段
  • 将新线段和加入堆中,更新当前最大线段和
  • 最后将结果数组逆序输出

这种方法避免了直接模拟删除的复杂性,时间复杂度为 O(n log n)。

代码实现

class Solution {
public:
    vector<long long> maximumSegmentSum(vector<int>& nums, vector<int>& removeQueries) {
        int n = nums.size();
        vector<long long> result(n);
        vector<bool> added(n, false);
        vector<long long> segmentSum(n);
        priority_queue<pair<long long, pair<int, int>>> pq; // {sum, {start, end}}
        
        // 逆向处理
        for (int i = n - 1; i >= 0; i--) {
            int pos = removeQueries[i];
            added[pos] = true;
            
            // 找到当前元素所在线段的左右边界
            int left = pos, right = pos;
            long long sum = nums[pos];
            
            // 向左扩展
            while (left > 0 && added[left - 1]) {
                left--;
                sum += nums[left];
            }
            
            // 向右扩展
            while (right < n - 1 && added[right + 1]) {
                right++;
                sum += nums[right];
            }
            
            // 将新线段加入堆
            if (sum > 0) {
                pq.push({sum, {left, right}});
            }
            
            // 找到当前最大的有效线段和
            long long maxSum = 0;
            while (!pq.empty()) {
                auto [currentSum, boundaries] = pq.top();
                int start = boundaries.first, end = boundaries.second;
                
                // 验证线段是否仍然有效
                bool valid = true;
                for (int j = start; j <= end; j++) {
                    if (!added[j]) {
                        valid = false;
                        break;
                    }
                }
                
                if (valid) {
                    maxSum = currentSum;
                    break;
                } else {
                    pq.pop();
                }
            }
            
            result[i] = maxSum;
        }
        
        return result;
    }
};
class Solution:
    def maximumSegmentSum(self, nums: List[int], removeQueries: List[int]) -> List[int]:
        n = len(nums)
        result = [0] * n
        added = [False] * n
        pq = []  # 最大堆,存储 (-sum, start, end)
        
        # 逆向处理
        for i in range(n - 1, -1, -1):
            pos = removeQueries[i]
            added[pos] = True
            
            # 找到当前元素所在线段的左右边界
            left = right = pos
            segment_sum = nums[pos]
            
            # 向左扩展
            while left > 0 and added[left - 1]:
                left -= 1
                segment_sum += nums[left]
            
            # 向右扩展
            while right < n - 1 and added[right + 1]:
                right += 1
                segment_sum += nums[right]
            
            # 将新线段加入堆(使用负数实现最大堆)
            if segment_sum > 0:
                heapq.heappush(pq, (-segment_sum, left, right))
            
            # 找到当前最大的有效线段和
            max_sum = 0
            while pq:
                neg_sum, start, end = pq[0]
                current_sum = -neg_sum
                
                # 验证线段是否仍然有效
                valid = True
                for j in range(start, end + 1):
                    if not added[j]:
                        valid = False
                        break
                
                if valid:
                    max_sum = current_sum
                    break
                else:
                    heapq.heappop(pq)
            
            result[i] = max_sum
        
        return result
public class Solution {
    public long[] MaximumSegmentSum(int[] nums, int[] removeQueries) {
        int n = nums.Length;
        long[] result = new long[n];
        bool[] added = new bool[n];
        var pq = new PriorityQueue<(long sum, int start, int end), long>(
            Comparer<long>.Create((x, y) => y.CompareTo(x))
        );
        
        // 逆向处理
        for (int i = n - 1; i >= 0; i--) {
            int pos = removeQueries[i];
            added[pos] = true;
            
            // 找到当前元素所在线段的左右边界
            int left = pos, right = pos;
            long segmentSum = nums[pos];
            
            // 向左扩展
            while (left > 0 && added[left - 1]) {
                left--;
                segmentSum += nums[left];
            }
            
            // 向右扩展
            while (right < n - 1 && added[right + 1]) {
                right++;
                segmentSum += nums[right];
            }
            
            // 将新线段加入堆
            if (segmentSum > 0) {
                pq.Enqueue((segmentSum, left, right), segmentSum);
            }
            
            // 找到当前最大的有效线段和
            long maxSum = 0;
            while (pq.Count > 0) {
                var (currentSum, start, end) = pq.Peek();
                
                // 验证线段是否仍然有效
                bool valid = true;
                for (int j = start; j <= end; j++) {
                    if (!added[j]) {
                        valid = false;
                        break;
                    }
                }
                
                if (valid) {
                    maxSum = currentSum;
                    break;
                } else {
                    pq.Dequeue();
                }
            }
            
            result[i] = maxSum;
        }
        
        return result;
    }
}
var maximumSegmentSum = function(nums, removeQueries) {
    const n = nums.length;
    const result = new Array(n);
    const added = new Array(n).fill(false);
    const pq = new MaxPriorityQueue(); // {sum, start, end}
    
    // 逆向处理
    for (let i = n - 1; i >= 0; i--) {
        const pos = removeQueries[i];
        added[pos] = true;
        
        // 找到当前元素所在线段的左右边界
        let left = pos, right = pos;
        let segmentSum = nums[pos];
        
        // 向左扩展
        while (left > 0 && added[left - 1]) {
            left--;
            segmentSum += nums[left];
        }
        
        // 向右扩展
        while (right < n - 1 && added[right + 1]) {
            right++;
            segmentSum += nums[right];
        }
        
        // 将新线段加入堆
        if (segmentSum > 0) {
            pq.enqueue({start: left, end: right}, segmentSum);
        }
        
        // 找到当前最大的有效线段和
        let maxSum = 0;
        while (!pq.isEmpty()) {
            const {element: {start, end}, priority: currentSum} = pq.front();
            
            // 验证线段是否仍然有效
            let valid = true;
            for (let j = start; j <= end; j++) {
                if (!added[j]) {
                    valid = false;
                    break;
                }
            }
            
            if (valid) {
                maxSum = currentSum;
                break;
            } else {
                pq.dequeue();
            }
        }
        
        result[i] = maxSum;
    }
    
    return result;
};

复杂度分析

复杂度类型复杂度说明
时间复杂度O(n² log n)最坏情况下,每次查询都需要验证堆中的所有线段,每次验证需要O(n)时间
空间复杂度O(n)需要额外的数组存储状态和优先队列存储线段信息

注意: 虽然理论上时间复杂度较高,但在实际情况下,由于大部分无效线段会被及时清理,平均性能会更好。对于更优的解法,可以使用并查集配合线段树来达到 O(n log n) 的时间复杂度。