Skip to content

Commit 2e66ca8

Browse files
committed
Add: Add 2025/11/27
1 parent b8a5576 commit 2e66ca8

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# 3381. Maximum Subarray Sum With Length Divisible by K
2+
3+
You are given an array of integers `nums` and an integer `k`.
4+
5+
Return the maximum sum of a subarray of `nums`, such that the size of the subarray is divisible by `k`.
6+
7+
**Constraints:**
8+
9+
- `1 <= k <= nums.length <= 2 * 10^5`
10+
- `-10^9 <= nums[i] <= 10^9`
11+
12+
## 基礎思路
13+
14+
本題要求從整數陣列 `nums` 中找出一段子陣列,使其長度可以被 `k` 整除,且總和最大。
15+
16+
要達成此目標,必須掌握以下觀察:
17+
18+
- **前綴和將子陣列求和轉成 prefix[r] − prefix[l − 1]**
19+
若子陣列長度 `(r - l + 1)` 可被 `k` 整除,等價於:
20+
**r % k === (l - 1) % k**
21+
22+
- **因此只需將前綴和依照「索引 % k」分類**
23+
若兩個前綴和 `prefix[i]``prefix[j]` 屬於相同餘數類別,則它們能形成合法子陣列。
24+
25+
- **最大子陣列和 ⇔ prefix[i] − min(prefix[j])(同餘數類別)**
26+
對於每個餘數 `r`,只需要維護「最小前綴和」,就能在掃描到下一個同餘數時求出最佳差值。
27+
28+
- **避免在迴圈中使用 expensive 的 `i % k`**
29+
因此使用 `residueIndex` 逐步遞增並在抵達 `k` 時手動歸零,效能更佳。
30+
31+
整體方法為:
32+
維護 `k` 個餘數類別的最小 prefixSum,並在一次線性掃描中完成所有比較,達成最佳 $O(n)$ 時間複雜度。
33+
34+
## 解題步驟
35+
36+
### Step 1:初始化每個餘數類別的最小前綴和
37+
38+
建立 `residueMinimumPrefixSumArray`,長度為 `k`,初始值設為正無限大,
39+
用於記錄每個餘數類別目前觀察到的最小前綴和。
40+
41+
```typescript
42+
const numsLength = nums.length;
43+
44+
// 為每個餘數類別配置最小前綴和空間
45+
const residueMinimumPrefixSumArray = new Float64Array(k);
46+
47+
// 初始化為 +Infinity,使後續前綴和值可以成為最小值
48+
for (let index = 0; index < k; index += 1) {
49+
residueMinimumPrefixSumArray[index] = Number.POSITIVE_INFINITY;
50+
}
51+
```
52+
53+
### Step 2:處理「從 index = 0 開始」的合法子陣列
54+
55+
若子陣列從 0 開始,其長度要能被 k 整除,則前綴和「前一個位置」視為 0。
56+
該值的餘數類別為 `k - 1`
57+
58+
```typescript
59+
// 處理從索引 0 開始、長度可被 k 整除的子陣列
60+
residueMinimumPrefixSumArray[k - 1] = 0;
61+
```
62+
63+
### Step 3:準備前綴和、答案與 residueIndex
64+
65+
設定 `prefixSum``maximumSum``residueIndex`
66+
並避免在迴圈中使用 costly 的 `% k` 運算。
67+
68+
```typescript
69+
// 初始最大值以強負值表示
70+
const negativeMaxSafeInteger = -Number.MAX_SAFE_INTEGER;
71+
72+
let prefixSum = 0;
73+
let maximumSum = negativeMaxSafeInteger;
74+
75+
// 手動遞增 residueIndex 取代 index % k
76+
let residueIndex = 0;
77+
```
78+
79+
### Step 4:一次掃描陣列,維護最小前綴和並更新最大子陣列答案
80+
81+
使用一次 `for` 迴圈,同時完成:
82+
83+
- 更新前綴和
84+
- 用同餘數類別的最小前綴和形成候選值
85+
- 更新全域最大值
86+
- 更新該餘數類別最小前綴和
87+
- `residueIndex` 遞增並包回
88+
89+
```typescript
90+
// 單次掃描陣列以求最大總和
91+
for (let index = 0; index < numsLength; index += 1) {
92+
// 更新前綴和
93+
prefixSum += nums[index];
94+
95+
// 使用相同餘數類別的最小前綴和形成候選值
96+
const candidateSum = prefixSum - residueMinimumPrefixSumArray[residueIndex];
97+
98+
// 更新最大子陣列總和(若更大)
99+
if (candidateSum > maximumSum) {
100+
maximumSum = candidateSum;
101+
}
102+
103+
// 維護該餘數類別最小前綴和
104+
if (prefixSum < residueMinimumPrefixSumArray[residueIndex]) {
105+
residueMinimumPrefixSumArray[residueIndex] = prefixSum;
106+
}
107+
108+
// 更新 residueIndex(避免使用 modulo)
109+
residueIndex += 1;
110+
if (residueIndex === k) {
111+
residueIndex = 0;
112+
}
113+
}
114+
```
115+
116+
### Step 5:回傳最大子陣列總和
117+
118+
```typescript
119+
return maximumSum;
120+
```
121+
122+
## 時間複雜度
123+
124+
- 使用單次線性掃描,每一步均為 $O(1)$;
125+
- 總時間複雜度為 $O(n)$。
126+
127+
> $O(n)$
128+
129+
## 空間複雜度
130+
131+
- 使用一個長度為 `k` 的陣列儲存最小前綴和;
132+
- 其餘皆為常數空間;
133+
- 總空間複雜度為 $O(k)$。
134+
135+
> $O(k)$
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
function maxSubarraySum(nums: number[], k: number): number {
2+
const numsLength = nums.length;
3+
4+
// Pre-allocate typed array for minimum prefix sum per residue class
5+
const residueMinimumPrefixSumArray = new Float64Array(k);
6+
7+
// Initialize all residues to +Infinity so the first real prefixSum becomes the minimum
8+
for (let index = 0; index < k; index += 1) {
9+
residueMinimumPrefixSumArray[index] = Number.POSITIVE_INFINITY;
10+
}
11+
12+
// Handle subarrays starting from index 0 with length divisible by k
13+
residueMinimumPrefixSumArray[k - 1] = 0;
14+
15+
// Use a strong negative initial value for maximum sum
16+
const negativeMaxSafeInteger = -Number.MAX_SAFE_INTEGER;
17+
18+
let prefixSum = 0;
19+
let maximumSum = negativeMaxSafeInteger;
20+
21+
// Track residue index without using modulo in the loop
22+
let residueIndex = 0;
23+
24+
// Single pass over the array to compute the best subarray sum
25+
for (let index = 0; index < numsLength; index += 1) {
26+
// Update prefix sum with current value
27+
prefixSum += nums[index];
28+
29+
// Compute candidate using best (minimum) prefixSum for this residue
30+
const candidateSum = prefixSum - residueMinimumPrefixSumArray[residueIndex];
31+
32+
// Update global maximum if current candidate is better
33+
if (candidateSum > maximumSum) {
34+
maximumSum = candidateSum;
35+
}
36+
37+
// Maintain minimum prefix sum for this residue class
38+
if (prefixSum < residueMinimumPrefixSumArray[residueIndex]) {
39+
residueMinimumPrefixSumArray[residueIndex] = prefixSum;
40+
}
41+
42+
// Move residue index forward and wrap around without modulo
43+
residueIndex += 1;
44+
if (residueIndex === k) {
45+
residueIndex = 0;
46+
}
47+
}
48+
49+
return maximumSum;
50+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function maxSubarraySum(nums: number[], k: number): number {
2+
3+
}

0 commit comments

Comments
 (0)