Beginner

Mock Exam 1: Warm-Up

Your first timed practice session. Three problems progressing from Easy to Medium. Target time: 45 minutes total. Start your timer now and try to solve all three before looking at the solutions.

Time Target: 45 Minutes
Problem 1: 10 min (Easy) | Problem 2: 15 min (Easy) | Problem 3: 20 min (Medium)
Start your timer before reading the problems. Do not look at solutions until time is up.

Problem 1: Two Sum (Easy)

Given an array of integers nums and an integer target, return the indices of the two numbers that add up to target. You may assume each input has exactly one solution, and you may not use the same element twice.

Examples

Input: nums = [2, 7, 11, 15], target = 9
Output: [0, 1]
Explanation: nums[0] + nums[1] = 2 + 7 = 9

Input: nums = [3, 2, 4], target = 6
Output: [1, 2]

Input: nums = [3, 3], target = 6
Output: [0, 1]

Constraints

  • 2 ≤ nums.length ≤ 104
  • -109 ≤ nums[i] ≤ 109
  • -109 ≤ target ≤ 109
  • Exactly one valid answer exists

Solution: Brute Force

def two_sum_brute(nums, target):
    """Brute force: check every pair.

    Time: O(n^2) - nested loops
    Space: O(1) - no extra storage

    Try every combination of two indices.
    Simple but too slow for large inputs.
    """
    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i, j]
    return []

Solution: Optimal (Hash Map)

def two_sum(nums, target):
    """Optimal: hash map for O(1) complement lookup.

    Time: O(n) - single pass through array
    Space: O(n) - hash map stores up to n elements

    For each number, check if its complement
    (target - num) was already seen. If yes,
    return both indices. If no, store current
    number and its index.
    """
    seen = {}  # value -> index
    for i, num in enumerate(nums):
        complement = target - num
        if complement in seen:
            return [seen[complement], i]
        seen[num] = i
    return []

# Test
print(two_sum([2, 7, 11, 15], 9))  # [0, 1]
print(two_sum([3, 2, 4], 6))       # [1, 2]
print(two_sum([3, 3], 6))          # [0, 1]
💡
Pattern: Hash map for complement lookup. This pattern appears in many problems: Two Sum, Three Sum, Four Sum, subarray sum equals K. Always ask: "Can I trade space for time with a hash map?"

Problem 2: Valid Anagram (Easy)

Given two strings s and t, return True if t is an anagram of s, and False otherwise. An anagram uses all the original letters exactly once.

Examples

Input: s = "anagram", t = "nagaram"
Output: True

Input: s = "rat", t = "car"
Output: False

Constraints

  • 1 ≤ s.length, t.length ≤ 5 × 104
  • s and t consist of lowercase English letters

Solution: Brute Force (Sort)

def is_anagram_sort(s, t):
    """Sort both strings and compare.

    Time: O(n log n) - sorting dominates
    Space: O(n) - sorted copies

    Simple but not optimal due to sorting overhead.
    """
    return sorted(s) == sorted(t)

Solution: Optimal (Character Count)

from collections import Counter

def is_anagram(s, t):
    """Optimal: count character frequencies.

    Time: O(n) - single pass through each string
    Space: O(1) - at most 26 lowercase letters

    Two strings are anagrams if and only if they
    have the same character frequency distribution.
    """
    if len(s) != len(t):
        return False
    return Counter(s) == Counter(t)

# Manual counting alternative (no imports):
def is_anagram_manual(s, t):
    """Without Counter: use a dictionary.

    Time: O(n), Space: O(1) - 26 chars max
    """
    if len(s) != len(t):
        return False

    count = {}
    for c in s:
        count[c] = count.get(c, 0) + 1
    for c in t:
        count[c] = count.get(c, 0) - 1
        if count[c] < 0:
            return False
    return True

# Test
print(is_anagram("anagram", "nagaram"))  # True
print(is_anagram("rat", "car"))          # False

Problem 3: Group Anagrams (Medium)

Given an array of strings strs, group the anagrams together. You can return the answer in any order. An anagram is a word formed by rearranging the letters of another word.

Examples

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 ≤ 104
  • 0 ≤ strs[i].length ≤ 100
  • strs[i] consists of lowercase English letters

Solution: Brute Force

def group_anagrams_brute(strs):
    """Brute force: compare every pair.

    Time: O(n^2 * k) where k = max string length
    Space: O(n * k)

    For each string, check against all existing
    groups using the is_anagram function. Very slow.
    """
    groups = []
    used = set()
    for i in range(len(strs)):
        if i in used:
            continue
        group = [strs[i]]
        for j in range(i + 1, len(strs)):
            if j not in used and sorted(strs[i]) == sorted(strs[j]):
                group.append(strs[j])
                used.add(j)
        groups.append(group)
    return groups

Solution: Optimal (Sorted Key)

from collections import defaultdict

def group_anagrams(strs):
    """Optimal: use sorted string as hash key.

    Time: O(n * k log k) where n = number of strings,
          k = max string length (for sorting each string)
    Space: O(n * k) - storing all strings in groups

    Anagrams produce the same sorted string.
    Use sorted version as dictionary key to group them.
    """
    groups = defaultdict(list)
    for s in strs:
        key = tuple(sorted(s))  # "eat" -> ('a','e','t')
        groups[key].append(s)
    return list(groups.values())

# Even faster: character count as key (avoids sorting)
def group_anagrams_count(strs):
    """Alternative: use character count tuple as key.

    Time: O(n * k) - no sorting needed
    Space: O(n * k)

    Count frequency of each letter and use that
    as the dictionary key. Avoids O(k log k) sort.
    """
    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())

# Test
print(group_anagrams(["eat","tea","tan","ate","nat","bat"]))
# [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]
Exam Debrief: If you finished all 3 problems in under 45 minutes with correct solutions, you are on track. If you took longer, focus on pattern recognition: all three problems use hashing as the core technique. Recognizing this family immediately saves planning time.

Score Your Performance

ResultScoreNext Step
All 3 solved optimally in < 45 min⭐ ExcellentMove to Mock Exam 2
All 3 solved (any approach) in < 45 min👍 GoodReview optimal solutions, then Mock Exam 2
2 solved in 45 min🔄 DevelopingRe-do the missed problem, then Mock Exam 2
1 or 0 solved📚 Study MoreReview hash map patterns, retry this exam tomorrow