From be754e41a4e3590ba5c490b72e00ef8f6132d32f Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Mon, 28 Apr 2025 22:51:54 +0100 Subject: [PATCH] medium array/string questions --- .../questions/container-with-most-water.yaml | 99 +++++++++++++++ .../longest-substring-without-repeating.yaml | 114 +++++++++++++++++ backend/data/questions/three-sum.yaml | 119 ++++++++++++++++++ 3 files changed, 332 insertions(+) create mode 100644 backend/data/questions/container-with-most-water.yaml create mode 100644 backend/data/questions/longest-substring-without-repeating.yaml create mode 100644 backend/data/questions/three-sum.yaml diff --git a/backend/data/questions/container-with-most-water.yaml b/backend/data/questions/container-with-most-water.yaml new file mode 100644 index 0000000..724e32c --- /dev/null +++ b/backend/data/questions/container-with-most-water.yaml @@ -0,0 +1,99 @@ +title: Container With Most Water +slug: container-with-most-water +difficulty: medium +leetcode_id: 11 +leetcode_url: https://leetcode.com/problems/container-with-most-water/ +categories: + - arrays + - two-pointers +patterns: + - two-pointers + - greedy + +description: | + You are given an integer array `height` of length n. There are n vertical lines drawn such + that the two endpoints of the ith line are (i, 0) and (i, height[i]). + + Find two lines that together with the x-axis form a container, such that the container + contains the most water. + + Return the maximum amount of water a container can store. + +constraints: | + - n == height.length + - 2 <= n <= 10^5 + - 0 <= height[i] <= 10^4 + +examples: + - input: "height = [1,8,6,2,5,4,8,3,7]" + output: "49" + explanation: "Lines at index 1 (height 8) and 8 (height 7) form container with area 7 * 7 = 49." + - input: "height = [1,1]" + output: "1" + explanation: "Only container possible has area 1 * 1 = 1." + +explanation: + approach: | + 1. Start with two pointers at the far left and far right + 2. Calculate the area formed by the two lines + 3. Move the pointer pointing to the shorter line inward + 4. Track maximum area seen + 5. Continue until pointers meet + + intuition: | + Area = width × height. The height is limited by the shorter line. + + Starting with maximum width (endpoints), we can only improve by finding taller lines. + If we move the taller pointer, width decreases and height can't increase beyond the + shorter line — so area can only decrease or stay the same. + + Moving the shorter pointer gives us a chance to find a taller line that could + increase the height enough to compensate for the reduced width. + + common_pitfalls: + - title: Moving the wrong pointer + description: | + Always move the pointer pointing to the shorter line. Moving the taller one + cannot possibly increase the area since height is constrained by the shorter. + wrong_approach: "Moving left pointer always or randomly" + correct_approach: "Move the pointer with smaller height[i]" + + - title: Using nested loops + description: | + O(n²) brute force is unnecessary. Two pointers achieve O(n) by eliminating + suboptimal pairs without checking them. + + key_takeaways: + - Two pointers from opposite ends is powerful for optimization + - Moving the limiting factor gives the best chance of improvement + - Greedy choice (move shorter) is provably optimal here + - Width × min(heights) is the key formula + + time_complexity: "O(n)" + space_complexity: "O(1)" + complexity_explanation: | + Time: Each pointer moves at most n times total. + Space: Only a few variables for pointers and max area. + +solutions: + - approach_name: Two Pointers (Optimal) + is_optimal: true + code: | + def max_area(height: list[int]) -> int: + left, right = 0, len(height) - 1 + max_water = 0 + + while left < right: + width = right - left + h = min(height[left], height[right]) + max_water = max(max_water, width * h) + + if height[left] < height[right]: + left += 1 + else: + right -= 1 + + return max_water + explanation: | + Start from both ends and move the shorter line inward. + Track maximum area found during traversal. diff --git a/backend/data/questions/longest-substring-without-repeating.yaml b/backend/data/questions/longest-substring-without-repeating.yaml new file mode 100644 index 0000000..da6bf66 --- /dev/null +++ b/backend/data/questions/longest-substring-without-repeating.yaml @@ -0,0 +1,114 @@ +title: Longest Substring Without Repeating Characters +slug: longest-substring-without-repeating +difficulty: medium +leetcode_id: 3 +leetcode_url: https://leetcode.com/problems/longest-substring-without-repeating-characters/ +categories: + - strings + - hash-tables +patterns: + - sliding-window + +description: | + Given a string `s`, find the length of the longest substring without repeating characters. + +constraints: | + - 0 <= s.length <= 5 * 10^4 + - s consists of English letters, digits, symbols and spaces + +examples: + - input: 's = "abcabcbb"' + output: "3" + explanation: "The answer is 'abc', with length 3." + - input: 's = "bbbbb"' + output: "1" + explanation: "The answer is 'b', with length 1." + - input: 's = "pwwkew"' + output: "3" + explanation: "The answer is 'wke', with length 3." + +explanation: + approach: | + 1. Use a sliding window with left and right pointers + 2. Maintain a set of characters in the current window + 3. Expand right pointer, adding characters to the set + 4. When a duplicate is found, shrink from left until duplicate is removed + 5. Track maximum window size throughout + + intuition: | + We're looking for the longest contiguous substring where all characters are unique. + A sliding window naturally represents a substring. + + When we encounter a duplicate, the current window is invalid. Instead of restarting + from scratch, we shrink the window from the left until the duplicate is removed. + This way, we never revisit characters unnecessarily. + + common_pitfalls: + - title: Resetting the window incorrectly + description: | + When finding a duplicate, don't start over from the next character. + Instead, shrink from the left until the duplicate is removed. + wrong_approach: "left = right when duplicate found" + correct_approach: "Increment left until duplicate is out of window" + + - title: Not handling empty string + description: | + An empty string should return 0. Make sure the algorithm handles this. + + - title: Off-by-one in length calculation + description: | + Window length is right - left + 1, or just track max before removing. + + key_takeaways: + - Sliding window is ideal for substring problems with constraints + - Use a set or map to track elements in current window + - Shrinking from one end maintains the contiguous property + - This pattern appears in many "longest/shortest with constraint" problems + + time_complexity: "O(n)" + space_complexity: "O(min(m, n))" + complexity_explanation: | + Time: Each character is visited at most twice (once by right, once by left). + Space: The set holds at most min(n, m) characters where m is the charset size. + +solutions: + - approach_name: Sliding Window with Set (Optimal) + is_optimal: true + code: | + def length_of_longest_substring(s: str) -> int: + char_set = set() + left = 0 + max_length = 0 + + for right in range(len(s)): + while s[right] in char_set: + char_set.remove(s[left]) + left += 1 + + char_set.add(s[right]) + max_length = max(max_length, right - left + 1) + + return max_length + explanation: | + Expand window by moving right pointer. + When duplicate found, shrink from left until window is valid again. + + - approach_name: Optimized with Hash Map + is_optimal: true + code: | + def length_of_longest_substring(s: str) -> int: + char_index = {} + left = 0 + max_length = 0 + + for right, char in enumerate(s): + if char in char_index and char_index[char] >= left: + left = char_index[char] + 1 + + char_index[char] = right + max_length = max(max_length, right - left + 1) + + return max_length + explanation: | + Store the last index of each character. + Jump left pointer directly past the duplicate instead of shrinking one by one. diff --git a/backend/data/questions/three-sum.yaml b/backend/data/questions/three-sum.yaml new file mode 100644 index 0000000..0712014 --- /dev/null +++ b/backend/data/questions/three-sum.yaml @@ -0,0 +1,119 @@ +title: 3Sum +slug: three-sum +difficulty: medium +leetcode_id: 15 +leetcode_url: https://leetcode.com/problems/3sum/ +categories: + - arrays + - two-pointers + - sorting +patterns: + - two-pointers + +description: | + Given an integer array `nums`, return all the triplets [nums[i], nums[j], nums[k]] such that + i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0. + + Notice that the solution set must not contain duplicate triplets. + +constraints: | + - 3 <= nums.length <= 3000 + - -10^5 <= nums[i] <= 10^5 + +examples: + - input: "nums = [-1,0,1,2,-1,-4]" + output: "[[-1,-1,2],[-1,0,1]]" + explanation: "The distinct triplets that sum to zero." + - input: "nums = [0,1,1]" + output: "[]" + explanation: "No triplet sums to zero." + - input: "nums = [0,0,0]" + output: "[[0,0,0]]" + explanation: "Only one triplet sums to zero." + +explanation: + approach: | + 1. Sort the array + 2. For each element nums[i], find pairs that sum to -nums[i] + 3. Use two pointers (left, right) to find pairs in the remaining array + 4. Skip duplicates at each level to avoid duplicate triplets + + intuition: | + After sorting, for each fixed element nums[i], we need to find nums[j] + nums[k] = -nums[i]. + This reduces to the Two Sum II problem on a sorted array, solvable with two pointers. + + Sorting enables two things: efficient two-pointer search and easy duplicate skipping. + We skip duplicates by checking if current value equals previous value. + + common_pitfalls: + - title: Not handling duplicates + description: | + Without duplicate skipping, you'll return duplicate triplets. + Skip at the outer loop and both pointers. + wrong_approach: "Not skipping when nums[i] == nums[i-1]" + correct_approach: "if i > 0 and nums[i] == nums[i-1]: continue" + + - title: Wrong pointer skipping + description: | + After finding a valid triplet, skip duplicates for both left and right pointers + while maintaining left < right. + + - title: Starting i too late + description: | + The outer loop should start at index 0. Also, skip if nums[i] > 0 since + sorted array means no valid triplet possible. + + key_takeaways: + - Reduce N-sum to (N-1)-sum by fixing one element + - Sorting enables two-pointer approach and duplicate handling + - Duplicate skipping happens at multiple levels + - Time complexity is O(n²) — can't do better for returning all triplets + + time_complexity: "O(n²)" + space_complexity: "O(log n) to O(n)" + complexity_explanation: | + Time: O(n log n) for sorting + O(n²) for the two-pointer search. + Space: Depends on sorting algorithm (log n for in-place, n for non-in-place). + +solutions: + - approach_name: Sort + Two Pointers (Optimal) + is_optimal: true + code: | + def three_sum(nums: list[int]) -> list[list[int]]: + nums.sort() + result = [] + + for i in range(len(nums) - 2): + # Skip duplicates for i + if i > 0 and nums[i] == nums[i - 1]: + continue + + # Early termination + if nums[i] > 0: + break + + left, right = i + 1, len(nums) - 1 + + while left < right: + total = nums[i] + nums[left] + nums[right] + + if total < 0: + left += 1 + elif total > 0: + right -= 1 + else: + result.append([nums[i], nums[left], nums[right]]) + + # Skip duplicates for left and right + while left < right and nums[left] == nums[left + 1]: + left += 1 + while left < right and nums[right] == nums[right - 1]: + right -= 1 + + left += 1 + right -= 1 + + return result + explanation: | + Fix one element and use two pointers to find the other two. + Skip duplicates at all levels to avoid duplicate triplets.