当前位置: 首页 > news >正文

3362. 零数组变换 III

3362. 零数组变换 III

我们有一个长度为 n 的整数数组 nums 和一个二维数组 queries,其中 queries[i] = [li, ri]。每个 queries[i] 表示对 nums 的以下操作:

  • 将 nums 中下标在范围 [li, ri] 之间的每一个元素 ​最多​ 减少 1。
    • 这意味着对于 nums[j](其中 li <= j <= ri),可以选择减少 1 或保持不变。
    • 不同元素可以减少的值是独立的。

零数组​ 指的是所有元素都为 0 的数组。我们需要回答:

  • 最多可以从 queries 中删除多少个元素,使得剩下的 queries 仍然能将 nums 变为零数组。
  • 如果无法将 nums 变为零数组,返回 -1。

初步理解

首先,我们需要明确几个关键点:

  1. 操作的含义​:每个查询 [l, r] 可以对区间 [l, r] 内的元素进行最多减少 1 的操作。这意味着可以选择对某些或全部元素减少 1,也可以选择不减少任何元素。

  2. 目标​:通过一系列这样的操作,最终将所有 nums 的元素减到 0。我们需要选择 queries 的一个子集(即删除一些查询),使得剩下的查询可以完成这个目标,并且希望删除的查询尽可能多(即剩下的查询尽可能少)。

  3. 最大化删除的查询数量​:等价于最小化剩下的查询数量。因此,我们需要找到一个最小的查询集合,能够覆盖所有 nums 的减少需求。

问题转化

这个问题可以转化为一个覆盖问题:

  • 每个 nums[i] 需要被减少 nums[i] 次。每次减少可以通过一个查询 [l, r] 覆盖 i(如果 l <= i <= r)。
  • 我们需要选择一组查询,使得对于每个 i,至少有 nums[i] 个查询覆盖 i
  • 目标是选择尽可能少的查询(即删除尽可能多的查询)。

这类似于集合覆盖问题,其中我们需要选择最少数量的集合(查询)来覆盖所有元素的需求。

贪心算法的思考

为了最小化剩下的查询数量,可以考虑贪心算法:

  1. 优先选择覆盖最多“需求”的查询​:即选择那些能够覆盖最多尚未被满足的 nums[i] 的查询。

  2. 具体步骤​:

    • 初始化一个数组 required,其中 required[i] = nums[i],表示 nums[i] 还需要被减少的次数。
    • 对于每个查询 [l, r],计算它能够覆盖的 i 中 required[i] > 0 的数量。选择能够覆盖最多这样的 i 的查询。
    • 应用这个查询:对于 i 在 [l, r] 且 required[i] > 0,将 required[i] 减 1。
    • 重复这个过程,直到所有 required[i] 都为 0 或无法继续减少。

可能的算法步骤

  1. 初始化 required = nums.copy()
  2. 初始化 selected_queries = []
  3. 当 required 不全为 0:
    • 对于每个查询 [l, r],计算其可以覆盖的 i 中 required[i] > 0 的数量(即 sum(1 for i in range(l, r+1) if required[i] > 0))。
    • 选择能够覆盖最多这样的 i 的查询(贪心选择)。
    • 如果没有查询可以覆盖任何 required[i] > 0 的 i,返回 -1。
    • 将选中的查询加入 selected_queries,并对 i 在 [l, r] 且 required[i] > 0 的 i,将 required[i] -= 1
    • 从 queries 中移除该查询(或标记为已使用)。
  4. 返回 len(queries) - len(selected_queries)(即删除的查询数量)。

示例验证

让我们通过一个简单的例子来验证这个算法:

例子 1:

  • nums = [1, 2, 1]
  • queries = [[0, 1], [1, 2], [0, 2]]

初始化 required = [1, 2, 1]

  1. 计算每个查询的覆盖:

    • [0,1]: covers required[0]=1required[1]=2 → count=2
    • [1,2]: covers required[1]=2required[2]=1 → count=2
    • [0,2]: covers all → count=3
    • 选择 [0,2]
    • 应用 [0,2]required becomes [0, 1, 0]
    • selected_queries = [[0,2]]
  2. required = [0, 1, 0]:

    • [0,1]: covers required[1]=1 → count=1
    • [1,2]: covers required[1]=1 → count=1
    • 选择 [0,1] 或 [1,2]
    • 选择 [0,1]:
      • required becomes [0, 0, 0]
      • selected_queries = [[0,2], [0,1]]

所有 required 为 0,删除的查询数量 = 3 - 2 = 1

但这不是最优的,因为我们可以只选择 [0,1] 和 [1,2]

  • [0,1]required -> [0,1,1]
  • [1,2]required -> [0,0,0]
  • selected_queries = [[0,1], [1,2]],删除 [0,2],也是删除 1。

看起来两种方式删除数量相同。但初始 nums 总和是 4,每次操作最多减少 2(两个元素),所以至少需要 2 次操作。

例子 2:

  • nums = [3, 0, 0]
  • queries = [[0,0], [0,0], [0,0]]

需要 nums[0] 被减少 3 次,只能通过 [0,0] 操作。需要至少 3 次 [0,0] 操作。
所以不能删除任何查询,删除数量 = 0。

更优的贪心策略

前面的贪心策略可能在选择查询时不够高效。可能需要更聪明的贪心选择:

  • 按查询的右端点排序,然后从左到右处理 nums,尽可能选择覆盖当前 nums[i] 的最右边的查询。

类似于区间覆盖问题中的经典贪心算法。

正确的贪心算法

更准确的贪心策略:

  1. 将 queries 按照右端点升序排序(或按左端点降序)。
  2. 初始化 required = nums.copy()
  3. 初始化 selected = 0
  4. 使用一个差分数组或线段树来高效地进行区间操作。
  5. 对于每个 i 从 0 到 n-1:
    • 如果 required[i] > 0
      • 选择覆盖 i 的最右边的查询 [l, r] 且 l <= i <= r
      • 应用该查询 required[i] 次(即选择该查询 required[i] 次)。
      • 对于 j in [l, r]required[j] -= required[i]
      • selected += required[i]
      • 如果无法找到覆盖 i 的查询,返回 -1。
  6. 返回 len(queries) - selected

示例验证

例子 1:
nums = [1, 2, 1]queries = [[0,1], [1,2], [0,2]]

排序 queries 按右端点:[[0,1], [1,2], [0,2]](已经按右端点升序)。

初始化 required = [1, 2, 1].

  1. i=0:
    • required[0]=1 > 0:
      • 覆盖 0 的最右边的查询:[0,2](右端点最大)。
      • 应用 [0,2] 1 次:
        • required becomes [0, 1, 0].
        • selected = 1.
  2. i=1:
    • required[1]=1 > 0:
      • 覆盖 1 的最右边的查询:[1,2]
      • 应用 [1,2] 1 次:
        • required becomes [0, 0, -1](但 required[2] 已经是 0)。
        • selected = 2.
  3. i=2:
    • required[2]=0,跳过。

selected = 2,删除 3 - 2 = 1

实现细节

为了实现这个贪心策略,我们需要:

  1. 对 queries 按右端点升序排序。
  2. 对于每个 i,找到覆盖 i 的最右边的查询(即 l <= i 的最大 r)。
    • 可以使用优先队列(最大堆)来维护当前可以覆盖 i 的查询。
  3. 使用差分数组或线段树来高效地进行区间减操作。

伪代码

def max_queries_to_delete(nums, queries):n = len(nums)required = nums.copy()queries_sorted = sorted(queries, key=lambda x: x[1])  # 按右端点升序heap = []ptr = 0selected = 0for i in range(n):# 将所有 l <= i 的查询加入堆(按 -l 最大堆)while ptr < len(queries_sorted) and queries_sorted[ptr][1] >= i:l, r = queries_sorted[ptr]heapq.heappush(heap, (-l, r))ptr += 1# 选择 l <= i <= r 的最大的 lwhile heap and required[i] > 0:neg_l, r = heapq.heappop(heap)l = -neg_lif l > i:continue  # 不覆盖 i# 可以应用这个查询 min(required[i], ...) 次delta = min(required[i], ...)# 需要区间 [l, r] 的 required 都减 delta# 这里需要高效区间减,可能需要差分数组# 假设可以高效进行区间减for j in range(l, r + 1):required[j] -= deltaselected += deltaif any(r > 0 for r in required):return -1return len(queries) - selected

使用差分数组

为了高效进行区间减操作,可以使用差分数组:

  1. 初始化 diff = [0] * (n + 1)
  2. required[i] = nums[i] + diff[i](通过差分数组表示)。
  3. 区间 [l, r] 减 deltadiff[l] -= deltadiff[r+1] += delta
  4. 计算 required[i] 时,required[i] = nums[i] + prefix_sum(diff[0..i])

完整算法

结合贪心选择和差分数组:

  1. 按右端点升序排序 queries
  2. 使用最大堆(按 l 降序)维护当前可覆盖 i 的查询。
  3. 使用差分数组进行区间减操作。
  4. 对于每个 i,选择覆盖 i 的最左边的查询(最大 l),并应用足够的次数。

最终答案

经过以上分析,以下是解决问题的步骤:

  1. 将 queries 按右端点升序排序。
  2. 使用最大堆(按 l 降序)维护当前可覆盖 i 的查询。
  3. 初始化 required = nums.copy() 和差分数组 diff = [0] * (n + 1)
  4. 对于每个 i 从 0 到 n-1:
    • 计算当前 required[i] = nums[i] + sum(diff[0..i])
    • 将所有 l <= i 的查询 [l, r] 加入堆(按 -l)。
    • 当 required[i] > 0
      • 弹出堆顶查询 [l, r](最大 l)。
      • 应用 min(required[i], ...) 次:
        • delta = min(required[i], ...)
        • diff[l] -= deltadiff[r+1] += delta
        • selected += delta
  5. 检查所有 required[i] 是否为 0。
  6. 返回 len(queries) - selected

代码实现

import heapqdef max_queries_to_delete(nums, queries):n = len(nums)queries_sorted = sorted(queries, key=lambda x: x[1])  # 按右端点升序heap = []ptr = 0selected = 0diff = [0] * (n + 1)for i in range(n):# 更新 required[i]if i > 0:diff[i] += diff[i-1]required_i = nums[i] + diff[i]# 添加所有 l <= i 的查询到堆while ptr < len(queries_sorted) and queries_sorted[ptr][1] >= i:l, r = queries_sorted[ptr]heapq.heappush(heap, (-l, r))ptr += 1# 应用查询直到 required_i <= 0while required_i > 0 and heap:neg_l, r = heapq.heappop(heap)l = -neg_lif l > i:continue  # 不覆盖 i# 可以应用这个查询最多 required_i 次delta = required_i# 区间 [l, r] 减 deltadiff[l] -= deltaif r + 1 < n:diff[r+1] += deltaselected += delta# 更新 required_irequired_i = nums[i] + (diff[i] if i == 0 else diff[i] + diff[i-1])# 检查所有 required[i] <= 0total = 0for i in range(n):total += diff[i]if nums[i] + total > 0:return -1return len(queries) - selected

复杂度分析

  • 排序 queries:O(m log m),其中 m 是 queries 的长度。
  • 堆操作:每个查询最多被加入和弹出一次,O(m log m)。
  • 差分数组操作:O(n + m)。
  • 总体复杂度:O(m log m + n)。

示例验证

例子 1:
nums = [1, 2, 1]queries = [[0,1], [1,2], [0,2]]

排序 queries[[0,1], [1,2], [0,2]](按右端点升序)。

  • i=0:
    • required[0] = 1:
      • 堆: [(-0,1), (-0,2)]
      • 选择 [0,2]delta=1:
        • diff[0] = -1diff[3] = 1
        • selected = 1
        • required[0] = 1 + (-1) = 0
  • i=1:
    • diff[1] += diff[0] = -1:
      • required[1] = 2 + (-1) = 1:
        • 堆: [(-1,2)]
        • 选择 [1,2]delta=1:
          • diff[1] = -1 -1 = -2diff[3] = 1 +1 = 2
          • selected = 2
          • required[1] = 2 + (-2) = 0
  • i=2:
    • diff[2] += diff[1] = -2:
      • required[2] = 1 + (-2) = -1(<=0)。
  • 检查 required:
    • i=01 + (-1) = 0
    • i=12 + (-2) = 0
    • i=21 + (-2) = -1
  • 返回 3 - 2 = 1

例子 2:
nums = [3, 0, 0]queries = [[0,0], [0,0], [0,0]]

排序 queries[[0,0], [0,0], [0,0]]

  • i=0:
    • required[0] = 3:
      • 堆: [(-0,0), (-0,0), (-0,0)]
      • 选择 [0,0]delta=3:
        • 但每个查询只能应用一次(因为最多减少 1),所以需要应用 3 次 [0,0]
        • selected = 3
        • required[0] = 0
  • 返回 3 - 3 = 0

修正贪心策略

之前的实现中,假设一个查询可以应用多次(如 delta 次),但实际上每个查询只能应用一次(最多减少 1)。因此,需要调整:

  • 每个查询 [l, r] 只能应用一次(即最多减少 1 的区间操作)。
  • 因此,required[i] 需要被至少 nums[i] 个不同的查询覆盖。

因此,问题转化为:

  • 对于每个 nums[i],需要至少 nums[i] 个查询覆盖 i
  • 选择最少数量的查询满足所有 nums[i] 的覆盖需求。

这类似于多覆盖问题(multi-set cover),可以使用贪心算法:

  1. 按右端点升序排序 queries
  2. 对于每个 i,选择覆盖 i 的最右边的查询,尽可能覆盖更多的 i
  3. 使用差分数组或线段树来跟踪每个 i 还需要被覆盖的次数。

修正后的算法

import heapqdef max_queries_to_delete(nums, queries):n = len(nums)queries_sorted = sorted(queries, key=lambda x: x[1])  # 按右端点升序heap = []ptr = 0selected = 0required = nums.copy()for i in range(n):# 添加所有 l <= i 的查询到堆while ptr < len(queries_sorted) and queries_sorted[ptr][1] >= i:l, r = queries_sorted[ptr]heapq.heappush(heap, (-l, r))  # 最大堆按 lptr += 1# 应用查询直到 required[i] <= 0while required[i] > 0 and heap:neg_l, r = heapq.heappop(heap)l = -neg_lif l > i:continue  # 不覆盖 i# 应用这个查询一次selected += 1# 区间 [l, r] 的 required 减 1for j in range(l, r + 1):required[j] -= 1if any(r > 0 for r in required):return -1return len(queries) - selected

复杂度分析

  • 排序:O(m log m)。
  • 堆操作:O(m log m)。
  • 区间减操作:最坏 O(n m)(如果每个查询覆盖整个数组)。
  • 需要优化区间减操作。

使用差分数组优化

import heapqdef max_queries_to_delete(nums, queries):n = len(nums)queries_sorted = sorted(queries, key=lambda x: x[1])  # 按右端点升序heap = []ptr = 0selected = 0required = nums.copy()diff = [0] * (n + 1)for i in range(n):# 更新 required[i]if i > 0:diff[i] += diff[i-1]required_i = required[i] + diff[i]# 添加所有 l <= i 的查询到堆while ptr < len(queries_sorted) and queries_sorted[ptr][1] >= i:l, r = queries_sorted[ptr]heapq.heappush(heap, (-l, r))  # 最大堆按 lptr += 1# 应用查询直到 required_i <= 0while required_i > 0 and heap:neg_l, r = heapq.heappop(heap)l = -neg_lif l > i:continue  # 不覆盖 i# 应用这个查询一次selected += 1# 区间 [l, r] 的 required 减 1diff[l] -= 1if r + 1 < n:diff[r+1] += 1# 更新 required_irequired_i += (diff[i] - (diff[i-1] if i > 0 else 0))# 检查所有 required[i] <= 0total = 0for i in range(n):total += diff[i]if nums[i] + total > 0:return -1return len(queries) - selected

最终代码

结合以上思路,以下是正确的实现:

 
import heapqdef max_queries_to_delete(nums, queries):n = len(nums)queries_sorted = sorted(queries, key=lambda x: x[1])  # 按右端点升序heap = []ptr = 0selected = 0required = nums.copy()for i in range(n):# 添加所有 l <= i 的查询到堆while ptr < len(queries_sorted) and queries_sorted[ptr][1] >= i:l, r = queries_sorted[ptr]heapq.heappush(heap, (-l, r))  # 最大堆按 lptr += 1# 应用查询直到 required[i] <= 0while required[i] > 0 and heap:neg_l, r = heapq.heappop(heap)l = -neg_lif l > i:continue  # 不覆盖 i# 应用这个查询一次selected += 1# 区间 [l, r] 的 required 减 1for j in range(l, r + 1):required[j] -= 1if any(r > 0 for r in required):return -1return len(queries) - selected

复杂度优化

为了优化区间减操作,可以使用差分数组:

import heapqdef max_queries_to_delete(nums, queries):n = len(nums)queries_sorted = sorted(queries, key=lambda x: x[1])  # 按右端点升序heap = []ptr = 0selected = 0required = nums.copy()diff = [0] * (n + 1)for i in range(n):# 更新 required[i]if i > 0:diff[i] += diff[i-1]required_i = required[i] + diff[i]# 添加所有 l <= i 的查询到堆while ptr < len(queries_sorted) and queries_sorted[ptr][1] >= i:l, r = queries_sorted[ptr]heapq.heappush(heap, (-l, r))  # 最大堆按 lptr += 1# 应用查询直到 required_i <= 0while required_i > 0 and heap:neg_l, r = heapq.heappop(heap)l = -neg_lif l > i:continue  # 不覆盖 i# 应用这个查询一次selected += 1# 区间 [l, r] 的 required 减 1diff[l] -= 1if r + 1 < n:diff[r+1] += 1# 更新 required_irequired_i -= 1# 检查所有 required[i] <= 0total = 0for i in range(n):total += diff[i]if nums[i] + total > 0:return -1return len(queries) - selected

示例验证

例子 1:
nums = [1, 2, 1]queries = [[0,1], [1,2], [0,2]]

排序 queries[[0,1], [1,2], [0,2]].

  • i=0:
    • required[0] = 1:
      • 堆: [(-0,1), (-0,2)]
      • 选择 [0,2]:
        • diff[0] = -1diff[3] = 1
        • selected = 1
        • required[0] = 1 + (-1) = 0
  • i=1:
    • diff[1] += diff[0] = -1:
      • required[1] = 2 + (-1) = 1:
        • 堆: [(-1,2)]
        • 选择 [1,2]:
          • diff[1] = -1 -1 = -2diff[3] = 1 +1 = 2
          • selected = 2
          • required[1] = 2 + (-2) = 0
  • i=2:
    • diff[2] += diff[1] = -2:
      • required[2] = 1 + (-2) = -1
  • 检查 required:
    • i=01 + (-1) = 0
    • i=12 + (-2) = 0
    • i=21 + (-2) = -1
  • 返回 3 - 2 = 1

结论

通过贪心算法和差分数组优化,可以高效地解决这个问题。最终的最多删除查询数量为 len(queries) - selected,其中 selected 是最小需要保留的查询数量。如果无法满足所有 nums[i] 的减少需求,则返回 -1。

相关文章:

  • Honeywell CV-DINA-DI1624-2A 数字输入模块
  • 【Web前端】JavaScript入门与基础(一)
  • 【软件测试】第三章·软件测试基本方法(逻辑覆盖、路径覆盖)
  • Redis 缓存使用的BigKey问题
  • Cesium基础对象介绍
  • MySQL别名规则与应用场景
  • 矩阵详解:线性代数在AI大模型中的核心支柱
  • 【ICL】上下文学习
  • 英语写作中“假设”suppose, assume, presume 的用法
  • Arthas(阿尔萨斯)
  • C++高效求解非线性方程组的实践指南
  • 第一个Python程序
  • 主类网络和无类网络,什么是主类网络边界
  • 5.23 打卡
  • 淘宝卖家评价等级如何区分?如何提升信誉等级?
  • centos原系统安装了Python3.7.9兼用在安装一个python3.8
  • 【JS】vue3具名导出与默认导出
  • 人工智能在优化算法与大规模求解器中的应用与发展
  • 【论文阅读】Stop Overthinking:高效大模型推理技术综述
  • 详解Mysql的 Binlog、UndoLog 和 RedoLog
  • 网站建设优化服务价格/百度做广告怎么收费
  • 合肥包河区最新消息/宁波seo网页怎么优化
  • 马云做的国外的网站叫什么名字/阜阳seo
  • 免费网站建设itcask/站长之家产品介绍
  • 1688网站建设方案书模板/百度信息流怎么投放
  • 广州楼市最新消息/优化大师下载电脑版