Skip to content

Commit c8902d8

Browse files
committed
Add: Add 2025/9/16
1 parent 068db21 commit c8902d8

File tree

3 files changed

+190
-0
lines changed

3 files changed

+190
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# 2197. Replace Non-Coprime Numbers in Array
2+
3+
You are given an array of integers `nums`.
4+
Perform the following steps:
5+
6+
1. Find any two adjacent numbers in `nums` that are non-coprime.
7+
2. If no such numbers are found, stop the process.
8+
3. Otherwise, delete the two numbers and replace them with their LCM (Least Common Multiple).
9+
4. Repeat this process as long as you keep finding two adjacent non-coprime numbers.
10+
11+
Return the final modified array.
12+
It can be shown that replacing adjacent non-coprime numbers in any arbitrary order will lead to the same result.
13+
14+
The test cases are generated such that the values in the final array are less than or equal to `10^8`.
15+
16+
Two values `x` and `y` are non-coprime if `GCD(x, y) > 1` where `GCD(x, y)` is the Greatest Common Divisor of `x` and `y`.
17+
18+
**Constraints:**
19+
20+
- `1 <= nums.length <= 10^5`
21+
- `1 <= nums[i] <= 10^5`
22+
- The test cases are generated such that the values in the final array are less than or equal to `10^8`.
23+
24+
## 基礎思路
25+
26+
我們需要反覆把相鄰且「不互質」的數合併為它們的最小公倍數,直到整列中再也找不到可合併的一對。
27+
關鍵觀察是:合併的最終結果與合併順序無關,因此可以用「單調堆疊」的思維從左到右處理:
28+
29+
- 依序把元素推入堆疊,並在每次推入前,檢查「堆疊頂端」與當前值是否不互質。
30+
- 只要頂端與當前值不互質,就把這兩個數以最小公倍數合併,彈出頂端,並用合併後的新值再與新的頂端比較,持續合併到兩者變成互質或堆疊為空為止。
31+
- 這樣能局部消除可能的連鎖合併(例如 `a``b` 合併後的新值可能又與先前更左邊的數不互質),確保一次掃描即可得到最終答案。
32+
33+
最小公倍數使用公式 $\mathrm{LCM}(a, b) = \left( \frac{a}{\gcd(a, b)} \right) \times b$,以避免中途溢位風險。由於每個元素最多被推入與彈出各一次,整體是線性的。
34+
35+
## 解題步驟
36+
37+
### Step 1:預先配置堆疊並實作 GCD(歐幾里得演算法)
38+
39+
先建立固定大小的堆疊(使用 TypedArray 降低動態配置成本),並提供一個無配置、迭代版本的 GCD。
40+
41+
```typescript
42+
const length = nums.length;
43+
44+
// 預先配置固定大小(TypedArray)的堆疊,降低動態開銷。
45+
// 題目保證結果值不超過 1e8,32 位元整數足夠儲存。
46+
const stack = new Uint32Array(length);
47+
let stackPointer = -1; // 堆疊當前頂端的索引。
48+
49+
/**
50+
* 使用迭代版歐幾里得演算法計算最大公因數(快速、無配置)。
51+
*
52+
* @param {number} a - 第一個非負整數。
53+
* @param {number} b - 第二個非負整數。
54+
* @returns {number} - a 與 b 的最大公因數。
55+
*/
56+
function computeGreatestCommonDivisor(a: number, b: number): number {
57+
while (b !== 0) {
58+
const remainder = a % b;
59+
a = b;
60+
b = remainder;
61+
}
62+
return a;
63+
}
64+
```
65+
66+
### Step 2:線性掃描並與堆疊頂端連鎖合併
67+
68+
逐一處理 `nums` 中的元素:每讀到一個值,就嘗試與堆疊頂端合併,只要仍不互質就繼續合併,直到變成互質或堆疊清空,最後把合併後的值推回堆疊。
69+
70+
```typescript
71+
for (let i = 0; i < length; i++) {
72+
// 以無號 32 位元形式存入堆疊(型別相容)。
73+
let currentValue = nums[i] >>> 0;
74+
75+
// 只要與堆疊頂端不互質,就持續合併。
76+
while (stackPointer >= 0) {
77+
const previousValue = stack[stackPointer];
78+
const divisor = computeGreatestCommonDivisor(previousValue, currentValue);
79+
80+
if (divisor === 1) {
81+
// 若互質,停止合併。
82+
break;
83+
}
84+
85+
// 以 LCM 取代兩數:(a / gcd) * b 可降低溢位風險。
86+
currentValue = Math.trunc((previousValue / divisor) * currentValue);
87+
88+
// 頂端元素已合併,彈出。
89+
stackPointer--;
90+
}
91+
92+
// 將合併後的值推入堆疊。
93+
stack[++stackPointer] = currentValue;
94+
}
95+
```
96+
97+
### Step 3:輸出堆疊有效區段即為最終陣列
98+
99+
處理完畢後,堆疊中的有效部分(自 0 至頂端索引)就是最後結果,拷貝到一般陣列回傳。
100+
101+
```typescript
102+
// 複製堆疊有效部分作為回傳結果。
103+
const resultLength = stackPointer + 1;
104+
const result: number[] = new Array(resultLength);
105+
for (let i = 0; i < resultLength; i++) {
106+
result[i] = stack[i];
107+
}
108+
109+
return result;
110+
```
111+
112+
## 時間複雜度
113+
114+
- 每個元素最多被推入與彈出堆疊各一次;合併過程的連鎖也只會使已有元素被再次檢查後彈出,不會重複無限次。
115+
- GCD 與 LCM 計算皆為常數均攤成本(相對於整體線性掃描)。
116+
- 總時間複雜度為 $O(n)$。
117+
118+
> $O(n)$
119+
120+
## 空間複雜度
121+
122+
- 使用一個與輸入等長的堆疊作為工作空間,外加常數個臨時變數。
123+
- 結果以新陣列輸出,其長度不超過輸入長度。
124+
- 總空間複雜度為 $O(n)$。
125+
126+
> $O(n)$
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
function replaceNonCoprimes(nums: number[]): number[] {
2+
const length = nums.length;
3+
4+
// Preallocate stack with fixed size (typed array) to minimize dynamic overhead.
5+
// Values are guaranteed to fit in 32 bits (<= 1e8).
6+
const stack = new Uint32Array(length);
7+
let stackPointer = -1; // Index of the current top element in the stack.
8+
9+
/**
10+
* Compute the Greatest Common Divisor (GCD) using
11+
* the iterative Euclidean algorithm (fast and allocation-free).
12+
*
13+
* @param {number} a - First non-negative integer.
14+
* @param {number} b - Second non-negative integer.
15+
* @returns {number} - The GCD of a and b.
16+
*/
17+
function computeGreatestCommonDivisor(a: number, b: number): number {
18+
while (b !== 0) {
19+
const remainder = a % b;
20+
a = b;
21+
b = remainder;
22+
}
23+
return a;
24+
}
25+
26+
for (let i = 0; i < length; i++) {
27+
// Coerce to unsigned 32-bit integer for stack storage.
28+
let currentValue = nums[i] >>> 0;
29+
30+
// Continuously merge with the stack's top element
31+
// as long as the two numbers are non-coprime.
32+
while (stackPointer >= 0) {
33+
const previousValue = stack[stackPointer];
34+
const divisor = computeGreatestCommonDivisor(previousValue, currentValue);
35+
36+
if (divisor === 1) {
37+
// Numbers are coprime, stop merging.
38+
break;
39+
}
40+
41+
// Replace the two numbers with their LCM.
42+
// Use (a / gcd) * b to minimize overflow risk.
43+
currentValue = Math.trunc((previousValue / divisor) * currentValue);
44+
45+
// Pop the stack top since it is merged.
46+
stackPointer--;
47+
}
48+
49+
// Push the current merged value onto the stack.
50+
stack[++stackPointer] = currentValue;
51+
}
52+
53+
// Copy the valid portion of the stack into the result array.
54+
const resultLength = stackPointer + 1;
55+
const result: number[] = new Array(resultLength);
56+
for (let i = 0; i < resultLength; i++) {
57+
result[i] = stack[i];
58+
}
59+
60+
return result;
61+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function replaceNonCoprimes(nums: number[]): number[] {
2+
3+
}

0 commit comments

Comments
 (0)