Skip to content

Commit 07bf04f

Browse files
committed
Add: Add 2025/10/22
1 parent a4f32fa commit 07bf04f

File tree

3 files changed

+266
-0
lines changed

3 files changed

+266
-0
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# 3347. Maximum Frequency of an Element After Performing Operations II
2+
3+
You are given an integer array `nums` and two integers `k` and `numOperations`.
4+
5+
You must perform an operation `numOperations` times on `nums`, where in each operation you:
6+
7+
- Select an index `i` that was not selected in any previous operations.
8+
- Add an integer in the range `[-k, k]` to `nums[i]`.
9+
10+
Return the maximum possible frequency of any element in `nums` after performing the operations.
11+
12+
**Constraints:**
13+
14+
- `1 <= nums.length <= 10^5`
15+
- `1 <= nums[i] <= 10^9`
16+
- `0 <= k <= 10^9`
17+
- `0 <= numOperations <= nums.length`
18+
19+
## 基礎思路
20+
21+
本題要我們在一個整數陣列中進行最多 `numOperations` 次操作。每次操作可選擇一個**未被選過的索引** `i`,並將 `nums[i]` 加上一個介於 `[-k, k]` 的整數。最終需找出任意元素能達到的**最高出現頻率**
22+
23+
在思考解法時,我們需要特別注意幾個要點:
24+
25+
- 每次操作只能對**不同索引**進行一次;
26+
- 每個元素的值可在範圍 `[nums[i] - k, nums[i] + k]` 內自由調整;
27+
- 若兩個數值區間有重疊,就可能被調整成相同的數;
28+
- 我們希望透過最多 `numOperations` 次調整,讓某個數值的出現頻率最大化。
29+
30+
為了解決這個問題,可以採取以下策略:
31+
32+
1. **排序後分析鄰近關係**:因為相近的數值較容易透過調整重合,所以先排序以方便使用滑動視窗。
33+
2. **滑動視窗找最大可重疊範圍**:找出在區間長度不超過 `2k` 的最大子集,代表這些元素可被調成同一值。
34+
3. **考慮現有元素為目標值的情況**:對每個不同數值,計算多少數在 `[value - k, value + k]` 範圍內可被轉為該值。
35+
4. **結合兩種情境**
36+
- 一種是任意目標(可自由選目標值);
37+
- 另一種是選用現有元素作為目標;
38+
最後取兩者的最大值作為答案。
39+
40+
## 解題步驟
41+
42+
### Step 1:處理空陣列的特例
43+
44+
若陣列為空,直接回傳 0。
45+
46+
```typescript
47+
// 若陣列為空,無法形成頻率,直接回傳 0
48+
if (nums.length === 0) {
49+
return 0;
50+
}
51+
```
52+
53+
### Step 2:初始化排序陣列
54+
55+
使用 `Int32Array` 儲存並排序,確保運算一致且利於滑動視窗。
56+
57+
```typescript
58+
// 建立型別化陣列以提升數值處理效率,並排序(遞增)
59+
const arr = Int32Array.from(nums);
60+
arr.sort();
61+
62+
const n = arr.length;
63+
```
64+
65+
### Step 3:Case A — 任意目標值(可自由調整成同一區間內)
66+
67+
使用滑動視窗找出最大範圍,使最大值與最小值差不超過 `2k`
68+
這代表所有這些數都可被調整至同一數值。
69+
70+
```typescript
71+
// 使用滑動視窗找出最大範圍 (max - min ≤ 2k)
72+
let leftPointer = 0;
73+
let maxWithinRange = 1;
74+
75+
for (let rightPointer = 0; rightPointer < n; rightPointer++) {
76+
// 若視窗寬度超出 2k,向右收縮左指標
77+
while (arr[rightPointer] - arr[leftPointer] > 2 * k) {
78+
leftPointer += 1;
79+
}
80+
const windowSize = rightPointer - leftPointer + 1;
81+
if (windowSize > maxWithinRange) {
82+
maxWithinRange = windowSize; // 更新最大區間長度
83+
}
84+
}
85+
86+
// 根據操作上限取最小值(不能超過 numOperations)
87+
const bestArbitrary = Math.min(maxWithinRange, numOperations);
88+
```
89+
90+
### Step 4:Case B — 以現有元素作為目標值
91+
92+
逐一考慮每個不同數值 `v`,找出所有可被轉為 `v` 的元素數量。
93+
統計當前值的出現次數、在 `[v - k, v + k]` 範圍內的總元素數,並計算可能轉換數量。
94+
95+
```typescript
96+
// 初始化最佳結果與雙指標
97+
let bestExisting = 1;
98+
let leftBound = 0;
99+
let rightBound = -1;
100+
let startIndex = 0;
101+
102+
// 逐一處理每個不同的數值群組
103+
while (startIndex < n) {
104+
let endIndex = startIndex;
105+
const value = arr[startIndex];
106+
107+
// 找出同值的群組範圍
108+
while (endIndex + 1 < n && arr[endIndex + 1] === value) {
109+
endIndex += 1;
110+
}
111+
112+
// 定義可轉換範圍 [value - k, value + k]
113+
const minAllowed = value - k;
114+
const maxAllowed = value + k;
115+
116+
// 向右移動 leftBound,確保 arr[leftBound] >= minAllowed
117+
while (leftBound < n && arr[leftBound] < minAllowed) {
118+
leftBound += 1;
119+
}
120+
121+
// 擴展 rightBound,直到 arr[rightBound] > maxAllowed
122+
while (rightBound + 1 < n && arr[rightBound + 1] <= maxAllowed) {
123+
rightBound += 1;
124+
}
125+
126+
// 當前值出現次數
127+
const countEqual = endIndex - startIndex + 1;
128+
129+
// 在可轉換範圍內的總元素數
130+
const totalWithin = rightBound >= leftBound ? (rightBound - leftBound + 1) : 0;
131+
132+
// 可被轉換成當前值的數量
133+
const convertible = totalWithin > countEqual ? (totalWithin - countEqual) : 0;
134+
135+
// 計算選此值為目標時可達最大頻率
136+
const candidate = countEqual + Math.min(numOperations, convertible);
137+
if (candidate > bestExisting) {
138+
bestExisting = candidate; // 更新最佳結果
139+
}
140+
141+
// 移動至下一組不同數值
142+
startIndex = endIndex + 1;
143+
}
144+
```
145+
146+
### Step 5:合併兩種情境並回傳最終結果
147+
148+
取兩種策略的最大值,且不得超過陣列長度。
149+
150+
```typescript
151+
// 結合兩種策略結果,並確保不超過 n
152+
const best = Math.max(bestExisting, bestArbitrary);
153+
return best < n ? best : n;
154+
```
155+
156+
## 時間複雜度
157+
158+
- 排序需 $O(n \log n)$;
159+
- Case A 與 Case B 各使用滑動視窗掃描一次,皆為 $O(n)$;
160+
- 總時間複雜度為 $O(n \log n)$。
161+
162+
> $O(n \log n)$
163+
164+
## 空間複雜度
165+
166+
- 使用一份排序陣列與少量指標變數;
167+
- 其餘操作皆為原地運算,額外空間為常數級。
168+
- 總空間複雜度為 $O(n)$(主要來自複製陣列)。
169+
170+
> $O(n)$
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
function maxFrequency(nums: number[], k: number, numOperations: number): number {
2+
// Guard: if array is empty, no frequency possible
3+
if (nums.length === 0) {
4+
return 0;
5+
}
6+
7+
// Use typed array for consistent numeric operations and locality
8+
const arr = Int32Array.from(nums);
9+
arr.sort(); // Numeric sort for ascending order
10+
11+
const n = arr.length;
12+
13+
/**
14+
* Case A — Arbitrary target value
15+
* Find the largest subset with max-min ≤ 2k (all intervals overlap).
16+
* Each element in that subset could be shifted to the same value.
17+
*/
18+
let leftPointer = 0;
19+
let maxWithinRange = 1;
20+
21+
// Sliding window to find the largest interval span ≤ 2k
22+
for (let rightPointer = 0; rightPointer < n; rightPointer++) {
23+
while (arr[rightPointer] - arr[leftPointer] > 2 * k) {
24+
leftPointer += 1; // Shrink window from left until condition fits
25+
}
26+
const windowSize = rightPointer - leftPointer + 1;
27+
if (windowSize > maxWithinRange) {
28+
maxWithinRange = windowSize; // Update largest valid subset
29+
}
30+
}
31+
32+
// If arbitrary target chosen, we can modify at most numOperations elements
33+
const bestArbitrary = Math.min(maxWithinRange, numOperations);
34+
35+
/**
36+
* Case B — Target equals an existing value in the array
37+
* For each distinct value v:
38+
* - Find how many numbers already equal v.
39+
* - Count how many fall inside [v - k, v + k].
40+
* - Compute achievable frequency using numOperations conversions.
41+
*/
42+
let bestExisting = 1;
43+
let leftBound = 0;
44+
let rightBound = -1;
45+
let startIndex = 0;
46+
47+
// Iterate over groups of identical values
48+
while (startIndex < n) {
49+
let endIndex = startIndex;
50+
const value = arr[startIndex];
51+
52+
// Find the range of equal values
53+
while (endIndex + 1 < n && arr[endIndex + 1] === value) {
54+
endIndex += 1;
55+
}
56+
57+
// Compute the allowed numeric range for transformation
58+
const minAllowed = value - k;
59+
const maxAllowed = value + k;
60+
61+
// Move leftBound to maintain arr[leftBound] >= minAllowed
62+
while (leftBound < n && arr[leftBound] < minAllowed) {
63+
leftBound += 1;
64+
}
65+
66+
// Expand rightBound while arr[rightBound] <= maxAllowed
67+
while (rightBound + 1 < n && arr[rightBound + 1] <= maxAllowed) {
68+
rightBound += 1;
69+
}
70+
71+
// Number of existing occurrences of this value
72+
const countEqual = endIndex - startIndex + 1;
73+
74+
// Total elements within transformable range
75+
const totalWithin = rightBound >= leftBound ? (rightBound - leftBound + 1) : 0;
76+
77+
// Elements that could be converted to value
78+
const convertible = totalWithin > countEqual ? (totalWithin - countEqual) : 0;
79+
80+
// Potential frequency if we pick this value as target
81+
const candidate = countEqual + Math.min(numOperations, convertible);
82+
if (candidate > bestExisting) {
83+
bestExisting = candidate; // Update best found so far
84+
}
85+
86+
// Move to the next distinct value group
87+
startIndex = endIndex + 1;
88+
}
89+
90+
// The final result combines both strategies; cannot exceed total elements
91+
const best = Math.max(bestExisting, bestArbitrary);
92+
return best < n ? best : n;
93+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function maxFrequency(nums: number[], k: number, numOperations: number): number {
2+
3+
}

0 commit comments

Comments
 (0)