Skip to content

Commit d097441

Browse files
committed
Add: Add 2025/11/30
1 parent 5f81a37 commit d097441

File tree

3 files changed

+219
-0
lines changed

3 files changed

+219
-0
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# 1590. Make Sum Divisible by P
2+
3+
Given an array of positive integers `nums`, remove the smallest subarray (possibly empty) such that the sum of the remaining elements is divisible by `p`.
4+
It is not allowed to remove the whole array.
5+
6+
Return the length of the smallest subarray that you need to remove, or `-1` if it's impossible.
7+
8+
A subarray is defined as a contiguous block of elements in the array.
9+
10+
**Constraints:**
11+
12+
- `1 <= nums.length <= 10^5`
13+
- `1 <= nums[i] <= 10^9`
14+
- `1 <= p <= 10^9`
15+
16+
## 基礎思路
17+
18+
本題要求移除最短的連續子陣列,使得剩餘元素總和可以被 `p` 整除。由於不能移除整個陣列,因此必須找出**長度最小且可修正餘數的子段**
19+
20+
可掌握以下核心觀察:
21+
22+
* **總和能否被 p 整除,取決於總和對 p 的餘數**
23+
若總和 `S % p = r`,代表我們需移除某段子陣列,使被移除的這段和的餘數也為 `r`,才能抵銷掉整體餘數。
24+
25+
* **欲移除的子陣列是連續的,因此可用前綴和模 p 來表示其餘數**
26+
子陣列 `[l, r]` 的和可表示為:
27+
`(prefix[r] − prefix[l−1]) % p`
28+
若此值等於 `r`,即代表移除此段能讓總和變為可被 p 整除。
29+
30+
* **前綴和餘數的差值可精準建構所需的子陣列**
31+
欲使 `(currentPrefix − previousPrefix) % p = targetRemainder`,即可倒推出 `previousPrefix` 需為某特定值。
32+
33+
* **保持某餘數類別的最新索引,有助於縮短可移除的子段長度**
34+
每次遇到某個前綴餘數,都可以更新其最新出現位置,使後續計算的候選子段更短。
35+
36+
* **單次掃描即可完成整體推導**
37+
隨著前綴和餘數逐步累積,便能透過查詢 map 快速找到符合條件的移除段落。
38+
39+
透過上述觀察,可利用前綴和模 p、哈希表與線性掃描達成本題要求。
40+
41+
## 解題步驟
42+
43+
### Step 1:處理 p = 1 的快速結論
44+
45+
`p = 1`,任何整數都能被 1 整除,因此無需移除任何子陣列。
46+
47+
```typescript
48+
// 任何整數總和都能被 1 整除,因此無須移除
49+
if (p === 1) {
50+
return 0;
51+
}
52+
```
53+
54+
### Step 2:計算整體總和模 p,並找出需抵銷的目標餘數
55+
56+
以線性方式累計 `nums` 的總和並取模,若結果為 0,代表已可整除,無需移除子陣列。
57+
58+
```typescript
59+
// 使用累加方式計算總和模 p,避免中間值變大
60+
let totalSumModulo = 0;
61+
for (let index = 0; index < length; index++) {
62+
// 維持總和模 p,以保持數值界線
63+
totalSumModulo = (totalSumModulo + nums[index]) % p;
64+
}
65+
66+
// 若總和已可整除,則無須移除任何子陣列
67+
if (totalSumModulo === 0) {
68+
return 0;
69+
}
70+
71+
const targetRemainder = totalSumModulo;
72+
```
73+
74+
### Step 3:建立餘數對應索引的映射,用以尋找可移除區段的起點
75+
76+
使用 Map 記錄「前綴餘數 → 最新出現位置」,並初始化餘數 0 對應到索引 −1,以方便處理從開頭移除的情況。
77+
78+
```typescript
79+
// 儲存前綴餘數對應其最新索引的位置
80+
const remainderIndexMap = new Map<number, number>();
81+
remainderIndexMap.set(0, -1);
82+
83+
let currentPrefixModulo = 0;
84+
let minimumRemovalLength = length;
85+
```
86+
87+
### Step 4:單次掃描陣列,逐步計算前綴和餘數並檢查最短可移除區段
88+
89+
以線性迴圈維持前綴餘數,並利用 Map 查找是否存在可構成符合條件的區段;同時維護最短移除長度。
90+
91+
```typescript
92+
// 單次掃描:持續更新前綴餘數並查找可移除的最短區段
93+
for (let index = 0; index < length; index++) {
94+
// 維持前綴餘數在範圍內
95+
currentPrefixModulo = (currentPrefixModulo + nums[index]) % p;
96+
97+
// 要求前綴餘數滿足 (currentPrefixModulo - previous) % p === targetRemainder
98+
const requiredRemainder =
99+
(currentPrefixModulo - targetRemainder + p) % p;
100+
101+
// 查詢符合條件的先前前綴位置
102+
const previousIndex = remainderIndexMap.get(requiredRemainder);
103+
104+
if (previousIndex !== undefined) {
105+
const candidateLength = index - previousIndex;
106+
107+
// 不允許移除整個陣列
108+
if (
109+
candidateLength < minimumRemovalLength &&
110+
candidateLength < length
111+
) {
112+
// 更新目前找到的最短移除子陣列
113+
minimumRemovalLength = candidateLength;
114+
}
115+
}
116+
117+
// 記錄此餘數最新出現的位置,以縮短未來可能的移除段
118+
remainderIndexMap.set(currentPrefixModulo, index);
119+
}
120+
```
121+
122+
### Step 5:回傳答案,若未找到有效子陣列則回傳 -1
123+
124+
若無任何子段能使結果可整除 `p`,則結果維持初始值,需回傳 −1。
125+
126+
```typescript
127+
// 若未找到任何有效子陣列,則回傳 -1
128+
if (minimumRemovalLength === length) {
129+
return -1;
130+
}
131+
132+
return minimumRemovalLength;
133+
```
134+
135+
## 時間複雜度
136+
137+
- 計算總和模 p 需要線性掃描。
138+
- 主迴圈再次線性掃描陣列。
139+
- Map 查詢與更新皆為均攤常數時間。
140+
- 總時間複雜度為 $O(n)$。
141+
142+
> $O(n)$
143+
144+
## 空間複雜度
145+
146+
- Map 最多儲存 n 個餘數對應索引。
147+
- 其餘使用固定數量變數。
148+
- 總空間複雜度為 $O(n)$。
149+
150+
> $O(n)$
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
function minSubarray(nums: number[], p: number): number {
2+
const length = nums.length;
3+
4+
// Any integer sum is divisible by 1, so no removal is needed
5+
if (p === 1) {
6+
return 0;
7+
}
8+
9+
// Compute total sum modulo p using a running accumulator
10+
let totalSumModulo = 0;
11+
for (let index = 0; index < length; index++) {
12+
// Keep value reduced modulo p to avoid large intermediate numbers
13+
totalSumModulo = (totalSumModulo + nums[index]) % p;
14+
}
15+
16+
// If already divisible by p, we do not need to remove anything
17+
if (totalSumModulo === 0) {
18+
return 0;
19+
}
20+
21+
const targetRemainder = totalSumModulo;
22+
23+
// Map from prefix-sum remainder to the latest index where it appears
24+
const remainderIndexMap = new Map<number, number>();
25+
remainderIndexMap.set(0, -1);
26+
27+
let currentPrefixModulo = 0;
28+
let minimumRemovalLength = length;
29+
30+
// Single pass: maintain prefix modulo and use the map for O(1) lookups
31+
for (let index = 0; index < length; index++) {
32+
// Maintain current prefix modulo to keep values bounded
33+
currentPrefixModulo = (currentPrefixModulo + nums[index]) % p;
34+
35+
// We want a previous prefix such that:
36+
// (currentPrefixModulo - previousPrefixModulo) % p === targetRemainder
37+
const requiredRemainder =
38+
(currentPrefixModulo - targetRemainder + p) % p;
39+
40+
// Query the latest index of the required remainder
41+
const previousIndex = remainderIndexMap.get(requiredRemainder);
42+
43+
if (previousIndex !== undefined) {
44+
const candidateLength = index - previousIndex;
45+
46+
// We are not allowed to remove the whole array
47+
if (
48+
candidateLength < minimumRemovalLength &&
49+
candidateLength < length
50+
) {
51+
// Update the current best answer
52+
minimumRemovalLength = candidateLength;
53+
}
54+
}
55+
56+
// Store the latest index for this remainder to keep candidate windows short
57+
remainderIndexMap.set(currentPrefixModulo, index);
58+
}
59+
60+
// If no valid subarray was found, return -1
61+
if (minimumRemovalLength === length) {
62+
return -1;
63+
}
64+
65+
return minimumRemovalLength;
66+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function minSubarray(nums: number[], p: number): number {
2+
3+
}

0 commit comments

Comments
 (0)