置頂

GeoGebra 文章目錄

GeoGebra 文章目錄  更新日期:2018/2/8 我將 GeoGebra 相關的文章及檔案連結都整理在這篇裡,之後如果有新的文章也會同時更新這個目錄。上傳到 GeoGebraTube 的檔案,我有試著用 Google Chrome 63.0.3239.13...

熱門文章

2026年6月12日 星期五

LeetCode 解題筆記:3559. Number of Ways to Assign Edge Weights II

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


LeetCode 題目連結:3559. Number of Ways to Assign Edge Weights II

解題想法


困難題。由於節點數量 $n$ 可能多達 $10^5$ 個,用暴力法找出任意兩個節點之間的距離一定會超時,需要利用倍增法 (binary lift) 找出要查詢的兩個節點的最近共同祖先 (lowest common ancestor, LCA),主要分成以下的步驟:
  • 用接鄰矩陣存圖
  • 先開好需要用到的串列,例如儲存每個節點深度用的 $depth$,儲存節點 $i$ 的第 $2^j$ 代祖先的二維串列 $up$。
  • 廣度優先搜尋 (BFS) 初始化 depth 和第 1 代祖先 up[i][0]
  • 建立倍增表 $up$,其中 $up[i][j]$ 代表節點 $i$ 的第 $2^j$ 代祖先,由於 $2^j = 2^{j-1} \times 2^{j-1}$,因此建表時 $up[i][j] = up[up[i][j-1]][j-1]$。
  • 寫一個查詢最近共同祖先的自訂函式 get_lca,找出代入的節點 $u, v$ 的最近共同祖先。
  • 假設要查詢的節點為 $u, v$,最短距離 $dist = depth[u] + depth[v] - 2 \times depth[lca]$,答案為 $(2^{dist - 1}) % 1000000007$。
雖然可以 AC 但速度不快,需要再改進。

Python 程式碼


Runtime: 1463 ms, beats 35.85%. Memory: 99.75 MB, beats 88.68%.
from collections import deque

class Solution:
    def assignEdgeWeights(self, edges: List[List[int]], queries: List[List[int]]) -> List[int]:
        # --- 用接鄰矩陣存圖 ---
        n = len(edges) + 1
        adj = [[] for _ in range(n+1)]
        for u, v in edges:
            adj[u].append(v)
            adj[v].append(u)

        # --- 用倍增法找節點 i 的第 2**j 代祖先
        LOG = 20  # 2**17 > 10**5,取 20 一定夠用
        depth = [0] * (n+1)  # 節點的深度
        up = [[1] * LOG for _ in range(n+1)]  # 儲存節點 i 的第 2**j 代祖先
        
        # --- 用 BFS 初始化 depth 和第 1 代祖先 up[i][0]
        que = deque([1])
        visited = {1}
        depth[1] = 0
        
        while que:
            u = que.popleft()
            for v in adj[u]:
                if v not in visited:
                    visited.add(v)
                    depth[v] = depth[u] + 1
                    up[v][0] = u  # v 的父節點為 u
                    que.append(v)
        
        # --- 建立倍增表 (binary lift)
        # up[i][j] 為 i 的第 2**(j-1) 代祖先的第 2**(j-1) 代祖先
        for j in range(1, LOG):  # 控制代數的 j 要放外㽪
            for i in range(1, n+1):
                up[i][j] = up[up[i][j-1]][j-1]  # 因為 2**j = 2**(j-1) * 2**(j-1)
                
        # --- 查詢最近共同祖先 (lowest common ancestor, LCA)
        def get_lca(u, v):
            # 防呆,確保 u 是比較深的節點
            if depth[u] < depth[v]:
                u, v = v, u
            # 將 u 往上提,直到跟 v 位於同一深度
            diff = depth[u] - depth[v]
            for j in range(LOG):
                if (diff >> j) & 1:
                    u = up[u][j]
            
            # 如果 u 往上提之後等於 u,直接回傳 u
            if u == v: return u
                
            # u 和 v 一起往上提,直到找到 LCA 的子節點
            for j in range(LOG - 1, -1, -1):
                if up[u][j] != up[v][j]:
                    u = up[u][j]
                    v = up[v][j]
                    
            return up[u][0] # 回傳 u 的父節點
        
        # --- 處理所有查詢 ---
        MOD = 10**9 + 7
        ans = []
        for u, v in queries:
            lca = get_lca(u, v)
            # 計算兩點在樹上的距離,邊的數量
            dist = depth[u] + depth[v] - 2 * depth[lca]
            
            if dist == 0:
                ans.append(0)
            else:
                ans.append(pow(2, dist - 1, MOD))
                
        return ans


2026年6月11日 星期四

LeetCode 解題筆記:3558. Number of Ways to Assign Edge Weights I

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


LeetCode 題目連結:3558. Number of Ways to Assign Edge Weights I

解題想法


中等難度題目。我先用接鄰矩陣 $adj$ 存樹,題目規定根節點編號為 $1$,父節點編號小於子節點編號,但是測資 $edges$ 之中的節點編號可能會是 $u > v$,如果遇到這個狀況要先交換 $u, v$ 之後再存入 $adj$之中。接下來寫一個自訂函式 $dfs$,從根節點 $1$ 開始往下走找最大深度 max_depth。最後答案為 2**(max_depth - 1) % 1000000007。由於答案很大而且需要取餘數,如果用 Python 解題可以直接用內建的 pow,但如果用 C++ 解題要自己寫快速冪。 證明答案的過程如下。假設有 $m$ 條邊,且 $m \geq 1$,選擇其中奇數條邊設定為 $1$ 可以符合題目的要求,因此組合數為 $$ \binom{m}{1} + \binom{m}{3} + \binom{m}{5} + \dots \binom{m}{m} = 2^{m-1} $$ 可以透過二項式定理 (binomial theorem) 導出這個結果。根據二項式展開式 $$ (1 + x)^m = \binom{m}{0} + \binom{m}{1}x + \binom{m}{2}x^2 + \dots + \binom{m}{m}x^m $$ 為了求出奇數項的總和,先對上式代入 $x = 1$,計算從 $m$ 條邊中隨意選擇任意數量的所有可能組合數 $$ 2^m = \binom{m}{0} + \binom{m}{1} + \binom{m}{2} + \binom{m}{3} + \dots + \binom{m}{m} $$ 再代入 $x = -1$,選擇偶數條邊的項,選擇奇數條邊的項變成負號。 $$ 0 = \binom{m}{0} - \binom{m}{1} + \binom{m}{2} - \binom{m}{3} + \dots + (-1)^m \binom{m}{m} $$ 第 1 式減去第 2 式,偶數項會被完全抵消,而奇數項會變成兩倍: $$ 2^m - 0 = 2 \times \left[ \binom{m}{1} + \binom{m}{3} + \binom{m}{5} + \dots \right] $$ 最後,將等式兩邊同除以 2,即可得到選擇奇數條邊的組合數總和: $$ \binom{m}{1} + \binom{m}{3} + \binom{m}{5} + \dots = \frac{2^m}{2} = 2^{m-1} $$

Python 程式碼


Runtime: 267 ms, beats 84.95%. Memory: 104.95 MB, beats 38.71%.
class Solution:
    def assignEdgeWeights(self, edges: List[List[int]]) -> int:
        n = len(edges) + 1  # n 個節點,n-1 條邊
        adj = [[] for _ in range(n+1)]  # 用接鄰矩陣存樹
        for u, v in edges:
            if u > v:
                u, v = v, u
            adj[u].append(v)

        # 用 dfs 找最大深度
        max_depth = 0

        def dfs(u, depth):
            nonlocal max_depth
            # 遞迴出口
            if not adj[u]:
                max_depth = max(max_depth, depth)
            # 繼續往下走
            for v in adj[u]:
                dfs(v, depth + 1)
        
        # 呼叫 dfs
        dfs(1, 0)  # 從根節點 1、深度 0 開始
        
        # 答案是 2**(max_depth - 1) % (10**9 + 7)
        return pow(2, max_depth - 1, 10**9 + 7)


2026年6月10日 星期三

LeetCode 解題筆記:3691. Maximum Total Subarray Value II

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


LeetCode 題目連結:3691. Maximum Total Subarray Value II

解題想法


困難題,昨天的 3689. Maximum Total Subarray Value I 加強版,難度提升很多。首先要建立稀疏表 (Sparse Tables),用 st_max[i][j] 儲存起點 $i$、長度 $2^{j}$ 的區間最大值,用 st_min[i][j] 儲存起點 $i$、長度 $2^{j}$ 的區間最小值。再用最大優先佇列 (max priority queue) $pq$ 儲存起點 $L = 0$ 到 $L = n-1$、終點皆為 $n-1$ 各區間的 `max_val - min_val`,從 $pq$ 之中取出 $k$ 個值。假設取出的區間為 $[L, R]$,先將值 $val$ 加到答案 $ans$ 之中,重新計算區間 $[L, R-1]$ 對應的值 val,再將 {val, L, R-1} 加入 $pq$ 之中。

Python 程式碼


Runtime: 3165 ms, beats 70.73%. Memory: 52.34 MB, beats 53.66%.
class Solution:
    def maxTotalValue(self, nums: List[int], k: int) -> int:
        import heapq

        n = len(nums)  # 長度
        # 特例,nums 是空的或是 k 等於 0,回傳 0
        if n == 0 or k == 0:
            return 0
        
        # 1. 預先計算 log2(0) ~ log2(n)
        log_table = [0] * (n+1)
        for i in range(2, n+1):
            log_table[i] = log_table[i//2] + 1
        log_max = log_table[n] + 1  # log2 的上限
        
        # 2. 初始化 Sparse Tables,table[i][j] 代表長度 2**j、起點 i 的區間極值
        st_max = [[0] * log_max for _ in range(n)]
        st_min = [[0] * log_max for _ in range(n)]
        for i in range(n):
            st_max[i][0] = nums[i]
            st_min[i][0] = nums[i]
        
        # 3. 建立 Sparse Tables
        for j in range(1, log_max):  # 1 ~ log2(n)
            step = 1 << (j - 1)  # 2**(j-1)
            for i in range(n - (1 << j) + 1):  # 起點 i
                st_max[i][j] = max(st_max[i][j-1], st_max[i + step][j-1])
                st_min[i][j] = min(st_min[i][j-1], st_min[i + step][j-1])
        
        # 4. 自訂區間查詢函式
        def query(L, R):
            length = R - L + 1
            j = log_table[length]
            max_val = max(st_max[L][j], st_max[R - (1 << j) + 1][j])
            min_val = min(st_min[L][j], st_min[R - (1 << j) + 1][j])
            return max_val - min_val
        
        # 5. 初始化最大優先佇列
        pq = []
        for L in range(n):  # 起點 L = 0 ~ L = n-1
            val = query(L, n-1)  # 取區間 [L, n-1] 極值
            heapq.heappush(pq, (-val, L, n-1))
        
        # 6. 取前 k 大的極值
        ans = 0
        for _ in range(k):
            neg_val, L, R = heapq.heappop(pq)
            ans += -neg_val
            # 更新 pq,同一個起點 L,新的終點 R-1
            if R > L:
                new_val = query(L, R-1)
                heapq.heappush(pq, (-new_val, L, R-1))
        # 7. 回傳答案
        return ans


2026年6月9日 星期二

LeetCode 解題筆記:3689. Maximum Total Subarray Value I

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


LeetCode 題目連結:3689. Maximum Total Subarray Value I

解題想法


中等難度的題目。題目給定一個陣列 $nums$,要從 $nums$ 之中選取 $k$ 個區間 $nums[l]$ 到 $nums[r]$,取區間的最大值、最小值相減,使 $k$ 個區間取出的相減結果加總最大。由於選取的區間可以重複,實際上這題就是取 $nums$ 的最大值 $imax$、最小值 $imin$,回傳 $(imax - imin) \times k$

Python 程式碼


使用 max 及 min 取極值,速度較慢。Runtime: 15 ms, beats 48.00%. Memory: 26.40 MB, beats 88.80%.
class Solution:
    def maxTotalValue(self, nums: List[int], k: int) -> int:
        imin, imax = min(nums), max(nums)
        return (imax - imin) * k

不使用 max 及 min 取極值,速度較快。Runtime: 11 ms, beats 69.60%. Memory: 25.75 MB, beats 100.00%.
class Solution:
    def maxTotalValue(self, nums: List[int], k: int) -> int:
        imin, imax = float('inf'), -float('inf')
        for num in nums:
            if num < imin:
                imin = num
            if num > imax:
                imax = num
        return (imax - imin) * k


2026年6月8日 星期一

LeetCode 解題筆記:2161. Partition Array According to Given Pivot

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


LeetCode 題目連結:2161. Partition Array According to Given Pivot

解題想法


中等難度題目。題目給一個串列 $nums$ 及樞紐值 $pivot$,要將 $nums$ 的值重新排列,小於 $pivot$ 的值按照原來的順序放在 $pivot$ 的左側,大於 $pivot$ 的值按照原來的順序放在 $pivot$ 的右側,中間是 $pivot$。$pivot$ 一定等於 $nums$ 其中一個或多個數字。我的想法是開兩個串列,其中 $ans$ 用來儲存答案,$large$ 用來儲存大於 $pivot$ 的值在 $nums$ 之中的索引值,另外用一個變數 $cnt$ 記錄 $pivot$ 於 $nums$ 之中的數量。接下來用一個 for 迴圈掃過 $nums$ 取出數值 $num$,如果 $num$ 等於 $pivot$ 就將 $cnt$ 加 1;如果 $num$ 小於 $pivot$ 就將 $num$ 加到 $ans$ 最後面;如果 $num$ 大於 $pivot$ 就將索引值 $i$ 加到 large。將 $cnt$ 個 $pivot$ 接到 $ans$ 之後。最後用一個 for 迴圈,取出 $large$ 記錄的索引值 $i$,將 $nums[i]$ 加到 $ans$ 最後面。程式碼再微調一下應該會更快。

Python 程式碼


Runtime: 40 ms, beats 33.75%. Memory: 34.31 MB, beats 5.07%.
class Solution:
    def pivotArray(self, nums: List[int], pivot: int) -> List[int]:
        ans, large = [], []  # 答案,大於 pivot 的索引值
        cnt = 0  # 等於 pivot 的數字數量
        for i, num in enumerate(nums):  # 依序讀取 nums 的索引值及數字
            if num == pivot:  # num 等於 pivot
                cnt += 1  # 數量加 1
            elif num < pivot:  # num 小於 pivot
                ans.append(num)  # num 加入 ans
            else:  # nums 大於 pviot
                large.append(i)  # 記錄索引值 i
        
        # ans 加上 cnt 個 pivot
        ans += [pivot] * cnt
        # 將 large 對應的數字接到 ans 後面
        for i in large:
            ans.append(nums[i])
        # 回傳答案
        return ans

用索引值找到填入數值的位置,速度反而比較慢。Runtime: 58 ms, beats 18.36%. Memory: 34.23 MB, beats 5.83%.
class Solution:
    def pivotArray(self, nums: List[int], pivot: int) -> List[int]:
        n, le = len(nums), 0  # 長度,從左側填數字的索引值
        ans, large = [0]*n, []  # 答案,大於 pivot 的索引值
        cnt = 0  # 等於 pivot 的數字數量
        for i, num in enumerate(nums):  # 依序讀取 nums 的索引值及數字
            if num == pivot:  # num 等於 pivot
                cnt += 1  # 數量加 1
            elif num < pivot:  # num 小於 pivot
                ans[le] = num  # num 填入 ans[le]
                le += 1  # 向右移 1 格
            else:  # nums 大於 pviot
                large.append(i)  # 記錄索引值 i
        
        # ans[le] ~ ans[le + cnt] 填入 pivot
        ans[le : le+cnt] = [pivot] * cnt
        le += cnt  # 向右移 cnt 格
        # 將 large 對應的數字接到 ans 後面
        for i in large:
            ans[le] = nums[i]
            le += 1
        # 回傳答案
        return ans


2026年6月7日 星期日

LeetCode 解題筆記:2196. Create Binary Tree From Descriptions

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


LeetCode 題目連結:2196. Create Binary Tree From Descriptions

解題想法


中級難度的題目。要用鏈結串列建二元樹。我先將每個節點的父節點、左子樹、右子樹分別存入字典之中,另外用一個集合儲存所有的節點。再找根節點,也就是所有節點當中唯一一個沒有父節點的節點。最後再用 BFS 或遞迴建樹。以下的程式碼速度不快,還可以再改進。

Python 程式碼


用 list 儲存待走訪的節點。Runtime: 202 ms, beats 15.04%. Memory: 28.30 MB, beats 24.19%.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def createBinaryTree(self, descriptions: List[List[int]]) -> Optional[TreeNode]:
        # 儲存所有的節點值,記錄每個節點的父節點、左子節點、右子節點
        parent = dict()
        left_child = dict()
        right_child = dict()
        all_nodes = set()
        for p, c, lr in descriptions:
            all_nodes.add(p)
            all_nodes.add(c)
            parent[c] = p
            if lr == 1:
                left_child[p] = c
            else:
                right_child[p] = c
        # 找出根節點的值
        root_val = -1
        for node in all_nodes:
            if node not in parent:
                root_val = node
                break
        # 用 list 建樹
        root = TreeNode(val=root_val)
        que = [root]
        head = 0
        while head < len(que):
            curr = que[head]
            head += 1
            if curr.val in left_child:
                curr.left = TreeNode(val = left_child[curr.val])
                que.append(curr.left)
            if curr.val in right_child:
                curr.right = TreeNode(val = right_child[curr.val])
                que.append(curr.right)
        return root

2026年6月6日 星期六

LeetCode 解題筆記:2574. Left and Right Sum Differences

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


LeetCode 題目連結:2574. Left and Right Sum Differences

解題想法


簡單難度的題目。假設陣列 $nums$ 長度為 $n$,先建立前綴和 $psum$ 及後綴和 $ssum$,為了在計算區間和、取索引值時比較方便,我將 $psum$ 及 $ssum$ 的長度都開成 $n+2$。再建立儲存答案用的陣列 $ans$,長度為 $n$。用一層 for 迴圈掃過 $i = 1$ 到 $i = n$,利用 $psum$ 及 $ssum$ 計算答案存入 $ans$。

Python 程式碼


Runtime: 7 ms, beats 34.68%. Memory: 19.48 MB, beats 57.43%.
class Solution:
    def leftRightDifference(self, nums: List[int]) -> List[int]:
        n = len(nums)
        # 前綴和
        psum = [0] + nums[:] + [0]
        for i in range(1, n+1):
            psum[i] += psum[i-1]
        # 後綴和
        ssum = [0] + nums[:] + [0]
        for i in range(n, 0, -1):
            ssum[i] += ssum[i+1]
        # 用前綴和及後綴和計算答案
        ans = [0]*n
        for i in range(1, n+1):
            ans[i-1] = abs(psum[i] - ssum[i])
        return ans


2026年6月5日 星期五

LeetCode 解題筆記:1. Two Sum

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


LeetCode 題目連結:1. Two Sum

解題想法


簡單難度的題目。可以用兩層 for 迴圈掃過所有從 $nums$ 取 2 個數字相加,時間複雜度 $O(n^2)$。

可以配合二分搜尋法將時間複雜度降成 $O(n log n)$。先將 $nums$ 的值 $num$ 及索引值 $i$ 組合成 tuple 存入串列 $arr$,將 $arr$ 依照 $num$ 排序,再取出排序後的值存成串列 sorted_nums 用於二分搜尋法。用一層 for 迴圈掃過 sorted_nums,其中第 $i$ 個值對應的目標值為 new_target = target - sorted_nums[i],用 bisect_left 於 sorted_nums 之中搜尋 new_target,如果有找到 new_target,再從 $arr$ 之中找出對應 $nums$ 的索引值並回傳答案。

Python 程式碼


$O(n^2)$ 解。Runtime: 1719 ms, beats 25.03%. Memory: 19.81 MB, beats 74.11%.
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # O(n**2) 暴力解
        n = len(nums)
        for i in range(n-1):
            for j in range(i+1, n):
                if nums[i] + nums[j] == target:
                    return [i, j]
        # 預設的回傳值,題目保證有解,用不到
        return [-1, -1]

$O(n log n)$ 解。Runtime: 7 ms, beats 34.41%. Memory: 20.80 MB, beats 8.08%.
from bisect import bisect_left

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # O(n log n) 解
        arr = [(num, i) for i, num in enumerate(nums)]  # 取出值 num 及索引值 i
        arr.sort()  # 排序
        sorted_nums = [num for num, _ in arr]  # 取出排序後的值,用於二分搜
        n = len(nums)
        for i in range(n-1):
            new_target = target - sorted_nums[i]
            j = bisect_left(sorted_nums, new_target, i+1)
            if j < n and sorted_nums[j] == new_target:
                return [arr[i][1], arr[j][1]]
        # 預設的回傳值,題目保證有解,用不到
        return [-1, -1]


2026年6月4日 星期四

LeetCode 解題筆記:3751. Total Waviness of Numbers in Range I

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


LeetCode 題目連結:3751. Total Waviness of Numbers in Range I

解題想法


題目要計算指定閉區間 $[num1, num2]$ 之間的數字有幾個山峰或山谷,山峰是指兩側的數字皆小於中間的數字,山谷是指兩側的數字皆大於中間的數字。這題最直接的想法,是用一個 for 迴圈掃過 $[num1, num2]$ 之間所有的數字 $i$,再將 $i$ 轉成字串 $s$。假設字串長度為 $n$,用一個 for 迴圈從索引值 $j = 1$ 檢查到索引值 $j = n-2$,如果 $s[j] > s[j-1] ~\text{and}~ s[j] > s[j+1]$ 或 $s[j] < s[j-1] ~\text{and}~ s[j] < s[j+1]$,就將答案加1。 我本來還在想應該有更好的解法,沒想到底下的提示直接寫 Use bruteforce,而且這個暴力解的速度還不慢,就暫時先這樣寫了。

Python 程式碼


Runtime: 235 ms, beats 90.42%. Memory: 19.27 MB, beats 69.73%.
class Solution:
    def totalWaviness(self, num1: int, num2: int) -> int:
        cnt = 0
        for i in range(num1, num2 + 1):
            s = str(i)
            n = len(s)
            for j in range(1, n-1):
                if (s[j] > s[j-1] and s[j] > s[j+1]) or \
                   (s[j] < s[j-1] and s[j] < s[j+1]):
                    cnt += 1
        return cnt


2026年6月3日 星期三

LeetCode 解題筆記:3635. Earliest Finish Time for Land and Water Rides II

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


LeetCode 題目連結:3635. Earliest Finish Time for Land and Water Rides II

解題想法


3633. Earliest Finish Time for Land and Water Rides I 的加強版,由於測資數量大很多,不能用簡單版的寫法,速度太慢。

Python 程式碼


Runtime: 778 ms, beats 7.27%. Memory: 39.98 MB, beats 12.73%.
from bisect import bisect_right

class Solution:
    def earliestFinishTime(self, landStartTime: List[int], landDuration: List[int], waterStartTime: List[int], waterDuration: List[int]) -> int:
        n, m = len(landStartTime), len(waterStartTime)
        # 合併開始、經過時刻,組成 (開始時刻, 經過時間) 
        land = list(zip(landStartTime, landDuration))
        water = list(zip(waterStartTime, waterDuration))
        land.sort()  # 依照開始時刻排序
        water.sort()
        
        # 取出排序後的開始時刻,用於二分搜尋法
        landStart = [s for s, _ in land]
        waterStart = [s for s, _ in water]

        # 前綴最短持續時間
        landDurationMin = [0] * n
        landDurationMin[0] = land[0][1]
        for i in range(1, n):
            landDurationMin[i] = min(landDurationMin[i-1], land[i][1])
        
        waterDurationMin = [0] * m
        waterDurationMin[0] = water[0][1]
        for i in range(1, m):
            waterDurationMin[i] = min(waterDurationMin[i-1], water[i][1])
        
        # 後綴最早結束時刻
        landFinishMin = [0] * n
        landFinishMin[-1] = land[-1][0] + land[-1][1]
        for i in range(n-2, -1, -1):
            landFinishMin[i] = min(landFinishMin[i+1], land[i][0] + land[i][1])
        
        waterFinishMin = [0] * m
        waterFinishMin[-1] = water[-1][0] + water[-1][1]
        for i in range(m-2, -1, -1):
            waterFinishMin[i] = min(waterFinishMin[i+1], water[i][0] + water[i][1])
        
        # 先去 land 再去 water 的最早結束時刻
        ans = float('inf')  # 答案
        for landS, landD in land:
            landF = landS + landD  # 結束時刻
            # 用二分搜尋法從 waterStart 之中找分界點 idx
            # idx 左側,已開放,可以直接玩
            # idx 右側,尚未開放,要等一段時間
            idx = bisect_right(waterStart, landF)
            currMin = float('inf')  # 目前的最短時間
            # 狀況1,可以直接玩已開放的設施,取 landF 加上 0 ~ idx-1 最短持續時間
            if idx > 0:
                currMin = min(currMin, landF + waterDurationMin[idx - 1])
            # 狀況2,等待還沒有開放的設施,取 idx ~ m-1 最早結束時刻
            if idx < m:
                currMin = min(currMin, waterFinishMin[idx])
            # 更新 ans
            ans = min(ans, currMin)
        
        # 先去 water 再去 land 的最早結束時刻
        for waterS, waterD in water:
            waterF = waterS + waterD  # 結束時刻
            # 用二分搜尋法從 landStart 之中找分界點 idx
            # idx 左側,已開放,可以直接玩
            # idx 右側,尚未開放,要等一段時間
            idx = bisect_right(landStart, waterF)
            currMin = float('inf')  # 目前的最短時間
            # 狀況1,可以直接玩已開放的設施,取 waterF 加上 0 ~ idx-1 最短持續時間
            if idx > 0:
                currMin = min(currMin, waterF + landDurationMin[idx - 1])
            # 狀況2,等待還沒有開放的設施,取 idx ~ n-1 最早結束時刻
            if idx < n:
                currMin = min(currMin, landFinishMin[idx])
            # 更新 ans
            ans = min(ans, currMin)
        # 回傳答案
        return ans