Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions articles/anagram-groups.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
55 changes: 55 additions & 0 deletions articles/duplicate-integer.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions articles/is-anagram.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
38 changes: 38 additions & 0 deletions articles/is-palindrome.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
95 changes: 95 additions & 0 deletions articles/longest-consecutive-sequence.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading