置頂

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

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

熱門文章

2026年9月25日 星期五

LeetCode 解題筆記:764. Largest Plus Sign

作者:王一哲
日期:2026年9月25日


LeetCode 題目連結:764. Largest Plus Sign

解題想法


中等難度題,題目一個正整數 $n$,代表一個 $n \times n$ 的二維陣列,陣列之中除了某些位置為 $0$,其它位置都是 $1$。再給一個二維陣列 $mines$,其中每一個元素為長度 $2$ 的陣列,代表數值為 $0$ 的位置。題目定義從 $n \times n$ 的二維陣列中找出 + 號大小的計算方式,從 + 中央開始為長度 1,向上、下、左、右延伸,如果 4 個方向都是 1 則長度加 1,如果任何一個方向是 0 就不能再延伸。題目要回傳最大的 + 大小。

首先為了便於查於指定坐標是否在 mines 之中,如果用 Python 解題,可以先將 $mines$ 轉成 set 會比較快;如果用 C++ 解題,再開另一個二維陣列標記 0 的位置會比較快。接下來定義一個 $n \times n$ 的二維陣列 $dp$,用來記錄以每一格為中心的 + 最大長度,預設值皆設為 $n$。用 for 迴圈掃過 $i = 0$ 到 $i = n-1$;裡面再用一層 for 迴圈掃瞄水平方向,分別更新由左向右、由右向左掃的十字大小,再更新對應位置的 $dp$ 值;再用另用一個 for 迴圈掃瞄鉛直方向,分別更新由上向下、由下向上掃的十字大小,再更新對應位置的 $dp$ 值。全部更新完畢之後,答案為 $dp$ 之中的最大值。

Python 程式碼


Runtime: 1205 ms, beats 21.40%. Memory: 22.84 MB, beats 61.87%.
class Solution:
    def orderOfLargestPlusSign(self, n: int, mines: list[list[int]]) -> int:
        # 轉成 set,查詢指定坐標是否在 mines 之中會比較快
        mineset = {tuple(mine) for mine in mines}
        dp = [[n]*n for _ in range(n)]  # 每一格最大的十字大小,預設為最大值 n
        
        for i in range(n):
            # 掃瞄水平方向
            lcnt, rcnt = 0, 0  # 左向右、右向左掃的十字大小
            for j in range(n):  # 改變欄坐標
                # 由左到右,如果 (i, j) 有地雷歸零,反之加 1
                lcnt = 0 if (i, j) in mineset else lcnt + 1
                dp[i][j] = min(dp[i][j], lcnt)

                # 由右到左,如果 (i, n-j-1) 有地雷歸零,反之加 1
                k = n-j-1
                rcnt = 0 if (i, k) in mineset else rcnt + 1
                dp[i][k] = min(dp[i][k], rcnt)
            
            # 掃瞄鉛直方向
            ucnt, dcnt = 0, 0  # 上向下、下向上掃的十字大小
            for j in range(n):  # 改變列坐標
                # 由上到下,如果 (j, i) 有地雷歸零,反之加 1
                ucnt = 0 if (j, i) in mineset else ucnt + 1
                dp[j][i] = min(dp[j][i], ucnt)

                # 由下到上,如果 (n-j-1) 有地雷歸零,反之加 1
                k = n-j-1
                dcnt = 0 if (k, i) in mineset else dcnt + 1
                dp[k][i] = min(dp[k][i], dcnt)
        
        # 掃過所有的格子找最大值
        ans = 0
        for i in range(n):
            for j in range(n):
                ans = max(ans, dp[i][j])
        return ans


2026年9月24日 星期四

LeetCode 解題筆記:3550. Smallest Index With Digit Sum Equal to Index

作者:王一哲
日期:2026年9月24日


LeetCode 題目連結:3550. Smallest Index With Digit Sum Equal to Index

解題想法


簡單題,題目一個整數陣列 $nums$,且 $0 \leq nums[i] \leq 1000$,長度小於等於 $100$,要找出 $nums$ 之中各個位數加總等於索引值的元素,如果有好幾個元素符合條件,回傳最小的索引值,如果沒有任何一個元素符合條件則回傳 $-1$。用一個 for 迴圈依序讀取每個元素,再用一個 while 迴圈或是轉成字串計算位數加總,如果位數加總等於索引值 $i$ 就回傳 $i$,不需要再跑之後的元素。如果 for 迴圈跑完還沒有找到符合條件的元素,回傳 $-1$。

Python 程式碼


Runtime: 0 ms, beats 100.00%. Memory: 19.33 MB, beats 29.46%.
class Solution:
    def smallestIndex(self, nums: List[int]) -> int:
        n = len(nums)
        for i in range(n):
            num = nums[i]
            dsum = 0
            while num:
                dsum += num % 10
                num //= 10
            if dsum == i: return i
        return -1


用 enumerate 比較慢。Runtime: 2 ms, beats 65.51%. Memory: 19.36 MB, beats 29.46%.
class Solution:
    def smallestIndex(self, nums: List[int]) -> int:
        for i, num in enumerate(nums):
            dsum = 0
            while num:
                dsum += num % 10
                num //= 10
            if dsum == i: return i
        return -1


轉成字串更慢。Runtime: 7 ms, beats 16.46%. Memory: 19.29 MB, beats 67.07%.
class Solution:
    def smallestIndex(self, nums: List[int]) -> int:
        for i, num in enumerate(nums):
            dsum = sum(int(c) for c in str(num))
            if dsum == i: return i
        return -1


2026年9月23日 星期三

LeetCode 解題筆記:1658. Minimum Operations to Reduce X to Zero

作者:王一哲
日期:2026年9月23日


LeetCode 題目連結:1658. Minimum Operations to Reduce X to Zero

解題想法


中等難度題,題目一個正整數陣列 $nums$ 及一個正整數 $x$,每次操作時可以選擇 $nums$ 最前面或最後面一個數字,將 $x$ 減去這個數字並從 $nums$ 之中移除此項,如果要使 $x$ 歸零,最少的操作次數是幾次?如果無法歸零,回傳 $-1$。這題底下的提示很重要,如果真的按照題目的要求寫程式,要先計算 $nums$ 的前綴和 $psum$ 及後綴和 $ssum$,再從 $psum$ 及 $ssum$ 之中分別檢查使 $x$ 歸零需要取的數量,這樣寫很麻煩。提示中有說,改成計算連續子陣列的和,假設 $nums$ 加總為 $total$,則我們要找的連續子陣列和為 $target = total - x$,如果 $target = 0$ 回傳 $nums$ 的長度 $n$;如果 $target$ 是其它的值,則用滑動視窗找區間和等於 $target$ 的最長子陣列長度 $length$,答案為 $n - length$。

Python 程式碼


Runtime: 71 ms, beats 76.89%. Memory: 30.84 MB, beats 71.36%.
class Solution:
    def minOperations(self, nums: list[int], x: int) -> int:
        n = len(nums)  # 數量
        target = sum(nums) - x  # 最長子陣列和目標值,等於全部的元素加總 - x
        # 特例,如果目標值為 0,全部都要刪掉,回傳 n
        if target == 0: return n  
        
        # 一般狀況,用滑動視窗找加總等於目標值的最長子陣列
        ans = n + 1  # 答案設定成不可能的值 n + 1
        left = 0  # 左端點
        isum = 0  # 區間和
        for right in range(n):  # 掃過右端點 0 ~ n-1
            isum += nums[right]  # 更新區間和
            # 如果左、右端點未重合,區間和大於目標值,移除左端點
            while left < right and isum > target:
                isum -= nums[left]
                left += 1
            # 如果區間和等於目標值,更新答案
            if isum == target:
                length = right - left + 1
                ans = min(ans, n - length)
        # 如果答案不是預設值回傳答案,反之回傳 -1
        return ans if ans < n + 1 else -1


2026年9月22日 星期二

LeetCode 解題筆記:739. Daily Temperatures

作者:王一哲
日期:2026年9月22日


LeetCode 題目連結:739. Daily Temperatures

解題想法


中等難度題,題目一個表示每日氣溫的整數陣列 $temperatures$,要找出每一天要在幾天之後才會遇到更高的氣溫,如果之後沒有任何一天的氣溫更高,則當天的答案為 0。這題很適合用堆疊處理,開一個堆疊 $st$,用來記錄目前已經讀到、而且還沒有找到答案的氣溫及索引值。用一個 for 迴圈讀取每天的氣溫 $t$ 及索引值 $i$;再用一個 while 迴圈,如果 $st$ 之中有資料而且 $t$ 大於 $st$ 最後一項的氣溫,移除 $st$ 的最後一項,如果這項的索引值為 $pre$,則這項對應的答案為 $pre - i$;跑完 while 迴圈之後再加入 $(t, i)$。

Python 程式碼


Runtime: 97 ms, beats 50.76%. Memory: 34.32 MB, beats 21.54%.
class Solution:
    def dailyTemperatures(self, temperatures: list[int]) -> list[int]:
        ans = [0] * len(temperatures)  # 答案
        st = []  # 堆疊,放入 (t, idx)
        
        for i, t in enumerate(temperatures):
            # 如果 st 有資料,t 大於 st 最後一項的溫度
            while st and t > st[-1][0]:
                pre = st.pop()[1]  # 移除 st 最後一項
                ans[pre] = i - pre  # 這項的索引值答案為 i - pre
            # (t, i) 加入 st
            st.append((t, i))
        return ans


2026年9月21日 星期一

LeetCode 解題筆記:3524. Find X Value of Array I

作者:王一哲
日期:2026年9月21日


LeetCode 題目連結:3524. Find X Value of Array I

解題想法


中等難度題,題目正整數陣列 $nums$、一個正整數 $k$,可以移 $nums$ 之中移除不重疊的前綴子陣列及後綴子陣列,使 $nums$ 乘下的元素積乘對 $k$ 取餘數,計算得到各種餘數有幾種方法數。題目下方有提示:
  1. 用動態規畫解題。
  2. 定義 $dp[i][r]$ 為以索引值 $i$ 為結尾的元素乘積,對 $k$ 取餘數為 $r$ 的方法數。
  3. 將每一個索引值的 $dp[i][r]$ 加起來,計算答案 $ans[r]$。
基本上按照提示寫程式碼,應該就可以得到答案。在更新 $dp$ 陣列的過程中,每次相乘後都要對 $k$ 取餘數,可以避免數字過大。而且更新 $dp$ 時只需要用到前一個數字的狀態,可以用滾動陣列節省記憶體。

Python 程式碼


Runtime: 435 ms, beats 26.32%. Memory: 46.36 MB, beats 19.74%.
class Solution:
    def resultArray(self, nums: List[int], k: int) -> List[int]:
        n = len(nums)
        # dp[i][j] 代表以索引值 i-1 結尾,其元素乘積除以 k 餘數為 j 的子陣列數量
        dp = [[0]*k for _ in range(n+1)]
        ans = [0]*k  # 答案
        for i in range(1, n+1):
            # nums[i-1] 為長度 1 的子陣列
            rem = nums[i-1] % k
            dp[i][rem] = 1
            # 內層迴圈,跑 j = 0 ~ k-1,將目前的數字接在前一個結尾的子陣列之後
            for j in range(k):
                if dp[i-1][j] > 0:  # 如果有前一個結尾對應的子陣列數量
                    new_rem = (j * rem) % k
                    dp[i][new_rem] += dp[i-1][j]
            # 將這一回産生的答案都加到 ans
            for j in range(k):
                ans[j] += dp[i][j]
        # 回傳答案
        return ans


2026年9月20日 星期日

LeetCode 解題筆記:3498. Reverse Degree of a String

作者:王一哲
日期:2026年9月20日


LeetCode 題目連結:3498. Reverse Degree of a String

解題想法


簡單題,題目給一個字串 $s$,將 $s[i]$ 換算成 $26 - (s[i] - 'a')$,再乘以 $i+1$,將全部的值加起來並回傳,用一個 for 迴圈就解決了。

Python 程式碼


Runtime: 3 ms, beats 97.65%. Memory: 19.30 MB, beats 55.57%.
class Solution:
    def reverseDegree(self, s: str) -> int:
        ans = 0
        for i, c in enumerate(s, start=1):
            ans += (26 - ord(c) + ord('a')) * i
        return ans


Runtime: 3 ms, beats 97.65%. Memory: 19.32 MB, beats 19.13%.
class Solution:
    def reverseDegree(self, s: str) -> int:
        return sum((26 - ord(c) + ord('a')) * i for i, c in enumerate(s, start=1))


2026年9月19日 星期六

LeetCode 解題筆記:1401. Circle and Rectangle Overlapping

作者:王一哲
日期:2026年9月19日


LeetCode 題目連結:1401. Circle and Rectangle Overlapping

解題想法


中等難度題,題目給一個圓的圓心坐標 $(xCenter, yCenter)$ 及半徑 $radius$,一個長方形的左下角頂點坐標 $(x1, y1)$,右上角頂點坐標 $(x2, y2)$,回傳這個圓形與長方形是否重疊,只要邊緣接觸到就當作重疊。這題的下方有提示,計算圓心與長方形最接近的點之間的距離,再判斷距離是否小於等於半徑,基本上按照這個提示寫程式就能過關。另外有一個狀況要記得考慮,圓心可能在長方形之中,這樣也是重疊,要回傳 True。

Python 程式碼


Runtime: 0 ms, beats 100.00%. Memory: 19.18 MB, beats 100.00%.
class Solution:
    def checkOverlap(self, radius: int, xCenter: int, yCenter: int, x1: int, y1: int, x2: int, y2: int) -> bool:
        d = 0
        # 如果圓心在長方形內
        if x1 <= xCenter <= x2 and y1 <= yCenter <= y2:
            return True
        # 如果圓心在長方形左上角
        elif xCenter <= x1 and yCenter >= y2:
            d = (xCenter - x1)**2 + (yCenter - y2)**2
        # 如果圓心在長方形左下角
        elif xCenter <= x1 and yCenter <= y1:
            d = (xCenter - x1)**2 + (yCenter - y1)**2
        # 如果圓心在長方形左側
        elif xCenter <= x1 and y1 < yCenter < y2:
            d = (xCenter - x1)**2
        # 如果圓心在長方形右上角
        elif xCenter >= x2 and yCenter >= y2:
            d = (xCenter - x2)**2 + (yCenter - y2)**2
        # 如果圓心在長方形右下角
        elif xCenter >= x2 and yCenter <= y1:
            d = (xCenter - x2)**2 + (yCenter - y1)**2
        # 如果圓心在長方形右側
        elif xCenter >= x2:
            d = (xCenter - x2)**2
        # 如果圓心在長方形上方
        elif yCenter >= y2:
            d = (yCenter - y2)**2
        # 如果圓心在長方形下方
        elif yCenter <= y1:
            d = (yCenter - y1)**2
        return d <= radius**2


2026年9月18日 星期五

LeetCode 解題筆記:735. Asteroid Collision

作者:王一哲
日期:2026年9月18日


LeetCode 題目連結:735. Asteroid Collision

解題想法


中等難度題,題目給一個陣列 $asteroids$,索引值代表小行星的位置,數值的絕對值代表小行星的大小,正值代表小行星向右移動,負值代表小行星向左移動,每個小行星的速度量值都相等。如果兩個小行星碰撞,會留下較大的小行星;如果兩者一樣大,兩者一起撞掉。題目要求最後留下來的小行星資料。

這題可以用堆疊解題。先建一個 list 或 vector $st$。用 for 迴圈依序讀取小行星的大小及移動方向 $a$,如果 st 是空的、st 最後一項是負的或者st 最後一項是正的且 a 是正的,不會碰撞,直接加入 a;如果發生碰撞,再用一個 while 迴圈,檢查 $a$ 與 $st$ 最後一項的關係,再決定是否移除 $st$ 的最後一項,以及是否要將 $a$ 加入 $st$。詳細的過程請看程式碼及註解。

Python 程式碼


Runtime: 3 ms, beats 94.41%. Memory: 20.38 MB, beats 33.14%.
class Solution:
    def asteroidCollision(self, asteroids: List[int]) -> List[int]:
        st = []
        for a in asteroids:
            # 如果 st 是空的、st 最後一項是負的或者st 最後一項是正的且 a 是正的,不會碰撞,直接加入 a
            if not st or st[-1] < 0 or (st[-1] > 0 and a > 0):
                st.append(a)
            else:  # 發生碰撞
                add_new = True  # 是否加入 a
                # 如果 st 有資料、 st 最後一項是正的且 a 是負的,發生碰撞
                while st and st[-1] > 0 and a < 0:
                    # a 量值較大,st 最後一項被撞掉
                    if abs(a) > st[-1]:
                        st.pop()
                    # 一樣大,st 最後一項及 a 同時被撞掉
                    elif abs(a) == st[-1]:
                        st.pop()
                        add_new = False
                        break
                    # a 較小,a 被撞掉
                    else:
                        add_new = False
                        break
                # 加入 a
                if add_new: st.append(a)
        return st


2026年9月17日 星期四

LeetCode 解題筆記:1477. Find Two Non-overlapping Sub-arrays Each With Target Sum

作者:王一哲
日期:2026年9月17日


LeetCode 題目連結:1477. Find Two Non-overlapping Sub-arrays Each With Target Sum

解題想法


中等難度題,題目給一個陣列 $arr$ 與一個整數 $target$,要找出 $2$ 個不重疊、加總等於 $target$ 的連續子陣列,回傳這兩個子陣列長度相加的最小值。題目下方有提示:
  1. 建立一個陣列 $prefix$,$prefix[i]$ 代表在 $i$ 之前結束、加總等於 $target$ 的連續子陣列最短長度。再建立一個陣列 $suffix$,$suffix[i]$ 代表從 $i$ 開始、加總等於 $target$ 的連續子陣列最短長度。
  2. 檢查 $i = 0$ 到 $i = n-1$,找出 $prefix[i] + suffix[i]$ 的最小值。
  3. 如果在建立 $prefix, suffix$ 時遇到困難,可以先將所有的值都設定成無窮大,分別先找出加總等於 $target$ 的前綴、後綴連續子陣列長度,之後再轉換成最短長度。
基本上按照以上的提示寫程式碼就可以過關了。

Python 程式碼


Runtime: 260 ms, beats 29.61%. Memory: 31.07 MB, beats 83.24%.
class Solution:
    def minSumOfLengths(self, arr: List[int], target: int) -> int:
        n = len(arr)  # 長度
        maxn = n + 1  # 長度加 1,答案不可能大於 n
        
        # 1. 用滑動視窗找 prefix
        prefix = [maxn] * n  # prefix[i] 代表於 i-1 結束,子陣列和等於 target 的長度
        rsum, left = 0, 0
        for right in range(n - 1):
            rsum += arr[right]
            while left < right and rsum > target:
                rsum -= arr[left]
                left += 1
            if rsum == target:
                length = right - left + 1
                prefix[right + 1] = min(prefix[right + 1], length)
        
        # 2. 再將 prefix[i] 改成從左往右找,於 i-1 結束、子陣列和等於 target 的最短長度
        pmin = maxn
        for i in range(1, n):
            if prefix[i] < pmin:
                pmin = prefix[i]
            elif prefix[i] > pmin:
                prefix[i] = pmin
        
        # 3. 用滑動視窗找 prefix
        suffix = [maxn] * n  # suffix[i] 代表於 i 結束,後綴子陣列和等於 target 的長度
        lsum, ri = 0, n-1
        for le in range(n-1, -1, -1):
            lsum += arr[le]
            while ri > le and lsum > target:
                lsum -= arr[ri]
                ri -= 1
            if lsum == target:
                length = ri - le + 1
                suffix[le] = min(suffix[le], length)
        
        # 4. 再將 suffix[i] 改成從右往左找,於 i 結束、後綴子陣列和等於 target 的最短長度
        smin = maxn
        for i in range(n-1, -1, -1):
            if suffix[i] < smin:
                smin = suffix[i]
            elif suffix[i] > smin:
                suffix[i] = smin

        # 5. 答案預設為 maxn * 2,從 i = 0 ~ n-1 找 prefix[i] + suffix[i] 的最小值
        ans = maxn * 2
        for i in range(n):
            if prefix[i] < maxn and suffix[i] < maxn:
                ans = min(ans, prefix[i] + suffix[i])
        # 如果 ans 小於預設值,回傳 ans;反之回傳 -1
        return ans if ans < maxn * 2 else -1


ZeroJudge 解題筆記:a007.判斷質數

作者:王一哲
日期:2026年9月17日


ZeroJudge 題目連結:a007.判斷質數

解題想法


這題要判斷讀取到的整數 $x$ 是否為質數,且 $2 \leq x \leq 2147483647$,最大值為 int 的上限。由於測資為多筆輸入,且最多有 $200000$ 筆,需要想辦法加速才行。這題的提示是建表,出題者的意思是建一個 $46340$ 以內的質數表,因為 $\sqrt{2147483647} \approx 46340$,如果要判斷 $x$ 是否為質數,只要用小於 $\sqrt{x}$ 的質數試除,如果 $x$ 可以被某個質數整除,則 $x$ 不是質數;如果所有小於 $\sqrt{x}$ 的質數都無法整除 $x$,則 $x$ 不是質數。所以解題時先用埃拉托斯特尼篩法建 $46340$ 以內的質數表,再用 while 迴圈讀取 $x$ 直到 EOF 為止,取出質數表中小於 $\sqrt{x}$ 的質數試除即可得到答案。但是這題如果用 Python 解題,即使按這個個邏輯寫程式碼也會超時,需要用比較特別的判斷質數方法才能過關。

C++ 程式碼


解題時間約為 0.6 s,使用記憶體約為 3.8 MB。
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    const int maxn = 46340;  // sqrt(2147483647)
    vector<bool> sieve (maxn + 1, true);
    sieve[0] = false;
    sieve[1] = false;
    for(int i = 2; i <= (int)sqrt(maxn); i++) {
        if (sieve[i]) {
            for(int j = i*i; j <= maxn; j += i) {
                sieve[j] = false;
            }
        }
    }
    vector<int> primes;
    for(int i = 0; i <= maxn; i++) {
        if (sieve[i]) {
            primes.push_back(i);
        }
    }
    
    int x;
    while(cin >> x) {
        bool is_prime = true;
        for(int p : primes) {
            if (p*p > x) break;
            if (x % p == 0) {
                is_prime = false;
                break;
            }
        }
        cout << (is_prime ? "質數\n" : "非質數\n");
    }
    return 0;
}