陣列 — 題目實作
範圍 — array.md 背後的題解倉庫:十三題真正在考「原地改寫陣列」或「拿索引當儲存空間」的題目,依各自用到的技巧分組。 另見:array.md — 母表:基本操作、特殊演算法,以及告訴你一題到底該歸哪張表的選擇表;2_pointers.md、sliding_window.md、prefix_sum.md、difference_array.md — 吃掉大部分 array 標籤題目的四個模式家族;sort.md、matrix.md、kadane_algorithm.md、stock_trading.md — 選擇表會把題目導向的其他主表。
LeetCode 題目清單
總覽
這裡是 array.md 的長尾。母表放的是操作、特殊演算法和選擇表;這個檔案放的是應用它們的題目。
關鍵性質
- 複雜度:逐題標註;原地那一組是 O(n) 時間、O(1) 額外空間,而這通常就是題目的重點
- 核心想法:陣列不只是輸入,它同時也是草稿紙 — 用正負號、用位置,或用一個由後往前寫的指標
- 什麼時候用:當選擇表已經判定這題其實不是視窗、指標或前綴和的問題之後
關於重複收錄
其中五題也出現在擁有該技巧的表裡 — LC 121 在 stock_trading.md 和 kadane_algorithm.md、 LC 1109 在 difference_array.md、LC 1567 在 kadane_algorithm.md、LC 251 在 design.md、LC 406 在 sort.md。現階段這些重複是刻意留著的:把它們合併是跨檔整併那一輪的工作,不是單張表自己該處理的事。
原地改寫與索引技巧
1) First Missing Positive — LC 41 Priority 5 of 5 — Must know — expect it in almost every loop
兩種把陣列本身當雜湊表的做法,都是 O(n) 時間、O(1) 額外空間。 之所以成對保留,是因為技巧不同:第一種靠翻正負號來標記一個位置, 第二種則是把每個值搬到它該待的位置。
做法 A — 用正負號標記(先把超出範圍的值夾掉,再把 nums[v-1] 變號):
# LC 41. First Missing Positive
# V1'
# IDEA : Index as a hash key.
# https://leetcode.com/problems/first-missing-positive/solution/
# /doc/pic/first-missing-positive.png
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
n = len(nums)
# Base case.
if 1 not in nums:
return 1
# Replace negative numbers, zeros,
# and numbers larger than n by 1s.
# After this convertion nums will contain
# only positive numbers.
for i in range(n):
if nums[i] <= 0 or nums[i] > n:
nums[i] = 1
# Use index as a hash key and number sign as a presence detector.
# For example, if nums[1] is negative that means that number `1`
# is present in the array.
# If nums[2] is positive - number 2 is missing.
for i in range(n):
a = abs(nums[i])
# If you meet number a in the array - change the sign of a-th element.
# Be careful with duplicates : do it only once.
if a == n:
nums[0] = - abs(nums[0])
else:
nums[a] = - abs(nums[a])
# Now the index of the first positive number
# is equal to first missing positive.
for i in range(1, n):
if nums[i] > 0:
return i
if nums[0] > 0:
return n
return n + 1
// java
// LC 41. First Missing Positive
// V0
// IDEA: CYCLIC SORT
/**
* Cyclic Sort Pattern:
* Place each positive number x at its "correct" index (x - 1)
*
* Key idea:
* - For a valid positive integer x (1 <= x <= n), it should be at index x-1
* - Example: number 3 should be at nums[2], number 1 should be at nums[0]
*
* Algorithm:
* 1. For each position i, keep swapping nums[i] to its correct position
* until nums[i] is already at the right place or out of range
* 2. After sorting, scan for the first index i where nums[i] != i + 1
* 3. That index + 1 is the first missing positive
*
* Example: nums = [3, 4, -1, 1]
* Step 1: Place 3 at index 2 → [-1, 4, 3, 1]
* Step 2: Place 4 at index 3 → [-1, 1, 3, 4]
* Step 3: Place 1 at index 0 → [1, -1, 3, 4]
* Step 4: Scan → nums[1] = -1 ≠ 2, return 2
*
* Time: O(N) - each element is swapped at most once
* Space: O(1) - in-place sorting
*/
public int firstMissingPositive(int[] nums) {
int n = nums.length;
// 1. "Cyclic Sort": Place each number x at index x - 1
// Example: nums[i] = 3 should be at nums[2]
for (int i = 0; i < n; i++) {
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
// Swap nums[i] with the element at its target index
int temp = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i];
nums[i] = temp;
}
}
// 2. Scan for the first index where the number is wrong
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) {
return i + 1; // Found the missing positive!
}
}
// 3. If all numbers 1 to n are present, the answer is n + 1
return n + 1;
}
做法 B — 循環排序(把每個值送回家,再掃出第一個缺口):
# LC 41. First Missing Positive
# V0
# IDEA : for loop + while loop + problem understanding
class Solution:
def firstMissingPositive(self, nums):
for i, n in enumerate(nums):
if n < 0:
continue
else:
while n <= len(nums) and n > 0:
tmp = nums[n-1]
nums[n-1] = float('inf')
n = tmp
for i in range(len(nums)):
if nums[i] != float('inf'):
return i+1
return len(nums)+1
變形 — LC 287 Find the Duplicate Number(用 SIGN 標記): 和 LC 41 一樣是「索引當雜湊 key」的想法,只是這次不交換值,而是把 nums[v] 變號來記錄「值 v 出現過」。第一次踩到已經是負數的位置,那個索引就是重複的數字。值落在 1..n、陣列長度是 n+1,所以 abs(v) 永遠是合法索引。
# python
# LC 287 - Find the Duplicate Number
# IDEA: index-as-hash + MARK BY SIGN (negate nums[v] to mean "v was seen")
class Solution(object):
def findDuplicate(self, nums):
# time = O(n), space = O(1) (mutates nums, then restores it)
res = -1
for x in nums:
### NOTE : always read through abs(), slots may already be negated
i = abs(x)
if nums[i] < 0: # slot i already marked -> i is the duplicate
res = i
break
nums[i] = -nums[i] # mark "value i seen"
# restore the array (needed if the caller must not see mutations)
for i in range(len(nums)):
nums[i] = abs(nums[i])
return res
// java
// LC 287 - Find the Duplicate Number
// IDEA: index-as-hash + MARK BY SIGN (negate nums[v] to mean "v was seen")
public int findDuplicate(int[] nums) {
// time = O(n), space = O(1) (mutates nums, then restores it)
int res = -1;
for (int x : nums) {
int i = Math.abs(x); // values are 1..n, array length n+1 -> always in range
if (nums[i] < 0) { // already marked -> duplicate found
res = i;
break;
}
nums[i] = -nums[i];
}
for (int i = 0; i < nums.length; i++) {
nums[i] = Math.abs(nums[i]); // restore
}
return res;
}
注意: LC 287 嚴格版的 follow-up 禁止修改陣列 — 那個版本要用 Floyd 環偵測來解(把
i -> nums[i]看成一條鏈結串列),見 2_pointers.md。上面這個變號版是「允許改動陣列」時該拿出來用的。變號標記檢查清單: ① 值必須能對應到合法索引,② 取值時一律用
abs(...),③ 如果陣列還要再用,記得把正負號還原(LC 442/448 用的是一模一樣的技巧)。
2) Rotate Array — LC 189 Priority 4 of 5 — High value — a gap here costs you rounds
# LC 189. Rotate Array
# V0
# IDEA : pop + insert
class Solution(object):
def rotate(self, nums, k):
_len = len(nums)
k = k % _len
while k > 0:
tmp = nums.pop(-1)
nums.insert(0, tmp)
k -= 1
# V0'
# IDEA : SLICE (in place)
class Solution(object):
def rotate(self, nums, k):
# edge case
if k == 0 or not nums or len(nums) == 1:
return nums
### NOTE this
k = k % len(nums)
if k == 0:
return nums
"""
NOTE this !!!!
"""
nums[:k], nums[k:] = nums[-k:], nums[:-k]
return nums
// java
// LC 189. Rotate Array
// V0
// IDEA: REVERSE ARRAY (3-reverse trick)
/**
* The 3-reverse trick:
* 1. Reverse the entire array
* 2. Reverse the first k elements
* 3. Reverse the remaining elements
*
* Example: nums = [1,2,3,4,5,6,7], k = 3
* Step 1: Reverse entire array → [7,6,5,4,3,2,1]
* Step 2: Reverse first k=3 elements → [5,6,7,4,3,2,1]
* Step 3: Reverse remaining elements → [5,6,7,1,2,3,4]
*
* Time: O(N) - each element is reversed twice
* Space: O(1) - in-place rotation
*/
public void rotate(int[] nums, int k) {
if (nums == null || nums.length <= 1)
return;
int n = nums.length;
// Step 1: Handle cases where k > n
k = k % n;
if (k == 0)
return;
// Step 2: Apply the 3-reverse trick
// 1. Reverse the whole array
reverse(nums, 0, n - 1);
// 2. Reverse the first k elements (0 to k-1)
reverse(nums, 0, k - 1);
// 3. Reverse the rest (k to n-1)
reverse(nums, k, n - 1);
}
private void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
3) Product of Array Except Self — LC 238 Priority 5 of 5 — Must know — expect it in almost every loop
# 238 Product of Array Except Self
# IDEA :
# SINCE output[i] = (x0 * x1 * ... * xi-1) * (xi+1 * .... * xn-1)
# -> SO DO A 2 LOOP
# -> 1ST LOOP : GO THROGH THE ARRAY (->) : (x0 * x1 * ... * xi-1)
# -> 2ND LOOP : GO THROGH THE ARRAY (<-) : (xi+1 * .... * xn-1)
# e.g.
# given [1,2,3,4], return [24,12,8,6].
# -> output = [2*3*4, 1,1,1] <-- 2*3*4 (right of 1: 2,3,4)
# -> output = [2*3*4, 1*3*4,1,1] <-- 1*3*4 (left of 2 :1, right of 2: 3,4)
# -> output = [2*3*4, 1*3*4,1*2*4,1] <-- 1*2*4 (left of 3: 1,2 right of 3 : 4)
# -> output = [2*3*4, 1*3*4,1*2*4,1*2*3] <-- 1*2*3 (left of 4 : 1,2,3)
# -> final output = [2*3*4, 1*3*4,1*2*4,1*2*3] = [24,12,8,6]
class Solution:
def productExceptSelf(self, nums):
size = len(nums)
output = [1] * size
left = 1
for x in range(size - 1):
left *= nums[x]
output[x + 1] *= left
right = 1
for x in range(size - 1, 0, -1):
right *= nums[x]
output[x - 1] *= right
return output
4) Maximum Swap — LC 670
# 670 Maximum Swap
class Solution(object):
def maximumSwap(self, num):
"""
:type num: int
:rtype: int
"""
# BE AWARE OF IT
digits = list(str(num))
left, right = 0, 0
max_idx = len(digits)-1
for i in range(len(digits))[::-1]:
# BE AWARE OF IT
if digits[i] > digits[max_idx]:
max_idx = i
# BE AWARE OF IT
# if current digit > current max digit -> swap them
elif digits[max_idx] > digits[i]:
left, right = i, max_idx # if current max digit > current digit -> save current max digit to right idnex, and save current index to left
digits[left], digits[right] = digits[right], digits[left] # swap left and right when loop finished
return int("".join(digits))
對照組 — O(n²) 的暴力解。值得看一次,因為這裡正好是
A[:]這個淺拷貝 慣用法發揮作用的地方:A每一輪都會被改動再還原,所以目前最佳解必須存成一份 拷貝,不能是參考。
# LC 670
# V0'
# IDEA : BRUTE FORCE
# NOTE : there is also 2 pointers solution :
# -> https://github.com/yennanliu/CS_basics/blob/master/leetcode_python/Array/maximum-swap.py#L49
# NOTE : ans = A[:]
# A[:] is a `shallow copy` syntax in python,
# it will copy "parent obj" (not child obj) to the other instance
# so the changes ("parent obj" only) in original instance will NOT affect the copied instance
# https://stackoverflow.com/questions/4081561/what-is-the-difference-between-list-and-list-in-python
# https://github.com/yennanliu/til#20210923
class Solution(object):
def maximumSwap(self, num):
A = list(str(num))
ans = A[:]
for i in range(len(A)):
for j in range(i+1, len(A)):
A[i], A[j] = A[j], A[i]
if A > ans:
ans = A[:]
A[i], A[j] = A[j], A[i]
return int("".join(ans))
掃描與滾動狀態
5) Best Time to Buy and Sell Stock — LC 121 Priority 4 of 5 — High value — a gap here costs you rounds
# LC 121 Best Time to Buy and Sell Stock
# V0
# IDEA : array op + problem understanding
class Solution(object):
def maxProfit(self, prices):
if len(prices) == 0:
return 0
### NOTE : we define 1st minPrice as prices[0]
minPrice = prices[0]
maxProfit = 0
### NOTE : we only loop prices ONCE
for p in prices:
# only if p < minPrice, we get minPrice
if p < minPrice:
minPrice = p
### NOTE : only if p - minPrice > maxProfit, we get maxProfit
elif p - minPrice > maxProfit:
maxProfit = p - minPrice
return maxProfit
變形 — LC 122 Best Time to Buy and Sell Stock II(交易次數不限): 轉折在於買賣次數不限之後,你根本不用再追蹤 minPrice — 只要把每天之間的正價差全部加起來(每一段上漲都能獨立賺到)。
# python
# LC 122 - Best Time to Buy and Sell Stock II
# IDEA: unlimited transactions -> greedily collect EVERY upward move
class Solution(object):
def maxProfit(self, prices):
# time = O(n), space = O(1)
profit = 0
for i in range(1, len(prices)):
# buy at i-1, sell at i, whenever it goes up
if prices[i] > prices[i - 1]:
profit += prices[i] - prices[i - 1]
return profit
// java
// LC 122 - Best Time to Buy and Sell Stock II
// IDEA: unlimited transactions -> greedily collect EVERY upward move
public int maxProfit(int[] prices) {
// time = O(n), space = O(1)
int profit = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] > prices[i - 1]) {
profit += prices[i] - prices[i - 1];
}
}
return profit;
}
貪婪為什麼是對的: 任何有利可圖的區間
[i, j]都能拆成每日價差的總和(p[j] - p[i] = Σ (p[k+1] - p[k])),而把負的價差丟掉只會讓總和變大。所以「正價差總和」既是上界,也真的做得到。對照: LC 121 = 1 次交易 → 追蹤滾動最小值。LC 122 = ∞ 次交易 → 加總正價差。
6) Maximum Length of Subarray With Positive Product — LC 1567
# LC 1567 Maximum Length of Subarray With Positive Product
# V0
class Solution:
def getMaxLen(self, nums):
first_neg, zero = None, -1
mx = neg = 0
for i,v in enumerate(nums):
if v == 0:
first_neg, zero, neg = None, i, 0
continue
if v < 0:
neg += 1
if first_neg == None:
first_neg = i
j = zero if not neg % 2 else first_neg if first_neg != None else 10**9
mx = max(mx, i-j)
return mx
# V0'
# IDEA : 2 POINTERS
class Solution:
def getMaxLen(self, nums):
res = 0
k = -1 # most recent 0
j = -1 # first negative after most recent 0
cnt = 0 # count of negatives after most recent 0
for i, n in enumerate(nums):
if n == 0:
k = i
j = i
cnt = 0
elif n < 0:
cnt += 1
if cnt % 2 == 0:
res = max(res, i - k)
else:
if cnt == 1:
j = i
else:
res = max(res, i - j)
else:
if cnt % 2 == 0:
res = max(res, i - k)
else:
res = max(res, i - j)
return res
7) Increasing Triplet Subsequence — LC 334
# LC 334 Increasing Triplet Subsequence
# V0
# IDEA : MAINTAIN var first, second
# AND GO THROUGH nums to check if there exists x (on the right hand side of a, b )
# such that x > second > first
class Solution(object):
def increasingTriplet(self, nums):
"""
NOTE !!! we init first, second as POSITIVE float('inf')
"""
first = float('inf')
second = float('inf')
# loop with normal ordering
for num in nums:
if num <= first: # min num
first = num
elif num <= second: # 2nd min num
second = num
else: # 3rd min num
return True
return False
8) Maximize Distance to Closest Person — LC 849
// java
// LC 849. Maximize Distance to Closest Person
// V0-1
// IDEA (fixed by gpt)
/**
* IDEA :
*
* Explanation of the Code:
* 1. Initial Setup:
* • lastOccupied keeps track of the index of the last seat occupied by a person.
* • maxDistance is initialized to 0 to store the maximum distance found.
*
* 2. Iterate Through the Array:
* • When a seat is occupied (seats[i] == 1):
* • If it’s the first occupied seat, calculate the distance from the start of the array to this seat (i).
* • Otherwise, calculate the middle distance between the current and the last occupied seat using (i - lastOccupied) / 2.
*
* 3. Check the Last Segment:
* • If the last seat is empty, calculate the distance from the last occupied seat to the end of the array (seats.length - 1 - lastOccupied).
*
* 4. Return the Maximum Distance:
* • The value of maxDistance at the end of the loop is the answer.
*
*
* Example :
* input : seats = [1, 0, 0, 0, 1, 0, 1]
*
* Execution Steps:
* 1. First occupied seat at index 0 → Distance to start = 0.
* 2. Second occupied seat at index 4 → Middle distance = (4 - 0) / 2 = 2.
* 3. Third occupied seat at index 6 → Middle distance = (6 - 4) / 2 = 1.
* 4. No empty seats after the last occupied seat.
* 5. maxDistance = 2.
*
* output: 2
*
*/
/**
* Cases
*
* Case 1) 0001 ( all "0" till meat first "1")
* Case 2) 1001001 (all "0" are enclosed by "1")
* Case 3) 1001000 (there are "0" that NOT enclosed by "1" on the right hand side)
*
*/
public int maxDistToClosest_0_1(int[] seats) {
int maxDistance = 0;
int lastOccupied = -1;
// Traverse the array to calculate maximum distances
for (int i = 0; i < seats.length; i++) {
/** NOTE !!! handle the seat val == 1 cases */
if (seats[i] == 1) {
if (lastOccupied == -1) {
// Handle the case where the `first` occupied seat is found
/**
* NOTE !!!
*
* for handling below case:
*
* e.g. : 0001
*
* (so, elements are all "0" till first visit "1")
* in this case, we still can get put a person to seat, and get distance
*
*/
maxDistance = i; // Distance from the start to the first occupied seat
} else {
// Calculate the distance to the closest person for the middle segment
/** NOTE !!! need to divided by 2, since the person need to seat at `middle` seat */
maxDistance = Math.max(maxDistance, (i - lastOccupied) / 2);
}
lastOccupied = i;
}
}
// Handle the case where the last segment is empty
/**
* NOTE !!!
*
* the condition is actually quite straightforward,
* just need to check if the last element in array is "0"
* if is "0", means the array is NOT enclosed by "1"
* then we need to handle such case
* (example as below)
*
* e.g. 100010000
*
*/
if (seats[seats.length - 1] == 0) {
maxDistance = Math.max(maxDistance, seats.length - 1 - lastOccupied);
}
return maxDistance;
}
計數、訂位與模擬
9) Corporate Flight Bookings — LC 1109
# LC 1109. Corporate Flight Bookings
# V1
# IDEA : ARRAY + prefix sum
# https://leetcode.com/problems/corporate-flight-bookings/discuss/328856/JavaC%2B%2BPython-Sweep-Line
# IDEA :
# Set the change of seats for each day.
# If booking = [i, j, k],
# it needs k more seat on ith day,
# and we don't need these seats on j+1th day.
# We accumulate these changes then we have the result that we want.
# Complexity
# Time O(booking + N) for one pass on bookings
# Space O(N) for the result
class Solution:
def corpFlightBookings(self, bookings, n):
res = [0] * (n + 1)
for i, j, k in bookings:
res[i - 1] += k
res[j] -= k
for i in range(1, n):
res[i] += res[i - 1]
return res[:-1]
# V1''
# IDEA : ARRAY
# https://leetcode.com/problems/corporate-flight-bookings/discuss/328893/Short-python-solution
# IDEA : Simply use two arrays to keep track of how many bookings are added for every flight.
class Solution:
def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]:
opens = [0]*n
closes = [0]*n
for e in bookings:
opens[e[0]-1] += e[2]
closes[e[1]-1] += e[2]
ret, tmp = [0]*n, 0
for i in range(n):
tmp += opens[i]
ret[i] = tmp
tmp -= closes[i]
return ret
10) Bulb Switcher III — LC 1375
# LC 1375. Bulb Switcher III
# V0
class Solution:
def numTimesAllBlue(self, light):
max_bulb_ind = 0
count = 0
turnedon_bulb = 0
for bulb in light:
max_bulb_ind = max(max_bulb_ind,bulb)
turnedon_bulb += 1
if turnedon_bulb == max_bulb_ind:
count += 1
return count
11) Robot Bounded In Circle — LC 1041
# LC 1041. Robot Bounded In Circle
# V0
# IDEA : math + array
class Solution:
def isRobotBounded(self, instructions):
"""
NOTE !!! we make direction as below
c == 'L': move LEFT : [0,-1]
c == 'R': move RIGHT : [0,1]
"""
dirs = [[0,1], [1,0], [0,-1], [-1,0]]
x = 0;
y = 0;
idx = 0;
for c in instructions:
print ("c = " + str(c) + " idx = " + str(idx))
"""
NOTE !!! since we need to verify if robot back to start point
-> we use (idx + k) % 4 for detecting cyclic cases
"""
if c == 'L':
idx = (idx + 3) % 4
elif c == 'R':
idx = (idx + 1) % 4
elif c == 'G':
x = x + dirs[idx][0]
y = y + dirs[idx][1]
return (x == 0 and y ==0) or idx !=0
12) Queue Reconstruction by Height — LC 406
# LC 406 Queue Reconstruction by Height
class Solution(object):
def reconstructQueue(self, people):
people.sort(key = lambda x : (-x[0], x[1]))
res = []
# py insert syntax:
# python_trick_indexing.html#insert-into-a-list-in-place-
# arr.insert(<index>, <value>)
for p in people:
res.insert(p[1], p)
return res
13) Flatten 2D Vector — LC 251
# LC 251. Flatten 2D Vector
# V0
# IDEA : ARRAY OP
class Vector2D:
def __init__(self, v):
# We need to iterate over the 2D vector, getting all the integers
# out of it and putting them into the nums list.
self.nums = []
for inner_list in v:
for num in inner_list:
self.nums.append(num)
# We'll keep position 1 behind the next number to return.
self.position = -1
def next(self):
# Move up to the current element and return it.
self.position += 1
return self.nums[self.position]
def hasNext(self):
# If the next position is a valid index of nums, return True.
return self.position + 1 < len(self.nums)