置頂

我的 VPython 教學文件 (HackMD 版本)

VPython 教學文件目錄 安裝及測試 基本語法 等速度直線運動 自由落下 終端速度 水平抛射 使用For迴圈計算水平抛射資料 斜向抛射 圓周運動 簡諧運動 單擺 木塊彈簧系統分離 重力及簡諧 行星運動 相疊木塊 雙重簡諧運動 一維彈性碰撞 ...

熱門文章

2026年8月8日 星期六

ZeroJudge 解題筆記:s562. 多項式 - 湊出 a_n

作者:王一哲
日期:2026年8月8日


ZeroJudge 題目連結:s562. 多項式 - 湊出 a_n

解題想法


題目給一個函數定義 $$ \begin{align*} &~ (1 + qx)(1 + qx^2)(1 + qx^4)(1 + qx^8)(1 + qx^{16}) + \dots \\ &= a_0 + a_1 x + a_2 x^2 + a_3 x^3 + \dots \end{align*} $$ 測資第一行為 $t$,代表接下來有 $t$ 行數字 $n$,要回傳 $a_n$ 對應的 $x$ 次方。實際上這題考的是二進位,$a_n$ 項的 $x$ 次方為 $n$ 的二進位制之中有幾個 $1$,例如 $a_6$ 為 $2 = 11_2$。

Python 程式碼


這題的記憶體限制很嚴格,只有 64 MB,而且測資數量極大。我一開始是用 for 迴圈及 input() 讀取資料,但是這樣會超時。後來改用 sys.stdin.read().split() 一次讀取所有測資,再用 sys.stdou.write() 輸出所有的答案,但是這樣寫會超出記憶體上限。最後是用 for 迴圈及 sys.stdin.readline() 讀取測資,計算完答案之後立刻用 sys.stdou.write() 輸出,才將時間壓在 0.5 s,記憶體壓在 8.5 MB。
超時。
t = int(input())
for _ in range(t):
    n = int(input())
    print(n.bit_count())

超出記憶體上限。
def solve():
    import sys

    result = []
    data = sys.stdin.read().split()
    ptr = 1
    while ptr < len(data):
        n = int(data[ptr])
        ptr += 1
        result.append(f"{n.bit_count()}\n")
    sys.stdout.write("".join(result))

if __name__ == "__main__":
    solve()

解題時間約為 0.5 s,使用記憶體約為 8.5 MB。
def solve():
    import sys

    t = int(sys.stdin.readline())
    for _ in range(t):
        n = int(sys.stdin.readline())
        sys.stdout.write(f"{n.bit_count()}\n")

if __name__ == "__main__":
    solve()


LeetCode 解題筆記:3302. Find the Lexicographically Smallest Valid Sequence

作者:王一哲
日期:2026年8月8日


LeetCode 題目連結:3302. Find the Lexicographically Smallest Valid Sequence

解題想法


中等難度題。題目給兩個字串 $word1$ 及 $word2$,如果最多可以改變 $word1$ 之中的一個字母,如何在 $word1$ 之中找出等於 $word2$ 的子字串,回傳最小字典序子字串於 $word1$ 之中的索引值。這題用貪心法解題比較方便。首先要找出 $word2[j]$ 及之後的字元於 $word1$ 可以被找到且在最右側的位置,將索引值存入陣列 right_match。接下來用貪心法,由 $word1$ 開頭往右找答案 $ans$,如果 $word1[i] = word2[j]$,$i$ 加入 $ans$;如果 $word1[i] \neq word2[j]$ 而且還沒有換過字母,如果已經找到 $word2[m-1]$ 或是 $word2[j+1]$ 及之後的字母可以在 $word1[i]$ 之後被找到,$i$ 加入 $ans$。最後檢查 $ans$ 的長度是否等於 $m$,如果相等回傳 $ans$,反之回傳空陣列。

Python 程式碼


Runtime: 425 ms, beats 84.78%. Memory: 48.21 MB, beats 47.83%.
class Solution:
    def validSequence(self, word1: str, word2: str) -> List[int]:
        n, m = len(word1), len(word2)  # 長度
        # 預處理,找出 word2[j] 及之後的字元於 word1 可以被找到且在最右側的位置
        right_match = [-1] * m
        j = m - 1
        for i in range(n-1, -1, -1):
            if j < 0: break  # 已經找完 word2,中止迴圈
            if word1[i] == word2[j]:
                right_match[j] = i
                j -= 1
        print(right_match)
        # 貪心法,由 word1 開頭往右找答案
        ans = []
        j = 0
        changed = False
        for i in range(n):
            # 已經找完 word2,中止迴圈
            if j == m: break
            # 相同的字母,直接加入 ans
            if word1[i] == word2[j]:
                ans.append(i)
                j += 1
            elif not changed:  # 不同的字母,還沒有換過字母
                # 如果已經找到 word2 最後一個字母或是 word2[j+1] 及之後的字母在 word1[i] 之後能被找到
                if j == m-1 or right_match[j+1] > i:
                    changed = True
                    ans.append(i)
                    j += 1
        # 如果 ans 長度等於 m,回傳 ans,反之回傳 []
        return ans if len(ans) == m else []


2026年8月7日 星期五

LeetCode 解題筆記:396. Rotate Function

作者:王一哲
日期:2026年8月7日


LeetCode 題目連結:396. Rotate Function

解題想法


中等難度題。題目給一個長度為 $n$ 的陣列 $nums$,並定義 $arr_k$ 為 $nums$ 向右平移 $k$ 格,求以下的方程式最大值。 $$ F(k) = 0 \times arr_k [0] + 1 \times arr_k [1] + 2 \times arr_k [2] + \dots + (n-1) \times arr_k [n-1] $$ 這題考動態規畫,假設 $nums$ 長度為 $4$ 先列出前幾項 $F(k)$ 找規律 $$ \begin{align*} F(0) &= 0 \times nums[0] + 1 \times nums[1] + 2 \times nums[2] + 3 \times nums[3] \\ F(1) &= 0 \times nums[3] + 1 \times nums[0] + 2 \times nums[1] + 3 \times nums[2] \\ F(2) &= 0 \times nums[2] + 1 \times nums[3] + 2 \times nums[0] + 3 \times nums[1] \\ F(3) &= 0 \times nums[1] + 1 \times nums[2] + 2 \times nums[3] + 3 \times nums[0] \end{align*} $$ 定義 $isum = \sum nums[i]$。將以上相鄰兩式相減可得 $$ \begin{align*} F(1) - F(0) &= nums[0] + nums[1] + nums[2] - 3 \times nums[3] = isum - 4 \times nums[3] \\ F(2) - F(1) &= nums[3] + nums[0] + nums[1] - 3 \times nums[2] = isum - 4 \times nums[2] \\ F(3) - F(2) &= nums[2] + nums[3] + nums[0] - 3 \times nums[1] = isum - 4 \times nums[1] \\ \end{align*} $$ 規律為 $$ F(i) = F(i-1) + isum - n \times nums[n-i] $$ 由於計算 $F(i)$ 時只會用到 $F(i-1)$ 的值,可以用一個變數 $dp$ 儲存資料,不需要用陣列。解題時,先計算加總 $isum$ 及 $dp = F(0)$,將答案 $ans$ 先設為 $dp$,再用 for 迴圈依序更新 $i = 1$ 到 $i = n-1$ 對應的 $dp$ 值,同時更新 $ans$。

Python 程式碼


Runtime: 143 ms, beats 52.49%. Memory: 31.37 MB, beats 18.77%.
class Solution:
    def maxRotateFunction(self, nums: List[int]) -> int:
        n = len(nums)
        isum, dp = 0, 0  # 加總,F(k)
        for i, num in enumerate(nums):
            isum += num
            dp += i * num
        ans = dp  # 答案
        for i in range(1, n):  # 更新 i = 1 ~ n-1
            dp = dp + isum - n * nums[n-i]
            ans = max(ans, dp)
        return ans


2026年8月6日 星期四

LeetCode 解題筆記:3345. Smallest Divisible Digit Product I

作者:王一哲
日期:2026年8月6日


LeetCode 題目連結:3345. Smallest Divisible Digit Product I

解題想法


簡單題。題目給兩個整數 $n$、$t$,要找出大於、等於 $n$ 且數字乘積可以被 $t$ 整除的最小整數。基本上答案不會太大,只要用一個 while 迴圈,從 $n$ 開始往上檢查數字乘積是否可以被 $t$ 整除,如果可以整除就回傳目前 $n$ 的值,反之則將 $n$ 加 $1$。

Python 程式碼


Runtime: 0 ms, beats 100.00%. Memory: 19.36 MB, beats 32.93%.
class Solution:
    def smallestNumber(self, n: int, t: int) -> int:
        while True:
            x = n
            d = 1
            while x:
                d *= x % 10
                x //= 10
            if d % t == 0:
                return n
            n += 1
        return -1


2026年8月5日 星期三

LeetCode 解題筆記:3310. Remove Methods From Project

作者:王一哲
日期:2026年8月5日


LeetCode 題目連結:3310. Remove Methods From Project

解題想法


中等難度題。對我而言,這題最大的困難在於看懂題目的意思。題目給一個計畫的方法之間先後順序的關係,方法數量為 $n$,編號為 $0$ 到 $n-1$,其中編號 $k$ 是可疑的方法,$k$ 之後的方法都被視為可疑的方法;但如果有這群可疑方法之外沒有問題的方法指向這個群組,則整群可疑的方法都不能被移除;最後要回傳剩下的方法。可以將這些方法視為有向圖的節點,先用 BFS 或 DFS 從節點 $k$ 出發,將 $k$ 及相連的節點都標記為可疑。再用一個 for 迴圈掃過所有節點,如果節點 $u$ 是沒有問題的節點,而且 $u$ 指向任何一個可疑的節點,則所有節點都不能被移除。如果需要移除可疑的節點,只回傳沒有問題的節點;反之,回傳所有節點。

Python 程式碼


BFS. Runtime: 227 ms, beats 90.15%. Memory: 99.66 MB, beats 89.39%.
class Solution:
    def remainingMethods(self, n: int, k: int, invocations: List[List[int]]) -> List[int]:
        # 用接鄰矩陣儲存下一個相關的方法
        adj = [[] for _ in range(n)]
        for u, v in invocations:
            adj[u].append(v)
        
        # 用 BFS 標記可疑的方法
        suspicious = [False] * n
        que = deque([k])
        while que:
            u = que.popleft()
            suspicious[u] = True
            for v in adj[u]:
                if not suspicious[v]:
                    que.append(v)
        
        # 檢查是否有外部可用的方法呼叫任何可疑的方法,如果有,不能移除任何可疑的方法
        removed = True
        for u in range(n):
            if not suspicious[u]:
                for v in adj[u]:
                    if suspicious[v]:
                        removed = False
                        break
        
        # 回傳答案,如果需要移除可疑的方法,只回傳可用的方法
        if removed:
            return [i for i in range(n) if not suspicious[i]]
        else:  # 反之,不能移除任何方法,回傳全部的方法
            return list(range(n))


2026年8月4日 星期二

LeetCode 解題筆記:3731. Find Missing Elements

作者:王一哲
日期:2026年8月4日


LeetCode 題目連結:3731. Find Missing Elements

解題想法


簡單題。題目給一個陣列 $nums$,其中的數字皆不相同,先找出 $nums$ 之中的最小值與最大值,再找出最小值、最大值之間不在 $nums$ 之中的整數,將缺少的整數排序後再回傳,如果沒有缺少的整數則回傳空陣列。我們可以用 Python 的 max、min 或是 C++ 的 max_element、min_element 找出最大值 $high$ 與最小值 $low$。為了標記區間 $[low, high]$ 所有的數字是否在 $nums$ 之中,可以用一個長度為 $high - low + 1$ 陣列 $found$,將 $nums$ 之中所有數字 $num$ 標示為 $found[num - low] = True$。也可以將 $nums$ 轉成 Python 的 set 或是 C++ 的 unordered_set,直接用 in 或是 count 檢查數字是否在 $nums$ 之中。兩者寫法的速度都很快。

Python 程式碼


用串列標記狀態。Runtime: 0 ms, beats 100.00%. Memory: 19.45 MB, beats 18.85%.
class Solution:
    def findMissingElements(self, nums: List[int]) -> List[int]:
        low, high = min(nums), max(nums)  # 最小值、最大值
        found = [False] * (high - low + 1)  # 是否有這個值
        for num in nums:  # 更新狀態
            found[num - low] = True
        
        ans = []  # 缺少的值
        for i in range(low + 1, high):
            if not found[i - low]:
                ans.append(i)
        return ans


用串列標記狀態,合併産生答案的程式碼。Runtime: 0 ms, beats 100.00%. Memory: 19.26 MB, beats 55.74%.
class Solution:
    def findMissingElements(self, nums: List[int]) -> List[int]:
        low, high = min(nums), max(nums)  # 最小值、最大值
        found = [False] * (high - low + 1)  # 是否有這個值
        for num in nums:  # 更新狀態
            found[num - low] = True
        
        return [i for i in range(low + 1, high) if not found[i - low]]


2026年8月3日 星期一

LeetCode 解題筆記:1406. Stone Game III

作者:王一哲
日期:2026年8月3日


LeetCode 題目連結:1406. Stone Game III

解題想法


困難題。題目給一個長度為 $n$ 的陣列 $stoneValue$ 代表每個石頭的分數,玩家 Alice、Bob 每回合行動時,可以選擇取走目前編號最小的1到3個石頭並獲得這幾個石頭的分數,固定由 Alice 先行動,最後依照總分回傳答案,如果 Alice 總分較高回傳 Alice,如果 Bob 總分較高回傳 Bob,如果平手回傳 Tie。

這題考動態規畫,定義長度為 $n+1$ 的陣列 $dp$,$dp[i]$ 代表剩下 $i$ 個石頭時的最大分差,除了 $dp[n] = 0$,其它項初始化為負無窮大。更新時由 $i = n-1$ 往回更新到 $i = 0$,由於可以選擇拿1到3個石頭,再用一層 for 迴圈跑 $j = 1$ 到 $j = 3$,如果 $i+j \leq n$ 更新目前取石頭的總分 $val += stoneValue[i+j-1]$,$dp[i] = max(dp[i], val - dp[i+j])$。最後依照 $dp[0]$ 的值回傳答案,如果 $dp[0] > 0$ 回傳 Alice,如果 $dp[0] < 0$ 回傳 Bob,如果 $dp[0] = 0$ 回傳 Tie。

Python 程式碼


Runtime: 617 ms, beats 74.25%. Memory: 23.91 MB, beats 71.59%.
class Solution:
    def stoneGameIII(self, stoneValue: List[int]) -> str:
        n = len(stoneValue)
        dp = [float('-inf')] * (n+1)  # dp[i] 代表取第 i 個時剩下的石頭最大分差
        dp[n] = 0  # 沒有剩下的石頭,之後的最大分差為 0
        # 由 stoneValue 後往前取值
        for i in range(n-1, -1, -1):
            val = 0  # 拿走的石頭得分
            for j in range(1, 4):  # 試著拿1、2、3個石頭
                if i + j <= n:  # 避免出界
                    val += stoneValue[i+j-1]  # 加上第 i+j-1 個石頭的分數
                    dp[i] = max(dp[i], val - dp[i+j])  # 更新 dp[i],可能是 val 減掉對手於 dp[i+j] 的最大分差
        
        if dp[0] > 0: return "Alice"
        elif dp[0] < 0 : return "Bob"
        else: return "Tie"


2026年8月2日 星期日

LeetCode 解題筆記:877. Stone Game

作者:王一哲
日期:2026年8月2日


LeetCode 題目連結:877. Stone Game

解題想法


中等難度題。這題看起來與昨天的 486. Predict the Winner 幾乎一樣,題目給一個陣列 $piless$,兩個玩家 Alice、Bob 每次行動時可以從 $piles$ 兩端選取並移除一堆石頭,Alice 先行動,如果 Alice 拿到比較多的石頭回傳 True。這題一 樣可以用動態規畫解題,建立一個 $n \times n$ 的二維陣列 $dp[i][j]$ 代表可選的值剩下 $piles[i]$ ~ $piles[j]$ 時目前行動玩家與另一人的最大分差。更新狀態時有 2 種選擇:
  1. 行動1,選 $piles[i]$,新的區間為 $piles[i+1]$ ~ $piles[j]$,最大分差為 $piles[i] - dp[i+1][j]$
  2. 行動2,選 $piles[j]$,新的區間為 $piles[i] ~ piles[j-1]$,最大分差為 $piles[j] - dp[i][j-1]$
玩家採用最佳策略,從行動1、2之中選分數高的,因此 $$ dp[i][j] = max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1]) $$ 當 $i = j$ 時,只能選擇一個數字,$dp[i][j] = piles[i]$。更新 $dp$ 時,要從長度 $2$ 開始,直到長度等於 $n$ 為止,因為長度較長的狀態,是基於長度較短的狀態更新數值。更新完畢之後,如果 $dp[0][n-1] \geq 0$,Alice 獲勝,回傳 True。

但是這題多加了兩個條件:$piles$ 的長度為偶數,$piles$ 所有的數量加起來為奇數,答案一定是 True。因為 Alice 可以選擇取走所有奇數索引值或是偶數索引值的石頭堆,選擇總數多的石頭堆就能獲勝。

Python 程式碼


dp. Runtime: 127 ms, beats 41.69%. Memory: 25.47 MB, beats 34.68%.
class Solution:
    def stoneGame(self, piles: List[int]) -> bool:
        n = len(piles)  # 長度
        # dp[i][j] 範圍為 piles[i] ~ piles[j] 對應的最大分差
        dp = [[0]*n for _ in range(n)]
        # 初始化,只剩下一堆能選
        for i in range(n):
            dp[i][i] = piles[i]
        # 更新 dp,外層更新長度 2 ~ n,內層更新起點 i = 0 ~ n - len
        for length in range(2, n+1):
            for i in range(n - length + 1):
                j = i + length - 1
                dp[i][j] = max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1])
        # 如果 dp[0][n-1] > 0,Alice 獲勝,回傳 True
        return dp[0][n-1] > 0


Runtime: 0 ms, beats 100.00%. Memory: 19.33 MB, beats 55.87%.
class Solution:
    def stoneGame(self, piles: List[int]) -> bool:
        return True


2026年8月1日 星期六

LeetCode 解題筆記:486. Predict the Winner

作者:王一哲
日期:2026年8月1日


LeetCode 題目連結:486. Predict the Winner

解題想法


中等難度題。題目給一個陣列 $nums$,兩個玩家 A、B 每次行動時可以從 $nums$ 兩端選取並移除一個數字,獲得這個數字的分數,玩家 A 先行動,如果玩家 A 總分大於等於玩家 B,回傳 True。這題很適合用動態規畫解題,建立一個 $n \times n$ 的二維陣列 $dp[i][j]$ 代表可選數字剩下 $nums[i]$ ~ $nums[j]$ 時目前行動玩家與另一人的最大分差。更新狀態時有 2 種選擇:
  1. 行動1,選 $nums[i]$,新的區間為 $nums[i+1]$ ~ $nums[j]$,最大分差為 $nums[i] - dp[i+1][j]$
  2. 行動2,選 $nums[j]$,新的區間為 $nums[i] ~ nums[j-1]$,最大分差為 $nums[j] - dp[i][j-1]$
玩家採用最佳策略,從行動1、2之中選分數高的,因此 $$ dp[i][j] = max(nums[i] - dp[i+1][j], nums[j] - dp[i][j-1]) $$ 當 $i = j$ 時,只能選擇一個數字,$dp[i][j] = nums[i]$。更新 $dp$ 時,要從長度 $2$ 開始,直到長度等於 $n$ 為止,因為長度較長的狀態,是基於長度較短的狀態更新數值。更新完畢之後,如果 $dp[0][n-1] \geq 0$,玩家 A 獲勝,回傳 True。

Python 程式碼


Runtime: 0 ms, beats 100.00%. Memory: 19.11 MB, beats 94.32%.
class Solution:
    def predictTheWinner(self, nums: List[int]) -> bool:
        n = len(nums)
        # 初始化 dp
        dp = [[0]*n for _ in range(n)]
        for i in range(n):
            dp[i][i] = nums[i]
        # 由長度 2 開始填表格,直到長度 n 為止
        for length in range(2, n+1):
            for i in range(n - length + 1):  # 起點
                j = i + length - 1  # 終點
                dp[i][j] = max(nums[i] - dp[i+1][j], nums[j] - dp[i][j-1])
        # 回傳答案
        return dp[0][n-1] >= 0


2026年7月31日 星期五

LeetCode 解題筆記:3016. Minimum Number of Pushes to Type Word II

作者:王一哲
日期:2026年7月31日


LeetCode 題目連結:3016. Minimum Number of Pushes to Type Word II

解題想法


中等難度題,3014. Minimum Number of Pushes to Type Word I 的加強版,但是這題 $word$ 之中可以有相同的字母,需要修改一下程式碼。如果要求最低的次數,原則上是先處理 $word$ 之中數量多的字母,數量最多的前 8 個字母按 1 下,第二多的 8 個字母按 2 下,其餘依此類推。我在計算相異字母的數量時使用了,Python collections 函式庫的 Counter,這是專門用來計數的容器,速度很快;於 C++ 則是用 unordered_map,速度比較慢,改用表格計數快很多。

Python 程式碼


字典計數。Runtime: 91 ms, beats 89.97%. Memory: 19.91 MB, beats 77.26%.
class Solution:
    def minimumPushes(self, word: str) -> int:
        cnt = Counter(word)  # 計數器
        vals = sorted(cnt.values(), reverse=True)  # 各相異字母對應的數量
        n = len(vals)  # 相異字母數量
        idx = 0  # 從 vals 讀取資料的索引值
        ans = 0  # 答案
        cof = 1  # 要按幾下才能輸入一個字母
        while idx < n:
            m = 0  # 按 cof 下才能輸入的字母數量
            while idx < n and m < 8:  # 最多 8 個字母
                ans += cof * vals[idx]  # 答案加上 cof * 數量
                idx += 1  # 索引值加 1
                m += 1  # m 加 1
            cof += 1  # cof 加 1
        return ans


表格計數。Runtime: 175 ms, beats 39.47%. Memory: 19.86 MB, beats 93.65%.
class Solution:
    def minimumPushes(self, word: str) -> int:
        cnt = [0] * 26  # 計數器
        for c in word:
            cnt[ord(c) - ord('a')] += 1
        vals = sorted([val for val in cnt if val > 0], reverse=True)  # 各相異字母對應的數量
        n = len(vals)  # 相異字母數量
        idx = 0  # 從 vals 讀取資料的索引值
        ans = 0  # 答案
        cof = 1  # 要按幾下才能輸入一個字母
        while idx < n:
            m = 0  # 按 cof 下才能輸入的字母數量
            while idx < n and m < 8:  # 最多 8 個字母
                ans += cof * vals[idx]  # 答案加上 cof * 數量
                idx += 1  # 索引值加 1
                m += 1  # m 加 1
            cof += 1  # cof 加 1
        return ans