難得的大爆發,雖然排名會和中文站(LCCN) 合併,但只看英文站(LCUS) 的排名的話,也算是進前百了。Q3因為一個小失誤吃了 WA , Q4一直影響不大的地方嘗試優化,結果一直 TLE 。如果能冷靜點,少吃點罰時,就真的是前百了。
All problems solved by python
題意
給定一個長度為 偶數 的陣列 nums
,每次從 n u m s nums n u m s 中取出 兩個 最小 的元素,並 反序 添加到 a r r arr a rr 中,直到 n u m s nums n u m s 為空。返回 a r r arr a rr 。
思路:Heap 模擬 / 排序後交換
方法一:Heap 模擬
建立一個Min Heap,按照題意模擬即可。
時間複雜度:O ( n log n ) O(n \log n) O ( n log n ) ,heapify
的時間複雜度為 O ( n ) O(n) O ( n ) ,heappop
的時間複雜度為 O ( log n ) O(\log n) O ( log n ) ,總共執行 n n n 次。
空間複雜度:O ( n ) O(n) O ( n )
方法二:排序後交換
先由小到大排序,再倆倆一對,交換相鄰的元素。
時間複雜度:O ( n log n ) O(n \log n) O ( n log n ) ,排序的時間複雜度為 O ( n log n ) O(n \log n) O ( n log n ) ,交換的時間複雜度為 O ( 1 ) O(1) O ( 1 ) ,總共執行 n / 2 n/2 n /2 次。
空間複雜度:O ( 1 ) O(1) O ( 1 ) ,原地修改,不需要額外空間。
1 2 3 4 5 6 7 8 9 10 11 class Solution : def numberGame (self, nums: List [int ] ) -> List [int ]: ans = [] hp = [x for x in nums] heapify(hp) while len (hp) > 1 : x = heappop(hp) y = heappop(hp) ans.append(y) ans.append(x) return ans
1 2 3 4 5 6 7 class Solution : def numberGame (self, nums: List [int ] ) -> List [int ]: n = len (nums) nums.sort() for i in range (1 , n, 2 ): nums[i-1 ], nums[i] = nums[i], nums[i-1 ] return nums
賽時的寫法有點繞,用了差分+前綴和,但其實和直接暴力枚舉是一樣的,這裡改成比較直觀的寫法。
題意
給定一個 ( m − 1 ) × ( n − 1 ) (m-1) \times (n-1) ( m − 1 ) × ( n − 1 ) 的土地田地,其兩個對角座標分別是 ( 1 , 1 ) (1, 1) ( 1 , 1 ) 和 ( m , n ) (m, n) ( m , n ) ,土地內部有一些水平柵欄和垂直柵欄,分別由陣列 h F e n c e s hFences h F e n ces 和 v F e n c e s vFences v F e n ces 表示。水平柵欄的座標為 ( h F e n c e s [ i ] , 1 ) (hFences[i], 1) ( h F e n ces [ i ] , 1 ) 到 ( h F e n c e s [ i ] , n ) (hFences[i], n) ( h F e n ces [ i ] , n ) ,垂直柵欄的座標為 ( 1 , v F e n c e s [ i ] ) (1, vFences[i]) ( 1 , v F e n ces [ i ]) 到 ( m , v F e n c e s [ i ] ) (m, vFences[i]) ( m , v F e n ces [ i ]) 。
現在要移除一些柵欄,使得剩下的柵欄能形成一個最大面積的正方形田地,並返回這個正方形田地的面積。如果無法形成正方形田地則返回 − 1 -1 − 1 ,由於答案可能很大,所以請傳回結果對 1 0 9 + 7 10^9 + 7 1 0 9 + 7 取餘 後的值。
注意:土地外圍兩個水平柵欄(座標 ( 1 , 1 ) (1, 1) ( 1 , 1 ) 到 ( 1 , n ) (1, n) ( 1 , n ) 和座標 ( m , 1 ) (m, 1) ( m , 1 ) 到 ( m , n ) (m, n) ( m , n ) )以及兩個垂直柵欄(座標 ( 1 , 1 ) (1, 1) ( 1 , 1 ) 到 ( m , 1 ) (m , 1) ( m , 1 ) 和座標 ( 1 , n ) (1, n) ( 1 , n ) 到 ( m , n ) (m, n) ( m , n ) )作為邊界,不能 被移除。
限制
3 ≤ m , n ≤ 1 0 9 3 \leq m, n \leq 10^9 3 ≤ m , n ≤ 1 0 9
1 ≤ h F e n c e s . l e n g t h , v F e n c e s . l e n g t h ≤ 600 1 \leq hFences.length, vFences.length \leq 600 1 ≤ h F e n ces . l e n g t h , v F e n ces . l e n g t h ≤ 600
思路:暴力枚舉 + 集合
這題和 Biweekly Contest 118
的 2943. Maximize Area of Square Hole in Grid 很像,甚至範例的圖都是一樣的。差別在 2943 是移除柵欄後,所構成的正方形內部沒有柵欄即可,這題需要用柵欄將正方形圍起來。
同樣是對水平和垂直分開討論,若水平方向可以有長度為 k k k 的邊長,垂直方向也可以有長度為 k k k 的邊長,則可以構成一個邊長為 k k k 的正方形。
因此,我們可以先對水平和垂直方向的柵欄做排序,暴力枚舉出所有可能的邊長,最後對水平和垂直方向的結果做交集,若交集不為空,則表示可以構成一個正方形,取其中的最大邊長即可。若交集為空,則表示無法構成正方形,返回 − 1 -1 − 1 。
時間複雜度:O ( h 2 + v 2 ) O(h^2 + v^2) O ( h 2 + v 2 ) ,其中 h h h 和 v v v 分別為水平和垂直方向的柵欄數量,主要花在暴力枚舉邊長上。
空間複雜度:O ( h 2 + v 2 ) O(h^2 + v^2) O ( h 2 + v 2 ) ,建立水平和垂直方向的柵欄集合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution : def maximizeSquareArea (self, m: int , n: int , hFences: List [int ], vFences: List [int ] ) -> int : def helper (fences: List [int ], mx: int ) -> Set [int ]: fences.sort() fences = [1 ] + fences + [mx] res = set () for i in range (1 , len (fences)): for j in range (i): res.add(fences[i] - fences[j]) return res h = helper(hFences, m) v = helper(vFences, n) intersection = h & v return pow (max (intersection), 2 , 10 **9 +7 ) if intersection else -1
類題
題意
給定兩個長度為 n n n ,全由 小寫 英文字母組成的字串 s o u r c e source so u rce 和 t a r g e t target t a r g e t ,以及兩個下標為 0 0 0 開始的字元陣列 o r i g i n a l original or i g ina l 和 c h a n g e d changed c han g e d ,以及一個整數陣列 c o s t cost cos t ,其中 c o s t [ i ] cost[i] cos t [ i ] 代表將字元 o r i g i n a l [ i ] original[i] or i g ina l [ i ] 更改為字元 c h a n g e d [ i ] changed[i] c han g e d [ i ] 的花費。
對字串 s o u r c e source so u rce 做一系列操作,每次操作可以選擇字串中的一個字元,並將其更改為另一個字元,並且花費為 c o s t [ i ] cost[i] cos t [ i ] 。操作結束後,若 s o u r c e source so u rce 與 t a r g e t target t a r g e t 相同,則返回操作的總花費,否則返回 − 1 -1 − 1 。
返回將字串 s o u r c e source so u rce 轉換為字串 t a r g e t target t a r g e t 所需的 最小 成本。 如果不可能完成轉換,則傳回 − 1 -1 − 1 。
注意 ,可能存在下標 i
、j
使得 original[j] == original[i]
且 changed[j] == changed[i]
。
思路:Floyd-Warshall 最短路徑
由範例中可以得出,對於將字元 a a a 轉換為 c c c ,可以由中間的字元 b b b 來轉換,即 a → b → c a \rightarrow b \rightarrow c a → b → c 。
因此可以建立一個有向圖,將每個字元 a a a 看成一個節點,若 a → b a \rightarrow b a → b 的花費為 w w w ,則在節點 a a a 和節點 b b b 之間連一條權重為 w w w 的邊。求出所有節點之間的 最短路徑 ,即可得到答案。
由於題目只存在小寫英文數字,因此建圖時直接建 26 個節點即可,並且將所有邊的權重初始化為 + ∞ +\infty + ∞ ,表示不可連通。
由於題目 可能存在重複的邊 ,因此在由 o r i g i n a l original or i g ina l 和 c h a n g e d changed c han g e d 建圖時,若兩個字元相同,則將邊的權重更新為最小值。
計算答案時,對於字串 s o u r c e source so u rce 中的每個字元 a a a ,若 a → b a \rightarrow b a → b 的權重為 + ∞ +\infty + ∞ ,則表示無法將 a a a 轉換為 b b b ,因此返回 − 1 -1 − 1 。否則將所有權重相加即可。
時間複雜度:O ( m + n + Σ 3 ) O(m+n+\Sigma^3) O ( m + n + Σ 3 ) ,其中 m m m 和 n n n 分別為 s o u r c e source so u rce 和 c o s t cost cos t 的長度,Σ \Sigma Σ 為字元集合的大小,本題中為 26 26 26 。
空間複雜度:O ( Σ 2 ) O(\Sigma^2) O ( Σ 2 ) ,建立的圖的大小為 Σ 2 \Sigma^2 Σ 2 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution : def minimumCost (self, source: str , target: str , original: List [str ], changed: List [str ], cost: List [int ] ) -> int : g = [[float ("inf" )] * 26 for _ in range (26 )] for i in range (26 ): g[i][i] = 0 for u, v, w in zip (original, changed, cost): uu, vv = ord (u)-ord ('a' ), ord (v)-ord ('a' ) g[uu][vv] = min (g[uu][vv], w) for k in range (26 ): for i in range (26 ): if g[i][k] == float ("inf" ): continue for j in range (26 ): g[i][j] = min (g[i][j], g[i][k]+g[k][j]) ans = 0 for u, v in zip (source, target): uu, vv = ord (u)-ord ('a' ), ord (v)-ord ('a' ) if g[uu][vv] == float ("inf" ): return -1 ans += g[uu][vv] return ans
類題
Floyd-Warshall
題單來自:@灵茶山艾府
賽時寫法跟看完題解後的寫法雖然思路相同,但速度差了10幾倍,希望不會被 rejudge …
題意
同上題,但每次可以更改的字元數量從 1 1 1 個變成多個,同樣求最小花費。
思路:Floyd-Warshall + DP
由於每次可以更改多個字元,因此在計算最小花費時要考慮多種情況,換句話說就是要考慮多個轉移來源,可以用 DP 來解決。
令 d p [ i ] dp[i] d p [ i ] 表示修改 s o u r c e [ : i ] source[:i] so u rce [ : i ] 為 t a r g e t [ : i ] target[:i] t a r g e t [ : i ] 的最小代價
則 d p [ i ] dp[i] d p [ i ] 可以由 d p [ j ] dp[j] d p [ j ] 轉移而來,其中 j < i j < i j < i 且 s o u r c e [ j : i ] source[j:i] so u rce [ j : i ] 可以在若干次操作後修改成 t a r g e t [ j : i ] target[j:i] t a r g e t [ j : i ] 。
若 s o u r c e [ i − 1 ] = = t a r g e t [ i − 1 ] source[i-1] == target[i-1] so u rce [ i − 1 ] == t a r g e t [ i − 1 ] ,則可以選擇不修改,因此 d p [ i ] = m i n ( d p [ i ] , d p [ i − 1 ] ] dp[i] = min(dp[i], dp[i-1]] d p [ i ] = min ( d p [ i ] , d p [ i − 1 ]] 。
在建圖和最短路徑的部分和上題一樣,但因為每次可以更改多個字元,因此可以分不同長度的情況來處理,將不同長度的節點記錄到 l e n _ t o _ n o d e s len\_to\_nodes l e n _ t o _ n o d es 。在執行 Floyd-Warshall
時,只需要處理長度相同的節點即可
在枚舉轉移來源時,可以直接枚舉轉移起始點 j j j ,或是枚舉轉移部份的長度 s i z e size s i ze 。但枚舉 j j j 需要注意最大長度,以及判斷 l e n _ t o _ n o d e s len\_to\_nodes l e n _ t o _ n o d es 中是否有當前長度的節點;直接從 l e n _ t o _ n o d e s len\_to\_nodes l e n _ t o _ n o d es 中枚舉 s i z e size s i ze 則不需要考慮這些問題,且速度更快。
時間複雜度:O ( n 3 ) O(n^3) O ( n 3 )
空間複雜度:O ( n 2 ) O(n^2) O ( n 2 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Solution : def minimumCost (self, source: str , target: str , original: List [str ], changed: List [str ], cost: List [int ] ) -> int : g = defaultdict(lambda : defaultdict(lambda : float ("inf" ))) len_to_nodes = defaultdict(set ) for u, v, w in zip (original, changed, cost): g[u][v] = min (g[u][v], w) len_to_nodes[len (u)].add(u) len_to_nodes[len (v)].add(v) g[u][u] = 0 g[v][v] = 0 for nodes in len_to_nodes.values(): for k in nodes: for i in nodes: if g[i][k] == float ("inf" ): continue for j in nodes: g[i][j] = min (g[i][j], g[i][k]+g[k][j]) max_len = max (len_to_nodes.keys()) n = len (source) dp = [float ("inf" )] * (n+1 ) dp[0 ] = 0 for i in range (1 , n+1 ): if source[i-1 ] == target[i-1 ]: dp[i] = dp[i-1 ] for size, nodes in len_to_nodes.items(): if i < size: continue j = i-size u = source[j:i] v = target[j:i] if v in nodes and u in nodes: dp[i] = min (dp[i], dp[j]+g[u][v]) return dp[n] if dp[n] != float ("inf" ) else -1
參考資料