本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。

大家好,我是小彭。

这场周赛是 LeetCode 双周赛第 103 场,难得在五一假期第一天打周赛的人数也没有少太多。这场比赛前 3 题比较简单,我们把篇幅留给最后一题。

往期周赛回顾:LeetCode 单周赛第 342 场 · 容斥原理、计数排序、滑动窗口、子数组 GCB

周赛概览

Q1. K 个元素的最大和(Easy)

简单模拟题,不过多讲解。

Q2. 找到两个数组的前缀公共数组(Medium)

简单模拟题,在计数的实现上有三种解法:

  • 解法 1:散列表 $O(n)$ 空间复杂度
  • 解法 2:技数数组 $O(n)$ 空间复杂度
  • 解法 3:状态压缩 $O(1)$ 空间复杂度

Q3. 网格图中鱼的最大数目(Hard)

这道题的难度标签是认真的吗?打 Medium 都过分了居然打 Hard?

  • 解法 1:BFS / DFS $O(nm)$
  • 解法 2:并查集 $O(nm)$

Q4. 将数组清空(Hard)

这道题的难点在于如何想到以及正确地将原问题转换为区间求和问题,思路想清楚后用树状数组实现。

  • 解法 1:树状数组 + 索引数组 $O(nlgn)$
  • 解法 2:树状数组 + 最小堆 $O(nlgn)$


Q1. K 个元素的最大和(Easy)

  1. https://leetcode.cn/problems/maximum-sum-with-exactly-k-elements/

题目描述

给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。你需要执行以下操作 恰好 k 次,最大化你的得分:

  1. 从 nums 中选择一个元素 m 。
  2. 将选中的元素 m 从数组中删除。
  3. 将新元素 m + 1 添加到数组中。
  4. 你的得分增加 m 。

请你返回执行以上操作恰好 k 次后的最大得分。

示例 1:

  1. 输入:nums = [1,2,3,4,5], k = 3
  2. 输出:18
  3. 解释:我们需要从 nums 中恰好选择 3 个元素并最大化得分。
  4. 第一次选择 5 。和为 5 nums = [1,2,3,4,6]
  5. 第二次选择 6 。和为 6 nums = [1,2,3,4,7]
  6. 第三次选择 7 。和为 5 + 6 + 7 = 18 nums = [1,2,3,4,8]
  7. 所以我们返回 18
  8. 18 是可以得到的最大答案。

示例 2:

  1. 输入:nums = [5,5,5], k = 2
  2. 输出:11
  3. 解释:我们需要从 nums 中恰好选择 2 个元素并最大化得分。
  4. 第一次选择 5 。和为 5 nums = [5,5,6]
  5. 第二次选择 6 。和为 6 nums = [5,5,7]
  6. 所以我们返回 11
  7. 11 是可以得到的最大答案。

提示:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 100
  • 1 <= k <= 100

预备知识 - 等差数列求和

  • 等差数列求和公式:(首项 + 尾项) * 项数 / 2

题解(模拟 + 贪心)

显然第一次操作的分数会选择数组中的最大值 max,后续操作是以 max 为首项的等差数列,直接使用等差数列求和公式即可。

  1. class Solution {
  2. fun maximizeSum(nums: IntArray, k: Int): Int {
  3. val max = Arrays.stream(nums).max().getAsInt()
  4. return (max + max + k - 1) * k / 2
  5. }
  6. }

复杂度分析:

  • 时间复杂度:$O(n)$ 其中 n 是 nums 数组的长度;
  • 空间复杂度:$O(1)$

Q2. 找到两个数组的前缀公共数组(Medium)

  1. https://leetcode.cn/problems/find-the-prefix-common-array-of-two-arrays/

题目描述

给你两个下标从 0 开始长度为 n 的整数排列 A 和 B 。

A 和 B 的 前缀公共数组 定义为数组 C ,其中 C[i] 是数组 A 和 B 到下标为 i 之前公共元素的数目。

请你返回 A 和 B 的 前缀公共数组 。

如果一个长度为 n 的数组包含 1 到 n 的元素恰好一次,我们称这个数组是一个长度为 n 的 排列 。

示例 1:

  1. 输入:A = [1,3,2,4], B = [3,1,2,4]
  2. 输出:[0,2,3,4]
  3. 解释:i = 0:没有公共元素,所以 C[0] = 0
  4. i = 11 3 是两个数组的前缀公共元素,所以 C[1] = 2
  5. i = 212 3 是两个数组的前缀公共元素,所以 C[2] = 3
  6. i = 3123 4 是两个数组的前缀公共元素,所以 C[3] = 4

示例 2:

  1. 输入:A = [2,3,1], B = [3,1,2]
  2. 输出:[0,1,3]
  3. 解释:i = 0:没有公共元素,所以 C[0] = 0
  4. i = 1:只有 3 是公共元素,所以 C[1] = 1
  5. i = 212 3 是两个数组的前缀公共元素,所以 C[2] = 3

提示:

  • 1 <= A.length == B.length == n <= 50
  • 1 <= A[i], B[i] <= n
  • 题目保证 A 和 B 两个数组都是 n 个元素的排列。

题解一(散列表)

从左到右遍历数组,并使用散列表记录访问过的元素,以及两个数组交集:

  1. class Solution {
  2. fun findThePrefixCommonArray(A: IntArray, B: IntArray): IntArray {
  3. val n = A.size
  4. val ret = IntArray(n)
  5. val setA = HashSet<Int>()
  6. val setB = HashSet<Int>()
  7. val interSet = HashSet<Int>()
  8. for (i in 0 until n) {
  9. setA.add(A[i])
  10. setB.add(B[i])
  11. if (setB.contains(A[i])) interSet.add(A[i])
  12. if (setA.contains(B[i])) interSet.add(B[i])
  13. ret[i] = interSet.size
  14. }
  15. return ret
  16. }
  17. }

复杂度分析:

  • 时间复杂度:$O(n)$ 其中 n 是 nums 数组的长度;
  • 空间复杂度:$O(n)$ 散列表空间。

题解二(计数数组)

题解一需要使用多倍空间,我们发现 A 和 B 都是 n 的排列,当访问到的元素 nums[i] 出现 2 次时就必然处于数组交集中。因此,我们不需要使用散列表记录访问过的元素,而只需要记录每个元素出现的次数。

  1. class Solution {
  2. fun findThePrefixCommonArray(A: IntArray, B: IntArray): IntArray {
  3. val n = A.size
  4. val ret = IntArray(n)
  5. val cnt = IntArray(n + 1)
  6. var size = 0
  7. for (i in 0 until n) {
  8. if (++cnt[A[i]] == 2) size ++
  9. if (++cnt[B[i]] == 2) size ++
  10. ret[i] = size
  11. }
  12. return ret
  13. }
  14. }

复杂度分析:

  • 时间复杂度:$O(n)$ 其中 n 是 nums 数组的长度;
  • 空间复杂度:$O(n)$ 计数数组空间;

题解三(状态压缩)

既然 A 和 B 的元素值不超过 50,我们可以使用两个 Long 变量代替散列表优化空间复杂度。

  1. class Solution {
  2. fun findThePrefixCommonArray(A: IntArray, B: IntArray): IntArray {
  3. val n = A.size
  4. val ret = IntArray(n)
  5. var flagA = 0L
  6. var flagB = 0L
  7. var size = 0
  8. for (i in 0 until n) {
  9. flagA = flagA or (1L shl A[i])
  10. flagB = flagB or (1L shl B[i])
  11. // Kotlin 1.5 才有 Long.countOneBits()
  12. // ret[i] = (flagA and flagB).countOneBits()
  13. ret[i] = java.lang.Long.bitCount(flagA and flagB)
  14. }
  15. return ret
  16. }
  17. }

复杂度分析:

  • 时间复杂度:$O(n)$ 其中 n 是 nums 数组的长度;
  • 空间复杂度:$O(1)$ 仅使用常量级别空间;

Q3. 网格图中鱼的最大数目(Hard)

  1. https://leetcode.cn/problems/maximum-number-of-fish-in-a-grid/description/

题目描述

给你一个下标从 0 开始大小为 m x n 的二维整数数组 grid ,其中下标在 (r, c) 处的整数表示:

  • 如果 grid[r][c] = 0 ,那么它是一块 陆地 。
  • 如果 grid[r][c] > 0 ,那么它是一块 水域 ,且包含 grid[r][c] 条鱼。

一位渔夫可以从任意 水域 格子 (r, c) 出发,然后执行以下操作任意次:

  • 捕捞格子 (r, c) 处所有的鱼,或者
  • 移动到相邻的 水域 格子。

请你返回渔夫最优策略下, 最多 可以捕捞多少条鱼。如果没有水域格子,请你返回 0 。

格子 (r, c) 相邻 的格子为 (r, c + 1) ,(r, c - 1) ,(r + 1, c) 和 (r - 1, c) ,前提是相邻格子在网格图内。

示例 1:

  1. 输入:grid = [[0,2,1,0],[4,0,0,3],[1,0,0,4],[0,3,2,0]]
  2. 输出:7
  3. 解释:渔夫可以从格子(1,3) 出发,捕捞 3 条鱼,然后移动到格子(2,3) ,捕捞 4 条鱼。

示例 2:

  1. 输入:grid = [[1,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,1]]
  2. 输出:1
  3. 解释:渔夫可以从格子 (0,0) 或者 (3,3) ,捕捞 1 条鱼。

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 10
  • 0 <= grid[i][j] <= 10

问题抽象

求 “加权连通分量 / 岛屿问题”,用二维 BFS 或 DFS 或并查集都可以求出所有连通块的最大值,史上最水 Hard 题。

题解一(二维 DFS)

  1. class Solution {
  2. private val directions = arrayOf(intArrayOf(0, 1), intArrayOf(0, -1), intArrayOf(1, 0), intArrayOf(-1, 0))
  3. fun findMaxFish(grid: Array<IntArray>): Int {
  4. var ret = 0
  5. for (i in 0 until grid.size) {
  6. for (j in 0 until grid[0].size) {
  7. ret = Math.max(ret, dfs(grid, i, j))
  8. }
  9. }
  10. return ret
  11. }
  12. private fun dfs(grid: Array<IntArray>, i: Int, j: Int): Int {
  13. if (grid[i][j] <= 0) return 0
  14. var cur = grid[i][j]
  15. grid[i][j] = -1
  16. for (direction in directions) {
  17. val newI = i + direction[0]
  18. val newJ = j + direction[1]
  19. if (newI < 0 || newI >= grid.size || newJ < 0 || newJ >= grid[0].size || grid[newI][newJ] <= 0) continue
  20. cur += dfs(grid, newI, newJ)
  21. }
  22. return cur
  23. }
  24. }

复杂度分析:

  • 时间复杂度:$O(n · m)$ 其中 n 和 m 是 grid 数组的行和列;
  • 空间复杂度:$O(n + m)$ 递归栈的最大深度。

题解二(并查集)

附赠一份并查集的解法:

  1. class Solution {
  2. private val directions = arrayOf(intArrayOf(0, 1), intArrayOf(0, -1), intArrayOf(1, 0), intArrayOf(-1, 0))
  3. fun findMaxFish(grid: Array<IntArray>): Int {
  4. val n = grid.size
  5. val m = grid[0].size
  6. var ret = 0
  7. // 并查集
  8. val helper = UnionFind(grid)
  9. // 合并
  10. for (i in 0 until n) {
  11. for (j in 0 until m) {
  12. ret = Math.max(ret, grid[i][j])
  13. if (grid[i][j] <= 0) continue
  14. for (direction in directions) {
  15. val newI = i + direction[0]
  16. val newJ = j + direction[1]
  17. if (newI < 0 || newI >= grid.size || newJ < 0 || newJ >= grid[0].size || grid[newI][newJ] <= 0) continue
  18. ret = Math.max(ret, helper.union(i * m + j, newI * m + newJ))
  19. }
  20. }
  21. }
  22. // helper.print()
  23. return ret
  24. }
  25. private class UnionFind(private val grid: Array<IntArray>) {
  26. private val n = grid.size
  27. private val m = grid[0].size
  28. // 父节点
  29. private val parent = IntArray(n * m) { it }
  30. // 高度
  31. private val rank = IntArray(n * m)
  32. // 数值
  33. private val value = IntArray(n * m)
  34. init {
  35. for (i in 0 until n) {
  36. for (j in 0 until m) {
  37. value[i * m + j] = grid[i][j]
  38. }
  39. }
  40. }
  41. // return 子集的和
  42. fun union(x: Int, y: Int): Int {
  43. // 按秩合并
  44. val parentX = find(x)
  45. val parentY = find(y)
  46. if (parentX == parentY) return value[parentY]
  47. if (rank[parentX] < rank[parentY]) {
  48. parent[parentX] = parentY
  49. value[parentY] += value[parentX]
  50. return value[parentY]
  51. } else if (rank[parentY] < rank[parentX]) {
  52. parent[parentY] = parentX
  53. value[parentX] += value[parentY]
  54. return value[parentX]
  55. } else {
  56. parent[parentY] = parentX
  57. value[parentX] += value[parentY]
  58. rank[parentY]++
  59. return value[parentX]
  60. }
  61. }
  62. fun print() {
  63. println("parent=${parent.joinToString()}")
  64. println("rank=${rank.joinToString()}")
  65. println("value=${value.joinToString()}")
  66. }
  67. private fun find(i: Int): Int {
  68. // 路径压缩
  69. var x = i
  70. while (parent[x] != x) {
  71. parent[x] = parent[parent[x]]
  72. x = parent[x]
  73. }
  74. return x
  75. }
  76. }
  77. }

复杂度分析:

  • 时间复杂度:$O(n · m)$ 其中 n 和 m 是 grid 数组的行和列;
  • 空间复杂度:$O(n + m)$ 递归栈的最大深度。

相似题目:

推荐阅读:


Q4. 将数组清空(Hard)

  1. https://leetcode.cn/problems/make-array-empty/

题目描述

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

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

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

预备知识 - 循环数组

循环数组:将数组尾部元素的后继视为数组首部元素,数组首部元素的前驱视为数组尾部元素。

预备知识 - 树状数组

OI · 树状数组

树状数组也叫二叉索引树(Binary Indexed Tree),是一种支持 “单点修改” 和 “区间查询” 的代码量少的数据结构。相比于线段树来说,树状数组的代码量远远更少,是一种精妙的数据结构。

树状数组核心思想是将数组 [0,x] 的前缀和拆分为不多于 logx 段非重叠的区间,在计算前缀和时只需要合并 logx 段区间信息,而不需要合并 n 个区间信息。同时,在更新单点值时,也仅需要修改 logx 段区间,而不需要(像前缀和数组)那样修改 n 个信息。可以说,树状数组平衡了单点修改和区间和查询的时间复杂度:

  • 单点更新 add(index,val):将序列第 index 位元素增加 val,时间复杂度为 O(lgn),同时对应于在逻辑树形结构上从小分块节点移动到大分块节点的过程(修改元素会影响大分块节点(子节点)的值);
  • 区间查询 prefixSum(index):查询前 index 个元素的前缀和,时间复杂度为 O(lgn),同时对应于在逻辑树形结构上累加区间段的过程。

树状数组

问题结构化

1、概括问题目标

求消除数组的操作次数。

2、分析题目要件

  • 观察:在每次操作中,需要观察数组首部元素是否为剩余元素中的最小值。例如序列 [3,2,1] 的首部元素不是最小值;
  • 消除:在每次操作中,如果数组首部元素是最小值,则可以消除数组头部元素。例序列 [1,2,3] 在一次操作后变为 [2,3];
  • 移动:在每次操作中,如果数组首部元素不是最小值,则需要将其移动到数组末尾。例如序列 [3,2,1] 在一次操作后变为 [2,1,3]。

3、观察数据特征

  • 数据量:测试用例的数据量上界为 10^5,这要求我们实现低于 O(n^2) 时间复杂度的算法才能通过;
  • 数据大小:测试用例的数据上下界为 [-10^9, 10^9],这要求我们考虑大数问题。

4、观察测试用例

以序列 [3,4,-1] 为例,一共操作 5 次:

  • [3,4,-1]:-1 是最小值,将 3 和 4 移动到末尾后才能消除 -1,一共操作 3 次;
  • [3,4]:3 是最小值,消除 3 操作 1 次;
  • [4]:4 是最小值,消除 4 操作 1 次;

5、提高抽象程度

  • 序列:线性表是由多个元素组成的序列,除了数组的头部和尾部元素之外,每个元素都有一个前驱元素和后继元素。在将数组首部元素移动到数组末尾时,将改变数组中的部分元素的关系,即原首部元素的前驱变为原尾部元素,原尾部元素的后继变为原首部元素。
  • 是否为决策问题:由于每次操作的行为是固定的,因此这道题只是纯粹的模拟问题,并不是决策问题。

6、具体化解决手段

消除操作需要按照元素值从小到大的顺序删除,那么如何判断数组首部元素是否为最小值?

  • 手段 1(暴力枚举):枚举数组剩余元素,判断首部元素是否为最小值,单次判断的时间复杂度是 O(n);
  • 手段 2(排序):对原始数组做预处理排序,由于原始数组的元素顺序信息在本问题中是至关重要的,所以不能对原始数组做原地排序,需要借助辅助数据结构,例如索引数组、最小堆,单次判断的均摊时间复杂度是 O(1)。

如何表示元素的移动操作:

  • 手段 1(数组):使用数组块状复制 Arrays.copy(),单次操作的时间复杂度是 O(n);
  • 手段 2(双向链表):将原始数组转换为双向链表,操作链表首尾元素的时间复杂度是 O(1),但会消耗更多空间;

如何解决问题:

  • 手段 1(模拟):模拟消除和移动操作,直到数组为空。在最坏情况下(降序数组)需要操作 n^2 次,因此无论如何都是无法满足题目的数据量要求;

至此,问题陷入瓶颈。

解决方法是重复「分析问题要件」-「具体化解决手段」的过程,枚举掌握的算法、数据结构和 Tricks 寻找突破口:

表示元素的移动操作的新手段:

  • 手段 3(循环数组):将原数组视为循环数组,数组尾部元素的后继是数组首部元素,数组首部元素的前驱是数组尾部元素,不再需要实际性的移动操作。

解决问题的新手段:

  • 手段 2(计数):观察测试用例发现,消除每个元素的操作次数取决于该元素的前驱中未被消除的元素个数,例如序列 [3,4,-1] 中 -1 前有 2 个元素未被删除,所以需要 2 次操作移动 3 和 4,再增加一次操作消除 -1。那么,我们可以定义 rangeSum(i,j) 表示区间 [i,j] 中未被删除的元素个数,每次消除操作只需要查询上一次的消除位置(上一个最小值)与当前的消除位置(当前的最小值)中间有多少个数字未被消除 rangeSum(上一个最小值位置, 当前的最小值位置),这个区间和就是消除当前元素需要的操作次数。

区分上次位置与当前位置的前后关系,需要分类讨论:

  • id < preId:消除次数 = rangeSum(id, preId)
  • id > preId:消除次数 = rangeSum(-1, id) + rangeSum(preId,n - 1)

如何实现手段 2(计数):

在代码实现上,涉及到「区间求和」和「单点更新」可以用线段数和树状数组实现。树状数组的代码量远比线段树少,所以我们选择后者。

示意图

答疑:

  • 消除每个元素的操作次数不用考虑前驱元素中小于当前元素的元素吗?

由于消除是按照元素值从小到大的顺序消除的,所以未被消除的元素一定比当前元素大,所以我们不强调元素大小关系。

题解一(树状数组 + 索引数组)

  • 使用「树状数组」的手段解决区间和查询和单点更新问题,注意树状数组是 base 1 的;
  • 使用「索引数组」的手段解决排序 / 最小值问题。
  1. class Solution {
  2. fun countOperationsToEmptyArray(nums: IntArray): Long {
  3. val n = nums.size
  4. var ret = 0L
  5. // 索引数组
  6. val ids = Array<Int>(n) { it }
  7. // 排序
  8. Arrays.sort(ids) { i1, i2 ->
  9. // 考虑大数问题
  10. // nums[i1] - nums[i2] x
  11. if (nums[i1] < nums[i2]) -1 else 1
  12. }
  13. // 树状数组
  14. val bst = BST(n)
  15. // 上一个被删除的索引
  16. var preId = -1
  17. // 遍历索引
  18. for (id in ids) {
  19. // 区间和
  20. if (id > preId) {
  21. ret += bst.rangeSum(preId, id)
  22. // println("id=$id, ${bst.rangeSum(preId, id)}")
  23. } else {
  24. ret += bst.rangeSum(-1, id) + bst.rangeSum(preId, n - 1)
  25. // println("id=$id, ${bst.rangeSum(-1,id)} + ${bst.rangeSum(preId, n - 1)}")
  26. }
  27. // 单点更新
  28. bst.dec(id)
  29. preId = id
  30. }
  31. return ret
  32. }
  33. // 树状数组
  34. private class BST(private val n: Int) {
  35. // base 1
  36. private val data = IntArray(n + 1)
  37. init {
  38. // O(nlgn) 建树
  39. // for (i in 0 .. n) {
  40. // update(i, 1)
  41. // }
  42. // O(n) 建树
  43. for (i in 1 .. n) {
  44. data[i] += 1
  45. val parent = i + lowbit(i)
  46. if (parent <= n) data[parent] += data[i]
  47. }
  48. }
  49. fun rangeSum(i1: Int, i2: Int): Int {
  50. return preSum(i2 + 1) - preSum(i1 + 1)
  51. }
  52. fun dec(i: Int) {
  53. update(i + 1, -1)
  54. }
  55. private fun preSum(i: Int): Int {
  56. var x = i
  57. var sum = 0
  58. while (x > 0) {
  59. sum += data[x]
  60. x -= lowbit(x)
  61. }
  62. return sum
  63. }
  64. private fun update(i: Int, delta: Int) {
  65. var x = i
  66. while (x <= n) {
  67. data[x] += delta
  68. x += lowbit(x)
  69. }
  70. }
  71. private fun lowbit(x: Int) = x and (-x)
  72. }
  73. }

复杂度分析:

  • 时间复杂度:$O(nlgn)$ 其中 n 是 nums 数组的长度,排序 $O(nlgn)$、树状数组建树 $O(n)$、单次消除操作的区间和查询和单点更新的时间为 $O(lgn)$;
  • 空间复杂度:$O(n)$ 索引数组空间 + 树状数组空间。

题解二(树状数组 + 最小堆)

附赠一份最小堆排序的代码:

  • 使用「树状数组」的手段解决区间和查询和单点更新问题,注意树状数组是 base 1 的;
  • 使用「最小堆」的手段解决排序 / 最小值问题。
  1. class Solution {
  2. fun countOperationsToEmptyArray(nums: IntArray): Long {
  3. val n = nums.size
  4. var ret = 0L
  5. // 最小堆
  6. val ids = PriorityQueue<Int>() { i1, i2 ->
  7. if (nums[i1] < nums[i2]) -1 else 1
  8. }
  9. for (id in 0 until n) {
  10. ids.offer(id)
  11. }
  12. // 树状数组
  13. val bst = BST(n)
  14. // 上一个被删除的索引
  15. var preId = -1
  16. // 遍历索引
  17. while (!ids.isEmpty()) {
  18. val id = ids.poll()
  19. // 区间和
  20. if (id > preId) {
  21. ret += bst.rangeSum(preId, id)
  22. } else {
  23. ret += bst.rangeSum(-1, id) + bst.rangeSum(preId, n - 1)
  24. }
  25. // 单点更新
  26. bst.dec(id)
  27. preId = id
  28. }
  29. return ret
  30. }
  31. }

复杂度分析:

  • 时间复杂度:$O(nlgn)$ 其中 n 是 nums 数组的长度,堆排序 $O(nlgn)$、树状数组建树 $O(n)$、单次消除操作的区间和查询和单点更新的时间为 $O(lgn)$;
  • 空间复杂度:$O(n)$ 堆空间 + 树状数组空间。

相似题目:


往期回顾

LeetCode 双周赛 103(2023/04/29)区间求和的树状数组经典应用的更多相关文章

  1. D 区间求和 [数学 树状数组]

    D 区间求和 题意:求 \[ \sum_{k=1}^n \sum_{l=1}^{n-k+1} \sum_{r=l+k-1}^n 区间前k大值和 \] 比赛时因为被B卡了没有深入想这道题 结果B没做出来 ...

  2. ACM学习历程—51NOD 1685 第K大区间2(二分 && 树状数组 && 中位数)

    http://www.51nod.com/contest/problem.html#!problemId=1685 这是这次BSG白山极客挑战赛的E题. 这题可以二分答案t. 关键在于,对于一个t,如 ...

  3. hdu-5700 区间交(二分+树状数组)

    题目链接: 区间交 Problem Description   小A有一个含有n个非负整数的数列与mm个区间.每个区间可以表示为l​i​​,r​i​​. 它想选择其中k个区间, 使得这些区间的交的那些 ...

  4. FZU2224 An exciting GCD problem 区间gcd预处理+树状数组

    分析:(别人写的) 对于所有(l, r)区间,固定右区间,所有(li, r)一共最多只会有log个不同的gcd值, 可以nlogn预处理出所有不同的gcd区间,这样区间是nlogn个,然后对于询问离线 ...

  5. 牛客网暑期ACM多校训练营(第一场):J-Different Integers(分开区间不同数+树状数组)

    链接:J-Different Integers 题意:给出序列a1, a2, ..., an和区间(l1, r1), (l2, r2), ..., (lq, rq),对每个区间求集合{a1, a2, ...

  6. SPOJ - DQUERY(区间不同数+树状数组)

    链接:SPOJ - DQUERY 题意:求给定区间不同数的个数(不更新). 题解:离线+树状数组. 对所求的所有区间(l, r)根据r从小到大排序.从1-n依次遍历序列数组,在树状数组中不断更新a[i ...

  7. hdu 1166 敌兵布阵——(区间和)树状数组/线段树

    pid=1166">here:http://acm.hdu.edu.cn/showproblem.php?pid=1166 Input 第一行一个整数T.表示有T组数据. 每组数据第一 ...

  8. TZOJ 5694 区间和II(树状数组区间加区间和)

    描述 给定n个整数,有两个操作: (1)给某个区间中的每个数增加一个值: (2)查询某个区间的和. 输入 第一行包括两个正整数n和q(1<=n, q<=100000),分别为序列的长度和操 ...

  9. 51nod 第K大区间2(二分+树状数组)

    题目链接: 第K大区间2 基准时间限制:1.5 秒 空间限制:131072 KB 分值: 160 定义一个长度为奇数的区间的值为其所包含的的元素的中位数.中位数_百度百科 现给出n个数,求将所有长度为 ...

  10. hdu1556 树状数组区间更新单点查询板子

    就是裸的区间更新: 相对于直观的线段树的区间更新,树状数组的区间更新原理不太相同:由于数组中的一个结点控制的是一块区间,当遇到更新[l,r]时,先将所有能控制到 l 的结点给更新了,这样一来就是一下子 ...

随机推荐

  1. VS 2022创建ATL组件 (C++)

    https://www.cnblogs.com/chechen/p/8119018.html 步骤如下: 1.新建ATL项目 打开Visual Studio 2022 新建ATL项目 2.添加接口类. ...

  2. Linux Broadcom Bluetooth BCM43142A0 蓝牙驱动安装

    Linux Broadcom Bluetooth BCM43142A0 蓝牙驱动安装 想转到Linux,奈何蓝牙鼠标不识别. 经历了4个发行版的努力(Linux Mint,Pop!OS,OpenSus ...

  3. bash中的basename与dirname以及${}

    var=/dir1/dir2/file.tar.gz basename $var        #获取文件名 file.tar.gz dirname $var            #获取目录名称 / ...

  4. 【攻防世界】web练习区write up

    目录: view_source robots backup cookie disabled button weak auth simple php xff referer webshell get p ...

  5. uglifyjs-webpack-plugin配置

    项目使用vuecli3搭建,在vue.config.js文件中进行配置,主要配置了去除线上环境的打印信息. 首先安装插件, 执行命令 npm install uglifyjs-webpack-plug ...

  6. rosetta Resfile语法和约束

    介绍 参考:https://www.rosettacommons.org/docs/latest/rosetta_basics/file_types/resfiles resfile包含输入到Pack ...

  7. Cryptanalyzing and Improving a Novel Color Image Encryption Algorithm Using RT-Enhanced Chaotic Tent Maps

    Cryptanalyzing and Improving a Novel ColorImage Encryption Algorithm Using RT-EnhancedChaotic Tent M ...

  8. 👋 和我一起学【Three.js】「初级篇」:0. 总论

    「和我一起学 XXX」是我 2023 年的一个新企划,目的是向读者(也包括未来的自己)介绍我正在学习的某项新技术.文章会通过长期反复迭代的方式保持其内容的新鲜度.文章有较大内容更新时,会在文章开头进行 ...

  9. Python学习笔记--元组+字符串

    元组 元组一旦定义完成,就不能再被修改 同样,元组也可以进行嵌套操作 当然,若是在元组里面嵌套一个list,那么list里面的元素是可以进行修改的! 案例: 实现: 字符串 查找索引值: 字符串替换: ...

  10. 在wxpython框架写的GUI中调用GIF格式图片,显示在指定行列的方法

    #首先需要从 wx.adv 模块导入 Animation, AnimationCtrl方法 from wx.adv import Animation, AnimationCtrl #然后在面板实现过程 ...