Hash Map & Set
Trade memory for time. A hash map remembers what you have already seen, so "is it here?", "how many?" and "where was it?" are answered in O(1) instead of rescanning the input.
1. What is this pattern for?
Python gives you two hash structures: a set (just keys: "have I seen this?") and a dict (keys with values: "what do I know about this?"). Both look up, insert and delete in O(1) on average. Almost every array or string problem has a slow version that rescans the input inside a loop, and a hash map usually removes that inner loop. The four jobs it does:
- Membership: "seen this before?" (duplicates, cycle detection, visited sets).
- Lookup by complement: "is the partner I need already here?" (Two Sum, pairs with a difference).
- Counting: "how many of each?" (anagrams, ransom notes, majority element).
- Grouping / mapping: "which bucket does this belong to?" (group anagrams, isomorphic strings).
2. How it works (bisected)
- Decide what the key is. The value itself (
x), its complement (target − x), a letter, or a canonical form (sorted letters, a tuple of counts). - Decide what to store with it. Nothing (a set), an index, a count, or a list of items in the same group.
- One pass, look before you store. For each element, first ask the map about it, then update the map. Asking first stops an element from matching itself.
- Read the answer from the map: a hit during the pass, a count after it, or
map.values()for groups.
3. Seeing a problem: how to spot Hashing
Trigger words
- "contains duplicate", "first repeated", "unique", "distinct"
- "two numbers that add up to", "pair with difference k" in an unsorted array
- "anagram", "same letters", "frequency", "count of each", "can be constructed from"
- "group the strings that…", "isomorphic", "follows the pattern"
- "in O(n)" when the obvious answer sorts (O(n log n)) or uses nested loops (O(n²))
Shape of the input
- An unsorted array or string where you need to relate an element to earlier elements.
- Values that are hashable (numbers, strings, tuples), not lists.
- Brute-force smell:
for i … for j …comparing every pair, orx in some_listinside a loop.
4. When to use it, and when not to
| Use a hash map / set when… | Prefer something else when… |
|---|---|
You need "seen before?" or "where was it?" in O(1) | The input is sorted and you need a pair: Two Pointers uses O(1) space |
| You count frequencies of letters or values | The alphabet is tiny and fixed (26 letters): a plain list of 26 counts is just as fast and simpler to compare |
| Items must be grouped by a computed key | You need items in order (smallest, k-th, range queries): a heap, sorting or a balanced tree |
| Running totals must be matched to earlier totals | That is Prefix Sum + a hash map (Subarray Sum Equals K), a combination worth knowing |
| A contiguous window must contain certain counts | Combine it with a Sliding Window; the map tracks the window's contents |
Cost: O(1) average per lookup/insert, so most solutions are O(n) time and O(n) extra space. (Worst case is O(n) per operation if many keys collide, which doesn't happen in practice with Python's hashing.)
5. The Python template (memorize this)
# A) Membership: have I seen this value before?
def has_duplicate(nums):
seen = set()
for x in nums:
if x in seen: # ask first ...
return True
seen.add(x) # ... then remember
return False
# B) Complement lookup: value -> index of the partner I need
def two_sum(nums, target):
index_of = {}
for i in range(len(nums)):
need = target - nums[i]
if need in index_of:
return [index_of[need], i]
index_of[nums[i]] = i
return []
# C) Counting: how many of each?
def counts(items):
count = {}
for x in items:
count[x] = count.get(x, 0) + 1 # .get gives 0 for a new key
return count # collections.Counter(items) does the same
# D) Grouping by a canonical key
def group_by_key(items, key_of):
groups = {}
for x in items:
k = key_of(x) # e.g. "".join(sorted(word))
if k not in groups:
groups[k] = []
groups[k].append(x)
return list(groups.values())
What the code is doing, line by line:
- A:
seen = set()remembers values.if x in seenis anO(1)question; we ask beforeseen.add(x)so a value never counts as its own duplicate. - B: the dictionary maps a value to the index where it appeared. For each number we compute the partner it needs (
target − nums[i]) and look it up. Storing after the lookup means an element can't pair with itself. - C:
count.get(x, 0) + 1reads the current count (0 ifxis new) and adds one. That avoids aKeyErrorwithout anif. - D:
key_of(x)turns each item into its canonical form; items with equal keys land in the same list. Keys must be hashable, so use a string or a tuple, never a list.
Dry run of template B on nums = [3, 2, 4], target = 6 (LeetCode 1, Example 2):
| i | nums[i] | need | need in index_of? | index_of after |
|---|---|---|---|---|
| 0 | 3 | 3 | no (3 itself isn't stored yet) | {3: 0} |
| 1 | 2 | 4 | no | {3: 0, 2: 1} |
| 2 | 4 | 2 | yes → return [1, 2] | — |
Row 0 shows why we look up before storing: the 3 must not pair with itself to make 6.
6. How to remember it forever
How this hook works: what “Coat check” means
What the slogan means, phrase by phrase
- “Coat check”
- A hash map or set: it stores items and finds them again instantly.
- “Hand in the item, get a ticket”
- Hashing turns the key into a hook number.
- “walk straight to the hook”
- Lookup goes directly to the right place,
O(1)on average, with no searching along the wall. - “Anything you'd otherwise search for again, put in the cloakroom the first time you see it”
- This is the pattern: replace an inner loop that searches earlier elements with one dict lookup.
Why this picture fits the pattern: Most slow solutions are slow because of a nested loop asking "have I seen X before?" The coat check answers that in constant time, turning O(n²) into O(n).
The story → the code
| In the story | In the code |
|---|---|
| The cloakroom | seen = set() / index_of = {} |
| Hand in a coat | index_of[nums[i]] = i |
| "Is my coat here?" | need in index_of |
| Collect it | index_of[need] |
| Ask, then store | look up before adding the current item |
Watch it on numbers
Two Sum: nums = [3, 2, 4], target = 6.
- i = 0, value 3: is there a 3 in the cloakroom? No, it's empty. Hand in 3 → index 0.
- i = 1, value 2: a 4 is needed, and none is there. Hand in 2 → index 1.
- i = 2, value 4: a 2 is needed, and 2 is on hook 1. Answer [1, 2].
- Had we stored before asking, at i = 0 we would find our own 3 and wrongly answer [0, 0].
The trap it saves you from
Storing before asking (an item pairs with itself), or using a list as a key, which isn't hashable. Use a tuple.
It still works when…
Group Anagrams: "same answer, same key". The key is the sorted letters, so eat, tea and ate hang on one hook. Counting: count[x] = count.get(x, 0) + 1. Longest Consecutive Sequence: a set, and only start counting at numbers whose x - 1 isn't checked in.
Picture
A wall of numbered hooks. Each value hangs on its own hook; finding it never means walking along the wall.
Chant
"Ask, then store. Count with get. Group by key."
Key rule
"Same answer, same key." Choose a key that is equal exactly when two items should be treated as the same (sorted letters for anagrams).
7. LeetCode problems, solved step by step
Easy 1. Two Sum Start here Google Meta Amazon Cisco Microsoft Apple
Problem: Given an integer array nums and an integer target, return the indices of the two numbers that add up to target. Exactly one answer exists, and you may not use the same element twice.
Solve it on LeetCode ↗ · Video walkthrough ↗
nums = [2,7,11,15], target = 9[0,1]nums = [3,2,4], target = 6[1,2]Constraints: 2 ≤ nums.length ≤ 10⁴ · -10⁹ ≤ nums[i] ≤ 10⁹ · -10⁹ ≤ target ≤ 10⁹ · Only one valid answer exists.
Hint 1
For each number x, which single value would complete the pair?
Hint 2
Store each value's index in a dictionary as you go; look up target − x before storing x.
From brute force to the pattern: Checking every pair is O(n²). Sorting loses the original indices. A value → index map finds each complement in O(1), so one pass: O(n).
Spot it: "Find a pair that sums to a target" in an unsorted array. Sorting would lose the original indices, so instead remember every number you have seen in a hash map: value → index.
Key insight: For each number x, its partner must be target − x. If the partner was seen earlier, the map answers "where?" in O(1).
Approach:
- Create an empty dictionary
seenthat maps a value to the index where it appeared. - Walk the array with its index
i. For the current numberx, computeneed = target − x. - If
needis already inseen, the pair is[seen[need], i]. Return it. - Otherwise store
seen[x] = iand move on. Storing after checking stopsxfrom pairing with itself.
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# value -> index where we saw it
seen = {}
for i in range(len(nums)):
x = nums[i]
# the number that would complete the pair
need = target - x
if need in seen:
# the partner appeared earlier: we are done
return [seen[need], i]
# remember x for the numbers that come later
seen[x] = i
# the problem guarantees an answer, so we never get here
return []
What the code is doing, line by line:
seen = {}: an empty dictionary. Keys are numbers we have already passed, values are their indices.for i in range(len(nums)):visit every position once;x = nums[i]is the number there.need = target - x: the only value that can pair withx.if need in seen:a dictionary lookup isO(1)on average. If the partner was seen, return its index and ours.seen[x] = i: we storexonly after the check, so an element can never be paired with itself (e.g.target = 6,x = 3does not match its own entry).return []: unreachable for valid input, but keeps the function returning a list on every path.
Dry run on the example:
nums = [2, 7, 11, 15], target = 9 (LeetCode Example 1). Returns [0, 1].
| i | x | need | need in seen? | seen after this step |
|---|---|---|---|---|
| 0 | 2 | 7 | no | {2: 0} |
| 1 | 7 | 2 | yes, at index 0 → return [0, 1] | — |
Edge cases to test:
- Two equal numbers form the pair, e.g.
[3, 3], target 6. - Negative numbers.
- Using the same element twice is not allowed (that's why we look up before storing).
Complexity: O(n) time (one pass, O(1) lookups), O(n) space for the map.
Medium 49. Group Anagrams Google Meta Nvidia Amazon Cisco Microsoft Apple
Problem: Group the strings that are anagrams of each other. Return the groups in any order.
Solve it on LeetCode ↗ · Video walkthrough ↗
strs = ["eat","tea","tan","ate","nat","bat"][["bat"],["nat","tan"],["ate","eat","tea"]]strs = [""][[""]]Constraints: 1 ≤ strs.length ≤ 10⁴ · 0 ≤ strs[i].length ≤ 100 · strs[i] consists of lowercase English letters.
Hint 1
All anagrams of each other share some canonical form.
Hint 2
Use the sorted letters (or a tuple of 26 counts) as a dictionary key, and group the words under it.
From brute force to the pattern: Comparing every pair of words for being anagrams is O(n² · k). One canonical key per word puts each word straight into its group: O(n · k log k) (or O(n · k) with count keys).
Spot it: "Group items that share a property" → a hash map from a canonical key to a list. All anagrams sort to the same string, so "".join(sorted(word)) is a perfect key.
Approach:
- For each word, compute
key = "".join(sorted(word)). - Append the word to
groups[key](creating the list the first time). - Return all the lists.
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
groups = {}
for word in strs:
# anagrams have the same letters, so they sort to the same key
key = "".join(sorted(word))
if key not in groups:
groups[key] = []
groups[key].append(word)
return list(groups.values())
What the code is doing, line by line:
sorted(word)returns a list of characters in order;"".join(...)glues them back into a string. Lists can't be dictionary keys, but strings can.if key not in groups: groups[key] = []: start a new group the first time a key appears (collections.defaultdict(list)does this automatically).groups[key].append(word): add the word to its group.list(groups.values()): just the groups, without their keys.- A faster key is a tuple of 26 letter counts:
O(k)per word instead ofO(k log k).
Dry run on the example:
strs = ["eat", "tea", "tan", "ate", "nat", "bat"] (LeetCode Example 1). Returns [["eat", "tea", "ate"], ["tan", "nat"], ["bat"]].
| word | key | groups after |
|---|---|---|
"eat" | "aet" | aet: [eat] |
"tea" | "aet" | aet: [eat, tea] |
"tan" | "ant" | aet: [eat, tea], ant: [tan] |
"ate" | "aet" | aet: [eat, tea, ate], ant: [tan] |
"nat" | "ant" | aet: [eat, tea, ate], ant: [tan, nat] |
"bat" | "abt" | … plus abt: [bat] |
Edge cases to test:
- An empty string
""forms its own group. - A single word.
- No two words are anagrams → every group has one word.
Complexity: O(n · k log k) time for n words of length up to k, O(n · k) space.
Medium 128. Longest Consecutive Sequence Google Meta Nvidia Amazon Cisco Microsoft Apple
Problem: Return the length of the longest run of consecutive integers that can be formed from nums (in any order), in O(n) time.
Solve it on LeetCode ↗ · Video walkthrough ↗
nums = [100,4,200,1,3,2]4nums = [0,3,7,2,5,8,4,6,0,1]9Constraints: 0 ≤ nums.length ≤ 10⁵ · -10⁹ ≤ nums[i] ≤ 10⁹
Hint 1
Sorting is O(n log n). A set gives O(1) "is x + 1 here?" checks.
Hint 2
Only start counting from x when x − 1 is not in the set; then walk upward.
From brute force to the pattern: Sorting and scanning is O(n log n); walking upward from every number is O(n²). Starting only at run beginnings walks each number once: O(n).
Spot it: O(n) rules out sorting. A set lets you ask "is x + 1 here?" in O(1). The trick is to count each run only once, from its smallest number.
Key insight: x starts a run exactly when x − 1 is not in the set. Counting only from run starts means each number is stepped over at most once in total.
Approach:
- Put all numbers in a set (this also drops duplicates).
- For each
xin the set: skip it ifx − 1is present (it is in the middle of a run). - Otherwise count upward
x, x + 1, x + 2, …while the numbers exist; update the best length.
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
values = set(nums)
best = 0
for x in values:
if x - 1 in values:
continue # not the start of a run
# x starts a run: walk it upward
length = 1
while x + length in values:
length = length + 1
best = max(best, length)
return best
What the code is doing, line by line:
values = set(nums):O(1)membership tests; duplicates disappear.if x - 1 in values: continue: someone smaller will count this run, so skip.while x + length in values:extend the run one number at a time.- Every number is walked over only from its run's start, so the total work is
O(n)even though there's a loop inside a loop.
Dry run on the example:
nums = [100, 4, 200, 1, 3, 2] (LeetCode Example 1). Returns 4 (the run 1, 2, 3, 4).
| x | x − 1 present? | run counted | best |
|---|---|---|---|
| 100 | no | 100 → length 1 | 1 |
| 4 | yes (3) | skip | 1 |
| 200 | no | 200 → length 1 | 1 |
| 1 | no | 1, 2, 3, 4 → length 4 | 4 |
| 3 | yes (2) | skip | 4 |
| 2 | yes (1) | skip | 4 |
(A set's iteration order isn't guaranteed; the rows follow the input order for readability. The answer doesn't depend on the order.)
Edge cases to test:
- An empty array → 0.
- Duplicates, e.g.
[1, 2, 2, 3]→ 3. - Negative numbers.
Complexity: O(n) time, O(n) space.
8. Common mistakes
nums[i] before looking up target − nums[i] lets an element pair with itself (e.g. 3 + 3 = 6 from a single 3).groups[sorted(word)] raises TypeError: unhashable type: 'list'. Convert to a string ("".join(...)) or a tuple.count[x] += 1 fails the first time. Use count.get(x, 0) + 1, collections.Counter or defaultdict(int).if x in some_list is O(n), which quietly turns the solution into O(n²). Convert it to a set once, before the loop.9. Self-check quiz
An unsorted array; find two indices whose values add up to a target in O(n). What do you keep?
O(n log n). A value → index map finds each complement in O(1).To group anagrams, what is the best dictionary key for a word?
Longest Consecutive Sequence in O(n): you only start counting a run at x when…
"ab" vs "cc": a single letter → letter map says they're isomorphic. What fixes it?
a → c and b → c are each consistent alone; only the reverse map (c is already taken by a) catches the clash.10. Practice list (in order)
| # | Problem | Level | Data structure | Twist |
|---|---|---|---|---|
| 217 | Contains Duplicate | Easy | A set answers "seen before?" in O(1) | |
| 242 | Valid Anagram | Easy | Equal letter counts ⇔ anagram | |
| 1 | Two Sum | Easy | One pass: look up target − x before storing x | |
| 383 | Ransom Note | Easy | Count the supply, then spend it | |
| 771 | Jewels and Stones | Easy | Put the jewel types in a set, then count matching stones | |
| 1189 | Maximum Number of Balloons | Easy | Divide each letter's supply by how many the word needs | |
| 205 | Isomorphic Strings | Easy | Two maps, one per direction, so the mapping is one-to-one | |
| 290 | Word Pattern | Easy | Isomorphic Strings between letters and words | |
| 169 | Majority Element | Easy | Count map works; Boyer–Moore voting does it in O(1) space | |
| 49 | Group Anagrams | Medium | Key = the word's sorted letters; group by key | |
| 128 | Longest Consecutive Sequence | Medium | Only start counting at a run's first number (x − 1 not in the set) | |
| 36 | Valid Sudoku | Medium | 27 sets: one per row, column and 3×3 box | |
| 560 | Subarray Sum Equals K | Medium | Solved in 01 · Prefix Sum: Prefix + hash map (core) | |
| 3 | Longest Substring Without Repeating Characters | Medium | Solved in 03 · Sliding Window: 3 | |
| 347 | Top K Frequent Elements | Medium | Solved in 07 · Top 'K' Elements: Count first, heap on (freq, val) | |
| 229 | Majority Element II | Medium | At most two values can exceed n/3: Boyer–Moore with two candidates, then verify with counts | |
| 299 | Bulls and Cows | Medium | Bulls = same position; cows = Σ min(count in secret, count in guess) over the non-bull digits | |
| 554 | Brick Wall | Medium | Count edge positions (prefix sums) across rows; cross where the most edges line up | |
| 811 | Subdomain Visit Count | Medium | Add each visit count to every suffix of the domain | |
| 833 | Find And Replace in String | Medium | Map index → (source, target) for matching replacements, then rebuild left to right | |
| 963 | Minimum Area Rectangle II | Medium | A rectangle's diagonals share midpoint and length: group point pairs by (midpoint, length²) | |
| 1657 | Determine if Two Strings Are Close | Medium | Same set of letters and the same multiset of letter counts | |
| 1781 | Sum of Beauty of All Substrings | Medium | Fix the start; extend the end with a 26-letter count array; add max − min of non-zero counts | |
| 2001 | Number of Pairs of Interchangeable Rectangles | Medium | Reduce width:height by the gcd; count pairs within each ratio group | |
| 2007 | Find Original Array From Doubled Array | Medium | Process values smallest first: each must pair with its double, tracked in a counter | |
| 2150 | Find All Lonely Numbers in the Array | Medium | count[x] == 1 and neither x − 1 nor x + 1 is present | |
| 2225 | Find Players With Zero or One Losses | Medium | Count losses per player (winners with 0); filter 0 and 1, sorted | |
| 2364 | Count Number of Bad Pairs | Medium | Good pair ⇔ nums[i] − i == nums[j] − j; bad = all pairs − good pairs | |
| 2657 | Find the Prefix Common Array of Two Arrays | Medium | A value becomes common the moment its second copy appears; count occurrences | |
| 2840 | Check if Strings Can be Made Equal With Operations II | Medium | Even-distance swaps permute even and odd positions separately: compare their letter counts | |
| 3016 | Minimum Number of Pushes to Type Word II | Medium | Sort letter frequencies; the i-th most frequent costs i // 8 + 1 pushes | |
| 3020 | Find the Maximum Number of Elements in Subset | Medium | From each base x, climb x → x² while two copies exist; the top needs one copy | |
| 3121 | Count the Number of Special Characters II | Medium | Record the last lowercase index and the first uppercase index per letter; special if last < first | |
| 3741 | Minimum Distance Between Three Equal Elements II | Medium | The distance of i < j < k is 2(k − i); use three consecutive positions of each value | |
| 3761 | Minimum Absolute Distance Between Mirror Pairs | Medium | Scan left to right; remember the latest index of each reverse(nums[i]); look up nums[j] | |
| 249 | Group Shifted Strings | Medium | Key = the gaps between consecutive letters (mod 26); group strings with the same key | |
| 791 | Custom Sort String | Medium | Count s's letters; emit them in order's order, then the rest | |
| 1570 | Dot Product of Two Sparse Vectors | Medium | Store only (index, value) pairs of non-zeros; merge the two sorted pair lists with two pointers | |
| 3960 | Frequency Balance Subarray | Medium | Fix the start, extend the end, and keep a map of counts plus a map of how many values have each count | |
| 1152 | Analyze User Website Visit Pattern | Medium | Per user, the set of 3-site patterns from their time-ordered visits; count users per pattern; best by (−count, pattern) | |
| 2910 | Minimum Number of Groups to Create a Valid Assignment | Medium | Count each value; try box sizes s (s or s + 1 balls) from the smallest count down | |
| 3872 | Longest Arithmetic Sequence After Changing At Most One Element | Medium | Runs of equal differences; either extend a run by one, or change nums[i] to bridge two runs with difference c = (nums[i+1] − nums[i−1]) / 2 | |
| 3927 | Minimize Array Sum Using Divisible Replacements | Medium | Each value can become its smallest divisor present in the array; a sieve over the distinct values finds it | |
| 149 | Max Points on a Line | Hard | For each anchor point, count other points by reduced slope (dy/g, dx/g) in a hash map | |
| 4007 | Widest Possible Fence | Hard | For each height H: planks of height H plus pairs summing to H; count pair contributions per sum in a hash map |
Worked solutions for the practice list
Problems already solved in section 7 link back up there, and rows marked "Solved in …" link to the page where that problem is worked through. Every other problem from the table is solved here.
Easy 217. Contains Duplicate Start here Google Meta Amazon Microsoft Netflix
Problem: Return True if any value appears at least twice in nums, otherwise False.
Solve it on LeetCode ↗ · Video walkthrough ↗
nums = [1,2,3,1]truenums = [1,2,3,4]falseConstraints: 1 ≤ nums.length ≤ 10⁵ · -10⁹ ≤ nums[i] ≤ 10⁹
Hint 1
You need to know whether you've seen a value before.
Hint 2
Keep a set; return True the moment a value is already in it.
From brute force to the pattern: Comparing every pair is O(n²); sorting and checking neighbours is O(n log n). A set answers "seen before?" in O(1): O(n).
Spot it: "Have I seen this before?" is the textbook question a hash set answers in O(1).
Approach:
- Keep a set
seenof values already passed. - For each number: if it is in
seen, returnTrue; otherwise add it. - If the loop finishes, every value was unique:
False.
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
seen = set()
for x in nums:
if x in seen:
return True # x appeared earlier
seen.add(x)
return False
What the code is doing, line by line:
seen = set(): an empty hash set.x in seenandseen.add(x)are bothO(1)on average.if x in seen: return True: stop at the first repeat; no need to look further.seen.add(x): rememberxfor the rest of the scan.return False: all values were distinct. (One-liner alternative:len(set(nums)) < len(nums), but it can't stop early.)
Dry run on the example:
nums = [1, 2, 3, 1] (LeetCode Example 1). Returns True.
| x | x in seen? | seen after |
|---|---|---|
| 1 | no | {1} |
| 2 | no | {1, 2} |
| 3 | no | {1, 2, 3} |
| 1 | yes → True | — |
Edge cases to test:
- A single element → False.
- The duplicate appears at the very end.
- Negative numbers.
Complexity: O(n) time, O(n) space. (Sorting first is O(n log n) time but O(1) extra space.)
Easy 242. Valid Anagram Start here Google Meta Amazon Cisco Microsoft Netflix
Problem: Return True if t is an anagram of s: the same letters, each used the same number of times, in any order.
Solve it on LeetCode ↗ · Video walkthrough ↗
s = "anagram", t = "nagaram"trues = "rat", t = "car"falseConstraints: 1 ≤ s.length, t.length ≤ 5 * 10⁴ · s and t consist of lowercase English letters.
Hint 1
Order doesn't matter, only how many of each letter.
Hint 2
Count the letters (+1 for s, −1 for t) and check that every count is 0.
From brute force to the pattern: Sorting both strings and comparing is O(n log n). Counting letters is O(n), and for lowercase letters an array of 26 counts works too.
Spot it: Order doesn't matter, only how many of each letter. A count map (letter → count) is the fingerprint.
Approach:
- Different lengths can't be anagrams.
- Count every letter of
s(+1) and every letter oft(−1) in one dictionary. - Anagrams cancel out: every count must end at 0.
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
if len(s) != len(t):
return False
count = {}
for i in range(len(s)):
# +1 for letters of s, -1 for letters of t
count[s[i]] = count.get(s[i], 0) + 1
count[t[i]] = count.get(t[i], 0) - 1
for letter in count:
if count[letter] != 0:
return False # this letter is used a different number of times
return True
What the code is doing, line by line:
if len(s) != len(t): return False: a quick exit, and it also lets one loop walk both strings at once.count.get(key, 0): returns 0 for a letter not seen yet, avoiding aKeyError.+ 1fors[i],- 1fort[i]: a letter that appears equally often in both ends at 0.if count[letter] != 0: return False: some letter is over- or under-used.collections.Counter(s) == Counter(t)is the same idea in one line.
Dry run on the example:
s = "rat", t = "car" (LeetCode Example 2). Returns False.
| i | s[i] (+1) | t[i] (−1) | count after |
|---|---|---|---|
| 0 | r | c | {r: 1, c: -1} |
| 1 | a | a | {r: 1, c: -1, a: 0} |
| 2 | t | r | {r: 0, c: -1, a: 0, t: 1} |
c is −1 (and t is 1), so the answer is False.
Edge cases to test:
- Different lengths → False immediately.
- Same letters with different counts, e.g.
"aab"vs"abb". - Unicode letters (a dictionary handles them; a 26-array doesn't).
Complexity: O(n) time, O(k) space for k distinct letters (at most 26 for lowercase English).
Easy 383. Ransom Note Google Meta Amazon Microsoft
Problem: Return True if ransomNote can be built from the letters of magazine, each magazine letter used at most once.
Solve it on LeetCode ↗ · Video walkthrough ↗
ransomNote = "a", magazine = "b"falseransomNote = "aa", magazine = "ab"falseConstraints: 1 ≤ ransomNote.length, magazine.length ≤ 10⁵ · ransomNote and magazine consist of lowercase English letters.
Hint 1
How many of each letter does the magazine offer?
Hint 2
Count the magazine's letters, then use one up for each letter of the note.
From brute force to the pattern: For each note letter, searching and removing it from the magazine string is O(m · n). Counting once and decrementing is O(m + n).
Spot it: A supply-and-demand question about letter counts → count the magazine's letters in a map, then use them up.
Approach:
- Count every letter in
magazine. - For each letter of
ransomNote: if none of it is left, returnFalse; otherwise use one (decrement). - If every letter was available, return
True.
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
available = {}
for ch in magazine:
available[ch] = available.get(ch, 0) + 1
for ch in ransomNote:
if available.get(ch, 0) == 0:
return False # we ran out of this letter
available[ch] = available[ch] - 1
return True
What the code is doing, line by line:
available: how many of each letter the magazine offers.available.get(ch, 0) == 0: either the magazine never hadch, or every copy is already used.available[ch] = available[ch] - 1: cut out one copy.return True: the whole note was covered.
Dry run on the example:
ransomNote = "aa", magazine = "aab" (LeetCode Example 3). Returns True.
| note letter | available before | action | available after |
|---|---|---|---|
a | {a: 2, b: 1} | use one a | {a: 1, b: 1} |
a | {a: 1, b: 1} | use one a | {a: 0, b: 1} |
Every letter was available → True. With magazine = "ab" the second a would find 0 left → False.
Edge cases to test:
- The note needs more copies of a letter than the magazine has.
- An empty note → True.
- Identical strings → True.
Complexity: O(m + n) time, O(k) space for k distinct letters.
Easy 771. Jewels and Stones Google Amazon
Problem: jewels lists the stone types that are jewels (case-sensitive). Return how many of your stones are jewels.
Solve it on LeetCode ↗ · Video walkthrough ↗
jewels = "aA", stones = "aAAbbbb"3jewels = "z", stones = "ZZ"0Constraints: 1 ≤ jewels.length, stones.length ≤ 50 · jewels and stones consist of only English letters. · All the characters of jewels are unique.
Hint 1
You check each stone against the set of jewel types.
Hint 2
Put the jewels in a set so each check is O(1).
From brute force to the pattern: Checking each stone against the jewels string is O(j · s). A set makes it O(j + s).
Spot it: Many "is this one of those?" checks → turn the list of types into a set so each check is O(1) instead of scanning jewels.
Approach:
- Build
jewel_set = set(jewels). - Count the stones whose type is in the set.
class Solution:
def numJewelsInStones(self, jewels: str, stones: str) -> int:
jewel_set = set(jewels)
count = 0
for stone in stones:
if stone in jewel_set:
count = count + 1
return count
What the code is doing, line by line:
set(jewels): each character of the string becomes a member; lookups areO(1).for stone in stones:look at every stone you own.if stone in jewel_set:case-sensitive, so'a'and'A'are different types.
Dry run on the example:
jewels = "aA", stones = "aAAbbbb" (LeetCode Example 1). Returns 3.
| stone | a jewel? | count |
|---|---|---|
a | yes | 1 |
A | yes | 2 |
A | yes | 3 |
b × 4 | no | 3 |
Edge cases to test:
- No jewels among the stones → 0.
- Case matters:
aandAare different. - Every stone is a jewel.
Complexity: O(j + s) time, O(j) space for the set.
Easy 1189. Maximum Number of Balloons Google Amazon
Problem: Using the letters of text at most once each, how many copies of the word "balloon" can you form?
Solve it on LeetCode ↗ · Video walkthrough ↗
text = "nlaebolko"1text = "loonbalxballpoon"2Constraints: 1 ≤ text.length ≤ 10⁴ · text consists of lower case English letters only.
Hint 1
The word needs b, a, n once and l, o twice.
Hint 2
Count the text's letters and take the minimum of have // need over those five letters.
From brute force to the pattern: Repeatedly removing the letters of "balloon" from the text is O(n · words). Counting once and dividing is O(n).
Spot it: Supply vs demand of letter counts → count map. "balloon" needs b, a, n once and l, o twice; the scarcest letter, relative to its need, limits the answer.
Approach:
- Count the letters of
text. - Count the letters needed for one
"balloon":{b: 1, a: 1, l: 2, o: 2, n: 1}. - For each needed letter,
have // needis how many words that letter alone allows. The answer is the minimum over all letters.
class Solution:
def maxNumberOfBalloons(self, text: str) -> int:
have = {}
for ch in text:
have[ch] = have.get(ch, 0) + 1
need = {"b": 1, "a": 1, "l": 2, "o": 2, "n": 1}
best = len(text) # an upper bound to shrink
for ch in need:
# how many words this letter alone could supply
best = min(best, have.get(ch, 0) // need[ch])
return best
What the code is doing, line by line:
have: letter counts of the input.need: written out by hand;landoappear twice in "balloon".have.get(ch, 0) // need[ch]: integer division, e.g. 3ls make only 1 word (3 // 2).best = min(...): the bottleneck letter decides.
Dry run on the example:
text = "loonbalxballpoon" (LeetCode Example 2). Returns 2.
| letter | have | need per word | words possible |
|---|---|---|---|
b | 2 | 1 | 2 |
a | 2 | 1 | 2 |
l | 4 | 2 | 2 |
o | 4 | 2 | 2 |
n | 2 | 1 | 2 |
The minimum is 2.
Edge cases to test:
- A missing letter → 0.
- Only one
lor oneo→ 0. - Lots of extra unrelated letters.
Complexity: O(n) time, O(1) space (at most 26 letters).
Easy 205. Isomorphic Strings Google Meta Amazon Microsoft
Problem: s and t are isomorphic if the characters of s can be replaced to get t: every occurrence of a character maps to the same character, and no two characters map to the same one. Check it.
Solve it on LeetCode ↗ · Video walkthrough ↗
s = "egg", t = "add"trues = "f11", t = "b23"falseConstraints: 1 ≤ s.length ≤ 5 * 10⁴ · t.length == s.length · s and t consist of any valid ascii character.
Hint 1
Each character of s must always map to the same character of t.
Hint 2
Also, two different characters may not map to the same one: keep a second map in the other direction.
From brute force to the pattern: Checking every pair of positions for consistency is O(n²). Two dictionaries (one per direction) check each position in O(1): O(n).
Spot it: A consistent character-to-character mapping is a dictionary. "No two map to the same" means it must also be consistent backwards, so keep a second map for t → s.
Approach:
- Walk both strings together, pairing
a = s[i]withb = t[i]. - If
aalready maps to something other thanb, fail. Ifbis already the image of something other thana, fail. - Otherwise record
a → bandb → a.
class Solution:
def isIsomorphic(self, s: str, t: str) -> bool:
s_to_t = {}
t_to_s = {}
for i in range(len(s)):
a = s[i]
b = t[i]
if a in s_to_t and s_to_t[a] != b:
return False # a was already promised to a different letter
if b in t_to_s and t_to_s[b] != a:
return False # b is already taken by a different letter
s_to_t[a] = b
t_to_s[b] = a
return True
What the code is doing, line by line:
s_to_t: where each letter ofsgoes.t_to_s: which letter ofsclaimed each letter oft.if a in s_to_t and s_to_t[a] != b:the sameawould have to become two different letters.if b in t_to_s and t_to_s[b] != a:two different letters ofswould both becomeb. One map alone can't catch this (e.g."ab"→"aa").s_to_t[a] = b,t_to_s[b] = a: record the pair (harmless if it was already there).
Dry run on the example:
s = "badc", t = "baba". Returns False (b and d would both map to b).
| i | pair (a → b) | check | maps after |
|---|---|---|---|
| 0 | b → b | new | {b: b} / {b: b} |
| 1 | a → a | new | {b: b, a: a} / {b: b, a: a} |
| 2 | d → b | b is already the image of b, not d | False |
Edge cases to test:
- Two different letters mapping to the same letter, e.g.
"ab"→"aa"→ False. - Identical strings → True.
- Single characters.
Complexity: O(n) time, O(k) space for k distinct characters.
Easy 290. Word Pattern Google Amazon Microsoft
Problem: Does s (words separated by single spaces) follow pattern? Each letter must map to exactly one word and each word to exactly one letter.
Solve it on LeetCode ↗ · Video walkthrough ↗
pattern = "abba", s = "dog cat cat dog"truepattern = "abba", s = "dog cat cat fish"falseConstraints: 1 ≤ pattern.length ≤ 300 · pattern contains only lower-case English letters. · 1 ≤ s.length ≤ 3000 · s contains only lowercase English letters and spaces ' '. · s does not contain any leading or trailing spaces. · All the words in s are separated by a single space.
Hint 1
Split s into words first. The counts must match.
Hint 2
Keep letter → word and word → letter maps; any contradiction means False.
From brute force to the pattern: Comparing every pair of positions is O(n²). Two maps check the one-to-one mapping in a single pass: O(n).
Spot it: A one-to-one (bijective) mapping → two dictionaries, letter → word and word → letter, exactly like LeetCode 205.
Approach:
- Split
sinto words. If the count differs fromlen(pattern), returnFalse. - Walk letter/word pairs; reject any pair that contradicts an existing mapping in either direction.
- Record both directions and continue.
class Solution:
def wordPattern(self, pattern: str, s: str) -> bool:
words = s.split()
if len(words) != len(pattern):
return False
letter_to_word = {}
word_to_letter = {}
for i in range(len(pattern)):
letter = pattern[i]
word = words[i]
if letter in letter_to_word and letter_to_word[letter] != word:
return False
if word in word_to_letter and word_to_letter[word] != letter:
return False
letter_to_word[letter] = word
word_to_letter[word] = letter
return True
What the code is doing, line by line:
s.split(): breaks the sentence at spaces into a list of words.if len(words) != len(pattern):every letter needs exactly one word.- The two
ifchecks catch "one letter, two words" and "two letters, one word". letter_to_word[letter] = word,word_to_letter[word] = letter: remember the pairing in both directions.
Dry run on the example:
pattern = "abba", s = "dog cat cat fish" (LeetCode Example 2). Returns False.
| letter | word | check | result |
|---|---|---|---|
a | "dog" | new pair | ok |
b | "cat" | new pair | ok |
b | "cat" | matches b → cat | ok |
a | "fish" | a already maps to dog | False |
Edge cases to test:
- Different numbers of letters and words → False.
- Different letters mapping to the same word → False.
- A single letter and a single word.
Complexity: O(n) time and space, where n is the length of s.
Easy 169. Majority Element Google Meta Amazon Microsoft
Problem: The majority element appears more than n / 2 times, and it always exists. Return it.
Solve it on LeetCode ↗ · Video walkthrough ↗
nums = [3,2,3]3nums = [2,2,1,1,1,2,2]2Constraints: n == nums.length · 1 ≤ n ≤ 5 * 10⁴ · -10⁹ ≤ nums[i] ≤ 10⁹ · The input is generated such that a majority element will exist in the array.
Hint 1
Count each value; the one with more than n / 2 occurrences is the answer.
Hint 2
Follow-up in O(1) space: Boyer–Moore voting cancels pairs of different values.
From brute force to the pattern: Counting the occurrences of every value separately is O(n²); sorting and taking the middle is O(n log n). A count map is O(n), and voting is O(n) with O(1) space.
Spot it: "How often does each value appear?" → a count map; return the value whose count passes n // 2. The follow-up asks for O(1) space, which is Boyer–Moore voting (shown second).
Approach:
- Count map version: count each value; as soon as a count exceeds
n // 2, return that value. - Voting version: keep a
candidateand avotescounter. Whenvotesis 0, adopt the current value as candidate. - Add a vote if the value equals the candidate, otherwise remove one. Pairs of different values cancel out, and the majority can't be fully cancelled.
class Solution:
def majorityElement(self, nums: List[int]) -> int:
# hash-map version: O(n) time, O(n) space
count = {}
for x in nums:
count[x] = count.get(x, 0) + 1
if count[x] > len(nums) // 2:
return x
return -1
def majorityElementVoting(self, nums: List[int]) -> int:
# Boyer-Moore voting: O(n) time, O(1) space
candidate = None
votes = 0
for x in nums:
if votes == 0:
candidate = x # nobody is ahead: x becomes the candidate
if x == candidate:
votes = votes + 1
else:
votes = votes - 1 # x cancels one vote of the candidate
return candidate
What the code is doing, line by line:
count[x] = count.get(x, 0) + 1: tally each value.if count[x] > len(nums) // 2: return x: the first value to pass half is the majority, so we can stop early.- Voting:
if votes == 0: candidate = x: the previous candidate was cancelled out, so start fresh withx. votes + 1for a match,votes - 1otherwise. Each−1pairs one majority element with one other element. The majority has more than half of the elements, so it always has votes left at the end.
Dry run on the example:
nums = [2, 2, 1, 1, 1, 2, 2] (LeetCode Example 2), using the voting version. Returns 2.
| x | candidate | votes after |
|---|---|---|
| 2 | 2 | 1 |
| 2 | 2 | 2 |
| 1 | 2 | 1 |
| 1 | 2 | 0 |
| 1 | 1 | 1 |
| 2 | 1 | 0 |
| 2 | 2 | 1 |
Edge cases to test:
- A single element.
- The majority at the start or the end.
- Negative numbers.
Complexity: Count map: O(n) time and space. Voting: O(n) time, O(1) space.
Medium 36. Valid Sudoku Google Meta Amazon Microsoft Apple
Problem: Check whether a partially filled 9 × 9 Sudoku board is valid: no digit may repeat within a row, a column, or a 3 × 3 box. Empty cells are ".". (It doesn't have to be solvable.)
Solve it on LeetCode ↗ · Video walkthrough ↗
board =
[["5","3",".",".","7",".",".",".","."]
,["6",".",".","1","9","5",".",".","."]
,[".","9","8",".",".",".",".","6","."]
,["8",".",".",".","6",".",".",".","3"]
,["4",".",".","8",".","3",".",".","1"]
,["7",".",".",".","2",".",".",".","6"]
,[".","6",".",".",".",".","2","8","."]
,[".",".",".","4","1","9",".",".","5"]
,[".",".",".",".","8",".",".","7","9"]]trueboard =
[["8","3",".",".","7",".",".",".","."]
,["6",".",".","1","9","5",".",".","."]
,[".","9","8",".",".",".",".","6","."]
,["8",".",".",".","6",".",".",".","3"]
,["4",".",".","8",".","3",".",".","1"]
,["7",".",".",".","2",".",".",".","6"]
,[".","6",".",".",".",".","2","8","."]
,[".",".",".","4","1","9",".",".","5"]
,[".",".",".",".","8",".",".","7","9"]]falseConstraints: board.length == 9 · board[i].length == 9 · board[i][j] is a digit 1-9 or '.'.
Hint 1
Each filled cell belongs to one row, one column and one 3 × 3 box.
Hint 2
Use a set per row, column and box (box index (r // 3) * 3 + c // 3); a repeat in any set means invalid.
From brute force to the pattern: Re-scanning the row, column and box of every filled cell is O(81 · 27), which is small but repetitive. Twenty-seven sets check each cell in O(1), in one pass.
Spot it: "No repeats within a group" → a set per group. A cell belongs to row r, column c and box (r // 3, c // 3); check all three sets before adding the digit.
Approach:
- Make 9 row sets, 9 column sets and 9 box sets.
- For every filled cell: compute
box = (r // 3) * 3 + c // 3. - If the digit is already in its row, column or box set, return
False. - Otherwise add it to all three. If the scan finishes, return
True.
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
rows = [set() for _ in range(9)]
cols = [set() for _ in range(9)]
boxes = [set() for _ in range(9)]
for r in range(9):
for c in range(9):
digit = board[r][c]
if digit == ".":
continue
# boxes are numbered 0..8, left to right, top to bottom
box = (r // 3) * 3 + c // 3
if digit in rows[r] or digit in cols[c] or digit in boxes[box]:
return False
rows[r].add(digit)
cols[c].add(digit)
boxes[box].add(digit)
return True
What the code is doing, line by line:
[set() for _ in range(9)]: nine separate sets. ([set()] * 9would be nine references to the same set.)if digit == ".": continue: empty cells can't conflict.box = (r // 3) * 3 + c // 3:r // 3is the band (0–2) andc // 3the stack (0–2), so e.g. cell(4, 7)is box1 * 3 + 2 = 5.if digit in rows[r] or ...: a repeat in any of the three groups breaks the rules.- Add the digit to all three sets so later cells can be checked against it.
Dry run on the example:
LeetCode Example 2: the valid example board, except its top-left 5 is replaced by 8. Returns False. Scanning row by row, the first conflict appears at (2, 2):
| cell | digit | box | conflict? |
|---|---|---|---|
| (0, 0) | 8 | 0 | no → add to row 0, col 0, box 0 |
| (0, 1) | 3 | 0 | no |
| (0, 4) | 7 | 1 | no |
| … | rows 1–2 have no conflicts | ||
| (2, 2) | 8 | 0 | box 0 already has 8 → False |
The 8 at (2, 2) repeats the 8 at (0, 0) inside the same top-left box, so the scan stops there.
Edge cases to test:
- An empty board → True.
- A duplicate only inside a box.
- A valid board that isn't solvable still counts as valid.
Complexity: O(81) = O(1) for a fixed 9 × 9 board (O(n²) for an n × n one), O(1) space.
Medium 229. Majority Element II Google Meta Amazon Microsoft
Problem: Return all values that appear more than ⌊n/3⌋ times.
nums = [3,2,3][3]nums = [1][1]Constraints: 1 ≤ nums.length ≤ 5 * 10⁴ · -10⁹ ≤ nums[i] ≤ 10⁹
Hint 1
How many values can appear more than n/3 times?
Hint 2
Generalise Boyer–Moore voting to two candidates, then verify.
From brute force to the pattern: A Counter is O(n) time but O(n) space; two-candidate voting is O(1) space.
Spot it: A hash map of counts solves it directly in O(n) space. The follow-up asks for O(1) space: at most two values can pass n/3, so keep two candidates with counters (Boyer–Moore voting), cancelling a triple of distinct values each time, then count the candidates to confirm.
Why not another pattern? Sorting and scanning is O(n log n).
Approach:
- Two candidate slots with counts.
- For each
x: if it matches a candidate, increment; else if a slot is empty, take it; else decrement both (cancel three distinct values). - Count the surviving candidates in a second pass; keep those above
n / 3.
class Solution:
def majorityElement(self, nums: List[int]) -> List[int]:
cand1, cand2, c1, c2 = None, None, 0, 0
for x in nums:
if x == cand1:
c1 += 1
elif x == cand2:
c2 += 1
elif c1 == 0:
cand1, c1 = x, 1 # take the empty slot
elif c2 == 0:
cand2, c2 = x, 1
else:
c1 -= 1 # x and both candidates cancel out
c2 -= 1
return [c for c in (cand1, cand2) if c is not None and nums.count(c) > len(nums) // 3] # verify
What the code is doing, line by line:
- Each cancellation removes three different values; a value with more than
n/3copies can't be fully cancelled. - The survivors are only candidates, so a second pass checks their real counts.
- The
is not Nonecheck skips an unused slot.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No majority →
[]. - Two majority values.
- All equal.
Complexity: O(n) time, O(1) space.
Medium 299. Bulls and Cows Google Amazon
Problem: Return the hint xAyB: x digits right in the right place, y right digits in the wrong place.
secret = "1807", guess = "7810""1A3B"secret = "1123", guess = "0111""1A1B"Constraints: 1 ≤ secret.length, guess.length ≤ 1000 · secret.length == guess.length · secret and guess consist of digits only.
Hint 1
Count bulls position by position first.
Hint 2
For the remaining digits, how many can be paired between the strings?
From brute force to the pattern: Pairing digits by searching is O(n²); counting is O(n).
Spot it: Count matching positions directly; for the rest, the number of shared digits is Σ min(count₁[d], count₂[d]) → counting with a hash map.
Why not another pattern? Matching each unmatched digit by searching the other string is O(n²).
Approach:
- For each position: equal → bull; otherwise count the secret digit and the guess digit separately.
- Cows =
Σ min(secret_count[d], guess_count[d]).
from collections import Counter
class Solution:
def getHint(self, secret: str, guess: str) -> str:
bulls = 0
left_s = Counter()
left_g = Counter()
for a, b in zip(secret, guess):
if a == b:
bulls += 1 # right digit, right place
else:
left_s[a] += 1 # unmatched digits on each side
left_g[b] += 1
cows = sum(min(left_s[d], left_g[d]) for d in left_s) # shared digits in wrong places
return f"{bulls}A{cows}B"
What the code is doing, line by line:
- Bulls are removed before counting cows, so a digit is never both.
- Each digit can be a cow as many times as it appears on both sides: the minimum of the two counts.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Repeated digits.
- All bulls.
- No matches.
Complexity: O(n) time, O(1) space (10 digits).
Medium 554. Brick Wall Google Amazon
Problem: Draw a vertical line through a brick wall crossing the fewest bricks (not along the outer edges). Return that number.
wall = [[1,2,2,1],[3,1,2],[1,3,2],[2,4],[3,1,2],[1,3,1,1]]2wall = [[1],[1],[1]]3Constraints: n == wall.length · 1 ≤ n ≤ 10⁴ · 1 ≤ wall[i].length ≤ 10⁴ · 1 ≤ sum(wall[i].length) ≤ 2 * 10⁴ · sum(wall[i]) is the same for each row i. · 1 ≤ wall[i][j] ≤ 2³¹ - 1
Hint 1
A line passing through an edge between bricks doesn't cross that row.
Hint 2
Count how many rows have an edge at each x position.
From brute force to the pattern: Testing every x up to the width is too slow; counting edge positions is linear in the number of bricks.
Spot it: A line crosses every row except those with a brick edge at its position. Count, with a hash map, how many rows have an edge at each position (prefix sums of brick widths); the answer is rows − max count.
Why not another pattern? Trying every x position up to the wall width (up to 2³¹) is impossible.
Approach:
- For each row, walk the bricks except the last, accumulating the position; increment
edges[position]. - Return
len(wall) − max(edges.values(), default=0).
from collections import Counter
class Solution:
def leastBricks(self, wall: List[List[int]]) -> int:
edges = Counter() # x position -> rows with a brick edge there
for row in wall:
x = 0
for width in row[:-1]: # skip the wall's right edge
x += width
edges[x] += 1
return len(wall) - max(edges.values(), default=0) # cross the bricks of all other rows
What the code is doing, line by line:
- Only positions where some row has an edge are worth considering.
- The last brick's edge is the wall's border, which isn't allowed, so it's skipped.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Every row is one brick → crosses all rows.
- All rows share an edge → 0.
- One row.
Complexity: O(total bricks) time and space.
Medium 811. Subdomain Visit Count Google
Problem: Given "count domain" entries, return the visit count of every domain and subdomain (any order).
cpdomains = ["9001 discuss.leetcode.com"]["9001 leetcode.com","9001 discuss.leetcode.com","9001 com"]cpdomains = ["900 google.mail.com", "50 yahoo.com", "1 intel.mail.com", "5 wiki.org"]["901 mail.com","50 yahoo.com","900 google.mail.com","5 wiki.org","5 org","1 intel.mail.com","951 com"]Constraints: 1 ≤ cpdomain.length ≤ 100 · 1 ≤ cpdomain[i].length ≤ 100 · cpdomain[i] follows either the "repi d1i.d2i.d3i" format or the "repi d1i.d2i" format. · repi is an integer in the range [1, 10⁴]. · d1i, d2i, and d3i consist of lowercase English letters.
Hint 1
Which domains does a visit to discuss.leetcode.com count toward?
Hint 2
Accumulate counts in a hash map.
From brute force to the pattern: This is already linear; the hash map is the whole idea.
Spot it: A visit to a.b.c is also a visit to b.c and c: accumulate into a hash map for every dot-suffix.
Why not another pattern? Nothing smarter is needed; the work is proportional to the input size.
Approach:
- Split each entry into count and domain.
- For each suffix after a dot (and the full domain), add the count.
- Format
"count domain"for each key.
from collections import Counter
class Solution:
def subdomainVisits(self, cpdomains: List[str]) -> List[str]:
visits = Counter()
for entry in cpdomains:
count, domain = entry.split()
parts = domain.split(".")
for i in range(len(parts)):
visits[".".join(parts[i:])] += int(count) # every parent domain is visited too
return [f"{c} {d}" for d, c in visits.items()]
What the code is doing, line by line:
parts[i:]gives the domain itself (i = 0) and each parent domain.- Counts for the same domain from different entries add up.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Top-level domains shared by many entries.
- One entry.
- Domains with many levels.
Complexity: O(total characters) time and space.
Medium 833. Find And Replace in String Google
Problem: Apply simultaneous, non-overlapping replacements: at indices[i], if sources[i] matches the original s, replace it with targets[i].
s = "abcd", indices = [0, 2], sources = ["a", "cd"], targets = ["eee", "ffff"]"eeebffff"s = "abcd", indices = [0, 2], sources = ["ab","ec"], targets = ["eee","ffff"]"eeecd"Constraints: 1 ≤ s.length ≤ 1000 · k == indices.length == sources.length == targets.length · 1 ≤ k ≤ 100 · 0 ≤ indexes[i] < s.length · 1 ≤ sources[i].length, targets[i].length ≤ 50 · s consists of only lowercase English letters. · sources[i] and targets[i] consist of only lowercase English letters.
Hint 1
Check all matches against the original string first.
Hint 2
Then build the result in one pass from left to right.
From brute force to the pattern: Applying replacements one by one in place shifts indices and needs care; one rebuild pass is simplest.
Spot it: Replacements change lengths, so apply them by rebuilding the string in one left-to-right pass. A hash map from index to the valid replacement says what to do at each position.
Why not another pattern? Replacing in place shifts the later indices; doing them right-to-left works but needs sorting.
Approach:
- For each operation whose source matches at its index, store
todo[index] = (len(source), target). - Walk
ithroughs: ifiis intodo, append the target and jump past the source; else appends[i].
class Solution:
def findReplaceString(self, s: str, indices: List[int], sources: List[str], targets: List[str]) -> str:
todo = {} # index -> (length to replace, replacement)
for i, src, tgt in zip(indices, sources, targets):
if s.startswith(src, i):
todo[i] = (len(src), tgt) # matches the original string
out = []
i = 0
while i < len(s):
if i in todo:
length, tgt = todo[i]
out.append(tgt)
i += length # skip the replaced part
else:
out.append(s[i])
i += 1
return "".join(out)
What the code is doing, line by line:
- Matching is checked against the original string, as required (
startswith(src, i)). - Building a new list keeps all original indices valid while replacing.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- A source that doesn't match (skip it).
- Indices given out of order.
- Replacements at the very end.
Complexity: O(n + Σ source lengths + Σ target lengths) time and space.
Medium 963. Minimum Area Rectangle II Google
Problem: Return the minimum area of a rectangle (any rotation) with corners among the points, or 0.
points = [[1,2],[2,1],[1,0],[0,1]]2.00000points = [[0,1],[2,1],[1,1],[1,0],[2,0]]1.00000Constraints: 1 ≤ points.length ≤ 50 · points[i].length == 2 · 0 ≤ xi, yi ≤ 4 * 10⁴ · All the given points are unique.
Hint 1
What do the two diagonals of a rectangle have in common?
Hint 2
Group pairs of points by midpoint and length.
From brute force to the pattern: All 4-point subsets is O(n⁴) with a rectangle test; grouping diagonals is about O(n²).
Spot it: Two segments are the diagonals of a rectangle iff they have the same midpoint and the same length. Group all point pairs in a hash map by (sum of coordinates, squared length); any two pairs in a group form a rectangle.
Why not another pattern? Trying all 4-point combinations is O(n⁴).
Approach:
- For every pair
(p, q): key =(px + qx, py + qy, |p − q|²); append the pair. - Within a group, for pairs
(p, q)and(r, s): area =|p − r| · |p − s|. - Return the minimum area (0 if none).
from collections import defaultdict
from math import dist
class Solution:
def minAreaFreeRect(self, points: List[List[int]]) -> float:
groups = defaultdict(list) # (2 * midpoint, squared diagonal length) -> point pairs
for i in range(len(points)):
for j in range(i + 1, len(points)):
(x1, y1), (x2, y2) = points[i], points[j]
key = (x1 + x2, y1 + y2, (x1 - x2) ** 2 + (y1 - y2) ** 2)
groups[key].append((points[i], points[j]))
best = float("inf")
for pairs in groups.values():
for a in range(len(pairs)):
for b in range(a + 1, len(pairs)):
p, q = pairs[a]
r, s = pairs[b] # same midpoint and length: a rectangle p, r, q, s
best = min(best, dist(p, r) * dist(p, s)) # two adjacent sides
return best if best < float("inf") else 0.0
What the code is doing, line by line:
- Using
x1 + x2(twice the midpoint) keeps keys as integers. - Equal diagonals bisecting each other make a rectangle; its sides are
p → randp → s. - Groups are usually small, so the pair-of-pairs loop is cheap in practice.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No rectangle → 0.
- Axis-aligned rectangles.
- Collinear points.
Complexity: O(n²) to group, plus O(Σ group²) to combine (O(n⁴) worst case, rare).
Medium 1657. Determine if Two Strings Are Close Google
Problem: Operations: swap any two characters; or swap all occurrences of two existing letters. Can word1 become word2?
word1 = "abc", word2 = "bca"trueword1 = "a", word2 = "aa"falseConstraints: 1 ≤ word1.length, word2.length ≤ 10⁵ · word1 and word2 contain only lowercase English letters.
Hint 1
What can operation 1 change? What can't it?
Hint 2
What does operation 2 do to the letter counts?
From brute force to the pattern: Simulating operations is hopeless; comparing two invariants is O(n).
Spot it: Swaps make order irrelevant; the second operation permutes which letter has which count. So the strings are close iff they use the same letters and the same list of counts → compare with hash maps.
Why not another pattern? Searching sequences of operations is pointless once the invariants are clear.
Approach:
- Count letters of both words.
- Same key sets, and same sorted count values → true.
from collections import Counter
class Solution:
def closeStrings(self, word1: str, word2: str) -> bool:
c1 = Counter(word1)
c2 = Counter(word2)
same_letters = c1.keys() == c2.keys() # operation 2 can't create new letters
same_counts = sorted(c1.values()) == sorted(c2.values()) # counts can be reassigned between letters
return same_letters and same_counts
What the code is doing, line by line:
- Operation 1 lets us reorder freely, so only counts matter.
- Operation 2 swaps the counts of two letters that both exist, so counts can be permuted among the same letter set.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Different lengths → false.
- A letter in one word only → false.
- Same counts on different letters.
Complexity: O(n) time, O(1) space (26 letters).
Medium 1781. Sum of Beauty of All Substrings Google Amazon
Problem: The beauty of a string is (most frequent count − least frequent count). Sum the beauty of all substrings.
s = "aabcb"5s = "aabcbaa"17Constraints: 1 ≤ s.length ≤ 500 · s consists of only lowercase English letters.
Hint 1
Enumerate substrings by start and extend the end.
Hint 2
Keep a running count of letters.
From brute force to the pattern: Recounting each substring is O(n³); incremental counts make it O(26 n²).
Spot it: There are O(n²) substrings; extending each start one character at a time keeps a frequency table, so each substring costs O(26).
Why not another pattern? Counting each substring from scratch is O(n³).
Approach:
- For each start
i: a fresh count array. - For each end
j ≥ i: adds[j]; addmax(counts) − min(non-zero counts).
class Solution:
def beautySum(self, s: str) -> int:
total = 0
for i in range(len(s)):
count = [0] * 26 # letter counts of s[i..j]
for j in range(i, len(s)):
count[ord(s[j]) - 97] += 1
present = [c for c in count if c]
total += max(present) - min(present) # beauty of s[i..j]
return total
What the code is doing, line by line:
- Extending the end by one only changes one count.
- Letters that don't appear (count 0) are ignored for the minimum.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All same letter → 0.
- Length 1.
- Two letters alternating.
Complexity: O(26 · n²) time, O(1) space.
Medium 2001. Number of Pairs of Interchangeable Rectangles Google
Problem: Count pairs of rectangles with the same width-to-height ratio.
rectangles = [[4,8],[3,6],[10,20],[15,30]]6rectangles = [[4,5],[7,8]]0Constraints: n == rectangles.length · 1 ≤ n ≤ 10⁵ · rectangles[i].length == 2 · 1 ≤ widthi, heighti ≤ 10⁵
Hint 1
Rectangles with the same ratio form groups.
Hint 2
Use an exact key: the reduced fraction.
From brute force to the pattern: Comparing every pair is O(n²); grouping is O(n).
Spot it: Group rectangles by their reduced ratio in a hash map; a group of size c contributes c(c − 1)/2 pairs.
Why not another pattern? Comparing all pairs is O(n²); float ratios risk precision errors, so use reduced integer pairs.
Approach:
- For each rectangle, key =
(w / g, h / g)withg = gcd(w, h). - Before counting it, add the number already seen with that key (counts pairs as they form).
from collections import Counter
from math import gcd
class Solution:
def interchangeableRectangles(self, rectangles: List[List[int]]) -> int:
seen = Counter()
pairs = 0
for w, h in rectangles:
g = gcd(w, h)
key = (w // g, h // g) # exact ratio, no floating point
pairs += seen[key] # pairs with every earlier rectangle of this ratio
seen[key] += 1
return pairs
What the code is doing, line by line:
- Dividing by the gcd gives every equal ratio the same key.
- Adding the earlier count before incrementing counts each pair once.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All the same ratio.
- All different.
- Large dimensions (floats would be risky).
Complexity: O(n log V) time, O(n) space.
Medium 2007. Find Original Array From Doubled Array Google
Problem: changed is an array plus all its doubles, shuffled. Return the original array, or [].
changed = [1,3,4,2,6,8][1,3,4]changed = [6,3,0,1][]Constraints: 1 ≤ changed.length ≤ 10⁵ · 0 ≤ changed[i] ≤ 10⁵
Hint 1
Which element must definitely be in the original array?
Hint 2
Process values from smallest to largest, pairing each with its double.
From brute force to the pattern: Trying pairings by search is exponential; sorted greedy with counts is O(n log n).
Spot it: The smallest remaining value can't be a double of anything left, so it must be original and its double must exist. Process values in sorted order with a count map.
Why not another pattern? Pairing greedily in arbitrary order can pair x with x/2 when x should be original.
Approach:
- If the length is odd, return
[]. - Count values; for each
xin increasing order withcount[x] > 0: needcount[2x] ≥ count[x](zeros pair with zeros). - Take
count[x]copies ofx, subtract fromcount[2x].
from collections import Counter
class Solution:
def findOriginalArray(self, changed: List[int]) -> List[int]:
if len(changed) % 2:
return []
count = Counter(changed)
original = []
for x in sorted(count): # smallest first: it can't be someone's double
if count[x] == 0:
continue
if x == 0:
if count[0] % 2:
return []
original += [0] * (count[0] // 2) # zeros pair with zeros
continue
if count[2 * x] < count[x]:
return [] # not enough doubles
original += [x] * count[x]
count[2 * x] -= count[x]
return original
What the code is doing, line by line:
- The smallest value can only be an original element (its half would be smaller).
- All copies of
xare handled at once using the counts. - Zero is its own double, so zeros must come in pairs.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Odd length →
[]. - Zeros.
- Chains like 1, 2, 2, 4.
Complexity: O(n log n) time, O(n) space.
Medium 2150. Find All Lonely Numbers in the Array Google Apple
Problem: Return every number that appears exactly once and whose neighbours x − 1, x + 1 don't appear.
nums = [10,6,5,8][10,8]nums = [1,3,5,3][1,5]Constraints: 1 ≤ nums.length ≤ 10⁵ · 0 ≤ nums[i] ≤ 10⁶
Hint 1
You need counts and fast neighbour checks.
Hint 2
A single Counter gives both.
From brute force to the pattern: Checking neighbours by scanning the array is O(n²); a counter is O(n).
Spot it: Both conditions are lookups → one counter answers each check in O(1).
Why not another pattern? Sorting and checking neighbours works in O(n log n); the counter is O(n).
Approach:
- Count all numbers.
- Keep
xifcount[x] == 1andx − 1,x + 1aren't in the counter.
from collections import Counter
class Solution:
def findLonely(self, nums: List[int]) -> List[int]:
count = Counter(nums)
return [x for x in count if count[x] == 1 and x - 1 not in count and x + 1 not in count] # alone and no neighbours
What the code is doing, line by line:
- Membership tests on the counter are
O(1). - Iterating over the counter's keys visits each distinct value once.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Duplicates aren't lonely.
- Negative numbers.
- Consecutive runs.
Complexity: O(n) time and space.
Medium 2225. Find Players With Zero or One Losses Google
Problem: Return [players with no losses, players with exactly one loss], each sorted, among players who played.
matches = [[1,3],[2,3],[3,6],[5,6],[5,7],[4,5],[4,8],[4,9],[10,4],[10,9]][[1,2,10],[4,5,7,8]]matches = [[2,3],[1,3],[5,4],[6,4]][[1,2,5,6],[]]Constraints: 1 ≤ matches.length ≤ 10⁵ · matches[i].length == 2 · 1 ≤ winneri, loseri ≤ 10⁵ · winneri != loseri · All matches[i] are unique.
Hint 1
Count losses for every player who played.
Hint 2
Winners need an entry too, with 0 losses.
From brute force to the pattern: Counting losses per player by scanning all matches is quadratic; one pass with a map is linear.
Spot it: A hash map of losses, with winners inserted at 0 so they're known to have played.
Why not another pattern? Scanning matches per player is O(players · matches).
Approach:
- For each match:
losses.setdefault(winner, 0),losses[loser] += 1. - Collect sorted players with 0 and with 1.
class Solution:
def findWinners(self, matches: List[List[int]]) -> List[List[int]]:
losses = {}
for winner, loser in matches:
losses.setdefault(winner, 0) # played, maybe never lost
losses[loser] = losses.get(loser, 0) + 1
never = sorted(p for p, c in losses.items() if c == 0)
once = sorted(p for p, c in losses.items() if c == 1)
return [never, once]
What the code is doing, line by line:
- Only players who appear in some match are in the map.
- Sorting each list at the end gives the required order.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- A player who only won.
- Nobody with one loss → empty list.
- One match.
Complexity: O(m + p log p) time, O(p) space.
Medium 2364. Count Number of Bad Pairs Google
Problem: A pair i < j is bad if j − i != nums[j] − nums[i]. Count bad pairs.
nums = [4,1,3,3]5nums = [1,2,3,4,5]0Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁹
Hint 1
Rewrite the good-pair condition so each side depends on one index only.
Hint 2
Count the complement: good pairs.
From brute force to the pattern: All pairs is O(n²); key counting is O(n).
Spot it: Rearranging the good condition gives nums[i] − i == nums[j] − j: the same key. Count good pairs per key with a hash map and subtract from all pairs.
Why not another pattern? Checking all pairs is O(n²).
Approach:
- For each
j,key = nums[j] − j; good pairs with earlier equal keys =seen[key]. - Bad =
j − seen[key]for thisj; sum it.
from collections import Counter
class Solution:
def countBadPairs(self, nums: List[int]) -> int:
seen = Counter()
bad = 0
for j, x in enumerate(nums):
key = x - j # good partners share this value
bad += j - seen[key] # earlier indices that are not good partners
seen[key] += 1
return bad
What the code is doing, line by line:
j − i = nums[j] − nums[i]⇔nums[i] − i = nums[j] − j.- There are
jearlier indices; those with the same key are good, the rest bad.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All good (arithmetic with step 1) → 0.
- All distinct keys → n(n − 1)/2.
- One element → 0.
Complexity: O(n) time and space.
Medium 2657. Find the Prefix Common Array of Two Arrays Google Amazon
Problem: For permutations A and B, C[i] = how many values appear in both A[:i+1] and B[:i+1].
A = [1,3,2,4], B = [3,1,2,4][0,2,3,4]A = [2,3,1], B = [3,1,2][0,1,3]Constraints: 1 ≤ A.length == B.length == n ≤ 50 · 1 ≤ A[i], B[i] ≤ n · It is guaranteed that A and B are both a permutation of n integers.
Hint 1
When does a value start counting as common?
Hint 2
Count how many times you've seen each value.
From brute force to the pattern: Set intersection per prefix is O(n²); counting is O(n).
Spot it: Each value appears once in each array; it's common from the index where its second occurrence (in either array) appears → a count array.
Why not another pattern? Intersecting the prefixes at every index is O(n²).
Approach:
seen[v]counts occurrences so far.- At index
i, addA[i]andB[i]; each time a count reaches 2,common += 1. C[i] = common.
class Solution:
def findThePrefixCommonArray(self, A: List[int], B: List[int]) -> List[int]:
seen = [0] * (len(A) + 1)
common = 0
C = []
for a, b in zip(A, B):
for v in (a, b):
seen[v] += 1
if seen[v] == 2:
common += 1 # now present in both prefixes
C.append(common)
return C
What the code is doing, line by line:
- Values are a permutation, so each appears exactly once per array.
- The second sighting means both prefixes contain it.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Same arrays → 1, 2, …, n.
A[i] == B[i].- n = 1.
Complexity: O(n) time and space.
Medium 2840. Check if Strings Can be Made Equal With Operations II Google Amazon Microsoft
Problem: You may swap characters whose indices differ by an even number, in either string. Can they be made equal?
s1 = "abcdba", s2 = "cabdab"trues1 = "abe", s2 = "bea"falseConstraints: n == s1.length == s2.length · 1 ≤ n ≤ 10⁵ · s1 and s2 consist only of lowercase English letters.
Hint 1
Can a character ever move from an even index to an odd one?
Hint 2
Compare the multisets of each parity class.
From brute force to the pattern: Searching swap sequences is exponential; two counter comparisons are linear.
Spot it: Even-distance swaps never move a character between even and odd positions, and within each class any arrangement is reachable. So compare the letter counts of even positions and of odd positions.
Why not another pattern? Simulating swaps is unnecessary once the invariant is clear.
Approach:
Counter(s1[0::2]) == Counter(s2[0::2]).Counter(s1[1::2]) == Counter(s2[1::2]).
from collections import Counter
class Solution:
def checkStrings(self, s1: str, s2: str) -> bool:
evens_match = Counter(s1[0::2]) == Counter(s2[0::2]) # characters at even indices
odds_match = Counter(s1[1::2]) == Counter(s2[1::2]) # characters at odd indices
return evens_match and odds_match
What the code is doing, line by line:
- Parity of the position is preserved by every allowed swap.
- Any permutation within a parity class is a product of allowed swaps.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Length 1.
- Same letters in the wrong parity.
- Already equal.
Complexity: O(n) time, O(1) space.
Medium 3016. Minimum Number of Pushes to Type Word II Google Amazon
Problem: Map letters onto keys 2–9 freely. Minimise total pushes to type word (the k-th letter on a key costs k pushes).
word = "abcde"5word = "xyzxyzxyzxyz"12Constraints: 1 ≤ word.length ≤ 10⁵ · word consists of lowercase English letters.
Hint 1
How many letters can cost 1 push? 2 pushes?
Hint 2
Which letters deserve the cheapest slots?
From brute force to the pattern: Trying letter-to-key assignments is factorial; greedy by frequency is O(n).
Spot it: Eight keys give 8 first slots, 8 second slots, …. Give the cheapest slots to the most frequent letters: count and sort descending.
Why not another pattern? Trying assignments is factorial; the exchange argument makes greedy optimal.
Approach:
- Count letters; sort counts descending.
- The
i-th count (0-indexed) is multiplied byi // 8 + 1.
from collections import Counter
class Solution:
def minimumPushes(self, word: str) -> int:
counts = sorted(Counter(word).values(), reverse=True) # most frequent first
return sum(c * (i // 8 + 1) for i, c in enumerate(counts)) # 8 letters per push level
What the code is doing, line by line:
- Swapping a more frequent letter into a cheaper slot never increases the total, so sorted order is optimal.
- Positions 0–7 cost 1, 8–15 cost 2, and so on.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- ≤ 8 distinct letters → length of word.
- 26 distinct letters.
- One letter repeated.
Complexity: O(n) time, O(1) space.
Medium 3020. Find the Maximum Number of Elements in Subset Google Meta Amazon
Problem: Pick the largest subset arrangeable as [x, x², x⁴, …, xᵏ, …, x⁴, x², x].
nums = [5,4,1,2,2]3nums = [1,3,2,4]1Constraints: 2 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁹
Hint 1
How many copies does each level of the pattern need?
Hint 2
Start from each value and keep squaring while you have two copies. Watch out for 1.
From brute force to the pattern: Subset enumeration is exponential; climbing square chains from each value is near-linear.
Spot it: Every level except the top needs two copies, the top needs one. With a counter, climb from each base value by squaring; 1 is special (1² = 1) and needs an odd count.
Why not another pattern? Trying subsets is exponential; values grow by squaring, so each chain has at most ~5 steps.
Approach:
- Ones: use the largest odd number ≤
count[1]. - For each
x > 1:length = 0; whilecount[x] ≥ 2:length += 2,x = x². - Then
+1ifxis present (top), else−1(the last pair's one copy becomes the top).
from collections import Counter
class Solution:
def maximumLength(self, nums: List[int]) -> int:
count = Counter(nums)
ones = count[1]
best = ones if ones % 2 else ones - 1 # a pattern of 1s needs an odd count
for x in count:
if x == 1:
continue
length = 0
while count[x] >= 2:
length += 2 # x on both sides
x = x * x
length += 1 if count[x] >= 1 else -1 # top of the pattern, or reuse the last pair's copy
best = max(best, length)
return best
What the code is doing, line by line:
- Each level other than the peak appears twice in the pattern.
- If the next square is missing, one copy of the last level becomes the peak (
−1). - Squaring grows fast, so each chain is short (at most about 5 levels).
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Many 1s (odd count).
- No pair anywhere → 1.
- Long chains like 2, 4, 16, 256.
Complexity: O(n log log V) time, O(n) space.
Medium 3121. Count the Number of Special Characters II Google
Problem: A letter is special if it appears in both cases and every lowercase occurrence comes before the first uppercase one.
word = "aaAbcBC"3word = "abc"0Constraints: 1 ≤ word.length ≤ 2 * 10⁵ · word consists of only lowercase and uppercase English letters.
Hint 1
Which occurrences actually decide the condition?
Hint 2
Track the last lowercase and the first uppercase index of each letter.
From brute force to the pattern: Comparing every lowercase/uppercase pair is O(n²); two maps are O(n).
Spot it: Only two positions per letter matter: the last lowercase and the first uppercase. Store them in hash maps in one pass.
Why not another pattern? Checking each pair of occurrences is O(n²).
Approach:
- One pass: update
last_lower[c]and setfirst_upper[c]once. - Count letters present in both with
last_lower < first_upper.
class Solution:
def numberOfSpecialChars(self, word: str) -> int:
last_lower = {}
first_upper = {}
for i, ch in enumerate(word):
if ch.islower():
last_lower[ch] = i # keeps the latest one
else:
first_upper.setdefault(ch.lower(), i) # keeps the earliest one
return sum(1 for c in last_lower if c in first_upper and last_lower[c] < first_upper[c])
What the code is doing, line by line:
- If the last lowercase comes before the first uppercase, every lowercase does.
setdefaultstores only the first uppercase index.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- A lowercase after an uppercase → not special.
- Letters in one case only.
- Single character.
Complexity: O(n) time, O(1) space (26 letters).
Medium 3741. Minimum Distance Between Three Equal Elements II Google Microsoft
Problem: Find three equal elements minimising |i − j| + |j − k| + |k − i|, or −1.
nums = [1,2,1,1,3]6nums = [1,1,2,3,2,1,2]8Constraints: 1 ≤ n == nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ n
Hint 1
Simplify the distance formula when i < j < k.
Hint 2
Only consecutive occurrences of each value need checking.
From brute force to the pattern: All triples: O(n³). Grouped consecutive windows: O(n).
Spot it: For i < j < k the distance is 2(k − i), so only the outer two indices matter, and they should be as close as possible: three consecutive occurrences of the same value. Group positions with a hash map.
Why not another pattern? Trying all triples is O(n³).
Approach:
- Group indices by value (in order).
- For each group, check every window of three consecutive indices:
2 · (pos[t + 2] − pos[t]).
from collections import defaultdict
class Solution:
def minimumDistance(self, nums: List[int]) -> int:
where = defaultdict(list) # value -> indices in increasing order
for i, x in enumerate(nums):
where[x].append(i)
best = float("inf")
for pos in where.values():
for t in range(len(pos) - 2):
best = min(best, 2 * (pos[t + 2] - pos[t])) # tightest triple: consecutive occurrences
return best if best < float("inf") else -1
What the code is doing, line by line:
|i − j| + |j − k| + |k − i| = 2(k − i)wheni < j < k.- Skipping an occurrence between them only makes
k − ilarger, so consecutive triples are best.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No value appears three times → −1.
- Many copies of one value.
- Adjacent triple → 4.
Complexity: O(n) time and space.
Medium 3761. Minimum Absolute Distance Between Mirror Pairs Google Meta Amazon Microsoft
Problem: Find i < j with reverse(nums[i]) == nums[j] minimising j − i, or −1.
nums = [12,21,45,33,54]1nums = [120,21]1Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁹
Hint 1
For index j, which earlier index is the best partner?
Hint 2
Store reversed values with their latest index.
From brute force to the pattern: All pairs: O(n²). One pass with a map: O(n).
Spot it: For each j, the best partner is the latest earlier i whose reversed value equals nums[j]. A hash map from reversed value → latest index answers that in O(1).
Why not another pattern? Checking all pairs is O(n²).
Approach:
- For each
j: ifnums[j]is inlatest, update the best withj − latest[nums[j]]. - Then store
latest[reverse(nums[j])] = j.
class Solution:
def minMirrorPairDistance(self, nums: List[int]) -> int:
latest = {} # reverse(nums[i]) -> the most recent such i
best = float("inf")
for j, x in enumerate(nums):
if x in latest:
best = min(best, j - latest[x]) # closest earlier mirror partner
latest[int(str(x)[::-1])] = j # leading zeros vanish in int()
return best if best < float("inf") else -1
What the code is doing, line by line:
- Looking up before inserting keeps
i < j. - Overwriting with the latest index keeps the closest partner.
int("021")is 21, which drops leading zeros as required.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Trailing zeros (120 → 21).
- Palindromes pair with equal values.
- No pair → −1.
Complexity: O(n · digits) time, O(n) space.
Medium 249. Group Shifted Strings Meta
Problem: Strings belong together if one can be shifted letter by letter (a→b→…→z→a) into the other. Group them (any order).
Solve it on LeetCode ↗ (Premium)
strings = ["abc","bcd","acef","xyz","az","ba","a","z"][["acef"],["a","z"],["abc","bcd","xyz"],["az","ba"]]strings = ["a"][["a"]]Constraints: 1 ≤ strings.length ≤ 200 · 1 ≤ strings[i].length ≤ 50 · strings[i] consists of lowercase English letters
Hint 1
What stays the same when every letter is shifted by the same amount?
Hint 2
Use it as a key, as in Group Anagrams.
From brute force to the pattern: Pairwise shift checks are quadratic; a canonical key groups in one pass.
Spot it: Shifting keeps the differences between neighbouring letters (mod 26). Use that tuple as a hash map key, like grouping anagrams.
Why not another pattern? Comparing every pair of strings by trying all 26 shifts is O(n² · L · 26).
Approach:
- key(s) =
tuple((ord(s[i]) − ord(s[i − 1])) % 26 for i ≥ 1). - Append each string to
groups[key]. - Return the groups.
from collections import defaultdict
class Solution:
def groupStrings(self, strings: List[str]) -> List[List[str]]:
groups = defaultdict(list)
for s in strings:
key = tuple((ord(b) - ord(a)) % 26 for a, b in zip(s, s[1:])) # shape that survives shifting
groups[key].append(s)
return list(groups.values())
What the code is doing, line by line:
- Shifting every letter by the same amount leaves each neighbouring gap unchanged.
% 26handles the wrap from z to a ("az" and "ba" both have gap 25).
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Single letters (all one group).
- Wrap-around (az, ba).
- Duplicates.
Complexity: O(total characters) time and space.
Medium 791. Custom Sort String Meta
Problem: Rearrange s so its letters follow the order given in order (letters not in order can go anywhere).
order = "cba", s = "abcd""cbad"order = "bcafg", s = "abcd""bcad"Constraints: 1 ≤ order.length ≤ 26 · 1 ≤ s.length ≤ 200 · order and s consist of lowercase English letters. · All the characters of order are unique.
Hint 1
Do you need to compare letters at all?
Hint 2
Count the letters of s, then write them in the custom order.
From brute force to the pattern: A key-based sort is O(n log n); counting is linear.
Spot it: Only counts matter: count s, write each letter of order as many times as it appears, then append the leftovers.
Why not another pattern? Sorting with a custom key works in O(n log n); counting is O(n).
Approach:
count = Counter(s).- For each letter in
order: append itcount[c]times and remove it. - Append the remaining letters.
from collections import Counter
class Solution:
def customSortString(self, order: str, s: str) -> str:
count = Counter(s)
out = []
for ch in order:
out.append(ch * count.pop(ch, 0)) # all copies, in the custom order
for ch, k in count.items():
out.append(ch * k) # letters order doesn't mention
return "".join(out)
What the code is doing, line by line:
popremoves letters already placed so they aren't repeated.- Any arrangement of the leftover letters is accepted.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Letters of order missing from s.
- Letters of s missing from order.
- Repeated letters.
Complexity: O(len(s) + len(order)) time, O(1) extra space (26 letters).
Medium 1570. Dot Product of Two Sparse Vectors Meta
Problem: Design SparseVector(nums) with dotProduct(vec) that is efficient when most entries are zero.
Solve it on LeetCode ↗ (Premium)
nums1 = [1,0,0,2,3], nums2 = [0,3,0,4,0]8nums1 = [0,1,0,0,0], nums2 = [0,0,0,0,2]0nums1 = [0,1,0,0,2,0,0], nums2 = [1,0,0,0,3,0,4]6Constraints: n == nums1.length == nums2.length · 1 ≤ n ≤ 10⁵ · 0 ≤ nums1[i], nums2[i] ≤ 100
Hint 1
Which entries actually affect the dot product?
Hint 2
Store only the non-zeros; match indices with two pointers.
From brute force to the pattern: The dense product is O(n); sparse pairs make it proportional to the number of non-zeros.
Spot it: Zeros contribute nothing, so store only non-zero (index, value) pairs. The dot product walks both sorted lists with two pointers, multiplying where indices match. (A hash map of index → value also works; pairs are what interviewers prefer when one vector is dense.)
Why not another pattern? Storing the whole array makes each product O(n) even when there are only a few non-zeros.
Approach:
- Constructor:
pairs = [(i, v) for v ≠ 0]. dotProduct: pointersa,b; equal indices → add the product and advance both; else advance the smaller index.
class SparseVector:
def __init__(self, nums: List[int]):
self.pairs = [(i, v) for i, v in enumerate(nums) if v] # only non-zero entries, by index
def dotProduct(self, vec: "SparseVector") -> int:
a = b = 0
total = 0
x, y = self.pairs, vec.pairs
while a < len(x) and b < len(y):
if x[a][0] == y[b][0]:
total += x[a][1] * y[b][1] # both non-zero at this index
a += 1
b += 1
elif x[a][0] < y[b][0]:
a += 1 # the other vector is zero here
else:
b += 1
return total
What the code is doing, line by line:
- Only indices where both vectors are non-zero add to the product.
- Both pair lists are sorted by index, so a merge-style walk finds the matches.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All zeros → 0.
- No overlapping non-zero indices.
- One dense and one sparse vector.
Complexity: O(n) to build, O(k₁ + k₂) per product for k non-zeros each.
Medium 3960. Frequency Balance Subarray Meta
Problem: A subarray is balanced if it has one distinct value, or every distinct value occurs f or 2f times with both frequencies present. Return the longest balanced subarray.
nums = [1,2,2,1,2,3,3,3]5nums = [5,5,5,5]4Constraints: 1 ≤ nums.length ≤ 10³ · 1 ≤ nums[i] ≤ 10⁹
Hint 1
n is at most 1000: an O(n²) scan is fine if each step is O(1).
Hint 2
Track how many values have each frequency.
From brute force to the pattern: Counting each subarray from scratch is O(n³); incremental counts make it O(n²).
Spot it: n ≤ 1000 allows checking all O(n²) subarrays, if each check is O(1). Extend the end one step at a time, keeping a count map and a count-of-counts map: the subarray is balanced when the count-of-counts has one key (and one distinct value) or exactly two keys f and 2f.
Why not another pattern? Recounting each subarray from scratch is O(n³).
Approach:
- For each start
l: emptycountandfreq_of(count → how many values have it). - Add
nums[r]: move that value fromfreq_of[c]tofreq_of[c + 1](deleting empty keys). - Balanced if
len(count) == 1, orfreq_ofhas two keys with the larger exactly twice the smaller.
from collections import defaultdict
class Solution:
def getLength(self, nums: List[int]) -> int:
best = 0
for l in range(len(nums)):
count = defaultdict(int) # value -> occurrences in nums[l..r]
freq_of = defaultdict(int) # occurrences -> how many values have that many
for r in range(l, len(nums)):
c = count[nums[r]]
if c:
freq_of[c] -= 1
if freq_of[c] == 0:
del freq_of[c]
count[nums[r]] = c + 1
freq_of[c + 1] += 1
if len(count) == 1:
best = max(best, r - l + 1) # one distinct value
elif len(freq_of) == 2:
low, high = sorted(freq_of)
if high == 2 * low:
best = max(best, r - l + 1) # every value occurs f or 2f times
return best
What the code is doing, line by line:
- The count-of-counts map always has at most a few keys, so each check is
O(1). - Empty frequency keys are deleted so
len(freq_of)is the number of distinct frequencies.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All equal → n.
- All distinct → 1.
- Frequencies f and 3f (not balanced).
Complexity: O(n²) time, O(n) space.
Medium 1152. Analyze User Website Visit Pattern Amazon
Problem: A pattern is 3 websites visited in that order by a user (not necessarily consecutively). Return the pattern visited by the most users (smallest lexicographically on ties).
Solve it on LeetCode ↗ (Premium)
username = ["joe","joe","joe","james","james","james","james","mary","mary","mary"], timestamp = [1,2,3,4,5,6,7,8,9,10], website = ["home","about","career","home","cart","maps","home","home","about","career"]["home","about","career"]username = ["ua","ua","ua","ub","ub","ub"], timestamp = [1,2,3,4,5,6], website = ["a","b","a","a","b","c"]["a","b","a"]Constraints: 3 ≤ username.length ≤ 50 · 1 ≤ username[i].length ≤ 10 · timestamp.length == username.length · 1 ≤ timestamp[i] ≤ 10⁹ · website.length == username.length · 1 ≤ website[i].length ≤ 10 · username[i] and website[i] consist of lowercase English letters · It is guaranteed that there is at least one user who visited at least three websites · All the tuples [username[i], timestamp[i], website[i]] are unique
Hint 1
Sort each user's visits by time.
Hint 2
Generate each user's 3-site patterns once and count users per pattern.
From brute force to the pattern: This is the direct counting; the set per user is what prevents double counting.
Spot it: Sort each user's visits by time, generate every ordered triple of their visits, keep them in a set (a user counts once per pattern), then count patterns across users in a hash map.
Why not another pattern? Checking each candidate pattern against every user separately repeats work.
Approach:
- Group
(time, site)by user and sort by time. - For each user:
set(combinations(sites, 3)). - Count over users; return
min(count, key=(−count, pattern)).
from collections import Counter, defaultdict
from itertools import combinations
class Solution:
def mostVisitedPattern(self, username: List[str], timestamp: List[int], website: List[str]) -> List[str]:
visits = defaultdict(list)
for t, user, site in sorted(zip(timestamp, username, website)):
visits[user].append(site) # in time order
score = Counter()
for sites in visits.values():
for pattern in set(combinations(sites, 3)): # each user counts a pattern once
score[pattern] += 1
best = min(score, key=lambda p: (-score[p], p)) # most users, then lexicographically smallest
return list(best)
What the code is doing, line by line:
combinationskeeps the visit order, which is what a pattern needs.- The set removes repeated patterns from the same user.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Users with fewer than 3 visits.
- Ties (lexicographic).
- Repeated sites in one user's history.
Complexity: O(Σ v³) for v visits per user (v ≤ 50), O of the number of patterns in space.
Medium 2910. Minimum Number of Groups to Create a Valid Assignment Amazon
Problem: Put balls into boxes: each box holds one value, and box sizes differ by at most 1. Return the fewest boxes.
balls = [3,2,3,2,3]2balls = [10,10,10,3,1,1]4Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁹
Hint 1
Only how many times each value appears matters.
Hint 2
All boxes have size s or s + 1. Which s can a count c be split into?
From brute force to the pattern: Assignments are combinatorial; trying each box size over the distinct counts is fast.
Spot it: Only the counts per value matter (a hash map). Every box has s or s + 1 balls for some s ≤ min count. A count c fits iff it can be split into ceil(c / (s + 1)) boxes of size s or s + 1. Try s from the largest possible down; the first that fits gives the fewest boxes.
Why not another pattern? Trying arbitrary box assignments is combinatorial.
Approach:
countsof each value;m = min(counts).- For
s = m … 1: boxes forc=ceil(c / (s + 1)); valid ifboxes · s ≤ cfor all counts. - Return the total boxes for the first valid
s.
from collections import Counter
class Solution:
def minGroupsForValidAssignment(self, balls: List[int]) -> int:
counts = list(Counter(balls).values())
for s in range(min(counts), 0, -1): # boxes hold s or s + 1 balls
total = 0
for c in counts:
boxes = -(-c // (s + 1)) # as few boxes as possible
if boxes * s > c:
break # c can't fill that many boxes with at least s each
total += boxes
else:
return total # every value fits: larger s means fewer boxes
return len(balls)
What the code is doing, line by line:
- With
bboxes of sizesors + 1, a countcfits iffb·s ≤ c ≤ b·(s + 1); the smallestbisceil(c / (s + 1)). - Larger box sizes never need more boxes, so the first valid
sfrom the top is optimal. s = 1always works, so the loop returns.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All distinct → n boxes.
- One value.
- Counts that can't share a size except 1.
Complexity: O(n · m) in the worst case, much less in practice (Σ over distinct counts).
Medium 3872. Longest Arithmetic Sequence After Changing At Most One Element Amazon
Problem: Change at most one element, then pick the longest arithmetic subarray.
nums = [9,7,5,10,1]5nums = [1,2,6,7]3Constraints: 4 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁵
Hint 1
Look at the array of consecutive differences.
Hint 2
Changing nums[i] only affects d[i − 1] and d[i]. What must they become to join the runs around them?
From brute force to the pattern: Trying each change with each value is O(n²) or worse; run lengths make it O(n).
Spot it: Work on the differences. Without a change, the answer is the longest run of equal differences + 1. A change either extends a run by one element at its border, or replaces an interior nums[i] so both neighbouring differences become c = (nums[i+1] − nums[i−1]) / 2, joining the equal-c runs on both sides.
Why not another pattern? Trying every change and value is O(n²) or worse.
Approach:
d[i] = nums[i+1] − nums[i];end_run[i]/start_run[i]= length of the equal-difference run ending / starting atd[i].- Extend:
min(n, longest run + 2)elements. - Bridge at interior
i(whennums[i+1] − nums[i−1]is even):3 + (end_run[i−2] if d[i−2] == c) + (start_run[i+1] if d[i+1] == c).
class Solution:
def longestArithmetic(self, nums: List[int]) -> int:
n = len(nums)
d = [nums[i + 1] - nums[i] for i in range(n - 1)]
m = len(d)
end_run = [1] * m # equal differences ending at d[i]
for i in range(1, m):
if d[i] == d[i - 1]:
end_run[i] = end_run[i - 1] + 1
start_run = [1] * m # equal differences starting at d[i]
for i in range(m - 2, -1, -1):
if d[i] == d[i + 1]:
start_run[i] = start_run[i + 1] + 1
best = min(n, max(end_run) + 2) # a run of k differences is k + 1 elements; change one neighbour to extend it
for i in range(1, n - 1): # change nums[i]
gap = nums[i + 1] - nums[i - 1]
if gap % 2:
continue # can't split an odd gap into two equal integer steps
c = gap // 2
length = 3 # nums[i-1], new nums[i], nums[i+1]
if i - 2 >= 0 and d[i - 2] == c:
length += end_run[i - 2] # equal steps continue on the left
if i + 1 < m and d[i + 1] == c:
length += start_run[i + 1] # and on the right
best = max(best, length)
return best
What the code is doing, line by line:
- Changing an element right next to a run makes one more difference match, adding one element (never beyond
n). - Changing an interior element affects exactly the two differences around it; their sum is fixed, so they can both equal
conly if the gap is even. - The runs on each side then join through the changed element.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Already arithmetic → n.
- Odd gaps (no bridge).
- Best change at an end.
Complexity: O(n) time and space.
Medium 3927. Minimize Array Sum Using Divisible Replacements Amazon
Problem: You may replace nums[a] by nums[b] when nums[b] divides nums[a]. Minimise the array sum.
nums = [3,6,2]7nums = [4,2,8,3]9Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁵
Hint 1
If b divides a and c divides b, what can a become?
Hint 2
For every value, find the smallest value in the array that divides it.
From brute force to the pattern: All pairs: O(n²); a sieve over values: O(M log M).
Spot it: Replacements chain (divisibility is transitive), so each value can drop to the smallest value in the array that divides it. Find it for all values at once with a sieve: walk the distinct values from small to large and claim their multiples (a hash set of present values).
Why not another pattern? Checking every pair is O(n²).
Approach:
present= set of values;best[v] = v.- For each distinct
dascending: for multiplesmofd≤ max: ifmis present and not yet claimed,best[m] = d. - Sum
best[x]over the array.
class Solution:
def minArraySum(self, nums: List[int]) -> int:
present = set(nums)
top = max(nums)
best = {} # value -> smallest present divisor
for d in sorted(present):
if d in best:
continue # its multiples are already claimed by a smaller divisor
for m in range(d, top + 1, d):
if m in present and m not in best:
best[m] = d
return sum(best[x] for x in nums)
What the code is doing, line by line:
- A value is replaced by any present divisor, and the smallest one gives the smallest sum.
- If
dwas claimed by a smaller divisore, every multiple ofdis a multiple ofe, so skippingdis safe. - The harmonic sum over divisors keeps the sieve near
O(M log M).
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No divisibility → unchanged.
- 1 present → everything becomes 1.
- Duplicates.
Complexity: O(M log M + n) time, O(M) space (M = max(nums)).
Hard 149. Max Points on a Line Amazon Cisco
Problem: Return the maximum number of points that lie on one straight line.
points = [[1,1],[2,2],[3,3]]3points = [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]4Constraints: 1 ≤ points.length ≤ 300 · points[i].length == 2 · -10⁴ ≤ xi, yi ≤ 10⁴ · All the points are unique.
Hint 1
Fix one point. How do you group the others by the line through it?
Hint 2
Represent slopes exactly as reduced fractions.
From brute force to the pattern: All triples: O(n³); slope counting per anchor: O(n²).
Spot it: Fix one point; every line through it is identified by its slope. Count points by slope in a hash map, with slopes stored as reduced integer pairs (gcd, fixed sign) to avoid floating-point errors.
Why not another pattern? Checking every triple of points is O(n³); float slopes can collide or differ wrongly.
Approach:
- For each
i: a counter of slopes to pointsj > i. - Slope key:
(dy / g, dx / g)withg = gcd, normalised sodx > 0(ordx = 0, dy = 1). - Best = 1 + the largest count.
from collections import Counter
from math import gcd
class Solution:
def maxPoints(self, points: List[List[int]]) -> int:
n = len(points)
best = 1
for i in range(n):
slopes = Counter()
x1, y1 = points[i]
for j in range(i + 1, n):
dx, dy = points[j][0] - x1, points[j][1] - y1
g = gcd(dx, dy)
dx, dy = dx // g, dy // g
if dx < 0 or (dx == 0 and dy < 0):
dx, dy = -dx, -dy # one canonical sign per direction
slopes[(dx, dy)] += 1
if slopes:
best = max(best, 1 + max(slopes.values())) # the anchor plus that line's points
return best
What the code is doing, line by line:
- Points are distinct, so
g > 0. - Reduced integer pairs make equal slopes compare equal exactly.
- Lines through earlier anchors were already counted when those anchors were processed.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- One point → 1.
- Vertical lines.
- Negative slopes.
Complexity: O(n² log C) time, O(n) space.
Hard 4007. Widest Possible Fence Amazon
Problem: Use single planks or pairs glued end to end; all fence planks must have the same height. Return the maximum width.
planks = [1,3,2,5,7,5,4,2,1]4planks = [2,3,7]1Constraints: 1 ≤ planks.length ≤ 1000 · 1 ≤ planks[i] ≤ 10⁹
Hint 1
Fix the target height H. Which planks can be used, and how?
Hint 2
Pairs (a, H − a) for different a never share planks.
From brute force to the pattern: Trying every H and pairing greedily from scratch is O(n³); per-sum accumulation is O(D²).
Spot it: For a target height H, use every plank of height H, plus as many disjoint pairs (a, H − a) as possible: min(cnt[a], cnt[H − a]) per pair of values. Different a never compete for the same planks, so accumulate pair contributions per sum in a hash map over all value pairs.
Why not another pattern? Trying every height and recounting pairs from scratch is O(n³).
Approach:
cntof heights;pairs[s]= planks usable as pairs summing tos.- For distinct values
a < b:pairs[a + b] += min(cnt[a], cnt[b]); for eacha:pairs[2a] += cnt[a] // 2. - Answer:
max(cnt[H] + pairs[H])over allH.
from collections import Counter
class Solution:
def maximumWidth(self, planks: List[int]) -> int:
cnt = Counter(planks)
vals = sorted(cnt)
pairs = Counter() # height -> number of glued pairs that reach it
for i, a in enumerate(vals):
pairs[2 * a] += cnt[a] // 2 # two planks of the same height
for b in vals[i + 1:]:
pairs[a + b] += min(cnt[a], cnt[b]) # each pair uses one a and one b
heights = set(cnt) | set(pairs)
return max(cnt[h] + pairs[h] for h in heights)
What the code is doing, line by line:
- A plank of height
Hcan't be in a pair summing toH(heights are positive), so singles and pairs never conflict. - For a fixed
H, each valueaonly pairs withH − a, so the pair counts for different values add up independently.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All planks equal.
- Only pairs reach the best height.
- One plank → 1.
Complexity: O(D²) time for D distinct heights (≤ 1000), O(D²) space.