feat(content): initial question set

This commit is contained in:
2025-04-21 21:17:13 +01:00
parent dae4efcd08
commit d710cd34a7
3 changed files with 395 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
title: Merge Two Sorted Lists
slug: merge-two-sorted-lists
difficulty: easy
leetcode_id: 21
leetcode_url: https://leetcode.com/problems/merge-two-sorted-lists/
categories:
- linked-lists
- recursion
patterns:
- two-pointers
description: |
You are given the heads of two sorted linked lists `list1` and `list2`.
Merge the two lists into one sorted list. The list should be made by splicing together
the nodes of the first two lists.
Return the head of the merged linked list.
constraints: |
- The number of nodes in both lists is in the range [0, 50].
- -100 <= Node.val <= 100
- Both list1 and list2 are sorted in non-decreasing order.
examples:
- input: "list1 = [1,2,4], list2 = [1,3,4]"
output: "[1,1,2,3,4,4]"
explanation: "Merge by comparing heads and taking the smaller value each time."
- input: "list1 = [], list2 = []"
output: "[]"
explanation: "Both lists empty, result is empty."
- input: "list1 = [], list2 = [0]"
output: "[0]"
explanation: "One list empty, return the other."
explanation:
approach: |
1. Create a dummy node to simplify edge cases (avoids special handling for the head)
2. Use a current pointer starting at the dummy node
3. While both lists have nodes:
- Compare the values at the heads of both lists
- Attach the smaller node to current.next
- Advance the pointer of the list we took from
- Advance current to the newly attached node
4. Attach any remaining nodes from the non-empty list
5. Return dummy.next (the actual head of the merged list)
intuition: |
Since both lists are already sorted, we can build the merged list by repeatedly taking
the smaller of the two current heads. This is the merge step from merge sort.
The dummy node technique is a common pattern for linked list problems. It eliminates
the need for special logic to initialize the head of the result list.
Think of it like merging two sorted piles of cards — always take the smaller top card.
common_pitfalls:
- title: Forgetting to handle empty lists
description: |
One or both lists might be empty. The dummy node pattern handles this naturally,
but without it, you need explicit null checks.
wrong_approach: "Assuming both lists have at least one node"
correct_approach: "Use dummy node or check for null at the start"
- title: Not linking remaining nodes
description: |
After the main loop, one list might still have nodes. Don't iterate through
them — just link the entire remaining portion.
wrong_approach: "Looping through remaining nodes one by one"
correct_approach: "current.next = list1 or list2"
- title: Returning dummy instead of dummy.next
description: |
The dummy node is just a placeholder. The actual merged list starts at dummy.next.
wrong_approach: "return dummy"
correct_approach: "return dummy.next"
key_takeaways:
- Dummy nodes simplify linked list construction
- This is the merge step of merge sort
- Comparing and advancing pointers is a fundamental linked list technique
- Can also be solved recursively with elegant code
time_complexity: "O(n + m)"
space_complexity: "O(1)"
complexity_explanation: |
Time: We visit each node exactly once, where n and m are the lengths of the two lists.
Space: We only use a few pointers; we reuse existing nodes (no new nodes created).
solutions:
- approach_name: Iterative with Dummy Node (Optimal)
is_optimal: true
code: |
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def merge_two_lists(
list1: ListNode | None,
list2: ListNode | None,
) -> ListNode | None:
dummy = ListNode()
current = dummy
while list1 and list2:
if list1.val <= list2.val:
current.next = list1
list1 = list1.next
else:
current.next = list2
list2 = list2.next
current = current.next
# Attach remaining nodes
current.next = list1 if list1 else list2
return dummy.next
explanation: |
Use a dummy node to build the result. Compare heads and attach the smaller one.
Finally, attach any remaining nodes from the non-empty list.
- approach_name: Recursive
is_optimal: false
code: |
def merge_two_lists(
list1: ListNode | None,
list2: ListNode | None,
) -> ListNode | None:
if not list1:
return list2
if not list2:
return list1
if list1.val <= list2.val:
list1.next = merge_two_lists(list1.next, list2)
return list1
else:
list2.next = merge_two_lists(list1, list2.next)
return list2
explanation: |
Elegant recursive solution. Base case: return the non-null list.
Recursive case: attach smaller head and recurse on remaining lists.
Space is O(n+m) due to recursion stack.

View File

@@ -0,0 +1,117 @@
title: Two Sum
slug: two-sum
difficulty: easy
leetcode_id: 1
leetcode_url: https://leetcode.com/problems/two-sum/
categories:
- arrays
- hash-tables
patterns:
- two-pointers
description: |
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 that each input would have exactly one solution, and you may not use the same element twice.
You can return the answer in any order.
constraints: |
- 2 <= nums.length <= 10^4
- -10^9 <= nums[i] <= 10^9
- -10^9 <= target <= 10^9
- Only one valid answer exists.
examples:
- input: "nums = [2,7,11,15], target = 9"
output: "[0,1]"
explanation: "Because nums[0] + nums[1] == 9, we return [0, 1]."
- input: "nums = [3,2,4], target = 6"
output: "[1,2]"
explanation: "Because nums[1] + nums[2] == 6, we return [1, 2]."
- input: "nums = [3,3], target = 6"
output: "[0,1]"
explanation: "Because nums[0] + nums[1] == 6, we return [0, 1]."
explanation:
approach: |
1. Create a hash map to store each number and its index as we iterate
2. For each number, calculate its complement (target - current number)
3. Check if the complement exists in the hash map
4. If found, return the current index and the complement's index
5. If not found, add the current number and its index to the hash map
6. Continue until a pair is found
intuition: |
The brute force approach would check every pair of numbers, resulting in O(n²) time.
Instead, we can use a hash map to achieve O(n) time by trading space for speed.
The key insight is that for each number x, we're looking for (target - x). Rather than
scanning the entire array each time, we store seen numbers in a hash map for O(1) lookup.
We build the hash map as we go, which elegantly handles the constraint of not using
the same element twice — when we check for a complement, it can only be a previously
seen element.
common_pitfalls:
- title: Using the same element twice
description: |
Checking if complement exists before adding current element to the map prevents
using the same index twice. If you add first then check, you might match an
element with itself.
wrong_approach: "Adding to map before checking complement"
correct_approach: "Check complement first, then add to map"
- title: Returning values instead of indices
description: |
The problem asks for indices, not the actual values. Make sure you store and
return the indices from the hash map.
wrong_approach: "return [num, complement]"
correct_approach: "return [seen[complement], i]"
- title: Forgetting duplicate values
description: |
When there are duplicate values (e.g., [3,3] with target 6), the algorithm
still works because we check for complement before adding to the map.
key_takeaways:
- Hash maps trade space for time, turning O(n) lookups into O(1)
- Building data structures incrementally can prevent edge cases
- Always clarify whether to return indices or values
- This pattern appears in many "find pair" problems
time_complexity: "O(n)"
space_complexity: "O(n)"
complexity_explanation: |
Time: We iterate through the array once, and each hash map operation is O(1) average.
Space: In the worst case, we store all n elements in the hash map before finding a match.
solutions:
- approach_name: Hash Map (Optimal)
is_optimal: true
code: |
def two_sum(nums: list[int], target: int) -> list[int]:
seen = {}
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [seen[complement], i]
seen[num] = i
return [] # No solution found
explanation: |
Single pass through the array, storing each number's index.
For each number, check if its complement exists in the map.
- approach_name: Brute Force
is_optimal: false
code: |
def two_sum(nums: list[int], target: int) -> list[int]:
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
if nums[i] + nums[j] == target:
return [i, j]
return []
explanation: |
Check every pair of numbers. Simple but inefficient for large inputs.
Time: O(n²), Space: O(1).

View File

@@ -0,0 +1,134 @@
title: Valid Parentheses
slug: valid-parentheses
difficulty: easy
leetcode_id: 20
leetcode_url: https://leetcode.com/problems/valid-parentheses/
categories:
- strings
- stack
patterns:
- monotonic-stack
description: |
Given a string `s` containing just the characters `'('`, `')'`, `'{'`, `'}'`, `'['` and `']'`, determine if the input string is valid.
An input string is valid if:
1. Open brackets must be closed by the same type of brackets.
2. Open brackets must be closed in the correct order.
3. Every close bracket has a corresponding open bracket of the same type.
constraints: |
- 1 <= s.length <= 10^4
- s consists of parentheses only '()[]{}'
examples:
- input: 's = "()"'
output: "true"
explanation: "Single pair of matching parentheses."
- input: 's = "()[]{}"'
output: "true"
explanation: "Three separate valid pairs."
- input: 's = "(]"'
output: "false"
explanation: "Mismatched bracket types."
- input: 's = "([)]"'
output: "false"
explanation: "Incorrect nesting order."
explanation:
approach: |
1. Create a mapping of closing brackets to their opening counterparts
2. Initialize an empty stack to track opening brackets
3. Iterate through each character in the string:
- If it's an opening bracket, push it onto the stack
- If it's a closing bracket, check if the stack is empty (invalid) or if the top
of the stack matches the corresponding opening bracket
4. After processing all characters, the stack should be empty for a valid string
intuition: |
The key insight is that brackets must be closed in LIFO (Last-In-First-Out) order.
The most recently opened bracket must be closed first, which is exactly what a stack does.
When we encounter a closing bracket, the most recent unclosed opening bracket (top of stack)
must match it. If they don't match, or if there's no opening bracket to match, the string
is invalid.
Think of it like nested function calls — the innermost function must return before the
outer one can.
common_pitfalls:
- title: Forgetting to check empty stack
description: |
When encountering a closing bracket, you must first check if the stack is empty.
If it is, there's no matching opening bracket.
wrong_approach: "Directly checking stack[-1] without empty check"
correct_approach: "Check if stack is empty before accessing stack[-1]"
- title: Not checking if stack is empty at the end
description: |
After processing all characters, leftover opening brackets in the stack mean
they were never closed. Return stack is empty, not just True.
wrong_approach: "return True after the loop"
correct_approach: "return len(stack) == 0"
- title: Confusing bracket mapping direction
description: |
Map closing brackets to opening brackets (not vice versa) because we encounter
closing brackets when we need to check for a match.
key_takeaways:
- Stacks are ideal for matching nested structures
- LIFO order matches the nesting requirement of brackets
- Always check edge cases (empty string, only opening, only closing)
- This pattern extends to validating HTML tags, code blocks, etc.
time_complexity: "O(n)"
space_complexity: "O(n)"
complexity_explanation: |
Time: We process each character exactly once.
Space: In the worst case (all opening brackets), the stack holds n/2 elements.
solutions:
- approach_name: Stack (Optimal)
is_optimal: true
code: |
def is_valid(s: str) -> bool:
stack = []
mapping = {')': '(', '}': '{', ']': '['}
for char in s:
if char in mapping:
# Closing bracket
if not stack or stack[-1] != mapping[char]:
return False
stack.pop()
else:
# Opening bracket
stack.append(char)
return len(stack) == 0
explanation: |
Use a stack to track opening brackets. For each closing bracket,
verify it matches the most recent opening bracket.
- approach_name: Stack with Early Return
is_optimal: true
code: |
def is_valid(s: str) -> bool:
# Quick check: odd length can never be valid
if len(s) % 2 != 0:
return False
stack = []
pairs = {'(': ')', '{': '}', '[': ']'}
for char in s:
if char in pairs:
stack.append(pairs[char])
elif not stack or stack.pop() != char:
return False
return not stack
explanation: |
Optimization: push the expected closing bracket instead of the opening one.
This simplifies the comparison when we encounter a closing bracket.