每日第1題為中文站(LCCN) ,第2題為英文站(LCUS) ,題目連結皆統一為英文站的題目連結。
Easy 、Medium 、Hard 難度的題目分別用🟢🟡🔴表示。
若為周賽題目會額外標註由 @zreotrac
零神計算的rating分數。
All problems solved by python
2023-12-11
題意
給定一個二維陣列 h e i g h t s heights h e i g h t s ,表示一個地圖,每個格子的高度為 h e i g h t s [ i ] [ j ] heights[i][j] h e i g h t s [ i ] [ j ] ,從左上角 ( 0 , 0 ) (0, 0) ( 0 , 0 ) 到右下角 ( m − 1 , n − 1 ) (m-1, n-1) ( m − 1 , n − 1 ) ,每次可以上下左右四個方向移動,求從左上角到右下角的 最小體力消耗 。
最小體力消耗 的定義是:所選路徑上,相鄰格子之間 高度差絕對值 的 最大值 決定的。
思路:BFS + Binary Search / Disjoint Set (Kruskal) / Dijkstra
方法一:BFS + Binary Search
先把問題簡化成:是否存在一條路徑,使得所有格子之間的高度差絕對值的最大值不超過 k k k ,此時可以用BFS檢查是否存在這樣的路徑。
再來就是如何找到最小的 k k k ,由於 k k k 超過答案值後,一定符合條件,反之則一定不符合,故符合單調性 ,因此可以用Binary Search
找到最小的 k k k 。
時間複雜度:O ( m n log C ) O(mn \log C) O ( mn log C ) ,其中 C C C 為最大高度差絕對值,本題中 C ≤ 1 0 6 C \leq 10^6 C ≤ 1 0 6 。
空間複雜度:O ( m n ) O(mn) O ( mn ) ,即BFS需要用到的空間。
方法二:Disjoint Set (Kruskal)
類似MST中的Kruskal演算法,將所有邊按照權重排序,從小到大加入,若加入後起點和終點在同一個集合中,則表示已經連通,此時的權重即為答案。
對於每個點,可以將其二維座標轉換為一維座標,方便使用Disjoint Set。
時間複雜度:O ( m n log ( m n ) ) O(mn \log(mn)) O ( mn log ( mn )) ,排序的時間複雜度為 O ( m n log ( m n ) ) O(mn \log(mn)) O ( mn log ( mn )) ,Disjoint Set的時間複雜度為 O ( m n α ( m n ) ) O(mn \alpha(mn)) O ( mn α ( mn )) ,其中 α \alpha α 為Ackermann函數的反函數。 α ( m n ) \alpha(mn) α ( mn ) 可以視為常數,因此時間複雜度為 O ( m n log ( m n ) ) O(mn \log(mn)) O ( mn log ( mn ))
空間複雜度:O ( m n ) O(mn) O ( mn ) ,Disjoint Set需要用到的空間。
方法三:Dijkstra
依照Dijkstra的思路,對於每個點,維護一個距離陣列 d i s t dist d i s t ,表示從起點到該點的最小體力消耗(Single Source Shortest Path),初始值為無限大,起點為 d i s t [ 0 ] [ 0 ] = 0 dist[0][0] = 0 d i s t [ 0 ] [ 0 ] = 0 。
對於每個點,維護一個是否已經訪問過的陣列 v i s i t e d visited v i s i t e d ,初始值為 F a l s e False F a l se 。
每次從 d i s t dist d i s t 中選擇一個最小的值,並且標記為已訪問,然後更新與其相鄰的點的距離,直到終點被訪問過。
時間複雜度:O ( m n log ( m n ) ) O(mn \log(mn)) O ( mn log ( mn )) ,每個點最多被訪問一次,每次訪問需要 O ( log ( m n ) ) O(\log(mn)) O ( log ( mn )) 的時間。
空間複雜度:O ( m n ) O(mn) O ( mn ) ,Dijkstra需要用到的空間。
BFS二分 Disjoint Set Dijkstra 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 class Solution : def minimumEffortPath (self, heights: List [List [int ]] ) -> int :。 m, n = len (heights), len (heights[0 ]) def check (k ): DIR = [(0 , 1 ), (0 , -1 ), (1 , 0 ), (-1 , 0 )] q = deque([(0 , 0 )]) visited = set ([(0 , 0 )]) while q: x, y = q.popleft() for dx, dy in DIR: nx, ny = x + dx, y + dy if 0 <= nx < m and 0 <= ny < n and (nx, ny) not in visited: if abs (heights[x][y] - heights[nx][ny]) <= k: q.append((nx, ny)) visited.add((nx, ny)) return (m - 1 , n - 1 ) in visited left, right = 0 , 10 **6 while left <= right: mid = (left + right) // 2 if check(mid): right = mid - 1 else : left = mid + 1 return left
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 class Solution : def minimumEffortPath (self, heights: List [List [int ]] ) -> int : m, n = len (heights), len (heights[0 ]) edges = [] for i in range (m): for j in range (n): idx = i * n + j if i + 1 < m: edges.append((idx + n, idx, abs (heights[i + 1 ][j] - heights[i][j]))) if j + 1 < n: edges.append((idx + 1 , idx, abs (heights[i][j + 1 ] - heights[i][j]))) edges.sort(key=lambda x: x[2 ]) p = [i for i in range (m * n)] def find (x ): if x != p[x]: p[x] = find(p[x]) return p[x] def union (x, y ): px, py = find(x), find(y) if px != py: p[px] = py st, ed = 0 , m * n - 1 for u, v, w in edges: union(u, v) if find(st) == find(ed): return w return 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution : def minimumEffortPath (self, heights: List [List [int ]] ) -> int : m, n = len (heights), len (heights[0 ]) dist = [[float ('inf' )] * n for _ in range (m)] dist[0 ][0 ] = 0 visited = [[False ] * n for _ in range (m)] DIR = [(0 , 1 ), (0 , -1 ), (1 , 0 ), (-1 , 0 )] q = [(0 , 0 , 0 )] while q: d, x, y = heapq.heappop(q) if visited[x][y]: continue visited[x][y] = True for dx, dy in DIR: nx, ny = x + dx, y + dy if 0 <= nx < m and 0 <= ny < n: nd = max (d, abs (heights[x][y] - heights[nx][ny])) if nd < dist[nx][ny]: dist[nx][ny] = nd heapq.heappush(q, (nd, nx, ny)) return dist[-1 ][-1 ]
參考資料
題意
給定一個非遞減的 有序 整數陣列 a r r arr a rr ,已知其中恰好有一個整數的出現次數超過陣列元素總數的 25%,返回這個整數。
思路:Counter / Binary Search
方法一:雜湊表統計(Counter)
統計每個元素出現的次數,根據題意,只有一個元素的出現次數超過25%,因此然後找到出現次數最多,或是出現次數超過25%的元素即可。
時間複雜度:O ( n ) O(n) O ( n ) ,Counter的時間複雜度為 O ( n ) O(n) O ( n ) ,找到出現次數最多的元素的時間複雜度為 O ( n ) O(n) O ( n ) 。
空間複雜度:O ( n ) O(n) O ( n ) ,Counter需要用到的空間。
方法二:二分搜尋(Binary Search)
將 a r r arr a rr 分成4等分,由於題目已經給定 a r r arr a rr 是有序的,出現超過25%的元素在 a r r arr a rr 中的分布會有兩種情況:
全部在其中一個四等分中
橫跨兩個四等分
不管是情況1還是情況2,在四個四等分中,必有至少一個四等分的第一個元素是出現超過25%的元素。
因此只要檢查每一個等分的第一個元素,檢查其出現次數是否超過25%即可。由於 a r r arr a rr 是有序的,因此可以用二分搜尋找到每個等分的第一個元素出現的左界和右界,然後計算出現次數即可。
bisect.bisect_right
回傳 > x >x > x 的第一個下標 (相當於C++中的upper_bound
),
bisect.bisect_left
回傳 ≥ x \geq x ≥ x 第一個下標 (相當於C++中的lower_bound
)。
時間複雜度:O ( 4 ∗ log n ) = O ( log n ) O(4 * \log n) = O(\log n) O ( 4 ∗ log n ) = O ( log n ) ,二分搜尋的時間複雜度為 O ( log n ) O(\log n) O ( log n ) ,需要執行4次。
空間複雜度:O ( 1 ) O(1) O ( 1 ) ,不需要額外的空間。
1 2 3 class Solution : def findSpecialInteger (self, arr: List [int ] ) -> int : return Counter(arr).most_common(1 )[0 ][0 ]
1 2 3 4 5 6 7 8 9 10 class Solution : def findSpecialInteger (self, arr: List [int ] ) -> int : n = len (arr) span = n // 4 + 1 for i in range (0 , n, span): l = bisect_left(arr, arr[i]) r = bisect_right(arr, arr[i]) if r - l >= span: return arr[i] return -1
2023-12-12
題意
給定一個非負整數陣列 n u m s nums n u m s ,找到 n u m s nums n u m s 中每個元素的的 第二大 整數,如果不存在,則為 − 1 -1 − 1 。
如果 n u m s [ j ] nums[j] n u m s [ j ] 滿足下列條件,那麼我們稱它為 nums[i]
的 第二大 整數:
j > i j > i j > i
n u m s [ j ] > n u m s [ i ] nums[j] > nums[i] n u m s [ j ] > n u m s [ i ]
恰好存在 一個 k k k 滿足 i < k < j i < k < j i < k < j 且 n u m s [ k ] > n u m s [ i ] nums[k] > nums[i] n u m s [ k ] > n u m s [ i ] 。
題目有點繞,但其實就是找到 n u m s [ i ] nums[i] n u m s [ i ] 的 下下個 比 n u m s [ i ] nums[i] n u m s [ i ] 大的數,下一個數和下下一個數的大小關係不用考慮。
思路:單調棧(monotonic stack) + Heap
方法一:單調棧 + Min Heap
在 O ( n ) O(n) O ( n ) 時間內找到下一個比 n u m s [ i ] nums[i] n u m s [ i ] 大的數,可以用單調棧來解決,對於每個 n u m s [ i ] nums[i] n u m s [ i ] ,維護一個單調遞減的棧,棧中存放的是 n u m s [ i ] nums[i] n u m s [ i ] 的下標,並且棧頂元素是最小的。
當 n u m s [ i ] nums[i] n u m s [ i ] 比棧頂元素大時,棧頂元素就是下一個比 n u m s [ i ] nums[i] n u m s [ i ] 大的數,此時若要題目要求是下一個比 n u m s [ i ] nums[i] n u m s [ i ] 大的數,將其從棧中彈出,並且更新答案即可。
但此題目要求是下下個比 n u m s [ i ] nums[i] n u m s [ i ] 大的數,因此需要再維護一個最小堆,來找到下下個比 n u m s [ i ] nums[i] n u m s [ i ] 大的數。
時間複雜度:O ( n log n ) O(n \log n) O ( n log n ) ,單調棧的時間複雜度為 O ( n ) O(n) O ( n ) ,最小堆的時間複雜度為 O ( n log n ) O(n \log n) O ( n log n ) 。
空間複雜度:O ( n ) O(n) O ( n ) ,單調棧和最小堆需要用到的空間。
方法二:兩個單調棧
承上,在找第二個比 n u m s [ i ] nums[i] n u m s [ i ] 大的數時,也具有單調性,因此可以用兩個單調棧來解決。
一個單調棧維護下一個比 n u m s [ i ] nums[i] n u m s [ i ] 大的數,另一個單調棧維護下下個比 n u m s [ i ] nums[i] n u m s [ i ] 大的數。
但需要注意,第一個單調棧元素由於是由後往前(小到大)彈出,加入到第二個單調棧中會破壞單調遞減性,因此需要對新增的部分進行反轉,使其仍然是單調遞減的。
時間複雜度:O ( n ) O(n) O ( n ) ,兩個單調棧的時間複雜度為 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 secondGreaterElement (self, nums: List [int ] ) -> List [int ]: n = len (nums) st = [] hp = [] ans = [-1 ] * n for idx, num in enumerate (nums): while hp and hp[0 ][0 ] < num: _, i = heappop(hp) ans[i] = num while st and nums[st[-1 ]] < num: i = st.pop() heappush(hp, (nums[i], i)) st.append(idx) return ans
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution : def secondGreaterElement (self, nums: List [int ] ) -> List [int ]: n = len (nums) st1 = [] st2 = [] ans = [-1 ] * n for idx, num in enumerate (nums): while st2 and nums[st2[-1 ]] < num: i = st2.pop() ans[i] = num size = len (st2) while st1 and nums[st1[-1 ]] < num: i = st1.pop() st2.append(i) if size != len (st2): st2[size:] = reversed (st2[size:]) st1.append(idx) return ans
類題
單調棧
矩形系列
字典序(最小)
貢獻法(計算所有子陣列的……的和)
題單來自:@灵茶山艾府
參考資料
題意
給定一個整數陣列 n u m s nums n u m s ,返回 ( n u m s [ i ] − 1 ) ∗ ( n u m s [ j ] − 1 ) (nums[i]-1)*(nums[j]-1) ( n u m s [ i ] − 1 ) ∗ ( n u m s [ j ] − 1 ) 的最大值,其中 i ≠ j i \neq j i = j 。
1 ≤ n u m s [ i ] ≤ 1 0 3 1 \leq nums[i] \leq 10^3 1 ≤ n u m s [ i ] ≤ 1 0 3
思路:簡單模擬
由於題目限制了 n u m s [ i ] ≥ 1 nums[i] \geq 1 n u m s [ i ] ≥ 1 ,這題只要考慮最大和次大的值的乘積,因此可以用兩個變數來維護最大和次大的值就可以了。
若是沒有限制 n u m s [ i ] ≥ 1 nums[i] \geq 1 n u m s [ i ] ≥ 1 ,則需要維護最小和次小的負數,以及最大和次大的正數,然後比較兩者的乘積。
1 2 3 4 5 6 7 8 9 class Solution : def maxProduct (self, nums: List [int ] ) -> int : mx1 = mx2 = -1 for num in nums: if num > mx1: mx1, mx2 = num, mx1 elif num > mx2: mx2 = num return (mx1 - 1 ) * (mx2 - 1 )
2023-12-13
題意
給定一個由 小寫英文字母 組成的字串 s s s ,在每次操作中,可以用其他小寫英文字母 取代 s s s 中的一個字元。
請你找到操作次數最少的方案,使得 s s s 變成一個 回文字串 。如果有多種方案,只需選取 字典序最小 的方案。返回最後的回文字串。
思路:貪心
要使 s s s 變成回文字串,只需要將 s s s 的前半部分和後半部分對稱即可,因此若對應的字元不同,對兩者取字典序較小的字元即可。
1 2 3 4 5 6 7 8 class Solution : def makeSmallestPalindrome (self, s: str ) -> str : n = len (s) res = list (s) for i in range ((n+1 )//2 ): if s[i] != s[n-1 -i]: res[i] = res[n-1 -i] = min (s[i], s[n-1 -i]) return '' .join(res)
題意
給定一個矩陣 m a t mat ma t ,其中 m a t [ i ] [ j ] mat[i][j] ma t [ i ] [ j ] 是 0 0 0 或 1 1 1 ,請回傳矩陣中 特殊位置 的數量。
若 m a t [ i ] [ j ] = 1 mat[i][j]=1 ma t [ i ] [ j ] = 1 ,第 i i i 行和第 j j j 列中的所有其他元素均為 0 0 0 ,則 ( i , j ) (i, j) ( i , j ) 被認為是一個 特殊位置
思路:簡單模擬
先處理每行每列中 1 1 1 的數量,然後再檢查是否為特殊位置即可。
由於 m a t [ i ] [ j ] mat[i][j] ma t [ i ] [ j ] 只有 0 0 0 和 1 1 1 ,因此可以用用每行每列的和來計算 1 1 1 的數量,不用判斷 m a t [ i ] [ j ] mat[i][j] ma t [ i ] [ j ] 是否為 1 1 1 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution : def numSpecial (self, mat: List [List [int ]] ) -> int : m, n = len (mat), len (mat[0 ]) row, col = [0 ] * m, [0 ] * n for i in range (m): for j in range (n): row[i] += mat[i][j] col[j] += mat[i][j] ans = 0 for i in range (m): for j in range (n): if mat[i][j] == 1 and row[i] == 1 and col[j] == 1 : ans += 1 return ans
2023-12-14
題意
給定一個二維矩陣 g r i d grid g r i d , 若 g r i d [ i ] [ j ] = 0 grid[i][j] = 0 g r i d [ i ] [ j ] = 0 表示該位置為空,若 g r i d [ i ] [ j ] = 1 grid[i][j] = 1 g r i d [ i ] [ j ] = 1 表示該位置被占據,給定郵票的大小 s t a m p H e i g h t × s t a m p W i d t h stampHeight \times stampWidth s t am p He i g h t × s t am p Wi d t h ,我們想用郵票把空的位置都填滿,且不能覆蓋到被占據的位置,郵票可以相互重疊,但不能旋轉,郵票必須完全在矩陣內,可以放入任意數量的郵票。若可以填滿,則返回 t r u e true t r u e ,否則返回 f a l s e false f a l se 。
思路:貪心 + 二維前綴和 + 二維差分
由於之前的 abc331 D ,所以看到題目的時候,很快就想到用二維前綴和檢查區域是否可以放置郵票,並貪心放置所有可能的位置。但一開始標記放置了多少張郵票的時候用了最暴力的 O ( H W ) O(HW) O ( H W ) 方式,因此總時間複雜度為 O ( m n H W ) O(mnHW) O ( mn H W ) ,很明顯會 TLE 。看了題解才知道可以用二維差分來標記放置了郵票的位置,這樣時間複雜度就可以降到 O ( m n ) O(mn) O ( mn ) 了,又學到了一招。
由於不限制郵票的數量,且郵票可以相互重疊,因此可以用貪心的思路,先將所有可以放置郵票的位置都放置郵票,然後檢查是否有沒有放置郵票的位置,若有,則不可行。因此問題主要分成兩個部分
如何 檢查 某個區域是否可以放置郵票;
如何 標記 某個區域放置了郵票。
對於第一個問題,可以用二維前綴和來檢查,對於第二個問題,可以用二維差分來標記。
首先對 g r i d grid g r i d 做二維前綴和,表示區域中有幾個 1 1 1 ,即不能放置郵票的位置數量,對於左上角為 ( a , b ) (a, b) ( a , b ) ,右下角為 ( c , d ) (c, d) ( c , d ) 的區域,其不能放置郵票的位置數量為 s [ c ] [ d ] − s [ a − 1 ] [ d ] − s [ c ] [ b − 1 ] + s [ a − 1 ] [ b − 1 ] s[c][d] - s[a-1][d] - s[c][b-1] + s[a-1][b-1] s [ c ] [ d ] − s [ a − 1 ] [ d ] − s [ c ] [ b − 1 ] + s [ a − 1 ] [ b − 1 ] 。
若區域中不能放置郵票的位置數量為 0 0 0 ,則可以放置郵票,在 d i f f diff d i ff 中的四個位置標記放置了郵票,即 d i f f [ a − 1 ] [ b − 1 ] + = 1 , d i f f [ c ] [ b − 1 ] − = 1 , d i f f [ a − 1 ] [ d ] − = 1 , d i f f [ c ] [ d ] + = 1 diff[a-1][b-1] += 1, diff[c][b-1] -= 1, diff[a-1][d] -= 1, diff[c][d] += 1 d i ff [ a − 1 ] [ b − 1 ] + = 1 , d i ff [ c ] [ b − 1 ] − = 1 , d i ff [ a − 1 ] [ d ] − = 1 , d i ff [ c ] [ d ] + = 1 。
最後對所有位置做二維差分前綴和,並檢查所有位置,若可放置郵票(即 g r i d [ i ] [ j ] = 0 grid[i][j] = 0 g r i d [ i ] [ j ] = 0 ),但卻沒有放置郵票(即 d i f f [ i ] [ j ] = 0 diff[i][j] = 0 d i ff [ i ] [ j ] = 0 )的位置,則返回 f a l s e false f a l se ,否則返回 t r u e true t r u e 。
時間複雜度:O ( m n ) O(mn) O ( mn ) ,二維前綴和的時間複雜度為 O ( m n ) O(mn) O ( mn ) ,二維差分的時間複雜度為 O ( m n ) O(mn) O ( mn ) ,二維差分前綴和的時間複雜度為 O ( m n ) O(mn) O ( mn ) 。
空間複雜度:O ( m n ) O(mn) O ( mn ) ,二維前綴和和二維差分需要用到的空間。
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 class Solution : def possibleToStamp (self, grid: List [List [int ]], stampHeight: int , stampWidth: int ) -> bool : m, n = len (grid), len (grid[0 ]) pre_sum = [[0 ] * (n + 1 ) for _ in range (m + 1 )] for i in range (m): for j in range (n): pre_sum[i + 1 ][j + 1 ] = pre_sum[i + 1 ][j] + pre_sum[i][j + 1 ] - pre_sum[i][j] + grid[i][j] diff = [[0 ] * (n + 2 ) for _ in range (m + 2 )] for c in range (stampHeight, m + 1 ): for d in range (stampWidth, n + 1 ): a, b = c - stampHeight + 1 , d - stampWidth + 1 if pre_sum[c][d] - pre_sum[a - 1 ][d] - pre_sum[c][b - 1 ] + pre_sum[a - 1 ][b - 1 ] == 0 : diff[a - 1 ][b - 1 ] += 1 diff[c][b - 1 ] -= 1 diff[a - 1 ][d] -= 1 diff[c][d] += 1 for i in range (m): for j in range (n): diff[i][j] += diff[i][j - 1 ] + diff[i - 1 ][j] - diff[i - 1 ][j - 1 ] if grid[i][j] == 0 and diff[i][j] == 0 : return False return True
類題
題單來自:@灵茶山艾府 、@newhar
參考資料
題意
給定一個 m × n m \times n m × n 的二進位矩陣 g r i d grid g r i d ,返回一個相同大小的差值矩陣 d i f f diff d i ff , d i f f [ i ] [ j ] = o n e s R o w i + o n e s C o l j − z e r o s R o w i − z e r o s C o l j diff[i][j] = onesRow_i + onesCol_j - zerosRow_i - zerosCol_j d i ff [ i ] [ j ] = o n es R o w i + o n es C o l j − zeros R o w i − zeros C o l j ,其中
o n e s R o w i onesRow_i o n es R o w i 表示第 i i i Row 1 1 1 的數目;
o n e s C o l j onesCol_j o n es C o l j 表示第 j j j Col 1 1 1 的數目;
z e r o s R o w i zerosRow_i zeros R o w i 表示第 i i i Row 0 0 0 的數目;
z e r o s C o l j zerosCol_j zeros C o l j 表示第 j j j Col 0 0 0 的數目。
思路:簡單模擬
由於是二進位矩陣,因此只需要計算每行每列的 1 1 1 的數量即可,z e r o s R o w i = n − o n e s R o w i zerosRow_i=n-onesRow_i zeros R o w i = n − o n es R o w i ,z e r o s C o l j = m − o n e s C o l j zerosCol_j=m-onesCol_j zeros C o l j = m − o n es C o l j ,因此 d i f f [ i ] [ j ] = 2 ∗ o n e s R o w i + 2 ∗ o n e s C o l j − m − n diff[i][j] = 2 * onesRow_i + 2 * onesCol_j - m - n d i ff [ i ] [ j ] = 2 ∗ o n es R o w i + 2 ∗ o n es C o l j − m − n 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution : def onesMinusZeros (self, grid: List [List [int ]] ) -> List [List [int ]]: m, n = len (grid), len (grid[0 ]) rows = [0 ] * m cols = [0 ] * n for i in range (m): for j in range (n): rows[i] += grid[i][j] cols[j] += grid[i][j] diff = [[0 ] * n for _ in range (m)] for i in range (m): for j in range (n): diff[i][j] = 2 * rows[i] + 2 * cols[j] - m - n return diff
2023-12-15
題意
給定一個二元樹 r o o t root roo t ,將所有奇數層的節點的值反轉,偶數層的節點的值不變,並返回反轉後的二元樹。
定義樹根為第 0 0 0 層,且這棵樹是 full binary tree
。
註:full/compelete/perfect binary tree的差別,說明及圖例來源自參考資料。
full binary tree
:除了樹葉以外,每個節點都有兩個小孩。
complete binary tree
:各層節點全滿,除了最後一層,最後一層節點全部靠左。
perfect binary tree
:各層節點全滿。同時也是 full binary tree 和 complete binary tree 。
思路:BFS (Level Order Traversal)
由於要求偶數層的節點的值不變,因此可以在基於 BFS (Level Order Traversal) 的基礎上來解決,對於每一層,若是奇數層,則將節點的值反轉,否則不變。
由於是 perfect binary tree
,因此每個節點都有對應的節點存在,直接交換對應節點的值即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution : def reverseOddLevels (self, root: Optional [TreeNode] ) -> Optional [TreeNode]: level = 0 q = deque([root]) while q: n = len (q) if level % 2 == 1 : for i in range (n // 2 ): q[i].val, q[-i - 1 ].val = q[-i - 1 ].val, q[i].val for _ in range (n): node = q.popleft() if node.left: q.append(node.left) if node.right: q.append(node.right) level += 1 return root
參考資料
題意
給定一個二維矩陣 p a t h s paths p a t h s ,其中 p a t h s [ i ] = [ c i t y A i , c i t y B i ] paths[i] = [cityA_i, cityB_i] p a t h s [ i ] = [ c i t y A i , c i t y B i ] ,表示一條從 c i t y A i cityA_i c i t y A i 到 c i t y B i cityB_i c i t y B i 的有向邊,返回旅途的終點,即沒有任何可以通往其他城市的路徑的城市。
思路:簡單模擬
統計每個點的outdegree,若outdegree為 0 0 0 ,則為答案。
或是直接用一個Hash Set紀錄所有可以通往其他城市的城市 c i t y A i cityA_i c i t y A i ,最後檢查 c i t y B i cityB_i c i t y B i 是否在 Hash Set 中,若不在則為答案。
1 2 3 4 5 6 7 8 9 10 11 class Solution : def destCity (self, paths: List [List [str ]] ) -> str : outdegree = defaultdict(int ) for u, v in paths: outdegree[u] += 1 outdegree[v] += 0 for node in outdegree: if outdegree[node] == 0 : return node return ""
2023-12-16
本月第3次未做先看題解,雖然遇到過好幾次線段樹了,但一直沒有好好了解,只能說該來的還是朵不掉。
題意
給定一個 空 的區間集合,每次給你一個區間,要求你將這個區間加入到區間集合之中,並且統計出現在 至少一個 區間的整數個數。
實作 CountIntervals
類別:
CountIntervals()
使用區間的空集合初始化對象
void add(int left, int right)
增加區間 [ l e f t , r i g h t ] [left, right] [ l e f t , r i g h t ] 到區間集合之中。
int count()
傳回出現在 至少一個 區間的整數個數。
**注意:**區間 [ l e f t , r i g h t ] [left, right] [ l e f t , r i g h t ] 表示滿足 l e f t < = x < = r i g h t left <= x <= right l e f t <= x <= r i g h t 的所有整數 x x x 。
思路:珂朵莉樹(Chtholly Tree) / 線段樹(Segment Tree)
方法一:珂朵莉樹(Chtholly Tree) / 老司機樹(Old Driver Tree,ODT)
暴力的美學
珂朵莉樹(Chtholly Tree)
,又稱 老司機樹(Old Driver Tree,ODT)
,起源於CF896C ,因為題目背景主角是《末日時在做什麼?有沒有空?可以來拯救嗎?》的主角 珂朵莉 ,因此該資料結構被稱為珂朵莉樹。該題目要求實作一種資料結構,可以較快實現以下四種操作:
區間加:將區間 [ L , R ] [L,R] [ L , R ] 中的所有數加上 v v v
區間賦值:將區間 [ L , R ] [L,R] [ L , R ] 中的所有數改成 v v v
求區間第k大值:輸出區間 [ L , R ] [L,R] [ L , R ] 中第 k k k 大的數
求區間n次方和:輸出 ∑ i = L R a i n \sum_{i=L}^{R}a_i^n ∑ i = L R a i n ,即區間 [ L , R ] [L,R] [ L , R ] 內元素的 n n n 次幂和
珂朵莉樹
可以實現區間推平操作,並且支持區間修改和區間查詢。因此適用於這類 有區間推平又有隨機操作 的題目。
珂朵莉樹
的核心思想在於隨機賦值操作很可能讓大量元素變成同一個數,因此我們可以用三元組 ( l , r , v ) (l,r,v) ( l , r , v ) 來表示一個節點,其中 l l l 表示節點的左端點, r r r 表示節點的右端點, v v v 表示 l l l 和 r r r 之間的所有元素都是 v v v 。
在保存這些三元組時,需要使用 有序集合 維護這些區間,以左端點作為關鍵字進行升序排列,這樣方便進行一些操作的時候方便查找到我們想要修改的區間。在 C++
中 的 std::set
即是一種有序集合,而在 Python
中,可以使用 sortedcontainers
來實現有序集合。雖然看似與樹無關,但實際上 std::set
是用 紅黑樹(Red-Black Tree)
實現的,因此 珂朵莉樹
也可以看作是一種樹。
在進行區間操作時,可能會把原本連續的區間斷開,因此需要一個函數來實現斷開操作,把 ( l , r , v ) (l,r,v) ( l , r , v ) 斷成 ( l , p o s − 1 , v ) (l,pos-1,v) ( l , p os − 1 , v ) 和 ( p o s , r , v ) (pos,r,v) ( p os , r , v ) ,可以使用 Binary Search
來找到需要斷開的位置。
回到本題,本題要求實現以下兩種操作:
區間染色:將區間 [ L , R ] [L,R] [ L , R ] 中的所有數從 0 0 0 改成 1 1 1
求區間和:輸出 ∑ i = L R a i \sum_{i=L}^{R}a_i ∑ i = L R a i ,即區間 [ L , R ] [L,R] [ L , R ] 內元素的和
由於本題染色只會從 0 0 0 改成 1 1 1 ,因此三元組中的 v v v 只有 1 1 1 一種情況,因此可以省略 v v v ,只用 ( l , r ) (l,r) ( l , r ) 來表示一個節點。
方法二:線段樹(Segment Tree)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from sortedcontainers import SortedListclass CountIntervals : def __init__ (self ): self.sl = SortedList(key=lambda x: x[1 ]) self.ans = 0 def add (self, left: int , right: int ) -> None : i = self.sl.bisect_left((0 , left)) while i < len (self.sl) and self.sl[i][0 ] <= right: l, r = self.sl[i] left = min (left, l) right = max (right, r) self.ans -= r - l + 1 self.sl.pop(i) self.ans += right - left + 1 self.sl.add((left, right)) def count (self ) -> int : return self.ans
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class CountIntervals : __slots__ = 'left' , 'right' , 'l' , 'r' , 'cnt' def __init__ (self, l=1 , r=10 ** 9 ): self.left = self.right = None self.l, self.r, self.cnt = l, r, 0 def add (self, L: int , R: int ) -> None : if self.cnt == self.r - self.l + 1 : return if L <= self.l and self.r <= R: self.cnt = self.r - self.l + 1 return mid = (self.l + self.r) // 2 if self.left is None : self.left = CountIntervals(self.l, mid) if self.right is None : self.right = CountIntervals(mid + 1 , self.r) if L <= mid: self.left.add(L, R) if mid < R: self.right.add(L, R) self.cnt = self.left.cnt + self.right.cnt def count (self ) -> int : return self.cnt
類題
珂朵莉樹(Chtholly Tree)
線段樹(Segment Tree)
參考資料
題意
給定兩個字串 s s s 和 t t t ,判斷 t t t 是否為 s s s 的 Anagram(字母異位詞) ,也就是說, s s s 和 t t t 中的字元出現的次數都相同,或是說, s s s 可以重新排列成 t t t 。
思路:排序 / 雜湊表
方法一:排序
將 s s s 和 t t t 排序後,判斷兩者是否相同即可。
時間複雜度:O ( n l o g n ) O(nlogn) O ( n l o g n ) ,排序的時間複雜度為 O ( n l o g n ) O(nlogn) O ( n l o g n ) ,比較兩個字串是否相同的時間複雜度為 O ( n ) O(n) O ( n ) 。
空間複雜度:O ( n ) O(n) O ( n ) ,排序需要用到的空間。
方法二:雜湊表
統計每個元素出現的次數,然後比較兩個字串中每個元素出現的次數是否相同。
時間複雜度:O ( n ) O(n) O ( n ) ,統計每個元素出現的次數的時間複雜度為 O ( n ) O(n) O ( n ) ,比較兩個字串中每個元素出現的次數是否相同的時間複雜度為 O ( n ) O(n) O ( n ) 。
空間複雜度:O ( n ) O(n) O ( n ) ,統計每個元素出現的次數需要用到的空間。
1 2 3 4 class Solution : def isAnagram (self, s: str , t: str ) -> bool : return Counter(s) == Counter(t)
2023-12-17
題意
給定一個Array c o s t cost cos t ,其中 c o s t [ i ] cost[i] cos t [ i ] 表示從第 i i i 個台階向上爬的花費,每次可以選擇向上爬一個或兩個台階,返回達到樓梯頂部的最低花費。
可以選擇從下標 0
或下標為 1
的階梯開始。
思路:動態規劃
定義 d p [ i ] dp[i] d p [ i ] 為爬到第 i i i 個台階的最低花費,則轉移方程式為 d p [ i ] = m i n ( d p [ i − 1 ] + c o s t [ i − 1 ] , d p [ i − 2 ] + c o s t [ i − 2 ] ) dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]) d p [ i ] = min ( d p [ i − 1 ] + cos t [ i − 1 ] , d p [ i − 2 ] + cos t [ i − 2 ]) 。
需要注意的是,可以選擇從下標 0
或下標為 1
的階梯開始,因此 d p [ 0 ] = d p [ 1 ] = 0 dp[0] = dp[1] = 0 d p [ 0 ] = d p [ 1 ] = 0 。
1 2 3 4 5 6 7 8 class Solution : def minCostClimbingStairs (self, cost: List [int ] ) -> int : n = len (cost) dp = [0 ]*(n+1 ) dp[0 ], dp[1 ] = 0 , 0 for i in range (2 , n+1 ): dp[i] = min (dp[i-1 ]+cost[i-1 ], dp[i-2 ]+cost[i-2 ]) return dp[-1 ]
題意
實作 FoodRatings
類別:
FoodRatings(String[] foods, String[] cuisines, int[] ratings)
初始化。 食物由 f o o d s foods f oo d s 、c u i s i n e s cuisines c u i s in es 和 r a t i n g s ratings r a t in g s 描述,長度均為 n n n 。
f o o d s [ i ] foods[i] f oo d s [ i ] 是第 i i i 種食物的名字。
c u i s i n e s [ i ] cuisines[i] c u i s in es [ i ] 是第 i i i 種食物的烹調方式。
r a t i n g s [ i ] ratings[i] r a t in g s [ i ] 是第 i i i 種食物的初步評分。
void changeRating(String food, int newRating)
修改名字為 f o o d food f oo d 的食物的評分為 n e w R a t i n g newRating n e wR a t in g 。
String highestRated(String cuisine)
傳回指定烹調方式 cuisine
下評分最高的食物的名字。 如果存在並列,則傳回 字典序較小 的食物名字。
思路:雜湊表 + 有序集合/懶刪除堆
方法一:雜湊表 + 有序集合
使用雜湊表 f d fd fd 來紀錄食物的評分和所屬烹調方式,雜湊表 c s cs cs 來紀錄烹調方式下所屬的食物和評分,並使用有序集合來維護。
對於 changeRating
操作,只需要在 f d fd fd 中修改食物的評分,並在 c s cs cs 中刪除原本的食物,再將修改後的食物加入 c s cs cs 中即可。
對於 highestRated
操作,只需要在 c s cs cs 中找到指定烹調方式下評分最高的食物即可,由於是有序集合,因此可以直接取第一個元素。
方法二:雜湊表 + 懶刪除堆
但我們 只在意評分最高的食物 ,因此也可以使用 懶刪除堆 來實現,在更改分數的時候不需要刪除原本的食物,直接添加新的食物即可。
當我們需要取得評分最高的食物時,只需要檢查堆頂的食物是否被修改過,若被修改過,則捨棄,直到堆頂的食物沒有被修改過,則為評分最高的食物。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from sortedcontainers import SortedSetclass FoodRatings : def __init__ (self, foods: List [str ], cuisines: List [str ], ratings: List [int ] ): self.fd = dict () ss = lambda : SortedSet([], key=lambda x: (-x[1 ], x[0 ])) self.cs = defaultdict(ss) for f, c, r in zip (foods, cuisines, ratings): self.fd[f] = (c, r) self.cs[c].add((f, r)) def changeRating (self, food: str , newRating: int ) -> None : c, r = self.fd[food] self.cs[c].remove((food, r)) self.cs[c].add((food, newRating)) self.fd[food] = (c, newRating) def highestRated (self, cuisine: str ) -> str : return self.cs[cuisine][0 ][0 ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class FoodRatings : def __init__ (self, foods: List [str ], cuisines: List [str ], ratings: List [int ] ): self.fd = dict () self.cs = defaultdict(list ) for f, c, r in zip (foods, cuisines, ratings): self.fd[f] = (c, r) self.cs[c].append((-r, f)) for c in self.cs: heapq.heapify(self.cs[c]) def changeRating (self, food: str , newRating: int ) -> None : c, r = self.fd[food] heappush(self.cs[c], (-newRating, food)) self.fd[food] = (c, newRating) def highestRated (self, cuisine: str ) -> str : hp = self.cs[cuisine] while -hp[0 ][0 ] != self.fd[hp[0 ][1 ]][1 ]: heappop(hp) return hp[0 ][1 ]
參考資料
2023-12-18
題意
給定一個整數陣列 n u m s nums n u m s ,找到一個 峰值元素 並返回其索引。 陣列可能包含多個峰值,在這種情況下,返回 任何一個峰值 所在位置即可。
峰值元素 是嚴格大於左右相鄰值的元素。
可以假設 n u m s [ − 1 ] = n u m s [ n ] = − ∞ nums[-1] = nums[n] = -∞ n u m s [ − 1 ] = n u m s [ n ] = − ∞ 。
需要以 O ( l o g n ) O(log n) O ( l o g n ) 的時間複雜度來解決此問題。
對於所有有效的 i i i 都有 n u m s [ i ] ≠ n u m s [ i + 1 ] nums[i] \neq nums[i + 1] n u m s [ i ] = n u m s [ i + 1 ]
思路:簡單模擬 / 找最大值 / 二分搜尋
方法一:簡單模擬
根據題目的假設: n u m s [ − 1 ] = n u m s [ n ] = − ∞ nums[-1] = nums[n] = -∞ n u m s [ − 1 ] = n u m s [ n ] = − ∞ ,在陣列前後添加 − ∞ -∞ − ∞ 後,從頭開始遍歷,找到第一個滿足 n u m s [ i − 1 ] < n u m s [ i ] > n u m s [ i + 1 ] nums[i-1] < nums[i] > nums[i+1] n u m s [ i − 1 ] < n u m s [ i ] > n u m s [ i + 1 ] 的 i i i 即可。
時間複雜度:O ( n ) O(n) O ( n )
空間複雜度:O ( n ) O(n) O ( n )
方法二:找最大值
由於題目假設 n u m s [ − 1 ] = n u m s [ n ] = − ∞ nums[-1] = nums[n] = -∞ n u m s [ − 1 ] = n u m s [ n ] = − ∞ ,且 n u m s [ i ] ≠ n u m s [ i + 1 ] nums[i] \neq nums[i + 1] n u m s [ i ] = n u m s [ i + 1 ] ,因此最大值的所在位置一定是峰值元素,找出最大值的所在位置即可。
時間複雜度:O ( n ) O(n) O ( n )
空間複雜度:O ( 1 ) O(1) O ( 1 ) ,不計算輸入的空間。
方法三:二分搜尋
題目要求實作 O ( l o g n ) O(logn) O ( l o g n ) 時間複雜度的演算法,這基本上就是在暗示我們使用「二分搜尋」了。
由於題目確保 n u m s [ − 1 ] = n u m s [ n ] = − ∞ nums[-1] = nums[n] = -∞ n u m s [ − 1 ] = n u m s [ n ] = − ∞ 且 n u m s [ i ] ≠ n u m s [ i + 1 ] nums[i] \neq nums[i + 1] n u m s [ i ] = n u m s [ i + 1 ] ,在以 m i d mid mi d 為分界點的陣列上,根據 n u m s [ m i d ] nums[mid] n u m s [ mi d ] 與 n u m s [ m i d ± 1 ] nums[mid±1] n u m s [ mi d ± 1 ] 的大小關係,可以確定其中一段滿足「必然有解」,另外一段不滿足「必然有解」(可能有解,可能無解)。
若 n u m s [ x ] > n u m s [ x − 1 ] nums[x] > nums[x-1] n u m s [ x ] > n u m s [ x − 1 ] ,則 n u m s [ x ] nums[x] n u m s [ x ] 的右邊一定存在峰值。
若 n u m s [ x ] > n u m s [ x + 1 ] nums[x] > nums[x+1] n u m s [ x ] > n u m s [ x + 1 ] ,則 n u m s [ x ] nums[x] n u m s [ x ] 的左邊一定存在峰值。
以上結論可由證明以下兩點得出,具體證明過程此處省略,請參考參考資料。
對於任意陣列而言,一定存在峰值(一定有解)
二分不會錯過峰值
時間複雜度:O ( l o g n ) O(logn) O ( l o g n )
空間複雜度:O ( 1 ) O(1) O ( 1 ) ,不計算輸入的空間。
1 2 3 4 5 6 7 8 class Solution : def findPeakElement (self, nums: List [int ] ) -> int : n = len (nums) nums = [float ('-inf' )] + nums + [float ('-inf' )] for i in range (1 , n+1 ): if nums[i-1 ] < nums[i] > nums[i+1 ]: return i-1 return -1
1 2 3 4 5 6 7 class Solution : def findPeakElement (self, nums: List [int ] ) -> int : ans = 0 for i, x in enumerate (nums): if x > nums[ans]: ans = i return ans
1 2 3 4 5 6 7 8 9 10 11 class Solution : def findPeakElement (self, nums: List [int ] ) -> int : n = len (nums) left, right = 0 , n-1 while (left <= right): mid = (left + right) // 2 if mid == (n-1 ) or nums[mid] > nums[mid+1 ]: right = mid - 1 else : left = mid + 1 return left
參考資料
題意
給定一個整數陣列 n u m s nums n u m s ,選出四個 不同的 下標 w w w 、x x x 、y y y 和 z z z ,使數對( n u m s [ w ] , n u m s [ x ] ) (nums[w], nums[x]) ( n u m s [ w ] , n u m s [ x ]) 和 ( n u m s [ y ] , n u m s [ z ] ) (nums[y], nums[z]) ( n u m s [ y ] , n u m s [ z ]) 之間的 product difference 有 最大值 。
兩個數對 ( a , b ) (a, b) ( a , b ) 和 ( c , d ) (c, d) ( c , d ) 之間的 product difference 定義為 ( a × b ) − ( c × d ) (a \times b) - (c \times d) ( a × b ) − ( c × d ) 。
返回 product difference 的 最大值 。
思路:貪心
要使 product difference 有最大值,很明顯 ( a × b ) (a \times b) ( a × b ) 的值要盡可能的大,( c × d ) (c \times d) ( c × d ) 的值要盡可能的小。
因此只要取出 n u m s nums n u m s 中的最大和最小的各兩個數字,計算後即可。
時間複雜度: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 class Solution : def maxProductDifference (self, nums: List [int ] ) -> int : mx1, mx2 = float ('-inf' ), float ('-inf' ) mn1, mn2 = float ('inf' ), float ('inf' ) for num in nums: if num > mx1: mx1, mx2 = num, mx1 elif num > mx2: mx2 = num if num < mn1: mn1, mn2 = num, mn1 elif num < mn2: mn2 = num return mx1 * mx2 - mn1 * mn2
2023-12-19
題意
給定一個 m × n m \times n m × n 的矩陣 m a t mat ma t ,找到一個 峰值元素 並返回其位置。 陣列可能包含多個峰值,在這種情況下,返回 任何一個峰值 所在位置即可。
二維陣列中的 峰值元素 是嚴格大於其相鄰(上、下、左、右)元素的元素。
m a t mat ma t 內所有元素皆為正整數,即 1 ≤ m a t [ i ] [ j ] 1 \leq mat[i][j] 1 ≤ ma t [ i ] [ j ] 。
可以假設整個矩陣週邊環繞著一圈值為 − 1 -1 − 1 的格子。
任兩個相鄰元素均不相等
需要以 O ( m l o g ( n ) ) O(m log(n)) O ( m l o g ( n )) 或 O ( n l o g ( m ) ) O(n log(m)) O ( n l o g ( m )) 的時間複雜度來解決此問題。
思路:找最大值 / 二分搜尋
昨天的每日一題的進階版,只是把一維陣列改成二維陣列。
方法一:找最大值
基本上說明和昨天的每日一題一樣,就不再贅述。
時間複雜度:O ( m n ) O(mn) O ( mn )
空間複雜度:O ( 1 ) O(1) O ( 1 ) ,不計算輸入的空間。
方法二:二分搜尋
在以 m i d mid mi d 為分界點的陣列上,根據 m a x ( m a t [ m i d ] ) max(mat[mid]) ma x ( ma t [ mi d ]) 和他下面的相鄰數字的大小關係,可以確定其中一段滿足「必然有解」,另外一段不滿足「必然有解」(可能有解,可能無解)。
若 m a x ( m a t [ m i d ] ) max(mat[mid]) ma x ( ma t [ mi d ]) 比他下面的相鄰數字小 ,則存在一個峰頂,其行號 > m i d >mid > mi d ,縮小二分範圍,更新二分區間左端點 l e f t left l e f t 。
若 m a x ( m a t [ m i d ] ) max(mat[mid]) ma x ( ma t [ mi d ]) 比他下面的相鄰數字大 ,則存在一個峰頂,其行號 ≤ m i d \leq mid ≤ mi d ,縮小二分範圍,更新二分區間右端點 r i g h t right r i g h t 。
對於本題,如果每次二分,都是 m a x ( m a t [ m i d ] ) max(mat[mid]) ma x ( ma t [ mi d ]) 比它下面的相鄰數字小,那麼最後會判斷出 峰頂行號 > ( m − 2 ) > (m-2) > ( m − 2 ) ,此時可以直接確定最後一行必然包含峰頂 。這表示 m − 1 m-1 m − 1 不需要在初始二分範圍內,因此二分範圍為 [ 0 , m − 2 ] [0, m-2] [ 0 , m − 2 ] 。
時間複雜度:O ( m l o g ( n ) ) O(m log(n)) O ( m l o g ( n ))
空間複雜度:O ( 1 ) O(1) O ( 1 ) ,不計算輸入的空間。
1 2 3 4 5 6 7 8 9 class Solution : def findPeakGrid (self, mat: List [List [int ]] ) -> List [int ]: m, n = len (mat), len (mat[0 ]) ans = [0 , 0 ] for i in range (m): for j in range (n): if mat[i][j] > mat[ans[0 ]][ans[1 ]]: ans = [i, j] return ans
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution : def findPeakGrid (self, mat: List [List [int ]] ) -> List [int ]: m, n = len (mat), len (mat[0 ]) left, right = 0 , m - 2 while left <= right: mid = (left + right) // 2 mx, idx = max ((v, i) for i, v in enumerate (mat[mid])) if mx > mat[mid + 1 ][idx]: right = mid - 1 else : left = mid + 1 i = left _, j = max ((v, j) for j, v in enumerate (mat[i])) return [i, j]
類題:二分題單
二分答案
最小化最大值
最大化最小值
題單來自:@灵茶山艾府
參考資料
題意
給定一個大小為 3 × 3 3 \times 3 3 × 3 的 image smoother ,用於對影像的每個單元格平滑處理,平滑處理後單元格的值為該單元格的平均灰階值。
每個單元格的 平均灰階值 定義為:此單元格本身及其周圍的 8 個單元格的平均值,結果 需向下取整 。 (即,需要計算藍色平滑器中 9 個單元格的平均值)。
如果一個單元格周圍存在單元格缺失的情況,則計算平均灰階值時不考慮缺少的單元格(即,需要計算紅色平滑器中 4 個單元格的平均值)。
思路:模擬 / 二維前綴和
方法一:模擬
用一個 8 8 8 方向的方向陣列來表示周圍的單元格,然後遍歷每個單元格,計算周圍單元格的數量和灰階值,最後計算平均灰階值即可。
時間複雜度:O ( C ∗ m ∗ n ) O(C*m*n) O ( C ∗ m ∗ n ) ,m m m 和 n n n 分別為影像的行數和列數,C C C 為image smoother 的大小,本題為 3 × 3 = 9 3 \times 3 = 9 3 × 3 = 9 。
空間複雜度:O ( m ∗ n ) O(m*n) O ( m ∗ n ) ,需要用到一個 m × n m \times n m × n 的二維陣列。
方法二:二維前綴和
在方法一中,對於每個像素點,都需要檢查其 8 8 8 個方向,若擴大 kernel
的大小,則需要檢查更多的單元格,這個計算過程可以使用二維前綴和來優化。
定義 s [ i ] [ j ] s[i][j] s [ i ] [ j ] 為 i m g img im g 中 ( 0 , 0 ) (0, 0) ( 0 , 0 ) 至 ( i − 1 , j − 1 ) (i-1, j-1) ( i − 1 , j − 1 ) 區域的灰階和,則 ( a , b ) (a, b) ( a , b ) 至 ( c , d ) (c, d) ( c , d ) 區域的灰階和為 s [ c + 1 ] [ d + 1 ] − s [ c + 1 ] [ b ] − s [ a ] [ d + 1 ] + s [ a ] [ b ] s[c+1][d+1] - s[c+1][b] - s[a][d+1] + s[a][b] s [ c + 1 ] [ d + 1 ] − s [ c + 1 ] [ b ] − s [ a ] [ d + 1 ] + s [ a ] [ b ] 。
在計算 [ x , y ] [x,y] [ x , y ] 單元格的平均灰階值時,需計算 ( x − 1 , y − 1 ) (x-1, y-1) ( x − 1 , y − 1 ) 至 ( x + 1 , y + 1 ) (x+1, y+1) ( x + 1 , y + 1 ) 區域的灰階和,然而為了避免越界,需要對 x x x 和 y y y 進行限制,即 a = m a x ( 0 , x − 1 ) a = max(0, x-1) a = ma x ( 0 , x − 1 ) 、b = m a x ( 0 , y − 1 ) b = max(0, y-1) b = ma x ( 0 , y − 1 ) 、c = m i n ( m − 1 , x + 1 ) c = min(m-1, x+1) c = min ( m − 1 , x + 1 ) 、d = m i n ( n − 1 , y + 1 ) d = min(n-1, y+1) d = min ( n − 1 , y + 1 ) 。
時間複雜度:O ( m ∗ n ) O(m*n) O ( m ∗ n ) ,m m m 和 n n n 分別為影像的行數和列數。
空間複雜度:O ( m ∗ n ) O(m*n) O ( m ∗ n ) ,需要用到一個 ( m + 1 ) × ( n + 1 ) (m+1) \times (n+1) ( m + 1 ) × ( n + 1 ) 的二維陣列 s s s ,以及一個 m × n m \times n m × n 的二維陣列 a n s ans an s 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution : def imageSmoother (self, img: List [List [int ]] ) -> List [List [int ]]: DIRS = [(-1 , -1 ), (-1 , 0 ), (-1 , 1 ), (0 , -1 ), (0 , 1 ), (1 , -1 ), (1 , 0 ), (1 , 1 )] m, n = len (img), len (img[0 ]) ans = [[0 ]*n for _ in range (m)] for i in range (m): for j in range (n): cnt, s = 1 , img[i][j] for di, dj in DIRS: ni, nj = i+di, j+dj if 0 <= ni < m and 0 <= nj < n: cnt += 1 s += img[ni][nj] ans[i][j] = s // cnt return ans
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution : def imageSmoother (self, img: List [List [int ]] ) -> List [List [int ]]: m, n = len (img), len (img[0 ]) s = [[0 ] * (n + 2 ) for _ in range (m + 2 )] for i in range (1 , m + 1 ): for j in range (1 , n + 1 ): s[i][j] = s[i - 1 ][j] + s[i][j - 1 ] - s[i - 1 ][j - 1 ] + img[i - 1 ][j - 1 ] ans = [[0 ] * n for _ in range (m)] for i in range (m): for j in range (n): a, b = max (0 , i - 1 ), max (0 , j - 1 ) c, d = min (m - 1 , i + 1 ), min (n - 1 , j + 1 ) cnt = (c - a + 1 ) * (d - b + 1 ) tot = s[c + 1 ][d + 1 ] - s[a][d + 1 ] - s[c + 1 ][b] + s[a][b] ans[i][j] = tot // cnt return ans
2023-12-20
第2次週賽的題目,沒想到還沒補到那時候的題目,就已經出現在每日一題了。
題意
給定一個字串陣列 w o r d s words w or d s 和一個字串 s s s ,判斷 s s s 是否為 w o r d s words w or d s 的 縮寫 。
思路:簡單模擬
若 s s s 的長度不等於 w o r d s words w or d s 的長度,則肯定不是縮寫。否則就檢查 s s s 的每個字元是否為對應的 w o r d s words w or d s 中的字串的首字元即可。
時間複雜度:O ( n ) O(n) O ( n ) ,n n n 為 w o r d s words w or d s 的長度。
1 2 3 4 5 6 7 8 9 class Solution : def isAcronym (self, words: List [str ], s: str ) -> bool : if len (words) != len (s): return False for idx, word in enumerate (words): if word[0 ] != s[idx]: return False return True
題意
給定一個整數陣列 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
寫在最後
1 2 3 (masterpiece, best quality), 1girl, solo, cowboy_shot Musician, Musical attire, Recording studio, Composing music, Performing live, playing guitar, Collaborating with other musicians, gotou1, gotou1, gotou hitori, solo, skirt, pink jacket, track jacket, bangs, hair between eyes, long sleeves,<lora:gotou_hitori_v1:0.800000> Negative prompt: (worst quality, low quality:1.4), negative_hand-neg, EasynegativeV2, bad-artist-anime, verybadimagenegative_v1.3, bad_prompt_version2 Steps: 30, Sampler: DPM++ 2M Karras, CFG scale: 8.0, Seed: 416831509, Size: 960x540, Model: CamelliaMix_V3: 1757182f367d", TI hashes: "negative_hand-neg, EasyNegativeV2, bad-artist-anime, verybadimagenegative_v1.3, bad_prompt_version2", Version: v1.6.0.109-2-gd1c0272