2025年11月28日 星期五

Kadane's algorithm

昨天練習的題目是 3381. Maximum Subarray Sum With Length Divisible by K,題目的最佳解是利用 Kadane's algorithm,我覺得比較容易理解的方式是先計算前面 k-1 個 kSum 之後,再往後面用 Kadane's algorithm 來解。

如果是寫 C++ 是這樣:

class Solution {
public:
    long long maxSubarraySum(vector< int>& nums, int k) {
        const int n = nums.size();
        vector< long long> kSum(k, 0);
        long long maxSum = numeric_limits< long long>::min(), prefixSum = 0;
        for (int i = 0; i < k - 1; ++i) {
            prefixSum += nums[i];
            kSum[i] = prefixSum;
        }
        for (int i = k - 1; i < n; ++i) {
            prefixSum += nums[i];
            int r = i % k;
            maxSum = max(maxSum, prefixSum - kSum[r]);
            kSum[r] = min(kSum[r], prefixSum);
        }
        return maxSum;
    }
};

寫 Python3 是這樣:

class Solution:
    def maxSubarraySum(self, nums: List[int], k: int) -> int:
        n = len(nums)
        kSum = [0] * k
        prefixSum = 0
        maxSum = float("-inf")
        for i in range(k - 1):
            prefixSum += nums[i]
            kSum[i] = prefixSum
        for i in range(k - 1, n):
            prefixSum += nums[i]
            r = i % k
            maxSum = max(maxSum, prefixSum - kSum[r])
            kSum[r] = min(kSum[r], prefixSum)
        return maxSum

寫 Go 是這樣,這邊要注意即便是使用 math.MinInt64 還是要用 int64() 明確轉型,不然預設會用 int 然後就會跟後面的 int64 衝突。

func maxSubarraySum(nums []int, k int) int64 {
	n := len(nums)
	kSum := make([]int64, k)
	prefixSum, maxSum := int64(0), int64(math.MinInt64)
	for i := range n {
		prefixSum += int64(nums[i])
		if i < k-1 {
			kSum[i] = prefixSum
			continue
		}
		r := i % k
		maxSum = max(maxSum, prefixSum-kSum[r])
		kSum[r] = min(kSum[r], prefixSum)
	}
	return maxSum
}

寫 Rust 是這樣,沒什麼特別要注意的地方,反正寫錯時編譯器會提醒你該如何修正。 :-P

impl Solution {
    pub fn max_subarray_sum(nums: Vec< i32>, k: i32) -> i64 {
        let k = k as usize;
        let n = nums.len();
        let mut kSum = vec![0i64; k];
        let mut prefixSum = 0i64;
        let mut maxSum = i64::MIN;
        for i in 0..(k-1) {
            prefixSum += nums[i] as i64;
            kSum[i] = prefixSum;
        }
        for i in (k-1)..n {
            prefixSum += nums[i] as i64;
            let r = i % k;
            maxSum = maxSum.max(prefixSum - kSum[r]);
            kSum[r] = kSum[r].min(prefixSum);
        }
        maxSum
    }
}

不曉得為何,寫 Rust 有最愉快的感覺。 :-P

2025年11月17日 星期一

Binary Search

今日練習的題目是 300. Longest Increasing Subsequence 主要是需要使用到 Binary Search

首先是 C++ 的 lower_bound

class Solution {
public:
    int lengthOfLIS(vector< int>& nums) {
        vector< int> arr;
        arr.reserve(nums.size());
        for (auto& num : nums) {
            auto ptr = ranges::lower_bound(arr.begin(), arr.end(), num);
            if (ptr == arr.end()) {
                arr.push_back(num);
            } else {
                *ptr = num;
            }
        }
        return arr.size();
    }
};

然後是 Python 的 bisect.bisect_left()

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        arr = []
        for num in nums:
            i = bisect.bisect_left(arr, num)
            if i == len(arr):
                arr.append(num)
            else:
                arr[i] = num
        return len(arr)

然後是 Go 的 sort.Search()

func lengthOfLIS(nums []int) int {
	arr := make([]int, 0, len(nums))
	for _, num := range nums {
		idx := sort.Search(len(arr), func(i int) bool {
			return arr[i] >= num
		})
		if idx == len(arr) {
			arr = append(arr, num)
		} else {
			arr[idx] = num
		}
	}
	return len(arr)
}

最後是 Rust 的 binary_search

impl Solution {
    pub fn length_of_lis(nums: Vec< i32>) -> i32 {
        let mut arr = Vec::with_capacity(nums.len());
        for num in nums {
            let i = match arr.binary_search(&num) {
                Ok(index) => index,
                Err(index) => index,
            };
            if i == arr.len() {
                arr.push(num);
            } else {
                arr[i] = num;
            }
        }
        arr.len() as i32
    }
}

每種程式語言使用方式都有所不同。

2025年11月8日 星期六

Gray code

今天練習的題目是 1611. Minimum One Bit Operations to Make Integers Zero,後來自己想出解法後去看官方解答才發現是在考 Gray code 的轉換。

我自己列出 16 轉換到 0 的所有 bits 的表現方式,然後仔細觀察後發現有某種規律,於是就猜想了一個方程式出來試試看,結果還真的可以解答,先聲明一下這不是最有效的方法,只是眾多解法中的其中一種而已。

設 $p_1, p_2, \dots, p_j$ 為 $n$ 的二進位中所有 1 的 0-based 索引,且 $p_1 > p_2 > \dots > p_j$ ($p_1$ 是 MSB)。
$$f(n) = \sum_{i=1}^{j} (-1)^{i-1} \cdot (2^{p_i+1} - 1)$$

這是我初始第一個版本

class Solution {
public:
    int minimumOneBitOperations(int n) {
        if (n == 0)
            return 0;
        int k = 32 - __builtin_clz(n);
        int ans = 0;
        bitset<30> arr(n);
        int sign = 1;
        for (int i = k; i > 0; --i) {
            if (arr[i - 1]) {
                ans += sign * ((1 << i) - 1);
                sign *= -1;
            }
        }
        return ans;
    }
};

簡化後的第二個版本

class Solution {
public:
    int minimumOneBitOperations(int n) {
        if (n == 0)
            return 0;
        int k = 32 - __builtin_clz(n);
        int ans = 0;
        int sign = 1;
        for (int i = k; i > 0; --i) {
            if (n & (1 << (i - 1))) {
                ans += sign * ((1 << i) - 1);
                sign *= -1;
            }
        }
        return ans;
    }
};

再進一步簡化後的第三個版本

class Solution {
public:
    int minimumOneBitOperations(int n) {
        if (n == 0)
            return 0;
        int ans = 0;
        int sign = 1;
        while (n) {
            int k = 32 - __builtin_clz(n);
            ans += sign * ((1 << k) - 1);
            sign *= -1;
            n ^= (1 << (k-1));
        }
        return ans;
    }
};

有用 Gemini AI 幫忙寫了一份解答分享在 LeetCode 上面 Using a Math Formula 這樣

2025年10月28日 星期二

accumulate/sum 加總

今天練習的題目是 3354. Make Array Elements Equal to Zero,題目可以利用加總來解題。

首先是 C++ 要使用 accumulate

class Solution {
public:
    int countValidSelections(vector< int>& nums) {
        int ans = 0, prev = 0, post = accumulate(nums.begin(), nums.end(), 0);
        for (auto num : nums) {
            if (num == 0) {
                if (prev == post)
                    ans += 2;
                else if (abs(prev - post) == 1)
                    ++ans;
            } else {
                post -= num;
                prev += num;
            }
        }
        return ans;
    }
};

Python3 就用 sum()

class Solution:
    def countValidSelections(self, nums: List[int]) -> int:
        ans, prev, post = 0, 0, sum(nums)
        for num in nums:
            if num == 0:
                if prev == post:
                    ans += 2
                elif abs(prev - post) == 1:
                    ans += 1
            else:
                prev += num
                post -= num
        return ans

Go 加總要自己來,abs() 也要自己刻,不過發現可以利用 slices.Values() 在 range 上面,這樣就不用多寫一個 _ 底線,像是 for _, num := range nums 這樣,不過多寫 slices.Values 也不一定好到哪裡去。

func abs(num int) int {
    if num < 0 {
        return -num
    }
    return num
}

func countValidSelections(nums []int) int {
    ans, prev, post := 0, 0, 0
    for num := range slices.Values(nums) {
        post += num
    }
    for num := range slices.Values(nums) {
        if num == 0 {
            if prev == post {
                ans += 2
            } else if abs(prev-post) == 1 {
                ans += 1
            }
        } else {
            prev += num
            post -= num
        }
    }
    return ans
}

最後 Rust 也可以用 sum() 不過要用在 iter() 後面,另外要注意的一點是 sum() 會需要指定型別,不然會有型別無法推導的問題,也可以用 nums.iter().sum::< i32>(),不過也可以用 fold 來算,像是 nums.iter().fold(0, |x, y| x + y) 這樣,就能成功推導型別。

nums.iter().sum() 的問題在於它非常泛型。sum() 方法可以將 &i32 的迭代器加總成多種不同的型別,例如 i32, i64, i128 等(任何實作了 Sum<&i32> trait 的型別)。

impl Solution {
    pub fn count_valid_selections(nums: Vec<i32>) -> i32 {
        let (mut ans, mut prev, mut post): (i32, i32, i32) = (0, 0, nums.iter().sum());
        for num in nums {
            if num == 0 {
                if prev == post {
                    ans += 2;
                } else if (prev-post).abs() == 1 {
                    ans += 1;
                }
            } else {
                prev += num;
                post -= num;
            }
        }
        ans
    }
}

2025年10月14日 星期二

ubuntu-dev-tools: /usr/bin/pbuilder-dist-simple

pbuilder-dist-simple 是個方便用來創建 pbuilder base 的 wrapper 腳本,使用的方式也很特別,就是要自己手動建立一個 symbolic link,然後它就會根據 symbolic link 的名稱來自動解析參數來處理。

例如:questing 是 Ubuntu 25.10 的 series name。

$ dpkg -S /usr/bin/pbuilder-dist-simple
ubuntu-dev-tools: /usr/bin/pbuilder-dist-simple
$ mkdir ~/bin
$ ln -s /usr/bin/pbuilder-dist-simple ~/bin/pbuilder-questing

建立完 symbolic link 就可以直接使用了,如果 PATH 有先設好的話。

$ echo $PATH
/home/user/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

$ pbuilder-questing create
W: /root/.pbuilderrc does not exist
I: Distribution is questing.
I: Current time: Tue Oct 14 06:55:39 UTC 2025
I: pbuilder-time-stamp: 1760424939
I: Building the build environment
I: running debootstrap
/usr/sbin/debootstrap
...
I: creating base tarball [/home/user/pbuilder/questing-base.tgz]
...

接下來我們可以抓 Debian libchewing source package 來試試看

$ pull-debian-source libchewing
Found libchewing 0.10.3-1 in sid
...
Downloading libchewing_0.10.3.orig.tar.xz from deb.debian.org (1.571 MiB)
[=====================================================>]100%
Downloading libchewing_0.10.3-1.debian.tar.xz from deb.debian.org (0.008 MiB)
[=====================================================>]100%

$ pbuilder-questing build libchewing_0.10.3-1.dsc
W: /root/.pbuilderrc does not exist
I: pbuilder: network access will be disabled during build
I: Current time: Tue Oct 14 07:02:09 UTC 2025
I: pbuilder-time-stamp: 1760425329
I: Building the build Environment
I: extracting base tarball [/home/user/pbuilder/questing-base.tgz]
...
dpkg-deb: building package 'libchewing3-data' in '../libchewing3-data_0.10.3-1_all.deb'.
dpkg-deb: building package 'libchewing3-dev' in '../libchewing3-dev_0.10.3-1_amd64.deb'.
dpkg-deb: building package 'libchewing3' in '../libchewing3_0.10.3-1_amd64.deb'.
dpkg-deb: building package 'chewing-tools' in '../chewing-tools_0.10.3-1_amd64.deb'.
...

$ cd ~/pbuilder/ && ls *chewing*
chewing-tools_0.10.3-1_amd64.deb          libchewing_0.10.3-1_amd64.buildinfo  libchewing_0.10.3-1.debian.tar.xz  libchewing_0.10.3-1_source.changes  libchewing3_0.10.3-1_amd64.deb     libchewing3-dbgsym_0.10.3-1_amd64.ddeb
chewing-tools-dbgsym_0.10.3-1_amd64.ddeb  libchewing_0.10.3-1_amd64.changes    libchewing_0.10.3-1.dsc            libchewing_0.10.3.orig.tar.xz       libchewing3-data_0.10.3-1_all.deb  libchewing3-dev_0.10.3-1_amd64.deb

看起來目前的 libchewing 0.10.3-1 in sid 可以在 Ubuntu 25.10 正常編譯沒有問題。

2025年10月13日 星期一

字串的排序

今日練習的題目是 2273. Find Resultant Array After Removing Anagrams,因為字串長度不大,所以用排序後的字串當作鍵值來比較就可以了。

C++ 字串可以當成陣列直接排序相當方便

class Solution {
public:
    vector< string> removeAnagrams(vector< string>& words) {
        vector< string> ans;
        ans.reserve(words.size());
        string prev;
        for (auto& word : words) {
            string key(word);
            sort(key.begin(), key.end());
            if (key != prev) {
                ans.emplace_back(word);
                prev = move(key);
            }
        }
        return ans;
    }
};

Python3 比較特別有內建的 groupby 可以利用跟搭配 sorted 來產生排序字串當鍵值

class Solution:
    def removeAnagrams(self, words: List[str]) -> List[str]:
        return [next(g) for _, g in groupby(words, sorted)]

Go 原本是用轉換成排序後的字串當鍵值

func removeAnagrams(words []string) []string {
	var prev string
	ans := make([]string, 0, len(words))
	for _, word := range words {
		runes := []rune(word)
		slices.Sort(runes)
		key := string(runes)
		if key != prev {
			ans = append(ans, word)
			prev = key
		}
	}
	return ans
}

Go 後來發現用 bytes.Equal 直接比較排序後的 []byte 也不錯


func removeAnagrams(words []string) []string {
	prev := []byte{}
	ans := make([]string, 0, len(words))
	for _, word := range words {
		key := []byte(word)
		slices.Sort(key)
		if !bytes.Equal(key, prev) {
			ans = append(ans, word)
			prev = key
		}
	}
	return ans
}

Rust 用轉換排序後的字串當鍵值效率沒那麼好,可能是因為要顧及 UTF-8 的轉換問題

impl Solution {
    pub fn remove_anagrams(words: Vec< String>) -> Vec< String> {
        let mut prev = "".to_string();
        let mut ans = Vec::with_capacity(words.len());
        for word in words {
            let copy = word.clone();
            let mut chars = word.chars().collect::< Vec< char>>();
            chars.sort_unstable();
            let key = chars.into_iter().collect::< String>();
            if key != prev {
                prev = key;
                ans.push(copy);
            }
        }
        ans
    }
}

Rust 改用排序後的 Vec< u8> 陣列直接比較就簡單有效多了

impl Solution {
    pub fn remove_anagrams(words: Vec< String>) -> Vec< String> {
        let mut prev: Vec< u8> = Vec::new();
        let mut ans = Vec::with_capacity(words.len());
        for word in words {
            let mut key: Vec< u8> = word.bytes().collect();
            key.sort_unstable();
            if key != prev {
                prev = key;
                ans.push(word);
            }
        }
        ans
    }
}

Rust 也可以利用 fold 寫出函數式程式設計的風格

impl Solution {
    pub fn remove_anagrams(words: Vec< String>) -> Vec< String> {
        let initial = (Vec::with_capacity(words.len()), Vec::< u8>::new());
        let (ans, _) = words.into_iter().fold(initial, |(mut ans, mut prev), word| {
            let mut key: Vec< u8> = word.bytes().collect();
            key.sort_unstable();
            if key != prev {
                prev = key;
                ans.push(word);
            }
            (ans, prev)
        });
        ans
    }
}

2025年10月11日 星期六

利用 Stack 上的陣列空間來加速執行速度

今天練習的題目是 3186. Maximum Total Damage With Spell Casting,我使用的方法是先對輸入陣列排序,然後再使用 stack 上固定大小的陣列來處理輸入陣列的資料,因為題目有對輸入陣列數量限制在 $10^5$ 以內,這樣的做法對靜態編譯的程式語言,通常都可以跑出不錯的執行時間。

C++ 就用 array< int, 100000> arr 來加速。

class Solution {
public:
    long long maximumTotalDamage(vector< int>& power) {
        int n = 0;
        array< int, 100000> arr;
        array< int, 100000> count;
        ranges::sort(power);
        for (auto p : power) {
            if (n == 0 || arr[n - 1] != p) {
                ++n;
                arr[n - 1] = p;
                count[n - 1] = 1;
            } else {
                count[n - 1]++;
            }
        }
        vector< long long> f(n, 0);
        long long mx = 0;
        for (int i = 0, j = 0; i < n; ++i) {
            while (i > j && arr[i] > arr[j] + 2) {
                mx = max(mx, f[j]);
                ++j;
            }
            f[i] = mx + 1LL * arr[i] * count[i];
        }
        return *ranges::max_element(f);
    }
};

Python3 比較特別,還是直接用 Counter 比較快,因為它有特別的最佳化處理,對於動態編譯執行的程式語言,還是多利用最佳化的內建函式,才會比較有效率。

class Solution:
    def maximumTotalDamage(self, power: List[int]) -> int:
        freq = Counter(sorted(power))
        keys = list(freq.keys())
        values = list(freq.values())
        n = len(keys)
        f = [0] * n
        j = 0
        mx = 0
        for i in range(n):
            while i > j and keys[i] > keys[j] + 2:
                mx = max(mx, f[j])
                j += 1
            f[i] = mx + keys[i] * values[i]
        return max(f)

Go 就用 arr := [100000]int{} 來加速。

func maximumTotalDamage(power []int) int64 {
	n := 0
	slices.Sort(power)
	arr := [100000]int{}
	count := [100000]int{}
	for _, p := range power {
		if n == 0 || arr[n-1] != p {
			n += 1
			arr[n-1] = p
			count[n-1] = 1
		} else {
			count[n-1]++
		}
	}
	j := 0
	var mx int64
	f := make([]int64, n)
	for i := range n {
		for i > j && arr[i] > arr[j]+2 {
			mx = max(mx, f[j])
			j++
		}
		f[i] = mx + int64(arr[i])*int64(count[i])
	}
	return slices.Max(f)
}

Rust 就用 let mut arr = [0; 100000] 來加速。

impl Solution {
    pub fn maximum_total_damage(mut power: Vec<i32>) -> i64 {
        power.sort_unstable();
        let mut arr = [0; 100000];
        let mut count = [0; 100000];
        let mut n = 0;
        for p in power {
            if n == 0 || arr[n - 1] != p as usize {
                n += 1;
                arr[n - 1] = p as usize;
                count[n - 1] = 1;
            } else {
                count[n - 1] += 1;
            }
        }
        let mut f = vec![0i64; n];
        let mut j = 0;
        let mut mx = 0;
        for i in 0..n {
            while i > j && arr[i] > arr[j] + 2 {
                mx = mx.max(f[j]);
                j += 1;
            }
            f[i] = mx + arr[i] as i64 * count[i] as i64;
        }
        f.into_iter().max().unwrap()
    }
}

在 LeetCode 上面看到 C++/Python3 排名前面都有用 display_runtime.txt 作弊,如果是用這個方法加上 display_runtime.txt 作弊可以輕鬆跑出 Runtime 0ms Beats 100% 的結果。