Skip to content

Commit c77ba0d

Browse files
committed
Add: Add 2025/11/12
1 parent b180a17 commit c77ba0d

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# 2654. Minimum Number of Operations to Make All Array Elements Equal to 1
2+
3+
You are given a 0-indexed array `nums` consisiting of positive integers.
4+
You can do the following operation on the array any number of times:
5+
6+
Select an index `i` such that `0 <= i < n - 1` and replace either of `nums[i]` or `nums[i+1]` with their gcd value.
7+
Return the minimum number of operations to make all elements of `nums` equal to `1`.
8+
If it is impossible, return `-1`.
9+
10+
The gcd of two integers is the greatest common divisor of the two integers.
11+
12+
**Constraints:**
13+
14+
- `2 <= nums.length <= 50`
15+
- `1 <= nums[i] <= 10^6`
16+
17+
## 基礎思路
18+
19+
本題要求以最少操作次數,將整個陣列的元素都變成 `1`。每次操作可選擇相鄰一對 `(nums[i], nums[i+1])`,並把其中一個改成 `gcd(nums[i], nums[i+1])`。思考時抓住幾個關鍵:
20+
21+
- **若陣列中已有若干個 `1`**:把其餘元素「向 `1` 擴散」即可;一個 `1` 可在一次操作把相鄰元素變成 `gcd(1, x) = 1`,因此已有 `k``1` 時,還需要的操作數為 `n - k`
22+
- **若整體 `gcd(nums)` 大於 1**:任何由相鄰 gcd 組合而成的數都會是整體 gcd 的倍數,不可能產生 `1`,答案為 `-1`
23+
- **若整體 `gcd(nums) = 1` 但沒有 `1`**:必定能藉由若干相鄰 gcd 操作先在某個子陣列內「萃出第一個 `1`」。這個成本等於把該子陣列的 gcd 降到 1 所需的步數;對於長度為 `L` 的這段,成本為 `L - 1`。接著再用這個 `1` 擴散到全陣列,需要再做 `n - 1` 次。因此**總成本 =(造出第一個 `1` 的成本)+(把 `1` 擴散到全陣列的成本)**。若我們找的是最短的能達到 gcd=1 的子陣列長度 `L_min`,則答案為 `(L_min - 1) + (n - 1) = L_min + n - 2`
24+
25+
基於以上觀察,解題策略為:
26+
27+
- 先統計陣列中 `1` 的個數,並同時計算整體 `gcd`
28+
- 依序處理三種情況:已有 `1` → 直接計算;整體 gcd > 1 → 回傳 `-1`;否則枚舉所有子陣列,尋找最短 gcd 可達 1 的長度 `L_min`,回傳 `L_min + n - 2`
29+
30+
## 解題步驟
31+
32+
### Step 1:宣告基本變數與輔助函式
33+
34+
宣告陣列長度、`1` 的數量、整體 gcd;並提供輔助函式 `getGcd`(歐幾里得算法)以計算兩數 gcd。
35+
36+
```typescript
37+
const length = nums.length;
38+
let countOfOnes = 0;
39+
let overallGcd = 0;
40+
41+
/**
42+
* 使用歐幾里得演算法計算兩數的最大公因數(GCD)。
43+
*
44+
* @param a - 第一個數
45+
* @param b - 第二個數
46+
* @returns 兩數的最大公因數
47+
*/
48+
function getGcd(a: number, b: number): number {
49+
while (b !== 0) {
50+
const remainder = a % b;
51+
a = b;
52+
b = remainder;
53+
}
54+
return a;
55+
}
56+
```
57+
58+
### Step 2:一次遍歷,統計 `1` 的數量並累積整體 GCD
59+
60+
同一趟掃描中:計數 `1` 的個數;並不斷以 `overallGcd = gcd(overallGcd, value)` 累積整體 gcd。
61+
62+
```typescript
63+
// 統計 1 的數量並計算整體 GCD
64+
for (const value of nums) {
65+
if (value === 1) {
66+
countOfOnes++;
67+
}
68+
overallGcd = getGcd(overallGcd, value);
69+
}
70+
```
71+
72+
### Step 3:根據快速判定分支返回答案(已有 1 / 不可能)
73+
74+
若已有 `1`,答案為把其餘元素都變成 `1` 的操作數 `n - countOfOnes`。若整體 gcd > 1,無法產生 `1`,回傳 `-1`
75+
76+
```typescript
77+
// 情況一:陣列中已存在至少一個 1
78+
if (countOfOnes > 0) {
79+
return length - countOfOnes;
80+
}
81+
82+
// 情況二:整體 GCD > 1,無法把任何數變為 1
83+
if (overallGcd > 1) {
84+
return -1;
85+
}
86+
```
87+
88+
### Step 4:尋找能產生第一個 1 的最短子陣列
89+
90+
當整體 gcd = 1 且沒有 `1` 時,我們需要先在某段子陣列中把 gcd 降到 1。枚舉左端點 `start`,滾動計算 `currentGcd`,一旦等於 1 就更新最短長度並提早結束該左端點的擴張。
91+
92+
```typescript
93+
// 情況三:整體 GCD = 1 且目前沒有 1,需先在某段子陣列內產生第一個 1
94+
let minimalLength = length;
95+
for (let start = 0; start < length; start++) {
96+
let currentGcd = nums[start];
97+
for (let end = start + 1; end < length; end++) {
98+
currentGcd = getGcd(currentGcd, nums[end]);
99+
if (currentGcd === 1) {
100+
const windowLength = end - start + 1;
101+
if (windowLength < minimalLength) {
102+
minimalLength = windowLength;
103+
}
104+
break; // 以此 start,已達 gcd==1,沒必要再延伸更長的 end
105+
}
106+
}
107+
}
108+
```
109+
110+
### Step 5:由最短子陣列長度推回總操作數並回傳
111+
112+
造出第一個 `1` 的成本是 `minimalLength - 1`,再擴散到整陣列需要 `n - 1`。合併為 `minimalLength + n - 2`
113+
114+
```typescript
115+
// 先造出第一個 1(成本 minimalLength - 1),再擴散到全陣列(成本 n - 1)
116+
return minimalLength + length - 2;
117+
```
118+
119+
## 時間複雜度
120+
121+
- 一次遍歷統計與整體 gcd:$O(n \log A)$,其中 $A$ 為數值上限。
122+
- 枚舉所有起點並滾動 gcd 的雙迴圈:最壞 $O(n^2 \log A)$(每次 gcd 計算攤入 $\log A$)。
123+
- 總時間複雜度為 $O(n^2 \log A)$。
124+
125+
> $O(n^2 \log A)$
126+
127+
## 空間複雜度
128+
129+
- 僅使用常數級額外變數(計數、指標與暫存 gcd)。
130+
- 總空間複雜度為 $O(1)$。
131+
132+
> $O(1)$
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
function minOperations(nums: number[]): number {
2+
const length = nums.length;
3+
let countOfOnes = 0;
4+
let overallGcd = 0;
5+
6+
/**
7+
* Compute the greatest common divisor of two numbers using Euclid's algorithm.
8+
*
9+
* @param a - First number
10+
* @param b - Second number
11+
* @returns Greatest common divisor
12+
*/
13+
function getGcd(a: number, b: number): number {
14+
while (b !== 0) {
15+
const remainder = a % b;
16+
a = b;
17+
b = remainder;
18+
}
19+
return a;
20+
}
21+
22+
// Count ones and compute overall gcd
23+
for (const value of nums) {
24+
if (value === 1) {
25+
countOfOnes++;
26+
}
27+
overallGcd = getGcd(overallGcd, value);
28+
}
29+
30+
// Case 1: already contains ones
31+
if (countOfOnes > 0) {
32+
return length - countOfOnes;
33+
}
34+
35+
// Case 2: impossible to reach 1
36+
if (overallGcd > 1) {
37+
return -1;
38+
}
39+
40+
// Case 3: find the shortest subarray that can produce gcd == 1
41+
let minimalLength = length;
42+
for (let start = 0; start < length; start++) {
43+
let currentGcd = nums[start];
44+
for (let end = start + 1; end < length; end++) {
45+
currentGcd = getGcd(currentGcd, nums[end]);
46+
if (currentGcd === 1) {
47+
const windowLength = end - start + 1;
48+
if (windowLength < minimalLength) {
49+
minimalLength = windowLength;
50+
}
51+
break;
52+
}
53+
}
54+
}
55+
56+
// After making one 1, it takes (n - 1) more operations to spread it
57+
return minimalLength + length - 2;
58+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function minOperations(nums: number[]): number {
2+
3+
}

0 commit comments

Comments
 (0)