Arrays And Hashing
Phase 1 22 solutionsArrays & Hashing
mindmap
root((Arrays & Hashing))
Hash Map Lookup
Two Sum LC1
Group Anagrams LC49
Valid Sudoku LC36
Longest Consecutive Sequence LC128
Subarray Sum Equals K LC560
Frequency Counting
Valid Anagram LC242
Top K Frequent Elements LC347
Majority Element LC169
Majority Element II LC229
Prefix Sum
Product of Array Except Self LC238
Range Sum Query 2D LC304
Subarray Sum Equals K LC560
Sorting
Sort an Array LC912
Sort Colors LC75
Longest Common Prefix LC14
Index Marking
First Missing Positive LC41
Design
Design HashSet LC705
Design HashMap LC706
Encoding
Encode and Decode Strings LC271
Overview
This section covers 22 problems focusing on array manipulation, hashing techniques, and frequency counting.
Problem List
| # | Problem | LC # | Difficulty | Key Techniques |
|---|---|---|---|---|
| 1 | Concatenation of Array | 1929 | Easy | Array |
| 2 | Contains Duplicate | 217 | Easy | Hash Set |
| 3 | Valid Anagram | 242 | Easy | Char Counting |
| 4 | Two Sum | 1 | Easy | Hash Map |
| 5 | Longest Common Prefix | 14 | Easy | String Scan |
| 6 | Group Anagrams | 49 | Medium | Hash Map + Sort |
| 7 | Remove Element | 27 | Easy | Two Pointers |
| 8 | Majority Element | 169 | Easy | Boyer-Moore |
| 9 | Design HashSet | 705 | Easy | Design |
| 10 | Design HashMap | 706 | Easy | Design |
| 11 | Sort an Array | 912 | Medium | Merge/Quick Sort |
| 12 | Sort Colors | 75 | Medium | Dutch National Flag |
| 13 | Top K Frequent Elements | 347 | Medium | Bucket Sort |
| 14 | Encode and Decode Strings | 271 | Medium | Length Prefix |
| 15 | Range Sum Query 2D | 304 | Medium | Prefix Sum 2D |
| 16 | Product of Array Except Self | 238 | Medium | Prefix/Suffix |
| 17 | Valid Sudoku | 36 | Medium | Hash Sets |
| 18 | Longest Consecutive Sequence | 128 | Medium | Hash Set |
| 19 | Best Time to Buy/Sell Stock II | 122 | Medium | Greedy |
| 20 | Majority Element II | 229 | Medium | Boyer-Moore Extended |
| 21 | Subarray Sum Equals K | 560 | Medium | Prefix Sum + Map |
| 22 | First Missing Positive | 41 | Hard | Index Marking |
Approach Flow Diagrams
Hash Map / Hash Set Pattern
flowchart TD
A[Input Array] --> B{Need to track seen elements?}
B -->|Yes| C[Use Hash Set]
B -->|No| D{Need key-value mapping?}
D -->|Yes| E[Use Hash Map]
D -->|No| F[Array manipulation]
C --> G[Contains Duplicate]
C --> H[Longest Consecutive Sequence]
C --> I[Valid Sudoku]
E --> J[Two Sum]
E --> K[Group Anagrams]
E --> L[Subarray Sum Equals K]
F --> M[Concatenation of Array]
F --> N[Remove Element]
Frequency Counting Pattern
flowchart TD
A[Input Data] --> B{Count frequencies}
B --> C["Hash Map / Counter"]
C --> D{What to find?}
D -->|Top K elements| E[Top K Frequent Elements]
D -->|Majority > n/2| F[Majority Element]
D -->|Majority > n/3| G[Majority Element II]
D -->|Anagram check| H[Valid Anagram]
E --> E1[Approach 1: Sort by freq O-n log n-]
E --> E2[Approach 2: Heap O-n log k-]
E --> E3[Approach 3: Bucket Sort O-n-]
F --> F1[Approach 1: Hash Map O-n-]
F --> F2[Approach 2: Sort O-n log n-]
F --> F3[Approach 3: Boyer-Moore O-n- O-1-]
Prefix Sum Pattern
flowchart TD
A["Array/Matrix"] --> B{Repeated range queries?}
B -->|1D Array| C[Prefix Sum Array]
B -->|2D Matrix| D[2D Prefix Sum]
B -->|Product queries| E[Prefix & Suffix Products]
C --> C1[Subarray Sum Equals K]
C1 --> C2[prefix_sum + hash_map = O-n-]
D --> D1[Range Sum Query 2D]
D1 --> D2[Precompute: O-mn-, Query: O-1-]
E --> E1[Product of Array Except Self]
E1 --> E2[Left pass × Right pass = O-n- O-1- space]
Sorting-Based Pattern
flowchart TD
A[Unsorted Array] --> B{Which sort?}
B -->|Divide & Conquer| C[Merge Sort O-n log n-]
B -->|Partition| D[Quick Sort O-n log n- avg]
B -->|Limited range 0,1,2| E[Dutch National Flag O-n-]
B -->|Count occurrences| F["Counting/Bucket Sort O-n-"]
C --> C1[Sort an Array - stable]
D --> D1[Sort an Array - in-place]
E --> E1[Sort Colors - one pass]
F --> F1[Top K Frequent Elements]
E1 --> G[Three pointers: low, mid, high]
G --> G1["low: boundary of 0s"]
G --> G2["mid: current element"]
G --> G3["high: boundary of 2s"]
Design Pattern (HashSet / HashMap)
flowchart TD
A[Design Problem] --> B{Data Structure}
B -->|HashSet| C[MyHashSet]
B -->|HashMap| D[MyHashMap]
C --> C1[Approach 1: Boolean Array]
C --> C2[Approach 2: Chaining - Linked List]
D --> D1[Approach 1: Direct Array]
D --> D2[Approach 2: Chaining - Linked List]
C1 --> E["arr[key] = True/False"]
C2 --> F["hash(key) % size -> bucket -> linked list"]
D1 --> G["arr[key] = value"]
D2 --> H["hash(key) % size -> bucket -> (key,val) pairs"]
Index Marking / Cyclic Sort Pattern
flowchart TD
A["Array with values in [1, n]"] --> B{"Find missing/duplicate?"}
B -->|Missing positive| C[First Missing Positive]
C --> D[Approach 1: Hash Set O-n- space]
C --> E[Approach 2: Index Marking O-1- space]
E --> F["For each num, mark nums[num-1] as negative"]
F --> G["First positive index i → answer is i+1"]
style E fill:#90EE90
Encode/Decode Pattern
flowchart TD
A["List of Strings"] --> B{Encode to single string}
B --> C[Approach 1: Delimiter with Escaping]
B --> D[Approach 2: Length Prefix]
C --> C1["Join with special delimiter"]
C --> C2["Must handle delimiter in strings"]
D --> D1["For each str: len + '#' + str"]
D --> D2["'5#Hello5#World'"]
D2 --> E{Decode}
E --> F["Read length → read that many chars → repeat"]
style D fill:#90EE90
Greedy Pattern (Stock Problems)
flowchart TD
A[Price Array] --> B{Multiple transactions allowed?}
B -->|Yes - LC 122| C["Best Time to Buy/Sell Stock II"]
C --> D[Approach 1: Recursion O-2^n-]
C --> E[Approach 2: Greedy O-n-]
E --> F["Sum all positive differences"]
F --> G["if prices[i] > prices[i-1]: profit += diff"]
style E fill:#90EE90
Complexity Summary
| Problem | Time | Space | Pattern |
|---|---|---|---|
| concatenation_of_array | O(n) | O(n) | Array copy |
| contains_duplicate | O(n) | O(n) | Hash set |
| valid_anagram | O(n) | O(1) | Char counting |
| two_sum | O(n) | O(n) | Hash map |
| longest_common_prefix | O(n·k) | O(1) | String scan |
| group_anagrams | O(n·k log k) | O(n·k) | Hash map + sort |
| remove_element | O(n) | O(1) | Two pointers |
| majority_element | O(n) | O(1) | Boyer-Moore |
| design_hashset | O(1) avg | O(n) | Chaining |
| design_hashmap | O(1) avg | O(n) | Chaining |
| sort_an_array | O(n log n) | O(n) | Merge/Quick sort |
| sort_colors | O(n) | O(1) | Dutch national flag |
| top_k_frequent_elements | O(n) | O(n) | Bucket sort |
| encode_and_decode_strings | O(n) | O(n) | Length prefix |
| range_sum_query_2d | O(mn) pre / O(1) query | O(mn) | 2D prefix sum |
| product_of_array_except_self | O(n) | O(1) | Prefix/suffix products |
| valid_sudoku | O(1) | O(1) | Hash sets (fixed 9×9) |
| longest_consecutive_sequence | O(n) | O(n) | Hash set |
| best_time_to_buy_sell_stock_ii | O(n) | O(1) | Greedy |
| majority_element_ii | O(n) | O(1) | Boyer-Moore extended |
| subarray_sum_equals_k | O(n) | O(n) | Prefix sum + map |
| first_missing_positive | O(n) | O(1) | Index marking |
Study Order (Recommended)
flowchart LR
A[Easy] --> B[Medium] --> C[Hard]
A --> A1[Concatenation]
A --> A2[Contains Duplicate]
A --> A3[Valid Anagram]
A --> A4[Two Sum]
A --> A5[Remove Element]
A --> A6[Majority Element]
B --> B1[Group Anagrams]
B --> B2[Top K Frequent]
B --> B3[Product Except Self]
B --> B4[Longest Consecutive]
B --> B5[Subarray Sum K]
B --> B6[Sort Colors]
C --> C1[First Missing Positive]
Arrays & Hashing — Pattern Notes
Core Intuition
Arrays + hashing problems are about trading space for time. A hash map/set turns O(n) lookups into O(1). The core patterns:
- Frequency map: Count occurrences, then reason about counts.
- Complement lookup: Store what you've seen; check if current element completes a pair/group.
- Prefix sum: Precompute cumulative sums for O(1) range queries.
- Index marking: Use the array itself as a hash map (mark visited by negating or swapping).
- Bucketing: Group elements by a computed key (e.g., sorted tuple for anagrams).
Decision Flowchart
flowchart TD
A["Array/Hashing Problem"] --> B{"Need O(1) range sum?"}
B -- Yes --> C["Prefix Sum / 2D Prefix Sum"]
B -- No --> D{Grouping by equivalence?}
D -- Yes --> E["HashMap with canonical key<br/>e.g. sorted string, char count"]
D -- No --> F{"Finding pair/complement?"}
F -- Yes --> G["HashMap: store seen values<br/>check complement"]
F -- No --> H{"Frequency / count based?"}
H -- Yes --> I[Frequency array or Counter]
H -- No --> J{"In-place, O(1) space?"}
J -- Yes --> K["Index marking / cyclic sort"]
J -- No --> L["HashSet for O(1) lookup"]
Per-Problem Notes
1. Concatenation of Array — LC 1929 | Easy
- Key insight: Return
nums + nums. That's it. - Tricky: Nothing — warm-up problem.
- Common mistakes: Overcomplicating it.
- Time: O(n) | Space: O(n)
2. Contains Duplicate — LC 217 | Easy
- Key insight: HashSet — if element already in set, return True.
- Tricky: Nothing. Alternative: sort and check adjacent.
- Common mistakes: Using O(n²) nested loop.
- Time: O(n) | Space: O(n)
3. Valid Anagram — LC 242 | Easy
- Key insight: Count character frequencies. Two strings are anagrams if their frequency maps are equal.
- Tricky: Unicode follow-up — use a dict instead of fixed-size array.
- Common mistakes: Sorting both strings (O(n log n)) when O(n) is possible.
- Time: O(n) | Space: O(1) (26 chars)
4. Two Sum — LC 1 | Easy
- Key insight: Store
{value: index}as you iterate. For each num, check iftarget - numis in map. - Tricky: Check complement before inserting current element (handles same-element pairs correctly).
- Common mistakes: Inserting before checking — can use same element twice.
- Time: O(n) | Space: O(n)
5. Longest Common Prefix — LC 14 | Easy
- Key insight: Compare character by character across all strings. Stop at first mismatch or shortest string end.
- Tricky: Sort strings and only compare first and last — they'll have the smallest common prefix.
- Common mistakes: Not handling empty array or empty string inputs.
- Time: O(n·m) | Space: O(1)
6. Group Anagrams — LC 49 | Medium
- Key insight: Key = sorted string (or tuple of 26 char counts). Group words by key in a defaultdict.
- Tricky: Char count tuple as key avoids O(k log k) sort → O(k) per word.
- Common mistakes: Using sorted string key (fine for interviews, but char count is faster).
- Time: O(n·k) | Space: O(n·k)
7. Remove Element — LC 27 | Easy
- Key insight: Two-pointer in-place. Write pointer
ktracks valid elements. Skip elements equal to val. - Tricky: Order doesn't matter — can swap with last element for O(1) deletion.
- Common mistakes: Shifting elements (O(n²)). Forgetting to return k.
- Time: O(n) | Space: O(1)
8. Majority Element — LC 169 | Easy
- Key insight: Boyer-Moore Voting. Candidate + count. Increment if same, decrement if different. Reset at 0.
- Tricky: Works because majority element appears > n/2 times — it always survives.
- Common mistakes: Using sort (O(n log n)) or hashmap (O(n) space) when O(1) space is possible.
- Time: O(n) | Space: O(1)
9. Design HashSet — LC 705 | Easy
- Key insight: Use array of linked lists (chaining) or open addressing. Or just use a boolean array for small key range.
- Tricky: Handle collisions properly if implementing real hashing.
- Common mistakes: Using Python's built-in set (defeats the purpose).
- Time: O(1) avg | Space: O(n)
10. Design HashMap — LC 706 | Easy
- Key insight: Same as HashSet but store (key, value) pairs. Use chaining for collision resolution.
- Tricky: Update existing key vs. insert new key.
- Common mistakes: Not handling key updates in chained list.
- Time: O(1) avg | Space: O(n)
11. Sort an Array — LC 912 | Medium
- Key insight: Implement merge sort or heap sort. Quick sort has O(n²) worst case.
- Tricky: Merge sort: careful with index bounds during merge. In-place merge is complex.
- Common mistakes: Using quick sort without randomization → O(n²) on sorted input.
- Time: O(n log n) | Space: O(n) merge sort
12. Sort Colors — LC 75 | Medium
- Key insight: Dutch National Flag (3-way partition). Three pointers: low, mid, high. One pass.
- Tricky: When swapping mid with high, don't increment mid (new element at mid is unprocessed).
- Common mistakes: Incrementing mid after swapping with high — skips unprocessed element.
- Time: O(n) | Space: O(1)
13. Top K Frequent Elements — LC 347 | Medium
- Key insight: Bucket sort by frequency. Bucket index = frequency, size = n+1. Collect from high to low.
- Tricky: Bucket sort is O(n) vs. heap's O(n log k). Both are valid.
- Common mistakes: Using full sort O(n log n). Heap approach: use
heapq.nlargest(k, count, key=count.get). - Time: O(n) bucket sort | Space: O(n)
14. Encode and Decode Strings — LC 271 | Medium
- Key insight: Encode as
len#stringfor each word. Decoder reads length, skips '#', reads exactly that many chars. - Tricky: Can't use a delimiter like ',' because strings may contain it. Length-prefix is delimiter-free.
- Common mistakes: Using a simple delimiter that appears in strings.
- Time: O(n) | Space: O(n)
15. Range Sum Query 2D — LC 304 | Medium
- Key insight: 2D prefix sum.
prefix[r][c]= sum of rectangle from (0,0) to (r,c). Query uses inclusion-exclusion. - Tricky: Formula:
prefix[r2+1][c2+1] - prefix[r1][c2+1] - prefix[r2+1][c1] + prefix[r1][c1]. - Common mistakes: Off-by-one in prefix array indexing. Forgetting the +1 offset for 1-indexed prefix array.
- Time: O(n·m) build, O(1) query | Space: O(n·m)
16. Product of Array Except Self — LC 238 | Medium
- Key insight: Two passes: left products then right products. No division needed.
- Tricky: Do it in O(1) extra space by using output array for left pass, then multiply right pass in-place.
- Common mistakes: Using division (fails with zeros). Using O(n) extra space for both passes.
- Time: O(n) | Space: O(1) extra
17. Valid Sudoku — LC 36 | Medium
- Key insight: Track seen values per row, column, and 3×3 box. Box index =
(r//3)*3 + c//3. - Tricky: Use sets of sets, or encode as
(val, row),(val, col),(val, box)in one set. - Common mistakes: Wrong box index formula. Not skipping '.' cells.
- Time: O(81) = O(1) | Space: O(81) = O(1)
18. Longest Consecutive Sequence — LC 128 | Medium
- Key insight: HashSet of all numbers. For each number that is a sequence start (num-1 not in set), count streak.
- Tricky: Only start counting from sequence beginnings to avoid O(n²).
- Common mistakes: Not checking
num-1 not in set→ O(n²) for each element. - Time: O(n) | Space: O(n)
19. Best Time to Buy/Sell Stock II — LC 122 | Medium
- Key insight: Greedy — sum all positive differences between consecutive days. Capture every upward move.
- Tricky: You can hold at most one stock, but this greedy is equivalent to buying/selling every day it goes up.
- Common mistakes: Trying DP when greedy is O(n) and simpler.
- Time: O(n) | Space: O(1)
20. Majority Element II — LC 229 | Medium
- Key insight: Boyer-Moore for 2 candidates (at most 2 elements can appear > n/3 times). Two-pass: find candidates, then verify counts.
- Tricky: Must verify candidates in second pass — not all candidates are majority elements.
- Common mistakes: Not verifying candidates. Forgetting there can be 0, 1, or 2 results.
- Time: O(n) | Space: O(1)
21. Subarray Sum Equals K — LC 560 | Medium
- Key insight: Prefix sum + hashmap.
count[prefix_sum]tracks how many times each prefix sum occurred. For each index, checkprefix - kin map. - Tricky: Initialize
count[0] = 1for subarrays starting at index 0. - Common mistakes: Forgetting
count[0] = 1. Sliding window doesn't work here (negative numbers). - Time: O(n) | Space: O(n)
22. First Missing Positive — LC 41 | Hard
- Key insight: Index marking. Place each number x in index x-1 (cyclic sort). Then scan for first index where nums[i] != i+1.
- Tricky: Answer is always in range [1, n+1]. Ignore numbers outside [1, n].
- Common mistakes: Using extra space (O(n) set) when O(1) is required. Not handling duplicates in cyclic sort.
- Time: O(n) | Space: O(1)
Edge Cases to Watch For
- Empty array: Return 0, [], or handle before main logic.
- Single element: Many problems have trivial single-element answers.
- Negative numbers: Prefix sum, subarray problems — sliding window won't work with negatives.
- Duplicates: Two Sum (same element twice), Group Anagrams, Consecutive Sequence.
- Integer overflow: Product of array, sum problems — use Python (no overflow) or long in Java.
- All same elements: Majority element, sort colors, consecutive sequence.
- Zeros in array: Product except self (can't divide by zero), subarray sum.
- Unicode strings: Valid anagram follow-up — use dict not fixed array.
| # | Problem | LC # | Difficulty | Key Techniques |
|---|---|---|---|---|
| 1 | Concatenation of Array | 1929 | Easy | Array |
| 2 | Contains Duplicate | 217 | Easy | Hash Set |
| 3 | Valid Anagram | 242 | Easy | Char Counting |
| 4 | Two Sum | 1 | Easy | Hash Map |
| 5 | Longest Common Prefix | 14 | Easy | String Scan |
| 6 | Group Anagrams | 49 | Medium | Hash Map + Sort |
| 7 | Remove Element | 27 | Easy | Two Pointers |
| 8 | Majority Element | 169 | Easy | Boyer-Moore |
| 9 | Design HashSet | 705 | Easy | Design |
| 10 | Design HashMap | 706 | Easy | Design |
| 11 | Sort an Array | 912 | Medium | Merge/Quick Sort |
| 12 | Sort Colors | 75 | Medium | Dutch National Flag |
| 13 | Top K Frequent Elements | 347 | Medium | Bucket Sort |
| 14 | Encode and Decode Strings | 271 | Medium | Length Prefix |
| 15 | Range Sum Query 2D | 304 | Medium | Prefix Sum 2D |
| 16 | Product of Array Except Self | 238 | Medium | Prefix/Suffix |
| 17 | Valid Sudoku | 36 | Medium | Hash Sets |
| 18 | Longest Consecutive Sequence | 128 | Medium | Hash Set |
| 19 | Best Time to Buy/Sell Stock II | 122 | Medium | Greedy |
| 20 | Majority Element II | 229 | Medium | Boyer-Moore Extended |
| 21 | Subarray Sum Equals K | 560 | Medium | Prefix Sum + Map |
| 22 | First Missing Positive | 41 | Hard | Index Marking |
Best Time To Buy And Sell Stock Ii ▼ expand
"""
# LC 122: Best Time to Buy and Sell Stock II
You are given an integer array `prices` where `prices[i]` is the price of a
given stock on the i-th day. On each day, you may decide to buy and/or sell the
stock. You can only hold at most one share of the stock at any time. However,
you can buy it then immediately sell it on the same day.
Find and return the maximum profit you can achieve.
## Examples
```text
Input: prices = [7,1,5,3,6,4]
Output: 7
Explanation: Buy on day 2 (price=1), sell on day 3 (price=5), profit=4.
Buy on day 4 (price=3), sell on day 5 (price=6), profit=3.
Total profit = 7.
Input: prices = [1,2,3,4,5]
Output: 4
Input: prices = [7,6,4,3,1]
Output: 0
```
## Constraints
- 1 <= prices.length <= 3 * 10^4
- 0 <= prices[i] <= 10^4
"""
from typing import List
class Solution:
def maxProfit_brute(self, prices: List[int]) -> int:
"""Recursion — O(2^n) time, O(n) stack space."""
# Recursive DFS: at each day decide to buy, sell, or skip
# Exponential without memoization — illustrates the decision tree
def dfs(i: int, holding: bool) -> int:
if i == len(prices):
return 0
# skip
best = dfs(i + 1, holding)
if holding:
best = max(best, prices[i] + dfs(i + 1, False))
else:
best = max(best, -prices[i] + dfs(i + 1, True))
return best
return dfs(0, False)
def maxProfit(self, prices: List[int]) -> int:
"""Greedy — O(n) time, O(1) space."""
# Greedy: capture every upward price movement as profit
# Equivalent to buying and selling on every consecutive rising day
return sum(max(prices[i] - prices[i - 1], 0) for i in range(1, len(prices)))
if __name__ == "__main__":
s = Solution()
assert s.maxProfit([7, 1, 5, 3, 6, 4]) == 7
assert s.maxProfit([1, 2, 3, 4, 5]) == 4
assert s.maxProfit([7, 6, 4, 3, 1]) == 0
assert s.maxProfit([1]) == 0
print("All tests passed.") Concatenation Of Array ▼ expand
"""
# LC 1929: Concatenation of Array
Given an integer array `nums` of length `n`, return an array `ans` of length `2n`
where `ans[i] == nums[i]` and `ans[i + n] == nums[i]` for all `0 <= i < n`.
## Examples
```text
Input: nums = [1,2,1]
Output: [1,2,1,1,2,1]
Input: nums = [1,3,2,1]
Output: [1,3,2,1,1,3,2,1]
```
## Constraints
- n == nums.length
- 1 <= n <= 1000
- 1 <= nums[i] <= 1000
"""
from typing import List
class Solution:
def getConcatenation_brute(self, nums: List[int]) -> List[int]:
# Append all elements twice using a loop
# O(n) time | O(n) space
ans = []
for _ in range(2):
for n in nums:
ans.append(n)
return ans
def getConcatenation(self, nums: List[int]) -> List[int]:
# Python list concatenation creates a new list with both copies
# O(n) time | O(n) space
return nums + nums
if __name__ == "__main__":
s = Solution()
assert s.getConcatenation([1, 2, 1]) == [1, 2, 1, 1, 2, 1]
assert s.getConcatenation([1, 3, 2, 1]) == [1, 3, 2, 1, 1, 3, 2, 1]
print("All tests passed.") Contains Duplicate ▼ expand
"""
# LC 217: Contains Duplicate
Given an integer array `nums`, return `true` if any value appears **at least twice**,
and return `false` if every element is distinct.
## Examples
```text
Input: nums = [1,2,3,1]
Output: true
Input: nums = [1,2,3,4]
Output: false
Input: nums = [1,1,1,3,3,4,3,2,4,2]
Output: true
```
## Constraints
- 1 <= nums.length <= 10^5
- -10^9 <= nums[i] <= 10^9
"""
from typing import List
class Solution:
def containsDuplicate_brute(self, nums: List[int]) -> bool:
# Compare every pair — O(n^2); fine for small inputs
# O(n^2) time | O(1) space
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] == nums[j]:
return True
return False
def containsDuplicate_sort(self, nums: List[int]) -> bool:
# After sorting, duplicates are adjacent — one linear scan suffices
# O(n log n) time | O(1) space
nums.sort()
for i in range(1, len(nums)):
if nums[i] == nums[i - 1]:
return True
return False
def containsDuplicate(self, nums: List[int]) -> bool:
# A set collapses duplicates; if lengths differ, a duplicate existed
# O(n) time | O(n) space
return len(nums) != len(set(nums))
if __name__ == "__main__":
s = Solution()
assert s.containsDuplicate([1, 2, 3, 1]) == True
assert s.containsDuplicate([1, 2, 3, 4]) == False
assert s.containsDuplicate([1, 1, 1, 3, 3, 4, 3, 2, 4, 2]) == True
print("All tests passed.") Design Hashmap ▼ expand
"""
# LC 706: Design HashMap
Design a HashMap without using any built-in hash table libraries.
Implement `MyHashMap`:
- `put(key, value)` — Inserts a `(key, value)` pair. Updates value if key exists.
- `get(key)` — Returns the value mapped to `key`, or `-1` if not present.
- `remove(key)` — Removes the `key` and its value if present.
## Examples
```text
Input:
["MyHashMap","put","put","get","get","put","get","remove","get"]
[[],[1,1],[2,2],[1],[3],[2,1],[2],[2],[2]]
Output:
[null,null,null,1,-1,null,1,null,-1]
```
## Constraints
- 0 <= key, value <= 10^6
- At most 10^4 calls will be made to put, get, and remove.
"""
# Approach 1: Array-based — O(1) time | O(10^6) space
class MyHashMap:
def __init__(self):
self.data = [-1] * (10**6 + 1)
def put(self, key: int, value: int) -> None:
self.data[key] = value
def get(self, key: int) -> int:
return self.data[key]
def remove(self, key: int) -> None:
self.data[key] = -1
# Approach 2: Chaining with lists of (key, value) pairs — O(1) avg time | O(n) space
class MyHashMapChaining:
def __init__(self):
self.size = 1000
self.buckets = [[] for _ in range(self.size)]
def _hash(self, key: int) -> int:
return key % self.size
def put(self, key: int, value: int) -> None:
h = self._hash(key)
for i, (k, v) in enumerate(self.buckets[h]):
if k == key:
self.buckets[h][i] = (key, value)
return
self.buckets[h].append((key, value))
def get(self, key: int) -> int:
for k, v in self.buckets[self._hash(key)]:
if k == key:
return v
return -1
def remove(self, key: int) -> None:
h = self._hash(key)
self.buckets[h] = [(k, v) for k, v in self.buckets[h] if k != key]
if __name__ == "__main__":
hm = MyHashMap()
hm.put(1, 1)
hm.put(2, 2)
assert hm.get(1) == 1
assert hm.get(3) == -1
hm.put(2, 1)
assert hm.get(2) == 1
hm.remove(2)
assert hm.get(2) == -1
print("All tests passed.") Design Hashset ▼ expand
"""
# LC 705: Design HashSet
Design a HashSet without using any built-in hash table libraries.
Implement `MyHashSet`:
- `add(key)` — Inserts the value `key` into the HashSet.
- `remove(key)` — Removes the value `key` in the HashSet. If `key` does not exist, do nothing.
- `contains(key)` — Returns whether the value `key` exists in the HashSet or not.
## Examples
```text
Input:
["MyHashSet","add","add","contains","contains","add","contains","remove","contains"]
[[],[1],[2],[1],[3],[2],[2],[2],[2]]
Output:
[null,null,null,true,false,null,true,null,false]
```
## Constraints
- 0 <= key <= 10^6
- At most 10^4 calls will be made to add, remove, and contains.
"""
# Approach 1: Boolean array — O(1) time | O(10^6) space
class MyHashSet:
def __init__(self):
self.data = [False] * (10**6 + 1)
def add(self, key: int) -> None:
self.data[key] = True
def remove(self, key: int) -> None:
self.data[key] = False
def contains(self, key: int) -> bool:
return self.data[key]
# Approach 2: Chaining with lists — O(1) avg time | O(n) space
class MyHashSetChaining:
def __init__(self):
self.size = 1000
self.buckets = [[] for _ in range(self.size)]
def _hash(self, key: int) -> int:
return key % self.size
def add(self, key: int) -> None:
h = self._hash(key)
if key not in self.buckets[h]:
self.buckets[h].append(key)
def remove(self, key: int) -> None:
h = self._hash(key)
if key in self.buckets[h]:
self.buckets[h].remove(key)
def contains(self, key: int) -> bool:
return key in self.buckets[self._hash(key)]
if __name__ == "__main__":
hs = MyHashSet()
hs.add(1)
hs.add(2)
assert hs.contains(1) == True
assert hs.contains(3) == False
hs.add(2)
assert hs.contains(2) == True
hs.remove(2)
assert hs.contains(2) == False
print("All tests passed.") Encode And Decode Strings ▼ expand
"""
# LC 271: Encode and Decode Strings
Design an algorithm to encode a list of strings to a single string. The encoded
string is then sent over the network and is decoded back to the original list of
strings.
## Examples
```text
Input: strs = ["Hello","World"]
Encoded: "5#Hello5#World"
Output: ["Hello","World"]
Input: strs = [""]
Output: [""]
```
## Constraints
- 0 <= strs.length <= 200
- 0 <= strs[i].length <= 200
- strs[i] contains any possible characters out of 256 valid ASCII characters
"""
from typing import List
class Codec:
def encode(self, strs: List[str]) -> str:
"""Delimiter with escaping — O(n) time."""
# Prefix each string with its length and '#' as delimiter
# This handles strings containing any character including '#'
return "".join(f"{len(s)}#{s}" for s in strs)
def decode(self, s: str) -> List[str]:
"""Length-prefix decoding — O(n) time."""
# Read length prefix up to '#', then extract exactly that many chars
# Advance i past the extracted string to process the next one
result, i = [], 0
while i < len(s):
j = s.index("#", i)
length = int(s[i:j])
result.append(s[j + 1: j + 1 + length])
i = j + 1 + length
return result
if __name__ == "__main__":
codec = Codec()
for strs in [
["Hello", "World"],
[""],
[],
["a#b", "c#d"],
["with spaces", "and\nnewlines"],
]:
assert codec.decode(codec.encode(strs)) == strs, strs
print("All tests passed.") First Missing Positive ▼ expand
"""
# LC 41: First Missing Positive
Given an unsorted integer array `nums`, return the smallest missing positive integer.
You must implement an algorithm that runs in O(n) time and uses O(1) auxiliary space.
## Examples
```text
Input: nums = [1,2,0]
Output: 3
Input: nums = [3,4,-1,1]
Output: 2
Input: nums = [7,8,9,11,12]
Output: 1
```
## Constraints
- 1 <= nums.length <= 10^5
- -2^31 <= nums[i] <= 2^31 - 1
"""
from typing import List
class Solution:
def firstMissingPositive_hashset(self, nums: List[int]) -> int:
"""Hash set — O(n) time, O(n) space."""
# Store all numbers in a set, then probe 1, 2, 3... for the first gap
s = set(nums)
i = 1
while i in s:
i += 1
return i
def firstMissingPositive(self, nums: List[int]) -> int:
"""Index marking (cyclic sort idea) — O(n) time, O(1) space."""
# Cyclic sort: place each number at index nums[i]-1 if in range [1,n]
# After rearranging, the first index where nums[i] != i+1 is the answer
# If all positions are correct, the answer is n+1
n = len(nums)
# Place each number in its correct index position (nums[i] == i+1)
for i in range(n):
while 1 <= nums[i] <= n and nums[nums[i] - 1] != nums[i]:
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]
for i in range(n):
if nums[i] != i + 1:
return i + 1
return n + 1
if __name__ == "__main__":
s = Solution()
assert s.firstMissingPositive([1, 2, 0]) == 3
assert s.firstMissingPositive([3, 4, -1, 1]) == 2
assert s.firstMissingPositive([7, 8, 9, 11, 12]) == 1
assert s.firstMissingPositive([1]) == 2
assert s.firstMissingPositive([1, 2, 3]) == 4
print("All tests passed.") Group Anagrams ▼ expand
"""
# LC 49: Group Anagrams
Given an array of strings `strs`, group the anagrams together. You can return
the answer in any order.
## Examples
```text
Input: strs = ["eat","tea","tan","ate","nat","bat"]
Output: [["bat"],["nat","tan"],["ate","eat","tea"]]
Input: strs = [""]
Output: [[""]]
Input: strs = ["a"]
Output: [["a"]]
```
## Constraints
- 1 <= strs.length <= 10^4
- 0 <= strs[i].length <= 100
- strs[i] consists of lowercase English letters.
"""
from typing import List
from collections import defaultdict
class Solution:
def groupAnagrams_sort(self, strs: List[str]) -> List[List[str]]:
# Sorted string is a canonical key — all anagrams share the same key
# O(n * k log k) time | O(n * k) space
groups = defaultdict(list)
for s in strs:
groups[tuple(sorted(s))].append(s)
return list(groups.values())
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
# Character-frequency tuple is a canonical key without sorting cost
# Build a 26-element count array per word, then use it as dict key
# O(n * k) time | O(n * k) space
groups = defaultdict(list)
for s in strs:
count = [0] * 26
for c in s:
count[ord(c) - ord('a')] += 1
groups[tuple(count)].append(s)
return list(groups.values())
if __name__ == "__main__":
s = Solution()
result = s.groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"])
assert sorted(sorted(g) for g in result) == [["ate", "eat", "tea"], ["bat"], ["nat", "tan"]]
assert s.groupAnagrams([""]) == [[""]]
assert s.groupAnagrams(["a"]) == [["a"]]
print("All tests passed.") Longest Common Prefix ▼ expand
"""
# LC 14: Longest Common Prefix
Write a function to find the longest common prefix string amongst an array of strings.
If there is no common prefix, return an empty string `""`.
## Examples
```text
Input: strs = ["flower","flow","flight"]
Output: "fl"
Input: strs = ["dog","racecar","car"]
Output: ""
```
## Constraints
- 1 <= strs.length <= 200
- 0 <= strs[i].length <= 200
- strs[i] consists of only lowercase English letters.
"""
from typing import List
class Solution:
def longestCommonPrefix_vertical(self, strs: List[str]) -> str:
# Check each character position across all strings simultaneously
# Stop as soon as any string diverges or runs out of characters
# O(S) time where S = sum of all characters | O(1) space
if not strs:
return ""
for i, ch in enumerate(strs[0]):
for s in strs[1:]:
if i >= len(s) or s[i] != ch:
return strs[0][:i]
return strs[0]
def longestCommonPrefix(self, strs: List[str]) -> str:
# After sorting, only the first and last strings need to be compared
# The common prefix of extremes is the common prefix of all strings
# O(n log n * m) time | O(1) space
strs.sort()
first, last = strs[0], strs[-1]
i = 0
while i < len(first) and i < len(last) and first[i] == last[i]:
i += 1
return first[:i]
if __name__ == "__main__":
s = Solution()
assert s.longestCommonPrefix(["flower", "flow", "flight"]) == "fl"
assert s.longestCommonPrefix(["dog", "racecar", "car"]) == ""
assert s.longestCommonPrefix(["interview", "interact", "interface"]) == "inter"
print("All tests passed.") Longest Consecutive Sequence ▼ expand
"""
# LC 128: Longest Consecutive Sequence
Given an unsorted array of integers `nums`, return the length of the longest
consecutive elements sequence. You must write an algorithm that runs in O(n) time.
## Examples
```text
Input: nums = [100,4,200,1,3,2]
Output: 4
Explanation: [1,2,3,4]
Input: nums = [0,3,7,2,5,8,4,6,0,1]
Output: 9
```
## Constraints
- 0 <= nums.length <= 10^5
- -10^9 <= nums[i] <= 10^9
"""
from typing import List
class Solution:
def longestConsecutive_sort(self, nums: List[int]) -> int:
"""Sorting — O(n log n) time, O(1) extra space."""
# After deduplication and sorting, consecutive elements differ by 1
if not nums:
return 0
nums = sorted(set(nums))
best = cur = 1
for i in range(1, len(nums)):
if nums[i] == nums[i - 1] + 1:
cur += 1
best = max(best, cur)
else:
cur = 1
return best
def longestConsecutive(self, nums: List[int]) -> int:
"""Hash set — O(n) time, O(n) space."""
# Only start counting from the beginning of a sequence (n-1 not in set)
# This ensures each sequence is counted exactly once — O(n) total
num_set = set(nums)
best = 0
for n in num_set:
if n - 1 not in num_set:
cur = 1
while n + cur in num_set:
cur += 1
best = max(best, cur)
return best
if __name__ == "__main__":
s = Solution()
assert s.longestConsecutive([100, 4, 200, 1, 3, 2]) == 4
assert s.longestConsecutive([0, 3, 7, 2, 5, 8, 4, 6, 0, 1]) == 9
assert s.longestConsecutive([]) == 0
assert s.longestConsecutive([1]) == 1
print("All tests passed.") Majority Element ▼ expand
"""
# LC 169: Majority Element
Given an array `nums` of size `n`, return the majority element — the element that
appears more than `⌊n / 2⌋` times. The majority element always exists.
## Examples
```text
Input: nums = [3,2,3]
Output: 3
Input: nums = [2,2,1,1,1,2,2]
Output: 2
```
## Constraints
- n == nums.length
- 1 <= n <= 5 * 10^4
- -10^9 <= nums[i] <= 10^9
- The majority element always exists.
"""
from typing import List
from collections import Counter
class Solution:
def majorityElement_map(self, nums: List[int]) -> int:
# Count occurrences and return the element with the highest count
# O(n) time | O(n) space
count = Counter(nums)
return max(count, key=count.get)
def majorityElement_sort(self, nums: List[int]) -> int:
# The majority element always occupies the middle index after sorting
# O(n log n) time | O(1) space
nums.sort()
return nums[len(nums) // 2]
def majorityElement(self, nums: List[int]) -> int:
# Boyer-Moore: maintain a candidate and a net vote count
# When count hits 0, the current element becomes the new candidate
# Majority element's votes always outlast all opposition combined
# O(n) time | O(1) space — Boyer-Moore Voting
candidate, count = None, 0
for n in nums:
if count == 0:
candidate = n
count += 1 if n == candidate else -1
return candidate
if __name__ == "__main__":
s = Solution()
assert s.majorityElement([3, 2, 3]) == 3
assert s.majorityElement([2, 2, 1, 1, 1, 2, 2]) == 2
assert s.majorityElement([1]) == 1
print("All tests passed.") Majority Element Ii ▼ expand
"""
# LC 229: Majority Element II
Given an integer array of size `n`, find all elements that appear more than
⌊n/3⌋ times.
## Examples
```text
Input: nums = [3,2,3]
Output: [3]
Input: nums = [1]
Output: [1]
Input: nums = [1,2]
Output: [1,2]
```
## Constraints
- 1 <= nums.length <= 5 * 10^4
- -10^9 <= nums[i] <= 10^9
"""
from typing import List
from collections import Counter
class Solution:
def majorityElement_hashmap(self, nums: List[int]) -> List[int]:
"""Hash map counting — O(n) time, O(n) space."""
# Count all elements; return those appearing more than n//3 times
threshold = len(nums) // 3
return [num for num, cnt in Counter(nums).items() if cnt > threshold]
def majorityElement(self, nums: List[int]) -> List[int]:
"""Boyer-Moore extended (at most 2 candidates) — O(n) time, O(1) space."""
# At most 2 elements can appear more than n/3 times
# Maintain two candidates with vote counts; cancel out different pairs
# Final verification pass confirms candidates actually exceed threshold
cand1, cand2, cnt1, cnt2 = 0, 1, 0, 0
for n in nums:
if n == cand1:
cnt1 += 1
elif n == cand2:
cnt2 += 1
elif cnt1 == 0:
cand1, cnt1 = n, 1
elif cnt2 == 0:
cand2, cnt2 = n, 1
else:
cnt1 -= 1
cnt2 -= 1
threshold = len(nums) // 3
return [c for c in (cand1, cand2) if nums.count(c) > threshold]
if __name__ == "__main__":
s = Solution()
assert sorted(s.majorityElement([3, 2, 3])) == [3]
assert sorted(s.majorityElement([1])) == [1]
assert sorted(s.majorityElement([1, 2])) == [1, 2]
assert sorted(s.majorityElement([1, 1, 1, 3, 3, 2, 2, 2])) == [1, 2]
print("All tests passed.") Product Of Array Except Self ▼ expand
"""
# LC 238: Product of Array Except Self
Given an integer array `nums`, return an array `answer` such that `answer[i]`
is equal to the product of all the elements of `nums` except `nums[i]`.
The product of any prefix or suffix of `nums` is guaranteed to fit in a 32-bit
integer. You must write an algorithm that runs in O(n) time and without using
the division operation.
## Examples
```text
Input: nums = [1,2,3,4]
Output: [24,12,8,6]
Input: nums = [-1,1,0,-3,3]
Output: [0,0,9,0,0]
```
## Constraints
- 2 <= nums.length <= 10^5
- -30 <= nums[i] <= 30
- The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer
"""
from typing import List
class Solution:
def productExceptSelf_brute(self, nums: List[int]) -> List[int]:
"""Brute force — O(n^2) time, O(1) extra space."""
# For each position, multiply all other elements — O(n^2)
n = len(nums)
answer = []
for i in range(n):
p = 1
for j in range(n):
if j != i:
p *= nums[j]
answer.append(p)
return answer
def productExceptSelf_prefix_suffix(self, nums: List[int]) -> List[int]:
"""Prefix and suffix arrays — O(n) time, O(n) space."""
# prefix[i] = product of all elements to the LEFT of i
# suffix[i] = product of all elements to the RIGHT of i
# answer[i] = prefix[i] * suffix[i] — no division needed
n = len(nums)
prefix = [1] * n
suffix = [1] * n
for i in range(1, n):
prefix[i] = prefix[i - 1] * nums[i - 1]
for i in range(n - 2, -1, -1):
suffix[i] = suffix[i + 1] * nums[i + 1]
return [prefix[i] * suffix[i] for i in range(n)]
def productExceptSelf(self, nums: List[int]) -> List[int]:
"""Single output array — O(n) time, O(1) extra space."""
# First pass: fill answer[i] with running left-product up to i-1
# Second pass: multiply in running right-product from i+1 onward
# This avoids the O(n) extra arrays by reusing the output array
n = len(nums)
answer = [1] * n
prefix = 1
for i in range(n):
answer[i] = prefix
prefix *= nums[i]
suffix = 1
for i in range(n - 1, -1, -1):
answer[i] *= suffix
suffix *= nums[i]
return answer
if __name__ == "__main__":
s = Solution()
assert s.productExceptSelf([1, 2, 3, 4]) == [24, 12, 8, 6]
assert s.productExceptSelf([-1, 1, 0, -3, 3]) == [0, 0, 9, 0, 0]
assert s.productExceptSelf([1, 1]) == [1, 1]
print("All tests passed.") Range Sum Query 2d Immutable ▼ expand
"""
# LC 304: Range Sum Query 2D - Immutable
Given a 2D matrix `matrix`, handle multiple queries of the following type:
Calculate the sum of the elements of `matrix` inside the rectangle defined by
its upper left corner `(row1, col1)` and lower right corner `(row2, col2)`.
Implement the `NumMatrix` class with efficient repeated queries.
## Examples
```text
matrix = [
[3, 0, 1, 4, 2],
[5, 6, 3, 2, 1],
[1, 2, 0, 1, 5],
[4, 1, 0, 1, 7],
[1, 0, 3, 0, 5]
]
sumRegion(2, 1, 4, 3) -> 8
sumRegion(1, 1, 2, 2) -> 11
sumRegion(1, 2, 2, 4) -> 12
```
## Constraints
- m == matrix.length
- n == matrix[i].length
- 1 <= m, n <= 200
- -10^4 <= matrix[i][j] <= 10^4
- 0 <= row1 <= row2 < m
- 0 <= col1 <= col2 < n
- At most 10^4 calls will be made to sumRegion
"""
from typing import List
class NumMatrixBrute:
"""Brute force — O(m*n) per query, O(1) extra space."""
def __init__(self, matrix: List[List[int]]) -> None:
self.matrix = matrix
def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
return sum(
self.matrix[r][c]
for r in range(row1, row2 + 1)
for c in range(col1, col2 + 1)
)
class NumMatrix:
"""2D prefix sum — O(m*n) build, O(1) per query."""
def __init__(self, matrix: List[List[int]]) -> None:
m, n = len(matrix), len(matrix[0])
self.prefix = [[0] * (n + 1) for _ in range(m + 1)]
for r in range(1, m + 1):
for c in range(1, n + 1):
self.prefix[r][c] = (
matrix[r - 1][c - 1]
+ self.prefix[r - 1][c]
+ self.prefix[r][c - 1]
- self.prefix[r - 1][c - 1]
)
def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
p = self.prefix
return (
p[row2 + 1][col2 + 1]
- p[row1][col2 + 1]
- p[row2 + 1][col1]
+ p[row1][col1]
)
if __name__ == "__main__":
matrix = [
[3, 0, 1, 4, 2],
[5, 6, 3, 2, 1],
[1, 2, 0, 1, 5],
[4, 1, 0, 1, 7],
[1, 0, 3, 0, 5],
]
nm = NumMatrix(matrix)
assert nm.sumRegion(2, 1, 4, 3) == 8
assert nm.sumRegion(1, 1, 2, 2) == 11
assert nm.sumRegion(1, 2, 2, 4) == 12
print("All tests passed.") Remove Element ▼ expand
"""
# LC 27: Remove Element
Given an integer array `nums` and an integer `val`, remove all occurrences of `val`
in `nums` **in-place**. The order of the elements may be changed. Return the number
of elements in `nums` which are not equal to `val`.
## Examples
```text
Input: nums = [3,2,2,3], val = 3
Output: 2, nums = [2,2,_,_]
Input: nums = [0,1,2,2,3,0,4,2], val = 2
Output: 5, nums = [0,1,4,0,3,_,_,_]
```
## Constraints
- 0 <= nums.length <= 100
- 0 <= nums[i] <= 50
- 0 <= val <= 100
"""
from typing import List
class Solution:
def removeElement_brute(self, nums: List[int], val: int) -> int:
# Shift all elements left when val found — O(n^2) due to shifting
# O(n^2) time | O(1) space
i = 0
while i < len(nums):
if nums[i] == val:
for j in range(i, len(nums) - 1):
nums[j] = nums[j + 1]
nums.pop()
else:
i += 1
return len(nums)
def removeElement(self, nums: List[int], val: int) -> int:
# k tracks the write position for non-val elements
# Only advance k when we copy a valid element — O(n) single pass
# O(n) time | O(1) space
k = 0
for i in range(len(nums)):
if nums[i] != val:
nums[k] = nums[i]
k += 1
return k
if __name__ == "__main__":
s = Solution()
nums = [3, 2, 2, 3]
assert s.removeElement(nums, 3) == 2 and nums[:2] == [2, 2]
nums = [0, 1, 2, 2, 3, 0, 4, 2]
assert s.removeElement(nums, 2) == 5 and sorted(nums[:5]) == [0, 0, 1, 3, 4]
print("All tests passed.") Sort An Array ▼ expand
"""
# LC 912: Sort an Array
Given an array of integers `nums`, sort the array in ascending order and return it.
You must solve the problem **without using any built-in functions** in O(n log n)
time complexity and with the smallest space complexity possible.
## Examples
```text
Input: nums = [5,2,3,1]
Output: [1,2,3,5]
Input: nums = [5,1,1,2,0,0]
Output: [0,0,1,1,2,5]
```
## Constraints
- 1 <= nums.length <= 5 * 10^4
- -5 * 10^4 <= nums[i] <= 5 * 10^4
"""
from typing import List
import random
class Solution:
# Approach 1: Merge Sort — O(n log n) time | O(n) space
def sortArray_merge(self, nums: List[int]) -> List[int]:
# Divide array in half recursively until single elements, then merge
if len(nums) <= 1:
return nums
mid = len(nums) // 2
left = self.sortArray_merge(nums[:mid])
right = self.sortArray_merge(nums[mid:])
return self._merge(left, right)
def _merge(self, left, right):
# Merge two sorted halves by comparing front elements one at a time
result, i, j = [], 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i]); i += 1
else:
result.append(right[j]); j += 1
return result + left[i:] + right[j:]
# Approach 2: Quick Sort — O(n log n) avg | O(log n) space
def sortArray_quick(self, nums: List[int]) -> List[int]:
# Randomized pivot avoids worst-case O(n^2) on sorted input
self._quick(nums, 0, len(nums) - 1)
return nums
def _quick(self, nums, lo, hi):
# Partition around pivot, then recursively sort both halves
if lo >= hi:
return
pivot_idx = self._partition(nums, lo, hi)
self._quick(nums, lo, pivot_idx - 1)
self._quick(nums, pivot_idx + 1, hi)
def _partition(self, nums, lo, hi):
# Randomly swap pivot to end, then move all <= pivot to the left
rand = random.randint(lo, hi)
nums[rand], nums[hi] = nums[hi], nums[rand]
pivot, i = nums[hi], lo - 1
for j in range(lo, hi):
if nums[j] <= pivot:
i += 1
nums[i], nums[j] = nums[j], nums[i]
nums[i + 1], nums[hi] = nums[hi], nums[i + 1]
return i + 1
# Approach 3: Heap Sort — O(n log n) time | O(1) space
def sortArray(self, nums: List[int]) -> List[int]:
# Heap sort: build max-heap, then repeatedly extract max to end
n = len(nums)
for i in range(n // 2 - 1, -1, -1):
self._heapify(nums, n, i)
for i in range(n - 1, 0, -1):
nums[0], nums[i] = nums[i], nums[0]
self._heapify(nums, i, 0)
return nums
def _heapify(self, nums, n, i):
# Sift down: swap with largest child to maintain heap property
largest, l, r = i, 2 * i + 1, 2 * i + 2
if l < n and nums[l] > nums[largest]:
largest = l
if r < n and nums[r] > nums[largest]:
largest = r
if largest != i:
nums[i], nums[largest] = nums[largest], nums[i]
self._heapify(nums, n, largest)
if __name__ == "__main__":
s = Solution()
assert s.sortArray([5, 2, 3, 1]) == [1, 2, 3, 5]
assert s.sortArray([5, 1, 1, 2, 0, 0]) == [0, 0, 1, 1, 2, 5]
assert s.sortArray([-4, 0, 7, 4, 9, -5, -1, 0, -7, -1]) == sorted([-4, 0, 7, 4, 9, -5, -1, 0, -7, -1])
print("All tests passed.") Sort Colors ▼ expand
"""
# LC 75: Sort Colors
Given an array `nums` with `n` objects colored red, white, or blue, sort them
in-place so that objects of the same color are adjacent, with the colors in the
order red, white, and blue. Use integers 0, 1, and 2 to represent red, white,
and blue respectively.
You must solve this without using the library's sort function.
## Examples
```text
Input: nums = [2,0,2,1,1,0]
Output: [0,0,1,1,2,2]
Input: nums = [2,0,1]
Output: [0,1,2]
```
## Constraints
- n == nums.length
- 1 <= n <= 300
- nums[i] is either 0, 1, or 2
"""
from typing import List
class Solution:
def sortColors_counting(self, nums: List[int]) -> None:
"""Counting sort — O(n) time, O(1) space, two-pass."""
# Count 0s, 1s, 2s then overwrite array in order — two passes
count = [0, 0, 0]
for n in nums:
count[n] += 1
i = 0
for val, cnt in enumerate(count):
for _ in range(cnt):
nums[i] = val
i += 1
def sortColors(self, nums: List[int]) -> None:
"""Dutch National Flag — O(n) time, O(1) space, one-pass."""
# Dutch National Flag: three regions — [0..lo-1]=0, [lo..mid-1]=1, [hi+1..n-1]=2
# If mid is 0: swap with lo (both advance), if 1: advance mid, if 2: swap with hi
# hi shrinks but mid doesn't advance after swap with hi (unknown value)
lo, mid, hi = 0, 0, len(nums) - 1
while mid <= hi:
if nums[mid] == 0:
nums[lo], nums[mid] = nums[mid], nums[lo]
lo += 1
mid += 1
elif nums[mid] == 1:
mid += 1
else:
nums[mid], nums[hi] = nums[hi], nums[mid]
hi -= 1
if __name__ == "__main__":
s = Solution()
nums = [2, 0, 2, 1, 1, 0]
s.sortColors(nums)
assert nums == [0, 0, 1, 1, 2, 2], nums
nums = [2, 0, 1]
s.sortColors(nums)
assert nums == [0, 1, 2], nums
nums = [0]
s.sortColors(nums)
assert nums == [0], nums
print("All tests passed.") Subarray Sum Equals K ▼ expand
"""
# LC 560: Subarray Sum Equals K
Given an array of integers `nums` and an integer `k`, return the total number
of subarrays whose sum equals to `k`.
A subarray is a contiguous non-empty sequence of elements within an array.
## Examples
```text
Input: nums = [1,1,1], k = 2
Output: 2
Input: nums = [1,2,3], k = 3
Output: 2
```
## Constraints
- 1 <= nums.length <= 2 * 10^4
- -1000 <= nums[i] <= 1000
- -10^7 <= k <= 10^7
"""
from typing import List
from collections import defaultdict
class Solution:
def subarraySum_brute(self, nums: List[int], k: int) -> int:
"""Brute force — O(n^2) time, O(1) space."""
# Try all subarrays starting at i — O(n^2)
count = 0
for i in range(len(nums)):
total = 0
for j in range(i, len(nums)):
total += nums[j]
if total == k:
count += 1
return count
def subarraySum(self, nums: List[int], k: int) -> int:
"""Prefix sum with hash map — O(n) time, O(n) space."""
# prefix[j] - prefix[i] == k => prefix[i] == prefix[j] - k
# Store how many times each prefix sum has occurred
# freq[0]=1 handles subarrays starting from index 0
count = 0
prefix = 0
freq: dict = defaultdict(int)
freq[0] = 1
for n in nums:
prefix += n
count += freq[prefix - k]
freq[prefix] += 1
return count
if __name__ == "__main__":
s = Solution()
assert s.subarraySum([1, 1, 1], 2) == 2
assert s.subarraySum([1, 2, 3], 3) == 2
assert s.subarraySum([1], 0) == 0
assert s.subarraySum([-1, -1, 1], 0) == 1
print("All tests passed.") Top K Frequent Elements ▼ expand
"""
# LC 347: Top K Frequent Elements
Given an integer array `nums` and an integer `k`, return the `k` most frequent
elements. You may return the answer in any order.
## Examples
```text
Input: nums = [1,1,1,2,2,3], k = 2
Output: [1,2]
Input: nums = [1], k = 1
Output: [1]
```
## Constraints
- 1 <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
- k is in the range [1, the number of unique elements in the array]
- The answer is guaranteed to be unique
"""
from typing import List
from collections import Counter
import heapq
class Solution:
def topKFrequent_sort(self, nums: List[int], k: int) -> List[int]:
"""Sort by frequency — O(n log n) time, O(n) space."""
# Count frequencies, then sort by descending frequency and take top k
count = Counter(nums)
return sorted(count, key=lambda x: -count[x])[:k]
def topKFrequent_heap(self, nums: List[int], k: int) -> List[int]:
"""Min-heap of size k — O(n log k) time, O(n) space."""
# heapq.nlargest keeps a min-heap of size k — O(n log k)
count = Counter(nums)
return heapq.nlargest(k, count, key=count.get)
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
"""Bucket sort — O(n) time, O(n) space."""
# Bucket sort: index = frequency, so we can iterate buckets in reverse
# Collect elements from highest-frequency bucket down until we have k
count = Counter(nums)
buckets: List[List[int]] = [[] for _ in range(len(nums) + 1)]
for num, freq in count.items():
buckets[freq].append(num)
result: List[int] = []
for freq in range(len(buckets) - 1, 0, -1):
for num in buckets[freq]:
result.append(num)
if len(result) == k:
return result
return result
if __name__ == "__main__":
s = Solution()
assert sorted(s.topKFrequent([1, 1, 1, 2, 2, 3], 2)) == [1, 2]
assert s.topKFrequent([1], 1) == [1]
assert sorted(s.topKFrequent([1, 2], 2)) == [1, 2]
print("All tests passed.") Two Sum ▼ expand
"""
# LC 1: Two Sum
Given an array of integers `nums` and an integer `target`, return *indices* of the
two numbers such that they add up to `target`. You may assume exactly one solution
exists, and you may not use the same element twice.
## Examples
```text
Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Input: nums = [3,2,4], target = 6
Output: [1,2]
Input: nums = [3,3], target = 6
Output: [0,1]
```
## Constraints
- 2 <= nums.length <= 10^4
- -10^9 <= nums[i] <= 10^9
- -10^9 <= target <= 10^9
- Only one valid answer exists.
"""
from typing import List
class Solution:
def twoSum_brute(self, nums: List[int], target: int) -> List[int]:
# Try every pair (i, j) — O(n^2) because we check all combinations
# O(n^2) time | O(1) space
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
return []
def twoSum(self, nums: List[int], target: int) -> List[int]:
# Use a hash map to remember each number's index as we scan
# For each number, compute what complement is needed to reach target
# If complement already seen, we found our pair in O(1) lookup
# O(n) time | O(n) space
seen = {}
for i, n in enumerate(nums):
diff = target - n
if diff in seen:
return [seen[diff], i]
seen[n] = i
return []
if __name__ == "__main__":
s = Solution()
assert s.twoSum([2, 7, 11, 15], 9) == [0, 1]
assert s.twoSum([3, 2, 4], 6) == [1, 2]
assert s.twoSum([3, 3], 6) == [0, 1]
print("All tests passed.") Valid Anagram ▼ expand
"""
# LC 242: Valid Anagram
Given two strings `s` and `t`, return `true` if `t` is an anagram of `s`,
and `false` otherwise.
## Examples
```text
Input: s = "anagram", t = "nagaram"
Output: true
Input: s = "rat", t = "car"
Output: false
```
## Constraints
- 1 <= s.length, t.length <= 5 * 10^4
- s and t consist of lowercase English letters
"""
class Solution:
def isAnagram_sort(self, s: str, t: str) -> bool:
# Sorting both strings makes them identical iff they're anagrams
# O(n log n) time | O(1) space
return sorted(s) == sorted(t)
def isAnagram(self, s: str, t: str) -> bool:
# Early exit if lengths differ — anagrams must have same length
# Increment count for s's char, decrement for t's char simultaneously
# All zeros at the end means every character balanced out
# O(n) time | O(1) space — at most 26 keys
if len(s) != len(t):
return False
count = [0] * 26
for a, b in zip(s, t):
count[ord(a) - ord('a')] += 1
count[ord(b) - ord('a')] -= 1
return all(c == 0 for c in count)
if __name__ == "__main__":
s = Solution()
assert s.isAnagram("anagram", "nagaram") == True
assert s.isAnagram("rat", "car") == False
assert s.isAnagram("a", "ab") == False
print("All tests passed.") Valid Sudoku ▼ expand
"""
# LC 36: Valid Sudoku
Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be
validated according to the following rules:
1. Each row must contain the digits 1-9 without repetition.
2. Each column must contain the digits 1-9 without repetition.
3. Each of the nine 3 x 3 sub-boxes must contain the digits 1-9 without repetition.
Note: A Sudoku board (partially filled) could be valid but is not necessarily solvable.
## Examples
```text
Input: 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"]]
Output: true
Input: board with "8" appearing twice in the first row -> false
```
## Constraints
- board.length == 9
- board[i].length == 9
- board[i][j] is a digit 1-9 or '.'
"""
from typing import List
from collections import defaultdict
class Solution:
def isValidSudoku_brute(self, board: List[List[str]]) -> bool:
"""Check rows, cols, boxes separately — O(1) time/space."""
# Check each row, column, and 3x3 box for duplicates separately
for i in range(9):
row = [x for x in board[i] if x != "."]
col = [board[r][i] for r in range(9) if board[r][i] != "."]
if len(row) != len(set(row)) or len(col) != len(set(col)):
return False
for br in range(3):
for bc in range(3):
box = [
board[br * 3 + r][bc * 3 + c]
for r in range(3)
for c in range(3)
if board[br * 3 + r][bc * 3 + c] != "."
]
if len(box) != len(set(box)):
return False
return True
def isValidSudoku(self, board: List[List[str]]) -> bool:
"""Single pass with hash sets — O(1) time/space."""
# Single pass: track seen values per row, column, and box simultaneously
# Box index is (r//3, c//3) — maps each cell to its 3x3 region
# If a value already exists in any of the three sets, board is invalid
rows = defaultdict(set)
cols = defaultdict(set)
boxes = defaultdict(set)
for r in range(9):
for c in range(9):
val = board[r][c]
if val == ".":
continue
box = (r // 3, c // 3)
if val in rows[r] or val in cols[c] or val in boxes[box]:
return False
rows[r].add(val)
cols[c].add(val)
boxes[box].add(val)
return True
if __name__ == "__main__":
s = Solution()
valid_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"],
]
assert s.isValidSudoku(valid_board) is True
invalid_board = [row[:] for row in valid_board]
invalid_board[0][1] = "5" # duplicate 5 in row 0
assert s.isValidSudoku(invalid_board) is False
print("All tests passed.") Hash Map
Trade space for time with complement / prefix lookups
2
[0]
7
[1]
11
[2]
15
[3]
HashMap: seen = {
empty
}
Press "Next Step" to walk through Two Sum hash map lookup.