Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 5f7ad64

Browse files
author
robot
committed
feat: 3229ドル
1 parent d301385 commit 5f7ad64

File tree

1 file changed

+44
-40
lines changed

1 file changed

+44
-40
lines changed

‎problems/1526.minimum-number-of-increments-on-subarrays-to-form-a-target-array.md

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -49,81 +49,81 @@ https://leetcode-cn.com/problems/minimum-number-of-increments-on-subarrays-to-fo
4949

5050
## 前置知识
5151

52-
- 差分与前缀和
52+
-
5353

5454
## 公司
5555

5656
- 暂无
5757

5858
## 思路
5959

60-
首先我们要有前缀和以及差分的知识。这里简单讲述一下:
6160

62-
- 前缀和 pres:对于一个数组 A [1,2,3,4],它的前缀和就是 [1,1+2,1+2+3,1+2+3+4],也就是 [1,3,6,10],也就是说前缀和 $pres[i] =\sum_{n=0}^{n=i}A[i]$
63-
- 差分数组 d:对于一个数组 A [1,2,3,4],它的差分数组就是 [1,2-1,3-2,4-3],也就是 [1,1,1,1],也就是说差分数组 $d[i] = A[i] - A[i-1](i > 0),ドル$d[i] = A[i](i == 0)$
61+
这道题是要我们将一个全为 0 的数组修改为 nums 数组。我们不妨反着思考,将 nums 改为一个长度相同且全为 0 的数组, 这是等价的。(不这么思考问题也不大,只不过会稍微方便一点罢了)
6462

65-
前缀和与差分数组互为逆运算。如何理解呢?这里的原因在于你对 A 的差分数组 d 求前缀和就是数组 A。前缀和对于求区间和有重大意义。而差分数组通常用于**先对数组的若干区间执行若干次增加或者减少操作**。仔细看这道题不就是**对数组若干区间执行 n 次增加操作**,让你返回从一个数组到另外一个数组的最少操作次数么?差分数组对两个数字的操作等价于原始数组区间操作,这样时间复杂度大大降低 O(N) -> O(1)
63+
而我们可以进行的操作是选择一个**子数组**,将子数组中的每个元素减去 1(题目是加 1, 但是我们是反着思考,那么就是减去 1)
6664

67-
题目要求**返回从 initial 得到 target 的最少操作次数**。这道题我们可以逆向思考**返回从 target 得到 initial 的最少操作次数**
65+
考虑 nums[0]:
6866

69-
这有什么区别么?对问题求解有什么帮助?由于 initial 是全为 0 的数组,如果将其作为最终搜索状态则不需要对状态进行额外的判断。这句话可能比较难以理解,我举个例子你就懂了。比如我不反向思考,那么初始状态就是 initial ,最终搜索状态自然是 target ,假如我们现在搜索到一个状态 state.我们需要**逐个判断 state[i] 是否等于 target[i]**,如果全部都相等则说明搜索到了 target ,否则没有搜索到,我们继续搜索。而如果我们从 target 开始搜,最终状态就是 initial,我们只需要判断每一位是否都是 0 就好了。 这算是搜索问题的常用套路。
67+
- 其如果是 0,我们没有必要对其进行修改。
68+
- 如果 nums[0] > 0,我们需要进行 nums[i] 次操作将其变为 0
7069

71-
上面讲到了对差分数组求前缀和可以还原原数组,这是差分数组的性质决定的。这里还有一个特点是**如果差分数组是全 0 数组,比如[0, 0, 0, 0],那么原数组也是[0, 0, 0, 0]**。因此将 target 的差分数组 d 变更为 全为 0 的数组就等价于 target 变更为 initaial。
70+
由于每次操作都可以选择一个子数组,而不是一个数。考虑这次修改的区间为 [l, r],这里 l 自然就是 0,那么 r 取多少可以使得结果最佳呢?
7271

73-
如何将 target 变更为 initaial?
72+
> 我们用 [l, r] 来描述一次操作将 nums[l...r](l和r都包含) 的元素减去 1 的操作。
7473
75-
由于我们是反向操作,也就是说我们可执行的操作是 **-1**,反映在差分数组上就是在 d 的左端点 -1,右端点(可选)+1。如果没有对应的右端点+1 也是可以的。这相当于给原始数组的 [i,n-1] +1,其中 n 为 A 的长度
74+
这实际上取决于 nums[1], nums[2] 的取值
7675

77-
如下是一种将 [3, -2, 0, 1] 变更为 [0, 0, 0, 0] 的可能序列。
76+
- 如果 nums[1] > 0,那么我们需要对 nums[1] 进行 nums[1] 次操作。(这个操作可能是 l 为 1 的,也可能是 r > 1 的)
77+
- 如果 nums[1] == 0,那么我们不需要对 nums[1] 进行操作。
7878

79-
```
80-
[3, -2, 0, 1] -> [**2**, **-1**, 0, 1] -> [**1**, **0**, 0, 1] -> [**0**, 0, 0, 1] -> [0, 0, 0, **0**]
81-
```
79+
我们的目的就是减少操作数,因此我们可以贪心地求最少操作数。具体为:
8280

83-
可以看出,上面需要进行四次区间操作,因此我们需要返回 4。
81+
1. 找到第一个满足 nums[i] != 0 的位置 i
82+
2. 先将操作的左端点固定为 i,然后选择右端点 r。对于端点 r,我们需要****操作 k 次操作,其中 k 为 min(nums[r], nums[r - 1], ..., nums[i]) 。最小值可以在遍历的同时求出来。
83+
3. 此时 nums[i] 变为了 nums[i] - k, nums[i + 1] 变为了 nums[i + 1] - k,...,nums[r] 变为了 nums[r] - k。**由于最小值 k 为0零,会导致我们白白计算一圈,没有意义,因此我们只能延伸到不为 0 的点**
84+
4. 答案加 k,我们继续使用同样的方法确定右端点 r。
85+
5. i = i + 1,重复 2-4 步骤。
8486

85-
至此,我们的算法就比较明了了
87+
总的思路就是先选最左边不为 0 的位置为左端点,然后**尽可能延伸右端点**,每次确定右端点的时候,我们需要找到 nums[i...r] 的最小值,然后将 nums[i...r] 减去这个最小值。这里的"尽可能延伸"就是没有遇到 num[j] == 0 的点
8688

87-
具体算法:
89+
这种做法的时间复杂度为 $O(n^2)$。而数据范围为 10ドル^5,ドル因此这种做法是不可以接受的。
8890

89-
- 对 A 计算差分数组 d
90-
- 遍历差分数组 d,对 d 中 大于 0 的求和。该和就是答案。
91+
> 不懂为什么不可以接受,可以看下我的这篇文章:https://lucifer.ren/blog/2020/12/21/shuati-silu3/
9192
92-
```py
93-
class Solution:
94-
def minNumberOperations(self, A: List[int]) -> int:
95-
d = [A[0]]
96-
ans = 0
97-
98-
for i in range(1, len(A)):
99-
d.append(A[i] - A[i-1])
100-
for a in d:
101-
ans += max(0, a)
102-
return ans
103-
```
93+
我们接下来考虑如何优化。
10494

105-
**复杂度分析** 令 N 为数组长度
95+
对于 nums[i] > 0,我们确定了左端点为 i 后,我们需要确定具体右端点 r 只是为了更新 nums[i...r] 的值。而更新这个值的目的就是想知道它们还需要几次操作。我们考虑如何将这个过程优化
10696

107-
- 时间复杂度:$O(N)$
108-
- 空间复杂度:$O(N)$
97+
考虑 nums[i+1] 和 nums[i] 的关系:
98+
99+
- 如果 nums[i+1] > nums[i],那么我们还需要对 nums[i+1] 进行 nums[i+1] - nums[i] 次操作。
100+
- 如果 nums[i+1] <= nums[i],那么我们不需要对 nums[i+1] 进行操作。
101+
102+
如果我们可以把 [i,r]的操作信息从 i 更新到 i + 1 的位置,那是不是说后面的数只需要看前面相邻的数就行了?
103+
104+
我们可以想象 nums[i+1] 就是一片木桶。
109105

110-
实际上,我们没有必要真实地计算差分数组 d,而是边遍历边求,也不需要对 d 进行存储。具体见下方代码区。
106+
- 如果 nums[i+1] 比 nums[i+2] 低,那么通过操作 [i,r] 其实也只能过来 nums[i+1] 这么多水。因此这个操作是从[i,r]还是[i+1,r]过来都无所谓。因为至少可以从左侧过来 nums[i+1] 的水。
107+
- 如果 nums[i+1] 比 nums[i+2] 高,那么我们也不必关心这个操作是 [i,r] 还是 [i+1,r]。因为既然 nums[i+1] 都已经变为 0 了,那么必然可以顺便把我搞定。
108+
109+
也就是说可以只考虑相邻两个数的关系,而不必考虑更远的数。而考虑的关键就是 nums[i] 能够从左侧的操作获得多少顺便操作的次数 m,nums[i] - m 就是我们需要额为的次数。我们不关心 m 个操作具体是左边哪一个操作带来的,因为题目只是让你求一个次数,而不是具体的操作序列。
111110

112111
## 关键点
113112

114113
- 逆向思考
115-
- 使用差分减少时间复杂度
114+
- 考虑修改的左右端点
116115

117116
## 代码
118117

119118
代码支持:Python3
120119

121120
```python
122121
class Solution:
123-
def minNumberOperations(self, A: List[int]) -> int:
124-
ans = A[0]
125-
for i in range(1, len(A)):
126-
ans += max(0, A[i] - A[i-1])
122+
def minNumberOperations(self, nums: List[int]) -> int:
123+
ans = abs(nums[0])
124+
for i in range(1, len(nums)):
125+
if abs(nums[i]) > abs(nums[i - 1]): # 这种情况,说明前面不能顺便把我改了,还需要我操作 k 次
126+
ans += abs(nums[i]) - abs(nums[i - 1])
127127
return ans
128128
```
129129

@@ -132,6 +132,10 @@ class Solution:
132132
- 时间复杂度:$O(N)$
133133
- 空间复杂度:$O(1)$
134134

135+
## 相似题目
136+
137+
- [3229. 使数组等于目标数组所需的最少操作次数](./3229.minimum-operations-to-make-array-equal-to-target.md)
138+
135139
## 扩展
136140

137141
如果题目改为:给你一个数组 nums,以及 size 和 K。 其中 size 指的是你不能对区间大小为 size 的子数组执行+1 操作,而不是上面题目的**任意**子数组。K 指的是你只能进行 K 次 +1 操作,而不是上面题目的任意次。题目让你求的是**经过这样的 k 次+1 操作,数组 nums 的最小值最大可以达到多少**

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /