42 Coding Interview Pattern
Here is 42 coding interview patterns would help you think clearer about DSA.
Ref:
1. Pattern 1: Sliding Window
1.1. Fixed Sliding Window
Ref: https://leetcode.com/problems/maximum-average-subarray-i/description/
Code
class Solution:
def findMaxAverage(self, nums: List[int], k: int) -> float:
result = []
windowStart, windowSum, windowEnd = 0, 0, 0
maxWindowAvg = float('-inf')
for windowEnd in range(0, len(nums)):
windowSum += nums[windowEnd]
if windowEnd >= k - 1:
maxWindowAvg = max(maxWindowAvg, windowSum / k)
windowSum -= nums[windowStart]
windowStart += 1
return maxWindowAvg
1.2. Variant Sliding Window
Ref: https://leetcode.com/problems/minimum-size-subarray-sum/description/
Code
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
windowSum, windowStart = 0, 0
minSubArrayLength = float('inf')
for windowEnd in range(0, len(nums)):
windowSum += nums[windowEnd]
while windowSum >= target:
minSubArrayLength = min(minSubArrayLength, windowEnd - windowStart + 1)
windowSum -= nums[windowStart]
windowStart += 1
if minSubArrayLength == float('inf'):
return 0
return minSubArrayLength
1.3. Shift All Window
Ref: https://leetcode.com/problems/repeated-dna-sequences/description/
Code
class Solution:
def findRepeatedDnaSequences(self, s: str) -> List[str]:
# If string length is less than 10, no possible repeats
if len(s) < 10:
return []
result = set() # Store repeated sequences
window = s[0:10] # Initial window
seen = {window} # Track all seen sequences
# Slide the window from index 1 to end
for i in range(1, len(s) - 9):
# Slide window one position right
window = s[i:i+10]
# If we've seen this sequence before, it's a repeat
if window in seen:
result.add(window)
# If not seen, add to seen set
else:
seen.add(window)
return list(result)
1.4. Longest window without duplicate character
Ref: https://leetcode.com/problems/longest-substring-without-repeating-characters/description/
Code
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
# Initialize variables
windowStart = 0
maxLength = 0
charIndexMap = {} # Store the last index of each character
# Iterate through the string
for windowEnd in range(0, len(s)):
rightChar = s[windowEnd]
# If the character is already in the map and its index is within our window
if rightChar in charIndexMap and charIndexMap[rightChar] >= windowStart:
# Move windowStart to the right of the previous occurrence
windowStart = charIndexMap[rightChar] + 1
# Update the character's last seen index
charIndexMap[rightChar] = windowEnd
# Update maxLength if current window is larger
maxLength = max(maxLength, windowEnd - windowStart + 1)
return maxLength
1.5. Longest Substring with K Distinct Characters
Ref: https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/
Code
class Solution:
def longest_substring_with_k_distinct(self, str, k):
# Given a string, find the length of the longest substring in it with no more than K distinct characters.
windowStart = 0
maxLength = 0
charFrequency = {} # store the frequence count of character
# in the following loop we'll try to extend the range [windowStart, windowEnd]
for windowEnd in range(0, len(str)):
endChar = str[windowEnd]
charFrequency[endChar] = charFrequency.get(endChar, 0) + 1
# shrink the window until we are left with k distinct characters
# in the charFrequency dictionary
while len(charFrequency) > k:
startChar = str[windowStart]
charFrequency[startChar] -= 1
if charFrequency[startChar] == 0:
del charFrequency[startChar]
windowStart += 1
maxLength = max(maxLength, windowEnd - windowStart + 1)
return maxLength
# Test cases
print(longest_substring_with_k_distinct("araaci", 2)) # 4, The longest substring with no more than '2' distinct characters is "araa"
print(longest_substring_with_k_distinct("araaci", 1)) # 2, The longest substring with no more than '1' distinct characters is "aa"
print(longest_substring_with_k_distinct("cbbebi", 3)) # 5, The longest substrings with no more than '3' distinct characters are "cbbeb" & "bbebi"
1.6. Longest Repeating Character Replacement
Ref: https://leetcode.com/problems/longest-repeating-character-replacement/description/
Code
class Solution:
def characterReplacement(self, s: str, k: int) -> int:
windowStart = 0
maxLength = 0
maxRepeatLetterCount = 0
charFrequency = {}
# Try to extend the range [windowStart, windowEnd]
for windowEnd in range(0, len(s)):
endChar = s[windowEnd]
charFrequency[endChar] = charFrequency.get(endChar, 0) + 1
# *REVIEW THIS LINE*
maxRepeatLetterCount = max(maxRepeatLetterCount, charFrequency[endChar])
# current window size is from windowStart to windowEnd, overall we have a letter which is
# repeating maxRepeatLetterCount times, this means we can have a window which has one letter
# repeating maxRepeatLetterCount times and the remaining letters we should replace
# if the remaining letters are more than k, it is the time to shrink the window as we
# are not allowed to replace more than k letters
if (windowEnd - windowStart + 1 - maxRepeatLetterCount) > k:
startChar = s[windowStart]
charFrequency[startChar] -= 1
windowStart += 1
maxLength = max(maxLength, windowEnd - windowStart + 1)
return maxLength
The key differences between this problem and others where we need while:
Problems needing while:
- When multiple characters might need to be removed to make the window valid
- Example: “Find longest substring with at most K distinct characters”
- Because adding one character might require removing multiple characters
This problem (using if):
- Adding one character can only make the window invalid by at most one character
- Because maxRepeatLetterCount can only increase or stay the same
- Therefore, we only ever need to remove one character at most
Notes: You do not replace it in real, you only increase the windowStart and imply that we can replace all it.
1.6. Longest Repeating Character Replacement With Bit 1
Ref: https://leetcode.com/problems/max-consecutive-ones-iii/
Code
class Solution:
def longestOnes(self, nums: List[int], k: int) -> int:
windowStart = 0
maxLength = 0
maxRepeatOneCount = 0 # Track count of 1's instead of 0's
bitFrequency = {}
# Try to extend the range [windowStart, windowEnd]
for windowEnd in range(len(nums)):
endBit = nums[windowEnd]
bitFrequency[endBit] = bitFrequency.get(endBit, 0) + 1
# Track the count of 1's as our maxRepeat count
if endBit == 1:
maxRepeatOneCount = max(maxRepeatOneCount, bitFrequency[1])
# Current window size minus count of 1's gives us count of 0's
# If count of 0's exceeds k, shrink window
if (windowEnd - windowStart + 1 - maxRepeatOneCount) > k:
startBit = nums[windowStart]
bitFrequency[startBit] -= 1
windowStart += 1
maxLength = max(maxLength, windowEnd - windowStart + 1)
return maxLength
1.7. Longest Subarray of Bit 1 After Deleting One Element
Ref: https://leetcode.com/problems/longest-subarray-of-1s-after-deleting-one-element/
Code
class Solution:
def longestSubarray(self, nums: List[int]) -> int:
windowStart = 0
maxLength = 0
maxRepeatOneCount = 0 # Track count of 1's instead of 0's
bitFrequency = {}
# Try to extend the range [windowStart, windowEnd]
for windowEnd in range(0, len(nums)):
endBit = nums[windowEnd]
bitFrequency[endBit] = bitFrequency.get(endBit, 0) + 1
# Track the count of 1's as our maxRepeat count
if endBit == 1:
maxRepeatOneCount = max(maxRepeatOneCount, bitFrequency[1])
# Current window size minus count of 1's gives us count of 0's
# If count of 0's exceeds k, shrink window
if (windowEnd - windowStart + 1 - maxRepeatOneCount) > 1:
startBit = nums[windowStart]
bitFrequency[startBit] -= 1
windowStart += 1
maxLength = max(maxLength, windowEnd - windowStart + 1)
return maxLength - 1 if maxLength > 0 else maxLength
1.8. Sliding Window in Multiple String
Ref: https://leetcode.com/problems/permutation-in-string/
Code
from collections import Counter
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
# Edge cases
if len(s1) > len(s2):
return False
# Initialize frequency maps
s1Map = {}
windowMap = {}
# Build frequency map for s1
for char in s1:
s1Map[char] = s1Map.get(char, 0) + 1
# Initialize sliding window with first len(s1) characters
windowStart = 0
# Try to extend the range [windowStart, windowEnd]
for windowEnd in range(len(s2)):
# Add character to window frequency map
endChar = s2[windowEnd]
windowMap[endChar] = windowMap.get(endChar, 0) + 1
# If window size is larger than s1 length, shrink window
if windowEnd >= len(s1):
startChar = s2[windowStart]
windowMap[startChar] -= 1
# Remove character from map if count becomes 0
if windowMap[startChar] == 0:
del windowMap[startChar]
windowStart += 1
# Check if current window is a permutation
if windowMap == s1Map:
return True
return False
1.9. String Anagrams
Ref: https://leetcode.com/problems/find-all-anagrams-in-a-string/
Code
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
# Edge cases
if len(p) > len(s):
return []
# Initialize frequency maps
pMap = {}
windowMap = {}
# Build frequency map for s1
for char in p:
pMap[char] = pMap.get(char, 0) + 1
# Initialize sliding window with first len(s1) characters
windowStart = 0
# Result
res = []
# Try to extend the range [windowStart, windowEnd]
for windowEnd in range(0, len(s)):
# Add character to window frequency map
endChar = s[windowEnd]
windowMap[endChar] = windowMap.get(endChar, 0) + 1
# If window size is larger than p length, shrink window
if windowEnd >= len(p):
startChar = s[windowStart]
windowMap[startChar] -= 1
# Remove character from map if count becomes 0
if windowMap[startChar] == 0:
del windowMap[startChar]
windowStart += 1
# Check if current window is a permutation
if windowMap == pMap:
res.append(windowEnd - len(p) + 1)
return res
1.10. Min Window Substring
Ref: https://leetcode.com/problems/minimum-window-substring/
Code
class Solution:
def minWindow(self, s: str, t: str) -> str:
# Edge cases
if not s or not t or len(s) < len(t):
return ""
# Initialize frequency maps
targetMap = {} # frequency map for string t
windowMap = {} # frequency map for current window
# Build frequency map for target string
for char in t:
targetMap[char] = targetMap.get(char, 0) + 1
# Initialize variables
windowStart = 0
minLength = float('inf')
minStart = 0
matched = 0 # count of characters matched with required frequency
# Try to extend the range [windowStart, windowEnd]
for windowEnd in range(len(s)):
# Add current character to window frequency map
endChar = s[windowEnd]
windowMap[endChar] = windowMap.get(endChar, 0) + 1
# If current character matches target frequency, increment matched count
if endChar in targetMap and windowMap[endChar] == targetMap[endChar]:
matched += 1
# Try to shrink window when we have matched all characters
while matched == len(targetMap):
# Update minimum window size if current window is smaller
windowLength = windowEnd - windowStart + 1
if windowLength < minLength:
minLength = windowLength
minStart = windowStart
# Remove character from start of window
startChar = s[windowStart]
windowMap[startChar] -= 1
# If removed character was part of target and now frequency is less,
# decrement matched count
if startChar in targetMap and windowMap[startChar] < targetMap[startChar]:
matched -= 1
windowStart += 1
# Return minimum window substring or empty string if not found
return s[minStart:minStart + minLength] if minLength != float('inf') else ""
1.11. Subarray Product Less Than K
Ref: https://leetcode.com/problems/subarray-product-less-than-k/
Code
class Solution:
def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
if k <= 1:
return 0
left = 0
product = 1
count = 0
for right in range(0, len(nums)):
product *= nums[right]
# Move left pointer until product is less than k
while product >= k:
product //= nums[left]
left += 1
# Number of new subarrays ending at right
count += right - left + 1
return count
1.12. Maximum Number of Vowels in a Substring of Given Length
Ref: https://leetcode.com/problems/maximum-number-of-vowels-in-a-substring-of-given-length/
Code
class Solution:
def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
if k <= 1:
return 0
left = 0
product = 1
count = 0
for right in range(0, len(nums)):
product *= nums[right]
# Move left pointer until product is less than k
while product >= k:
product //= nums[left]
left += 1
# Number of new subarrays ending at right
count += right - left + 1
return count
1.13. Substring Sliding Window
Code
class Solution: def isSubstring(self, s: str, t: str) -> bool: if len(t) > len(s): return False left, right = 0, 0 while right < len(s): if right - left < len(t): right += 1 continue if s[left:right] == t: return True left += 1 right += 1 return False
1.14. Prefix and Postfix
Ref: https://leetcode.com/problems/product-of-array-except-self/
Code
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
prefix = [1] * len(nums)
postfix = [1] * len(nums)
# Create prefix and postfix list
for i in range(0, len(nums)):
prefix[i] = nums[i] if i == 0 else prefix[i - 1] * nums[i]
postfix[len(nums) - 1 - i] = nums[len(nums) - 1] if i == 0 else postfix[len(nums) - i] * nums[len(nums) - 1 - i]
# print(prefix)
# print(postfix)
# Compute result using prefix and postfix
result = [1] * len(nums)
for i in range(0, len(nums)):
left = prefix[i - 1] if i > 0 else 1
right = postfix[i + 1] if i < len(nums) - 1 else 1
result[i] = left * right
return result
1.15. Second Smallest
Ref: https://leetcode.com/problems/increasing-triplet-subsequence/
Code
class Solution:
def increasingTriplet(self, nums: List[int]) -> bool:
first = float('inf')
second = float('inf')
for num in nums:
if num <= first:
first = num # smallest so far
elif num <= second:
second = num # second smallest
else:
# found a number greater than both -> triplet exists
return True
return False
1.15. Group String (Count From Index 1)
Ref: https://leetcode.com/problems/string-compression/
Code
from typing import List
class Solution:
def compress(self, chars: List[str]) -> int:
res = []
count = 1 # Start with 1 since we always have at least one occurrence
for i in range(1, len(chars) + 1):
if i < len(chars) and chars[i] == chars[i - 1]:
count += 1
else:
# End of a group
res.append(chars[i - 1])
if count > 1:
for digit in str(count):
res.append(digit)
count = 1 # Reset count for the next character group
# Modify input list in-place
chars[:] = res
return len(res)
1.16. Prefix and Suffix
Ref: https://leetcode.com/problems/find-pivot-index/description/
Code
class Solution:
def pivotIndex(self, nums: List[int]) -> int:
prefix = [0] * len(nums)
for i in range(0, len(nums)):
prefix[i] = nums[0] if i == 0 else prefix[i - 1] + nums[i]
postfix = [0] * len(nums)
for i in range(len(nums) - 1, -1, -1):
postfix[i] = nums[len(nums) - 1] if i == len(nums) - 1 else postfix[i + 1] + nums[i]
for i in range(len(nums)):
if prefix[i] == postfix[i]:
return i
return -1
2. Pattern 2: Two Pointer
2.1. Two Sum
Ref: https://leetcode.com/problems/two-sum/
Code
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# O(N * logN)
# Pair each number with its index
nums_with_indices = list(enumerate(nums)) # [(index, value)]
# Sort based on the values
nums_with_indices.sort(key=lambda x: x[1])
# Two Pointer Technique
start, end = 0, len(nums_with_indices) - 1
while start < end:
curr_sum = nums_with_indices[start][1] + nums_with_indices[end][1]
if curr_sum == target:
return [nums_with_indices[start][0], nums_with_indices[end][0]]
elif curr_sum < target:
start += 1
else:
end -= 1
return [-1, -1]
Code
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# O(N)
numToIndex = {}
for pE in range(0, len(nums)):
currVal = nums[pE]
if target - currVal in numToIndex:
return [pE, numToIndex[target - currVal]]
numToIndex[currVal] = pE
return [-1, -1]
2.2. Remove duplicates
Ref: https://leetcode.com/problems/remove-duplicates-from-sorted-array/
Code
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
nextNonDup = 0
pE = 0
while pE < len(nums):
if pE == 0 or nums[pE] != nums[nextNonDup - 1]:
nums[nextNonDup] = nums[pE]
nextNonDup += 1
pE += 1
return nextNonDup
2.3. Remove Element
Ref: https://leetcode.com/problems/remove-element/
Code
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
nextNonTarget = 0
pE = 0
while pE < len(nums):
if nums[pE] != val:
nums[nextNonTarget] = nums[pE]
nextNonTarget += 1
pE += 1
return nextNonTarget
2.4. Squares of a Sorted Array
Ref: https://leetcode.com/problems/squares-of-a-sorted-array/description/
Code
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
start, end = 0, len(nums) - 1
highestSquare = len(nums) - 1
result = [0] * len(nums)
while start <= end:
startSquare = nums[start] ** 2
endSquare = nums[end] ** 2
if startSquare > endSquare:
result[highestSquare] = startSquare
start += 1
else:
result[highestSquare] = endSquare
end -= 1
# M lớn nhất rồi thì t fill thằng thứ 2
highestSquare -= 1
return result
2.5. Three sums
Ref: https://leetcode.com/problems/3sum/description/
Code
class Solution:
def twoSum(self, nums: List[int], start: int, end: int, target: int) -> List[int]:
# O(N)
numToIndex = {}
result = []
for pE in range(start, end + 1):
currVal = nums[pE]
if target - currVal in numToIndex:
result.append([currVal, target - currVal])
numToIndex[currVal] = pE
return result
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort() # Sort the array to handle duplicates
result = set()
for pE in range(0, len(nums)):
# because the previous solution is contain this solution too
if pE > 0 and nums[pE] == nums[pE - 1]:
continue
firstVal = nums[pE]
resultTwoSum = self.twoSum(nums, pE + 1, len(nums) - 1, 0 - firstVal)
for [secondVal, thirdVal] in resultTwoSum:
result.add((firstVal, secondVal, thirdVal))
return [list(triplet) for triplet in result]
2.6. Three sums closest
Ref: https://leetcode.com/problems/3sum-closest/
Code
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
closestSum = float('inf')
for pE in range(0, len(nums) - 2):
left = pE + 1
right = len(nums) - 1
while (left < right):
currSum = nums[pE] + nums[left] + nums[right]
if abs(currSum - target) < abs(closestSum - target):
closestSum = currSum
# Check with currSum
if currSum < target:
left += 1
elif currSum > target:
right -= 1
else:
return currSum
return closestSum
2.7. Three sums smaller
Ref: https://leetcode.com/problems/3sum-smaller/
Code
class Solution:
def tripletsWithSmallerSum(self, nums: List[int], target: int) -> int:
nums.sort() # Sort the array to use the two-pointer technique
count = 0
for i in range(len(nums) - 2):
left, right = i + 1, len(nums) - 1
while left < right:
current_sum = nums[i] + nums[left] + nums[right]
if current_sum < target:
# If nums[i] + nums[left] + nums[right] is less than target,
# then all elements from left to right form valid triplets
count += right - left
left += 1
else:
right -= 1
return count
2.8. Dutch National Problem (Sort In Place)
Ref: https://leetcode.com/problems/sort-colors/
Code
class Solution:
def twoSum(self, nums: List[int], start: int, end: int, target: int) -> List[int]:
# O(N)
numToIndex = {}
result = []
for pE in range(start, end + 1):
currVal = nums[pE]
if target - currVal in numToIndex:
result.append([currVal, target - currVal])
numToIndex[currVal] = pE
return result
def threeSum(self, nums: List[int], start: int, end: int, target: int) -> List[List[int]]:
result = []
for pE in range(start, end + 1):
firstVal = nums[pE]
resultTwoSum = self.twoSum(nums, pE + 1, len(nums) - 1, target - firstVal)
for [secondVal, thirdVal] in resultTwoSum:
result.append([firstVal, secondVal, thirdVal])
return result
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort() # Sort the array to handle duplicates
result = set()
for pE in range(0, len(nums)):
if pE > 0 and nums[pE] == nums[pE - 1]:
continue
firstVal = nums[pE]
resultThreeSum = self.threeSum(nums, pE + 1, len(nums) - 1, target - firstVal)
for [secondVal, thirdVal, fourVal] in resultThreeSum:
result.add((firstVal, secondVal, thirdVal, fourVal))
return [list(triplet) for triplet in result]
2.9. Four sum
Ref: https://leetcode.com/problems/4sum/
Code
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
left = 0
right = len(nums) - 1
curr = 0
# Because we compare curr <= right, do not increase the left pointer
while curr <= right:
if nums[curr] == 0:
nums[left], nums[curr] = nums[curr], nums[left]
left += 1
# Because after swap: 1,2 will always > 0
curr += 1
elif nums[curr] == 2:
nums[right], nums[curr] = nums[curr], nums[right]
right -= 1
# Because after swap may be it is 2, and 2 may be > 1
else:
curr += 1
return nums
2.10. Shortest Subarray to be Removed to Make Array Sorted
Ref: https://leetcode.com/problems/shortest-subarray-to-be-removed-to-make-array-sorted/description/
Code
class Solution:
def findLengthOfShortestSubarray(self, arr: List[int]) -> int:
n = len(arr)
# Find the longest non-decreasing prefix
left = 0
while left < n - 1 and arr[left] <= arr[left + 1]:
left += 1
# If the entire array is non-decreasing
if left == n - 1:
return 0
# Find the longest non-decreasing suffix
right = n - 1
while right > 0 and arr[right] >= arr[right - 1]:
right -= 1
# We have two options:
# 1. Remove everything after left
# 2. Remove everything before right
# First is remove all the prefix or the suffix
result = min(n - left - 1, right)
# Try to merge the prefix and suffix, because we merge i to j, and find the min gap
i = 0
j = right
while i <= left and j < n:
if arr[i] <= arr[j]:
# We can merge from i to j
result = min(result, j - i - 1)
i += 1
else:
j += 1
return result
2.11. Max number of K-sum pairs
Ref: https://leetcode.com/problems/max-number-of-k-sum-pairs/
Code
class Solution:
def maxOperations(self, nums: List[int], k: int) -> int:
freqMap = {}
count = 0
for num in nums:
complement = k - num
# If we have the complement and haven't used it yet
if complement in freqMap and freqMap[complement] > 0:
count += 1
freqMap[complement] -= 1 # Mark the complement as used
else:
# Add current number to frequency map
freqMap[num] = freqMap.get(num, 0) + 1
return count
2.12. Merge Alternatively
Ref: https://leetcode.com/problems/merge-strings-alternately/description
Code
class Solution:
def mergeAlternately(self, word1: str, word2: str) -> str:
s, i, j = "", 0, 0
while i < len(word1) and j < len(word2):
s += word1[i] + word2[j]
i += 1
j += 1
if i < len(word1):
s += word1[i:]
if j < len(word2):
s += word2[j:]
return s
2.13. String Immutable, Need to change to List Character
Ref: https://leetcode.com/problems/reverse-vowels-of-a-string/
Code
class Solution:
def reverseVowels(self, s: str) -> str:
# Give all the vowels first
vowels = ['a', 'e', 'i', 'o', 'u']
vowelWord = []
for character in s:
if character.lower() in vowels:
vowelWord.append(character)
right = len(vowelWord) - 1
s_list = list(s)
for i in range(0, len(s_list)):
if s[i].lower() in vowels:
s_list[i] = vowelWord[right]
right -= 1
return ''.join(s_list)
2.14. Can Place Flower (Adjacent Bit 0 and 1)
Ref: https://leetcode.com/problems/can-place-flowers/
Code
class Solution:
def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:
count = 0
for i in range(0, len(flowerbed)):
if flowerbed[i] == 0:
empty_left = (i == 0 or flowerbed[i - 1] == 0)
empty_right = (i == len(flowerbed) - 1 or flowerbed[i + 1] == 0)
if empty_left and empty_right:
flowerbed[i] = 1
count += 1
return count >= n
2.15. Check A Map Values in Unique
Ref: https://leetcode.com/problems/unique-number-of-occurrences/
Code
class Solution:
def uniqueOccurrences(self, arr: List[int]) -> bool:
numToCount = {}
for num in arr:
numToCount[num] = numToCount.get(num, 0) + 1
# Get all frequency values
frequencies = list(numToCount.values())
# Use a set to check if all frequencies are unique
return len(frequencies) == len(set(frequencies))
2.16. Determine if Two Strings Are Close
Ref: https://leetcode.com/problems/determine-if-two-strings-are-close/description/
Code
from collections import Counter
class Solution:
def closeStrings(self, word1: str, word2: str) -> bool:
# Must be same length
if len(word1) != len(word2):
return False
# Same characters
if set(word1) != set(word2):
return False
# Same frequency pattern (ignoring which character)
# Such as [2,3,1] == [1,2,3] => sort for easy compare
return sorted(Counter(word1).values()) == sorted(Counter(word2).values())
2.17. Count Tuple In Map
Ref: https://leetcode.com/problems/equal-row-and-column-pairs/description/
Code
from collections import Counter
class Solution:
def equalPairs(self, grid: List[List[int]]) -> int:
# O(N^3)
N = len(grid)
count = 0
for i in range(0, N):
firstRow = tuple(grid[i]) # Row i
secondCol = []
for k in range(0, N):
thirdCol = []
for j in range(0, N):
thirdCol.append(grid[j][k])
secondCol.append(tuple(thirdCol))
counterCol = Counter(secondCol)
count += counterCol[firstRow]
return count
# O(N^2) => tranpose matrix O(N)
# n = len(grid)
# # Convert rows to tuples
# row_tuples = [tuple(row) for row in grid]
# # Convert columns to tuples using zip
# col_tuples = list(zip(*grid))
# print(row_tuples, col_tuples)
# # Count frequency of each column
# col_counter = Counter(col_tuples)
# # Count how many rows match columns
# count = 0
# for row in row_tuples:
# count += col_counter[row]
# return count
2.18. Merge Sort Array
Ref: https://leetcode.com/problems/merge-sorted-array/description/
Code
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
# Create subarrays for nums1 and nums2
subNums1 = nums1[:m] if m > 0 else []
subNums2 = nums2[:n] if n > 0 else []
i = 0
j = 0
k = 0
res = [0] * (m + n)
# Merge the two arrays into res
while i < m and j < n:
if subNums1[i] < subNums2[j]:
res[k] = subNums1[i]
i += 1
else:
res[k] = subNums2[j]
j += 1
k += 1
while i < m:
res[k] = subNums1[i]
i += 1
k += 1
while j < n:
res[k] = subNums2[j]
j += 1
k += 1
# Modify nums1 in-place
for index in range(m + n):
nums1[index] = res[index]
2.19. Remove duplicates 2
Ref: https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/
Code
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
nextNonDup = 0
pE = 0
while pE < len(nums):
if pE < 2 or nums[pE] != nums[nextNonDup - 1] or nums[pE] != nums[nextNonDup - 2]:
nums[nextNonDup] = nums[pE]
nextNonDup += 1
pE += 1
return nextNonDup
2.20. Majority Element
Ref: https://leetcode.com/problems/majority-element/description/
Code
from collections import Counter
import math
class Solution:
def majorityElement(self, nums: List[int]) -> int:
numsCounter = Counter(nums)
for key, value in numsCounter.items():
if value > math.floor(len(nums) / 2):
return key
return -1
2.21. Rotate Array
Ref: https://leetcode.com/problems/rotate-array/description/
Code
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
original = nums[:]
# Loop in the end
for i in range(n - 1, -1, -1):
nums[(i + k) % n] = original[i]
# n = len(nums)
# k %= n # In case k > n
# # Copy the last k elements + the rest
# nums[:] = nums[-k:] + nums[:-k]
2.22. Append Characters to String to Make Subsequence
Ref: https://leetcode.com/problems/append-characters-to-string-to-make-subsequence/description/
Code
class Solution:
def appendCharacters(self, s: str, t: str) -> int:
i = 0
j = 0
while i < len(s) and j < len(t):
if s[i] == t[j]:
j += 1
i += 1
return len(t) - j
2.23. String Matching in an Array
Ref: https://leetcode.com/problems/string-matching-in-an-array/description/
Code
class Solution:
def stringMatching(self, words: List[str]) -> List[str]:
# O(NlogN)
# O(N^2)
# Remove dup
result = set()
sortedWords = sorted(words, key=len)
for i in range(0, len(sortedWords) - 1):
for j in range(i + 1, len(sortedWords)):
if sortedWords[i] in sortedWords[j]:
result.add(sortedWords[i])
return list(result)
2.24. Group Anagrams
Ref: https://leetcode.com/problems/group-anagrams/description/
Code
class Solution:
# O(NlogN)
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
# O(N * L)
result = []
outHashMap = {}
for i in range(0, len(strs)):
hashMap = {}
for character in strs[i]:
hashMap[character] = hashMap.get(character, 0) + 1
# Sort hashmap by key
sorted_hashMap = tuple(sorted(hashMap.items()))
if sorted_hashMap not in outHashMap:
outHashMap[sorted_hashMap] = []
outHashMap[sorted_hashMap].append(strs[i])
return list(outHashMap.values())
2.25. Maximum Swap
Ref: https://leetcode.com/problems/maximum-swap/description/
Code
class Solution:
def maximumSwap(self, num: int) -> int:
num_str = list(str(num))
last = {int(x): i for i, x in enumerate(num_str)} # last position of each digit
for i, digit in enumerate(num_str):
for d in range(9, int(digit), -1): # check digits larger than current
if last.get(d, -1) > i:
# swap
num_str[i], num_str[last[d]] = num_str[last[d]], num_str[i]
return int(''.join(num_str))
return num # already the maximum
2.26. Max Chunks To Make Sorted II
Ref: https://leetcode.com/problems/max-chunks-to-make-sorted-ii/
Code
from typing import List
class Solution:
def maxChunksToSorted(self, arr: List[int]) -> int:
n = len(arr)
max_left = [0] * n
min_right = [0] * n
# Fill max_left array
max_left[0] = arr[0]
for i in range(1, n):
max_left[i] = max(max_left[i - 1], arr[i])
# Fill min_right array
min_right[-1] = arr[-1]
for i in range(n - 2, -1, -1):
min_right[i] = min(min_right[i + 1], arr[i])
# Count valid chunks
chunks = 0
for i in range(n - 1):
if max_left[i] <= min_right[i + 1]:
chunks += 1
return chunks + 1
3. Pattern 3: Fast & Slow Pointer
4. Pattern 4: Merge Interval
4.1. Merge Intervals
Ref: https://leetcode.com/problems/merge-intervals/description/
Code
from typing import List
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
if len(intervals) < 2:
return intervals
# Sort intervals based on the start time
intervals.sort(key=lambda x: x[0])
merged = []
start, end = intervals[0]
for i in range(1, len(intervals)):
curr_start, curr_end = intervals[i]
if curr_start <= end: # Overlapping intervals
end = max(end, curr_end)
else:
# Non-overlapping interval, append previous one
merged.append([start, end])
start, end = curr_start, curr_end
# Add the last interval
merged.append([start, end])
return merged
4.2. Insert Interval
Ref: https://leetcode.com/problems/insert-interval/
Code
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
# Sort intervals based on the start time
intervals += [newInterval]
if len(intervals) < 2:
return intervals
intervals.sort(key=lambda x: x[0])
merged = []
start, end = intervals[0]
for i in range(1, len(intervals)):
curr_start, curr_end = intervals[i]
if curr_start <= end: # Overlapping intervals
end = max(end, curr_end)
else:
# Non-overlapping interval, append previous one
merged.append([start, end])
start, end = curr_start, curr_end
# Add the last interval
merged.append([start, end])
return merged
4.3. Intersection Interval
Ref: https://leetcode.com/problems/interval-list-intersections/description/
Code
from typing import List
class Solution:
def intervalIntersection(self, firstList: List[List[int]], secondList: List[List[int]]) -> List[List[int]]:
# Step 1: Merge both lists
intervals = firstList + secondList
# Step 2: Sort by start time
intervals.sort(key=lambda x: x[0])
result = []
prev = intervals[0]
for i in range(1, len(intervals)):
curr = intervals[i]
# Check for overlap
start = max(prev[0], curr[0])
end = min(prev[1], curr[1])
if start <= end:
result.append([start, end])
# Update prev to the one that extends further
if curr[1] > prev[1]:
prev = curr
return result
4.4. Meeting Rooms
Ref: https://leetcode.com/problems/meeting-rooms/description/
Input: intervals = [[0,30],[5,10],[15,20]]
Output: False # Because [0,30] overlaps with both other meetings
Code
from typing import List
class Solution:
def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
intervals.sort(key=lambda x: x[0])
for i in range(1, len(intervals)):
if intervals[i][0] < intervals[i-1][1]: # overlap
return False
return True
4.5. Meeting Rooms 2 (Pop ra thêm vào)
Ref: https://leetcode.com/problems/meeting-rooms-ii/description/
Meetings: [[4,5], [2,3], [2,4], [3,5]]
Output: 2
Explanation: We will need one room for [2,3] and [3,5], and another room for [2,4] and [4,5].
Code
# Start processing:
# Heap: []
# Add [2,3]:
# No rooms yet, so we allocate one.
# Heap becomes [3].
# Process [2,4]:
# Start time 2 < earliest end time 3 → Overlap → need new room.
# Heap: [3, 4]
# Process [3,5]:
# Start time 3 ≥ earliest end time 3 → Room becomes free → reuse room.
# Pop 3, push 5.
# Heap: [4, 5]
# Process [4,5]:
# Start time 4 ≥ earliest end time 4 → Room becomes free → reuse room.
# Pop 4, push 5.
# Heap: [5, 5]
from typing import List
import heapq
class Solution:
def minMeetingRooms(self, intervals: List[List[int]]) -> int:
if not intervals:
return 0
# Sort by start time
intervals.sort(key=lambda x: x[0])
# Initialize a heap with the end time of the first meeting
heap = [intervals[0][1]]
for i in range(1, len(intervals)):
# If the room is free, reuse it (pop the earliest end time)
if intervals[i][0] >= heap[0]:
heapq.heappop(heap)
# Allocate a new room
heapq.heappush(heap, intervals[i][1])
return len(heap)
4.6. Car Pooling (Greedy)
Ref: https://leetcode.com/problems/car-pooling/description/
Code
from typing import List
class Solution:
def carPooling(self, trips: List[List[int]], capacity: int) -> bool:
# Step 1: Use a difference array to track passenger changes at each location
changes = [0] * 1001 # locations go from 0 to 1000
for num_passengers, start, end in trips:
changes[start] += num_passengers
changes[end] -= num_passengers
# Step 2: Sweep through the route and track the number of passengers
current_passengers = 0
for passengers in changes:
current_passengers += passengers
if current_passengers > capacity:
return False
return True
4.7. Max CPU Load (Merge intervals => Find max)
Ref: https://www.geeksforgeeks.org/maximum-cpu-load-from-the-given-list-of-jobs/
Code
import heapq
from typing import List
def findMaxCPULoad(jobs: List[List[int]]) -> int:
# Step 1: Sort jobs by start time
jobs.sort(key=lambda x: x[0])
max_cpu_load = 0
current_load = 0
min_heap = [] # stores [end_time, load]
for start, end, load in jobs:
# Step 2: Remove all jobs that have ended
while min_heap and min_heap[0][0] <= start:
ended_job = heapq.heappop(min_heap)
current_load -= ended_job[1]
# Step 3: Add current job
heapq.heappush(min_heap, [end, load])
current_load += load
# Step 4: Update max load
max_cpu_load = max(max_cpu_load, current_load)
return max_cpu_load
4.8. Employee Freetime (Merge Overlap, and get the free gap)
Ref: https://leetcode.com/problems/employee-free-time/description/
Code
from typing import List
# Provided Interval class
class Interval:
def __init__(self, start: int, end: int):
self.start = start
self.end = end
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
if len(intervals) < 2:
return intervals
intervals.sort(key=lambda x: x[0])
merged = []
start, end = intervals[0]
for i in range(1, len(intervals)):
curr_start, curr_end = intervals[i]
if curr_start <= end:
end = max(end, curr_end)
else:
merged.append([start, end])
start, end = curr_start, curr_end
merged.append([start, end])
return merged
def employeeFreeTime(self, schedule: 'List[List[Interval]]') -> 'List[Interval]':
# Step 1: Flatten intervals
intervals = [[i.start, i.end] for employee in schedule for i in employee]
# Step 2: Merge using your function
merged = self.merge(intervals)
# Step 3: Find gaps (free time)
free_times = []
for i in range(1, len(merged)):
prev_end = merged[i-1][1]
curr_start = merged[i][0]
if prev_end < curr_start:
free_times.append(Interval(prev_end, curr_start))
return free_times
5. Pattern 5: Cyclic Sort (Missing Number)
5.1. Find A Missing Number
Ref: https://leetcode.com/problems/missing-number/description/
Code
from typing import List
class Solution:
def missingNumber(self, nums: List[int]) -> int:
n = len(nums)
i = 0
while i < n:
correct_pos = nums[i]
# Place nums[i] at its correct position if possible
# Also check that correct_pos < n to avoid index out of range since missing number is in 0..n
if correct_pos < n and nums[i] != nums[correct_pos]:
nums[i], nums[correct_pos] = nums[correct_pos], nums[i]
else:
i += 1
# [9,6,4,2,3,5,7,0,1]
# [0, 1, 2, 3, 4, 5, 6, 7, 9]
print(nums)
# After sorting, the first index where nums[i] != i is the missing number
for i in range(n):
if nums[i] != i:
return i
# If all numbers are at correct positions, then missing number is n
return n
5.2. Find All Missing Number
Ref: https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/description/
Code
from typing import List
class Solution:
def findDisappearedNumbers(self, nums: List[int]) -> List[int]:
n = len(nums)
i = 0
# Place each number at its correct index: nums[i] should be at nums[nums[i]-1]
while i < n:
correct_pos = nums[i] - 1
if nums[i] != nums[correct_pos]:
nums[i], nums[correct_pos] = nums[correct_pos], nums[i]
else:
i += 1
# After placement, numbers which are not at correct index are missing
disappeared = []
for i in range(n):
if nums[i] != i + 1:
disappeared.append(i + 1)
return disappeared
5.3. Find the Duplicate Number
Ref: https://leetcode.com/problems/find-the-duplicate-number/description/
Code
from typing import List
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
i = 0
n = len(nums)
while i < n:
correct_pos = nums[i] - 1
if nums[i] != nums[correct_pos]:
nums[i], nums[correct_pos] = nums[correct_pos], nums[i]
else:
if i != correct_pos:
# Duplicate found
return nums[i]
else:
i += 1
return -1 # If no duplicate found (problem guarantees one duplicate)
5.4. Find all Duplicate Numbers
Ref: https://leetcode.com/problems/find-all-duplicates-in-an-array/
Code
from typing import List
class Solution:
def findDuplicates(self, nums: List[int]) -> List[int]:
i = 0
n = len(nums)
duplicates = []
while i < n:
correct_pos = nums[i] - 1
if nums[i] != nums[correct_pos]:
nums[i], nums[correct_pos] = nums[correct_pos], nums[i]
else:
i += 1
# [4,3,2,7,8,2,3,1]
# [1, 2, 3, 4, 3, 2, 7, 8]
print(nums)
for i in range(n):
if nums[i] != i + 1:
duplicates.append(nums[i])
return duplicates
5.5. Find the Corrupt Pair
Find duplicates and missing numbers.
Code
from typing import List
class Solution:
def findErrorNums(self, nums: List[int]) -> List[int]:
i = 0
n = len(nums)
while i < n:
correct_pos = nums[i] - 1
if nums[i] != nums[correct_pos]:
nums[i], nums[correct_pos] = nums[correct_pos], nums[i]
else:
i += 1
for i in range(n):
if nums[i] != i + 1:
# nums[i] is the duplicate, i+1 is the missing
return [nums[i], i + 1]
return [-1, -1]
5.6. First Missing Positive
Ref: https://leetcode.com/problems/first-missing-positive/description/
Code
from typing import List
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
n = len(nums)
i = 0
while i < n:
correct_pos = nums[i] - 1
# Place nums[i] at its correct position if possible
if 1 <= nums[i] <= n and nums[i] != nums[correct_pos]:
nums[i], nums[correct_pos] = nums[correct_pos], nums[i]
else:
i += 1
# After placement, the first index where nums[i] != i+1 is the missing positive
for i in range(n):
if nums[i] != i + 1:
return i + 1
# If all numbers are in correct place, missing positive is n+1
return n + 1
5.7. Kth Missing Positive Number
Ref: https://leetcode.com/problems/kth-missing-positive-number/description/
Code
from typing import List
class Solution:
def findKthPositive(self, arr: List[int], k: int) -> int:
missing_count = 0
current = 1
i = 0
n = len(arr)
while True:
if i < n and arr[i] == current:
i += 1
else:
missing_count += 1
if missing_count == k:
return current
current += 1
6. Pattern 6: In-place Reversal of a LinkedList
7. Pattern 7: Binary Tree (Tree)
Ancestor
7.1. Lowest Common Ancestor of a Binary Search Tree
Ref: https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# If both nodes are smaller than the current root, go left.
# If both are greater, go right.
# Otherwise, you have found the split point, i.e., the Lowest Common Ancestor.
while root:
if p.val < root.val and q.val < root.val:
root = root.left
elif p.val > root.val and q.val > root.val:
root = root.right
else:
return root
7.2. Lowest Common Ancestor of a Binary Tree
Ref: https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# Base case: if root is None or matches either p or q
if root is None or root == p or root == q:
return root
# Search in the left and right subtrees
# Loop in both left and right, and find the first reach node
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
# If both sides return non-null, current root is the LCA
if left and right:
return root
# If only one side is non-null, return that side
return left if left else right
7.3. Maximum Difference Between Node and Ancestor
Ref: https://leetcode.com/problems/maximum-difference-between-node-and-ancestor/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxAncestorDiff(self, root: Optional[TreeNode]) -> int:
# cur_max and cur_min flow only along one path at a time.
# When we go down the left subtree (5 → 3 → 1), the state is isolated to that path.
# When we go down the right (5 → 8 → 10), it’s a separate set of state values.
def dfs(node, cur_max, cur_min):
if not node:
return cur_max - cur_min
# Update current max and min
# I mean curr_max and curr_min is reset based on different path (path root to left or path root to right)
cur_max = max(cur_max, node.val)
cur_min = min(cur_min, node.val)
# Continue DFS traversal
left = dfs(node.left, cur_max, cur_min)
right = dfs(node.right, cur_max, cur_min)
return max(left, right)
return dfs(root, root.val, root.val)
7.4. Lowest Common Ancestor of Deepest Leaves
Ref: https://leetcode.com/problems/lowest-common-ancestor-of-deepest-leaves/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def lcaDeepestLeaves(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
# Helper function to return both depth and LCA
def dfs(node):
if not node:
return (0, None)
left_depth, left_lca = dfs(node.left)
right_depth, right_lca = dfs(node.right)
if left_depth > right_depth:
return (left_depth + 1, left_lca)
elif right_depth > left_depth:
return (right_depth + 1, right_lca)
else:
# Equal depth => current node is the LCA of both deepest sides
return (left_depth + 1, node)
return dfs(root)[1]
7.5. Kth Ancestor of a Tree Node (Store All Tree Ancestor)
Ref: https://leetcode.com/problems/kth-ancestor-of-a-tree-node/
Code
from typing import List
class TreeAncestor:
def __init__(self, n: int, parent: List[int]):
self.LOG = 20 # Enough for trees with up to around 10^6 nodes
self.dp = [[-1] * self.LOG for _ in range(n)]
# Initialize each node's direct parent (2^0-th ancestor)
for i in range(n):
self.dp[i][0] = parent[i]
# Fill in the dp table for all 2^j-th ancestors
for j in range(1, self.LOG):
for i in range(n):
prev_ancestor = self.dp[i][j - 1]
if prev_ancestor != -1:
self.dp[i][j] = self.dp[prev_ancestor][j - 1]
print(self.dp)
def getKthAncestor(self, node: int, k: int) -> int:
power = 0
while k > 0 and node != -1:
if k % 2 == 1:
node = self.dp[node][power]
k //= 2
power += 1
return node
Tree Depth First Search (DFS)
7.6. Path Sum
Ref: https://leetcode.com/problems/path-sum/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
def dfs(node, currentSum):
if not node:
return False
currentSum += node.val
# If it's a leaf node, check if the path sum matches targetSum
if not node.left and not node.right:
return currentSum == targetSum
# Continue DFS on left and right subtrees
return dfs(node.left, currentSum) or dfs(node.right, currentSum)
return dfs(root, 0)
7.7. Binary Tree Paths
Ref: https://leetcode.com/problems/binary-tree-paths/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
result = []
def dfs(node, path):
if not node:
return
path.append(str(node.val))
if not node.left and not node.right:
result.append("->".join(path))
else:
dfs(node.left, path)
dfs(node.right, path)
# Backtrack
path.pop()
dfs(root, [])
print(result)
return result
7.8. Path Sum II (Backtracking Magic)
Ref: https://leetcode.com/problems/path-sum-ii/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
result = []
def dfs(node, path, target):
if not node:
return
path.append(node.val)
target -= node.val
if not node.left and not node.right and target == 0:
result.append(path[:])
else:
dfs(node.left, path, target)
dfs(node.right, path, target)
# Backtrack
path.pop()
target += node.val
dfs(root, [], targetSum)
return result
7.9. Sum Root to Leaf Numbers (Modify an nonlocal variable)
Ref: https://leetcode.com/problems/sum-root-to-leaf-numbers/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def sumNumbers(self, root: Optional[TreeNode]) -> int:
total = 0
def dfs(node, path):
nonlocal total # Declare nonlocal so we can modify 'total'
if not node:
return
path.append(str(node.val))
if not node.left and not node.right:
total += int("".join(path))
else:
dfs(node.left, path)
dfs(node.right, path)
# Backtrack
path.pop()
dfs(root, [])
return total
7.10. Path Sum III (Sum of the local branch - Idea - Root -> M - Root-> N = M -> N)
Ref: https://leetcode.com/problems/path-sum-iii/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
from typing import Optional
from collections import defaultdict
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
prefix_sum = defaultdict(int)
prefix_sum[0] = 1 # base case: empty path has sum = 0
self.count = 0
def dfs(node, current_sum):
if not node:
return
current_sum += node.val
# Check if there is a prefix path we can subtract to reach targetSum
# IDEA: current_sum = sum from the root to the current node
# If there exists a prefix_sum = current_sum - targetSum, then
# the subpath from after that earlier prefix to the current node must sum to targetSum.
# IDEA: Sum root -> m subtract for root -> n = targetSum => n -> m is the prefix path
self.count += prefix_sum[current_sum - targetSum]
# Add current sum to prefix sums
prefix_sum[current_sum] += 1
dfs(node.left, current_sum)
dfs(node.right, current_sum)
# Backtrack: remove current sum from the map
prefix_sum[current_sum] -= 1
dfs(root, 0)
return self.count
7.11. Diameter of Binary Tree (Backtrack Height of the Node)
Ref: https://leetcode.com/problems/diameter-of-binary-tree/description/
Code
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
self.max_diameter = 0
def dfs(node):
if not node:
return 0
left = dfs(node.left)
right = dfs(node.right)
self.max_diameter = max(self.max_diameter, left + right)
# Backtrack height of the node
return 1 + max(left, right)
dfs(root)
return self.max_diameter
7.12. Binary Tree Maximum Path Sum (Care about what we return in root and leaf, it is enough)
Ref: https://leetcode.com/problems/binary-tree-maximum-path-sum/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxPathSum(self, root: Optional[TreeNode]) -> int:
self.max_sum = float('-inf')
def dfs(node):
if not node:
return 0
# Compute max path sum *starting* from left/right, ignore negatives
left = max(dfs(node.left), 0)
right = max(dfs(node.right), 0)
# Max path THROUGH this node = left + node.val + right
self.max_sum = max(self.max_sum, node.val + left + right)
# Return max gain from this node to parent
# IDEA: In root, in left and right, we have multiple branches, we only select a branch to do this
# Care about what we return in root and leaf, it is enough
return node.val + max(left, right)
dfs(root)
return self.max_sum
BFS
7.13. Binary Tree Level Order Traversal
Ref: https://leetcode.com/problems/binary-tree-level-order-traversal/description/
Code
from collections import deque
from typing import Optional, List
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
# NOTES: Each level we declare here
level_nodes = []
for _ in range(level_size):
node = queue.popleft()
level_nodes.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level_nodes)
return result
7.14. Binary Tree Level Order Traversal II
Ref: https://leetcode.com/problems/binary-tree-level-order-traversal-ii/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def levelOrderBottom(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
# NOTES: Each level we declare here
level_nodes = []
for _ in range(level_size):
node = queue.popleft()
level_nodes.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level_nodes)
return result[::-1]
7.15. ZigZag Tree (Binary Tree Zigzag Level Order Traversal)
Ref: https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
result = []
queue = deque([root])
left_to_right = True
# When out the loop
while queue:
level_size = len(queue)
# NOTES: Each level we declare here
level_nodes = []
for _ in range(level_size):
node = queue.popleft()
if left_to_right:
level_nodes.append(node.val)
else:
level_nodes = [node.val] + level_nodes
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level_nodes)
# NOTES: If we come to backtrack here => it change level
left_to_right = not left_to_right
return result
7.16. Average of Levels in Binary Tree
Ref: https://leetcode.com/problems/average-of-levels-in-binary-tree/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def averageOfLevels(self, root: Optional[TreeNode]) -> List[float]:
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
# NOTES: Each level we declare here
level_nodes = []
for _ in range(level_size):
node = queue.popleft()
level_nodes.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(float(sum(level_nodes) / len(level_nodes)))
return result
7.17. Maximum Level Sum of a Binary Tree
Ref: https://leetcode.com/problems/maximum-level-sum-of-a-binary-tree/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxLevelSum(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
queue = deque([root])
maxSum = float('-inf')
maxLevelSum = 0
currLevel = 0
while queue:
level_size = len(queue)
# NOTES: Each level we declare here
level_nodes = []
for _ in range(level_size):
node = queue.popleft()
level_nodes.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
currLevel += 1
if sum(level_nodes) > maxSum:
maxSum = sum(level_nodes)
maxLevelSum = currLevel
return maxLevelSum
7.18. Minimum Depth of Binary Tree
Ref: https://leetcode.com/problems/minimum-depth-of-binary-tree/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def minDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
result = []
queue = deque([root])
# NOTES: Each level we declare here
currLevel = 1
while queue:
level_size = len(queue)
for _ in range(level_size):
node = queue.popleft()
if not node.left and not node.right:
return currLevel
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
currLevel += 1
return 1
7.19. Maximum Depth of Binary Tree
Ref: https://leetcode.com/problems/maximum-depth-of-binary-tree/description/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
self.max_height = 0
# def dfs(node):
# if not node:
# return 0
# left = dfs(node.left)
# right = dfs(node.right)
# return 1 + max(left, right)
def dfs(node, depth):
if not node:
return
self.max_height = max(self.max_height, depth)
dfs(node.left, depth + 1)
dfs(node.right, depth + 1)
dfs(root, 1)
return self.max_height
7.20. Populating Next Right Pointers in Each Node (Keep the prev node to find sibling)
Ref: https://leetcode.com/problems/populating-next-right-pointers-in-each-node/description/
Code
from collections import deque
class Solution:
def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
if not root:
return None
queue = deque([root])
while queue:
size = len(queue)
prev = None
for i in range(size):
node = queue.popleft()
if prev:
prev.next = node
prev = node
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
# Last node in the level points to None (implicitly already None)
return root
7.21. Right View of Binary Tree
Ref: https://leetcode.com/problems/binary-tree-right-side-view/description/
Code
from collections import deque
from typing import Optional, List
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
# level_nodes = []
for i in range(level_size):
node = queue.popleft()
# Capture the rightmost element at this level
if i == level_size - 1:
result.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
# result.append(level_nodes)
return result
7.22. Left View of Binary Tree
Ref: https://www.geeksforgeeks.org/problems/left-view-of-binary-tree/1
Code
from collections import deque
from typing import Optional, List
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
class Solution:
def LeftView(self, root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
for i in range(level_size):
node = queue.popleft()
# Capture the leftmost element at this level
if i == 0:
result.append(node.data)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return result
Code
class Solution:
def LeftView(self, root):
result = []
def dfs(node, depth):
if not node:
return
if depth == len(result): # First node seen at this depth
result.append(node.data)
dfs(node.left, depth + 1)
dfs(node.right, depth + 1)
dfs(root, 0)
return result
8. Pattern 8: BFS, DFS in Tree
9. Pattern 9: Two Heaps
9.1. Find Median from Data Stream
Ref: https://leetcode.com/problems/find-median-from-data-stream/description/
Code
import heapq
# IDEA: A max heap to store the lower half of the numbers.
# IDEA: A min heap to store the upper half of the numbers.
class MedianFinder:
def __init__(self):
self.low = [] # Max heap (inverted min heap)
self.high = [] # Min heap
def addNum(self, num: int) -> None:
# Add to max heap (invert the value to simulate max heap)
heapq.heappush(self.low, -num)
# Make sure every number in low is <= every number in high
heapq.heappush(self.high, -heapq.heappop(self.low))
# Balance the sizes (low can have one more element than high)
if len(self.low) < len(self.high):
heapq.heappush(self.low, -heapq.heappop(self.high))
def findMedian(self) -> float:
if len(self.low) > len(self.high):
return -self.low[0]
return (-self.low[0] + self.high[0]) / 2.0
# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()
9.2. Sliding Window Median
Ref: https://leetcode.com/problems/sliding-window-median/description/
Code
from typing import List
class Solution:
def medianSlidingWindow(self, nums: List[int], k: int) -> List[float]:
def find_median(window: List[int]) -> float:
window_sorted = sorted(window)
n = len(window_sorted)
if n % 2 == 1:
return float(window_sorted[n // 2])
else:
return (window_sorted[n // 2 - 1] + window_sorted[n // 2]) / 2
result = []
for i in range(len(nums) - k + 1):
window = nums[i:i + k]
result.append(find_median(window))
return result
9.3. IPO (Classical Application of Heap)
Ref: https://leetcode.com/problems/ipo/description/
Code
import heapq
from typing import List
class Solution:
def findMaximizedCapital(self, k: int, w: int, profits: List[int], capital: List[int]) -> int:
# Pair up capital and profit
projects = list(zip(capital, profits))
# Sort by capital needed ascending
projects.sort()
max_heap = []
i = 0
n = len(profits)
for _ in range(k):
# Push all projects we can afford into max-heap
while i < n and projects[i][0] <= w:
# Use negative profit because heapq is min-heap
heapq.heappush(max_heap, -projects[i][1])
i += 1
# If no available projects, break
if not max_heap:
break
# Choose the most profitable project
w += -heapq.heappop(max_heap)
return w
9.4. Find Right Interval
Ref: https://leetcode.com/problems/find-right-interval/description/
Code
from typing import List
import bisect
class Solution:
def findRightInterval(self, intervals: List[List[int]]) -> List[int]:
# Create a list of (start, index) and sort by start
sorted_starts = sorted((start, i) for i, (start, end) in enumerate(intervals))
starts_only = [start for start, _ in sorted_starts]
result = []
for interval in intervals:
end = interval[1]
# Binary search for the smallest start >= current end
# IDEA hay: tìm thằng start đầu tiên lớn hơn thằng end là được bằng binary search
idx = bisect.bisect_left(starts_only, end)
if idx < len(intervals):
result.append(sorted_starts[idx][1]) # Return original index
else:
result.append(-1)
return result
10. Pattern 10: Subsets
11. Pattern 11: Binary Search (As for the question “When can we use binary search?”, my answer is that, If we can discover some kind of monotonicity, for example, if condition(k) is True then condition(k + 1) is True, then we can consider binary search.)
Optimize Binary Search (Cái gì monolithic increase được là cứ binary search được)
11.1. Minimum Time to Complete Trips (Greedy in range)
Ref: https://leetcode.com/problems/minimum-time-to-complete-trips/description/
Code
class Solution:
def minimumTime(self, time: List[int], totalTrips: int) -> int:
# Set search boundaries
left = 1
right = min(time) * totalTrips # worst-case upper bound
# Binary search
while left < right:
mid = (left + right) // 2
trips = sum(mid // t for t in time)
if trips >= totalTrips:
right = mid
else:
left = mid + 1
return left
11.2. Minimum Speed to Arrive on Time (Bài nào mà tìm time có khoảng cụ thể cứ Binary Search)
Ref: https://leetcode.com/problems/minimum-speed-to-arrive-on-time/description/
Code
from typing import List
import math
class Solution:
def minSpeedOnTime(self, dist: List[int], hour: float) -> int:
def time_required(speed: int) -> float:
time = 0.0
for i in range(len(dist) - 1):
time += math.ceil(dist[i] / speed)
time += dist[-1] / speed
return time
left, right = 1, 10**7
result = -1
while left <= right:
mid = (left + right) // 2
if time_required(mid) <= hour:
result = mid
right = mid - 1
else:
left = mid + 1
return result
11.3. Kth Smallest Number in Multiplication Table
Ref: https://leetcode.com/problems/kth-smallest-number-in-multiplication-table/description/
Code
class Solution:
def findKthNumber(self, m: int, n: int, k: int) -> int:
# Count how many numbers in the m x n multiplication table are less than or equal to x.
def count(x):
total = 0
for i in range(1, m + 1):
total += min(x // i, n)
return total
left, right = 1, m * n
while left < right:
mid = (left + right) // 2
if count(mid) < k:
left = mid + 1
else:
right = mid
return left
11.4. Split Array Largest Sum
Ref: https://leetcode.com/problems/split-array-largest-sum/description/
Code
from typing import List
class Solution:
def splitArray(self, nums: List[int], k: int) -> int:
def can_split(max_sum: int) -> bool:
count = 1
current_sum = 0
for num in nums:
if current_sum + num > max_sum:
count += 1
current_sum = num
else:
current_sum += num
return count <= k
left, right = max(nums), sum(nums)
while left < right:
mid = (left + right) // 2
if can_split(mid):
right = mid
else:
left = mid + 1
return left
11.5. Maximum Profit in Job Scheduling
Ref: https://leetcode.com/problems/maximum-profit-in-job-scheduling/description/
Code
from typing import List
import bisect
class Solution:
def jobScheduling(self, startTime: List[int], endTime: List[int], profit: List[int]) -> int:
# Combine all jobs into a list of tuples and sort by end time
jobs = sorted(zip(startTime, endTime, profit), key=lambda x: x[1])
# dp will store pairs of (endTime, maxProfitUntilThatTime)
dp = [(0, 0)] # base case: at time 0, profit is 0
for s, e, p in jobs:
# Use binary search to find the latest job that ends before the current job starts
i = bisect.bisect_right(dp, (s, float('inf'))) - 1
# Calculate new profit if this job is included
curr_profit = dp[i][1] + p
# Only add to dp if it's better than the last recorded profit
if curr_profit > dp[-1][1]:
dp.append((e, curr_profit))
return dp[-1][1]
**Lower Bound **
11.6. First Bad Version
Ref: https://leetcode.com/problems/first-bad-version/description/
Code
# The isBadVersion API is already defined for you.
# def isBadVersion(version: int) -> bool:
class Solution:
def firstBadVersion(self, n: int) -> int:
left, right = 1, n
while left < right:
mid = (left + right) // 2
if isBadVersion(mid):
right = mid # the first bad version is at mid or before
else:
left = mid + 1 # the first bad version must be after mid
return left
11.7. Lower Bound and Higher Bound Template
Code
import bisect
arr = [1, 3, 3, 5, 7, 9]
# Lower Bound: First position >= target
lb = bisect.bisect_left(arr, 3) # Returns 1
# Upper Bound: First position > target
ub = bisect.bisect_right(arr, 3) # Returns 3
print("Lower Bound:", lb)
print("Upper Bound:", ub)
11.8. Find Target Indices After Sorting Array
Ref: https://leetcode.com/problems/find-target-indices-after-sorting-array/description/
Code
class Solution:
def targetIndices(self, nums: List[int], target: int) -> List[int]:
nums.sort()
start = bisect.bisect_left(nums, target) # First occurrence (lower bound)
end = bisect.bisect_right(nums, target) # First after last occurrence (upper bound)
return list(range(start, end))
11.9. Find First and Last Position of Element in Sorted Array
Code
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
left = bisect.bisect_left(nums, target)
right = bisect.bisect_right(nums, target)
if left == right:
return [-1, -1]
return [left, right - 1]
11.10. Sqrt(x)
Ref: https://leetcode.com/problems/sqrtx/description/
Code
class Solution:
def mySqrt(self, x: int) -> int:
if x < 2:
return x
left, right = 1, x // 2
while left <= right:
mid = (left + right) // 2
square = mid * mid
if square == x:
return mid
elif square < x:
left = mid + 1
else:
right = mid - 1
return right # Right is the integer part of sqrt(x)
11.12. Search Insert Position
Ref: https://leetcode.com/problems/search-insert-position/description/
Code
import bisect
from typing import List
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
return bisect.bisect_left(nums, target)
11.13. Capacity To Ship Packages Within D days
Ref: https://leetcode.com/problems/capacity-to-ship-packages-within-d-days/description/
Code
from typing import List
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
def can_ship(capacity):
current = 0
required_days = 1
for weight in weights:
if current + weight > capacity:
required_days += 1
current = 0
current += weight
return required_days <= days
left, right = max(weights), sum(weights)
while left < right:
mid = (left + right) // 2
if can_ship(mid):
right = mid # Try a smaller capacity
else:
left = mid + 1 # Need more capacity
return left
11.14. Koko Eating Bananas
Ref: https://leetcode.com/problems/koko-eating-bananas/description/
Code
from typing import List
import math
class Solution:
def minEatingSpeed(self, piles: List[int], h: int) -> int:
# Define the search space for k
left, right = 1, max(piles)
while left < right:
mid = (left + right) // 2
# Calculate the total hours it would take at speed mid
hours = sum(math.ceil(pile / mid) for pile in piles)
if hours <= h:
right = mid # Try a smaller k
else:
left = mid + 1 # Increase k
return left
11.15. Minimum Number of Days to Make m Bouquets
Ref: https://leetcode.com/problems/minimum-number-of-days-to-make-m-bouquets/description/
Code
from typing import List
class Solution:
def minDays(self, bloomDay: List[int], m: int, k: int) -> int:
# Total flowers needed
if m * k > len(bloomDay):
return -1 # Not enough flowers to form required bouquets
# Helper function to check if we can make m bouquets by `day`
def canMake(day: int) -> bool:
bouquets = 0
flowers = 0
for bloom in bloomDay:
if bloom <= day:
flowers += 1
if flowers == k:
bouquets += 1
flowers = 0
else:
flowers = 0
return bouquets >= m
# Binary search on the number of days
left, right = min(bloomDay), max(bloomDay)
while left < right:
mid = (left + right) // 2
if canMake(mid):
right = mid
else:
left = mid + 1
return left
11.16. Ugly Number III
Ref: https://leetcode.com/problems/ugly-number-iii/description/
Code
from math import gcd
class Solution:
def nthUglyNumber(self, n: int, a: int, b: int, c: int) -> int:
# Least Common Multiple
def lcm(x, y):
return x * y // gcd(x, y)
# Count of numbers <= x divisible by a, b, or c
def count(x):
ab = lcm(a, b)
bc = lcm(b, c)
ac = lcm(a, c)
abc = lcm(ab, c)
return (x // a) + (x // b) + (x // c) - (x // ab) - (x // bc) - (x // ac) + (x // abc)
# Binary search to find the smallest number with count >= n
left, right = 1, 2 * 10**9
while left < right:
mid = (left + right) // 2
if count(mid) < n:
left = mid + 1
else:
right = mid
return left
11.17. Find the Smallest Divisor Given a Threshold
Ref: https://leetcode.com/problems/find-the-smallest-divisor-given-a-threshold/description/
Code
from typing import List
import math
class Solution:
def smallestDivisor(self, nums: List[int], threshold: int) -> int:
# Helper to compute the total sum of ceil divisions
def compute_sum(divisor: int) -> int:
return sum((num + divisor - 1) // divisor for num in nums)
# Binary search range: 1 to max(nums)
left, right = 1, max(nums)
while left < right:
mid = (left + right) // 2
if compute_sum(mid) > threshold:
left = mid + 1
else:
right = mid
return left
11.18. Find K-th Smallest Pair Distance
Ref: https://leetcode.com/problems/find-k-th-smallest-pair-distance/description/
Code
from typing import List
class Solution:
def smallestDistancePair(self, nums: List[int], k: int) -> int:
nums.sort()
def count_pairs(max_dist: int) -> int:
count = 0
left = 0
for right in range(len(nums)):
while nums[right] - nums[left] > max_dist:
left += 1
count += right - left
return count
# Binary search on distance
left, right = 0, nums[-1] - nums[0]
while left < right:
mid = (left + right) // 2
if count_pairs(mid) < k:
left = mid + 1
else:
right = mid
return left
12. Pattern 12: Bitwise XOR
13. Pattern 13: Top ‘K’ Elements
13.1. Top K Frequent Elements
Ref: https://leetcode.com/problems/top-k-frequent-elements/description/
Code
from collections import Counter
import heapq
from typing import List
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
# Count frequencies using Counter
count = Counter(nums)
# Build a heap of the k most frequent elements
heap = heapq.nlargest(k, count.items(), key=lambda x: x[1])
return [item for item, freq in heap]
13.2. Kth Largest Element in an Array (Pop Min => Min Heap Size K)
Ref: https://leetcode.com/problems/kth-largest-element-in-an-array/description/
Code
import heapq
class Solution:
# IDEA: Min Heap to pop min
def findKthLargest(self, nums: List[int], k: int) -> int:
# Use a max-heap (invert distances)
min_heap = []
for num in nums:
heapq.heappush(min_heap, num)
if len(min_heap) > k:
heapq.heappop(min_heap)
return min_heap[0]
13.3. K Closest Points to Origin (Pop Max => Max Heap Size K)
Ref: https://leetcode.com/problems/k-closest-points-to-origin/description/
Code
import heapq
import math
from typing import List
class Solution:
def distance(self, point: List[int]) -> float:
return point[0] ** 2 + point[1] ** 2 # skip sqrt for efficiency
def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
# Use a max-heap (invert distances)
max_heap = []
for point in points:
dist = -self.distance(point) # invert to simulate max-heap
heapq.heappush(max_heap, (dist, point))
if len(max_heap) > k:
# IDEA: Python's heapq is a min-heap by default, meaning:
# IDEA: It always keeps the smallest element at the top (heap[0]), here is dist
heapq.heappop(max_heap)
# Extract only the points from the heap
return [point for _, point in max_heap]
13.4. Connect Ropes
Ref: https://leetcode.com/problems/minimum-cost-to-connect-sticks/description/
Code
import heapq
def connect_ropes(ropes):
heapq.heapify(ropes) # Convert list into a min-heap
total_cost = 0
while len(ropes) > 1:
# Pop two smallest ropes
first = heapq.heappop(ropes)
second = heapq.heappop(ropes)
cost = first + second
total_cost += cost
# Push the combined rope back into the heap
heapq.heappush(ropes, cost)
return total_cost
13.5. Sort Characters By Frequency
Ref: https://leetcode.com/problems/sort-characters-by-frequency/description/
Code
from collections import Counter
import heapq
from typing import List
class Solution:
def frequencySort(self, s: str) -> str:
# Count frequencies using Counter
count = Counter(s)
# Build a heap of the k most frequent elements
heap = heapq.nlargest(len(s), count.items(), key=lambda x: x[1])
return ''.join([item * freq for item, freq in heap])
13.6. Kth Largest Element in a Stream (Real-time idea)
Ref: https://leetcode.com/problems/kth-largest-element-in-a-stream/description/
Code
import heapq
# IDEA: K largest use min heap
class KthLargest:
def __init__(self, k: int, nums: List[int]):
self.k = k
self.min_heap = nums
# Inplace
heapq.heapify(self.min_heap)
# Maintain only the k largest elements in the heap
while len(self.min_heap) > k:
heapq.heappop(self.min_heap)
def add(self, val: int) -> int:
# Push first
heapq.heappush(self.min_heap, val)
# If heap grows beyond k, remove the smallest
if len(self.min_heap) > self.k:
heapq.heappop(self.min_heap)
# The root is the k-th largest
return self.min_heap[0]
# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)
13.7. ‘K’ Closest Numbers
Ref: https://leetcode.com/problems/find-k-closest-elements/description/
Code
import heapq
class Solution:
# Max Heap
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
# Use a max-heap (invert distances)
max_heap = []
for num in arr:
dist = -abs(num - x) # invert to simulate max-heap
heapq.heappush(max_heap, (dist, -num))
# Because it push larger, remove the smaller first => reverse to remove the larger first, push smaller later
if len(max_heap) > k:
# IDEA: Python's heapq is a min-heap by default, meaning:
# IDEA: It always keeps the smallest element at the top (heap[0]), here is dist
heapq.heappop(max_heap)
# Extract only the arr from the heap
result = [-num for _, num in max_heap]
# Why -num?
# This ensures that when two numbers have the same distance from x, the smaller number is preferred — as required by the problem.
result.sort()
return result
13.8. Least Number of Unique Integers after K Removals
Ref: https://leetcode.com/problems/least-number-of-unique-integers-after-k-removals/description/
Code
class Solution:
def findLeastNumOfUniqueInts(self, arr: List[int], k: int) -> int:
# Step 1: Count the frequency of each number
count = Counter(arr)
# Step 2: Sort the frequencies from smallest to largest
freq_list = sorted(count.values()) # we only care about the frequencies
# Step 3: Remove the least frequent elements first, reducing k
unique_count = len(freq_list)
for freq in freq_list:
if k >= freq:
k -= freq
unique_count -= 1 # one unique number is fully removed
else:
break # can't remove this whole group, stop here
return unique_count
13.9. Reorganize String
Ref: https://leetcode.com/problems/reorganize-string/description/
Code
import heapq
from collections import Counter
class Solution:
# To avoid having the same characters next to each other, you want to spread out the most frequent characters as evenly as possible.
def reorganizeString(self, s: str) -> str:
# Step 1: Count the frequency of each character
count = Counter(s)
if any(freq > (len(s) + 1) // 2 for freq in count.values()):
return ""
max_heap = [(-freq, char) for char, freq in count.items()]
heapq.heapify(max_heap)
prev_freq, prev_char = 0, ''
result = []
while max_heap:
freq, char = heapq.heappop(max_heap)
result.append(char)
# If the previous character can still be used, push it back
if prev_freq < 0:
heapq.heappush(max_heap, (prev_freq, prev_char))
# Update previous character to the current one
prev_freq, prev_char = freq + 1, char # since freq is negative
reorganized = ''.join(result)
# Final check: if the result is valid
for i in range(1, len(reorganized)):
if reorganized[i] == reorganized[i - 1]:
return ""
return reorganized
13.10. Task Scheduler
Ref: https://leetcode.com/problems/task-scheduler/description/
Code
from collections import Counter, deque
class Solution:
def leastInterval(self, tasks, n):
time = 0
task_counts = Counter(tasks)
cooldown = {} # task -> next available time
while task_counts:
available = [task for task in task_counts if cooldown.get(task, 0) <= time]
if available:
# Choose task with highest remaining count
task = max(available, key=lambda x: task_counts[x])
task_counts[task] -= 1
if task_counts[task] == 0:
del task_counts[task]
cooldown[task] = time + n + 1
time += 1
return time
13.11. Maximum Frequency Stack
Ref: https://leetcode.com/problems/maximum-frequency-stack/description/
Code
from collections import Counter, deque
class Solution:
def leastInterval(self, tasks, n):
time = 0
task_counts = Counter(tasks)
cooldown = {} # task -> next available time
while task_counts:
available = [task for task in task_counts if cooldown.get(task, 0) <= time]
if available:
# Choose task with highest remaining count
task = max(available, key=lambda x: task_counts[x])
task_counts[task] -= 1
if task_counts[task] == 0:
del task_counts[task]
cooldown[task] = time + n + 1
time += 1
return time
13.12. Maximum Frequency Stack
Ref: https://leetcode.com/problems/maximum-frequency-stack/description/
Code
from collections import defaultdict
class FreqStack:
def __init__(self):
self.freq = defaultdict(int) # val -> freq
self.group = defaultdict(list) # freq -> list (stack of values)
self.max_freq = 0
def push(self, val: int) -> None:
f = self.freq[val] + 1
self.freq[val] = f
self.group[f].append(val)
if f > self.max_freq:
self.max_freq = f
def pop(self) -> int:
val = self.group[self.max_freq].pop()
self.freq[val] -= 1
if not self.group[self.max_freq]:
self.max_freq -= 1
return val
13.13. Top K Frequent Words
Ref: https://leetcode.com/problems/top-k-frequent-words/description/
Code
class Solution:
def topKFrequent(self, words: List[str], k: int) -> List[str]:
# Count frequencies using Counter
count = Counter(words)
# Build a heap of the k most frequent elements, and by lexicalgraphical order
# -x[1] ensures higher frequency comes first.
# x[0] ensures lexicographical order among words with the same frequency.
heap = heapq.nsmallest(k, count.items(), key=lambda x: (-x[1], x[0]))
return [item for item, freq in heap]
Add new element to the pq if future calculation will depend on the current calculated value
13.14. Maximum Average Pass Ratio (Sample Calculation for Max Heap)
Ref: https://leetcode.com/problems/maximum-average-pass-ratio/description/
Code
class Solution:
def maxAverageRatio(self, classes: List[List[int]], extraStudents: int) -> float:
def gain(p: int, t: int) -> float:
return (p + 1) / (t + 1) - p / t
heap = []
for p, t in classes:
heapq.heappush(heap, (-gain(p, t), p, t))
for _ in range(extraStudents):
g, p, t = heapq.heappop(heap)
p += 1
t += 1
heapq.heappush(heap, (-gain(p, t), p, t))
return sum(p / t for _, p, t in heap) / len(classes)
13.15. Maximum Ice Cream Bars
Ref: https://leetcode.com/problems/maximum-ice-cream-bars/description/
Code
class Solution:
def maxIceCream(self, costs: List[int], coins: int) -> int:
costs.sort()
count = 0
for price in costs:
if coins < price:
break
coins -= price
count += 1
return count
13.16. Minimum Interval to Include Each Query
Ref: https://leetcode.com/problems/minimum-interval-to-include-each-query/description/
Code
from typing import List
import heapq
class Solution:
def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
intervals.sort()
sorted_queries = sorted((q, i) for i, q in enumerate(queries))
result = [-1] * len(queries)
min_heap = []
i = 0 # Pointer for intervals
for query, idx in sorted_queries:
# Add all intervals starting before or at the query
while i < len(intervals) and intervals[i][0] <= query:
start, end = intervals[i]
if end >= query:
heapq.heappush(min_heap, (end - start + 1, end))
i += 1
# Remove intervals from heap that don't cover the query
while min_heap and min_heap[0][1] < query:
heapq.heappop(min_heap)
if min_heap:
result[idx] = min_heap[0][0]
return result
13.17. Equal Sum Arrays With Minimum Number of Operations (Two Heap, Min Heap and Max Heap)
Ref: https://leetcode.com/problems/equal-sum-arrays-with-minimum-number-of-operations/description/
Code
from typing import List
import heapq
class Solution:
def minOperations(self, nums1: List[int], nums2: List[int]) -> int:
if len(nums1) * 6 < len(nums2) or len(nums2) * 6 < len(nums1):
return -1 # impossible to match sums
sum1, sum2 = sum(nums1), sum(nums2)
if sum1 == sum2:
return 0
# Ensure sum1 is smaller
if sum1 > sum2:
nums1, nums2 = nums2, nums1
sum1, sum2 = sum2, sum1
# Gains for increasing nums1 (values: 6 - num)
min_heap = [6 - num for num in nums1 if 6 - num > 0]
# Gains for decreasing nums2 (values: num - 1)
max_heap = [num - 1 for num in nums2 if num - 1 > 0]
# Use max-heaps: invert signs
min_heap = [-x for x in min_heap]
max_heap = [-x for x in max_heap]
heapq.heapify(min_heap)
heapq.heapify(max_heap)
diff = sum2 - sum1
ops = 0
while diff > 0:
gain1 = -min_heap[0] if min_heap else 0
gain2 = -max_heap[0] if max_heap else 0
if gain1 == 0 and gain2 == 0:
return -1 # No possible gains left
if gain1 >= gain2:
diff -= gain1
heapq.heappop(min_heap)
else:
diff -= gain2
heapq.heappop(max_heap)
ops += 1
return ops
14. Pattern 14: K-way merge
14.1. Merge k Sorted Lists
Ref: https://leetcode.com/problems/merge-k-sorted-lists/
Code
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
heap = []
# Step 1: Initialize the heap with the head of each list
for idx, node in enumerate(lists):
if node:
heappush(heap, (node.val, idx, node))
dummy = ListNode(0)
current = dummy
# Step 2: Extract min and add next node from the same list to the heap
while heap:
val, idx, node = heappop(heap)
current.next = node
current = current.next
if node.next:
heappush(heap, (node.next.val, idx, node.next))
return dummy.next
14.2. Find K Pairs with Smallest Sums
Ref: https://leetcode.com/problems/find-k-pairs-with-smallest-sums/description/
Code
import heapq
from typing import List
class Solution:
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:
if not nums1 or not nums2 or k == 0:
return []
min_heap = []
result = []
# Initialize the heap with the first element in nums2 paired with the first k elements in nums1
for i in range(min(k, len(nums1))):
heapq.heappush(min_heap, (nums1[i] + nums2[0], i, 0))
while min_heap and len(result) < k:
curr_sum, i, j = heapq.heappop(min_heap)
result.append([nums1[i], nums2[j]])
# If there is another element in nums2 for the same nums1[i], push it to the heap
if j + 1 < len(nums2):
heapq.heappush(min_heap, (nums1[i] + nums2[j + 1], i, j + 1))
return result
14.3. Kth Smallest Element in a Sorted Matrix
Ref: https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/description/
Code
import heapq
from typing import List
class Solution:
def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
n = len(matrix)
# Min-heap: each entry is (value, row, col)
heap = []
# Initialize the heap with the first element of each row
for r in range(min(k, n)): # We don't need more than k rows
heapq.heappush(heap, (matrix[r][0], r, 0))
# Extract the smallest element k times
count = 0
while heap:
val, r, c = heapq.heappop(heap)
count += 1
if count == k:
return val
if c + 1 < n:
heapq.heappush(heap, (matrix[r][c + 1], r, c + 1))
14.4. Smallest Range Covering Elements from K Lists
Ref: https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/description/
Code
from heapq import heappush, heappop
from typing import List
class Solution:
def smallestRange(self, nums: List[List[int]]) -> List[int]:
min_heap = []
max_val = float('-inf')
# Initialize heap with the first element from each list
for i in range(len(nums)):
val = nums[i][0]
heappush(min_heap, (val, i, 0)) # (value, list index, element index)
max_val = max(max_val, val)
range_start, range_end = float('-inf'), float('inf')
while True:
min_val, list_idx, elem_idx = heappop(min_heap)
# Update the range if smaller
if max_val - min_val < range_end - range_start:
range_start, range_end = min_val, max_val
# Move to the next element in the same list
if elem_idx + 1 == len(nums[list_idx]):
break # We've reached the end of one of the lists
next_val = nums[list_idx][elem_idx + 1]
heappush(min_heap, (next_val, list_idx, elem_idx + 1))
max_val = max(max_val, next_val)
return [range_start, range_end]
14.5. Median of Two Sorted Arrays
Ref: https://leetcode.com/problems/median-of-two-sorted-arrays/description/
Code
from typing import List
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
merged = []
i = j = 0
# Merge the arrays
while i < len(nums1) and j < len(nums2):
if nums1[i] < nums2[j]:
merged.append(nums1[i])
i += 1
else:
merged.append(nums2[j])
j += 1
while i < len(nums1):
merged.append(nums1[i])
i += 1
while j < len(nums2):
merged.append(nums2[j])
j += 1
n = len(merged)
if n % 2 == 1:
return float(merged[n // 2])
else:
return (merged[n // 2 - 1] + merged[n // 2]) / 2
15. Pattern 15: Dynamic Programming
Pattern DP-1: 0/1 Knapsack (Chỉ dùng được 1 Item - 1 Quantity)
🧭 What is the Knapsack Problem?
-
The Knapsack Problem is a classic optimization problem. The goal is:
-
Given a list of items, each with a weight and a value, and a knapsack capacity, select a subset of items to maximize total value without exceeding the knapsack’s weight capacity.
Template:
Top Down
Code
from typing import List
def solveKnapsack(profits: List[int], weights: List[int], capacity: int) -> int:
memo = {}
def knapsackRecursive(currIndex: int, capacity: int) -> int:
# base cases
if capacity <= 0 or currIndex >= len(profits):
return 0
if (currIndex, capacity) in memo:
return memo[(currIndex, capacity)]
currentProfit = 0
# include the item if possible
if weights[currIndex] <= capacity:
currentProfit = profits[currIndex] + knapsackRecursive(currIndex + 1, capacity - weights[currIndex])
# exclude the item
profitWithoutCurrent = knapsackRecursive(currIndex + 1, capacity)
memo[(currIndex, capacity)] = max(currentProfit, profitWithoutCurrent)
return memo[(currIndex, capacity)]
return knapsackRecursive(0, capacity)
# Example usage:
print(f"Total knapsack profit: ---> ${solveKnapsack([1, 6, 10, 16], [1, 2, 3, 5], 7)}")
print(f"Total knapsack profit: ---> ${solveKnapsack([1, 6, 10, 16], [1, 2, 3, 5], 6)}")
Bottom Up
Code
from typing import List
def solveKnapsack(profits: List[int], weights: List[int], capacity: int) -> int:
n = len(profits)
if capacity <= 0 or n == 0 or len(weights) != n:
return 0
# initialize dp array: n rows, capacity+1 cols
dp = [[0 for _ in range(capacity + 1)] for _ in range(n)]
# populate capacity=0 columns; profit is 0 if capacity is zero
for i in range(n):
dp[i][0] = 0
# fill first row (only one item)
for c in range(capacity + 1):
if weights[0] <= c:
dp[0][c] = profits[0]
# process all items for all capacities
for i in range(1, n):
for c in range(1, capacity + 1):
profit_with_i = 0
profit_without_i = 0
# include item if weight allows
if weights[i] <= c:
profit_with_i = profits[i] + dp[i-1][c - weights[i]]
# exclude item
profit_without_i = dp[i-1][c]
# choose max
dp[i][c] = max(profit_with_i, profit_without_i)
# answer is in bottom-right corner
return dp[n-1][capacity]
# Example usage
print(f"Total knapsack profit: ---> ${solveKnapsack([1, 6, 10, 16], [1, 2, 3, 5], 7)}")
print(f"Total knapsack profit: ---> ${solveKnapsack([1, 6, 10, 16], [1, 2, 3, 5], 6)}")
15.1. Maximum Earnings From Taxi
Ref: https://leetcode.com/problems/maximum-earnings-from-taxi/description/
Top Down
Code
from typing import List
import bisect
class Solution:
def maxTaxiEarnings(self, n: int, rides: List[List[int]]) -> int:
# Sort rides by start time
rides.sort(key=lambda x: x[0])
starts = [ride[0] for ride in rides]
memo = {}
def knapsack(currIndex: int) -> int:
# Base case
if currIndex >= len(rides):
return 0
if currIndex in memo:
return memo[currIndex]
start, end, tip = rides[currIndex]
earning = (end - start) + tip
# Find next ride index that can be taken after this one
nextIndex = bisect.bisect_left(starts, end)
# Option 1: Include current ride + optimal from next compatible ride
include = earning + knapsack(nextIndex)
# Option 2: Skip current ride
exclude = knapsack(currIndex + 1)
memo[currIndex] = max(include, exclude)
return memo[currIndex]
return knapsack(0)
Bottom-up
Code
from typing import List
import bisect
class Solution:
def maxTaxiEarnings(self, n: int, rides: List[List[int]]) -> int:
# Sort rides by end time to process them in order
rides.sort(key=lambda x: x[1])
ends = [ride[1] for ride in rides]
m = len(rides)
# dp[i] will store max earnings considering rides up to i-th (1-indexed)
dp = [0] * (m + 1)
for i in range(1, m + 1):
start, end, tip = rides[i - 1]
earning = (end - start) + tip
# Find the last ride that ends before the current ride starts
idx = bisect.bisect_right(ends, start)
# max earnings if we include current ride
include = earning + dp[idx]
# max earnings if we exclude current ride
exclude = dp[i - 1]
# Take the max of including or excluding the current ride
dp[i] = max(include, exclude)
return dp[m]
15.2. Partition Equal Subset Sum
Ref: https://leetcode.com/problems/partition-equal-subset-sum/description/
Top Down
Code
from typing import List
class Solution:
def canPartition(self, nums: List[int]) -> bool:
total_sum = sum(nums)
# If sum is odd, partition not possible
if total_sum % 2 != 0:
return False
target = total_sum // 2
n = len(nums)
memo = {}
def can_find_sum(curr_index: int, curr_sum: int) -> bool:
# Base cases
if curr_sum == 0:
return True
if curr_index >= n or curr_sum < 0:
return False
# Check memo
if (curr_index, curr_sum) in memo:
return memo[(curr_index, curr_sum)]
# Choose to include nums[curr_index]
include = can_find_sum(curr_index + 1, curr_sum - nums[curr_index])
# Or exclude nums[curr_index]
exclude = can_find_sum(curr_index + 1, curr_sum)
memo[(curr_index, curr_sum)] = include or exclude
return memo[(curr_index, curr_sum)]
return can_find_sum(0, target)
Bottom-up
Code
from typing import List
class Solution:
def canPartition(self, nums: List[int]) -> bool:
total_sum = sum(nums)
# If total sum is odd, can't partition equally
if total_sum % 2 != 0:
return False
target = total_sum // 2
n = len(nums)
# dp[i][j] = True if subset of first i numbers can make sum j
dp = [[False] * (target + 1) for _ in range(n)]
# Sum 0 is always possible: take no elements
for i in range(n):
dp[i][0] = True
# With only the first number, can we form sum nums[0]?
if nums[0] <= target:
dp[0][nums[0]] = True
# Process all subsets
for i in range(1, n):
for j in range(1, target + 1):
# Exclude current number
dp[i][j] = dp[i - 1][j]
# Include current number if it does not exceed the sum
if nums[i] <= j:
dp[i][j] = dp[i][j] or dp[i - 1][j - nums[i]]
return dp[n - 1][target]
15.3. Subset Sum Problem
Ref: https://www.techiedelight.com/subset-sum-problem/
Code
from typing import List
class Solution:
def subsetSum(self, nums: List[int], target: int) -> bool:
memo = {}
def can_find_sum(curr_index: int, curr_sum: int) -> bool:
# Base cases
if curr_sum == 0:
return True
if curr_index >= len(nums) or curr_sum < 0:
return False
# Check memo
if (curr_index, curr_sum) in memo:
return memo[(curr_index, curr_sum)]
# Include current number
include = can_find_sum(curr_index + 1, curr_sum - nums[curr_index])
# Exclude current number
exclude = can_find_sum(curr_index + 1, curr_sum)
memo[(curr_index, curr_sum)] = include or exclude
return memo[(curr_index, curr_sum)]
return can_find_sum(0, target)
15.4. Target Sum
Ref: https://leetcode.com/problems/target-sum/description/
Code
from typing import List
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
memo = {}
def backtrack(index: int, current_sum: int) -> int:
# Base case: if at the end of array
if index == len(nums):
return 1 if current_sum == target else 0
# Check memo
if (index, current_sum) in memo:
return memo[(index, current_sum)]
# Choose '+'
positive = backtrack(index + 1, current_sum + nums[index])
# Choose '-'
negative = backtrack(index + 1, current_sum - nums[index])
memo[(index, current_sum)] = positive + negative
return memo[(index, current_sum)]
return backtrack(0, 0)
Pattern DP-2: Unbounded Knapsack (Được dùng nhiều quantity của 1 item)
Top Down
Code
from typing import List
def solveKnapsack(profits: List[int], weights: List[int], capacity: int) -> int:
dp = {}
def knapsackRecursive(profits, weights, capacity, currIndex):
# base case
if capacity <= 0 or currIndex >= len(profits):
return 0
if len(profits) == 0 or len(profits) != len(weights):
return 0
if (currIndex, capacity) in dp:
return dp[(currIndex, capacity)]
currentProfit = 0
# include the item at currIndex if it fits
if weights[currIndex] <= capacity:
currentProfit = profits[currIndex] + knapsackRecursive(
profits, weights, capacity - weights[currIndex], currIndex
)
# exclude the item at currIndex and move to next
currentProfitMinusIndexItem = 0
if capacity - weights[currIndex] >= 0: # same as above but careful with capacity
currentProfitMinusIndexItem = knapsackRecursive(
profits, weights, capacity - weights[currIndex], currIndex + 1
)
else:
# can't include the item, so just move on without reducing capacity
currentProfitMinusIndexItem = knapsackRecursive(
profits, weights, capacity, currIndex + 1
)
dp[(currIndex, capacity)] = max(currentProfit, currentProfitMinusIndexItem)
return dp[(currIndex, capacity)]
return knapsackRecursive(profits, weights, capacity, 0)
profits = [15, 50, 60, 90]
weights = [1, 3, 4, 5]
print(f"Total knapsack profit: ---> {solveKnapsack(profits, weights, 8)}")
Bottom Up
Code
from typing import List
def solveKnapsack(profits: List[int], weights: List[int], capacity: int) -> int:
if capacity <= 0 or len(profits) == 0 or len(profits) != len(weights):
return 0
n = len(profits)
dp = [[0] * (capacity + 1) for _ in range(n)]
# initialize capacity=0 columns with 0 (optional since default is 0)
for i in range(n):
dp[i][0] = 0
for i in range(n):
for c in range(1, capacity + 1):
currentProfit = 0
currentProfitMinusIndex = 0
if weights[i] <= c:
# note dp[i][c - weights[i]] allows reusing same item multiple times
currentProfit = profits[i] + dp[i][c - weights[i]]
if i > 0:
currentProfitMinusIndex = dp[i - 1][c]
dp[i][c] = max(currentProfit, currentProfitMinusIndex)
# Uncomment this to see dp table
# for row in dp:
# print(row)
return dp[n - 1][capacity]
profits = [15, 50, 60, 90]
weights = [1, 3, 4, 5]
print(f"Total knapsack profit: ---> {solveKnapsack(profits, weights, 8)}")
print(f"Total knapsack profit: ---> {solveKnapsack(profits, weights, 6)}")
15.5. Coin Change
Ref: https://leetcode.com/problems/coin-change/
Code
from typing import List
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
memo = {}
def dp(rem: int) -> int:
# Base case: if amount is 0, we need 0 coins
if rem == 0:
return 0
# If amount becomes negative, no solution
if rem < 0:
return float('inf')
# If already computed, return cached result
if rem in memo:
return memo[rem]
# Try every coin and choose the best (min)
min_coins = float('inf')
for coin in coins:
res = dp(rem - coin)
if res != float('inf'):
min_coins = min(min_coins, res + 1)
memo[rem] = min_coins
return min_coins
result = dp(amount)
return result if result != float('inf') else -1
15.6. Coin Change II
Ref: https://leetcode.com/problems/coin-change-ii/description/
Code
from typing import List
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
memo = {}
def count_ways(index: int, remaining: int) -> int:
# Base cases
if remaining == 0:
return 1
if index == len(coins):
return 0
# Check memo
if (index, remaining) in memo:
return memo[(index, remaining)]
# Choose the coin
pick = 0
if coins[index] <= remaining:
pick = count_ways(index, remaining - coins[index])
# Skip the coin
skip = count_ways(index + 1, remaining)
memo[(index, remaining)] = pick + skip
return memo[(index, remaining)]
return count_ways(0, amount)
15.7. Maximum Ribbon Cut
Ref: https://leetcode.com/problems/cutting-ribbons/
Code
from typing import List
class Solution:
def maxRibbonPieces(self, ribbonLengths: List[int], total: int) -> int:
n = len(ribbonLengths)
dp = [[float('-inf')] * (total + 1) for _ in range(n)]
# Base case: zero total length => zero pieces
for i in range(n):
dp[i][0] = 0
for i in range(n):
for t in range(1, total + 1):
if i > 0:
# Exclude current ribbon length
dp[i][t] = dp[i - 1][t]
if t >= ribbonLengths[i] and dp[i][t - ribbonLengths[i]] != float('-inf'):
# Include current ribbon length
dp[i][t] = max(dp[i][t], dp[i][t - ribbonLengths[i]] + 1)
return -1 if dp[n - 1][total] == float('-inf') else dp[n - 1][total]
# Example usage:
sol = Solution()
print(sol.maxRibbonPieces([2, 3, 5], 5)) # Output: 2
print(sol.maxRibbonPieces([2, 3], 7)) # Output: 3
print(sol.maxRibbonPieces([3, 5, 7], 13)) # Output: 3
print(sol.maxRibbonPieces([3, 5], 7)) # Output: -1
Pattern DP-3: Fibonacci Numbers (Kết quả sau dựa trên các kết quả n - 1 và n - 2)
Top down
Code
def calculateFibonacci(n):
memoize = {}
def fib(n):
if n < 2:
return n
if n in memoize:
return memoize[n]
memoize[n] = fib(n - 1) + fib(n - 2)
return memoize[n]
return fib(n)
print(f"5th Fibonacci is ---> {calculateFibonacci(5)}")
print(f"6th Fibonacci is ---> {calculateFibonacci(6)}")
print(f"7th Fibonacci is ---> {calculateFibonacci(7)}")
Bottom up
Code
def calculateFibonacci(n):
if n < 2:
return n
dp = [0, 1]
for i in range(2, n + 1):
dp.append(dp[i - 1] + dp[i - 2])
return dp[n]
print(f"5th Fibonacci is ---> {calculateFibonacci(5)}")
print(f"6th Fibonacci is ---> {calculateFibonacci(6)}")
print(f"7th Fibonacci is ---> {calculateFibonacci(7)}")
15.8. Climbing Stairs
Ref: https://leetcode.com/problems/climbing-stairs/description/
Code
class Solution:
def climbStairs(self, n: int) -> int:
if n <= 2:
return n
dp = [0] * (n + 1)
dp[1], dp[2] = 1, 2
for i in range(3, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
15.9. Number factors
Ref: https://www.geeksforgeeks.org/count-ofdifferent-ways-express-n-sum-1-3-4/
Code
class Solution:
def countWays(self, n):
dp = {}
def countWaysRecursive(n):
# base cases
if n <= 2:
return 1
if n == 3:
return 2
if n not in dp:
subtract1 = countWaysRecursive(n - 1)
subtract3 = countWaysRecursive(n - 3)
subtract4 = countWaysRecursive(n - 4)
dp[n] = subtract1 + subtract3 + subtract4
return dp[n]
return countWaysRecursive(n)
print(f"Number of ways: ---> {countWays(4)}")
print(f"Number of ways: ---> {countWays(5)}")
print(f"Number of ways: ---> {countWays(6)}")
15.10. Minimum jumps with fee
Ref: https://leetcode.com/problems/min-cost-climbing-stairs/description/
Top Down
Code
def findMinFee(fee):
dp = {}
def findMinFeeRecursive(currIndex):
if currIndex > len(fee) - 1:
return 0
if currIndex not in dp:
# if we take 1 step, we are left with n-1 steps
take1Step = findMinFeeRecursive(currIndex + 1)
# if we take 2 steps, we are left with n-2 steps
take2Step = findMinFeeRecursive(currIndex + 2)
# if we take 3 steps, we are left with n-3 steps
take3Step = findMinFeeRecursive(currIndex + 3)
dp[currIndex] = min(take1Step, take2Step, take3Step) + fee[currIndex]
return dp[currIndex]
return findMinFeeRecursive(0)
Bottom Up
Code
from typing import List
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
n = len(cost)
dp = [0] * (n + 1) # dp[i] = min cost to reach step i
# Base cases
dp[0] = 0 # start before step 0, no cost
dp[1] = 0 # start before step 1, no cost
for i in range(2, n + 1):
dp[i] = min(
dp[i-1] + cost[i-1], # cost to come from one step below
dp[i-2] + cost[i-2] # cost to come from two steps below
)
return dp[n]
15.11. House thief
Ref: https://leetcode.com/problems/house-robber/
Code
# If there are no houses, return 0.
# If there's only one house, return the money in that house.
# For each house, there are two choices:
# You can rob the current house, which means you should add the money from that house to the total amount up to house i-2.
# Or, you can skip robbing the current house, and the total amount will just be the same as the amount up to house i-1.
# Formula:
# dp[i] = max(dp[i-1], nums[i] + dp[i-2])
class Solution:
def rob(self, nums):
if not nums:
return 0
elif len(nums) == 1:
return nums[0]
# Initialize the dp array
dp = [0] * len(nums)
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, len(nums)):
dp[i] = max(dp[i-1], nums[i] + dp[i-2])
return dp[-1]
Pattern DP-4: Longest Palindromic Subsequence (liên quan palindrome)
Top Down
Code
def find_lps_length(s):
dp = {}
def find_lps_length_recursive(start_index, end_index):
# Base case: invalid substring
if start_index > end_index:
return 0
# Base case: single character is a palindrome of length 1
if start_index == end_index:
return 1
# Check if result is already in dp
if (start_index, end_index) in dp:
return dp[(start_index, end_index)]
if s[start_index] == s[end_index]:
dp[(start_index, end_index)] = 2 + find_lps_length_recursive(start_index + 1, end_index - 1)
else:
skip_start = find_lps_length_recursive(start_index + 1, end_index)
skip_end = find_lps_length_recursive(start_index, end_index - 1)
dp[(start_index, end_index)] = max(skip_start, skip_end)
return dp[(start_index, end_index)]
return find_lps_length_recursive(0, len(s) - 1)
Bottom Up
Code
def find_lps_length(s):
n = len(s)
# dp[i][j] stores the length of LPS from index i to j
dp = [[0 for _ in range(n)] for _ in range(n)]
# every sequence with 1 character is a palindrome of length 1
for i in range(n):
dp[i][i] = 1
# build the table in a bottom-up manner
for start_index in range(n - 1, -1, -1):
for end_index in range(start_index + 1, n):
if s[start_index] == s[end_index]:
dp[start_index][end_index] = 2 + dp[start_index + 1][end_index - 1]
else:
dp[start_index][end_index] = max(dp[start_index + 1][end_index],
dp[start_index][end_index - 1])
return dp[0][n - 1]
15.12. Longest Palindromic Subsequence
Ref: https://leetcode.com/problems/longest-palindromic-subsequence/description/
Code
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
# Create a 2D DP array initialized to 0
dp = [[0] * n for _ in range(n)]
# All substrings of length 1 are palindromes of length 1
for i in range(n):
dp[i][i] = 1
# Build the DP table
for length in range(2, n + 1): # Substring lengths from 2 to n
for i in range(n - length + 1):
j = i + length - 1
if s[i] == s[j]:
dp[i][j] = 2 + dp[i + 1][j - 1]
else:
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
return dp[0][n - 1]
15.13. Palindromic Substrings
Ref: https://leetcode.com/problems/palindromic-substrings/description/
Code
class Solution:
def countSubstrings(self, s: str) -> int:
n = len(s)
dp = [[False] * n for _ in range(n)]
count = 0
for end in range(n):
for start in range(end + 1):
if s[start] == s[end] and (end - start <= 1 or dp[start + 1][end - 1]):
dp[start][end] = True
count += 1
return count
15.14. Minimum Deletions in a String to make it a Palindrome
Ref: https://www.geeksforgeeks.org/minimum-number-deletions-make-string-palindrome/
Code
class Solution:
def minDeletionsToMakePalindrome(self, s: str) -> int:
n = len(s)
# dp[i][j] = length of longest palindromic subsequence in s[i..j]
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] = 1 # every single character is a palindrome of length 1
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
if s[i] == s[j]:
dp[i][j] = 2 + dp[i + 1][j - 1]
else:
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
lps_length = dp[0][n - 1]
return n - lps_length
15.15. Minimum insertions in a string to make it a palindrome
Ref: https://leetcode.com/problems/minimum-insertion-steps-to-make-a-string-palindrome/
Code
class Solution:
def minInsertions(self, s: str) -> int:
n = len(s)
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] = 1 # A single character is a palindrome of length 1
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
if s[i] == s[j]:
dp[i][j] = 2 + dp[i + 1][j - 1]
else:
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
lps = dp[0][n - 1]
return n - lps
15.16. Find if a string is K-Palindromic
Ref: https://leetcode.com/problems/valid-palindrome-iii/description/
Code
class Solution:
def isKPalindromic(self, s: str, k: int) -> bool:
n = len(s)
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] = 1 # single characters are palindromes
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
if s[i] == s[j]:
dp[i][j] = 2 + dp[i + 1][j - 1]
else:
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
lps = dp[0][n - 1]
min_deletions = n - lps
return min_deletions <= k
15.17. Palindrome Partitioning II
Ref: https://leetcode.com/problems/palindrome-partitioning-ii/description/
Code
class Solution:
def minCut(self, s: str) -> int:
n = len(s)
# pal[i][j] = True if s[i:j+1] is a palindrome
pal = [[False] * n for _ in range(n)]
for i in range(n):
pal[i][i] = True # single character is always a palindrome
# Fill pal table
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
if s[i] == s[j]:
if length == 2 or pal[i + 1][j - 1]:
pal[i][j] = True
# dp[i] = min cuts needed for s[0..i]
dp = [0] * n
for i in range(n):
if pal[0][i]:
dp[i] = 0
else:
dp[i] = float('inf')
for j in range(i):
if pal[j + 1][i]:
dp[i] = min(dp[i], dp[j] + 1)
return dp[n - 1]
Pattern DP-5: Longest Common Substring (find common substring between 2 string)
15.18. Longest Common Substring
Ref: https://www.geeksforgeeks.org/longest-common-substring-dp-29/
Code
class Solution:
def longestCommonSubstring(self, s1: str, s2: str) -> int:
m, n = len(s1), len(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
max_len = 0
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i - 1] == s2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
max_len = max(max_len, dp[i][j])
else:
dp[i][j] = 0
return max_len
15.19. Longest Common Subsequence
Ref: https://leetcode.com/problems/longest-common-subsequence/description/
Code
class Solution:
def longestCommonSubsequence(self, s1: str, s2: str) -> int:
m, n = len(s1), len(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i - 1] == s2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
return dp[m][n]
15.20. Minimum Deletions & Insertions to Transform a String into another
Ref: https://www.geeksforgeeks.org/problems/minimum-number-of-deletions-and-insertions0209/1
Code
class Solution:
def minInsertDelete(self, s1: str, s2: str) -> (int, int):
m, n = len(s1), len(s2)
# Step 1: Compute LCS
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i - 1] == s2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
lcs = dp[m][n]
deletions = m - lcs
insertions = n - lcs
return deletions, insertions
15.21. Longest Increasing Subsequence
Ref: https://leetcode.com/problems/longest-increasing-subsequence/
Code
from typing import List
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
n = len(nums)
if n == 0:
return 0
dp = [1] * n # every number is an LIS of length 1 by itself
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
15.22. Maximum Sum Increasing Subsequence
Ref: https://www.geeksforgeeks.org/maximum-sum-increasing-subsequence-dp-14/
Code
from typing import List
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
n = len(nums)
if n == 0:
return 0
dp = [1] * n
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
15.23. Shortest Common Supersequence
Ref: https://leetcode.com/problems/shortest-common-supersequence/description/
Code
class Solution:
def shortestCommonSupersequence(self, str1: str, str2: str) -> str:
m, n = len(str1), len(str2)
# Step 1: Compute LCS
dp = [[""] * (n + 1) for _ in range(m + 1)]
for i in range(m):
for j in range(n):
if str1[i] == str2[j]:
dp[i + 1][j + 1] = dp[i][j] + str1[i]
else:
dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j], key=len)
lcs = dp[m][n]
# Step 2: Merge based on LCS
res = []
i = j = 0
for c in lcs:
while i < m and str1[i] != c:
res.append(str1[i])
i += 1
while j < n and str2[j] != c:
res.append(str2[j])
j += 1
res.append(c)
i += 1
j += 1
# Append remaining characters
res.extend(str1[i:])
res.extend(str2[j:])
return ''.join(res)
15.24. Minimum number of deletions to make a sorted sequence
Ref: https://www.geeksforgeeks.org/minimum-number-deletions-make-sorted-sequence/
Code
from bisect import bisect_left
from typing import List
class Solution:
def minDeletionsToSort(self, nums: List[int]) -> int:
sub = [] # to hold the current LIS
for num in nums:
i = bisect_left(sub, num)
if i == len(sub):
sub.append(num)
else:
sub[i] = num
return len(nums) - len(sub)
15.25. Longest Repeating Subsequence
Ref: https://www.geeksforgeeks.org/longest-repeating-subsequence/
Code
class Solution:
def LongestRepeatingSubsequence(self, s: str) -> int:
n = len(s)
dp = [[0] * (n + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, n + 1):
if s[i - 1] == s[j - 1] and i != j:
dp[i][j] = 1 + dp[i - 1][j - 1]
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
return dp[n][n]
15.26. Find number of times a string occurs as a subsequence in given string
Ref: https://www.geeksforgeeks.org/find-number-times-string-occurs-given-string/
Code
class Solution:
def numDistinct(self, S: str, T: str) -> int:
m, n = len(S), len(T)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# An empty T is a subsequence of any prefix of S
for i in range(m + 1):
dp[i][0] = 1
for i in range(1, m + 1):
for j in range(1, n + 1):
if S[i - 1] == T[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
else:
dp[i][j] = dp[i - 1][j]
return dp[m][n]
15.27. Longest Bitonic Subsequence
Ref: https://www.geeksforgeeks.org/longest-bitonic-subsequence-dp-15/
Code
from typing import List
class Solution:
def LongestBitonicSubsequence(self, nums: List[int]) -> int:
n = len(nums)
# Step 1: Compute LIS ending at each index
inc = [1] * n
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
inc[i] = max(inc[i], inc[j] + 1)
# Step 2: Compute LDS starting at each index
dec = [1] * n
for i in range(n - 1, -1, -1):
for j in range(i + 1, n):
if nums[j] < nums[i]:
dec[i] = max(dec[i], dec[j] + 1)
# Step 3: Compute max of (inc[i] + dec[i] - 1)
max_len = 0
for i in range(n):
max_len = max(max_len, inc[i] + dec[i] - 1)
return max_len
15.28. Longest alternating subsequence
Ref: https://www.geeksforgeeks.org/longest-alternating-subsequence/
Code
from typing import List
class Solution:
def AlternatingMaxLength(self, nums: List[int]) -> int:
n = len(nums)
up = [1] * n
down = [1] * n
for i in range(1, n):
for j in range(i):
if nums[i] > nums[j]:
up[i] = max(up[i], down[j] + 1)
elif nums[i] < nums[j]:
down[i] = max(down[i], up[j] + 1)
return max(max(up), max(down))
15.29. Edit Distance
Ref: https://leetcode.com/problems/edit-distance/
Code
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# Initialize base cases
for i in range(m + 1):
dp[i][0] = i
for j in range(n + 1):
dp[0][j] = j
# Fill DP table
for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = 1 + min(
dp[i - 1][j], # delete
dp[i][j - 1], # insert
dp[i - 1][j - 1] # replace
)
return dp[m][n]
15.30. Interleaving String
Ref: https://leetcode.com/problems/interleaving-string/description/
Code
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
if len(s1) + len(s2) != len(s3):
return False
m, n = len(s1), len(s2)
dp = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True
for i in range(m + 1):
for j in range(n + 1):
if i > 0 and s1[i - 1] == s3[i + j - 1]:
dp[i][j] |= dp[i - 1][j]
if j > 0 and s2[j - 1] == s3[i + j - 1]:
dp[i][j] |= dp[i][j - 1]
return dp[m][n]
16. Pattern 16: Topological Sort
17. Pattern 17: Stacks
17.1. Next Greater Element I
Ref: https://leetcode.com/problems/next-greater-element-i/description/
Code
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
n = len(nums2)
result = [0] * n
stack = []
for i in range(n - 1, -1, -1):
while stack and stack[-1] <= nums2[i]:
stack.pop()
if stack:
result[i] = stack[-1]
else:
result[i] = -1
stack.append(nums2[i])
hashMapGreater = {}
for i in range(len(nums2)):
hashMapGreater[nums2[i]] = result[i]
return [hashMapGreater[num] for num in nums1]
17.2. Next Greater Element II
Ref: https://leetcode.com/problems/next-greater-element-ii/
Code
from typing import List
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
nums2 = nums + nums
n = len(nums2)
result = [0] * n
stack = []
for i in range(n - 1, -1, -1):
while stack and stack[-1] <= nums2[i]:
stack.pop()
if stack:
result[i] = stack[-1]
else:
result[i] = -1
stack.append(nums2[i])
return result[:len(nums)]
17.3. Next Greater Array
Code
def next_greater_elements(arr):
n = len(arr)
result = [None] * n
stack = []
for i in range(n - 1, -1, -1):
# Pop elements from the stack that are less than or equal to current element
while stack and stack[-1] <= arr[i]:
stack.pop()
# If stack is not empty, top is the next greater element
if stack:
result[i] = stack[-1]
else:
result[i] = None # Or use -1 if preferred
# Push current element onto stack
stack.append(arr[i])
return result
# Test input
arr = [13, 8, 1, 5, 2, 5, 9, 7, 6, 12]
# [None, 9, 5, 9, 5, 9, 12, 12, 12, None]
print(next_greater_elements(arr))
17.4. Previous Greater Array
Code
from typing import List
class Solution:
def previousGreaterElements(self, nums: List[int]) -> List[int]:
res = []
stack = [] # This will store elements in decreasing order
for num in nums:
while stack and stack[-1] <= num:
stack.pop()
if stack:
res.append(stack[-1])
else:
res.append(-1)
stack.append(num)
return res
17.5. Next Smaller Element
Code
from typing import List
def next_smaller_elements(arr: List[int]) -> List[int]:
result = [-1] * len(arr)
stack = []
for i in range(len(arr) - 1, -1, -1):
while stack and stack[-1] >= arr[i]:
stack.pop()
if stack:
result[i] = stack[-1]
stack.append(arr[i])
return result
# Example
arr = [13, 8, 1, 5, 2, 5, 9, 7, 6, 12]
print(next_smaller_elements(arr))
17.6. Previous Smaller Element
Code
from typing import List
def previous_smaller_elements(arr: List[int]) -> List[int]:
result = [-1] * len(arr)
stack = []
for i in range(len(arr)):
while stack and stack[-1] >= arr[i]:
stack.pop()
if stack:
result[i] = stack[-1]
stack.append(arr[i])
return result
# Example
arr = [13, 8, 1, 5, 2, 5, 9, 7, 6, 12]
print(previous_smaller_elements(arr))
17.7. Daily Temperatures
Ref: https://leetcode.com/problems/daily-temperatures/description/
Code
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
n = len(temperatures)
result = [0] * n
stack = []
for i in range(n - 1, -1, -1):
while stack and temperatures[stack[-1]] <= temperatures[i]:
stack.pop()
if stack:
result[i] = stack[-1] - i
else:
result[i] = 0
stack.append(i)
return result
17.8. Largest Rectangle in Histogram
Ref: https://leetcode.com/problems/largest-rectangle-in-histogram/description/
Code
from typing import List
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
prev_smaller = [-1] * n
next_smaller = [n] * n
# Compute Previous Smaller Element (PSE)
stack = []
for i in range(n):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
if stack:
prev_smaller[i] = stack[-1]
stack.append(i)
# Compute Next Smaller Element (NSE)
stack = []
for i in range(n - 1, -1, -1):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
if stack:
next_smaller[i] = stack[-1]
stack.append(i)
# Compute max area
max_area = 0
for i in range(n):
height = heights[i]
width = next_smaller[i] - prev_smaller[i] - 1
area = height * width
max_area = max(max_area, area)
return max_area
17.9. Online Stock Span
Ref: https://leetcode.com/problems/online-stock-span/description/
Code
class StockSpanner:
def __init__(self):
self.prices = [] # Stores all prices seen so far
def next(self, price: int) -> int:
self.prices.append(price)
res = []
stack = [] # This will store elements in decreasing order
for i in range(len(self.prices)):
while stack and self.prices[stack[-1]] <= self.prices[i]:
stack.pop()
if stack:
res.append(stack[-1])
else:
res.append(-1)
stack.append(i)
# Get the previous greater index for the latest price
prev_greater_index = res[-1]
current_index = len(self.prices) - 1
if prev_greater_index == -1:
span = current_index + 1
else:
span = current_index - prev_greater_index
return span
# Your StockSpanner object will be instantiated and called as such:
# obj = StockSpanner()
# param_1 = obj.next(price)
17.20. 132 Pattern
Ref: https://leetcode.com/problems/132-pattern/description/
Code
from typing import List
class Solution:
def find132pattern(self, nums: List[int]) -> bool:
stack = [] # This will store potential "2"s (nums[k]) in decreasing order
third = float('-inf') # This keeps track of the "2" in the 132 pattern
# Traverse from right to left
# Next greater element
# Next
for num in reversed(nums):
if num < third:
# Found a 132 pattern
return True
# Greater
while stack and stack[-1] < num:
# Pop all smaller elements and update the "2" (third)
third = stack.pop()
stack.append(num)
return False
17.21. Trapping Rain Water
Ref: https://leetcode.com/problems/trapping-rain-water/description/
Code
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
stack = []
water = 0
for i in range(len(height)):
while stack and height[stack[-1]] <= height[i]:
top = stack.pop()
if not stack:
break # No left boundary to trap water
# Distance (width) between the left and right boundaries
distance = i - stack[-1] - 1
# Height of water above the current bar
# Area of 2 rectangle - Area of bottom valley
bounded_height = min(height[stack[-1]], height[i]) - height[top]
# Add trapped water
water += distance * bounded_height
stack.append(i)
return water
17.28. Number of Visible People in a Queue (Remove the height when needed)
Ref: https://leetcode.com/problems/number-of-visible-people-in-a-queue/description/
Code
class Solution:
def canSeePersonsCount(self, heights: List[int]) -> List[int]:
n = len(heights)
res = [0] * n
stack = []
# Next greater element
for i in range(n - 1, -1, -1):
count = 0
while stack and stack[-1] < heights[i]:
stack.pop()
count += 1
if stack:
count += 1
res[i] = count
stack.append(heights[i])
return res
18. Pattern 18: Monotonic Stack
19. Pattern 19: Graphs
19.1. Network Delay Time (Dijkstra Algorithm)
Ref: https://leetcode.com/problems/network-delay-time/description/
Code
import heapq
from collections import defaultdict
class Solution:
def networkDelayTime(self, times, n, k):
# Build adjacency list
graph = defaultdict(list)
for u, v, w in times:
graph[u].append((v, w))
# Dijkstra Min-heap to track the shortest path: (current_cost, current_node)
heap = [(0, k)]
visited = {}
while heap:
time, node = heapq.heappop(heap)
# Skip if already visited
if node in visited:
continue
visited[node] = time
for neighbor, weight in graph[node]:
if neighbor not in visited:
heapq.heappush(heap, (time + weight, neighbor))
# k = 2, value: {2: 0, 1: 1, 3: 1, 4: 2}
print(visited)
# If all nodes are visited, return the max time; else, -1
return max(visited.values()) if len(visited) == n else -1
19.2. Paths in Maze That Lead to Same Room (Find All Paths)
Ref: https://leetcode.com/problems/paths-in-maze-that-lead-to-same-room/description/
Code
from collections import defaultdict
class Solution:
def find_all_paths(self, graph, start):
stack = [(start, [start])]
room_paths = defaultdict(list)
while stack:
node, path = stack.pop()
room_paths[node].append(path)
for neighbor in graph.get(node, []):
if neighbor not in path: # avoid cycles
stack.append((neighbor, path + [neighbor]))
# {'A': [['A']], 'C': [['A', 'C']], 'D': [['A', 'C', 'D'], ['A', 'B', 'D']], 'B': [['A', 'B']]}
print(room_paths)
# Filter rooms that have more than one path
multiple_path_rooms = {
room: paths for room, paths in room_paths.items()
if len(paths) > 1
}
return multiple_path_rooms
# Example usage
graph = {
'A': ['B', 'C'],
'B': ['D'],
'C': ['D'],
'D': []
}
result = find_all_paths(graph, 'A')
for room, paths in result.items():
print(f"Room {room} has {len(paths)} paths:")
for p in paths:
print(" -> " + " -> ".join(p))
19.3. Clone Graph (DFS)
Ref: https://leetcode.com/problems/clone-graph/description/
Code
"""
# Definition for a Node.
class Node:
def __init__(self, val = 0, neighbors = None):
self.val = val
self.neighbors = neighbors if neighbors is not None else []
"""
from typing import Optional
class Solution:
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
if not node:
return None
visited = {} # Dictionary to store cloned nodes
def dfs(current: 'Node') -> 'Node':
if current in visited:
return visited[current]
# Clone the current node
copy = Node(current.val)
visited[current] = copy # Mark as visited (cloned)
# Visit all neighbors
for neighbor in current.neighbors:
copy.neighbors.append(dfs(neighbor)) # Recursively clone neighbors
return copy
return dfs(node)
Union Find
19.4. Number of Provinces (DFS Island, Union Find)
Ref: https://leetcode.com/problems/number-of-provinces/description/
Code
class Solution:
def findCircleNum(self, isConnected: List[List[int]]) -> int:
def dfs(city: int):
visited[city] = True
for neighbor in range(len(isConnected)):
# Explore the graph when needed
if isConnected[city][neighbor] == 1 and not visited[neighbor]:
dfs(neighbor)
n = len(isConnected)
visited = [False] * n
provinces = 0
for i in range(n):
if not visited[i]:
dfs(i)
provinces += 1
return provinces
from typing import List
class Solution:
def findCircleNum(self, isConnected: List[List[int]]) -> int:
n = len(isConnected)
parent = list(range(n)) # Initially, each node is its own parent
def find(x):
if parent[x] != x:
parent[x] = find(parent[x]) # Path compression
return parent[x]
def union(x, y):
rootX = find(x)
rootY = find(y)
if rootX != rootY:
parent[rootX] = rootY # Union
# Iterate through the upper triangle of the matrix to avoid duplicates
for i in range(n):
for j in range(i + 1, n):
if isConnected[i][j] == 1:
union(i, j)
# Count unique roots
return len({find(i) for i in range(n)})
19.5. Redundant Connection (Union Find)
Ref: https://leetcode.com/problems/redundant-connection/description/
Code
from typing import List
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
parent = [i for i in range(len(edges) + 1)]
def find(x):
if parent[x] != x:
parent[x] = find(parent[x]) # Path compression
return parent[x]
def union(x, y):
rootX = find(x)
rootY = find(y)
if rootX == rootY:
return False # Cycle detected
parent[rootY] = rootX
return True
for u, v in edges:
if not union(u, v):
return [u, v]
19.6. Most Stones Removed with Same Row or Column (Union Find)
Ref: https://leetcode.com/problems/most-stones-removed-with-same-row-or-column/description/
Code
class Solution:
def removeStones(self, stones):
parent = {}
def find(x):
if x not in parent:
parent[x] = x # Initialize parent if x is new
if parent[x] != x:
parent[x] = find(parent[x]) # Path compression
return parent[x]
def union(x, y):
parent[find(x)] = find(y)
for x, y in stones:
row = f"r{x}"
col = f"c{y}"
union(row, col)
# {'c0': 'c1', 'r0': 'c0', 'c1': 'c2', 'r1': 'c1', 'c2': 'c2', 'r2': 'c2'}
print(parent)
unique_groups = {find(x) for x in parent}
# {'c2'}
# For every node in parent, we find its ultimate root using find(x. Then collect all unique roots
print(unique_groups)
return len(stones) - len(unique_groups)
19.7. Number of Operations to Make Network Connected (Union Find)
Ref: https://leetcode.com/problems/number-of-operations-to-make-network-connected/description/
Code
from typing import List
class Solution:
def makeConnected(self, n: int, connections: List[List[int]]) -> int:
if len(connections) < n - 1:
return -1 # Not enough cables
parent = [i for i in range(n)]
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
rootX = find(x)
rootY = find(y)
if rootX != rootY:
parent[rootX] = rootY
for a, b in connections:
union(a, b)
# Count number of connected components
components = len(set(find(i) for i in range(n)))
return components - 1
19.8. Satisfiability of Equality Equations
Ref: https://leetcode.com/problems/satisfiability-of-equality-equations/description/
Code
from typing import List
class Solution:
def equationsPossible(self, equations: List[str]) -> bool:
parent = [i for i in range(26)] # one for each lowercase letter
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
parent[find(x)] = find(y)
# First, process all '==' equations
for eq in equations:
if eq[1:3] == '==':
x = ord(eq[0]) - ord('a')
y = ord(eq[3]) - ord('a')
union(x, y)
# Then, process all '!=' equations
for eq in equations:
if eq[1:3] == '!=':
x = ord(eq[0]) - ord('a')
y = ord(eq[3]) - ord('a')
if find(x) == find(y):
return False
return True
19.9. Accounts Merge
Ref: https://leetcode.com/problems/accounts-merge/description/
Code
from typing import List
from collections import defaultdict
class Solution:
def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]:
parent = {}
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
parent.setdefault(x, x)
parent.setdefault(y, y)
parent[find(x)] = find(y)
email_to_name = {}
# Union all emails under the same person
for account in accounts:
name = account[0]
first_email = account[1]
for email in account[1:]:
email_to_name[email] = name
union(first_email, email)
# Merge emails by their root parent
merged = defaultdict(list)
for email in parent:
root = find(email)
merged[root].append(email)
# Build final result
result = []
for root, emails in merged.items():
# Because all email have the same root
name = email_to_name[root]
result.append([name] + sorted(emails))
return result
DFS
19.10. Surrounded Regions
Ref: https://leetcode.com/problems/surrounded-regions/
Code
class Solution:
def solve(self, board):
if not board or not board[0]:
return
rows, cols = len(board), len(board[0])
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or board[r][c] != 'O':
return
board[r][c] = '#' # temporary mark
dfs(r + 1, c)
dfs(r - 1, c)
dfs(r, c + 1)
dfs(r, c - 1)
# Step 1: Mark border-connected 'O's
for i in range(rows):
dfs(i, 0)
dfs(i, cols - 1)
for j in range(cols):
dfs(0, j)
dfs(rows - 1, j)
# Step 2: Flip internal 'O' to 'X', and '#' back to 'O'
for i in range(rows):
for j in range(cols):
if board[i][j] == 'O':
board[i][j] = 'X'
elif board[i][j] == '#':
board[i][j] = 'O'
19.11. Number of Enclaves
Ref: https://leetcode.com/problems/number-of-enclaves/description/
Code
class Solution:
def numEnclaves(self, grid: List[List[int]]) -> int:
if not grid or not grid[0]:
return 0
rows, cols = len(grid), len(grid[0])
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] != 1:
return
grid[r][c] = 2 # temporary mark
dfs(r + 1, c)
dfs(r - 1, c)
dfs(r, c + 1)
dfs(r, c - 1)
# Step 1: Mark border-connected 'O's
for i in range(rows):
dfs(i, 0)
dfs(i, cols - 1)
for j in range(cols):
dfs(0, j)
dfs(rows - 1, j)
count = 0
# Step 2: Flip internal 'O' to 'X', and '#' back to 'O'
for i in range(rows):
for j in range(cols):
if grid[i][j] == 1:
count += 1
return count
Time taken to reach all nodes or share information to all graph nodes
19.12. Time Needed to Inform All Employees
Ref: https://leetcode.com/problems/time-needed-to-inform-all-employees/description/
Code
class Solution:
# Employee: 0 1 2 3 4 5 6
# Manager: -1 0 0 1 1 2 2
# 0
# / \
# 1 2
#. / \ / \
# 3 4 5 6
def numOfMinutes(self, n: int, headID: int, manager: List[int], informTime: List[int]) -> int:
tree = defaultdict(list)
for emp, mgr in enumerate(manager):
if mgr != -1:
tree[mgr].append(emp)
def dfs(emp_id):
max_time = 0
for subordinate in tree[emp_id]:
max_time = max(max_time, dfs(subordinate))
return informTime[emp_id] + max_time
return dfs(headID)
19.13. Number of Closed Islands
Ref: http://leetcode.com/problems/number-of-closed-islands/description/
Code
class Solution:
def closedIsland(self, grid: List[List[int]]) -> int:
rows, cols = len(grid), len(grid[0])
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == 1:
return
grid[r][c] = 1 # mark as visited
for dr, dc in [(-1,0), (1,0), (0,-1), (0,1)]:
dfs(r + dr, c + dc)
# Step 1: Remove lands connected to borders
# Reason: Change all 0 in the border to 1
for i in range(rows):
dfs(i, 0)
dfs(i, cols - 1)
for j in range(cols):
dfs(0, j)
dfs(rows - 1, j)
# Step 2: Count closed islands
count = 0
for i in range(1, rows - 1):
for j in range(1, cols - 1):
# It must be an isolation and count += 1
if grid[i][j] == 0:
dfs(i, j)
count += 1
return count
19.14. Number of Islands
Ref: https://leetcode.com/problems/number-of-islands/description/
Code
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
# Flip 1 to 0
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == '0':
return
grid[r][c] = '0' # mark as visited
for dr, dc in [(-1,0), (1,0), (0,-1), (0,1)]:
dfs(r + dr, c + dc)
# Find 1 and flip to 0
count = 0
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
dfs(i, j)
count += 1
return count
19.15. Keys and Rooms
Ref: https://leetcode.com/problems/keys-and-rooms/description/
Code
class Solution:
def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
visited = set()
def dfs(room: int):
if room in visited:
return
visited.add(room)
for key in rooms[room]:
dfs(key)
dfs(0) # Start from room 0
return len(visited) == len(rooms)
19.16. Max Area of Island
Ref: https://leetcode.com/problems/max-area-of-island/description/
Code
from typing import List
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
maxArea = 0
# Flip 1 to 0
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == 0:
return 0
grid[r][c] = 0 # mark as visited
# When first DFS, count it
area = 1
for dr, dc in [(-1,0), (1,0), (0,-1), (0,1)]:
area += dfs(r + dr, c + dc)
# Area each time count DFS
return area
for i in range(rows):
for j in range(cols):
if grid[i][j] == 1:
maxArea = max(maxArea, dfs(i, j))
return maxArea
19.17. Flood Fill
Ref: https://leetcode.com/problems/flood-fill/description/
Code
class Solution:
def floodFill(self, image: List[List[int]], sr: int, sc: int, color: int) -> List[List[int]]:
rows, cols = len(image), len(image[0])
original_color = image[sr][sc]
if original_color == color:
return image # no need to fill if color is same
# Change color to original_color, similar idea flip 1 to 0 and count
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or image[r][c] != original_color:
return
image[r][c] = color # mark as visited
for dr, dc in [(-1,0), (1,0), (0,-1), (0,1)]:
dfs(r + dr, c + dc)
# Step 2: DFS here
dfs(sr, sc)
return image
Graph Cycle
19.18. Find Eventual Safe States (Cycle)
Ref: https://leetcode.com/problems/find-eventual-safe-states/description/
Code
class Solution:
def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]:
# Still run 0 -> n
n = len(graph)
state = [0] * n
def dfs(node):
if state[node] != 0:
return state[node] == 2 # safe if previously marked safe
state[node] = 1 # mark as visiting
for neighbor in graph[node]:
if not dfs(neighbor): # if any neighbor is unsafe
return False
state[node] = 2 # mark as safe
return True
return [i for i in range(n) if dfs(i)]
19.19. Coloring A Border (Edge Case, color the frame only in border)
Ref: https://leetcode.com/problems/coloring-a-border/description/
Code
from typing import List
class Solution:
def colorBorder(self, grid: List[List[int]], row: int, col: int, color: int) -> List[List[int]]:
rows, cols = len(grid), len(grid[0])
original_color = grid[row][col]
visited = [[False] * cols for _ in range(rows)]
borders = []
def dfs(r, c):
visited[r][c] = True
is_border = False
for dr, dc in [(-1,0), (1,0), (0,-1), (0,1)]:
nr, nc = r + dr, c + dc
# Logic check border, and we only want to color original_color
if nr < 0 or nr >= rows or nc < 0 or nc >= cols or grid[nr][nc] != original_color:
is_border = True
elif not visited[nr][nc]:
dfs(nr, nc)
if is_border:
borders.append((r, c))
dfs(row, col)
for r, c in borders:
grid[r][c] = color
return grid
19.20. Find the Town Judge
Ref: https://leetcode.com/problems/find-the-town-judge/description/
Code
from typing import List
from collections import defaultdict
class Solution:
def findJudge(self, n: int, trust: List[List[int]]) -> int:
graph = defaultdict(list)
out_degree = [0] * (n + 1)
in_degree = [0] * (n + 1)
for a, b in trust:
graph[a].append(b)
out_degree[a] += 1
in_degree[b] += 1
# def dfs(person, visited):
# visited.add(person)
# for neighbor in graph[person]:
# if neighbor not in visited:
# dfs(neighbor, visited)
for i in range(1, n + 1):
if out_degree[i] == 0 and in_degree[i] == n - 1:
return i
return -1
19.21. Employee Importance
Ref: https://leetcode.com/problems/employee-importance/description/
Code
"""
# Definition for Employee.
class Employee:
def __init__(self, id: int, importance: int, subordinates: List[int]):
self.id = id
self.importance = importance
self.subordinates = subordinates
"""
class Solution:
def getImportance(self, employees: List['Employee'], id: int) -> int:
# Create a mapping from id to employee object for quick lookup
emp_map = {employee.id: employee for employee in employees}
def dfs(emp_id: int) -> int:
employee = emp_map[emp_id]
total_importance = employee.importance
for sub_id in employee.subordinates:
total_importance += dfs(sub_id)
return total_importance
return dfs(id)
BFS - Shortest Path
19.22. 01 Matrix
Ref: https://leetcode.com/problems/01-matrix/description/
Code
class Solution:
def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]:
rows, cols = len(mat), len(mat[0])
q = deque()
# Initialize
for r in range(rows):
for c in range(cols):
if mat[r][c] == 0:
q.append((r, c))
else:
mat[r][c] = float('inf')
# BFS
while q:
r, c = q.popleft()
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:
# Update the shortest distance from r,c to nr,nc
if mat[nr][nc] > mat[r][c] + 1:
mat[nr][nc] = mat[r][c] + 1
q.append((nr, nc))
return mat
19.22. As Far from Land as Possible (Largest Distance from A to B)
Ref: https://leetcode.com/problems/as-far-from-land-as-possible/
Code
from collections import deque
from typing import List
class Solution:
# After BFS ends, distance holds the maximum distance a water cell is from the nearest land.
def maxDistance(self, grid: List[List[int]]) -> int:
n = len(grid)
q = deque()
# Step 1: Add all land cells to the queue
for i in range(n):
for j in range(n):
if grid[i][j] == 1:
q.append((i, j))
# If there is no land or no water, return -1
if not q or len(q) == n * n:
return -1
# Step 2: BFS from all land cells simultaneously
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
distance = -1
# IDEA: Start from land to the deepest water
while q:
distance += 1
for _ in range(len(q)):
x, y = q.popleft()
for dx, dy in directions:
nx, ny = x + dx, y + dy
# If it's a water cell, mark it visited and add to queue
if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] == 0:
grid[nx][ny] = 2 # Mark as visited
q.append((nx, ny))
return distance
19.33. Rotting Oranges (BFS Classic)
Ref: https://leetcode.com/problems/rotting-oranges/description/
Code
from collections import deque
from typing import List
class Solution:
def orangesRotting(self, grid: List[List[int]]) -> int:
rows, cols = len(grid), len(grid[0])
queue = deque()
fresh_oranges = 0
# Step 1: Add all rotten oranges to the queue and count fresh ones
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2:
queue.append((r, c, 0)) # (row, col, minutes)
elif grid[r][c] == 1:
fresh_oranges += 1
# Step 2: BFS to rot adjacent fresh oranges
directions = [(-1,0), (1,0), (0,-1), (0,1)] # up, down, left, right
minutes = 0
while queue:
r, c, mins = queue.popleft()
minutes = max(minutes, mins)
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
grid[nr][nc] = 2 # Rot the orange
fresh_oranges -= 1
queue.append((nr, nc, mins + 1))
return minutes if fresh_oranges == 0 else -1
19.34. Shortest Path in Binary Matrix (8 directions)
Ref: https://leetcode.com/problems/shortest-path-in-binary-matrix/
Code
from collections import deque
from typing import List
class Solution:
def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
# Check if the starting or ending cell is blocked
if grid[0][0] == 1 or grid[-1][-1] == 1:
return -1
# Directions for 8 possible moves (right, left, down, up, diagonals)
directions = [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (-1, 1), (1, -1), (1, 1)]
# BFS setup: queue stores (x, y, distance)
queue = deque([(0, 0, 1)]) # Start from the top-left corner, with distance 1
n = len(grid) # Size of the grid
# Mark the starting cell as visited
grid[0][0] = 1
while queue:
x, y, dist = queue.popleft()
# If we reach the bottom-right corner, return the distance
if x == n - 1 and y == n - 1:
return dist
# Explore all 8 directions
for dx, dy in directions:
nx, ny = x + dx, y + dy
# Check if the new position is within bounds and not visited
if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] == 0:
# Mark the cell as visited
grid[nx][ny] = 1
# Add the new position to the queue with the incremented distance
queue.append((nx, ny, dist + 1))
# If we finish BFS without reaching the bottom-right corner, return -1
return -1
Graph coloring/Bipartition
19.35. Graph Bipartite
Ref: https://leetcode.com/problems/is-graph-bipartite/description/
Code
from typing import List
from collections import deque
class Solution:
def isBipartite(self, graph: List[List[int]]) -> bool:
n = len(graph)
color = [-1] * n # -1 means unvisited; 0 and 1 are two colors
for start in range(n):
if color[start] == -1:
queue = deque([start])
color[start] = 0
while queue:
node = queue.popleft()
for neighbor in graph[node]:
if color[neighbor] == -1:
color[neighbor] = 1 - color[node] # alternate color
queue.append(neighbor)
elif color[neighbor] == color[node]:
return False # same color on both sides means not bipartite
return True
19.36. Possible Bipartition
Ref: https://leetcode.com/problems/possible-bipartition/description/
Code
class Solution:
def possibleBipartition(self, n: int, dislikes: List[List[int]]) -> bool:
graph = defaultdict(list)
for a, b in dislikes:
graph[a].append(b)
graph[b].append(a)
color = {}
for node in range(1, n+1):
if node not in color:
queue = deque([node])
color[node] = 0
while queue:
curr = queue.popleft()
for neighbor in graph[curr]:
if neighbor not in color:
color[neighbor] = 1 - color[curr]
queue.append(neighbor)
elif color[neighbor] == color[curr]:
return False
return True
Topology Sort (Kahn Algorithm)
19.37. Course Schedule
Ref: https://leetcode.com/problems/course-schedule/description/
Code
from collections import defaultdict, deque
from typing import List
# BFS Topology Sort)
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# Build the graph and in-degree array
graph = defaultdict(list)
in_degree = [0] * numCourses
for dest, src in prerequisites:
graph[src].append(dest)
in_degree[dest] += 1
# Initialize queue with nodes having zero in-degree (no prerequisites)
queue = deque([i for i in range(numCourses) if in_degree[i] == 0])
completed_courses = 0
result = []
while queue:
course = queue.popleft()
result.append(course)
completed_courses += 1
for neighbor in graph[course]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
# [0, 1]
print("Result: ", result)
return completed_courses == numCourses
19.37. Course Schedule II
Ref: https://leetcode.com/problems/course-schedule-ii/
Code
class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
# Build the graph and in-degree array
graph = defaultdict(list)
in_degree = [0] * numCourses
for dest, src in prerequisites:
graph[src].append(dest)
in_degree[dest] += 1
# Initialize queue with nodes having zero in-degree (no prerequisites)
queue = deque([i for i in range(numCourses) if in_degree[i] == 0])
completed_courses = 0
result = []
while queue:
course = queue.popleft()
result.append(course)
completed_courses += 1
for neighbor in graph[course]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
return result if completed_courses == numCourses else []
Find Shortest Path in Weighted Graph (Dijkstra’s/Bellman Ford)
About no-directed graph, we use BFS
19.38. Network Delay Time (Dijkstra - Find 1 to 1)
Ref: https://leetcode.com/problems/network-delay-time/description/
Code
from typing import List
import heapq
from collections import defaultdict
class Solution:
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
# Build the adjacency list
graph = defaultdict(list)
for u, v, w in times:
graph[u].append((v, w))
# Min-heap to get the next closest node
min_heap = [(0, k)] # (time, node)
visited = set()
time_to_reach = {}
while min_heap:
time, node = heapq.heappop(min_heap)
if node in visited:
continue
visited.add(node)
time_to_reach[node] = time
for neighbor, weight in graph[node]:
if neighbor not in visited:
heapq.heappush(min_heap, (time + weight, neighbor))
# {2: 0, 1: 1, 3: 1, 4: 2}
print(time_to_reach)
return max(time_to_reach.values()) if len(visited) == n else -1
19.38. Find the City With the Smallest Number of Neighbors at a Threshold Distance (Floyd-Warshall - Find N to N)
Code
from typing import List
class Solution:
def findTheCity(self, n: int, edges: List[List[int]], distanceThreshold: int) -> int:
# Step 1: Initialize distance matrix
dist = [[float('inf')] * n for _ in range(n)]
for i in range(n):
dist[i][i] = 0
for u, v, w in edges:
dist[u][v] = w
dist[v][u] = w # Because the graph is undirected
# Step 2: Floyd-Warshall to compute all-pairs shortest paths
for k in range(n):
for i in range(n):
for j in range(n):
if dist[i][j] > dist[i][k] + dist[k][j]:
dist[i][j] = dist[i][k] + dist[k][j]
# Step 3: Count reachable cities within distanceThreshold
min_reachable = n
city_index = -1
for i in range(n):
# Number of nodes can reachable
count = sum(1 for j in range(n) if i != j and dist[i][j] <= distanceThreshold)
if count <= min_reachable:
min_reachable = count
city_index = i # Prefer the city with the greatest number in case of tie
return city_index
19.39. Cheapest Flights Within K Stops (Bellman-Ford - Snapshot in Kth)
Ref: https://leetcode.com/problems/cheapest-flights-within-k-stops/
Code
from typing import List
class Solution:
def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
# Step 1: Initialize distances array with "infinite" cost
prices = [float('inf')] * n
prices[src] = 0
# Step 2: Run the Bellman-Ford algorithm for k+1 times
for i in range(k + 1):
tmp = prices.copy()
for u, v, w in flights:
if prices[u] == float('inf'):
continue
if prices[u] + w < tmp[v]:
tmp[v] = prices[u] + w
prices = tmp
return -1 if prices[dst] == float('inf') else prices[dst]
Prim Algorithm
19.40. Connecting Cities With Minimum Cost (Prim)
Ref: https://leetcode.com/problems/connecting-cities-with-minimum-cost/description/
Code
from typing import List
class Solution:
def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
if n == 0:
return 0
# Step 1: Build graph
graph = defaultdict(list)
for city1, city2, cost in connections:
graph[city1].append((cost, city2))
graph[city2].append((cost, city1))
visited = set()
min_heap = [(0, 1)] # (cost, starting_city)
total_cost = 0
while min_heap and len(visited) < n:
cost, city = heapq.heappop(min_heap)
if city in visited:
continue
visited.add(city)
total_cost += cost
for nei_cost, nei_city in graph[city]:
if nei_city not in visited:
heapq.heappush(min_heap, (nei_cost, nei_city))
return total_cost if len(visited) == n else -1
Strongly Connected Components
19.41. Strongly Connected Components: Tarjan’s Algorithm
Ref: https://leetcode.com/problems/critical-connections-in-a-network/
Code
from typing import List
from collections import defaultdict
class Solution:
def criticalConnections(self, n: int, connections: List[List[int]]) -> List[List[int]]:
graph = defaultdict(list)
for u, v in connections:
graph[u].append(v)
graph[v].append(u)
discovery = [-1] * n
low = [-1] * n
time = [0]
result = []
def dfs(node, parent):
discovery[node] = low[node] = time[0]
time[0] += 1
for neighbor in graph[node]:
if neighbor == parent:
continue
if discovery[neighbor] == -1:
dfs(neighbor, node)
low[node] = min(low[node], low[neighbor])
if low[neighbor] > discovery[node]:
result.append([node, neighbor])
else:
low[node] = min(low[node], discovery[neighbor])
dfs(0, -1)
return result
A* Search
19.42. Sliding puzzle (Game Backtracking)
Ref: https://leetcode.com/problems/sliding-puzzle/
Code
from typing import List
from collections import deque
class Solution:
def slidingPuzzle(self, board: List[List[int]]) -> int:
start = ''.join(str(cell) for row in board for cell in row)
target = '123450'
# Mapping of index to its neighbors on a 2x3 board
neighbors = {
0: [1, 3],
1: [0, 2, 4],
2: [1, 5],
3: [0, 4],
4: [1, 3, 5],
5: [2, 4]
}
visited = set()
queue = deque([(start, 0)])
visited.add(start)
while queue:
state, steps = queue.popleft()
if state == target:
return steps
zero_idx = state.index('0')
for neighbor in neighbors[zero_idx]:
new_state = list(state)
# Swap '0' with the neighbor
new_state[zero_idx], new_state[neighbor] = new_state[neighbor], new_state[zero_idx]
new_state_str = ''.join(new_state)
if new_state_str not in visited:
visited.add(new_state_str)
queue.append((new_state_str, steps + 1))
return -1
19.43. Find if Path Exists in Graph
Ref: https://leetcode.com/problems/find-if-path-exists-in-graph/description/
Code
class Solution:
def validPath(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool:
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
visited = [False] * n
result = []
def dfs(node, target, path = []):
if node == target:
result.append(path[:])
return
visited[node] = True # mark as visiting
path.append(node)
for neighbor in graph[node]:
if not visited[neighbor]:
dfs(neighbor, target, path)
# Backtracking
# Do not set visited[node] = False for do not repeated visited
path.pop()
dfs(source, destination)
return len(result) > 0
19.44. Steps by Knight (BFS with 8 direction)
Ref: https://www.geeksforgeeks.org/problems/steps-by-knight5927/1
Code
from collections import deque
class Solution:
def minStepToReachTarget(self, knightPos, targetPos, n):
# All 8 possible movements for a knight in chess
directions = [
(-2, -1), (-1, -2), (1, -2), (2, -1),
(2, 1), (1, 2), (-1, 2), (-2, 1)
]
# Convert to 0-indexed positions
knightPos = (knightPos[0] - 1, knightPos[1] - 1)
targetPos = (targetPos[0] - 1, targetPos[1] - 1)
# If starting position is the same as target
if knightPos == targetPos:
return 0
visited = [[False for _ in range(n)] for _ in range(n)]
queue = deque()
queue.append((knightPos[0], knightPos[1], 0)) # (x, y, steps)
visited[knightPos[0]][knightPos[1]] = True
while queue:
x, y, steps = queue.popleft()
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < n and 0 <= ny < n and not visited[nx][ny]:
if (nx, ny) == targetPos:
return steps + 1
visited[nx][ny] = True
queue.append((nx, ny, steps + 1))
return -1 # If unreachable (shouldn't happen on an open board)
19.45. Reorder Routes to Make All Paths Lead to the City Zero (Idea đi từ 0 đến các thành phố khác, nào đi không được thì change + 1)
Ref: https://leetcode.com/problems/reorder-routes-to-make-all-paths-lead-to-the-city-zero/description/
Code
from collections import defaultdict, deque
from typing import List
class Solution:
def minReorder(self, n: int, connections: List[List[int]]) -> int:
# Build the graph with direction info
graph = defaultdict(list)
for u, v in connections:
graph[u].append((v, 1)) # original direction
graph[v].append((u, 0)) # reverse direction
visited = set()
queue = deque([0])
visited.add(0)
changes = 0
while queue:
current = queue.popleft()
for neighbor, direction in graph[current]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
changes += direction # increment if we need to reverse this edge
return changes
19.46. Detect cycle in an undirected graph
Code
from collections import defaultdict
class Solution:
def hasCycle(self, n: int, edges: list[list[int]]) -> bool:
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
graph[v].append(u) # undirected
visited = [False] * n
def dfs(node, parent):
visited[node] = True
for neighbor in graph[node]:
if not visited[neighbor]:
if dfs(neighbor, node):
return True
elif neighbor != parent:
return True # found a back edge (cycle)
return False
for i in range(n):
if not visited[i]:
if dfs(i, -1):
return True
return False
19.47. Detect cycle in an directed graph
Code
from collections import defaultdict
class Solution:
def hasCycle(self, n: int, edges: list[list[int]]) -> bool:
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v) # directed edge
visited = [False] * n
rec_stack = [False] * n # recursion stack
def dfs(node):
visited[node] = True
rec_stack[node] = True
for neighbor in graph[node]:
if not visited[neighbor]:
if dfs(neighbor):
return True
elif rec_stack[neighbor]:
return True # cycle detected
rec_stack[node] = False
return False
for i in range(n):
if not visited[i]:
if dfs(i):
return True
return False
19.48. Eventual Safe States
Ref: https://www.geeksforgeeks.org/problems/eventual-safe-states/1
Code
from typing import List
class Solution:
def eventualSafeNodes(self, V : int, adj : List[List[int]]) -> List[int]:
state = [0] * V # 0 = unvisited, 1 = visiting, 2 = safe
def dfs(node):
if state[node] == 1:
return False # cycle detected
if state[node] == 2:
return True # already determined safe
state[node] = 1 # mark as visiting
for neighbor in adj[node]:
if not dfs(neighbor):
return False
state[node] = 2 # mark as safe
return True
result = []
for i in range(V):
if dfs(i):
result.append(i)
return result
For example:
-
So the unsafe nodes are: 0, 1, 3
-
And the safe nodes are: 2, 4, 5, 6
19.49. Longest Cycle in a Graph
Ref: https://leetcode.com/problems/longest-cycle-in-a-graph/description/
Code
from typing import List
class Solution:
def longestCycle(self, edges: List[int]) -> int:
state = [0] * len(edges) # 0 = unvisited, 1 = visiting, 2 = safe
max_cycle_length = -1
def dfs(node, path):
if state[node] == 1: # cycle detected
cycle_start = node
cycle_length = 0
for i in range(len(path) - 1, -1, -1):
cycle_length += 1
if path[i] == cycle_start:
break
return cycle_length
if state[node] == 2: # already safe
return 0
state[node] = 1 # mark as visiting
path.append(node)
next_node = edges[node]
cycle_length = 0
# Loop dfs from a node until meet cycle
if next_node != -1: # valid edge
cycle_length = dfs(next_node, path)
state[node] = 2 # mark as safe
path.pop()
return cycle_length
for i in range(len(edges)):
if state[i] == 0: # unvisited node
cycle_length = dfs(i, [])
max_cycle_length = max(max_cycle_length, cycle_length)
return max_cycle_length if max_cycle_length > 0 else -1
19.50. Largest Color Value in a Directed Graph (Topology Sort): Can not generate all paths because it will cause out of memory if we have a loop
Ref: https://leetcode.com/problems/largest-color-value-in-a-directed-graph/description/
Code
from collections import defaultdict, deque
from typing import List
class Solution:
def largestPathValue(self, colors: str, edges: List[List[int]]) -> int:
n = len(colors)
graph = defaultdict(list)
in_degree = [0] * n
for u, v in edges:
graph[u].append(v)
in_degree[v] += 1
# color_count[i][c] = max count of color c in any path ending at node i
color_count = [[0] * 26 for _ in range(n)]
queue = deque()
for i in range(n):
if in_degree[i] == 0:
queue.append(i)
color_count[i][ord(colors[i]) - ord('a')] = 1
visited = 0
max_color_value = 0
while queue:
node = queue.popleft()
visited += 1
for neighbor in graph[node]:
for c in range(26):
# "For node i, what is the max number of times each color can appear on a path ending at i?"
color_count[neighbor][c] = max(
color_count[neighbor][c],
color_count[node][c] + (1 if c == ord(colors[neighbor]) - ord('a') else 0)
)
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
max_color_value = max(max_color_value, max(color_count[node]))
return max_color_value if visited == n else -1
19.51. Geek’s Village and Wells (BFS to find shortest distance in Matrix)
Ref: https://www.geeksforgeeks.org/problems/geeks-village-and-wells–170647/1
Code
from typing import List
from collections import deque
class Solution:
# BFS from W to other node
def chefAndWells(self, n: int, m: int,
c: List[List[str]]) -> List[List[int]]:
result = [[0 if c[i][j] != 'H' else -1 for j in range(m)] for i in range(n)]
visited = [[False] * m for _ in range(n)]
q = deque()
# Start from all wells
for i in range(n):
for j in range(m):
if c[i][j] == 'W':
q.append((i, j, 0)) # (row, col, dist)
visited[i][j] = True
dirs = [(-1,0), (1,0), (0,-1), (0,1)]
while q:
x, y, d = q.popleft()
for dx, dy in dirs:
nx, ny = x + dx, y + dy
if 0 <= nx < n and 0 <= ny < m and not visited[nx][ny]:
if c[nx][ny] != 'N': # not blocked
visited[nx][ny] = True
q.append((nx, ny, d + 1))
if c[nx][ny] == 'H':
if result[nx][ny] == -1 or result[nx][ny] > (d + 1) * 2:
result[nx][ny] = (d + 1) * 2
return result
19.52. Path With Minimum Effort (Dijkstra in Matrix with weight not 01)
Ref: https://leetcode.com/problems/path-with-minimum-effort/description/
Code
import heapq
from typing import List
class Solution:
def minimumEffortPath(self, heights: List[List[int]]) -> int:
rows, cols = len(heights), len(heights[0])
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
effort = [[float('inf')] * cols for _ in range(rows)]
effort[0][0] = 0
min_heap = [(0, 0, 0)] # (effort, x, y)
while min_heap:
curr_effort, x, y = heapq.heappop(min_heap)
if x == rows - 1 and y == cols - 1:
return curr_effort
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < rows and 0 <= ny < cols:
next_effort = max(curr_effort, abs(heights[nx][ny] - heights[x][y]))
if next_effort < effort[nx][ny]:
effort[nx][ny] = next_effort
heapq.heappush(min_heap, (next_effort, nx, ny))
return 0 # fallback (shouldn't be reached)
20. Pattern 20: Island
21. Pattern 21: Greedy Algorithms
Sort/Array
21.1. Jump Game
Ref: https://leetcode.com/problems/jump-game/description/
Code
class Solution:
def canJump(self, nums: List[int]) -> bool:
farthest = 0
for i in range(0, len(nums)):
# Previous step can not read here
if (i > farthest):
return False
# Farthest jump at step i
farthest = max(farthest, i + nums[i])
return True
23.2. Jump Game II
Ref: https://leetcode.com/problems/jump-game-ii/description/
Code
class Solution:
def jump(self, nums: List[int]) -> int:
jumps = 0
current_end = 0
farthest = 0
# [0, 2]
for i in range(len(nums) - 1):
farthest = max(farthest, i + nums[i])
if i == current_end:
jumps += 1
current_end = farthest
return jumps
21.3. Ugly Number II (Heap)
Ref: https://leetcode.com/problems/ugly-number-ii/description/
Code
import heapq
class Solution:
def nthUglyNumber(self, n: int) -> int:
heap = [1]
seen = {1}
primes = [2, 3, 5]
for _ in range(n):
ugly = heapq.heappop(heap)
for prime in primes:
new_ugly = ugly * prime
if new_ugly not in seen:
seen.add(new_ugly)
heapq.heappush(heap, new_ugly)
return ugly
21.4. Gas Station
Ref: https://leetcode.com/problems/gas-station/description/
Code
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
# O(N)
total_gas = 0
total_cost = 0
tank = 0
start = 0
for i in range(len(gas)):
total_gas += gas[i]
total_cost += cost[i]
# No need to add gas[i + 1] — it gets picked up in the next loop iteration.
tank += gas[i] - cost[i]
if tank < 0:
# Can't reach this station from previous start
start = i + 1
tank = 0
return start if total_gas >= total_cost else -1
21.5. Candy
Ref: https://leetcode.com/problems/candy/description/
Code
class Solution:
def candy(self, ratings: List[int]) -> int:
# Greedy
n = len(ratings)
candies = [1] * n
# Left to right
for i in range(1, n):
if ratings[i] > ratings[i - 1]:
candies[i] = candies[i - 1] + 1
# Right to left
for i in range(n - 2, -1, -1):
if ratings[i] > ratings[i + 1]:
candies[i] = max(candies[i], candies[i + 1] + 1)
return sum(candies)
21.6. Remove K Digits
Ref: https://leetcode.com/problems/remove-k-digits/description/
Code
class Solution:
def removeKdigits(self, num: str, k: int) -> str:
stack = []
for digit in num:
while k > 0 and stack and stack[-1] > digit:
stack.pop()
k -= 1
stack.append(digit)
# If k > 0, remove the last k digits
stack = stack[:-k] if k else stack
# Convert to string and remove leading zeros
result = ''.join(stack).lstrip('0')
return result if result else "0"
21.7. Wiggle Subsequence
Ref: https://leetcode.com/problems/wiggle-subsequence/description/
Code
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
if not nums:
return 0
n = len(nums)
up = down = 1
# If the sequence goes up, we can extend the last down sequence.
# If it goes down, we can extend the last up sequence.
for i in range(1, n):
if nums[i] > nums[i - 1]:
up = down + 1
elif nums[i] < nums[i - 1]:
down = up + 1
return max(up, down)
21.8. Assign Cookies (Hay)
Ref: https://leetcode.com/problems/assign-cookies/submissions/1641721832/
Code
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
# Sort both arrays
g.sort()
s.sort()
# Two pointers for greed factors and cookie sizes
child = cookie = 0
# Try to satisfy each child with the smallest sufficient cookie
while child < len(g) and cookie < len(s):
if s[cookie] >= g[child]:
# Cookie satisfies this child
child += 1
# Move to the next cookie
cookie += 1
return child
21.9. Boats to Save People
Ref: https://leetcode.com/problems/boats-to-save-people/description/
Code
from typing import List
class Solution:
def numRescueBoats(self, people: List[int], limit: int) -> int:
people.sort()
left = 0
right = len(people) - 1
boats = 0
while left <= right:
if people[left] + people[right] <= limit:
left += 1 # one boat for both
right -= 1 # heaviest person always goes
boats += 1
return boats
22.10. Bag of Tokens
Ref: https://leetcode.com/problems/bag-of-tokens/description/
Code
from typing import List
class Solution:
def bagOfTokensScore(self, tokens: List[int], power: int) -> int:
tokens.sort()
score = max_score = 0
i, j = 0, len(tokens) - 1
while i <= j:
if power >= tokens[i]:
power -= tokens[i]
score += 1
i += 1
max_score = max(max_score, score)
elif score >= 1:
power += tokens[j]
score -= 1
j -= 1
else:
break
return max_score
22.11. Number of Burgers with No Waste of Ingredients
Ref: https://leetcode.com/problems/number-of-burgers-with-no-waste-of-ingredients/description/
Code
from typing import List
class Solution:
def bagOfTokensScore(self, tokens: List[int], power: int) -> int:
tokens.sort()
score = max_score = 0
i, j = 0, len(tokens) - 1
while i <= j:
if power >= tokens[i]:
power -= tokens[i]
score += 1
i += 1
max_score = max(max_score, score)
elif score >= 1:
power += tokens[j]
score -= 1
j -= 1
else:
break
return max_score
22.12. Number of Burgers with No Waste of Ingredients
Ref: https://leetcode.com/problems/number-of-burgers-with-no-waste-of-ingredients/description/
Code
class Solution:
def numOfBurgers(self, tomatoSlices: int, cheeseSlices: int) -> List[int]:
x = (tomatoSlices - 2 * cheeseSlices) // 2
y = cheeseSlices - x
# Check that x and y are non-negative and valid
if x < 0 or y < 0 or 4 * x + 2 * y != tomatoSlices or x + y != cheeseSlices:
return []
return [x, y]
22.13. Queue Reconstruction by Height
Ref: https://leetcode.com/problems/queue-reconstruction-by-height/description/
Code
class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
people.sort(key=lambda x: (-x[0], x[1]))
queue = []
for person in people:
queue.insert(person[1], person)
return queue
22.14. Minimum Cost to Move Chips to The Same Position
Ref: https://leetcode.com/problems/minimum-cost-to-move-chips-to-the-same-position/description/
Code
from typing import List
class Solution:
def minCostToMoveChips(self, position: List[int]) -> int:
even = sum(1 for p in position if p % 2 == 0)
odd = len(position) - even
return min(even, odd)
22.15. Previous Permutation With One Swap
Ref: https://leetcode.com/problems/previous-permutation-with-one-swap/description/
Code
from typing import List
class Solution:
def prevPermOpt1(self, arr: List[int]) -> List[int]:
n = len(arr)
# Step 1: Find the first index i such that arr[i] > arr[i + 1] from the right
i = n - 2
while i >= 0 and arr[i] <= arr[i + 1]:
i -= 1
if i < 0:
return arr # Already the smallest permutation
# Step 2: Find the largest j > i such that arr[j] < arr[i]
# and arr[j] is the rightmost occurrence of that value
j = n - 1
while arr[j] >= arr[i]:
j -= 1
# Move to the leftmost j that has the same value (to avoid unnecessary swaps)
while j - 1 > i and arr[j] == arr[j - 1]:
j -= 1
# Step 3: Swap arr[i] and arr[j]
arr[i], arr[j] = arr[j], arr[i]
return arr
22.16. Lemonade Change (Hay)
Ref: https://leetcode.com/problems/lemonade-change/description/
Code
from typing import List
class Solution:
def lemonadeChange(self, bills: List[int]) -> bool:
five, ten = 0, 0
for bill in bills:
if bill == 5:
five += 1
elif bill == 10:
if five == 0:
return False
five -= 1
ten += 1
else: # bill == 20
if ten > 0 and five > 0:
ten -= 1
five -= 1
elif five >= 3:
five -= 3
else:
return False
return True
Hash/Multi-set
22.17. Task Scheduler
Ref: https://leetcode.com/problems/task-scheduler/description/
Code
from collections import Counter, deque
class Solution:
def leastInterval(self, tasks, n):
time = 0
task_counts = Counter(tasks)
cooldown = {} # task -> next available time
while task_counts:
available = [task for task in task_counts if cooldown.get(task, 0) <= time]
if available:
# Choose task with highest remaining count
task = max(available, key=lambda x: task_counts[x])
task_counts[task] -= 1
if task_counts[task] == 0:
del task_counts[task]
cooldown[task] = time + n + 1
time += 1
return time
22.18. Partition Labels
Ref: https://leetcode.com/problems/partition-labels/description/
Code
class Solution:
def partitionLabels(self, s: str) -> List[int]:
# Step 1: Record the last occurrence of each character
last_index = {char: idx for idx, char in enumerate(s)}
result = []
start = 0
end = 0
# Step 2: Iterate through the string to find partitions
for i, char in enumerate(s):
end = max(end, last_index[char])
if i == end:
result.append(end - start + 1)
start = i + 1
return result
22.19. Car Pooling
Ref: https://leetcode.com/problems/car-pooling/description/
Code
class Solution:
def carPooling(self, trips: List[List[int]], capacity: int) -> bool:
# Use a list to store the change in passengers at each location
passenger_changes = [0] * 1001 # Locations range from 0 to 1000
for num_passengers, start, end in trips:
passenger_changes[start] += num_passengers
passenger_changes[end] -= num_passengers
current_passengers = 0
for passengers in passenger_changes:
current_passengers += passengers
if current_passengers > capacity:
return False
return True
22.20. Divide Array in Sets of K Consecutive Numbers
Ref: https://leetcode.com/problems/divide-array-in-sets-of-k-consecutive-numbers/description/
Code
from typing import List
from collections import Counter
import heapq
class Solution:
def isPossibleDivide(self, nums: List[int], k: int) -> bool:
if len(nums) % k != 0:
return False
count = Counter(nums)
min_heap = list(count.keys())
heapq.heapify(min_heap)
while min_heap:
first = min_heap[0]
for i in range(first, first + k):
if count[i] == 0:
return False
count[i] -= 1
if count[i] == 0:
if i != min_heap[0]:
return False
heapq.heappop(min_heap)
return True
22.21. Group the People Given the Group Size They Belong To
Ref: https://leetcode.com/problems/group-the-people-given-the-group-size-they-belong-to/description/
Code
class Solution:
def groupThePeople(self, groupSizes: List[int]) -> List[List[int]]:
size_to_people = defaultdict(list)
result = []
for person, size in enumerate(groupSizes):
size_to_people[size].append(person)
# Once a group of the correct size is formed, add it to result
if len(size_to_people[size]) == size:
result.append(size_to_people[size])
size_to_people[size] = []
return result
22.22. Cinema Seat Allocation
Ref: https://leetcode.com/problems/cinema-seat-allocation/description/
Code
from typing import List
from collections import defaultdict
class Solution:
def maxNumberOfFamilies(self, n: int, reservedSeats: List[List[int]]) -> int:
reserved = defaultdict(set)
for row, seat in reservedSeats:
reserved[row].add(seat)
max_families = 0
for row in reserved:
taken = reserved[row]
# Check three possible blocks of 4 contiguous seats:
# Block A: seats 2-5, Block B: seats 4-7, Block C: seats 6-9
can_place_left = all(seat not in taken for seat in range(2, 6))
can_place_right = all(seat not in taken for seat in range(6, 10))
can_place_middle = all(seat not in taken for seat in range(4, 8))
if can_place_left and can_place_right:
max_families += 2
elif can_place_left or can_place_right or can_place_middle:
max_families += 1
# Else: no family can be placed in this row
# Rows without any reserved seats can accommodate 2 families
rows_without_reservations = n - len(reserved)
max_families += rows_without_reservations * 2
return max_families
22.23. Construct K Palindrome Strings
Ref: https://leetcode.com/problems/construct-k-palindrome-strings/description/
Code
from collections import Counter
class Solution:
def canConstruct(self, s: str, k: int) -> bool:
if len(s) < k:
return False # Not enough characters to make k palindromes
char_counts = Counter(s)
odd_count = sum(1 for count in char_counts.values() if count % 2 != 0)
return odd_count <= k
22.24. Advantage Shuffle
Ref: https://leetcode.com/problems/advantage-shuffle/description/
Code
class Solution:
def advantageCount(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1.sort()
max_heap = [(-val, idx) for idx, val in enumerate(nums2)]
heapq.heapify(max_heap)
result = [0] * len(nums1)
low = 0
high = len(nums1) - 1
while max_heap:
val, idx = heapq.heappop(max_heap)
val = -val
# If the largest in nums1 can beat the largest in nums2
if nums1[high] > val:
result[idx] = nums1[high]
high -= 1
else:
# Use the smallest number — sacrifice it
result[idx] = nums1[low]
low += 1
return result
Strings
22.25. Reorganize String
Ref: https://leetcode.com/problems/reorganize-string/
Code
import heapq
from collections import Counter
class Solution:
# To avoid having the same characters next to each other, you want to spread out the most frequent characters as evenly as possible.
def reorganizeString(self, s: str) -> str:
# Step 1: Count the frequency of each character
count = Counter(s)
if any(freq > (len(s) + 1) // 2 for freq in count.values()):
return ""
max_heap = [(-freq, char) for char, freq in count.items()]
heapq.heapify(max_heap)
prev_freq, prev_char = 0, ''
result = []
while max_heap:
freq, char = heapq.heappop(max_heap)
result.append(char)
# If the previous character can still be used, push it back
if prev_freq < 0:
heapq.heappush(max_heap, (prev_freq, prev_char))
# Update previous character to the current one
prev_freq, prev_char = freq + 1, char # since freq is negative
reorganized = ''.join(result)
# Final check: if the result is valid
for i in range(1, len(reorganized)):
if reorganized[i] == reorganized[i - 1]:
return ""
return reorganized
22.26. String Without AAA or BBB
Ref: https://leetcode.com/problems/string-without-aaa-or-bbb/description/
Code
class Solution:
def strWithout3a3b(self, a: int, b: int) -> str:
result = []
while a > 0 or b > 0:
if a > b:
if a >= 2:
result.append('aa')
a -= 2
else:
result.append('a')
a -= 1
if b > 0:
result.append('b')
b -= 1
elif b > a:
if b >= 2:
result.append('bb')
b -= 2
else:
result.append('b')
b -= 1
if a > 0:
result.append('a')
a -= 1
else: # a == b
result.append('ab' * a)
break
return ''.join(result)
22.27. Check If a String Can Break Another String
Ref: https://leetcode.com/problems/check-if-a-string-can-break-another-string/description/
Code
class Solution:
def checkIfCanBreak(self, s1: str, s2: str) -> bool:
s1_sorted = sorted(s1)
s2_sorted = sorted(s2)
def can_break(a, b):
return all(x >= y for x, y in zip(a, b))
return can_break(s1_sorted, s2_sorted) or can_break(s2_sorted, s1_sorted)
22.28. Remove Duplicate Letters (Hay)
Ref: https://leetcode.com/problems/remove-duplicate-letters/description/
Code
class Solution:
def removeDuplicateLetters(self, s: str) -> str:
last_index = {c: i for i, c in enumerate(s)} # Last occurrence of each character
stack = []
seen = set()
for i, c in enumerate(s):
if c in seen:
continue
while stack and c < stack[-1] and i < last_index[stack[-1]]:
seen.remove(stack.pop())
stack.append(c)
seen.add(c)
return ''.join(stack)
Heap
22.29. Last Stone Weight
Ref: https://leetcode.com/problems/last-stone-weight/description/
Code
from typing import List
import heapq
class Solution:
def lastStoneWeight(self, stones: List[int]) -> int:
# Use a max-heap by pushing negative values
max_heap = [-stone for stone in stones]
heapq.heapify(max_heap)
while len(max_heap) > 1:
first = -heapq.heappop(max_heap)
second = -heapq.heappop(max_heap)
if first != second:
heapq.heappush(max_heap, -(first - second))
return -max_heap[0] if max_heap else 0
22.30. Reduce Array Size to The Half
Ref: https://leetcode.com/problems/reduce-array-size-to-the-half/description/
Code
from typing import List
from collections import Counter
import heapq
class Solution:
def minSetSize(self, arr: List[int]) -> int:
count = Counter(arr)
max_heap = [-freq for freq in count.values()] # Use max-heap
heapq.heapify(max_heap)
removed = 0
total_removed = 0
half = len(arr) // 2
while total_removed < half:
freq = -heapq.heappop(max_heap)
total_removed += freq
removed += 1
return removed
Stack
22.31. Minimum Add to Make Parentheses Valid
Ref: https://leetcode.com/problems/minimum-add-to-make-parentheses-valid/description/
Code
class Solution:
def minAddToMakeValid(self, s: str) -> int:
open_needed = 0 # Count of unmatched '('
insertions = 0 # Count of insertions needed
for char in s:
if char == '(':
open_needed += 1
else: # char == ')'
if open_needed > 0:
open_needed -= 1
else:
insertions += 1 # Need to insert a '(' before this ')'
return open_needed + insertions
22. Pattern 22: Backtracking
22.1. Permutations
Ref: https://leetcode.com/problems/permutations/description/
Code
from typing import List
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
result = []
def backtrack(nums, i):
if i == len(nums):
result.append(nums)
return
for j in range(i, len(nums)):
new_nums = nums[:] # make a fresh copy
new_nums[i], new_nums[j] = new_nums[j], new_nums[i]
backtrack(new_nums, i + 1)
backtrack(nums, 0)
return result
# Solution 2
from typing import List
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
result = []
def backtrack(start=0):
if start == len(nums):
result.append(nums[:])
return
for i in range(start, len(nums)):
# [1,2], [1,3], [1,4]
# When the i = 1 is pass, i = 2 and continue
nums[start], nums[i] = nums[i], nums[start] # swap
backtrack(start + 1)
# Backtrack
nums[start], nums[i] = nums[i], nums[start] # backtrack
backtrack()
return result
22.2. Permutations II (Unique)
Ref: https://leetcode.com/problems/permutations-ii/description/
Code
from typing import List
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort() # Sort to group duplicates together
def backtrack(nums, i):
if i == len(nums):
result.append(nums)
return
used = set()
for j in range(i, len(nums)):
if nums[j] in used:
continue # Skip duplicates at this level
used.add(nums[j])
new_nums = nums[:] # make a copy
new_nums[i], new_nums[j] = new_nums[j], new_nums[i]
backtrack(new_nums, i + 1)
backtrack(nums, 0)
return result
from typing import List
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort() # Sort to make it easy to skip duplicates
def backtrack(start=0):
if start == len(nums):
result.append(nums[:])
return
# [1,2], [1,3], [1,4]
# When the i = 1 is pass, i = 2 and continue
seen = set() # Used to skip duplicates at this recursion level
for i in range(start, len(nums)):
if nums[i] in seen:
continue
seen.add(nums[i])
nums[start], nums[i] = nums[i], nums[start]
backtrack(start + 1)
nums[start], nums[i] = nums[i], nums[start] # backtrack
backtrack()
return result
22.3. Combinations
Ref: https://leetcode.com/problems/combinations/
Code
from typing import List
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
res = []
def backtrack(start: int, path: List[int]):
if len(path) == k:
res.append(path[:])
return
# [1,2], [1,3], [1,4]
# When the i = 1 is pass, i = 2 and continue
for i in range(start, n + 1):
path.append(i)
backtrack(i + 1, path)
path.pop()
backtrack(1, [])
return res
22.4. Combination Sum
Ref: https://leetcode.com/problems/combination-sum/description/
Code
from typing import List
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
result = []
def backtrack(remaining, start, path):
if remaining == 0:
result.append(path[:])
return
elif remaining < 0:
return
for i in range(start, len(candidates)):
path.append(candidates[i])
# i, not i+1 because we can reuse same elements
backtrack(remaining - candidates[i], i, path)
path.pop()
backtrack(target, 0, [])
return result
22.5. Combination Sum II
Ref: https://leetcode.com/problems/combination-sum-ii/description/
Code
from typing import List
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
candidates.sort()
result = set()
def backtrack(remaining, start, path):
if remaining == 0:
result.add(tuple(path)) # Convert to tuple to store in set
return
elif remaining < 0:
return
for i in range(start, len(candidates)):
# Skip duplicates (common and understand)
if i > start and candidates[i] == candidates[i - 1]:
continue
path.append(candidates[i])
# i + 1: each number can be used only once
backtrack(remaining - candidates[i], i + 1, path)
path.pop()
backtrack(target, 0, [])
return [list(comb) for comb in result] # Convert each tuple back to list
22.6. Combination Sum III
Ref: https://leetcode.com/problems/combination-sum-iii/
Code
class Solution:
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
candidates = [1,2,3,4,5,6,7,8,9]
candidates.sort()
result = set()
def backtrack(remaining, start, path):
if remaining == 0 and len(path) == k:
result.add(tuple(path)) # Convert to tuple to store in set
return
elif remaining < 0:
return
for i in range(start, len(candidates)):
# Skip duplicates (common and understand)
if i > start and candidates[i] == candidates[i - 1]:
continue
path.append(candidates[i])
# i + 1: each number can be used only once
backtrack(remaining - candidates[i], i + 1, path)
path.pop()
backtrack(n, 0, [])
return [list(comb) for comb in result] # Convert each tuple back to list
22.7. Subsets
Ref: https://leetcode.com/problems/subsets/description/
Code
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
result = []
# Giong combination sum nhung khong co diem dung ma lay het
def backtrack(start, path):
result.append(path[:]) # Add the current subset to the result
for i in range(start, len(nums)):
path.append(nums[i])
# Do not contain duplicate
backtrack(i + 1, path)
path.pop()
backtrack(0, [])
return result
22.8. Subsets II
Ref: https://leetcode.com/problems/subsets-ii/description/
Code
from typing import List
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
nums.sort()
result = []
def backtrack(start, path):
result.append(path[:])
for i in range(start, len(nums)):
# Skip duplicates
if i > start and nums[i] == nums[i - 1]:
continue
path.append(nums[i])
backtrack(i + 1, path)
path.pop()
backtrack(0, [])
return result
22.9. Palindrome Partitioning
Ref: https://leetcode.com/problems/palindrome-partitioning/description/
Code
from typing import List
class Solution:
def partition(self, s: str) -> List[List[str]]:
result = []
current_partition = []
def is_palindrome(sub: str) -> bool:
return sub == sub[::-1]
def backtrack(start: int):
if start == len(s):
result.append(current_partition[:])
return
for end in range(start + 1, len(s) + 1):
substring = s[start:end]
if is_palindrome(substring):
current_partition.append(substring)
backtrack(end)
current_partition.pop()
backtrack(0)
return result
22.10. Generate Parentheses
Ref: https://leetcode.com/problems/generate-parentheses/description/
Code
from typing import List
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
result = []
def backtrack(current: str, open_count: int, close_count: int):
# Base case: if the current string has all pairs
if len(current) == 2 * n:
result.append(current)
return
# Add an opening parenthesis if we still have one
if open_count < n:
backtrack(current + "(", open_count + 1, close_count)
# Add a closing parenthesis if it won't exceed the number of opens
# Why You Don’t See current.pop()
# That creates a new string every time (current + "("), so we don’t need to undo anything — the old current is untouched.
if close_count < open_count:
backtrack(current + ")", open_count, close_count + 1)
backtrack("", 0, 0)
return result
22.11. Letter Combinations of a Phone Number
Ref: https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/
Code
from typing import List
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits:
return []
phone_map = {
"2": "abc",
"3": "def",
"4": "ghi",
"5": "jkl",
"6": "mno",
"7": "pqrs",
"8": "tuv",
"9": "wxyz"
}
result = []
def backtrack(index: int, path: str):
if index == len(digits):
result.append(path)
return
possible_letters = phone_map[digits[index]]
for letter in possible_letters:
# Backtrack for each letter "a", "b", "c"
backtrack(index + 1, path + letter)
backtrack(0, "")
return result
22.12. Word search
Ref: https://leetcode.com/problems/word-search/description/
Code
from typing import List
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
rows, cols = len(board), len(board[0])
def dfs(r, c, i):
if i == len(word):
return True
if (r < 0 or c < 0 or r >= rows or c >= cols
or board[r][c] != word[i]):
return False
# Temporarily mark the cell as visited
temp = board[r][c]
# Flip the word[i] to '#' is found
board[r][c] = "#"
# Explore neighbors in 4 directions
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
found = dfs(r + dr, c + dc, i + 1) # Recursively check neighbors
if found:
return True
# Restore the cell
board[r][c] = temp
return False
for r in range(rows):
for c in range(cols):
if board[r][c] == word[0]:
if dfs(r, c, 0):
return True
return False
22.13. N-Queens
Ref: https://leetcode.com/problems/n-queens/description/
Code
from typing import List
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
def is_valid(board, row, col):
# Check column
for i in range(row):
if board[i][col] == 'Q':
return False
# Check upper-left diagonal
i, j = row - 1, col - 1
while i >= 0 and j >= 0:
if board[i][j] == 'Q':
return False
i -= 1
j -= 1
# Check upper-right diagonal
i, j = row - 1, col + 1
while i >= 0 and j < n:
if board[i][j] == 'Q':
return False
i -= 1
j += 1
return True
def backtrack(row):
if row == n:
result.append(["".join(r) for r in board])
return
for col in range(n):
if is_valid(board, row, col):
board[row][col] = 'Q'
backtrack(row + 1)
board[row][col] = '.'
result = []
board = [['.'] * n for _ in range(n)]
backtrack(0)
return result
22.14. Sudoku Solver
Ref: https://leetcode.com/problems/sudoku-solver/description/
Code
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
def is_valid(r, c, ch):
# Check row
for i in range(9):
if board[r][i] == ch:
return False
# Check column
for i in range(9):
if board[i][c] == ch:
return False
# Check 3x3 sub-box
box_row_start = (r // 3) * 3
box_col_start = (c // 3) * 3
for i in range(3):
for j in range(3):
if board[box_row_start + i][box_col_start + j] == ch:
return False
return True
def backtrack():
for i in range(9):
for j in range(9):
if board[i][j] == '.':
for ch in map(str, range(1, 10)):
if is_valid(i, j, ch):
board[i][j] = ch
if backtrack():
return True
board[i][j] = '.' # backtrack
return False # No valid number found
return True # All cells filled
backtrack()
22.15. Permutations of a String (Permutation)
Ref: https://www.geeksforgeeks.org/problems/permutations-of-a-given-string2041/1
Code
class Solution:
def findPermutation(self, s):
s = list(s)
result = set()
def backtrack(start=0):
if start == len(s):
result.add(''.join(s[:]))
return
for i in range(start, len(s)):
s[start], s[i] = s[i], s[start] # swap
backtrack(start + 1)
# Backtrack
s[start], s[i] = s[i], s[start] # backtrack
backtrack()
return list(result)
22.16. Word Break (Combination Sum)
Ref: https://leetcode.com/problems/word-break/description/
Code
from typing import List
# Idea of Combination Sum
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> List[str]:
result = []
word_set = set(wordDict)
n = len(s)
def backtrack(start: int, path: List[str]):
# Multiple function call so we can not return False here
if start == n:
result.append(" ".join(path))
return
for end in range(start + 1, n + 1):
word = s[start:end]
if word in word_set:
path.append(word)
backtrack(end, path)
path.pop()
backtrack(0, [])
return len(result) > 0
22.17. Rat in a Maze Problem
Ref: https://www.geeksforgeeks.org/problems/rat-in-a-maze-problem/1
Code
class Solution:
# Function to find all possible paths in the maze
def ratInMaze(self, maze):
n = len(maze)
result = []
# Directions: D (down), L (left), R (right), U (up)
# ('D', dx + 1, dy), ('L', dx, dy - 1), ('R', dx, dy + 1), ('U', dx - 1, dy)
directions = [('D', 1, 0), ('L', 0, -1), ('R', 0, 1), ('U', -1, 0)]
def isSafe(x, y, visited):
return 0 <= x < n and 0 <= y < n and maze[x][y] == 1 and not visited[x][y]
def backtrack(x, y, path, visited):
if x == n - 1 and y == n - 1:
result.append(path)
return
visited[x][y] = True
for dir_char, dx, dy in directions:
new_x, new_y = x + dx, y + dy
if isSafe(new_x, new_y, visited):
backtrack(new_x, new_y, path + dir_char, visited)
visited[x][y] = False # backtrack
if maze[0][0] == 1:
visited = [[False for _ in range(n)] for _ in range(n)]
backtrack(0, 0, '', visited)
return sorted(result)
22.18. Path with Maximum Gold
Ref: https://leetcode.com/problems/path-with-maximum-gold/description/
Code
from typing import List
class Solution:
def getMaximumGold(self, grid: List[List[int]]) -> int:
rows, cols = len(grid), len(grid[0])
def dfs(x: int, y: int) -> int:
gold = grid[x][y]
grid[x][y] = 0 # mark as visited
# NOTES: TOTAL RESULT AFTER BACKTRACKING
max_gold = 0
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = x + dx, y + dy
if 0 <= nx < rows and 0 <= ny < cols and grid[nx][ny] > 0:
max_gold = max(max_gold, dfs(nx, ny))
grid[x][y] = gold # backtrack
return gold + max_gold
max_total = 0
for i in range(rows):
for j in range(cols):
if grid[i][j] > 0:
max_total = max(max_total, dfs(i, j))
return max_total
22.19. Distribute Candies Among Children II
Ref: https://leetcode.com/problems/distribute-candies-among-children-ii/description/
Code
class Solution:
def distributeCandies(self, n: int, limit: int) -> int:
result = 0
def backtrack(i, remaining):
nonlocal result
if i == 3:
if remaining == 0:
result += 1
return
for candies in range(min(limit, remaining) + 1):
backtrack(i + 1, remaining - candies)
backtrack(0, n)
return result
23. Pattern 23: Trie
23.1. Implement Trie (Prefix Tree)
Ref: https://leetcode.com/problems/implement-trie-prefix-tree/description/
Code
class TrieNode:
def __init__(self):
self.children = {}
self.is_end_of_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end_of_word = True
def search(self, word: str) -> bool:
node = self.root
for char in word:
if char not in node.children:
return False
node = node.children[char]
return node.is_end_of_word
def startsWith(self, prefix: str) -> bool:
node = self.root
for char in prefix:
if char not in node.children:
return False
node = node.children[char]
return True
# Example usage:
# obj = Trie()
# obj.insert("apple")
# param_2 = obj.search("apple") # Returns True
# param_3 = obj.startsWith("app") # Returns True
23.2. Longest Word in Dictionary (All Prefix is in the list)
Ref: https://leetcode.com/problems/longest-word-in-dictionary/description/
Code
from typing import List
class TrieNode:
def __init__(self):
self.children = {}
self.word = None # store the word at the end node for retrieval
class Solution:
def longestWord(self, words: List[str]) -> str:
root = TrieNode()
# Insert words into the Trie
for word in words:
node = root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.word = word # mark the end of the word
# DFS to find the longest valid word
stack = list(root.children.values())
longest = ""
while stack:
node = stack.pop()
if node.word is not None: # only consider complete words
if len(node.word) > len(longest) or \
(len(node.word) == len(longest) and node.word < longest):
longest = node.word
for child in node.children.values():
stack.append(child)
return longest
23.3. Map Sum Pairs
Ref: https://leetcode.com/problems/map-sum-pairs/description/
Code
class MapSum:
def __init__(self):
self.map = {}
def insert(self, key: str, val: int) -> None:
self.map[key] = val
def sum(self, prefix: str) -> int:
total = 0
for k in self.map:
if k.startswith(prefix):
total += self.map[k]
return total
# Your MapSum object will be instantiated and called as such:
# obj = MapSum()
# obj.insert(key,val)
# param_2 = obj.sum(prefix)
23.4. Word Search II
Ref: https://leetcode.com/problems/word-search-ii/description/
Code
from typing import List
class TrieNode:
def __init__(self):
self.children = {}
self.word = None # Stores the complete word at the end node
class Solution:
def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
root = TrieNode()
# Build Trie from the words list
for word in words:
node = root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.word = word # Mark the end of a word
rows, cols = len(board), len(board[0])
result = []
def dfs(r, c, node):
char = board[r][c]
if char not in node.children:
return
next_node = node.children[char]
if next_node.word:
result.append(next_node.word)
next_node.word = None # Avoid duplicates
board[r][c] = '#' # Mark visited
for dr, dc in [(-1,0), (1,0), (0,-1), (0,1)]: # Up, Down, Left, Right
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and board[nr][nc] != '#':
dfs(nr, nc, next_node)
board[r][c] = char # Restore after DFS
# Optional optimization: prune the Trie
if not next_node.children:
del node.children[char]
for r in range(rows):
for c in range(cols):
dfs(r, c, root)
return result
23.5. Maximum XOR of Two Numbers in an Array
Ref: https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/description/
Code
from typing import List
class TrieNode:
def __init__(self):
self.children = {}
class Solution:
def findMaximumXOR(self, nums: List[int]) -> int:
# Build the Trie
root = TrieNode()
for num in nums:
node = root
for i in range(31, -1, -1): # 32-bit integer
bit = (num >> i) & 1
if bit not in node.children:
node.children[bit] = TrieNode()
node = node.children[bit]
# Find maximum XOR
max_xor = 0
for num in nums:
node = root
curr_xor = 0
for i in range(31, -1, -1):
bit = (num >> i) & 1
toggled_bit = 1 - bit
if toggled_bit in node.children:
curr_xor |= (1 << i)
node = node.children[toggled_bit]
else:
node = node.children[bit]
max_xor = max(max_xor, curr_xor)
return max_xor
23.6. Design Add and Search Words Data Structure
Ref: https://leetcode.com/problems/design-add-and-search-words-data-structure/description/
Code
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
class WordDictionary:
def __init__(self):
self.root = TrieNode()
def addWord(self, word: str) -> None:
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end = True
def search(self, word: str) -> bool:
def dfs(node, i):
if i == len(word):
return node.is_end
if word[i] == '.':
for child in node.children.values():
if dfs(child, i + 1):
return True
return False
if word[i] in node.children:
return dfs(node.children[word[i]], i + 1)
return False
return dfs(self.root, 0)
# Your WordDictionary object will be instantiated and called as such:
# obj = WordDictionary()
# obj.addWord(word)
# param_2 = obj.search(word)
23.7. Replace Words
Ref: https://leetcode.com/problems/replace-words/description/
Code
from typing import List
class TrieNode:
def __init__(self):
self.children = {}
self.word = None # Store word at end of root
class Solution:
def replaceWords(self, dictionary: List[str], sentence: str) -> str:
# Build Trie from dictionary
root = TrieNode()
for word in dictionary:
node = root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.word = word # Mark end of a root word
def replace(word):
node = root
for char in word:
if char not in node.children:
break
node = node.children[char]
if node.word:
return node.word
return word
return ' '.join(replace(w) for w in sentence.split())
24. Pattern 24: Union Find
25. Pattern 25: Ordered Set
26. Pattern 26: Prefix Sum
27. Pattern 27: Multi-threaded
28. Pattern 28: Unbounded Knapsack
29. Pattern 29: Fibonacci Numbers
30. Pattern 30: Palindromic Subsequence
31. Pattern 31: Longest Common Substring
32. Pattern 32: Counting Pattern
33. Pattern 33: Monotonic Queue Pattern
34. Pattern 34: Simulation Pattern
35. Pattern 35: Linear Sorting Algorithm Pattern
36. Pattern 36: Meet in the Middle Pattern
37. Pattern 37: MO’s Algorithm Pattern
38. Pattern 38: Serialize and Deserialize Pattern
39. Pattern 39: Clone Pattern
40. Pattern 40: Articulation Points and Bridges Pattern
41. Pattern 41: Segment Tree Pattern
42. Pattern 42: Binary Indexed Tree Pattern
43. Pattern 43: Math
43.1. Greatest Common Divisor of Strings
Ref: https://leetcode.com/problems/greatest-common-divisor-of-strings/
Code
import math
class Solution:
def gcdOfStrings(self, str1: str, str2: str) -> str:
# If concatenating in both orders yields different results, there's no GCD string
if str1 + str2 != str2 + str1:
return ""
# The length of the GCD string is the GCD of the lengths
gcd_len = math.gcd(len(str1), len(str2))
return str1[:gcd_len]
43.2. Rabbits in Forest
Ref: https://leetcode.com/problems/rabbits-in-forest/description/
Code
from collections import Counter
import math
class Solution:
def numRabbits(self, answers: List[int]) -> int:
# If a rabbit says "x other rabbits have the same color," it means there are x + 1 total rabbits of that color.
# But if multiple rabbits say the same number, they might be in the same group, or represent multiple groups of that same size.
counter = Counter(answers)
count = 0
# Edgecase: 1,1,1 => 2 group
for key, freq in counter.items():
group_size = key + 1
groups = math.ceil(freq / group_size)
count += groups * group_size
return count
43.3. How Many Numbers Are Smaller Than the Current Number
Ref: https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/description/
Code
class Solution:
def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]:
# O(N^2)
res = []
for i in range(0, len(nums)):
count = 0
for j in range(0, len(nums)):
if j != i and nums[j] < nums[i]:
count += 1
res.append(count)
return res
# O(NlogN)
sort_num = sorted(nums)
numToIndex = {}
# [1,2,2,3,8]
for i, value in enumerate(sort_num):
if value not in numToIndex:
numToIndex[value] = i
return [numToIndex[num] for num in nums]
43.4. Best Time to Buy and Sell Stock (Finding Min Dynamic)
Ref: https://leetcode.com/problems/best-time-to-buy-and-sell-stock
Code
class Solution:
def maxProfit(self, prices: List[int]) -> int:
min_price = float('inf')
max_profit = 0
for i in range(0, len(prices)):
if prices[i] < min_price:
min_price = prices[i]
# profit = price[i] - min_price at previous time
max_profit = max(max_profit, prices[i] - min_price)
return max_profit
43.4. Best Time to Buy and Sell Stock 2 (If larger than previous price and you will buy)
Ref: https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/
Code
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# If larger than you will buy
maxProfit = 0
for i in range(1, len(prices)):
if prices[i] > prices[i - 1]:
maxProfit += prices[i] - prices[i - 1]
return maxProfit
43.5. Jump Game
Ref: https://leetcode.com/problems/jump-game/
Code
class Solution:
def canJump(self, nums: List[int]) -> bool:
farthest = 0
for i in range(0, len(nums)):
# Previous step can not read here
if (i > farthest):
return False
# Farthest jump at step i
farthest = max(farthest, i + nums[i])
return True
43.5. Jump Game 2
Ref: https://leetcode.com/problems/jump-game-ii/
Code
class Solution:
def jump(self, nums: List[int]) -> int:
farthest = 0
nextStep = 0
count = 0
# Final step do not need to jump
for i in range(0, len(nums) - 1):
# Previous step can not read here
if (i > farthest):
return 0
# Farthest jump at step i
farthest = max(farthest, i + nums[i]) if i + nums[i] < len(nums) else len(nums) - 1
# Jump in the largest step
if i == nextStep:
count += 1
nextStep = farthest
return count
43.5. H-Index (Max i and n - i)
Ref: https://leetcode.com/problems/h-index/description/
Code
class Solution:
def hIndex(self, citations: List[int]) -> int:
# Find i number is larger than i
# O(NlogN)
citations.sort()
maxHIndex = 0
for i in range(0, len(citations)):
# For example [1, and 5 values] => 1 must be pass
h = min(citations[i], len(citations) - i)
maxHIndex = max(maxHIndex, h)
return maxHIndex
43.6. HashMap - Insert Delete GetRandom O(1)
Ref: https://leetcode.com/problems/insert-delete-getrandom-o1/
Code
import random
class RandomizedSet:
def __init__(self):
self.hashMap = {}
def insert(self, val: int) -> bool:
if val in self.hashMap:
return False
self.hashMap[val] = True
return True
def remove(self, val: int) -> bool:
if val not in self.hashMap:
return False
del self.hashMap[val]
return True
# O(N) => you can use a list to make it to O(1)
def getRandom(self) -> int:
return random.choice(list(self.hashMap.keys()))
# Your RandomizedSet object will be instantiated and called as such:
# obj = RandomizedSet()
# param_1 = obj.insert(val)
# param_2 = obj.remove(val)
# param_3 = obj.getRandom()
# import random
# class RandomizedSet:
# def __init__(self):
# self.dict = {} # val -> index in list
# self.list = [] # index -> val
# def insert(self, val: int) -> bool:
# if val in self.dict:
# return False
# self.list.append(val)
# self.dict[val] = len(self.list) - 1
# return True
# def remove(self, val: int) -> bool:
# if val not in self.dict:
# return False
# index = self.dict[val]
# last_element = self.list[-1]
# # Move last element to the spot of the one to remove
# self.list[index] = last_element
# self.dict[last_element] = index
# # Remove last element
# self.list.pop()
# del self.dict[val]
# return True
# def getRandom(self) -> int:
# return random.choice(self.list)
43.7. Gas Station (Circular Array)
Ref: https://leetcode.com/problems/gas-station/description/
Code
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
# O(N^2)
# for i in range(len(gas)):
# fuel = gas[i] - cost[i]
# if fuel < 0:
# continue
# j = (i + 1) % len(gas)
# count = 1
# while j != i:
# fuel += gas[j] - cost[j]
# if fuel < 0:
# break
# j = (j + 1) % len(gas)
# count += 1
# if count == len(gas) and fuel >= 0:
# return i
# return -1
# O(N)
total_gas = 0
total_cost = 0
tank = 0
start = 0
for i in range(len(gas)):
total_gas += gas[i]
total_cost += cost[i]
# No need to add gas[i + 1] — it gets picked up in the next loop iteration.
tank += gas[i] - cost[i]
if tank < 0:
# Can't reach this station from previous start
start = i + 1
tank = 0
return start if total_gas >= total_cost else -1
43.8. Candy
Ref: https://leetcode.com/problems/candy/
Code
# Do a left-to-right pass to make sure each child has more candies than the left neighbor if their rating is higher.
# Do a right-to-left pass to ensure each child has more candies than the right neighbor if their rating is higher.
class Solution:
def candy(self, ratings: List[int]) -> int:
# Greedy
n = len(ratings)
candies = [1] * n
# Left to right
for i in range(1, n):
if ratings[i] > ratings[i - 1]:
candies[i] = candies[i - 1] + 1
# Right to left
for i in range(n - 2, -1, -1):
if ratings[i] > ratings[i + 1]:
candies[i] = max(candies[i], candies[i + 1] + 1)
return sum(candies)
43.9. Container With Most Water
Ref: https://leetcode.com/problems/container-with-most-water/
Code
class Solution:
def maxArea(self, height: List[int]) -> int:
left, right = 0, len(height) - 1
maxArea = 0
while left < right:
currArea = min(height[left], height[right]) * (right - left)
maxArea = max(maxArea, currArea)
if height[left] <= height[right]:
left += 1
else:
right -= 1
return maxArea
43.10. Trapping Rain Water
Ref: https://leetcode.com/problems/trapping-rain-water/
Code
class Solution:
def trap(self, height: List[int]) -> int:
stack = []
water_trapped = 0
for i in range(0, len(height)):
while stack and height[i] > height[stack[-1]]:
top = stack.pop()
if stack:
width = i - stack[-1] - 1
bounded_height = min(height[i], height[stack[-1]]) - height[top]
water_trapped += width * bounded_height
stack.append(i)
return water_trapped
43.11. Next Greater Element II
Ref: https://leetcode.com/problems/next-greater-element-ii/description/
Code
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
n = len(nums)
result = [-1] * n
stack = []
for i in range(2 * n - 1, -1, -1):
curr_idx = i % n
while stack and nums[stack[-1]] <= nums[curr_idx]:
stack.pop()
if stack:
result[curr_idx] = nums[stack[-1]]
stack.append(curr_idx)
return result
43.12. Roman to Integer
Ref: https://leetcode.com/problems/roman-to-integer/description/
Code
class Solution:
def romanToInt(self, s: str) -> int:
hashMap = {
'I': 1,
'V': 5,
'X': 10,
'L': 50,
'C': 100,
'D': 500,
'M': 1000
}
val = 0
for i in range(0, len(s)):
if i < len(s) - 1 and hashMap[s[i]] < hashMap[s[i + 1]]:
val -= hashMap[s[i]]
else:
val += hashMap[s[i]]
return val
43.13. Integer to Roman
Ref: https://leetcode.com/problems/integer-to-roman/description/
Code
class Solution:
def intToRoman(self, num: int) -> str:
val = [
1000, 900, 500, 400,
100, 90, 50, 40,
10, 9, 5, 4, 1
]
syms = [
"M", "CM", "D", "CD",
"C", "XC", "L", "XL",
"X", "IX", "V", "IV", "I"
]
roman = ""
for i in range(len(val)):
while num >= val[i]:
roman += syms[i]
num -= val[i]
return roman
43.14. Count Largest Group
Ref: https://leetcode.com/problems/count-largest-group/
Code
from collections import defaultdict
class Solution:
def countLargestGroup(self, n: int) -> int:
count = defaultdict(int)
for i in range(1, n + 1):
digit_sum = sum(int(d) for d in str(i))
count[digit_sum] += 1
max_size = max(count.values())
return sum(1 for v in count.values() if v == max_size)
43.15. Longest Common Prefix
Ref: https://leetcode.com/problems/longest-common-prefix/description/
Code
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
minLen = min(len(word) for word in strs)
result = ""
for i in range(0, minLen):
currChar = strs[0][i]
if all(word[i] == currChar for word in strs):
result += currChar
else:
break
return result
43.16. Zigzag Conversion
Ref: https://leetcode.com/problems/zigzag-conversion/
Code
class Solution:
def convert(self, s: str, numRows: int) -> str:
if numRows == 1 or numRows >= len(s):
return s
# Create a list of strings for all rows
# Character in one row
rows = [''] * numRows
current_row = 0
going_down = False
for char in s:
rows[current_row] += char
# Change direction at the first or last row
if current_row == 0 or current_row == numRows - 1:
going_down = not going_down
current_row += 1 if going_down else -1
return ''.join(rows)
43.17. Text Justification
Ref: https://leetcode.com/problems/text-justification/description/
Code
from typing import List
class Solution:
def fullJustify(self, words: List[str], maxWidth: int) -> List[str]:
res = []
i = 0
while i < len(words):
# Step 1: Determine how many words fit into the current line
line_len = len(words[i])
j = i + 1
while j < len(words) and line_len + 1 + len(words[j]) <= maxWidth:
line_len += 1 + len(words[j])
j += 1
line_words = words[i:j]
num_words = j - i
total_chars = sum(len(w) for w in line_words)
num_spaces = maxWidth - total_chars
# Step 2: Build the line
# Build last line
if j == len(words) or num_words == 1:
# Last line or single word -> left justified
line = ' '.join(line_words)
line += ' ' * (maxWidth - len(line))
# Build middle line
else:
# Fully justify
spaces_between = num_words - 1
space, extra = divmod(num_spaces, spaces_between)
line = ''
for k in range(spaces_between):
line += line_words[k]
line += ' ' * (space + (1 if k < extra else 0))
line += line_words[-1] # Last word
res.append(line)
i = j
return res
43.18. Substring
Ref: https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/
Code
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
if not needle:
return 0
for i in range(len(haystack) - len(needle) + 1):
if haystack[i:i + len(needle)] == needle:
return i
return -1
43.19. Pascal’s Triangle
Ref: https://leetcode.com/problems/pascals-triangle/description/
Code
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
triangle = []
for i in range(0, numRows):
result = [1] * (i + 1) # 1,2,3,4,5,...
for j in range(1, len(result) - 1):
if i < 2:
continue
result[j] = triangle[i - 1][j - 1] + triangle[i - 1][j]
triangle.append(result)
return triangle
43.20. Unique Email Addresses
Ref: https://leetcode.com/problems/unique-email-addresses/description/
Code
class Solution:
def numUniqueEmails(self, emails: List[str]) -> int:
uniqueEmail = []
count = 0
for email in emails:
parts = email.split("@")
if len(parts) != 2:
continue
localName = parts[0]
# Remove . character
localName = localName.replace(".", "")
# Skip data after + in localName
localName = localName.split("+")[0]
# Domain name
domainName = parts[1]
# Email
email = localName + "@" + domainName
if email not in uniqueEmail:
count += 1
uniqueEmail.append(email)
return count
43.21. Isomorphic Strings
Ref: https://leetcode.com/problems/isomorphic-strings/description/
Code
class Solution:
def isIsomorphic(self, s: str, t: str) -> bool:
if len(s) != len(t):
return False
hashMapStoT = {}
hashMapTtoS = {}
for i in range(0, len(s)):
if s[i] not in hashMapStoT:
hashMapStoT[s[i]] = t[i]
if t[i] not in hashMapTtoS:
hashMapTtoS[t[i]] = s[i]
if hashMapTtoS[t[i]] != s[i]:
return False
if hashMapStoT[s[i]] != t[i]:
return False
return True
44. Minimum number Pattern
44.1. Minimum Cost to Hire K Workers
Ref: https://leetcode.com/problems/minimum-cost-to-hire-k-workers/description/
Code
class Solution:
def mincostToHireWorkers(self, quality: List[int], wage: List[int], k: int) -> float:
workers = sorted([(w/q, q) for q, w in zip(quality, wage)])
heap = []
total_quality = 0
min_cost = float('inf')
for ratio, q in workers:
heapq.heappush(heap, -q) # Max heap by pushing negative
total_quality += q
if len(heap) > k:
total_quality += heapq.heappop(heap) # Remove largest quality (least efficient)
if len(heap) == k:
cost = total_quality * ratio
min_cost = min(min_cost, cost)
return min_cost
44.2. Task Scheduler
Ref: https://leetcode.com/problems/task-scheduler/description/
Code
from typing import List
from collections import Counter
import heapq
class Solution:
def leastInterval(self, tasks: List[str], n: int) -> int:
task_counts = Counter(tasks)
# Max heap by pushing negative frequencies
max_heap = [-cnt for cnt in task_counts.values()]
heapq.heapify(max_heap)
time = 0
while max_heap:
temp = []
for _ in range(n + 1):
if max_heap:
cnt = heapq.heappop(max_heap)
if cnt < -1:
temp.append(cnt + 1) # reduce frequency
time += 1
if not max_heap and not temp:
break # all tasks done
for item in temp:
heapq.heappush(max_heap, item)
return time
44.3. Minimum Number of Refueling Stops
Ref: https://leetcode.com/problems/minimum-number-of-refueling-stops/description/
Code
from typing import List
import heapq
class Solution:
def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:
max_heap = []
fuel = startFuel
prev = 0
i = 0
refuels = 0
while fuel < target:
# Push all reachable stations into the heap
while i < len(stations) and stations[i][0] <= fuel:
# Use negative for max-heap
heapq.heappush(max_heap, -stations[i][1])
i += 1
# If no fuel stations to use and can't reach target
if not max_heap:
return -1
# Refuel with the largest available
fuel += -heapq.heappop(max_heap)
refuels += 1
return refuels
45. Two Pointer Master
Running from both ends of an array (Binary Search)
2 Sum problem (Binary Search Greedy)
45.1. Number of Subsequences That Satisfy the Given Sum Condition
Code
from typing import List
class Solution:
def numSubseq(self, nums: List[int], target: int) -> int:
MOD = 10**9 + 7
nums.sort()
n = len(nums)
# Precompute powers of 2 up to n
# The number of subsequences between left and right is 2^(right - left).
power = [1] * n
for i in range(1, n):
power[i] = (power[i - 1] * 2) % MOD
left, right = 0, n - 1
result = 0
while left <= right:
if nums[left] + nums[right] <= target:
result = (result + power[right - left]) % MOD
left += 1
else:
right -= 1
return result
45.2. Two Sum IV - Input is a BST
Ref: https://leetcode.com/problems/two-sum-iv-input-is-a-bst/
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def findTarget(self, root: Optional[TreeNode], k: int) -> bool:
if not root:
return k == 0
result = []
queue = deque([root])
# When out the loop
while queue:
level_size = len(queue)
for _ in range(level_size):
node = queue.popleft()
result.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
numToIndex = {}
for pE in range(0, len(result)):
currVal = result[pE]
if k - currVal in numToIndex:
return True
numToIndex[currVal] = pE
return False
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def findTarget(self, root: Optional[TreeNode], k: int) -> bool:
# Step 1: In-order DFS traversal to get sorted list
def in_order(node):
if not node:
return []
return in_order(node.left) + [node.val] + in_order(node.right)
nums = in_order(root)
# Step 2: Two-pointer technique to find if two numbers sum to k
left, right = 0, len(nums) - 1
while left < right:
total = nums[left] + nums[right]
if total == k:
return True
elif total < k:
left += 1
else:
right -= 1
return False
45.3. Sum of Square Numbers
Ref: https://leetcode.com/problems/sum-of-square-numbers/description/
Code
class Solution:
def judgeSquareSum(self, c: int) -> bool:
# Start with left = 0 and right = sqrt(c)
left, right = 0, int(c**0.5)
while left <= right:
total = left * left + right * right
if total == c:
return True
elif total < c:
left += 1
else:
right -= 1
return False
45.4. Boats to Save People
Ref: https://leetcode.com/problems/boats-to-save-people/
Code
from typing import List
class Solution:
def numRescueBoats(self, people: List[int], limit: int) -> int:
people.sort()
left = 0
right = len(people) - 1
boats = 0
while left <= right:
if people[left] + people[right] <= limit:
left += 1 # one boat for both
right -= 1 # heaviest person always goes
boats += 1
return boats
45.5. Minimize Maximum Pair Sum in Array
Ref: https://leetcode.com/problems/minimize-maximum-pair-sum-in-array/description/
Code
class Solution:
def minPairSum(self, nums: List[int]) -> int:
nums.sort()
max_pair_sum = 0
left = 0
right = len(nums) - 1
while left < right:
pair_sum = nums[left] + nums[right]
max_pair_sum = max(max_pair_sum, pair_sum)
left += 1
right -= 1
return max_pair_sum
45.6. 3Sum With Multiplicity
Ref: https://leetcode.com/problems/3sum-with-multiplicity/description/
Code
from typing import List
from collections import Counter
class Solution:
def twoSum(self, nums: List[int], start: int, end: int, target: int, freq: Counter) -> List[tuple]:
seen = {}
result = set()
for i in range(start, end + 1):
curr = nums[i]
complement = target - curr
pair = tuple(sorted((curr, complement)))
if complement in seen:
result.add(pair)
seen[curr] = i
# Include counts with each pair
counted_results = []
for a, b in result:
if a == b:
count = freq[a] * (freq[a] - 1) // 2
else:
count = freq[a] * freq[b]
counted_results.append((a, b, count))
return counted_results
def threeSumMulti(self, nums: List[int], target: int) -> int:
MOD = 10**9 + 7
nums.sort()
freq = Counter(nums)
n = len(nums)
count = 0
for i in range(n):
if i > 0 and nums[i] == nums[i - 1]:
continue # Skip duplicates for first value
firstVal = nums[i]
rest_target = target - firstVal
# Get unique (a, b, count) pairs from twoSum
resultTwoSum = self.twoSum(nums, i + 1, n - 1, rest_target, freq)
for secondVal, thirdVal, pairCount in resultTwoSum:
# Ensure order and uniqueness: firstVal <= secondVal <= thirdVal
triplet = tuple(sorted([firstVal, secondVal, thirdVal]))
a, b, c = triplet
# Count valid combinations depending on value equality
if a == b == c:
count += freq[a] * (freq[a] - 1) * (freq[a] - 2) // 6
elif a == b:
count += freq[a] * (freq[a] - 1) // 2 * freq[c]
elif b == c:
count += freq[b] * (freq[b] - 1) // 2 * freq[a]
elif a == c:
count += freq[a] * (freq[a] - 1) // 2 * freq[b]
else:
count += freq[a] * freq[b] * freq[c]
return count % MOD
Trap Water
45.7. Trapping Rain Water (Hold water based on left-max and right-max)
Ref: https://leetcode.com/problems/trapping-rain-water/description/
Code
class Solution:
def trap(self, height: List[int]) -> int:
left, right = 0, len(height) - 1
left_max = right_max = 0
water = 0
while left < right:
if height[left] < height[right]:
if height[left] >= left_max:
left_max = height[left]
else:
water += left_max - height[left]
left += 1
else:
if height[right] >= right_max:
right_max = height[right]
else:
water += right_max - height[right]
right -= 1
return water
45.8. Container With Most Water
Ref: https://leetcode.com/problems/container-with-most-water/description/
Code
class Solution:
def maxArea(self, height: List[int]) -> int:
left, right = 0, len(height) - 1
maxArea = 0
while left < right:
currArea = min(height[left], height[right]) * (right - left)
maxArea = max(maxArea, currArea)
if height[left] <= height[right]:
left += 1
else:
right -= 1
return maxArea
Next Permutation
45.9. Next Permutation
Ref: https://leetcode.com/problems/next-permutation/description/
Code
from typing import List
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
i = n - 2
# Step 1: Find the first decreasing element from the right
while i >= 0 and nums[i] >= nums[i + 1]:
i -= 1
if i >= 0:
# Step 2: Find the next greater element to the right of nums[i]
j = n - 1
while nums[j] <= nums[i]:
j -= 1
# Swap nums[i] and nums[j]
nums[i], nums[j] = nums[j], nums[i]
# Step 3: Reverse the subarray nums[i+1:]
left, right = i + 1, n - 1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
45.10. Next Greater Element III
Ref: https://leetcode.com/problems/next-greater-element-iii/description/
Code
class Solution:
def nextGreaterElement(self, n: int) -> int:
digits = list(str(n))
i = len(digits) - 2
# Step 1: Find the first decreasing digit from the right
while i >= 0 and digits[i] >= digits[i + 1]:
i -= 1
if i == -1:
return -1 # digits are in descending order, no next permutation
# Step 2: Find the next greater digit to the right of digits[i]
j = len(digits) - 1
while digits[j] <= digits[i]:
j -= 1
# Step 3: Swap and reverse the suffix
digits[i], digits[j] = digits[j], digits[i]
digits[i + 1:] = reversed(digits[i + 1:])
# Step 4: Convert back to integer
result = int("".join(digits))
# Step 5: Check if result is within 32-bit signed integer range
return result if result < 2**31 else -1
45.11. Minimum Adjacent Swaps to Reach the Kth Smallest Number
Ref: https://leetcode.com/problems/minimum-adjacent-swaps-to-reach-the-kth-smallest-number/description/
Code
class Solution:
def getMinSwaps(self, num: str, k: int) -> int:
def next_permutation(arr):
i = len(arr) - 2
while i >= 0 and arr[i] >= arr[i + 1]:
i -= 1
if i == -1:
return
j = len(arr) - 1
while arr[j] <= arr[i]:
j -= 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1:] = reversed(arr[i + 1:])
# Step 1: Compute the k-th permutation
target = list(num)
for _ in range(k):
next_permutation(target)
# Step 2: Count minimum adjacent swaps to match target
original = list(num)
swaps = 0
for i in range(len(original)):
if original[i] == target[i]:
continue
j = i
while original[j] != target[i]:
j += 1
# Now swap original[j] leftward to position i
while j > i:
original[j], original[j - 1] = original[j - 1], original[j]
swaps += 1
j -= 1
return swaps
Reversing / Swapping
45.12. Valid Palindrome
Ref: https://leetcode.com/problems/valid-palindrome/description/
Code
class Solution:
def isPalindrome(self, s: str) -> bool:
left, right = 0, len(s) - 1
while left < right:
# Move left to next alphanumeric
while left < right and not s[left].isalnum():
left += 1
# Move right to previous alphanumeric
while left < right and not s[right].isalnum():
right -= 1
# Compare characters
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return True
45.13. Reverse String
Ref: https://leetcode.com/problems/reverse-string/description/
Code
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
left, right = 0, len(s) - 1
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
45.14. Reverse Vowels of a String
Ref: https://leetcode.com/problems/reverse-vowels-of-a-string/description/
Code
class Solution:
def reverseVowels(self, s: str) -> str:
vowels = set('aeiouAEIOU')
s = list(s) # Convert string to list for in-place modification
left, right = 0, len(s) - 1
while left < right:
# Move left to the next vowel
while left < right and s[left] not in vowels:
left += 1
# Move right to the previous vowel
while left < right and s[right] not in vowels:
right -= 1
# Swap vowels
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
return ''.join(s)
45.15. Valid Palindrome II
Ref: https://leetcode.com/problems/valid-palindrome-ii/description/
Code
class Solution:
def validPalindrome(self, s: str) -> bool:
def is_palindrome_range(left: int, right: int) -> bool:
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
left, right = 0, len(s) - 1
while left < right:
if s[left] != s[right]:
# Try skipping left or right character
return is_palindrome_range(left + 1, right) or is_palindrome_range(left, right - 1)
left += 1
right -= 1
return True
45.16. Reverse Only Letters
Ref: https://leetcode.com/problems/reverse-only-letters/description/
Code
class Solution:
def reverseOnlyLetters(self, s: str) -> str:
s = list(s) # Convert string to list for in-place modification
left, right = 0, len(s) - 1
while left < right:
# Move left to the next vowel
while left < right and not s[left].isalpha():
left += 1
# Move right to the previous vowel
while left < right and not s[right].isalpha():
right -= 1
# Swap vowels
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
return ''.join(s)
45.17. Sort Colors (Move 0 first, move 2 last)
Ref: https://leetcode.com/problems/sort-colors/description/
Code
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
left = 0
right = len(nums) - 1
curr = 0
# Because we compare curr <= right, do not increase the left pointer
while curr <= right:
if nums[curr] == 0:
nums[left], nums[curr] = nums[curr], nums[left]
left += 1
# Because after swap: 1,2 will always > 0
curr += 1
elif nums[curr] == 2:
nums[right], nums[curr] = nums[curr], nums[right]
right -= 1
# Because after swap may be it is 2, and 2 may be > 1
else:
curr += 1
return nums
45.18. Flipping an Image
Ref: https://leetcode.com/problems/flipping-an-image/description/
Code
from typing import List
class Solution:
def flipAndInvertImage(self, image: List[List[int]]) -> List[List[int]]:
for row in image:
# Flip the row (reverse it) and invert each value (1 becomes 0, 0 becomes 1)
for i in range((len(row) + 1) // 2):
# Swap and invert in a single step
row[i], row[-i - 1] = 1 - row[-i - 1], 1 - row[i]
return image
45.19. Squares of a Sorted Array
Ref: https://leetcode.com/problems/squares-of-a-sorted-array/description/
Code
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
start, end = 0, len(nums) - 1
highestSquare = len(nums) - 1
result = [0] * len(nums)
while start <= end:
startSquare = nums[start] ** 2
endSquare = nums[end] ** 2
if startSquare > endSquare:
result[highestSquare] = startSquare
start += 1
else:
result[highestSquare] = endSquare
end -= 1
highestSquare -= 1
return result
45.20. Sort Array By Parity
Ref: https://leetcode.com/problems/sort-array-by-parity/description/
Code
from typing import List
class Solution:
def sortArrayByParity(self, nums: List[int]) -> List[int]:
left, right = 0, len(nums) - 1
while left < right:
# Move left forward if nums[left] is even
if nums[left] % 2 == 0:
left += 1
# Move right backward if nums[right] is odd
elif nums[right] % 2 == 1:
right -= 1
# If nums[left] is odd and nums[right] is even, swap them
else:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
return nums
45.21. Sort Array By Parity II
Ref: https://leetcode.com/problems/sort-array-by-parity-ii/description/
Code
class Solution:
def sortArrayByParityII(self, nums: List[int]) -> List[int]:
even_idx, odd_idx = 0, 1
n = len(nums)
while even_idx < n and odd_idx < n:
if nums[even_idx] % 2 == 0:
even_idx += 2
elif nums[odd_idx] % 2 == 1:
odd_idx += 2
else:
# nums[even_idx] is odd and nums[odd_idx] is even, swap them
nums[even_idx], nums[odd_idx] = nums[odd_idx], nums[even_idx]
even_idx += 2
odd_idx += 2
return nums
45.22. Pancake Sorting
Ref: https://leetcode.com/problems/pancake-sorting/description/
Code
from typing import List
class Solution:
def pancakeSort(self, arr: List[int]) -> List[int]:
res = []
n = len(arr)
for size in range(n, 1, -1):
# Order from max 4,3,2,1
# Find the index of the max number in arr[0:size]
max_idx = arr.index(max(arr[:size]))
if max_idx == size - 1:
continue # Already in correct position
# Flip to bring the max element to the front (if not already there)
if max_idx != 0:
res.append(max_idx + 1)
arr[:max_idx + 1] = reversed(arr[:max_idx + 1])
# Flip to move it to its correct position
res.append(size)
arr[:size] = reversed(arr[:size])
return res
45.23. Reverse Prefix of Word
Ref: https://leetcode.com/problems/reverse-prefix-of-word/description/
Code
class Solution:
def reversePrefix(self, word: str, ch: str) -> str:
idx = word.find(ch)
if idx == -1:
return word
return word[:idx + 1][::-1] + word[idx + 1:]
45.26. Reverse String II
Ref: https://leetcode.com/problems/reverse-string-ii/description/
Code
class Solution:
def reverseStr(self, s: str, k: int) -> str:
s = list(s)
for i in range(0, len(s), 2 * k):
s[i:i + k] = reversed(s[i:i + k])
return ''.join(s)
45.27. Reverse Words III
Ref: https://leetcode.com/problems/reverse-words-in-a-string-iii/description/
Code
class Solution:
def reverseWords(self, s: str) -> str:
words = s.split()
return ' '.join([word[::-1] for word in words])
Others
45.28. Bag of Tokens
Ref: https://leetcode.com/problems/bag-of-tokens/description/
Code
from typing import List
class Solution:
def bagOfTokensScore(self, tokens: List[int], power: int) -> int:
tokens.sort()
score = max_score = 0
i, j = 0, len(tokens) - 1
while i <= j:
if power >= tokens[i]:
power -= tokens[i]
score += 1
i += 1
max_score = max(max_score, score)
elif score >= 1:
power += tokens[j]
score -= 1
j -= 1
else:
break
return max_score
45.29. DI String Match
Ref: https://leetcode.com/problems/di-string-match/description/
Code
from typing import List
class Solution:
def diStringMatch(self, s: str) -> List[int]:
low, high = 0, len(s)
result = []
for char in s:
if char == 'I':
result.append(low)
low += 1
else: # char == 'D'
result.append(high)
high -= 1
result.append(low) # low == high at this point
return result
45.30. Minimum Length of String After Deleting Similar Ends
Ref: https://leetcode.com/problems/minimum-length-of-string-after-deleting-similar-ends/description/
Code
class Solution:
def minimumLength(self, s: str) -> int:
left, right = 0, len(s) - 1
while left < right and s[left] == s[right]:
char = s[left]
while left <= right and s[left] == char:
left += 1
while left <= right and s[right] == char:
right -= 1
return right - left + 1
45.31. Sentence Similarity III
Ref: https://leetcode.com/problems/sentence-similarity-iii/description/
Code
class Solution:
def areSentencesSimilar(self, sentence1: str, sentence2: str) -> bool:
words1 = sentence1.split()
words2 = sentence2.split()
# Always make words1 the shorter sentence
if len(words1) > len(words2):
words1, words2 = words2, words1
# Match from the start
i = 0
while i < len(words1) and words1[i] == words2[i]:
i += 1
# Match from the end
j = 0
while j < len(words1) - i and words1[-1 - j] == words2[-1 - j]:
j += 1
return i + j == len(words1)
45.32. Shortest Distance to a Character
Ref: https://leetcode.com/problems/shortest-distance-to-a-character/description/
Code
from typing import List
class Solution:
def shortestToChar(self, s: str, c: str) -> List[int]:
n = len(s)
result = [0] * n
# List of all indices where c appears
positions = [i for i, char in enumerate(s) if char == c]
# Pointer for positions
j = 0
for i in range(n):
# Move j if next c position is closer
while j < len(positions) - 1 and abs(positions[j + 1] - i) < abs(positions[j] - i):
j += 1
result[i] = abs(positions[j] - i)
return result
Slow & Fast Pointers
45.33. Linked List Cycle
Ref: https://leetcode.com/problems/linked-list-cycle/description/
Code
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True # Cycle detected
return False # No cycle
45.34. Linked List Cycle II
Ref: https://leetcode.com/problems/linked-list-cycle-ii/
Code
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head or not head.next:
return None
slow = head
fast = head
# Step 1: Determine whether a cycle exists
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
# Step 2: Find the entry location of the cycle
entry = head
while entry != slow:
entry = entry.next
slow = slow.next
return entry
return None
45.35. Remove Nth Node From End of List
Ref: https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/
Code
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy = ListNode(0, head)
fast = slow = dummy
# Move fast ahead by n+1 steps to maintain a gap
for _ in range(n + 1):
fast = fast.next
# Move both pointers until fast reaches the end
while fast:
fast = fast.next
slow = slow.next
# Remove the nth node from end
slow.next = slow.next.next
return dummy.next
45.36. Rotate List
Ref: https://leetcode.com/problems/rotate-list/description/
Code
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if not head or not head.next or k == 0:
return head
# Step 1: Compute the length and get the tail
length = 1
tail = head
while tail.next:
tail = tail.next
length += 1
# Step 2: Normalize k
k = k % length
if k == 0:
return head
# Step 3: Make the list circular
tail.next = head
# Step 4: Find new tail and new head
steps_to_new_tail = length - k
new_tail = head
for _ in range(steps_to_new_tail - 1):
new_tail = new_tail.next
new_head = new_tail.next
new_tail.next = None # Break the circle
return new_head
Cyclic Detection
45.37. Find the Duplicate Number
Ref: https://leetcode.com/problems/find-the-duplicate-number/description/
Code
from typing import List
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
# Phase 1: Finding the intersection point of the two runners.
slow = nums[0]
fast = nums[0]
while True:
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast:
break
# Phase 2: Find the entrance to the cycle.
slow = nums[0]
while slow != fast:
slow = nums[slow]
fast = nums[fast]
return slow
Sliding Window/Caterpillar Method
45.38. Number of Subarrays with Bounded Maximum (Idea two pointer hay)
Ref: https://leetcode.com/problems/number-of-subarrays-with-bounded-maximum/description/
Code
from typing import List
class Solution:
def numSubarrayBoundedMax(self, nums: List[int], left: int, right: int) -> int:
count = 0
prev_count = 0
start = -1
for i, num in enumerate(nums):
# num > right (invalid)
if num > right:
start = i
prev_count = 0
# left <= num <= right
elif num >= left:
prev_count = i - start
count += prev_count
# num < left
else:
count += prev_count
return count
45.39. Count Binary Substrings
Ref: https://leetcode.com/problems/count-binary-substrings/description/
Code
class Solution:
def countBinarySubstrings(self, s: str) -> int:
prev = 0 # previous group length
curr = 1 # current group length
count = 0
for i in range(1, len(s)):
if s[i] == s[i - 1]:
curr += 1
else:
count += min(prev, curr)
prev = curr
curr = 1
# Last group pair
count += min(prev, curr)
return count
45.40. K-diff Pairs in an Array (Idea two pointer hay)
Ref: https://leetcode.com/problems/k-diff-pairs-in-an-array/description/
Code
from typing import List
class Solution:
def findPairs(self, nums: List[int], k: int) -> int:
if k < 0:
return 0 # Absolute difference cannot be negative
count = 0
seen = {}
for num in nums:
seen[num] = seen.get(num, 0) + 1
if k == 0:
# Count elements with frequency >= 2
for val in seen.values():
if val > 1:
count += 1
else:
# Count unique pairs where num + k exists
for num in seen:
if num + k in seen:
count += 1
return count
Rotation
45.41. Rotating the Box
Ref: https://leetcode.com/problems/rotating-the-box/description/
Code
class Solution:
def rotateTheBox(self, boxGrid: List[List[str]]) -> List[List[str]]:
m, n = len(boxGrid), len(boxGrid[0])
# Step 1: Apply gravity before rotation (simulate rightward fall)
for row in boxGrid:
write = n - 1
for col in reversed(range(n)):
if row[col] == '*':
write = col - 1
elif row[col] == '#':
row[col] = '.'
row[write] = '#'
write -= 1
# Step 2: Rotate the boxGrid clockwise
rotated = [[None] * m for _ in range(n)]
for i in range(m):
for j in range(n):
rotated[j][m - 1 - i] = boxGrid[i][j]
return rotated
45.42. Rotate Array
Ref: https://leetcode.com/problems/rotate-array/description/
Code
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
original = nums[:]
# Loop in the end
for i in range(n - 1, -1, -1):
nums[(i + k) % n] = original[i]
# n = len(nums)
# k %= n # In case k > n
# # Copy the last k elements + the rest
# nums[:] = nums[-k:] + nums[:-k]
String
45.43. String Compression
Ref: https://leetcode.com/problems/string-compression/description/
Code
from typing import List
class Solution:
def compress(self, chars: List[str]) -> int:
res = []
count = 1 # Start with 1 since we always have at least one occurrence
for i in range(1, len(chars) + 1):
if i < len(chars) and chars[i] == chars[i - 1]:
count += 1
else:
# End of a group
res.append(chars[i - 1])
if count > 1:
for digit in str(count):
res.append(digit)
count = 1 # Reset count for the next character group
# Modify input list in-place
chars[:] = res
return len(res)
45.44. Last Substring in Lexicographical Order
Ref: https://leetcode.com/problems/last-substring-in-lexicographical-order/description/
Code
class Solution:
def lastSubstring(self, s: str) -> str:
max_char = max(s)
candidates = [i for i, c in enumerate(s) if c == max_char]
return max(s[i:] for i in candidates)
class Solution:
def lastSubstring(self, s: str) -> str:
n = len(s)
i, j = 0, 1
k = 0 # offset
while j + k < n:
if s[i + k] == s[j + k]:
k += 1
elif s[i + k] > s[j + k]:
j = j + k + 1
k = 0
else:
i = max(i + k + 1, j)
j = i + 1
k = 0
return s[i:]
Remove duplicates
45.45. Remove Duplicates from Sorted Array
Ref: https://leetcode.com/problems/remove-duplicates-from-sorted-array/description/
Code
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
nextNonDup = 0
pE = 0
while pE < len(nums):
if pE == 0 or nums[pE] != nums[nextNonDup - 1]:
nums[nextNonDup] = nums[pE]
nextNonDup += 1
pE += 1
return nextNonDup
45.46. Remove Duplicates from Sorted Array II
Ref: https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/description/
Code
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
nextNonDup = 0
pE = 0
while pE < len(nums):
if pE < 2 or nums[pE] != nums[nextNonDup - 1] or nums[pE] != nums[nextNonDup - 2]:
nums[nextNonDup] = nums[pE]
nextNonDup += 1
pE += 1
return nextNonDup
45.47. Remove Duplicates from Sorted List II
Ref: https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/description/
Code
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
current = head
while current and current.next:
if current.val == current.next.val:
current.next = current.next.next # Skip the duplicate
else:
current = current.next # Move forward only when values differ
return head
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(0)
dummy.next = head
prev = dummy
current = head
while current:
# Detect duplicates
if current.next and current.val == current.next.val:
# Skip all nodes with the same value
while current.next and current.val == current.next.val:
current = current.next
prev.next = current.next # Bypass all duplicates
else:
prev = prev.next # Move forward if no duplicate
current = current.next
return dummy.next
45.48. Duplicate Zeros (Hay)
Ref: https://leetcode.com/problems/duplicate-zeros/description/
Code
from typing import List
class Solution:
def duplicateZeros(self, arr: List[int]) -> None:
"""
Do not return anything, modify arr in-place instead.
"""
n = len(arr)
zeros = 0
# Count the number of zeros to be duplicated
for i in range(n):
if arr[i] == 0:
zeros += 1
# Start from the end and move elements backwards
i = n - 1
j = n + zeros - 1
while i < j:
if j < n:
if arr[i] != 0:
arr[j] = arr[i]
else:
arr[j] = 0
j -= 1
if arr[i] == 0:
if j < n:
arr[j] = 0
j -= 1
i -= 1
45.49. Magical String
Ref: https://leetcode.com/problems/magical-string/description/
Code
class Solution:
def magicalString(self, n: int) -> int:
if n <= 0:
return 0
if n <= 3:
return 1
s = [1, 2, 2]
i = 2 # pointer to the index in s that tells us how many times to append
num = 1 # next number to append (flip between 1 and 2)
while len(s) < n:
s.extend([num] * s[i])
num = 3 - num # flip between 1 and 2
i += 1
return s[:n].count(1)
45.50. Friends Of Appropriate Ages
Ref: https://leetcode.com/problems/friends-of-appropriate-ages/description/
Code
from typing import List
from collections import Counter
class Solution:
def numFriendRequests(self, ages: List[int]) -> int:
count = Counter(ages)
total = 0
for ageA in count:
for ageB in count:
if ageB <= 0.5 * ageA + 7:
continue
if ageB > ageA:
continue
if ageB > 100 and ageA < 100:
continue
if ageA == ageB:
total += count[ageA] * (count[ageA] - 1)
else:
total += count[ageA] * count[ageB]
return total
45.51. Longest Mountain in Array (Hay)
Ref: https://leetcode.com/problems/longest-mountain-in-array/description/
Code
from typing import List
class Solution:
def longestMountain(self, arr: List[int]) -> int:
n = len(arr)
longest = 0
i = 1 # Start from second element
while i < n - 1:
# Check if arr[i] is a peak
if arr[i - 1] < arr[i] > arr[i + 1]:
# Expand left
left = i - 1
while left > 0 and arr[left - 1] < arr[left]:
left -= 1
# Expand right
right = i + 1
while right < n - 1 and arr[right] > arr[right + 1]:
right += 1
# Update longest mountain
longest = max(longest, right - left + 1)
i = right # Move i to the end of this mountain
else:
i += 1 # Not a peak, move forward
return longest
45.52. Shortest Subarray to be Removed to Make Array Sorted
Ref: https://leetcode.com/problems/shortest-subarray-to-be-removed-to-make-array-sorted/description/
Code
class Solution:
def findLengthOfShortestSubarray(self, arr: List[int]) -> int:
n = len(arr)
# Find the longest non-decreasing prefix
left = 0
while left < n - 1 and arr[left] <= arr[left + 1]:
left += 1
# If the entire array is non-decreasing
if left == n - 1:
return 0
# Find the longest non-decreasing suffix
right = n - 1
while right > 0 and arr[right] >= arr[right - 1]:
right -= 1
# We have two options:
# 1. Remove everything after left
# 2. Remove everything before right
result = min(n - left - 1, right)
# Try to merge the prefix and suffix
i = 0
j = right
while i <= left and j < n:
if arr[i] <= arr[j]:
# We can merge from i to j
result = min(result, j - i - 1)
i += 1
else:
j += 1
return result
Running from beginning of 2 arrays / Merging 2 arrays
Sorted arrays
45.43. Merge Sorted Array
Ref: https://leetcode.com/problems/merge-sorted-array/description/
Code
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
# Create subarrays for nums1 and nums2
subNums1 = nums1[:m] if m > 0 else []
subNums2 = nums2[:n] if n > 0 else []
i = 0
j = 0
k = 0
res = [0] * (m + n)
# Merge the two arrays into res
while i < m and j < n:
if subNums1[i] < subNums2[j]:
res[k] = subNums1[i]
i += 1
else:
res[k] = subNums2[j]
j += 1
k += 1
while i < m:
res[k] = subNums1[i]
i += 1
k += 1
while j < n:
res[k] = subNums2[j]
j += 1
k += 1
# Modify nums1 in-place
for index in range(m + n):
nums1[index] = res[index]
45.44. Heaters (Hay)
Ref: https://leetcode.com/problems/heaters/description/
Code
from typing import List
import bisect
class Solution:
def findRadius(self, houses: List[int], heaters: List[int]) -> int:
houses.sort()
heaters.sort()
radius = 0
for house in houses:
# Find the position to insert the house in the sorted heaters list
index = bisect.bisect_left(heaters, house)
# Calculate distances to the nearest heater on the left and right
left_dist = float('inf') if index == 0 else house - heaters[index - 1]
right_dist = float('inf') if index == len(heaters) else heaters[index] - house
# The nearest heater distance for this house
nearest = min(left_dist, right_dist)
# Update the required radius to cover all houses
radius = max(radius, nearest)
return radius
45.45. Find the Distance Value Between Two Arrays (Hay)
Ref: https://leetcode.com/problems/find-the-distance-value-between-two-arrays/description/
Code
from typing import List
import bisect
class Solution:
def findTheDistanceValue(self, arr1: List[int], arr2: List[int], d: int) -> int:
arr2.sort()
count = 0
for num in arr1:
# Binary search to find the insertion point in arr2
index = bisect.bisect_left(arr2, num)
# Check left neighbor and right neighbor (if any)
left_close = abs(num - arr2[index - 1]) if index > 0 else float('inf')
right_close = abs(num - arr2[index]) if index < len(arr2) else float('inf')
# If both neighbors are farther than d, count this number
if min(left_close, right_close) > d:
count += 1
return count
Intersections/LCA like
45.46. Intersection of Two Linked Lists
Ref: https://leetcode.com/problems/intersection-of-two-linked-lists/description/
Code
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
from typing import Optional
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
if not headA or not headB:
return None
ptrA, ptrB = headA, headB
# List A: A1 → A2 → A3
# List B: B1 → B2 → B3 → B4 → B5
# ptrA path: A1 → A2 → A3 → B1 → B2 → B3 → B4 → B5 → None
# ptrB path: B1 → B2 → B3 → B4 → B5 → A1 → A2 → A3 → None
while ptrA != ptrB:
# Move each pointer to the next node, or switch lists at the end
ptrA = ptrA.next if ptrA else headB
ptrB = ptrB.next if ptrB else headA
# They either meet at the intersection or both become None (no intersection)
return ptrA
45.47. Intersection of Two Arrays
Ref: https://leetcode.com/problems/intersection-of-two-arrays/description/
Code
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
45.47. Intersection of Two Arrays 2 (Hay)
Ref: https://leetcode.com/problems/intersection-of-two-arrays-ii/description/
Code
from typing import List
from collections import Counter
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
counts1 = Counter(nums1)
result = []
for num in nums2:
if counts1[num] > 0:
result.append(num)
counts1[num] -= 1
return result
SubString
45.48. Find the Index of the First Occurrence in a String
Ref: https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/description/
Code
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
if not needle:
return 0
for i in range(len(haystack) - len(needle) + 1):
if haystack[i:i + len(needle)] == needle:
return i
return -1
45.49. Longest Word in Dictionary through Deleting
Ref: https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/
Code
from typing import List
class Solution:
def findLongestWord(self, s: str, dictionary: List[str]) -> str:
def is_subsequence(word: str, s: str) -> bool:
# Check if word is a subsequence of s
i = 0
for char in s:
if i < len(word) and word[i] == char:
i += 1
return i == len(word)
longest = ""
for word in dictionary:
if is_subsequence(word, s):
if len(word) > len(longest) or (len(word) == len(longest) and word < longest):
longest = word
return longest
45.50. Long Pressed Name (Hay, Không dùng is_subsequence được)
Ref: https://leetcode.com/problems/long-pressed-name/description/
Code
class Solution:
def isLongPressedName(self, name: str, typed: str) -> bool:
i = j = 0 # i for name, j for typed
while j < len(typed):
if i < len(name) and name[i] == typed[j]:
i += 1
j += 1
elif j > 0 and typed[j] == typed[j - 1]:
j += 1
else:
return False
return i == len(name)
46. Bit Manipulation
Gray code
46.1. Gray code
Ref: https://leetcode.com/problems/gray-code/
Code
from typing import List
class Solution:
def grayCode(self, n: int) -> List[int]:
result = []
for i in range(1 << n): # 2^n combinations
result.append(i ^ (i >> 1))
return result
46.2. Circular Permutation in Binary Representation
Ref: https://leetcode.com/problems/circular-permutation-in-binary-representation/description/
Code
class Solution:
def circularPermutation(self, n: int, start: int) -> List[int]:
# Step 1: Generate the standard Gray code sequence
gray_codes = [i ^ (i >> 1) for i in range(1 << n)]
# Step 2: Find the index of the starting number
start_index = gray_codes.index(start)
# Step 3: Rotate the sequence so it starts from `start`
return gray_codes[start_index:] + gray_codes[:start_index]
Power of n
46.3. Power of Two
Ref: https://leetcode.com/problems/power-of-two/description/
Code
class Solution:
def isPowerOfTwo(self, n: int) -> bool:
return n > 0 and (n & (n - 1)) == 0
46.4. Power of Three
Ref: https://leetcode.com/problems/power-of-three/description/
Code
class Solution:
def isPowerOfThree(self, n: int) -> bool:
if n < 1:
return False
while n % 3 == 0:
n //= 3
return n == 1
46.5. Power of Four
Ref: https://leetcode.com/problems/power-of-four/description/
Code
class Solution:
def isPowerOfFour(self, n: int) -> bool:
return n > 0 and (n & (n - 1)) == 0 and (n & 0x55555555) != 0
Question on the Basic Prop of xor
46.6. Single Number
Ref: https://leetcode.com/problems/single-number/description/
Code
from typing import List
class Solution:
def singleNumber(self, nums: List[int]) -> int:
result = 0
for num in nums:
result ^= num # XOR cancels out duplicates
return result
46.7. Single Number II
Ref: https://leetcode.com/problems/single-number/
Code
from typing import List
class Solution:
def singleNumber(self, nums: List[int]) -> int:
result = 0
for i in range(32):
bit_sum = sum((num >> i) & 1 for num in nums)
if bit_sum % 3 != 0:
result |= (1 << i)
# Handle negative numbers (since Python ints are not limited to 32-bit)
if result >= 2**31:
result -= 2**32
return result
46.8. Single Number III
Ref: https://leetcode.com/problems/single-number-iii/description/
Code
from typing import List
class Solution:
def singleNumber(self, nums: List[int]) -> List[int]:
# Step 1: XOR all numbers. The result is the XOR of the two unique numbers.
xor_all = 0
for num in nums:
xor_all ^= num
# Step 2: Find a bit that is set (i.e., 1) in xor_all.
# This bit is different between the two unique numbers.
diff_bit = xor_all & -xor_all
# Step 3: Divide numbers into two groups and XOR separately.
result = [0, 0]
for num in nums:
if num & diff_bit:
result[0] ^= num
else:
result[1] ^= num
return result
46.9. Find The Original Array of Prefix Xor
Ref: https://leetcode.com/problems/find-the-original-array-of-prefix-xor/description/
Code
class Solution:
def findArray(self, pref: List[int]) -> List[int]:
n = len(pref)
arr = [0] * n
arr[0] = pref[0]
for i in range(1, n):
arr[i] = pref[i] ^ pref[i - 1]
return arr
46.10. Maximum XOR of Two Numbers in an Array
Ref: https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/description/
Code
from typing import List
class TrieNode:
def __init__(self):
self.children = {} # keys: 0 or 1
class Solution:
def findMaximumXOR(self, nums: List[int]) -> int:
# Build Trie
root = TrieNode()
for num in nums:
node = root
for i in range(31, -1, -1): # 32-bit integers
bit = (num >> i) & 1
if bit not in node.children:
node.children[bit] = TrieNode()
node = node.children[bit]
max_xor = 0
for num in nums:
node = root
curr_xor = 0
for i in range(31, -1, -1):
bit = (num >> i) & 1
opposite = 1 - bit
if opposite in node.children:
curr_xor |= (1 << i)
node = node.children[opposite]
else:
node = node.children.get(bit, node)
max_xor = max(max_xor, curr_xor)
return max_xor
46.11. XOR Queries of a Subarray
Ref: https://leetcode.com/problems/xor-queries-of-a-subarray/description/
Code
from typing import List
class Solution:
def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]:
n = len(arr)
prefix = [0] * n
prefix[0] = arr[0]
for i in range(1, n):
prefix[i] = prefix[i - 1] ^ arr[i]
result = []
for l, r in queries:
if l == 0:
result.append(prefix[r])
else:
result.append(prefix[r] ^ prefix[l - 1])
return result
47. Line Sweep (Quét hết khoảng)
1-axis Problem
47.1. Maximum Population Year
Ref: https://leetcode.com/problems/maximum-population-year/description/
Code
from typing import List
class Solution:
def maximumPopulation(self, logs: List[List[int]]) -> int:
population = [0] * 101 # years 1950 to 2050 → index 0 to 100
for birth, death in logs:
population[birth - 1950] += 1
population[death - 1950] -= 1
max_pop = year = 0
current_pop = 0
for i in range(101):
current_pop += population[i]
if current_pop > max_pop:
max_pop = current_pop
year = 1950 + i
return year
47.2. Points That Intersect With Cars
Ref: https://leetcode.com/problems/points-that-intersect-with-cars/description/
Code
from typing import List
class Solution:
def numberOfPoints(self, nums: List[List[int]]) -> int:
points = set()
for start, end in nums:
for x in range(start, end + 1):
points.add(x)
return len(points)
47.3. Meeting Rooms II
Ref: https://leetcode.com/problems/meeting-rooms-ii/description/
Code
import heapq
from typing import List
class Solution:
def minMeetingRooms(self, intervals: List[List[int]]) -> int:
if not intervals:
return 0
# Sort by start time
intervals.sort(key=lambda x: x[0])
# Min-heap for end times
heap = []
for interval in intervals:
start, end = interval
# Free up rooms that are done
if heap and heap[0] <= start:
heapq.heappop(heap)
# Allocate new room (push current meeting's end time)
heapq.heappush(heap, end)
return len(heap) # Max number of concurrent meetings
47.4. My Calendar II
Ref: https://leetcode.com/problems/my-calendar-ii/description/
Code
class MyCalendarTwo:
def __init__(self):
self.booked = []
self.overlaps = []
def book(self, start: int, end: int) -> bool:
# Check if it would cause a triple booking
for os, oe in self.overlaps:
if start < oe and end > os:
return False
# Add overlaps with existing bookings into overlaps list
for bs, be in self.booked:
if start < be and end > bs:
self.overlaps.append((max(start, bs), min(end, be)))
# Add to booked list
self.booked.append((start, end))
return True
47.5. Count Positions on Street With Required Brightness
Ref: https://leetcode.com/problems/count-positions-on-street-with-required-brightness/
Code
from typing import List
class Solution:
def meetRequirement(self, n: int, lights: List[List[int]], requirement: List[int]) -> int:
diff = [0] * (n + 1) # Difference array
# Apply range increment for each light
for pos, rng in lights:
left = max(0, pos - rng)
right = min(n - 1, pos + rng)
diff[left] += 1
diff[right + 1] -= 1
# Build brightness array using prefix sum
brightness = [0] * n
curr = 0
for i in range(n):
curr += diff[i]
brightness[i] = curr
# Count how many positions meet or exceed requirement
count = 0
for i in range(n):
if brightness[i] >= requirement[i]:
count += 1
return count
47.6. Check if All the Integers in a Range Are Covered
Ref: https://leetcode.com/problems/check-if-all-the-integers-in-a-range-are-covered/
Code
from typing import List
class Solution:
def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool:
diff = [0] * 52 # We use size 52 because 1 ≤ left, right ≤ 50
# Mark the difference array for each range
for start, end in ranges:
diff[start] += 1
if end + 1 <= 51:
diff[end + 1] -= 1
# Convert to prefix sum to get actual coverage
for i in range(1, 52):
diff[i] += diff[i - 1]
# Check if all numbers in [left, right] are covered
for i in range(left, right + 1):
if diff[i] <= 0:
return False
return True
47.7. Range Addition
Ref: https://leetcode.com/problems/range-addition/
Code
from typing import List
class Solution:
def getModifiedArray(self, length: int, updates: List[List[int]]) -> List[int]:
res = [0] * (length + 1) # Use one extra space for easier subtraction
# Apply difference array technique
for start, end, inc in updates:
res[start] += inc
if end + 1 < len(res):
res[end + 1] -= inc
# Convert to prefix sum to finalize updates
for i in range(1, length):
res[i] += res[i - 1]
return res[:length]
47.8. Car Pooling
Ref: https://leetcode.com/problems/car-pooling/
Code
class Solution:
def carPooling(self, trips: List[List[int]], capacity: int) -> bool:
# Use a list to store the change in passengers at each location
passenger_changes = [0] * 1001 # Locations range from 0 to 1000
for num_passengers, start, end in trips:
passenger_changes[start] += num_passengers
passenger_changes[end] -= num_passengers
current_passengers = 0
for passengers in passenger_changes:
current_passengers += passengers
if current_passengers > capacity:
return False
return True
47.9. Minimum Number of Arrows to Burst Balloons
Ref: https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/description/
Code
from typing import List
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
if not points:
return 0
# Sort the balloons based on their ending x-coordinate
points.sort(key=lambda x: x[1])
arrows = 1
end = points[0][1]
for start, curr_end in points[1:]:
if start > end:
arrows += 1
end = curr_end
return arrows
47.10. Non-overlapping Intervals
Ref: https://leetcode.com/problems/non-overlapping-intervals/description/
Code
from typing import List
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
if not intervals:
return 0
# Sort intervals by end time
intervals.sort(key=lambda x: x[1])
count = 0
prev_end = intervals[0][1]
for i in range(1, len(intervals)):
start, end = intervals[i]
if start < prev_end:
# Overlap found, need to remove one
count += 1
else:
# No overlap, update prev_end
prev_end = end
return count
47.11. Maximum Length of Pair Chain
Ref: https://leetcode.com/problems/maximum-length-of-pair-chain/
Code
from typing import List
class Solution:
def findLongestChain(self, pairs: List[List[int]]) -> int:
# Sort pairs by the second element (end of the pair)
pairs.sort(key=lambda x: x[1])
curr_end = float('-inf')
count = 0
for start, end in pairs:
if start > curr_end:
count += 1
curr_end = end
return count
47.12. Insert Interval
Ref: https://leetcode.com/problems/insert-interval/
Code
from typing import List
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
result = []
i = 0
n = len(intervals)
# Add all intervals ending before newInterval starts
while i < n and intervals[i][1] < newInterval[0]:
result.append(intervals[i])
i += 1
# Merge overlapping intervals with newInterval
while i < n and intervals[i][0] <= newInterval[1]:
newInterval[0] = min(newInterval[0], intervals[i][0])
newInterval[1] = max(newInterval[1], intervals[i][1])
i += 1
result.append(newInterval)
# Add remaining intervals
while i < n:
result.append(intervals[i])
i += 1
return result
47.13. Remove Interval
Ref: https://leetcode.com/problems/remove-interval/
Code
def removeInterval(intervals, toBeRemoved):
result = []
for start, end in intervals:
# No overlap
if toBeRemoved[1] <= start or toBeRemoved[0] >= end:
result.append([start, end])
else:
# Left non-overlapping part
if start < toBeRemoved[0]:
result.append([start, toBeRemoved[0]])
# Right non-overlapping part
if end > toBeRemoved[1]:
result.append([toBeRemoved[1], end])
return result
47.14. Remove Covered Intervals
Ref: https://leetcode.com/problems/remove-covered-intervals/description/
Code
from typing import List
class Solution:
def removeCoveredIntervals(self, intervals: List[List[int]]) -> int:
# Sort intervals by start ascending, and end descending
intervals.sort(key=lambda x: (x[0], -x[1]))
count = 0
prev_end = 0
for _, end in intervals:
# If current end is greater than previous, it's not covered
if end > prev_end:
count += 1
prev_end = end
# else: current interval is covered
return count
47.15. Maximum Number of Events That Can Be Attended
Ref: https://leetcode.com/problems/maximum-number-of-events-that-can-be-attended/
Code
from typing import List
import heapq
class Solution:
def maxEvents(self, events: List[List[int]]) -> int:
# Sort events by their start day
events.sort()
total_days = max(end for _, end in events)
event_index = 0
attended = 0
min_heap = []
for day in range(1, total_days + 1):
# Add all events that start today
while event_index < len(events) and events[event_index][0] == day:
heapq.heappush(min_heap, events[event_index][1])
event_index += 1
# Remove events that already expired
while min_heap and min_heap[0] < day:
heapq.heappop(min_heap)
# Attend the event that ends the earliest
if min_heap:
heapq.heappop(min_heap)
attended += 1
return attended
47.16. Maximum Sum Obtained of Any Permutation
Ref: https://leetcode.com/problems/maximum-sum-obtained-of-any-permutation/description/
Code
from typing import List
class Solution:
def maxSumRangeQuery(self, nums: List[int], requests: List[List[int]]) -> int:
MOD = 10**9 + 7
n = len(nums)
freq = [0] * (n + 1)
# Use prefix sums to count frequency of each index in requests
for start, end in requests:
freq[start] += 1
if end + 1 < n:
freq[end + 1] -= 1
# Prefix sum to get the exact frequency for each index
for i in range(1, n):
freq[i] += freq[i - 1]
freq.pop() # remove the extra element used for prefix sum
# Sort nums and freq in descending order
nums.sort(reverse=True)
freq.sort(reverse=True)
# Calculate max sum by multiplying sorted nums and freq
result = 0
for f, num in zip(freq, nums):
result = (result + f * num) % MOD
return result
47.17. Describe the Painting
Ref: https://leetcode.com/problems/describe-the-painting/description/
Code
from typing import List
from collections import defaultdict
class Solution:
def splitPainting(self, segments: List[List[int]]) -> List[List[int]]:
changes = defaultdict(int)
# Record color increments and decrements at segment boundaries
for start, end, color in segments:
changes[start] += color
changes[end] -= color
points = sorted(changes.keys())
result = []
curr_color = 0
prev_point = points[0]
for i in range(len(points)):
point = points[i]
if i > 0 and curr_color > 0:
# Create segment from prev_point to current point
result.append([prev_point, point, curr_color])
# Update curr_color after recording the segment
curr_color += changes[point]
prev_point = point
return result
47.18. Minimum Moves to Make Array Complementary
Ref: https://leetcode.com/problems/minimum-moves-to-make-array-complementary/description/
Code
from typing import List
class Solution:
def minMoves(self, nums: List[int], limit: int) -> int:
n = len(nums)
diff = [0] * (2 * limit + 2) # difference array for sums from 2 to 2*limit
for i in range(n // 2):
a, b = nums[i], nums[n - 1 - i]
low = 1 + min(a, b)
high = limit + max(a, b)
# For all sums, initially assume 2 moves per pair
diff[2] += 2
# For sums from low to high, reduce moves by 1 (1 move needed)
diff[low] -= 1
diff[high + 1] += 1
# For the exact sum a + b, reduce moves by another 1 (0 moves needed)
diff[a + b] -= 1
diff[a + b + 1] += 1
min_moves = float('inf')
curr = 0
# Calculate prefix sums over diff array to get moves needed for each sum
for s in range(2, 2 * limit + 1):
curr += diff[s]
min_moves = min(min_moves, curr)
return min_moves
47.19. Amount of New Area Painted Each Day
Ref: https://leetcode.com/problems/amount-of-new-area-painted-each-day/
Code
from typing import List
class Solution:
def amountPainted(self, paint: List[List[int]]) -> List[int]:
painted = set()
result = []
for start, end in paint:
new_paint = 0
i = start
while i < end:
if i not in painted:
painted.add(i)
new_paint += 1
i += 1
result.append(new_paint)
return result
47.20. Number of Flowers in Full Bloom
Ref: https://leetcode.com/problems/number-of-flowers-in-full-bloom/description/
Code
from typing import List
import bisect
class Solution:
def fullBloomFlowers(self, flowers: List[List[int]], people: List[int]) -> List[int]:
starts = sorted(f[0] for f in flowers)
ends = sorted(f[1] for f in flowers)
result = []
for t in people:
# Flowers that started blooming on or before t
started = bisect.bisect_right(starts, t)
# Flowers that ended before t (not blooming at t)
ended = bisect.bisect_left(ends, t)
result.append(started - ended)
return result
47.21. Minimum Number of Taps to Open to Water a Garden
Ref: https://leetcode.com/problems/minimum-number-of-taps-to-open-to-water-a-garden/description/
Code
from typing import List
class Solution:
def minTaps(self, n: int, ranges: List[int]) -> int:
max_range = [0] * (n + 1)
# Convert to [start, end] intervals and track max reach from each start
for i in range(n + 1):
left = max(0, i - ranges[i])
right = min(n, i + ranges[i])
max_range[left] = max(max_range[left], right)
taps = 0
curr_end = 0
next_end = 0
for i in range(n + 1):
if i > next_end:
return -1 # Cannot reach this point
if i > curr_end:
taps += 1
curr_end = next_end
next_end = max(next_end, max_range[i])
return taps
47.22. My Calendar III
Ref: https://leetcode.com/problems/my-calendar-iii/
Code
from collections import defaultdict
class MyCalendarThree:
def __init__(self):
self.timeline = defaultdict(int)
self.max_overlap = 0
def book(self, startTime: int, endTime: int) -> int:
self.timeline[startTime] += 1
self.timeline[endTime] -= 1
# Sort keys and compute current overlap
active = 0
for time in sorted(self.timeline):
active += self.timeline[time]
self.max_overlap = max(self.max_overlap, active)
return self.max_overlap
47.23. Employee Free Time
Ref: https://leetcode.com/problems/employee-free-time/description/
Code
# Definition for an Interval.
class Interval:
def __init__(self, start: int, end: int):
self.start = start
self.end = end
class Solution:
def employeeFreeTime(self, schedule: 'List[List[Interval]]') -> 'List[Interval]':
# Flatten all intervals
intervals = [i for employee in schedule for i in employee]
# Sort by start time
intervals.sort(key=lambda x: x.start)
merged = []
for interval in intervals:
if not merged or merged[-1].end < interval.start:
merged.append(interval)
else:
merged[-1].end = max(merged[-1].end, interval.end)
# Find gaps between merged intervals
free_time = []
for i in range(1, len(merged)):
free_time.append(Interval(merged[i-1].end, merged[i].start))
return free_time
47.24. Minimum Interval to Include Each Query
Ref: https://leetcode.com/problems/minimum-interval-to-include-each-query/description/
Code
from typing import List
import heapq
class Solution:
def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
intervals.sort()
sorted_queries = sorted((q, i) for i, q in enumerate(queries))
result = [-1] * len(queries)
min_heap = []
i = 0 # Pointer for intervals
for q, idx in sorted_queries:
# Add all intervals where start ≤ q
while i < len(intervals) and intervals[i][0] <= q:
start, end = intervals[i]
if end >= q:
heapq.heappush(min_heap, (end - start + 1, end))
i += 1
# Remove intervals from heap that end < q
while min_heap and min_heap[0][1] < q:
heapq.heappop(min_heap)
if min_heap:
result[idx] = min_heap[0][0] # Length of smallest valid interval
return result
2D Array
47.25. Rectangle Area II (Hay)
Ref: https://leetcode.com/problems/rectangle-area-ii/description/
Code
from typing import List
from bisect import bisect_left, insort
from collections import defaultdict
class Solution:
def rectangleArea(self, rectangles: List[List[int]]) -> int:
MOD = 10**9 + 7
events = []
for r in rectangles:
# (x, type, y1, y2)
events.append([r[0], 0, r[1], r[3]]) # opening edge
events.append([r[2], 1, r[1], r[3]]) # closing edge
# Sort by x, then by type (open before close)
events.sort(key=lambda x: (x[0], x[1]))
yline = [] # active intervals as (y, type): 1 = entry, -1 = exit
prev_x = None
area = 0
def get_area(width):
yline_sorted = sorted(yline)
count = 0
total_y = 0
prev_y = None
for y, typ in yline_sorted:
if count == 0:
prev_y = y
count += typ
if count == 0 and prev_y is not None:
total_y += y - prev_y
return total_y * width
for x, typ, y1, y2 in events:
if prev_x is not None:
width = x - prev_x
area = (area + get_area(width)) % MOD
if typ == 0:
# insert y1 (entry) and y2 (exit)
yline.append((y1, 1))
yline.append((y2, -1))
else:
# remove y1 (entry) and y2 (exit)
yline.remove((y1, 1))
yline.remove((y2, -1))
prev_x = x
return area
47.26. Perfect Rectangle
Ref: https://leetcode.com/problems/perfect-rectangle/description/
Code
from typing import List
class Solution:
def isRectangleCover(self, rectangles: List[List[int]]) -> bool:
corner_set = set()
total_area = 0
min_x = float('inf')
min_y = float('inf')
max_x = float('-inf')
max_y = float('-inf')
for x1, y1, x2, y2 in rectangles:
# Update bounding box
min_x = min(min_x, x1)
min_y = min(min_y, y1)
max_x = max(max_x, x2)
max_y = max(max_y, y2)
# Accumulate area
total_area += (x2 - x1) * (y2 - y1)
# Define all four corners
corners = [(x1, y1), (x1, y2), (x2, y1), (x2, y2)]
# Use a set to track corners
for c in corners:
if c in corner_set:
corner_set.remove(c)
else:
corner_set.add(c)
# Final bounding rectangle area
expected_area = (max_x - min_x) * (max_y - min_y)
if total_area != expected_area:
return False
# Check corners — must match the 4 corners of bounding rectangle
expected_corners = {(min_x, min_y), (min_x, max_y), (max_x, min_y), (max_x, max_y)}
return corner_set == expected_corners
47.27. The Skyline Problem
Ref: https://leetcode.com/problems/the-skyline-problem/description/
Code
from typing import List
import heapq
class Solution:
def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
# Create events: (x, height, start/end)
events = []
for left, right, height in buildings:
events.append((left, -height, right)) # start event; height negated to make max-heap using min-heap
events.append((right, 0, 0)) # end event
# Sort by x, then by height
events.sort()
result = []
# Heap of active buildings: (neg_height, end)
heap = [(0, float('inf'))] # dummy building with height 0 to avoid empty heap
prev_max = 0
for x, neg_height, right in events:
# Remove buildings from heap that ended before or at x
while heap and heap[0][1] <= x:
heapq.heappop(heap)
if neg_height != 0:
# Add building height and end to heap
heapq.heappush(heap, (neg_height, right))
current_max = -heap[0][0] if heap else 0
if current_max != prev_max:
result.append([x, current_max])
prev_max = current_max
return result
47.28. Erect the Fence
Ref: https://leetcode.com/problems/erect-the-fence/description/
Code
from typing import List
class Solution:
def outerTrees(self, trees: List[List[int]]) -> List[List[int]]:
# Cross product of OA and OB vectors
def cross(o, a, b):
return (a[0]-o[0])*(b[1]-o[1]) - (a[1]-o[1])*(b[0]-o[0])
# Sort points
points = sorted(trees)
if len(points) <= 3:
return points
# Build lower hull
lower = []
for p in points:
while len(lower) >= 2 and cross(lower[-2], lower[-1], p) < 0:
lower.pop()
lower.append(p)
# Build upper hull
upper = []
for p in reversed(points):
while len(upper) >= 2 and cross(upper[-2], upper[-1], p) < 0:
upper.pop()
upper.append(p)
# Concatenate lower and upper hull to get full hull
# Remove duplicates by converting to set then back to list
hull = lower[:-1] + upper[:-1]
return list(map(list, set(map(tuple, hull))))