每日第1題為中文站(LCCN) ,第2題為英文站(LCUS) ,題目連結皆統一為英文站的題目連結。
Easy 、Medium 、Hard 難度的題目分別用🟢🟡🔴表示。
若為周賽題目會額外標註由 @zreotrac
零神計算的rating分數。
All problems solved by python
2023-12-21
題意
給定一個長度為 n n n 的整數陣列 m a x H e i g h t s maxHeights ma x He i g h t s ,你的任務是在座標軸上建造 n n n 座塔。 第 i i i 座塔的下標為 i i i ,高度為 h e i g h t s [ i ] heights[i] h e i g h t s [ i ] ,並滿足以下條件:
1 < = h e i g h t s [ i ] < = m a x H e i g h t s [ i ] 1 <= heights[i] <= maxHeights[i] 1 <= h e i g h t s [ i ] <= ma x He i g h t s [ i ]
h e i g h t s heights h e i g h t s 是一個 山脈 陣列。
對於所有 0 < j < = i 0 < j <= i 0 < j <= i ,都有 h e i g h t s [ j − 1 ] < = h e i g h t s [ j ] heights[j - 1] <= heights[j] h e i g h t s [ j − 1 ] <= h e i g h t s [ j ]
對所有 i < = k < n − 1 i <= k < n - 1 i <= k < n − 1 ,都有 h e i g h t s [ k + 1 ] < = h e i g h t s [ k ] heights[k + 1] <= heights[k] h e i g h t s [ k + 1 ] <= h e i g h t s [ k ]
請你返回可行方案中,高度和的最大值 。
思路:單調棧 + 前後綴分解
題目要求的 山脈 陣列,可以分成兩個部分,前半部分是遞增的,後半部分是遞減的,且分界點可以是 0 0 0 到 n − 1 n - 1 n − 1 之間的任意一個位置。
因此問題可以拆分成兩個部分:構建出前半部分的遞增陣列,和構建出後半部分的遞減陣列。將長度為 x x x 的遞增陣列和長度為 n − x n -x n − x 的遞減陣列合併,可以直接得到一個長度為 n n n 的 山脈 陣列。
遞增陣列的最後一個元素,和遞減陣列的第一個元素,兩者較大的即為 山脈 陣列的最高點,因此 不用考慮最高點為何,只需要構建遞增和遞減陣列 。
為了構建出最大值,我們可以使用 Monotonic Stack(單調棧)
來構建遞增和遞減陣列,對於後半部分的遞減陣列,可以由後往前構建遞增陣列,然後將其反轉即可。
單調棧的作用是,對於每個元素 x x x ,找到最大的 y y y ,使得 y y y 在 x x x 的左邊,且 y y y 滿足某種條件。
這裡的條件是,y y y 在 x x x 的左邊,且 y y y 的高度小於等於 x x x 的高度。
這樣的 y y y 可以用單調棧來找到,且單調棧中的元素都是由底到上 遞增 的。
令單調棧內保存最大高度的下標 s t [ − 1 ] = j st[-1] = j s t [ − 1 ] = j ,則 j j j 對答案的貢獻為 m a x H e i g h t s [ j ] ∗ ( j − k ) maxHeights[j] * (j - k) ma x He i g h t s [ j ] ∗ ( j − k ) ,其中 k k k 為單調棧中 j j j 的下一個元素的下標。
為了計算第一個元素的貢獻,我們可以在單調棧的底部加入一個 dummy 元素,在計算前綴部分時,其下標為 − 1 -1 − 1 ;在計算後綴部分時,其下標為 n n n 。
對於新加入的元素 x x x ,我們需要將單調棧中所有高度大於等於 ≥ x \geq x ≥ x 的元素彈出,並計算其對答案的貢獻。
這是因為,這些元素的高度都不能 > x >x > x ,否則就不是 山脈 陣列了。
為了維持單調性,對於高度= x =x = x 的元素,也需要彈出。
這些元素的高度都是 x x x ,因此對答案的貢獻為 x ∗ ( i − s t [ − 1 ] ) x * (i - st[-1]) x ∗ ( i − s t [ − 1 ]) ,其中 i i i 為 x x x 的下標,s t [ − 1 ] st[-1] s t [ − 1 ] 為棧頂元素的下標。
用同樣的方法計算前綴和和後綴和,即可得到答案。
前綴和 p r e [ i ] pre[i] p re [ i ] 表示 [ 0 , i ] [0, i] [ 0 , i ] 區間所構建的遞增陣列的最大值。
後綴和 s u f [ i ] suf[i] s u f [ i ] 表示 [ i , n ) [i, n) [ i , n ) 區間所構建的遞減陣列的最大值。
答案為 m a x 0 ≤ i < n ( p r e [ i ] + s u f [ i + 1 ] ) max_{0 \leq i < n}(pre[i] + suf[i + 1]) ma x 0 ≤ i < n ( p re [ i ] + s u f [ i + 1 ])
時間複雜度 O ( n ) O(n) O ( n )
空間複雜度 O ( n ) O(n) O ( n )
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 class Solution : def maximumSumOfHeights (self, maxHeights: List [int ] ) -> int : n = len (maxHeights) pre = [0 ] * (n + 1 ) st = [-1 ] s = 0 for i, x in enumerate (maxHeights): while len (st) > 1 and x <= maxHeights[st[-1 ]]: j = st.pop() s -= maxHeights[j] * (j - st[-1 ]) s += x * (i - st[-1 ]) pre[i] = s st.append(i) suf = [0 ] * (n + 1 ) st = [n] s = 0 for i in range (n - 1 , -1 , -1 ): x = maxHeights[i] while len (st) > 1 and x <= maxHeights[st[-1 ]]: j = st.pop() s -= maxHeights[j] * (st[-1 ] - j) s += x * (st[-1 ] - i) suf[i] = s st.append(i) ans = suf[0 ] for i in range (n): ans = max (ans, pre[i] + suf[i + 1 ]) return ans
類題
前後綴分解
單調棧
矩形系列
字典序(最小)
貢獻法(計算所有子陣列的……的和)
題單來自:@灵茶山艾府 [1] , [2] , [3]
題意
給定 n n n 個二維平面上的點 p o i n t s points p o in t s ,其中 p o i n t s [ i ] = [ x i , y i ] points[i] = [x_i, y_i] p o in t s [ i ] = [ x i , y i ] ,返回兩點之間內部不包含任何點的 最寬垂直區域 的寬度。
垂直區域 的定義是固定寬度,而 y 軸上無限延伸的一塊區域(也就是高度為無限大)。 最寬垂直區域 為寬度最大的一個垂直區域。
在垂直區域 邊界上 的點,被視為在 不在 區域內。
思路:排序 + 找最大差值
根據題意,我們可以對陣列 p o i n t s points p o in t s 依照 x x x 升序排列,計算相鄰兩點之差的最大值,即m a x 1 ≤ i < n ( x i − x i − 1 ) max_{1 \leq i < n}(x_{i} - x_{i-1}) ma x 1 ≤ i < n ( x i − x i − 1 ) ,即為答案。
時間複雜度 O ( n l o g n ) O(nlogn) O ( n l o g n ) ,主要是排序的時間複雜度。
空間複雜度 O ( n ) O(n) O ( n )
1 2 3 4 5 6 7 8 class Solution : def maxWidthOfVerticalArea (self, points: List [List [int ]] ) -> int : n = len (points) points.sort(key=lambda x: x[0 ]) ans = 0 for i in range (1 , n): ans = max (ans, points[i][0 ] - points[i - 1 ][0 ]) return ans
2023-12-22
題意
給定一個整數陣列 n u m s nums n u m s ,請你返回使 n u m s nums n u m s 成為 山形陣列(mountain array) 的 最少 刪除次數。
當 a r r arr a rr 滿足以下條件時,則稱 a r r arr a rr 是 mountain array :
a r r . l e n g t h > = 3 arr.length >= 3 a rr . l e n g t h >= 3
存在某個下標 i i i (從 0 0 0 開始)滿足 0 < i < a r r . l e n g t h − 1 0 < i < arr.length - 1 0 < i < a rr . l e n g t h − 1 且:
a r r [ 0 ] < a r r [ 1 ] < . . . < a r r [ i − 1 ] < a r r [ i ] arr[0] < arr[1] < ... < arr[i - 1] < arr[i] a rr [ 0 ] < a rr [ 1 ] < ... < a rr [ i − 1 ] < a rr [ i ]
a r r [ i ] > a r r [ i + 1 ] > . . . > a r r [ a r r . l e n g t h − 1 ] arr[i] > arr[i + 1] > ... > arr[arr.length - 1] a rr [ i ] > a rr [ i + 1 ] > ... > a rr [ a rr . l e n g t h − 1 ]
思路:最長遞增子序列(LIS) + 前後綴分解
上禮拜剛寫過
題意要求最少的刪除次數,等價於求最長的山形陣列的長度 。
這題相比昨天的 2866. Beautiful Towers II ,我們並不在意每個元素的高度,而只在意最長的山形陣列的長度,因此並不需要單調棧來維護最大高度。
這題可以轉換成求Longest Increasing Subsequence (LIS)
的問題::
令 d p 1 [ i ] dp1[i] d p 1 [ i ] 表示由前往後,以 n u m s [ i ] nums[i] n u m s [ i ] 結尾的 LIS 長度。
令 d p 2 [ i ] dp2[i] d p 2 [ i ] 表示由後往前,以 n u m s [ i ] nums[i] n u m s [ i ] 結尾的 LIS 長度。
令 m a x L e n maxLen ma xL e n 表示最長的山形陣列的長度。
則 m a x L e n = m a x 1 ≤ i < n − 1 ( d p 1 [ i ] + d p 2 [ i ] − 1 ) maxLen = max_{1 \leq i < n-1}(dp1[i] + dp2[i] - 1) ma xL e n = ma x 1 ≤ i < n − 1 ( d p 1 [ i ] + d p 2 [ i ] − 1 ) ,其中 n n n 為陣列長度,− 1 -1 − 1 是因為 i i i 位置被重複計算了兩次。
需要注意,若 d p 1 [ i ] = = 1 dp1[i] == 1 d p 1 [ i ] == 1 或 d p 2 [ i ] = = 1 dp2[i] == 1 d p 2 [ i ] == 1 ,代表山頂前/後沒有比它小的數字,根據定義, i i i 位置不可能是山形陣列的山頂,因此不應該考慮此位置。
若要進一步提升時間複雜度,在計算LIS
的時候,可以用貪心+二分搜尋,上禮拜寫的 這篇文章
時間複雜度:O ( n 2 ) O(n^2) O ( n 2 )
空間複雜度:O ( n ) O(n) O ( n )
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 class Solution : def minimumMountainRemovals (self, nums: List [int ] ) -> int : n = len (nums) dp1 = [1 ] * n for i in range (1 , n): for j in range (i): if nums[j] < nums[i]: if dp1[j] + 1 > dp1[i]: dp1[i] = dp1[j] + 1 dp2 = [1 ] * n for i in range (n-2 , -1 , -1 ): for j in range (n-1 , i, -1 ): if nums[j] < nums[i]: if dp2[j] + 1 > dp2[i]: dp2[i] = dp2[j] + 1 max_len = 0 for i in range (1 , n-1 ): if dp1[i] != 1 and dp2[i] != 1 : max_len = max (max_len, dp1[i] + dp2[i] - 1 ) return n - max_len
類題
最長遞增子序列(LIS)
前後綴分解 (同昨日)
題單來自:@灵茶山艾府 [1] , [2]
題意
給定一個由若干 0 0 0 和 1 1 1 組成的字串 s s s ,請你計算並返回將該字串分割成兩個非空 子字串(即左 子字串和右 子字串)後,左字串中 0 0 0 的數量加上右字串中 1 1 1 的數量的最大值 。
思路:簡單模擬
先計算出 s s s 中 1 1 1 的總數 c n t 1 cnt1 c n t 1 ,接著枚舉分割點 i i i :
若 s [ i ] = 0 s[i] = 0 s [ i ] = 0 代表右子串中有一個 0 0 0 跑到左子串中,因為 0 0 0 只在左子串中存在貢獻,因此答對答案的貢獻 + 1 +1 + 1
若 s [ i ] = 1 s[i] = 1 s [ i ] = 1 代表右子串中有一個 1 1 1 跑到左子串中,因為 1 1 1 只在右子串中存在貢獻,因此答對答案的貢獻 − 1 -1 − 1
思考上,可以先令 l c n t 0 lcnt0 l c n t 0 和 r c n t 1 rcnt1 rc n t 1 分別表示左子串中 0 0 0 的數量和右子串中 1 1 1 的數量,但事實上兩個變數對答案的貢獻變化相同,可以合併。
時間複雜度 O ( n ) O(n) O ( n )
空間複雜度 O ( 1 ) O(1) O ( 1 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution : def maxScore (self, s: str ) -> int : n = len (s) ans = 0 cur = s.count('1' ) for ch in s[:n-1 ]: cur += 1 if ch == '0' else -1 ans = max (ans, cur) return ans
2023-12-23
題意
給定一個整數陣列 p i l e s piles p i l es ,其中 p i l e s [ i ] piles[i] p i l es [ i ] 表示第 i i i 堆石頭中的石頭數量。另給定一個整數 k k k ,表示可以操作的次數。
在每次操作中,可以從 p i l e s piles p i l es 中選擇一堆石頭,並從中移除 f l o o r ( p i l e s [ i ] / 2 ) floor(piles[i] / 2) f l oor ( p i l es [ i ] /2 ) 顆石頭,可以對同一堆石頭多次執行此操作。
返回執行 k k k 次操作後,剩下石子的 最小 總數。
思路:Greedy + Heap
很顯然地,要使剩下的石頭數量最小,每次都要盡可能地移除最多的石頭。因此可以建立一個Max Heap,每次從堆中取出最大的元素,並將其減半後放回堆中。
這樣做的好處是,每次取出的元素都是當前堆中最大的元素,因此可以保證每次取出的元素都是最佳的選擇。
時間複雜度 O ( n + k log n ) O(n + k \log n) O ( n + k log n ) ,heapify
的時間複雜度為 O ( n ) O(n) O ( n ) (buttom-up),每次從堆中取出元素的時間複雜度為 O ( log n ) O(\log n) O ( log n ) ,總共取出 k k k 次。
空間複雜度 O ( n ) O(n) O ( n )
1 2 3 4 5 6 7 8 9 class Solution : def minStoneSum (self, piles: List [int ], k: int ) -> int : n = len (piles) hp = [-x for x in piles] heapify(hp) for _ in range (k): x = -heappop(hp) heappush(hp, -(x - x // 2 )) return -sum (hp)
題意
給定一個字串 p a t h path p a t h ,其中 p a t h [ i ] path[i] p a t h [ i ] 可以是 ′ N ′ 'N' ′ N ′ 、′ S ′ 'S' ′ S ′ 、′ E ′ 'E' ′ E ′ 或 ′ W ′ 'W' ′ W ′ ,分別代表向北、南、東、或西移動一個單位。
從二維平面的原點 ( 0 , 0 ) (0, 0) ( 0 , 0 ) 開始,並按照 p a t h path p a t h 指定的路徑行走。如果路徑在任何點出現交叉,也就是說,走到之前訪問過的位置,則返回 t r u e true t r u e ;否則,返回 f a l s e false f a l se 。
思路:簡單模擬 + 雜湊表
建一個雜湊表 v i s i t e d visited v i s i t e d ,用來記錄走過的位置,若某個位置已經在 v i s i t e d visited v i s i t e d 中,則代表路徑出現交叉,返回 t r u e true t r u e ,否則返回 f a l s e false f a l se 。
時間複雜度 O ( n ) O(n) O ( n )
空間複雜度 O ( n ) O(n) O ( n )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution : def isPathCrossing (self, path: str ) -> bool : visited = set ([(0 ,0 )]) x, y = 0 , 0 for ch in path: if ch == "N" : y += 1 elif ch == "S" : y -= 1 elif ch == "E" : x += 1 else : x -= 1 if (x, y) in visited: return True visited.add((x, y)) return False
2023-12-24
題意
給定一個無限大的二維平面,每一個整數座標 ( i , j ) (i,j) ( i , j ) 上都有 ∣ i ∣ + ∣ j ∣ |i| + |j| ∣ i ∣ + ∣ j ∣ 個蘋果樹。又給定一個整數 n e e d e d A p p l e s neededApples n ee d e d A ppl es ,表示需要獲取的蘋果數量。
以二維平面的原點 ( 0 , 0 ) (0, 0) ( 0 , 0 ) 為中心,找到最小的正方形土地,使得土地中裡面或邊緣上 包含的蘋果數量 至少 為 n e e d e d A p p l e s neededApples n ee d e d A ppl es ,並返回該土地的 最小周長 。
思路:數學 + 二分搜尋
令右上角座標為 ( n , n ) (n, n) ( n , n ) 的正方形表示第 n n n 層,即邊長為 2 n 2n 2 n 、周長為 8 n 8n 8 n 的正方形。
首先推導每層的蘋果數量:
第 0 0 0 層:0 0 0
第 1 1 1 層:4 × ( 2 + 1 ) 4 \times (2+1) 4 × ( 2 + 1 )
第 2 2 2 層:4 × ( ( 4 + 3 ) + ( 2 + 3 ) ) 4 \times ((4+3) + (2+3)) 4 × (( 4 + 3 ) + ( 2 + 3 ))
第 3 3 3 層:4 × ( ( 6 + 5 + 4 ) + ( 3 + 4 + 5 ) ) 4 \times ((6+5+4) + (3+4+5)) 4 × (( 6 + 5 + 4 ) + ( 3 + 4 + 5 ))
第 n n n 層:4 × ( ( ( 2 n ) + ( 2 n − 1 ) + . . . + ( n + 1 ) ) + ( ( n ) + ( n + 1 ) + . . . + ( 2 n − 1 ) ) ) 4 \times (((2n)+(2n-1)+...+(n+1)) + ((n)+(n+1)+...+(2n-1))) 4 × ((( 2 n ) + ( 2 n − 1 ) + ... + ( n + 1 )) + (( n ) + ( n + 1 ) + ... + ( 2 n − 1 )))
得第 n n n 層的蘋果數量為 4 × ( ( 3 n + 1 ) ∗ n 2 + ( 3 n − 1 ) ∗ n 2 ) = 12 × n 2 4 \times (\frac{(3n+1)*n}{2} + \frac{(3n-1)*n}{2}) = 12 \times n^2 4 × ( 2 ( 3 n + 1 ) ∗ n + 2 ( 3 n − 1 ) ∗ n ) = 12 × n 2
因此前 n n n 層的蘋果數量為 Σ i = 1 n 12 × i 2 = 12 × n ( n + 1 ) ( 2 n + 1 ) 6 = 2 × n ( n + 1 ) ( 2 n + 1 ) \Sigma_{i=1}^{n} 12 \times i^2 = 12 \times \frac{n(n+1)(2n+1)}{6} = 2 \times n(n+1)(2n+1) Σ i = 1 n 12 × i 2 = 12 × 6 n ( n + 1 ) ( 2 n + 1 ) = 2 × n ( n + 1 ) ( 2 n + 1 )
對於給定的 n e e d e d A p p l e s neededApples n ee d e d A ppl es ,我們可以二分搜尋 n n n ,使得 2 × n ( n + 1 ) ( 2 n + 1 ) ≥ n e e d e d A p p l e s 2 \times n(n+1)(2n+1) \geq neededApples 2 × n ( n + 1 ) ( 2 n + 1 ) ≥ n ee d e d A ppl es ,即可得到答案。
時間複雜度 O ( log m ) O(\log m) O ( log m ) ,其中 m m m 為 n e e d e d A p p l e s neededApples n ee d e d A ppl es 的上界。
空間複雜度 O ( 1 ) O(1) O ( 1 )
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def minimumPerimeter (self, neededApples: int ) -> int : def calc (n ): return 2 * n * (n + 1 ) * (2 * n + 1 ) left, right = 0 , 10 ** 5 while (left <= right): mid = (left + right) // 2 if calc(mid) >= neededApples: right = mid - 1 else : left = mid + 1 return 8 * left
題意
給定一個僅由字元 0 0 0 和 1 1 1 組成的字串 s s s 。在每次操作中,可以將任一 ′ 0 ′ '0' ′ 0 ′ 變成 ′ 1 ′ '1' ′ 1 ′ ,或將 ′ 1 ′ '1' ′ 1 ′ 變成 ′ 0 ′ '0' ′ 0 ′ 。
返回使 s s s 變成 交替字串 所需的 最少 操作次數。 交替字串 定義為:如果字串中不存在相鄰兩個字元相等的情況,那麼該字串就是交替字串。例如,字串 ′ 01 0 ′ '010' ′ 01 0 ′ 是交替字串,而字串 ′ 010 0 ′ '0100' ′ 010 0 ′ 不是。
思路:簡單模擬
由於 s s s 是二進位字串,因此s s s 只有「從 0 0 0 開始」或「從 1 1 1 開始」兩種可能,模擬交替字串的構建過程,返回這兩種情況中的最小值即可。
時間複雜度 O ( n ) O(n) O ( n )
空間複雜度 O ( 1 ) O(1) O ( 1 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution : def minOperations (self, s: str ) -> int : ans1 = ans2 = 0 for i, ch in enumerate (s): if i % 2 == 0 : if ch == '1' : ans1 += 1 else : ans2 += 1 else : if ch == '0' : ans1 += 1 else : ans2 += 1 return min (ans1, ans2)
2023-12-25
題意
給定兩個整數 t o m a t o S l i c e s tomatoSlices t o ma t o Sl i ces 和 c h e e s e S l i c e s cheeseSlices c h eese Sl i ces ,表示番茄片和起司片的數量。
製作A漢堡需要 4 4 4 片番茄和 1 1 1 片起司,製作B漢堡需要 2 2 2 片番茄和 1 1 1 片起司,返回能夠剩下的番茄片和起司片數量都為 0 0 0 的 [A漢堡的數量, B漢堡的數量] ,若無法使剩下的番茄片和起司片數量都為 0 0 0 ,則返回 [ ] [] [ ] 。
思路:數學
根據題意,可以列出以下兩個方程式:
4 x + 2 y = t o m a t o S l i c e s 4x + 2y = tomatoSlices 4 x + 2 y = t o ma t o Sl i ces
x + y = c h e e s e S l i c e s x + y = cheeseSlices x + y = c h eese Sl i ces
解方程式,得到:
x = ( t o m a t o S l i c e s − 2 × c h e e s e S l i c e s ) / 2 x = (tomatoSlices - 2 \times cheeseSlices) / 2 x = ( t o ma t o Sl i ces − 2 × c h eese Sl i ces ) /2
y = ( 4 × c h e e s e S l i c e s − t o m a t o S l i c e s ) / 2 y = (4 \times cheeseSlices - tomatoSlices) / 2 y = ( 4 × c h eese Sl i ces − t o ma t o Sl i ces ) /2
最後檢查 x x x 和 y y y 是否為非負整數,若是,則返回 [ x , y ] [x, y] [ x , y ] ,否則返回 [ ] [] [ ] 。
時間複雜度 O ( 1 ) O(1) O ( 1 )
空間複雜度 O ( 1 ) O(1) O ( 1 )
1 2 3 4 5 6 7 class Solution : def numOfBurgers (self, tomatoSlices: int , cheeseSlices: int ) -> List [int ]: x = (tomatoSlices - 2 * cheeseSlices) / 2 y = (4 * cheeseSlices - tomatoSlices) / 2 if x < 0 or y < 0 or x != int (x) or y != int (y): return [] return [int (x), int (y)]
題意
給定一個只含數字的 非空 字串 s
,請計算並傳回 解碼 方法的 總數 。
一條包含字母 A-Z
的訊息,透過 A → 1 , B → 2 , . . . , Z → 26 A \rightarrow 1, B \rightarrow 2, ..., Z \rightarrow 26 A → 1 , B → 2 , ... , Z → 26 的對應關係進行了 編碼 。
例如," B E A N " "BEAN" " BE A N " 可以編碼為 " 25114 " "25114" "25114" (B → 2 B \rightarrow 2 B → 2 、E → 5 E \rightarrow 5 E → 5 、A → 1 A \rightarrow 1 A → 1 、N → 14 N \rightarrow 14 N → 14 )。
要 解碼 已編碼的訊息,所有數字必須以上述對應的關係,反向映射回字母(可能有多種方法)。例如," 11106 " "11106" "11106" 可以映射為:
" A A J F " "AAJF" " AA J F " ,將訊息分組為 ( 1 , 1 , 10 , 6 ) (1,1,10,6) ( 1 , 1 , 10 , 6 )
" K J F " "KJF" " K J F " ,將訊息分組為 ( 11 , 10 , 6 ) (11,10,6) ( 11 , 10 , 6 )
注意,訊息不能分組為 ( 1 , 11 , 06 ) (1,11,06) ( 1 , 11 , 06 ) ,因為" 06 " "06" "06" 不能映射為" F " "F" " F " ,這是由於" 6 " "6" "6" 和" 06 " "06" "06" 在映射中並不等價 。
思路:DP
令 d p [ i ] dp[i] d p [ i ] 表示 s [ : i ] s[:i] s [ : i ] 的解碼方法總數,則 d p [ i ] dp[i] d p [ i ] 可以由以下兩種情況轉移而來:
若 s [ i − 1 ] ≠ 0 s[i-1] \neq 0 s [ i − 1 ] = 0 且 s [ i − 2 : i ] ∈ [ 10 , 26 ] s[i-2:i] \in [10, 26] s [ i − 2 : i ] ∈ [ 10 , 26 ] ,則 d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] dp[i] = dp[i-1] + dp[i-2] d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] 。
若 s [ i − 1 ] ≠ 0 s[i-1] \neq 0 s [ i − 1 ] = 0 且 s [ i − 2 : i ] ∉ [ 10 , 26 ] s[i-2:i] \notin [10, 26] s [ i − 2 : i ] ∈ / [ 10 , 26 ] ,則 d p [ i ] = d p [ i − 1 ] dp[i] = dp[i-1] d p [ i ] = d p [ i − 1 ] 。因為 s [ i − 1 ] ≠ 0 s[i-1] \neq 0 s [ i − 1 ] = 0 ,因此 s [ i − 1 ] s[i-1] s [ i − 1 ] 可以單獨解碼,且 s [ i − 1 ] s[i-1] s [ i − 1 ] 不能和 s [ i − 2 ] s[i-2] s [ i − 2 ] 組合解碼。
剩餘情況,d p [ i ] = 0 dp[i] = 0 d p [ i ] = 0 。
時間複雜度 O ( n ) O(n) O ( n )
空間複雜度 O ( n ) O(n) O ( n ) ,可以透過只保留前兩項的狀態優化到 O ( 1 ) O(1) O ( 1 )
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def numDecodings (self, s: str ) -> int : n = len (s) dp = [0 ] * (n+1 ) dp[0 ] = 1 dp[1 ] = 1 if s[0 ] != '0' else 0 for i in range (2 , n+1 ): if s[i-1 ] != '0' : dp[i] += dp[i-1 ] if s[i-2 ] != '0' and int (s[i-2 :i]) <= 26 : dp[i] += dp[i-2 ] return dp[-1 ]
2023-12-26
沒碰過狀態壓縮DP,本月第4次看題解。看是看懂了但要把思路完整地寫下來還是有點困難,思路待補。
題意
題意待補
思路:狀態壓縮DP + 位運算
思路待補
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution : def maxStudents (self, seats: List [List [str ]] ) -> int : m, n = len (seats), len (seats[0 ]) arr = [0 ] * m for i, row in enumerate (seats): for j, ch in enumerate (row): if ch == '.' : arr[i] |= 1 << j @cache def dfs (i: int , j: int ) -> int : if i == 0 : lb = j & -j return dfs(i, j & ~(lb * 3 )) + 1 if j else 0 res = dfs(i - 1 , arr[i - 1 ]) s = j while s: if (s & (s >> 1 )) == 0 : t = arr[i - 1 ] & ~(s << 1 | s >> 1 ) res = max (res, dfs(i - 1 , t) + s.bit_count()) s = (s - 1 ) & j return res return dfs(m-1 , arr[-1 ])
參考資料
題意
給定 n n n 顆相同的骰子,每個骰子有 k k k 面,點數從 1 1 1 到 k k k 。 求擲出的骰子點數總和為 t a r g e t target t a r g e t 的擲骰子方法總數。
由於答案可能很大,請將答案對 1 0 9 + 7 10^9 + 7 1 0 9 + 7 取模 後返回。
思路:DP
這題可以視為分組背包問題:有 n n n 組物品,每組物品都有 k k k 個,大小分別為 1 , 2 , . . . , k 1,2,...,k 1 , 2 , ... , k ,求每組恰好選一個物品,組成大小為 t a r g e t target t a r g e t 的方案數。
令 d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 為擲 i i i 顆骰子和為 j j j 的方法數,在擲第 i i i 顆骰子時,令第 i i i 顆骰子的點數為 x x x ,考慮前 i i i 顆骰子和為 j j j 的情況:
若 j − x < 0 j - x < 0 j − x < 0 ,則 d p [ i ] [ j ] = 0 dp[i][j] = 0 d p [ i ] [ j ] = 0 ,因為 j j j 不能由 i i i 顆骰子構成。
否則,d p [ i ] [ j ] = Σ x = 1 k d p [ i − 1 ] [ j − x ] dp[i][j] = \Sigma_{x=1}^{k} dp[i-1][j-x] d p [ i ] [ j ] = Σ x = 1 k d p [ i − 1 ] [ j − x ] ,其中 j − x ≥ 0 j-x \geq 0 j − x ≥ 0 ,因為第 i i i 顆骰子的點數為 x x x ,因此 j j j 可以由 j − x j-x j − x 和 x x x 構成。
時間複雜度 O ( n × k × t a r g e t ) O(n \times k \times target) O ( n × k × t a r g e t )
空間複雜度 O ( n × t a r g e t ) O(n \times target) O ( n × t a r g e t ) ,可以透過滾動陣列優化到 O ( t a r g e t ) O(target) O ( t a r g e t )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution : def numRollsToTarget (self, n: int , k: int , target: int ) -> int : MOD = 10 **9 + 7 dp = [[0 ] * (target + 1 ) for _ in range (n + 1 )] dp[0 ][0 ] = 1 for j in range (1 , min (k, target) + 1 ): dp[1 ][j] = 1 for i in range (2 , n+1 ): for j in range (1 , target+1 ): for x in range (1 , k+1 ): if j - x < 0 : break dp[i][j] += dp[i-1 ][j-x] % MOD return dp[n][target] % MOD
2023-12-27
題意
給定一個保齡球比賽的記錄,兩個整數陣列 p l a y e r 1 player1 pl a yer 1 和 p l a y e r 2 player2 pl a yer 2 ,分別表示玩家 1 和玩家 2 每局擊中的瓶數,每局的瓶數為最多為 10 10 10 。
假設玩家在第 i i i 局擊中 x i x_i x i 瓶。 玩家第 i i i 局的得分為:
若玩家在該局的前兩局的任何一局中擊中了 10 10 10 個瓶子,則為 2 × x i 2 \times x_i 2 × x i 。
否則,為 x i x_i x i 。
玩家的得分是其 n n n 局的總和。若玩家 1 1 1 的得分高於玩家 2 2 2 的得分,則返回 1 1 1 ;若玩家 2 2 2 的得分高於玩家 1 1 1 的得分,則返回 2 2 2 ;如果平局,則返回 0 0 0 。
思路:簡單模擬
記錄前兩局是否擊中 10 10 10 個瓶子,簡單模擬即可。
時間複雜度 O ( n ) O(n) O ( n )
空間複雜度 O ( 1 ) O(1) O ( 1 )
1 2 3 4 5 6 7 8 9 10 class Solution : def isWinner (self, player1: List [int ], player2: List [int ] ) -> int : def f (player: List [int ] ) -> int : res = 0 pre1 = pre2 = 0 for x in player: res += x * 2 if pre1 or pre2 else x pre1, pre2 = pre2, x == 10 return res return 1 if f(player1) > f(player2) else 2 if f(player1) < f(player2) else 0
題意
給定一個下標從 0 0 0 開始、長度為 n n n 的字串 c o l o r color co l or ,其中 c o l o r [ i ] color[i] co l or [ i ] 表示第 i i i 個氣球的顏色;以及一個下標從 0 0 0 開始、長度為 n n n 的整數陣列 n e e d e d T i m e neededTime n ee d e d T im e ,其中 n e e d e d T i m e [ i ] neededTime[i] n ee d e d T im e [ i ] 表示移除第 i i i 個氣球需要的時間。
求移除部分氣球,使得相鄰的氣球顏色不同 的最少時間 。
思路:貪心 + 分組循環
對於相鄰的 k k k 個氣球,若 k > 1 k > 1 k > 1 ,為使相鄰的氣球顏色不同,需要移除 k − 1 k-1 k − 1 個氣球,基於貪心的思想,我們應該移除花費最低的 k − 1 k-1 k − 1 個氣球,也就是保留花費最大的氣球。
用分組循環的方式,將所有相鄰且顏色相同的氣球視為同一組,每一組中對答案的貢獻為:組內總和 - 組內最大值。可以在分組的過程中同時計算答案。
時間複雜度 O ( n ) O(n) O ( n ) ,雖然有兩層循環,但每個下標只會被訪問一次。
空間複雜度 O ( 1 ) O(1) O ( 1 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution : def minCost (self, colors: str , neededTime: List [int ] ) -> int : n = len (colors) ans = 0 i = 0 while i < n: st = i mx = s = neededTime[i] while i < n - 1 and colors[i] == colors[i + 1 ]: i += 1 mx = max (mx, neededTime[i]) s += neededTime[i] if i > st: ans += (s - mx) i += 1 return ans
2023-12-28
題意
給定一個長度為 n n n 的整數陣列 n u m s nums n u m s , n u m s [ i ] nums[i] n u m s [ i ] 表示收集第 i i i 種巧克力的成本。
除此之外,在一次操作中,可以用 x x x 的成本修改 所有 巧克力的類型,將第 i i i 種巧克力的類型修改為 ( i + 1 ) m o d n (i+1) \mod n ( i + 1 ) mod n 。
假設可以執行任意次操作,計算並返回收集所有類型巧克力所需的最小成本。
思路:分別計算貢獻
對於第 i i i 種類型的巧克力,做 0 0 0 次操作時其成本為 n u m s [ i ] nums[i] n u m s [ i ] ,做 1 1 1 次操作時其最小成本為 m i n ( n u m s [ i ] , n u m s [ ( i + 1 ) m o d n ] ) min(nums[i], nums[(i+1) \mod n]) min ( n u m s [ i ] , n u m s [( i + 1 ) mod n ]) ,做 2 2 2 次操作時其最小成本為 m i n ( n u m s [ i ] , n u m s [ ( i + 1 ) m o d n ] , n u m s [ ( i + 2 ) m o d n ] ) min(nums[i], nums[(i+1) \mod n], nums[(i+2) \mod n]) min ( n u m s [ i ] , n u m s [( i + 1 ) mod n ] , n u m s [( i + 2 ) mod n ]) ,以此類推。
令 a n s [ k ] ans[k] an s [ k ] 表示操作 k 次時,收集所有巧克力的最小總花費,則第 i i i 種類型的巧克力對 a n s [ k ] ans[k] an s [ k ] 的貢獻為在操作 k k k 次時,收集第 i i i 種類型的巧克力的最小花費。此外,操作 k k k 次需要花費 x × k x \times k x × k (只計算一次)。
因此得出 a n s [ k ] = x × k + Σ i = 0 n − 1 m i n ( n u m s [ i ] , n u m s [ ( i + 1 ) m o d n ] , . . . , n u m s [ ( i + k ) m o d n ] ) ans[k] = x \times k + \Sigma_{i=0}^{n-1} min(nums[i], nums[(i+1) \mod n], ..., nums[(i+k) \mod n]) an s [ k ] = x × k + Σ i = 0 n − 1 min ( n u m s [ i ] , n u m s [( i + 1 ) mod n ] , ... , n u m s [( i + k ) mod n ]) 。
計算時可以枚舉每種巧克力,在依序枚舉操作次數,前一次操作時的最小花費可以用來計算當前操作時的最小花費,即 c o s t = m i n ( c o s t , n u m s [ ( i + k ) m o d n ] ) cost = min(cost, nums[(i+k) \mod n]) cos t = min ( cos t , n u m s [( i + k ) mod n ]) 。 將結果累加至 a n s [ k ] ans[k] an s [ k ] 的貢獻,最後對 a n s ans an s 取最小值即可。
時間複雜度 O ( n 2 ) O(n^2) O ( n 2 ) ,總共 n n n 種巧克力,每種枚舉1次,每次枚舉時計算 a n s [ k ] ans[k] an s [ k ] , k k k 從 0 0 0 到 n − 1 n-1 n − 1 。
空間複雜度 O ( n ) O(n) O ( n )
1 2 3 4 5 6 7 8 9 class Solution : def minCost (self, nums: List [int ], x: int ) -> int : n = len (nums) ans = [x * i for i in range (n)] for i, cost in enumerate (nums): for k in range (n): cost = min (cost, nums[(i+k)%n]) ans[k] += cost return min (ans)
題意
題意待補
思路:DP
思路待補
將問題轉化成從 n n n 個字元中「選擇」 n − k n-k n − k 個字元,使得壓縮後的長度最小
d p [ i ] [ x ] dp[i][x] d p [ i ] [ x ] 表示從第 i 個字元開始,已選擇 x 個字元的最小壓縮長度
可以用 Recursive+記憶化搜索
或 Iterative
的方式實現。由於遞迴函數對同樣的傳入參數 計算結果都是相同的,因此可以用 記憶化搜尋 來最佳化:
如果一個傳入參數 是第一次遇到,那麼可以在返回前,把狀態及其結果記到一個 m e m o memo m e m o 陣列 或 雜湊表 中
如果再次遞迴到該狀態,那麼直接傳回 m e m o memo m e m o 中儲存的結果
Python
可以對函數加上 @cache
裝飾器實現記憶化搜索。
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 class Solution : def getLengthOfOptimalCompression (self, s: str , k: int ) -> int : n = len (s) def calc (x ): if x == 1 : return 1 res = 0 while x: x //= 10 res += 1 return res + 1 @cache def dp (i, x ): if x > (n-k): return float ('inf' ) if i == n: return 0 if x == n-k else float ('inf' ) res = dp(i+1 , x) cnt = 0 for j in range (i, n): if s[i] == s[j]: cnt += 1 res = min (res, dp(j+1 , x+cnt) + calc(cnt)) return res return dp(0 , 0 )
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 class Solution : def getLengthOfOptimalCompression (self, s: str , k: int ) -> int : n = len (s) def calc (x ): if x == 1 : return 1 res = 0 while x: x //= 10 res += 1 return res + 1 dp = [[float ('inf' )]*(n-k+1 ) for _ in range (n+1 )] dp[n][n-k] = 0 for i in range (n-1 , -1 , -1 ): for x in range (n-k+1 ): cnt = 0 dp[i][x] = dp[i+1 ][x] for j in range (i, n): if s[i] == s[j]: cnt += 1 if x+cnt > n-k: break dp[i][x] = min (dp[i][x], dp[j+1 ][x+cnt] + calc(cnt)) return dp[0 ][0 ]
2023-12-29
同 英文站(LCUS) 2023-12-20
的每日一題,直接複製過來,還好我聖誕節不用買巧克力送人。
題意
給定一個整數陣列 p r i c e s prices p r i ces ,表示若干種巧克力的價格,同時給定一個整數 m o n e y money m o n ey ,表示擁有的金錢數量
返回購買兩種巧克力後,最多能剩下多少錢。如果無法購買任兩種巧克力,則返回 m o n e y money m o n ey 。
思路:簡單模擬
很顯然購買價格最低的兩種巧克力即可滿足條件,遍歷一遍 p r i c e s prices p r i ces ,找到最小的兩個價格,然後計算剩下的錢即可。
時間複雜度:O ( n ) O(n) O ( n ) ,n n n 為 p r i c e s prices p r i ces 的長度。
空間複雜度:O ( 1 ) O(1) O ( 1 ) 。
1 2 3 4 5 6 7 8 9 10 class Solution : def buyChoco (self, prices: List [int ], money: int ) -> int : min1 = min2 = float ('inf' ) for p in prices: if p < min1: min2 = min1 min1 = p elif p < min2: min2 = p return money - (min1 + min2) if money >= min1 + min2 else money
題意
給定一個長度為 n n n 的整數陣列 j o b D i f f i c u l t y jobDifficulty j o b D i ff i c u lt y 表示每份工作的難度,以及一個整數 d d d 表示需要在 d d d 天內完成工作。
制定一份工作計畫表,每天 至少 需要完成一項任務。且工作之間有依賴,要想執行第 i i i 項工作,必須先完成全部 j j j 項工作( 0 ≤ j < i 0 \leq j < i 0 ≤ j < i )。
工作計畫的總難度是這 d d d 天每一天的難度總和,而一天的工作難度是當天應該完成工作的最大難度 。
返回整個工作計畫的 最小難度 。如果無法制定工作計畫,則返回 − 1 -1 − 1 。
思路:DP
由於在完成第 i i i 項工作前,需要完成前面所有工作,因此任務一定是按照順序完成的,即 0 , 1 , . . . , n − 1 0, 1, ..., n-1 0 , 1 , ... , n − 1 。
因此可以將 j o b D i f f i c u l t y jobDifficulty j o b D i ff i c u lt y 分成 d d d 個互斥的子區間,且每個子區間不為空,求每個子區間的最大值,使得這些最大值的和最小。
很明顯的,無法完成工作計畫的情況只有在當 n < d n < d n < d 時才會發生,返回 − 1 -1 − 1 。
令 d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 表示前 i i i 天完成 j j j 個工作的最小難度,則 d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 可以由以下兩種情況轉移而來:
若 i = 1 i = 1 i = 1 ,則 d p [ i ] [ j ] = m a x ( j o b D i f f i c u l t y [ : j ] ) dp[i][j] = max(jobDifficulty[:j]) d p [ i ] [ j ] = ma x ( j o b D i ff i c u lt y [ : j ]) ,即只有一天,則需要完成任務中的最小難度為這一天的最大難度。
否則,d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ k ] + m a x ( j o b D i f f i c u l t y [ k : j ] ) ) dp[i][j] = min(dp[i-1][k] + max(jobDifficulty[k:j])) d p [ i ] [ j ] = min ( d p [ i − 1 ] [ k ] + ma x ( j o b D i ff i c u lt y [ k : j ])) ,其中 k ∈ [ i − 1 , j − 1 ] k \in [i-1, j-1] k ∈ [ i − 1 , j − 1 ] ,即前 i − 1 i-1 i − 1 天完成 k k k 個工作的最小難度,加上第 i i i 天的最大難度。
可以用 Recursive+記憶化搜索
或 Iterative
的方式實現。由於遞迴函數對同樣的傳入參數 計算結果都是相同的,因此可以用 記憶化搜尋 來最佳化:
如果一個傳入參數 是第一次遇到,那麼可以在返回前,把狀態及其結果記到一個 m e m o memo m e m o 陣列 或 雜湊表 中
如果再次遞迴到該狀態,那麼直接傳回 m e m o memo m e m o 中儲存的結果
Python
可以對函數加上 @cache
裝飾器實現記憶化搜索。
時間複雜度 O ( n 2 × d ) O(n^2 \times d) O ( n 2 × d )
空間複雜度 O ( n × d ) O(n \times d) O ( n × d ) ,可以透過滾動陣列優化到 O ( n ) O(n) O ( n )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution : def minDifficulty (self, jobDifficulty: List [int ], d: int ) -> int : n = len (jobDifficulty) if n < d: return -1 @cache def dp (i, j ): if i == 1 : return max (jobDifficulty[:j]) mx = jobDifficulty[j-1 ] res = dp(i-1 , j-1 ) + mx for k in range (j-2 , i-2 , -1 ): mx = max (mx, jobDifficulty[k]) res = min (res, dp(i-1 , k) + mx) return res return dp(d, n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution : def minDifficulty (self, jobDifficulty: List [int ], d: int ) -> int : n = len (jobDifficulty) if n < d: return -1 dp = [[float ('inf' )]*(n+1 ) for _ in range (d+1 )] for j in range (1 , n+1 ): dp[1 ][j] = max (jobDifficulty[:j]) for i in range (2 , d+1 ): for j in range (i, n+1 ): mx = jobDifficulty[j-1 ] for k in range (j-1 , i-2 , -1 ): mx = max (mx, jobDifficulty[k]) dp[i][j] = min (dp[i][j], dp[i-1 ][k] + mx) return dp[d][n]
2023-12-30
題意
給定三個整數 y e a r year ye a r 、m o n t h month m o n t h 和 d a y day d a y ,分別表示年、月、日,代表一個日期,請你設計一個演算法來判斷它是對應一週中的哪一天。
返回的結果必須為 [ " S u n d a y " , " M o n d a y " , " T u e s d a y " , " W e d n e s d a y " , " T h u r s d a y " , " F r i d a y " , " S a t u r d a y " ] ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] [ " S u n d a y " , " M o n d a y " , " T u es d a y " , " W e d n es d a y " , " T h u rs d a y " , " F r i d a y " , " S a t u r d a y " ] 中的一個。
思路:內建函數 / 找基準點 / 蔡勒公式
方法一:內建函數(built-in function)
這題可以用內建函數解決,我是沒想到 LeetCode
的允許的函數庫包含 datetime
,所以沒有用內建函數解決。
方法二:找基準點
這題可以找一個基準點,例如 1971 1971 1971 年 1 1 1 月 1 1 1 日是星期五,然後計算 y e a r year ye a r 年 m o n t h month m o n t h 月 d a y day d a y 日與基準點的天數差,再對 7 7 7 取模即可。
思路待補
方法三:蔡勒公式
1 2 3 4 import datetimeclass Solution : def dayOfTheWeek (self, day: int , month: int , year: int ) -> str : return datetime.datetime(year, month, day).strftime("%A" )
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : """ Zeller's Congruence """ def dayOfTheWeek (self, day: int , month: int , year: int ) -> str : ANS_DICT = ["Sunday" , "Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" , "Saturday" ] if month <= 2 : year -= 1 month += 12 c = year // 100 year %= 100 return ANS_DICT[(year + (year // 4 ) + (c // 4 ) - 2 * c + (13 * (month + 1 )) // 5 + day - 1 ) % 7 ]
題意
給定一個字串陣列 w o r d s words w or d s ,判斷是否可以將所有字串中的字母重新排列,使得所有字串都相等。
如果可以,返回 t r u e true t r u e ;否則,返回 f a l s e false f a l se 。
思路:雜湊表統計
統計所有字母出現的次數,如果每個字母出現的次數都是 n n n 的倍數,則可以構造出一個合法的答案,否則不行。
時間複雜度 O ( m × n ) O(m \times n) O ( m × n ) , n n n 為 w o r d s words w or d s 的長度, m m m 為 w o r d s [ i ] words[i] w or d s [ i ] 的長度。
空間複雜度 O ( 1 ) O(1) O ( 1 ) ,因為字母只有 26 26 26 個。
1 2 3 4 5 class Solution : def makeEqual (self, words: List [str ] ) -> bool : n = len (words) cnt = Counter([ch for word in words for ch in word]) return all (v % n == 0 for v in cnt.values())
2023-12-31
題意
給定一個格式為 YYYY-MM-DD
的日期,求這一天是這一年的第幾天。
思路:前綴和 + 閏年判斷
為了避免每次都要計算閏年,可以先計算出每個月的前綴和 ,即 S [ i ] S[i] S [ i ] 表示 1 1 1 月到 i i i 月的天數總和,則 S [ i ] − S [ i − 1 ] S[i] - S[i-1] S [ i ] − S [ i − 1 ] 表示第 i i i 個月的天數。
令 y y y 表示年份, m m m 表示月份, d d d 表示日期,則答案為 S [ m − 1 ] + d S[m-1] + d S [ m − 1 ] + d ,但是需要考慮閏年的情況:
如果 m > 2 m > 2 m > 2 且 y y y 是閏年,則答案需要加 1 1 1 。
當 y m o d 4 = 0 ∧ y m o d 100 ≠ 0 ∨ y m o d 400 = 0 y \mod 4 = 0 \land y \mod 100 \neq 0 \lor y \mod 400 = 0 y mod 4 = 0 ∧ y mod 100 = 0 ∨ y mod 400 = 0 時,y y y 是閏年。
時間複雜度 O ( 1 ) O(1) O ( 1 )
空間複雜度 O ( 1 ) O(1) O ( 1 )
1 2 3 4 5 6 7 class Solution : DAYS = [31 ,28 ,31 ,30 ,31 ,30 ,31 ,31 ,30 ,31 ,30 ,31 ] S = list (accumulate(DAYS, initial=0 )) def dayOfYear (self, date: str ) -> int : y, m, d = map (int , date.split('-' )) isLeap = (y%4 ==0 and y%100 !=0 ) or y%400 ==0 return self.S[m-1 ] + d + (m>2 and isLeap)
題意
給定一個字串 s s s ,返回字串中兩個相同字元的最長子字串的長度 ,計算長度時不含這兩個字元。如果不存在這樣的子字串,則回傳 − 1 -1 − 1 。
子字串 是字串中的一個連續字元序列。
思路
其實就是對每個字元 c h ch c h ,計算出現的第一個位置 x x x 和最後一個位置 y y y ,則答案為 y − x − 1 y - x - 1 y − x − 1 。
由於 s s s 中只有小寫字母,開一個大小為 26 × 2 26 \times 2 26 × 2 的陣列紀錄每個字母的出現位置即可。
時間複雜度 O ( n ) O(n) O ( n )
空間複雜度 O ( 1 ) O(1) O ( 1 )
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def maxLengthBetweenEqualCharacters (self, s: str ) -> int : ans = -1 pos = [[-1 , -1 ] for _ in range (26 )] for i, ch in enumerate (s): ch = ord (ch) - ord ('a' ) if pos[ch][0 ] == -1 : pos[ch][0 ] = i else : pos[ch][1 ] = i ans = max (ans, pos[ch][1 ] - pos[ch][0 ] - 1 ) return ans
寫在最後
每天寫解題紀錄是比寫題目更困難的事情,因為要把思路寫清楚,而且要寫得好看,這是一件很花時間的事情。但是我還是堅持下來了,之後1月因為要準備考試的關係,就先暫停一個月,等考完試再繼續。