From 66231e1f11a19262cd4749b3f2cb4b08db33282a Mon Sep 17 00:00:00 2001 From: Srihari Date: Mon, 24 Nov 2025 21:41:27 +0530 Subject: [PATCH] Update articles --- articles/anagram-groups.md | 32 +++++ articles/duplicate-integer.md | 55 ++++++++ articles/is-anagram.md | 50 ++++++++ articles/is-palindrome.md | 38 ++++++ articles/longest-consecutive-sequence.md | 95 ++++++++++++++ articles/max-water-container.md | 41 ++++++ articles/products-of-array-discluding-self.md | 114 +++++++++++++++++ articles/string-encode-and-decode.md | 55 ++++++++ articles/three-integer-sum.md | 79 ++++++++++++ articles/top-k-elements-in-list.md | 61 +++++++++ articles/trapping-rain-water.md | 100 +++++++++++++++ articles/two-integer-sum-ii.md | 70 ++++++++++ articles/valid-sudoku.md | 121 ++++++++++++++++++ 13 files changed, 911 insertions(+) diff --git a/articles/anagram-groups.md b/articles/anagram-groups.md index 0969c406c..c89e29a61 100644 --- a/articles/anagram-groups.md +++ b/articles/anagram-groups.md @@ -1,5 +1,20 @@ ## 1. Sorting +### Intuition + +Anagrams become identical when their characters are sorted. +For example, `"eat"`, `"tea"`, and `"ate"` all become `"aet"` after sorting. +By using the sorted version of each string as a key, we can group all anagrams together. +Strings that share the same sorted form must be anagrams, so placing them in the same group is both natural and efficient. + +### Algorithm + +1. Create a hash map where each key is the sorted version of a string, and the value is a list of strings belonging to that anagram group. +2. Iterate through each string in the input list: + - Sort the characters of the string to form a key. + - Append the original string to the list corresponding to this key. +3. After processing all strings, return all values from the hash map, which represent the grouped anagrams. + ::tabs-start ```python @@ -153,6 +168,23 @@ class Solution { ## 2. Hash Table +### Intuition + +Instead of sorting each string, we can represent every string by the frequency of its characters. +Since the problem uses lowercase English letters, a fixed-size array of length `26` can capture how many times each character appears. +Two strings are anagrams if and only if their frequency arrays are identical. +By using this frequency array (converted to a tuple so it can be a dictionary key), we can group all strings that share the same character counts. + +### Algorithm + +1. Create a hash map where each key is a `26`-length tuple representing character frequencies, and each value is a list of strings belonging to that anagram group. +2. For each string in the input: + - Initialize a frequency array of size `26` with all zeros. + - For each character in the string, increment the count at the corresponding index. + - Convert the frequency array to a tuple and use it as the key. + - Append the string to the list associated with this key. +3. After processing all strings, return all the lists stored in the hash map. + ::tabs-start ```python diff --git a/articles/duplicate-integer.md b/articles/duplicate-integer.md index 828d15e2e..50573d3e1 100644 --- a/articles/duplicate-integer.md +++ b/articles/duplicate-integer.md @@ -1,5 +1,16 @@ ## 1. Brute Force +### Intuition + +We can check every pair of different elements in the array and return `true` if any pair has equal values. +This is the most intuitive approach because it directly compares all possible pairs, but it is also the least efficient since it examines every combination. + +### Algorithm + +1. Iterate through the array using two nested loops to check all possible pairs of distinct indices. +2. If any pair of elements has the same value, return `true`. +3. If all pairs are checked and no duplicates are found, return `false`. + ::tabs-start ```python @@ -131,6 +142,20 @@ class Solution { ## 2. Sorting +### Intuition + +If we sort the array, then any duplicate values will appear next to each other. +Sorting groups identical elements together, so we can simply check adjacent positions to detect duplicates. +This reduces the problem to a single linear scan after sorting, making it easy to identify if any value repeats. + +### Algorithm + +1. Sort the array in non-decreasing order. +2. Iterate through the array starting from index `1`. +3. Compare the current element with the previous element. +4. If both elements are equal, we have found a duplicate — return `True`. +5. If the loop finishes without detecting equal neighbors, return `False`. + ::tabs-start ```python @@ -255,6 +280,22 @@ class Solution { ## 3. Hash Set +### Intuition + +We can use a hash set to efficiently keep track of the values we have already encountered. +As we iterate through the array, we check whether the current value is already present in the set. +If it is, that means we've seen this value before, so a duplicate exists. +Using a hash set allows constant-time lookups, making this approach much more efficient than comparing every pair. + +### Algorithm + +1. Initialize an empty hash set to store seen values. +2. Iterate through each number in the array. +3. For each number: + - If it is already in the set, return `True` because a duplicate has been found. + - Otherwise, add it to the set. +4. If the loop finishes without finding any duplicates, return `False`. + ::tabs-start ```python @@ -387,6 +428,20 @@ class Solution { ## 4. Hash Set Length +### Intuition + +This approach uses the same idea as the previous hash set method: a set only stores unique values, so duplicates are automatically removed. +Instead of checking each element manually, we simply compare the length of the set to the length of the original array. +If duplicates exist, the set will contain fewer elements. +The logic is identical to the earlier approach — this version is just a shorter and more concise implementation of it. + +### Algorithm + +1. Convert the array into a hash set, which removes duplicates. +2. Compare the size of the set with the size of the original array. +3. If the set is smaller, return `True` because duplicates must have been removed. +4. Otherwise, return `False`. + ::tabs-start ```python diff --git a/articles/is-anagram.md b/articles/is-anagram.md index 9cef09353..aabb5d586 100644 --- a/articles/is-anagram.md +++ b/articles/is-anagram.md @@ -1,5 +1,19 @@ ## 1. Sorting +### Intuition + +If two strings are anagrams, they must contain exactly the same characters with the same frequencies. +By sorting both strings, all characters will be arranged in a consistent order. +If the two sorted strings are identical, then every character and its count match, which means the strings are anagrams. + +### Algorithm + +1. If the lengths of the two strings differ, return `False` immediately because they cannot be anagrams. +2. Sort both strings. +3. Compare the sorted versions of the strings: + - If they are equal, return `True`. + - Otherwise, return `False`. + ::tabs-start ```python @@ -133,6 +147,24 @@ class Solution { ## 2. Hash Map +### Intuition + +If two strings are anagrams, they must use the same characters with the same frequencies. +Instead of sorting, we can count how many times each character appears in both strings. +By using two hash maps (or dictionaries), we track the frequency of every character in each string. +If both frequency maps match exactly, then the strings contain the same characters with same frequencies, meaning they are anagrams. + +### Algorithm + +1. If the two strings have different lengths, return `False` immediately. +2. Create two hash maps to store character frequencies for each string. +3. Iterate through both strings at the same time: + - Increase the character count for `s[i]` in the first map. + - Increase the character count for `t[i]` in the second map. +4. After building both maps, compare them: + - If the maps are equal, return `True`. + - Otherwise, return `False`. + ::tabs-start ```python @@ -310,6 +342,24 @@ class Solution { ## 3. Hash Table (Using Array) +### Intuition + +Since the problem guarantees lowercase English letters, we can use a fixed-size array of length `26` to count character frequencies instead of a hash map. +As we iterate through both strings simultaneously, we increment the count for each character in `s` and decrement the count for each character in `t`. +If the strings are anagrams, every increment will be matched by a corresponding decrement, and all values in the array will end at zero. +This approach is efficient because it avoids hashing and uses constant space. + +### Algorithm + +1. If the lengths of the strings differ, return `False` immediately. +2. Create a frequency array `count` of size `26` initialized to zero. +3. Iterate through both strings: + - Increment the count at the index corresponding to `s[i]`. + - Decrement the count at the index corresponding to `t[i]`. +4. After processing both strings, scan through the `count` array: + - If any value is not zero, return `False` because the frequencies differ. +5. If all values are zero, return `True` since the strings are anagrams. + ::tabs-start ```python diff --git a/articles/is-palindrome.md b/articles/is-palindrome.md index 76910a13a..388664f22 100644 --- a/articles/is-palindrome.md +++ b/articles/is-palindrome.md @@ -1,5 +1,21 @@ ## 1. Reverse String +### Intuition + +To check if a string is a palindrome, we only care about letters and digits—everything else can be ignored. +We can build a cleaned version of the string that contains only alphanumeric characters, all converted to lowercase for consistency. +Once we have this cleaned string, the problem becomes very simple: +a string is a palindrome if it is exactly the same as its reverse. + +### Algorithm + +1. Create an empty string `newStr`. +2. Loop through each character `c` in the input string: + - If `c` is alphanumeric, convert it to lowercase and add it to `newStr`. +3. Compare `newStr` with its reverse (`newStr[::-1]`): + - If they are equal, return `True`. + - Otherwise, return `False`. + ::tabs-start ```python @@ -152,6 +168,28 @@ class Solution { ## 2. Two Pointers +### Intuition + +Instead of building a new string, we can check the palindrome directly in-place using two pointers. +One pointer starts at the beginning (`l`) and the other at the end (`r`). +We move both pointers inward, skipping any characters that are not letters or digits. +Whenever both pointers point to valid characters, we compare them in lowercase form. +If at any point they differ, the string is not a palindrome. +This method avoids extra space and keeps the logic simple and efficient. + +### Algorithm + +1. Initialize two pointers: + - `l` at the start of the string, + - `r` at the end of the string. +2. While `l` is less than `r`: + - Move `l` forward until it points to an alphanumeric character. + - Move `r` backward until it points to an alphanumeric character. + - Compare the lowercase characters at `l` and `r`: + - If they don’t match, return `False`. + - Move both pointers inward: `l += 1`, `r -= 1`. +3. If the loop finishes without mismatches, return `True`. + ::tabs-start ```python diff --git a/articles/longest-consecutive-sequence.md b/articles/longest-consecutive-sequence.md index 6112daf7b..57c728957 100644 --- a/articles/longest-consecutive-sequence.md +++ b/articles/longest-consecutive-sequence.md @@ -1,5 +1,25 @@ ## 1. Brute Force +### Intuition + +A consecutive sequence grows by checking whether the next number (`num + 1`, `num + 2`, …) exists in the set. +The brute-force approach simply starts from every number in the list and tries to extend a consecutive streak as far as possible. +For each number, we repeatedly check if the next number exists, increasing the streak length until the sequence breaks. +Even though this method works, it does unnecessary repeated work because many sequences get recomputed multiple times. + +### Algorithm + +1. Convert the input list to a set for **O(1)** lookups. +2. Initialize `res` to store the maximum streak length. +3. For each number `num` in the original list: + - Start a new streak count at 0. + - Set `curr = num`. + - While `curr` exists in the set: + - Increase the streak count. + - Move to the next number (`curr += 1`). + - Update `res` with the longest streak found so far. +4. Return `res` after checking all numbers. + ::tabs-start ```python @@ -178,6 +198,33 @@ class Solution { ## 2. Sorting +### Intuition + +If we sort the numbers first, then all consecutive values will appear next to each other. +This makes it easy to walk through the sorted list and count how long each consecutive sequence is. +We simply move forward while the current number matches the expected next value in the sequence. +Duplicates don’t affect the result—they are just skipped—while gaps reset the streak count. +This approach is simpler and more organized than the brute force method because sorting places all potential sequences in order. + +### Algorithm + +1. If the input list is empty, return `0`. +2. Sort the array in non-decreasing order. +3. Initialize: + - `res` to track the longest streak, + - `curr` as the first number, + - `streak` as `0`, + - index `i = 0`. +4. While `i` is within bounds: + - If `nums[i]` does not match `curr`, reset: + - `curr = nums[i]` + - `streak = 0` + - Skip over all duplicates of `curr` by advancing `i` while `nums[i] == curr`. + - Increase `streak` by `1` since we found the expected number. + - Increase `curr` by `1` to expect the next number in the sequence. + - Update `res` with the maximum streak found so far. +5. Return `res` after scanning the entire list. + ::tabs-start ```python @@ -413,6 +460,27 @@ class Solution { ## 3. Hash Set +### Intuition + +To avoid repeatedly recounting the same sequences, we only want to start counting when we find the **beginning** of a consecutive sequence. +A number is the start of a sequence if `num - 1` is **not** in the set. +This guarantees that each consecutive sequence is counted exactly once. + +Once we identify such a starting number, we simply keep checking if `num + 1`, `num + 2`, … exist in the set and extend the streak as far as possible. +This makes the solution efficient and clean because each number contributes to the sequence only one time. + +### Algorithm + +1. Convert the list into a set `numSet` for O(1) lookups. +2. Initialize `longest` to track the length of the longest consecutive sequence. +3. For each number `num` in `numSet`: + - Check if `num - 1` is **not** in the set: + - If true, `num` is the start of a sequence. + - Initialize `length = 1`. + - While `num + length` exists in the set, increase `length`. + - Update `longest` with the maximum length found. +4. Return `longest` after scanning all numbers. + ::tabs-start ```python @@ -597,6 +665,33 @@ class Solution { ## 4. Hash Map +### Intuition + +When we place a new number into the map, it may connect two existing sequences or extend one of them. +Instead of scanning forward or backward, we only look at the lengths stored at the **neighbors**: + +- `mp[num - 1]` gives the length of the sequence ending right before `num` +- `mp[num + 1]` gives the length of the sequence starting right after `num` + +By adding these together and including the current number, we know the total length of the new merged sequence. +We then update the **left boundary** and **right boundary** of this sequence so the correct length can be retrieved later. +This keeps the whole operation very efficient and avoids repeated work. + +### Algorithm + +1. Create a hash map `mp` that stores sequence lengths at boundary positions. +2. Initialize `res = 0` to store the longest sequence found. +3. For each number `num` in the input: + - If `num` is already in `mp`, skip it. + - Compute the new sequence length: + - `length = mp[num - 1] + mp[num + 1] + 1` + - Store this length at `num`. + - Update the boundaries: + - Left boundary: `mp[num - mp[num - 1]] = length` + - Right boundary: `mp[num + mp[num + 1]] = length` + - Update `res` to keep track of the longest sequence. +4. Return `res` after processing all numbers. + ::tabs-start ```python diff --git a/articles/max-water-container.md b/articles/max-water-container.md index f2c2a83df..2c13b5ee6 100644 --- a/articles/max-water-container.md +++ b/articles/max-water-container.md @@ -1,5 +1,23 @@ ## 1. Brute Force +### Intuition + +We try every possible pair of lines and compute the area they form. +For each pair `(i, j)`, the height of the container is the shorter of the two lines, and the width is the distance between them. +By checking all pairs, we are guaranteed to find the maximum area. + +### Algorithm + +1. Initialize `res = 0` to track the maximum area found. +2. Use two nested loops: + - Outer loop picks the left line `i`. + - Inner loop picks the right line `j > i`. +3. For each pair `(i, j)`: + - Compute the height as `min(heights[i], heights[j])`. + - Compute the width as `j - i`. + - Update `res` with the maximum of its current value and the new area. +4. After checking all pairs, return `res`. + ::tabs-start ```python @@ -135,6 +153,29 @@ class Solution { ## 2. Two Pointers +### Intuition + +Using two pointers lets us efficiently search for the maximum area without checking every pair. +We start with the widest container (left at start, right at end). +The height is limited by the shorter line, so to potentially increase the area, we must move the pointer at the shorter line inward. +Moving the taller line never helps because it keeps the height the same but reduces the width. +By always moving the shorter side, we explore all meaningful possibilities. + +### Algorithm + +1. Initialize two pointers: + - `l = 0` + - `r = len(heights) - 1` +2. Set `res = 0` to store the maximum area. +3. While `l < r`: + - Compute the current area: + `area = min(heights[l], heights[r]) * (r - l)` + - Update `res` with the maximum area so far. + - Move the pointer at the shorter height: + - If `heights[l] <= heights[r]`, move `l` right. + - Otherwise, move `r` left. +4. Return `res` after the pointers meet. + ::tabs-start ```python diff --git a/articles/products-of-array-discluding-self.md b/articles/products-of-array-discluding-self.md index c0a836cd5..cfcfd2834 100644 --- a/articles/products-of-array-discluding-self.md +++ b/articles/products-of-array-discluding-self.md @@ -1,5 +1,22 @@ ## 1. Brute Force +### Intuition + +For each position in the array, we can compute the product of all other elements by multiplying every value except the one at the current index. +This directly follows the problem statement and is the most straightforward approach: +**for each index, multiply all elements except itself.** +Although simple, this method is inefficient because it repeats a full pass through the array for every element. + +### Algorithm + +1. Let `n` be the length of the input array and create a result array `res` of size `n`. +2. For each index `i` from `0` to `n - 1`: + - Initialize a running product `prod = 1`. + - Loop through all indices `j` from `0` to `n - 1`: + - If `j` is not equal to `i`, multiply `prod` by `nums[j]`. + - Store `prod` in `res[i]`. +3. After all indices are processed, return `res`. + ::tabs-start ```python @@ -177,6 +194,33 @@ class Solution { ## 2. Division +### Intuition + +This approach works by using a simple idea: +If we know the **product of all non-zero numbers**, we can easily compute the answer for each position using division — as long as there are no division-by-zero issues. + +So we first check how many zeros the array has: +- If there are **two or more zeros**, then every product will include at least one zero → the entire result is all zeros. +- If there is **exactly one zero**, then only the position containing that zero will get the product of all non-zero numbers. All other positions become zero. +- If there are **no zeros**, we can safely do: + **result[i] = total_product // nums[i]** + +### Algorithm + +1. Traverse the array once: + - Multiply all **non-zero** numbers to get the total product. + - Count how many zeros appear. +2. If the zero count is greater than 1: + - Return an array of all zeros. +3. Create a result array of size `n`. +4. Loop through the numbers again: + - If there is one zero: + - The index with zero gets the product of all non-zero numbers. + - All other positions get `0`. + - If there are no zeros: + - Set each result value to `total_product // nums[i]`. +5. Return the result array. + ::tabs-start ```python @@ -426,6 +470,50 @@ class Solution { ## 3. Prefix & Suffix +### Intuition + +For each index, we need the product of all elements **before it** and all elements **after it**. +Instead of recomputing the product repeatedly, we can pre-compute two helpful arrays: + +- **Prefix product**: `pref[i]` = product of all elements to the left of `i` +- **Suffix product**: `suff[i]` = product of all elements to the right of `i` + +Then, the final answer for each index is simply: + +**result[i] = prefix[i] × suffix[i]** + +This works because: +- The prefix handles everything before the index +- The suffix handles everything after the index + +Both pieces together form the product of all numbers except the one at that position. + +### Algorithm + +1. Let `n` be the length of the array. + Create three arrays of size `n`: + - `pref` for prefix products + - `suff` for suffix products + - `res` for the final result + +2. Set: + - `pref[0] = 1` (nothing to the left of index 0) + - `suff[n - 1] = 1` (nothing to the right of last index) + +3. Build the prefix product array: + - For each `i` from `1` to `n - 1`: + - `pref[i] = nums[i - 1] × pref[i - 1]` + +4. Build the suffix product array: + - For each `i` from `n - 2` down to `0`: + - `suff[i] = nums[i + 1] × suff[i + 1]` + +5. Build the result: + - For each index `i`, compute: + - `res[i] = pref[i] × suff[i]` + +6. Return the result array. + ::tabs-start ```python @@ -631,6 +719,32 @@ class Solution { ## 4. Prefix & Suffix (Optimal) +### Intuition + +We can compute the product of all elements except the current one **without using extra prefix and suffix arrays**. +Instead, we reuse the result array and build the answer in two simple passes: + +- In the **first pass**, we fill `res[i]` with the product of all elements to the left of `i` (prefix product). +- In the **second pass**, we multiply each `res[i]` with the product of all elements to the right of `i` (postfix product). + +By maintaining two running values — `prefix` and `postfix` — we avoid the need for separate prefix and suffix arrays. +This gives us the same logic as the previous method, but with **O(1) extra space**. + +### Algorithm + +1. Initialize the result array `res` with all values set to `1`. +2. Create a variable `prefix = 1`. +3. First pass (left to right): + - For each index `i`: + - Set `res[i] = prefix` (product of all elements to the left). + - Update `prefix *= nums[i]`. +4. Create a variable `postfix = 1`. +5. Second pass (right to left): + - For each index `i`: + - Multiply `res[i]` by `postfix` (product of all elements to the right). + - Update `postfix *= nums[i]`. +6. Return the result array `res`. + ::tabs-start ```python diff --git a/articles/string-encode-and-decode.md b/articles/string-encode-and-decode.md index 6eabea551..a342a267d 100644 --- a/articles/string-encode-and-decode.md +++ b/articles/string-encode-and-decode.md @@ -1,5 +1,32 @@ ## 1. Encoding & Decoding +### Intuition + +To encode a list of strings into a single string, we need a way to store each string so that we can later separate them correctly during decoding. +A simple and reliable strategy is to record the **length of each string** first, followed by a special separator, and then append all the strings together. +During decoding, we can read the recorded lengths to know exactly how many characters to extract for each original string. +This avoids any issues with special characters, commas, or symbols inside the strings because the lengths tell us precisely where each string starts and ends. + +### Algorithm + +#### Encoding +1. If the input list is empty, return an empty string. +2. Create an empty list to store the sizes of each string. +3. For each string, append its length to the sizes list. +4. Build a single string by: + - Writing all sizes separated by commas. + - Adding a `'#'` to mark the end of the size section. + - Appending all the actual strings in order. +5. Return the final encoded string. + +#### Decoding +1. If the encoded string is empty, return an empty list. +2. Read characters from the start until reaching `'#'` to extract all recorded sizes: + - Parse each size by reading until a comma. +3. After the `'#'`, extract substrings according to the sizes list: + - For each size, read that many characters and append the substring to the result. +4. Return the list of decoded strings. + ::tabs-start ```python @@ -348,6 +375,34 @@ class Solution { ## 2. Encoding & Decoding (Optimal) +### Intuition + +Instead of storing all string lengths first and then appending the strings, we can directly attach each string to its length. +For every string, we write **`length#string`**. +The `#` character acts as a clear boundary between the length and the actual content, and using the length ensures we know exactly how many characters to read—no matter what characters appear in the string itself. +During decoding, we simply read characters until we reach `#` to find the length, then extract exactly that many characters as the string. +This approach is both simpler and more efficient because it avoids building separate sections for lengths and content. + +### Algorithm + +#### Encoding +1. Initialize an empty result string. +2. For each string in the list: + - Compute its length. + - Append `"length#string"` to the result. +3. Return the final encoded string. + +#### Decoding +1. Initialize an empty list for the decoded strings and a pointer `i = 0`. +2. While `i` is within the bounds of the encoded string: + - Move a pointer `j` forward until it finds `'#'` — this segment represents the length. + - Convert the substring `s[i:j]` into an integer `length`. + - Move `i` to the character right after `'#'`. + - Extract the next `length` characters — this is the original string. + - Append the extracted string to the result list. + - Move `i` forward by `length` to continue decoding the next segment. +3. Return the list of decoded strings. + ::tabs-start ```python diff --git a/articles/three-integer-sum.md b/articles/three-integer-sum.md index c3d13aa3e..923d6f174 100644 --- a/articles/three-integer-sum.md +++ b/articles/three-integer-sum.md @@ -1,5 +1,24 @@ ## 1. Brute Force +### Intuition + +The brute-force approach simply tries every possible triplet. +Since we check all combinations `(i, j, k)` with `i < j < k`, we are guaranteed to find all sets of three numbers that sum to zero. +Sorting helps keep the triplets in order and makes it easier to avoid duplicates by storing them in a set. + +### Algorithm + +1. Sort the array to make handling duplicates easier. +2. Create an empty set `res` to store unique triplets. +3. Use three nested loops: + - For each `i`, + - For each `j > i`, + - For each `k > j`, + - Check if `nums[i] + nums[j] + nums[k] == 0`. + - If true, add the sorted triplet to the set. +4. Convert the set of tuples back into a list of lists. +5. Return the result. + ::tabs-start ```python @@ -179,6 +198,31 @@ class Solution { ## 2. Hash Map +### Intuition + +After sorting the array, we can fix two numbers and look for the third number that completes the triplet. +To do this efficiently, we use a hash map that stores how many times each number appears. +As we pick the first and second numbers, we temporarily reduce their counts in the map so we don’t reuse them. +Then we check whether the needed third value still exists in the map. +Sorting also helps us skip duplicates easily so we only add unique triplets. + +### Algorithm + +1. Sort the array to organize duplicates and allow easy skipping. +2. Build a frequency map `count` for all numbers. +3. Initialize an empty list `res` for storing valid triplets. +4. Loop through each index `i`: + - Decrease the count of `nums[i]` (so it won’t be reused). + - Skip duplicates of the first element. + - Loop through each index `j > i`: + - Decrease the count of `nums[j]`. + - Skip duplicates of the second element. + - Compute the needed third value: + `target = -(nums[i] + nums[j])` + - If `target` still has a positive count, add the triplet. + - After finishing all `j`s, restore the counts for the second loop by adding back the decremented values. +5. Return `res` containing all found triplets. + ::tabs-start ```python @@ -466,6 +510,41 @@ class Solution { ## 3. Two Pointers +### Intuition + +After sorting the array, we can fix one number and then search for the other two using the two-pointer technique. +Sorting helps in two ways: +1. It lets us skip duplicates easily. +2. It ensures that moving the left or right pointer will increase or decrease the sum in a predictable way. + +For each fixed number `a`, we place two pointers: +- `l` starts just after `i`, +- `r` starts at the end. + +If the current sum is too large, we move `r` left to reduce it. +If the sum is too small, we move `l` right to increase it. +When the sum is exactly zero, we record the triplet and skip duplicates. + +### Algorithm + +1. Sort the array to handle duplicates and enable two-pointer logic. +2. Loop through the array using index `i`: + - Let `a = nums[i]`. + - If `a > 0`, break (all remaining numbers are positive). + - Skip duplicate values for the first number. +3. Set two pointers: + - `l = i + 1` + - `r = len(nums) - 1` +4. While `l < r`: + - Compute `threeSum = a + nums[l] + nums[r]`. + - If `threeSum > 0`, move `r` left. + - If `threeSum < 0`, move `l` right. + - If `threeSum == 0`: + - Add the triplet to the result. + - Move both pointers inward. + - Skip duplicates at the left pointer. +5. Return the list of all valid triplets. + ::tabs-start ```python diff --git a/articles/top-k-elements-in-list.md b/articles/top-k-elements-in-list.md index c41763d75..40c053854 100644 --- a/articles/top-k-elements-in-list.md +++ b/articles/top-k-elements-in-list.md @@ -1,5 +1,25 @@ ## 1. Sorting +### Intuition + +To find the `k` most frequent elements, we first need to know how often each number appears. +Once we count the frequencies, we can sort the unique numbers based on how many times they occur. +After sorting, the numbers with the highest frequencies will naturally appear at the end of the list. +By taking the last `k` entries, we get the `k` most frequent elements. + +This approach is easy to reason about: +**count the frequencies → sort by frequency → take the top k.** + +### Algorithm + +1. Create a hash map to store the frequency of each number. +2. Build a list of `[frequency, number]` pairs from the map. +3. Sort this list in ascending order based on frequency. +4. Create an empty result list. +5. Repeatedly pop from the end of the sorted list (highest frequency) and append the number to the result. +6. Stop when the result contains `k` elements. +7. Return the result list. + ::tabs-start ```python @@ -194,6 +214,24 @@ class Solution { ## 2. Min-Heap +### Intuition + +After counting how often each number appears, we want to efficiently keep track of only the `k` most frequent elements. +A **min-heap** is perfect for this because it always keeps the smallest element at the top. +By pushing `(frequency, value)` pairs into the heap and removing the smallest whenever the heap grows beyond size `k`, we ensure that the heap always contains the top `k` most frequent elements. +In the end, the heap holds exactly the `k` values with the highest frequencies. + +### Algorithm + +1. Build a frequency map that counts how many times each number appears. +2. Create an empty min-heap. +3. For each number in the frequency map: + - Push `(frequency, number)` into the heap. + - If the heap size becomes greater than `k`, pop once to remove the smallest frequency. +4. After processing all numbers, the heap contains the `k` most frequent elements. +5. Pop all elements from the heap and collect their numbers into the result list. +6. Return the result. + ::tabs-start ```python @@ -427,6 +465,29 @@ class Solution { ## 3. Bucket Sort +### Intuition + +Each number in the array appears a certain number of times, and the maximum possible frequency is the length of the array. +We can use this idea by creating a list where the index represents a frequency, and at each index we store all numbers that appear exactly that many times. + +For example: +- All numbers that appear 1 time go into group `freq[1]`. +- All numbers that appear 2 times go into group `freq[2]`. +- And so on. + +After we build these groups, we look from the highest possible frequency down to the lowest and collect numbers from these groups until we have `k` of them. +This way, we directly jump to the most frequent numbers without sorting all the elements by frequency. + +### Algorithm + +1. Build a frequency map that counts how many times each number appears. +2. Create a list of groups `freq`, where `freq[i]` will store all numbers that appear exactly `i` times. +3. For each number and its frequency in the map, add the number to `freq[frequency]`. +4. Initialize an empty result list. +5. Loop from the largest possible frequency down to `1`: + - For each number in `freq[i]`, add it to the result list. + - Once the result contains `k` numbers, return it. + ::tabs-start ```python diff --git a/articles/trapping-rain-water.md b/articles/trapping-rain-water.md index 175aa3dbb..6686c7526 100644 --- a/articles/trapping-rain-water.md +++ b/articles/trapping-rain-water.md @@ -1,5 +1,24 @@ ## 1. Brute Force +### Intuition + +For each position, the water trapped above it depends on the **tallest bar to its left** and the **tallest bar to its right**. +If we know these two values, the water at index `i` is: + +`min(leftMax, rightMax) - height[i]` + +The brute-force method recomputes the left maximum and right maximum for every index by scanning the array each time. + +### Algorithm + +1. If the input list is empty, return `0`. +2. Let `n` be the length of the array and initialize `res = 0`. +3. For each index `i`: + - Compute `leftMax` by scanning from index `0` to `i`. + - Compute `rightMax` by scanning from index `i + 1` to the end. + - Add `min(leftMax, rightMax) - height[i]` to `res`. +4. After processing all positions, return `res`. + ::tabs-start ```python @@ -235,6 +254,38 @@ class Solution { ## 2. Prefix & Suffix Arrays +### Intuition + +Instead of recomputing the tallest bar to the left and right for every index, we can precompute these values once. +We build two arrays: + +- `leftMax[i]` = tallest bar from the start up to index `i` +- `rightMax[i]` = tallest bar from the end up to index `i` + +Once we have these, the trapped water at position `i` is simply: + +`min(leftMax[i], rightMax[i]) - height[i]` + +This removes the repeated work from the brute-force approach and makes the solution more efficient and easier to understand. + +### Algorithm + +1. If the array is empty, return `0`. +2. Create two arrays: + - `leftMax` of size `n` + - `rightMax` of size `n` +3. Fill `leftMax`: + - `leftMax[0] = height[0]` + - For each `i` from `1` to `n - 1`, + `leftMax[i] = max(leftMax[i - 1], height[i])` +4. Fill `rightMax`: + - `rightMax[n - 1] = height[n - 1]` + - For each `i` from `n - 2` down to `0`, + `rightMax[i] = max(rightMax[i + 1], height[i])` +5. Compute trapped water: + - For each index `i`, add `min(leftMax[i], rightMax[i]) - height[i]` to the result. +6. Return the total trapped water. + ::tabs-start ```python @@ -497,6 +548,26 @@ class Solution { ## 3. Stack +### Intuition + +The stack helps us find places where water can collect. +When we see a bar that is taller than the bar on top of the stack, it means we’ve found a **right wall** for a container. +The bar we pop is the **bottom**, and the new top of the stack becomes the **left wall**. +With a left wall, bottom, and right wall, we can calculate how much water fits in between. +We keep doing this as long as the current bar keeps forming valid containers. + +### Algorithm + +1. Create an empty stack and set `res = 0`. +2. Loop through each index `i`: + - While the stack is not empty and `height[i]` is taller than the bar at the stack’s top: + - Pop the top index — that’s the **bottom**. + - If the stack is not empty: + - Compute the trapped water between the new top (left wall) and the current bar (right wall). + - Add it to `res`. + - Push the current index onto the stack. +3. Return `res` after the loop finishes. + ::tabs-start ```python @@ -746,6 +817,35 @@ class Solution { ## 4. Two Pointers +### Intuition + +Water at any position depends on the **shorter** wall between the left and right sides. +So if the left wall is shorter, the right wall can’t help us—water is limited by the left side. +That means we safely move the **left pointer** inward and calculate how much water can be trapped there. +Similarly, if the right wall is shorter, we move the **right pointer** left. + +As we move the pointers, we keep track of the highest wall seen so far on each side (`leftMax` and `rightMax`). +The water at each position is simply: + +`max wall on that side – height at that position` + +### Algorithm + +1. Set two pointers: + - `l` at the start + - `r` at the end + Track `leftMax` and `rightMax` as the tallest walls seen. +2. While `l < r`: + - If `leftMax < rightMax`: + - Move `l` right. + - Update `leftMax`. + - Add `leftMax - height[l]` to the result. + - Else: + - Move `r` left. + - Update `rightMax`. + - Add `rightMax - height[r]` to the result. +3. Return the total trapped water. + ::tabs-start ```python diff --git a/articles/two-integer-sum-ii.md b/articles/two-integer-sum-ii.md index 95727fe90..9b11d7f34 100644 --- a/articles/two-integer-sum-ii.md +++ b/articles/two-integer-sum-ii.md @@ -1,5 +1,20 @@ ## 1. Brute Force +### Intuition + +Brute force ignores the ordering and simply checks every possible pair. +For each index `i`, we look at every index `j > i` and check whether their sum equals the target. +This approach is easy to understand but inefficient because it tries all combinations without using the sorted property. + +### Algorithm + +1. Loop through the array using index `i` from `0` to `n - 1`. +2. For each `i`, loop through index `j` from `i + 1` to `n - 1`. +3. If `numbers[i] + numbers[j]` equals the target: + - Return `[i + 1, j + 1]` (1-indexed as required by the problem). +4. If no such pair is found, return an empty list. + + ::tabs-start ```python @@ -132,6 +147,25 @@ class Solution { ## 2. Binary Search +### Intuition + +Because the array is already sorted, we don’t need to check every pair. +For each number at index `i`, we know exactly what value we need to find: +`target - numbers[i]`. +Since the array is sorted, we can efficiently search for this value using **binary search** instead of scanning linearly. +This reduces the inner search from **O(n)** to **O(log n)**, making the solution much faster. + +### Algorithm + +1. Loop through each index `i` from `0` to `n - 1`. +2. For each number `numbers[i]`, compute the needed complement: + - `tmp = target - numbers[i]` +3. Perform binary search on the subarray from `i + 1` to the end: + - If `numbers[mid] == tmp`, return `[i + 1, mid + 1]` + - If `numbers[mid] < tmp`, search the right half + - Otherwise, search the left half +4. If no pair is found after checking all indices, return an empty list. + ::tabs-start ```python @@ -321,6 +355,23 @@ class Solution { ## 3. Hash Map +### Intuition + +Even though the array is sorted, we can still use a hash map to solve the problem efficiently. +As we scan through the list, we compute the needed complement for each number. +If that complement has already been seen earlier (stored in the hash map), then we have found the required pair. +Otherwise, we store the current number with its **(1-indexed)** position. + +### Algorithm + +1. Create an empty hash map `mp` that maps numbers to their 1-indexed positions. +2. Loop through the array with index `i` from `0` to `n - 1`: + - Compute the complement: `tmp = target - numbers[i]` + - If `tmp` exists in `mp`, return `[mp[tmp], i + 1]` + - Otherwise, store the current number in the map: + - `mp[numbers[i]] = i + 1` +3. If no pair is found, return an empty list. + ::tabs-start ```python @@ -464,6 +515,25 @@ class Solution { ## 4. Two Pointers +### Intuition + +Because the array is sorted, we can use two pointers to adjust the sum efficiently. +If the current sum is too big, moving the right pointer left makes the sum smaller. +If the sum is too small, moving the left pointer right makes the sum larger. +This lets us quickly close in on the target without checking every pair. + +### Algorithm + +1. Initialize two pointers: + - `l = 0` (start) + - `r = len(numbers) - 1` (end) +2. While `l < r`: + - Compute `curSum = numbers[l] + numbers[r]`. + - If `curSum > target`, move `r` left to reduce the sum. + - If `curSum < target`, move `l` right to increase the sum. + - If `curSum == target`, return `[l + 1, r + 1]`. +3. If no pair matches the target, return an empty list. + ::tabs-start ```python diff --git a/articles/valid-sudoku.md b/articles/valid-sudoku.md index e2498f6ed..81a59e798 100644 --- a/articles/valid-sudoku.md +++ b/articles/valid-sudoku.md @@ -1,5 +1,49 @@ ## 1. Brute Force +### Intuition + +A valid Sudoku board must follow three rules: + +1. Each **row** can contain digits `1–9` at most once. +2. Each **column** can contain digits `1–9` at most once. +3. Each **3×3 box** can contain digits `1–9` at most once. + +We can directly check all these conditions one by one. +For every row, every column, and every 3×3 box, we keep a set of seen digits and make sure no number appears twice. +If we ever find a duplicate in any of these three checks, the board is invalid. + +### Algorithm + +1. **Check all rows**: + - For each row index `row` from `0` to `8`: + - Create an empty set `seen`. + - For each column index `i` from `0` to `8`: + - Skip if the cell is `"."`. + - If the value is already in `seen`, return `False`. + - Otherwise, add it to `seen`. + +2. **Check all columns**: + - For each column index `col` from `0` to `8`: + - Create an empty set `seen`. + - For each row index `i` from `0` to `8`: + - Skip if the cell is `"."`. + - If the value is already in `seen`, return `False`. + - Otherwise, add it to `seen`. + +3. **Check all 3×3 boxes**: + - Number the 3×3 boxes from `0` to `8`. + - For each `square`: + - Create an empty set `seen`. + - For `i` in `0..2` and `j` in `0..2`: + - Compute: + - `row = (square // 3) * 3 + i` + - `col = (square % 3) * 3 + j` + - Skip if the cell is `"."`. + - If the value is already in `seen`, return `False`. + - Otherwise, add it to `seen`. + +4. If all rows, columns, and 3×3 boxes pass these checks without duplicates, return `True`. + ::tabs-start ```python @@ -334,6 +378,45 @@ class Solution { ## 2. Hash Set (One Pass) +### Intuition + +Instead of checking rows, columns, and 3×3 boxes separately, we can validate the entire Sudoku board in **one single pass**. +For each cell, we check whether the digit has already appeared in: + +1. the same **row** +2. the same **column** +3. the same **3×3 box** + +We track these using three hash sets: +- `rows[r]` keeps digits seen in row `r` +- `cols[c]` keeps digits seen in column `c` +- `squares[(r // 3, c // 3)]` keeps digits in the 3×3 box + +If a digit appears again in any of these places, the board is invalid. + +### Algorithm + +1. Create three hash maps of sets: + - `rows` to track digits in each row + - `cols` to track digits in each column + - `squares` to track digits in each 3×3 sub-box, keyed by `(r // 3, c // 3)` + +2. Loop through every cell in the board: + - Skip the cell if it contains `"."`. + - Let `val` be the digit in the cell. + - If `val` is already in: + - `rows[r]` → duplicate in the row + - `cols[c]` → duplicate in the column + - `squares[(r // 3, c // 3)]` → duplicate in the 3×3 box + Then return `False`. + +3. Otherwise, add the digit to all three sets: + - `rows[r]` + - `cols[c]` + - `squares[(r // 3, c // 3)]` + +4. If the whole board is scanned without detecting duplicates, return `True`. + ::tabs-start ```python @@ -593,6 +676,44 @@ class Solution { ## 3. Bitmask +### Intuition + +Every digit from `1` to `9` can be represented using a single bit in an integer. +For example, digit `1` uses bit `0`, digit `2` uses bit `1`, …, digit `9` uses bit `8`. +This means we can track which digits have appeared in a row, column, or 3×3 box using just **one integer per row/column/box** instead of a hash set. + +When we encounter a digit, we compute its bit position and check: +- if that bit is already set in the row → duplicate in row +- if that bit is already set in the column → duplicate in column +- if that bit is already set in the box → duplicate in box + +If none of these checks fail, we “turn on” that bit to mark the digit as seen. +This approach is both memory efficient and fast. + +### Algorithm + +1. Create three arrays of size 9: + - `rows[i]` stores bits for digits seen in row `i` + - `cols[i]` stores bits for digits seen in column `i` + - `squares[i]` stores bits for digits seen in 3×3 box `i` + +2. Loop through each cell `(r, c)` of the board: + - Skip if the cell contains `"."`. + - Convert the digit to a bit index: `val = int(board[r][c]) - 1`. + - Compute the mask: `mask = 1 << val`. + +3. Check for duplicates: + - If `mask` is already set in `rows[r]`, return `False`. + - If `mask` is already set in `cols[c]`, return `False`. + - If `mask` is already set in `squares[(r // 3) * 3 + (c // 3)]`, return `False`. + +4. Mark the digit as seen: + - `rows[r] |= mask` + - `cols[c] |= mask` + - `squares[(r // 3) * 3 + (c // 3)] |= mask` + +5. If all cells are processed without conflicts, return `True`. + ::tabs-start ```python