2025年9月29日 星期一

凸多邊形的三角形分割以及二維陣列與整數最大值的使用

今天練習的題目是 1039. Minimum Score Triangulation of Polygon,第一次遇到這種 DP 類型,後來想通後,發現解題辦法不外乎兩種。

一種是由上而下,透過記憶跟遞迴來處理,效率較差,寫成 C++ 會像是這樣:

class Solution {
public:
    int minScoreTriangulation(vector<int>& values) {
        int n = values.size();
        array<array<int, 50>, 50> memo{};
        function<int(int, int)> dp = [&](int i, int j) -> int {
        	if (i + 2 > j)
                return 0;
            if (!memo[i][j]) {
                if (i + 2 == j)
                    return memo[i][j] =
                               values[i] * values[i + 1] * values[i + 2];
                int score = numeric_limits<int>::max();
                int product = values[i] * values[j];
                for (int k = i + 1; k < j; ++k)
                    score =
                        min(score, product * values[k] + dp(i, k) + dp(k, j));
                memo[i][j] = score;
            }
            return memo[i][j];
        };
        return dp(0, n - 1);
    }
};

另一種是由下而上,透過疊代來處理,效率較好,寫成 C++ 會像是這樣:

class Solution {
public:
    int minScoreTriangulation(vector<int>& values) {
    	int n = values.size();
        array<array<int, 50>, 50> dp{};
        for (int d = 2; d < n; ++d) {
            for (int i = 0; i + d < n; ++i) {
                int j = i + d;
                int score = numeric_limits<int>::max();
                int product = values[i] * values[j];
                for (int k = i + 1; k < j; ++k)
                    score =
                        min(score, product * values[k] + dp[i][k] + dp[k][j]);
                dp[i][j] = score;
            }
        }
        return dp[0][n - 1];
    }
};

用疊代的方法寫成 Python3 會像這樣:

class Solution:
    def minScoreTriangulation(self, values: List[int]) -> int:
        n = len(values)
        dp = [[0 for _ in range(n)] for _ in range(n)]
        for d in range(2, n):
            for i in range(n - d):
                j = i + d
                score = float("inf")
                product = values[i] * values[j]
                for k in range(i + 1, j):
                    score = min(score, product * values[k] + dp[i][k] + dp[k][j])
                dp[i][j] = score
        return dp[0][n - 1]

用疊代的方法寫成 Go 會像這樣:

func minScoreTriangulation(values []int) int {
	n := len(values)
	dp := make([][]int, n)
	for i := range n {
		dp[i] = make([]int, n)
	}
	for d := 2; d < n; d++ {
		for i := range n - d {
			j := i + d
			score := math.MaxInt
			product := values[i] * values[j]
			for k := i + 1; k < j; k++ {
				score = min(score, product*values[k]+dp[i][k]+dp[k][j])
			}
			dp[i][j] = score
		}
	}
	return dp[0][n-1]
}

用疊代的方法寫成 Rust 會像這樣:

impl Solution {
    pub fn min_score_triangulation(values: Vec<i32>) -> i32 {
    	let n = values.len();
        let mut dp = vec![vec![0; n]; n];
        for d in 2..n {
            for i in 0..(n - d) {
                let j = i + d;
                let mut score = i32::MAX;
                let mut product = values[i] * values[j];
                for k in (i + 1)..j {
                    score = score.min(product*values[k]+dp[i][k]+dp[k][j]);
                }
                dp[i][j] = score;
            }
        }
        dp[0][n-1]
    }
}

另外值得一提的是 Rust 目前 1.90.0 還沒有支援 nested recursive function call,雖然上面沒有全部寫出來,但是 C++, Python3, Go 都有支援,如果這題要用 Rust 寫記憶跟遞迴會蠻麻煩的,反正效率也比較差,就寫個 C++ 意思意思一下就好了。

2025年9月28日 星期日

Heapify

今日練習的題目是 976. Largest Perimeter Triangle,官方的解答是直接排序後,從後面開始找出第一個合法的三角形周長就可以了,不過還有另一種解法就是利用 Heapify,雖然最糟的情況下演算法效能會跟直接排序法差不多,但是最佳狀況會比排序法好,但是現實上也可能會因為排序法使用連續的記憶體也可能會效率更好一點,所以還是要看資料本身適合哪一種。

如果是使用 Python3 的話,因為預設是 Min Heap 所以會需要使用點小技巧來使用成 Max Heap 的樣子。

class Solution:
    def largestPerimeter(self, nums: List[int]) -> int:
        nums = [-num for num in nums]
        heapq.heapify(nums)
        a = -heapq.heappop(nums)
        b = -heapq.heappop(nums)
        while len(nums) > 0:
            c = -heapq.heappop(nums)
            if b + c > a:
            	return a + b + c
            a, b = b, c
        return 0

如果是使用 C++ 的話,由於預設是 Max Heap,所以直接倒進去 priority_queue 使用就可以了。

class Solution {
public:
    int largestPerimeter(vector<int>& nums) {
    	priority_queue<int> pq(nums.begin(), nums.end());
        int a = pq.top();
        pq.pop();
        int b = pq.top();
        pq.pop();
        while (!pq.empty()) {
            int c = pq.top();
            if (b + c &gt; a)
            	return a + b + c;
            pq.pop();
            swap(a, b);
            swap(b, c);
        }
        return 0;
    }
};

如果是使用 Go 的話,要自己刻一些東西搭配 container/heap 使用,使用起來相當麻煩,不然就是要使用第三方套件才會比較輕鬆些。

import "container/heap"

type IntHeap []int

func (h IntHeap) Len() int { return len(h) }

func (h IntHeap) Less(i, j int) bool { return h[i] &gt; h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

func (h *IntHeap) Push(x any) {
	*h = append(*h, x.(int))
}

func (h *IntHeap) Pop() any {
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[0 : n-1]
	return x
}

func largestPerimeter(nums []int) int {
	if len(nums) &lt; 3 {
    	return 0
	}

	h := IntHeap(nums)
	heap.Init(&amp;h)
	for h.Len() &gt;= 3 {
    	a := heap.Pop(&amp;h).(int)
		b := h[0] 
		c := h[1]

        if h.Len() &gt; 2 &amp;&amp; h[2] &gt; c {
        	c = h[2]
        }

		if b+c &gt; a {
        	return a + b + c
		}
	}

	return 0
}

如果是使用 Rust 的話,就需要使用到 std::collections::BinaryHeap,預設就是 Max Heap,所以使用上也算是簡單。


use std::collections::BinaryHeap;

impl Solution {
    pub fn largest_perimeter(nums: Vec&lt;i32&gt;) -&gt; i32 {
        let mut heap = BinaryHeap::from(nums);

        while heap.len() &gt;= 3 {
            let a = heap.pop().unwrap();
            let b = heap.pop().unwrap();
            let c = heap.pop().unwrap();

            if b + c &gt; a {
                return a + b + c;
            } else {
                heap.push(b);
                heap.push(c);
            }
        }

        0
    }
}

不過 Rust 寫成排序的方法,寫起來會比較漂亮跟簡潔,充滿著滿滿的 Rust 程式碼正統風格。

impl Solution {
    pub fn largest_perimeter(mut nums: Vec&lt;i32&gt;) -&gt; i32 {
        nums.sort_unstable();
        nums.windows(3)
            .rev()
            .find_map(|window| {
                if window[0] + window[1] &gt; window[2] {
                    Some(window[0] + window[1] + window[2])
                } else {
                    None
                }
            })
            .unwrap_or(0)
    }
}

程式碼加速魔法:尋找最大面積三角形的三種策略

***此篇文章由 Gemini AI 產生*** 在電腦程式設計與幾何學的領域裡,尋找平面點集中最大面積三角形是個經典問題。當我們手上的點的數量 $N$ 越來越多時,要怎麼從最初的 $O(N^3)$ 暴力解法加速呢? 這篇文章將帶你深入了解三種不同的解法:從**最直覺的暴力嘗試**,到善用**幾何特性來加速**,最後是挑戰理論極限的**旋轉卡尺法**。我們將提供完整的 Python 程式碼實作,並分析它們在不同情境下的優缺點。 ----- ## 輔助工具:面積計算 不論用哪種方法,我們都需要一個快速計算三角形面積的工具。這裡我們採用行列式形式的**鞋帶公式 (Shoelace Formula)**,它可以算出兩倍的三角形面積(可能帶有正負號)。 $$\text{Area} = \frac{1}{2} \left| x_1(y_2 - y_3) + x_2(y_3 - y_1) + x_3(y_1 - y_2) \right|$$ 在所有實作中,我們都將重複使用這個核心函式: ```python from typing import List def get_signed_double_area(p1: List[int], p2: List[int], p3: List[int]) -> float: """計算由 p1, p2, p3 三點組成的三角形,有正負號的兩倍面積。""" # 這就是行列式 (determinant) 的計算 return ( p1[0] * (p2[1] - p3[1]) + p2[0] * (p3[1] - p1[1]) + p3[0] * (p1[1] - p2[1]) ) ``` ----- ## 方法一:暴力窮舉 (Brute Force) - $O(N^3)$ 這是最簡單、最容易寫的方法。 ### 核心原理 我們只需要找出所有 $\binom{N}{3}$ 種**三個點的組合**,計算每一個三角形的面積,然後挑出最大的那個。由於它的邏輯非常簡單,**常數因子極小**,所以在點的數量 $N$ 不大的時候(例如 $N \le 50$),這個方法**跑起來通常是最快的**。 ### 程式碼實作 ```python class Solution_O_N3: def largestTriangleArea(self, points: List[List[int]]) -> float: n = len(points) ans = 0.0 # 三層迴圈遍歷所有可能的 i < j < k 組合 for i in range(n): for j in range(i + 1, n): for k in range(j + 1, n): p1 = points[i] p2 = points[j] p3 = points[k] # 計算兩倍面積 (行列式) determinant = get_signed_double_area(p1, p2, p3) # 面積 = abs(行列式) / 2 ans = max(ans, abs(determinant) / 2.0) return ans ``` ----- ## 方法二:凸包 (Convex Hull) + $O(M^3)$ 檢查 - $O(N \log N + M^3)$ 這個方法開始運用幾何上的小技巧。它利用了這個很重要的特性: > **最大面積三角形的三個頂點,一定都在點集合的** **凸包 (Convex Hull)** **上。** ### 核心原理 1. **計算凸包 (O(N log N)):** 先用 **Monotone Chain** 等演算法找出凸包 $H$。 2. **縮小檢查範圍:** 假設凸包上有 $M$ 個點,我們只需要檢查這 $M$ 個點之間的 $\binom{M}{3}$ 種組合。 3. **暴力檢查 (O(M^3)):** 在凸包點上執行最可靠的三層迴圈。 如果你的點很多 ($N$ 很大),但它們大多擠在中間,只有少數幾個點在邊緣 ($M$ 很小),這個方法就會有非常顯著的加速效果。 ### 程式碼實作 ```python class Solution_ConvexHull_O_M3: # Monotone Chain 凸包演算法 def convex_hull(self, points: List[List[int]]) -> List[List[int]]: # 1. 排序 O(N log N) points.sort() if len(points) <= 2: return points # 2. 構造上凸包 (upper) upper = [] for p in points: # 移除造成順時針轉向或共線的中間點 (<= 0) while len(upper) >= 2 and get_signed_double_area(upper[-2], upper[-1], p) <= 0: upper.pop() upper.append(p) # 3. 構造下凸包 (lower) lower = [] for p in reversed(points): # 移除造成順時針轉向或共線的中間點 (<= 0) while len(lower) >= 2 and get_signed_double_area(lower[-2], lower[-1], p) <= 0: lower.pop() lower.append(p) # 4. 結合上下凸包,去除頭尾重複點 return upper[:-1] + lower[:-1] def largestTriangleArea(self, points: List[List[int]]) -> float: # 步驟 1: 計算凸包 O(N log N) hull_points = self.convex_hull(points) M = len(hull_points) if M < 3: return 0.0 ans = 0.0 # 步驟 2: 僅在凸包點上進行 O(M^3) 檢查 for i in range(M): for j in range(i + 1, M): for k in range(j + 1, M): p1 = hull_points[i] p2 = hull_points[j] p3 = hull_points[k] determinant = get_signed_double_area(p1, p2, p3) ans = max(ans, abs(determinant) / 2.0) return ans ``` ----- ## 方法三:凸包 + 旋轉卡尺 (Rotating Calipers) - $O(N \log N)$ 這是挑戰演算法理論極限的方法。它將凸包上的檢查從 $O(M^3)$ 降低到驚人的 $O(M)$。 ### 核心原理:單調性 旋轉卡尺利用了幾何上的**單調性**:當我們固定一條基底邊 $\overline{p_i p_j}$,離它最遠的第三個點 $p_k$ 會隨著 $p_j$ 的移動而**順序移動**。 因此,我們不用三層迴圈,而是讓三個指針 $i, j, k$ **同步沿著凸包前進**。在整個演算法過程中,每個指針都只會繞凸包一圈,讓總複雜度降到 $O(M)$。 ### 程式碼實作 ```python class Solution_RotatingCaliper_O_NlogN: # (convex_hull 函式和方法二相同,這裡為保持簡潔省略) def convex_hull(self, points: List[List[int]]) -> List[List[int]]: # ... (請參考方法二的實作) ... points.sort() if len(points) <= 2: return points upper = [] for p in points: while len(upper) >= 2 and get_signed_double_area(upper[-2], upper[-1], p) <= 0: upper.pop() upper.append(p) lower = [] for p in reversed(points): while len(lower) >= 2 and get_signed_double_area(lower[-2], lower[-1], p) <= 0: lower.pop() lower.append(p) return upper[:-1] + lower[:-1] def largestTriangleArea(self, points: List[List[int]]) -> float: hull_points = self.convex_hull(points) M = len(hull_points) if M < 3: return 0.0 ans = 0.0 i = 0 j = 1 k = 2 # O(M) 主迴圈,i 走一圈 while i < M: p_i = hull_points[i % M] # 內層迴圈優化 j 和 k 點的單調移動 (總移動次數 O(M)) while True: p_j = hull_points[j % M] # 優化 k 點:找到離 p_i p_j 基底最遠的點 p_k while True: p_k = hull_points[k % M] p_k_next = hull_points[(k + 1) % M] # 比較 p_k 和 p_{k+1} 誰離 p_i p_j 更遠 area_k = abs(get_signed_double_area(p_i, p_j, p_k)) area_k_next = abs(get_signed_double_area(p_i, p_j, p_k_next)) if area_k_next > area_k: k = (k + 1) % M # k 點前進 else: break # p_k 是最遠點 # 更新最大面積 ans = max(ans, area_k / 2.0) # 優化 j 點:檢查 p_{j+1} 是否能形成更大的三角形 p_j_next = hull_points[(j + 1) % M] area_j_next = abs(get_signed_double_area(p_i, p_j_next, p_k)) if area_j_next > area_k: # area_k 是 $\Delta p_i p_j p_k$ 的面積 j = (j + 1) % M # j 點前進 else: break # j 點停止 # 移動 i 點 i += 1 # 確保指針順序 (避免 j 或 k 追上 i) if j == i: j = (j + 1) % M if k == j: k = (k + 1) % M return ans ``` ----- ## 總結:理論與實戰的取捨 實測結果是很棒的經驗!它告訴我們:**理論上複雜度低,不代表實際上跑得快**。 | 策略 | 複雜度 | 優點 | 實用建議 | | :--- | :--- | :--- | :--- | | **方法一** | $O(N^3)$ | 程式碼最簡單,**常數開銷最小**。 | **N 較小** ($N \le 50$) 時,這是最快的選擇。 | | **方法二** | $O(N \log N + M^3)$ | 利用幾何性質,且 $M^3$ 檢查較為**穩健可靠**。 | **N 很大**,但 $M$ 預期較小時。 | | **方法三** | $O(N \log N)$ | **漸進複雜度最低**。 | **N 極大** ($N \ge 10000$) 時的最終優化方案。 | 這三種方法各有所長,選擇哪個,完全取決於你的專案中對點的數量 ($N$) 和程式**穩健性**的要求!

2025年9月25日 星期四

字串串接

今天練習的 LeetCode 題目是 166. Fraction to Recurring Decimal,解題的思路是使用 Hash Map 去記錄小數點後面每一位遇到的餘數的位置,如果遇到記錄過的餘數就能使用記錄的位置將括號插入,但是這個題目也是在考驗如何用程式語言來串接字串。

如果是使用 Python3 來寫的話最簡單,不用考慮整數溢位的問題, str 的串接也相當直覺容易。

class Solution:
    def fractionToDecimal(self, numerator: int, denominator: int) -&gt; str:
        if numerator == 0:
            return "0"

        num = ""

        if (numerator &lt; 0) != (denominator &lt; 0):
        	num += "-"

        n = abs(numerator)
        d = abs(denominator)

        num += str(n // d)
        r = n % d

        if r == 0:
            return num

        num += "."

        pos = {}
        while r != 0:
            if r in pos:
                num = num[0 : pos[r]] + "(" + num[pos[r] :]
                num += ")"
                return num
            pos[r] = len(num)
            r *= 10
            num += str(r // d)
            r %= d

        return num

如果是用 C++ 的話,就要小心整數溢位的問題,不過 string 的串接也還算簡單容易上手。

class Solution {
public:
    string fractionToDecimal(int numerator, int denominator) {
        if (numerator == 0)
            return "0";

        string num;

        if ((numerator &lt; 0) != (denominator &lt; 0))
            num += "-";

        long long n = abs((long long)numerator);
        long long d = abs((long long)denominator);

        num += to_string(n / d);
        long long r = n % d;

        if (r == 0)
            return num;

        num += ".";

        unordered_map&lt;long long, int&gt; pos;
        while (r != 0) {
            if (pos.contains(r)) {
                num.insert(pos[r], "(");
                num += ")";
                break;
            }
            pos[r] = num.length();
            r *= 10;
            num += to_string(r / d);
            r %= d;
        }

        return num;
    }
};

如果是用 Go 的話,也要小心整數溢位的問題,不過 string 的串接最好使用 strings.Builder 比較有效率,要用 strconv.FormatInt 來轉換小數點後面的數字,比較有點小麻煩的是要準備 abs 函式,因為 Go 沒有提供!什麼這麼基本的東西居然沒提供!

func abs(x int64) int64 {
	if x &lt; 0 {
    	return -x
	}
	return x
}

func fractionToDecimal(numerator int, denominator int) string {
	if numerator == 0 {
		return "0"
	}

	var builder strings.Builder

	if (numerator &lt; 0) != (denominator &lt; 0) {
    	builder.WriteString("-")
	}

	n := abs(int64(numerator))
	d := abs(int64(denominator))

	builder.WriteString(strconv.FormatInt(n/d, 10))
	r := n % d

	if r == 0 {
		return builder.String()
	}

	builder.WriteString(".")

	rMap := make(map[int64]int)
	for r != 0 {
		if pos, ok := rMap[r]; ok {
			result := builder.String()
			return result[:pos] + "(" + result[pos:] + ")"
		}

		rMap[r] = builder.Len()

		r *= 10

		builder.WriteString(strconv.FormatInt(r/d, 10))

		r %= d
	}

	return builder.String()
}

如果是用 Rust 的話,就要懂得使用 String::new() 跟 push_str() 還有 push() 的方法,還有如何使用 char::from_digit() 來轉換字元,當然也需要注意整數溢位的問題。

use std::collections::HashMap;

impl Solution {
    pub fn fraction_to_decimal(numerator: i32, denominator: i32) -> String {
        if numerator == 0 {
            return "0".to_string();
        }

        let mut result = String::new();

        if (numerator &lt; 0) != (denominator &lt; 0) {
        	result.push('-');
        }

        let n = (numerator as i64).abs();
        let d = (denominator as i64).abs();

        result.push_str(&amp;(n / d).to_string());
        let mut remainder = n % d;

        if remainder == 0 {
            return result;
        }

        result.push('.');

        let mut remainder_map = HashMap::&lt;i64, usize&gt;::new();
        while remainder != 0 {
            if let Some(&amp;pos) = remainder_map.get(&amp;remainder) {
            	result.insert(pos, '(');
                result.push(')');
                break;
            }

            remainder_map.insert(remainder, result.len());

            remainder *= 10;

            if let Some(digit_char) = char::from_digit((remainder / d) as u32, 10) {
                result.push(digit_char);
            }
            
            remainder %= d;
        }

        result
    }
}

最後是 Go 跟 Rust 檢查餘數是否存在於 Hash Map 中的方式,比起 C++ 跟 Python3 來說比較特別,展現了各自程式語言本身的設計哲學。

2025年9月23日 星期二

字串分割

今日的 165. Compare Version Numbers Solved 使用 Python3 五分鐘就可以解掉了,主要的思考套路是設法弄出兩個等長的整數陣列來比較就可以了。

class Solution:
    def compareVersion(self, version1: str, version2: str) -> int:
        ver1 = [int(x) for x in version1.split(".")]
        ver2 = [int(x) for x in version2.split(".")]
        for x, y in zip_longest(ver1, ver2, fillvalue=0):
            if x < y:
                return -1
            elif x > y:
                return 1
        return 0

如果要用 C++ 就會有點囉唆,要使用 stringstream 搭配 getline 才行,不是那麼直覺,之後再用 resize 調整成一樣的長度來比較。

class Solution {
    vector&lt;int&gt; parse(string version) {
    	string num;
        stringstream ss(version);
        vector&lt;int&gt; ver;
        while (getline(ss, num, '.'))
            ver.emplace_back(stoi(num));
        return ver;
    }

public:
    int compareVersion(string version1, string version2) {
        auto ver1 = parse(version1);
        auto ver2 = parse(version2);
        int len = max(ver1.size(), ver2.size());
        ver1.resize(len, 0);
        ver2.resize(len, 0);

        for (int i = 0; i &lt; len; ++i) {
        	if (ver1[i] &lt; ver2[i])
            	return -1;
            else if (ver1[i] &lt; ver2[i])
            	return 1;
        }

        return 0;
    }
};

如果是 Go 要熟悉一下內建的 strings.Split 以及 strconv.Atoi

func compareVersion(version1 string, version2 string) int {
	str1 := strings.Split(version1, ".")
	str2 := strings.Split(version2, ".")
	length := max(len(str1), len(str2))
	ver1 := make([]int, length)
	ver2 := make([]int, length)
	for i, s := range str1 {
		num, _ := strconv.Atoi(s)
		ver1[i] = num
	}
	for i, s := range str2 {
		num, _ := strconv.Atoi(s)
		ver2[i] = num
	}
	for i := range length {
		if ver1[i] &lt; ver2[i] {
        	return -1
		} else if ver1[i] &gt; ver2[i] {
        	return 1
		}
	}
	return 0
}

如果是 Rust 要懂得使用 split, filter_map, collect 的串接組合技,跟 trim, parse, ok 來將字串轉換成整數,以及用 resize 將陣列調整成一樣長度,再用跟 Python 一樣的 zip 結合起來一個一個比較。

impl Solution {
    pub fn compare_version(version1: String, version2: String) -> i32 {
        let mut ver1: Vec&lt;i32&gt; = version1.split('.').filter_map(|s| s.trim().parse::&lt;i32&gt;().ok()).collect();
        let mut ver2: Vec&lt;i32&gt; = version2.split('.').filter_map(|s| s.trim().parse::&lt;i32&gt;().ok()).collect();
        let len = ver1.len().max(ver2.len());
        ver1.resize(len, 0);
        ver2.resize(len, 0);
        for (x, y) in ver1.into_iter().zip(ver2) {
            if x &lt; y {
            	return -1;
            } else if x &gt; y {
            	return 1;
            }
        }
        0
    }
}

2025年9月17日 星期三

C++ 自製的 gcd 比標準函式庫提供的跑得還要快?

今天在用 C++ 重複練習 2197. Replace Non-Coprime Numbers in Array 一遍,試著寫出跑得更快的版本,發現自己寫出來的 gcd 在 LeetCode 平台上反而跑得比較快,目前還不曉得背後到底是怎麼一回事,先記錄一下,目前想到的做法是盡量避免呼叫 gcd 以及盡量使用已使用的記憶體空間來加速,最好的時候可以跑到 Runtime 2ms Beats 98.62% 的成績。

class Solution {
    int gcd(int a, int b) {
        if (b > a)
            swap(a, b);
        while (b != 0) {
            a %= b;
            swap(a, b);
        }
        return a;
    }

public:
    vector&lt;int&gt; replaceNonCoprimes(vector&lt;int&gt;& nums) {
        int idx = 0;
        for (int i = 1; i < nums.size(); ++i) {
            int num = gcd(nums[idx], nums[i]);
            if (num == 1)
                nums[++idx] = nums[i];
            else
                nums[idx] = nums[idx] / num * nums[i];
            for (int j = idx; j > 0; --j) {
                int num = gcd(nums[j], nums[j - 1]);
                if (num == 1)
                    break;
                nums[j - 1] *= nums[j] / num;
                --idx;
            }
        }
        nums.resize(idx + 1);
        return nums;
    }
};

2025年9月16日 星期二

Rust 沒官方標準支援 gcd 跟 lcm 的 traits 蠻令人意外的

今天在練習 2197. Replace Non-Coprime Numbers in Array 時,需要使用到 GCD 跟 LCM,在 C++/Python 都有官方支援,Rust 跟 Go 都沒有支援,有點意外。

impl Solution {
    pub fn replace_non_coprimes(nums: Vec&lt;i32>) -> Vec&lt;i32> {
        let mut arr = Vec::with_capacity(nums.len());
        let mut curr = nums[0];
        let gcd = |mut a: i32, mut b: i32| -> i32 {
            if a < b {
                (a, b) = (b, a);
            }
            while b != 0 {
                (a, b) = (b, a % b);
            }
            a
        };
        let lcm = |a: i32, b: i32| -> i32 {
            a / gcd(a, b) * b
        };
        for &num in &nums[1..] {
            if gcd(num, curr) > 1 {
                curr = lcm(num, curr);
                while let Some(&last) = arr.last() && gcd(curr, last) > 1 {
                    curr = lcm(last, curr);
                    arr.pop();
                }
            } else {
                arr.push(curr);
                curr = num;
            }
        }
        arr.push(curr);
        arr
    }
}

這裡還剛好可以使用到 Rust 1.88 版本才開始有支援的 Let chains,Let chains 讓程式碼的表示更直白,真是太棒了。

if let Channel::Stable(v) = release_info()
    && let Semver { major, minor, .. } = v
    && major == 1
    && minor == 88
{
    println!("`let_chains` was stabilized in this version");
}