LC 315Count of Smaller Numbers After Self
Hard
Two viable expert approaches: a Binary Indexed Tree over rank-compressed values scanned right-to-left, or a modified merge sort that counts cross-inversions during the merge step.
def countSmaller(nums):
n = len(nums)
sorted_vals = sorted(set(nums))
rank = {v: i + 1 for i, v in enumerate(sorted_vals)}
tree = [0] * (len(sorted_vals) + 1)
def update(i):
while i < len(tree):
tree[i] += 1
i += i & (-i)
def query(i):
s = 0
while i > 0:
s += tree[i]
i -= i & (-i)
return s
res = [0] * n
for i in range(n - 1, -1, -1):
r = rank[nums[i]]
res[i] = query(r - 1)
update(r)
return res
LC 4Median of Two Sorted Arrays
Hard
Binary search on the SMALLER array to find a partition point such that every element on the left half is <= every element on the right half across both arrays.
def findMedianSortedArrays(A, B):
if len(A) > len(B):
A, B = B, A
m, n = len(A), len(B)
lo, hi = 0, m
half = (m + n + 1) // 2
while lo <= hi:
i = (lo + hi) // 2
j = half - i
A_left = A[i - 1] if i > 0 else float('-inf')
A_right = A[i] if i < m else float('inf')
B_left = B[j - 1] if j > 0 else float('-inf')
B_right = B[j] if j < n else float('inf')
if A_left <= B_right and B_left <= A_right:
if (m + n) % 2:
return max(A_left, B_left)
return (max(A_left, B_left) + min(A_right, B_right)) / 2
elif A_left > B_right:
hi = i - 1
else:
lo = i + 1
LC 947Most Stones Removed (Union-Find)
Medium
Union stones that share a row or a column. Every connected component of size c lets you remove c-1 stones (keep exactly one per component).
def removeStones(stones):
parent = {}
def find(x):
parent.setdefault(x, x)
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
def union(a, b):
ra, rb = find(a), find(b)
if ra != rb:
parent[ra] = rb
for r, c in stones:
union(('r', r), ('c', c))
roots = {find(('r', r)) for r, c in stones} | {find(('c', c)) for r, c in stones}
return len(stones) - len(roots)
LC 218The Skyline Problem
Hard
Sweep through critical x-coordinates using a max-heap of active building heights (lazy deletion); a skyline key point is emitted whenever the max height changes.
import heapq
def getSkyline(buildings):
events = []
for l, r, h in buildings:
events.append((l, -h, r))
events.append((r, 0, 0))
events.sort()
res = []
live = [(0, float('inf'))]
for x, neg_h, r in events:
while live[0][1] <= x:
heapq.heappop(live)
if neg_h:
heapq.heappush(live, (neg_h, r))
cur_height = -live[0][0]
if not res or res[-1][1] != cur_height:
res.append([x, cur_height])
return res
LC 632Smallest Range Covering Elements from K Lists
Hard
K-way merge with a min-heap: always advance the pointer of the list holding the current minimum, tracking the running max to compute each candidate range.
import heapq
def smallestRange(nums):
heap = [(lst[0], i, 0) for i, lst in enumerate(nums)]
heapq.heapify(heap)
cur_max = max(lst[0] for lst in nums)
best = [float('-inf'), float('inf')]
while True:
val, i, j = heapq.heappop(heap)
if cur_max - val < best[1] - best[0]:
best = [val, cur_max]
if j + 1 == len(nums[i]):
return best
nxt = nums[i][j + 1]
cur_max = max(cur_max, nxt)
heapq.heappush(heap, (nxt, i, j + 1))
LC 493Reverse Pairs (Merge Sort + Count)
Hard
Augment merge sort: before merging two sorted halves, count cross pairs where left[i] > 2 * right[j] using a two-pointer sweep, since both halves are already sorted.
def reversePairs(nums):
def sort_count(arr):
if len(arr) <= 1:
return arr, 0
mid = len(arr) // 2
left, cl = sort_count(arr[:mid])
right, cr = sort_count(arr[mid:])
count = cl + cr
j = 0
for i in range(len(left)):
while j < len(right) and left[i] > 2 * right[j]:
j += 1
count += j
merged, i, j = [], 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
merged.append(left[i]); i += 1
else:
merged.append(right[j]); j += 1
merged.extend(left[i:]); merged.extend(right[j:])
return merged, count
return sort_count(nums)[1]
LC 327Count of Range Sum
Hard
Build prefix sums, then apply the same merge-sort-with-cross-counting trick: count pairs (i, j) where prefix[j] - prefix[i] falls inside [lower, upper] during each merge.
def countRangeSum(nums, lower, upper):
prefix = [0]
for n in nums:
prefix.append(prefix[-1] + n)
def sort_count(arr):
if len(arr) <= 1:
return arr, 0
mid = len(arr) // 2
left, cl = sort_count(arr[:mid])
right, cr = sort_count(arr[mid:])
count = cl + cr
lo = hi = 0
for x in left:
while lo < len(right) and right[lo] - x < lower:
lo += 1
while hi < len(right) and right[hi] - x <= upper:
hi += 1
count += hi - lo
merged, i, j = [], 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
merged.append(left[i]); i += 1
else:
merged.append(right[j]); j += 1
merged.extend(left[i:]); merged.extend(right[j:])
return merged, count
return sort_count(prefix)[1]
LC 407Trapping Rain Water II
Hard
3D generalization solved with a min-heap starting from every border cell: always expand from the currently lowest boundary wall, tracking the max wall height seen along each path.
import heapq
def trapRainWater(heightMap):
rows, cols = len(heightMap), len(heightMap[0])
visited = [[False] * cols for _ in range(rows)]
heap = []
for r in range(rows):
for c in range(cols):
if r == 0 or r == rows - 1 or c == 0 or c == cols - 1:
heapq.heappush(heap, (heightMap[r][c], r, c))
visited[r][c] = True
water = 0
while heap:
h, r, c = heapq.heappop(heap)
for dr, dc in ((1,0),(-1,0),(0,1),(0,-1)):
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and not visited[nr][nc]:
visited[nr][nc] = True
water += max(0, h - heightMap[nr][nc])
heapq.heappush(heap, (max(h, heightMap[nr][nc]), nr, nc))
return water
LC 995Minimum K Consecutive Bit Flips
Hard
Use a difference array to track "currently flipped" state in O(1) per position; greedily flip a window whenever the current effective bit is 0, without ever mutating the future directly.
def minKBitFlips(nums, k):
n = len(nums)
diff = [0] * (n + 1)
flips = cur_flips = 0
for i in range(n):
cur_flips += diff[i]
effective = (nums[i] + cur_flips) % 2
if effective == 0:
if i + k > n:
return -1
flips += 1
cur_flips += 1
diff[i + k] -= 1
return flips
LC 1425Constrained Subsequence Sum
Hard
DP where dp[i] = nums[i] + max(0, best dp value within the last k indices). A monotonic deque maintains that windowed maximum in O(1) amortized per step.
from collections import deque
def constrainedSubsetSum(nums, k):
dq = deque()
dp = [0] * len(nums)
best = float('-inf')
for i, n in enumerate(nums):
if dq and dq[0] < i - k:
dq.popleft()
dp[i] = n + (dp[dq[0]] if dq and dp[dq[0]] > 0 else 0)
best = max(best, dp[i])
while dq and dp[dq[-1]] <= dp[i]:
dq.pop()
dq.append(i)
return best