這兩次週賽都已經做到10分鐘內寫完Q1、Q2了,Q3一開始用前綴和 + 雜湊表統計吃了TLE 。先去看Q4發現就是區間合併的問題,但忘了用qpowqpow,吃了 RE 。回去看Q3發現是滑動窗口,喜提第一次 AK 4題!

All problems solved by python


Q1: 🟢 2960. Count Tested Devices After Test Operations 1169

tags: 陣列(Array) 模擬(Simulation) 差分陣列(Difference Array) 前綴和(Prefix Sum)

題意

給定一個長度為 nn 的整數陣列 batteryPercentagesbatteryPercentages ,表示 nn 個裝置的電池百分比,你的任務是按照順序測試每個設備 ii,執行以下測試操作:

  • 如果 batteryPercentages[i]>0batteryPercentages[i] > 0
    • 增加答案數,表示已測試設備的計數。
    • 將下標在 [i+1,n1][i + 1, n - 1] 的所有設備的電池百分比減少 11,確保它們的電池百分比不會低於 00 ,即 batteryPercentages[j]=max(0,batteryPercentages[j]1)batteryPercentages[j] = max(0, batteryPercentages[j] - 1)
    • 移動到下一個裝置。
  • 否則,請移動到下一個設備而不執行任何測試。
    返回已測試設備的數量。

思路:直接模擬 / 差分陣列

方法一:直接模擬

  • 按照題目要求模擬即可
  • 時間複雜度 O(n2)O(n^2)

方法二:差分陣列

  • 對於每個設備,其需要減少的電量為前面已測試設備的數量,因此若前面已測試設備的數量大於當前電量,則當前設備不需要測試。
  • 時間複雜度 O(n)O(n)
1
2
3
4
5
6
7
8
9
10
11
class Solution:
def countTestedDevices(self, batteryPercentages: List[int]) -> int:
n = len(batteryPercentages)
ans = 0
for i, b in enumerate(batteryPercentages):
if b > 0:
batteryPercentages[i] += 1
for j in range(i+1, n):
batteryPercentages[j] = max(0, batteryPercentages[j]-1)
ans += 1
return ans
1
2
3
4
5
6
7
class Solution:
def countTestedDevices(self, batteryPercentages: List[int]) -> int:
ans = 0
for i, x in enumerate(batteryPercentages):
if x - ans > 0:
ans += 1
return ans

Q2: 🟡 2961. Double Modular Exponentiation 1451

tags: 模擬(Simulation) 快速冪(Fast Power) 數學(Math) 陣列(Array)

題意

給定一個二維陣列 variablesvariables ,其中 variables[i]=[ai,bi,ci,mi]variables[i] = [a_i, b_i, c_i, m_i],以及一個整數 targettarget。求滿足 ((aibimod10)cimodmi)=target((a_i^{b_i} \mod 10)^{c_i} \mod m_i) = targetii 的集合。

思路:暴力 + 快速冪

  • 對於每個 ii,計算 ((aibimod10)cimodmi)((a_i^{b_i} \mod 10)^{c_i} \mod m_i) 即可。
  • 對於 abmodma^b \mod m,可以使用快速冪計算,時間複雜度 O(logb)O(\log b)
    • Python 中,pow(a,b,m)pow(a, b, m) 可以直接計算 abmodma^b \mod m,時間複雜度 O(logb)O(\log b)
  • 時間複雜度 O(nlogU)O(n \log U),其中 nnvariablesvariables 的長度, UUbi,cib_i, c_i 的最大值。
1
2
3
4
5
6
7
class Solution:
def getGoodIndices(self, variables: List[List[int]], target: int) -> List[int]:
ans = []
for i, (a, b, c, m) in enumerate(variables):
if pow(pow(a, b, 10), c, m) == target:
ans.append(i)
return ans

類題


Q3: 🟡 2962. Count Subarrays Where Max Element Appears at Least K Times 1701

題意

給定一個整數陣列 numsnums 和一個正整數 kk。統計有多少個子陣列滿足「 numsnums 中的最大元素至少出現 kk 次」,並返回滿足這一條件的子陣列的數目。

思路:滑動窗口 / 前綴和 + 二分搜索

方法一:滑動窗口

  • 讓窗口保持在向左擴張1次就能滿足條件的狀態,即保證 [left1,right][left-1, right]mxmx 出現的次數恰好為 kk
  • 由於上述性質,對於每個 rightright ,都可以往左擴張 leftleft 次,使其滿足題目條件,因此答案增加 leftleft
  • nums=[1,3,2,3,2,3]nums=[1,3,2,3,2,3], k=2k=2 為例,mx=3mx=3
    • right=0right=0left=0left=0cnt=0cnt=0left=0left=0ans+=0=0ans+=0=0
    • right=1right=1left=0left=0cnt=1cnt=1left=0left=0ans+=0=0ans+=0=0
    • right=2right=2left=0left=0cnt=1cnt=1left=0left=0ans+=0=0ans+=0=0
    • right=3right=3left=2left=2cnt=1cnt=1left=2left=2ans+=2=2ans+=2=2
    • right=4right=4left=2left=2cnt=1cnt=1left=2left=2ans+=2=4ans+=2=4
    • right=5right=5left=4left=4cnt=1cnt=1left=4left=4ans+=4=8ans+=4=8
  • 時間複雜度 O(n)O(n)

方法二:前綴和 + 二分搜索

  • 先用前綴和 prepre 統計 numsnums 中最大元素出現的次數,則 pre[i]pre[i] 表示 nums[:i]nums[:i]mxmx 出現的次數。
  • 對於每個左端點 ii,找到 pre[i]+kpre[i] + k 的位置 jj,則 nums[i:j]nums[i:j]mxmx 出現的次數恰好為 kk,subarray可以繼續向右,故延伸答案增加 n(j1)n - (j - 1)
  • 還是以 nums=[1,3,2,3,2,3]nums=[1,3,2,3,2,3], k=2k=2 為例,mx=3mx=3pre=[0,0,1,1,2,2,3]pre=[0, 0, 1, 1, 2, 2, 3]
    • i=0i=0j=4j=4nums[i:j]=[1,3,2,3]nums[i:j]=[1,3,2,3]ans+=3=3ans+=3=3
    • i=1i=1j=4j=4nums[i:j]=[3,2,3]nums[i:j]=[3,2,3]ans+=3=6ans+=3=6
    • i=2i=2j=6j=6nums[i:j]=[2,3,2,3]nums[i:j]=[2,3,2,3]ans+=1=7ans+=1=7
    • i=3i=3j=6j=6nums[i:j]=[3,2,3]nums[i:j]=[3,2,3]ans+=1=8ans+=1=8
    • i=4i=4j=7j=7nums[i:j]=[2,3]nums[i:j]=[2,3]ans+=0=8ans+=0=8。(此時已經找不到滿足條件的j了)
    • i=5i=5j=7j=7nums[i:j]=[3]nums[i:j]=[3]ans+=0=8ans+=0=8
  • 時間複雜度 O(nlogn)O(n \log n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution:
def countSubarrays(self, nums: List[int], k: int) -> int:
n = len(nums)
mx = max(nums)

ans = 0
cnt = 0 # count of mx
left = 0
for right in range(n):
if nums[right] == mx:
cnt += 1
while cnt == k: # 讓窗口保持在 left -= 1 就能滿足 cnt == k 的狀態
if nums[left] == mx:
cnt -= 1
left += 1
ans += left
return ans
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def countSubarrays(self, nums: List[int], k: int) -> int:
n = len(nums)
mx = max(nums)

pre = [0]
for num in nums:
pre.append(pre[-1] + (num == mx))
ans = 0
for i in range(n): # 對於每個 i,找到 pre[i] + k 的位置
j = bisect_left(pre, pre[i] + k)
ans += n - (j - 1)
return ans

類題

不定長度的滑動窗口 (求最長或最大)

不定長度的滑動窗口 (求最短或最小)

不定長度的滑動窗口 (求subarray個數)

多指針滑動窗口

題單來自:@灵茶山艾府


Q4. 🔴 2963. Count the Number of Good Partitions 1985

tags: 區間合併(Interval Merge) 快速冪(Fast Power) 陣列(Array) 排序(Sorting) 雜湊表(Hash Table) 數學(Math) 組合數學(Combinatorics)

題意

  • 給定一個由 正整數 組成的陣列 numsnums,將其分割成一個或多個 連續 子陣列,如果不同子陣列中不包含相同數字,則認為是一種 好分割方案。求好分割方案的數目。
  • 由於答案可能很大,返回對 109+710^9 + 7 取餘 的結果。

思路:區間合併

  • 題目要求相同數字必在同一個子陣列中,因此可以將每個數字出現的位置視為一個區間,對於重疊的區間,需合併成一個區間,則問題變為「求合併後的區間數量」。
  • 若剩下區間數量為 mm ,則有 m1m-1 個位置可選擇連接或不連接,可能的分割方案數量為 2m12^{m-1} 種。
  • 對於每個數字,可以使用雜湊表統計其出現的最左和最右位置,再將區間按照最左位置排序,即可合併區間。
    • 若當前區間的左端點大於前一個區間的右端點,則不重疊,新增當前區間。
    • 否則當前區間與前一個區間重疊,更新前一個區間的右端點。
  • 對於 abmodma^b \mod m,可以使用快速冪計算,時間複雜度 O(logb)O(\log b)
    • Python 中,pow(a,b,m)pow(a, b, m) 可以直接計算 abmodma^b \mod m,時間複雜度 O(logb)O(\log b)
  • 時間複雜度 O(nlogn)O(n \log n),其中 nnnumsnums 的長度,排序的時間複雜度為 O(nlogn)O(n \log 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
class Solution:
def numberOfGoodPartitions(self, nums: List[int]) -> int:
MOD = 10 ** 9 + 7
# 區間
n = len(nums)
cnt = defaultdict(list) # 每個數字出現的最左和最右位置
for i, num in enumerate(nums):
cnt[num].append(i)

# 區間合併
intervals = []
for num, indices in cnt.items():
intervals.append((indices[0], indices[-1]))
intervals.sort(key=lambda x: x[0])

res = []
for x, y in intervals:
if not res or x > res[-1][1]: # not overlap
res.append([x, y])
else:
res[-1][1] = max(res[-1][1], y) # overlap, update interval
return pow(2, len(res) - 1, MOD)

類題