Simple Two Pointers

The Simple Two Pointers technique is commonly used to solve problems involving sorted array, where a set of elements needs to be found that satisfy certain constrains. This approach is efficient in scenarios such as finding a pair that sums to a target or reversing an array or string. In the two simple two pointers technique, two pointers – typically labeled “left” and “right” – are initialized and move in a specific direction based on the problem’s requirements.

345. Reverse Vowels of a String

SHOW PROBLEM

Problem Statement

Given a string s, reverse only the vowels in the string and return the modified string. The vowels are ‘a’, ’e’, ‘i’, ‘o’, ‘u’ (both lowercase and uppercase). They may appear multiple times in s.

Example 1:

  • Input: s = "IceCreAm"
  • Output: "AceCreIm"
  • Explanation: The vowels in s are ['I', 'e', 'e', 'A']. After reversing their order, s becomes "AceCreIm".

Example 2:

  • Input: s = "leetcode"
  • Output: "leotcede"

Constraints:

  • 1 <= s.length <= 3 * 10⁵
  • s consists of printable ASCII characters.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public String reverseVowels(String s) {
        String vowels = "aeiouAEIOU";
        char[] chars = s.toCharArray();

        int left = 0, right = s.length() - 1;
        while (left < right) {
            // Move left pointer to find the next vowel
            while (left < right && vowels.indexOf(chars[left]) == -1) {
                left++;
            }

            // Move right pointer to find the previous vowel
            while (left < right && vowels.indexOf(chars[right]) == -1) {
                right--;
            }

            // Swap vowels
            char temp = chars[left];
            chars[left] = chars[right];
            chars[right] = temp;
            left++;
            ;
            right--;
        }

        return new String(chars);
    }
}

SHOW NOTES

Visulization:

Reverse Vowels


167. Two Sum II - Input Array Is Sorted

SHOW PROBLEM

Problem Statement

Given a 1-indexed array of integers, numbers, sorted in non-decreasing order, find two numbers that add up to a specific target. Let these two numbers be numbers[index1] and numbers[index2], where 1 <= index1 < index2 <= numbers.length.

Return the indices of the two numbers, index1 and index2, incremented by one, as an integer array [index1, index2] of length 2.

  • The problem guarantees that there is exactly one solution.
  • You may not use the same element twice.
  • Your solution must use only constant extra space.

Example 1:

  • Input: numbers = [2,7,11,15], target = 9
  • Output: [1, 2]
  • Explanation: The sum of 2 and 7 is 9, so index1 = 1, index2 = 2. We return [1, 2].

Example 2:

  • Input: numbers = [2,3,4], target = 6
  • Output: [1, 3]
  • Explanation: The sum of 2 and 4 is 6, so index1 = 1, index2 = 3. We return [1, 3].

Example 3:

  • Input: numbers = [-1,0], target = -1
  • Output: [1, 2]
  • Explanation: The sum of -1 and 0 is -1, so index1 = 1, index2 = 2. We return [1, 2].

Constraints:

  • 2 <= numbers.length <= 30,000
  • -1000 <= numbers[i] <= 1000
  • numbers is sorted in non-decreasing order.
  • -1000 <= target <= 1000
  • There is exactly one solution for each test case.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0, right = numbers.length - 1;

        // Iterate while left pointer is before right pointer
        while (left < right) {
            int currentSum = numbers[left] + numbers[right];

            // Check if the current sum matches the target
            if (currentSum == target) {
                break;
            }

            // Adjust the pointers to move towards the target sum
            if (currentSum < target) { 
                left++; // Increase sum by moving left pointer right
            } else { 
                right--; // Decrease sum by moving right pointer left
            }
        }

        // Return 1-indexed result
        return new int[] { left + 1, right + 1 }; 
    }
}

SHOW NOTES

Algorithm Walkthrough

  • nums = [1, 2, 3, 4, 5, 8]
  • target = 7
  1. Initialize pointers: left = 0, right = 5
  2. First iteration:
    • currentSum = 1 + 8 = 9 (greater than the target)
    • Decrease the right pointer to 4
  3. Second iteration:
    • currentSum = 1 + 5 = 6 (less than the target)
    • Increase the left pointer to 1
  4. Third iteration:
    • currentSum = 2 + 4 = 6 (equals the target)
    • Return the indices [1, 3].

Visualization

Two Sum II - Input Array Is Sorted


977. Squares of a Sorted Array

SHOW PROBLEM

Problem Statement

You are given an integer array nums sorted in non-decreasing order. Your task is to return an array of the squares of each number, sorted in non-decreasing order.

Example 1:

  • Input: nums = [-4,-1,0,3,10]
  • Output: [0,1,9,16,100]
  • Explanation: After squaring, the array becomes [16,1,0,9,100]. After sorting, it becomes [0,1,9,16,100].

Example 2:

  • Input: nums = [-7,-3,2,3,11]
  • Output: [4,9,9,49,121]

Constraints:

  • 1 <= nums.length <= 10^4
  • -10^4 <= nums[i] <= 10^4
  • nums is sorted in non-decreasing order.

Follow-up:
Squaring each element and sorting the new array is a trivial solution with a time complexity of O(n log n). Can you find a solution with O(n) time complexity using a different approach?


Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[] sortedSquares(int[] nums) {
        int n = nums.length;
        int left = 0, right = nums.length - 1;
        int[] squares = new int[n];

        // Traverse the array from both ends to calculate squares in descending order
        while (left <= right) {
            if (Math.abs(nums[left]) <= Math.abs(nums[right])) {
                // Place the square of the right element at the current position
                squares[--n] = nums[right] * nums[right];
                right--;
            } else {
                // Place the square of the left element at current position
                squares[--n] = nums[left] * nums[left];
                left++;
            }
        }

        return squares;
    }
}

SHOW NOTES

Visulization:

Squares of a Sorted Array


283. Move Zeroes

SHOW PROBLEM

Problem Statement

You are given an integer array nums. Your task is to move all 0’s to the end of the array while maintaining the relative order of the non-zero elements.

You must perform this operation in-place, without making a copy of the array.

Example 1:

  • Input: nums = [0,1,0,3,12]
  • Output: [1,3,12,0,0]

Example 2:

  • Input: nums = [0]
  • Output: [0]

Constraints:

  • 1 <= nums.length <= 10^4
  • -2^{31} <= nums[i] <= 2^{31} - 1

Follow-up:
Can you minimize the total number of operations performed?


Go to Leetcode 🔗
SHOW CODE
class Solution {
    public void moveZeroes(int[] nums) {
        int k = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                int temp = nums[i];
                nums[i] = nums[k];
                nums[k++] = temp;
            }
        }       
    }
}

27. Remove Element

SHOW PROBLEM

Problem Statement

You are given an integer array nums and an integer val. Your task is to remove all occurrences of val in nums in-place. The order of the elements may be changed. Then, return the number of elements in nums which are not equal to val.

Consider the number of elements in nums which are not equal to val to be k. To get accepted, you need to do the following things:

  1. Change the array nums such that the first k elements of nums contain the elements which are not equal to val. The remaining elements of nums are not important, and their values don’t need to be considered.
  2. Return k.

Custom Judge:

Example 1:

  • Input: nums = [3,2,2,3], val = 3
  • Output: 2, nums = [2,2,_,_]
  • Explanation: Your function should return k = 2, with the first two elements of nums being 2. It does not matter what you leave beyond the returned k (hence they are underscores).

Example 2:

  • Input: nums = [0,1,2,2,3,0,4,2], val = 2
  • Output: 5, nums = [0,1,4,0,3,_,_,_]
  • Explanation: Your function should return k = 5, with the first five elements of nums containing [0,1,4,0,3]. Note that the five elements can be returned in any order. It does not matter what you leave beyond the returned k (hence they are underscores).

Constraints:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int removeElement(int[] nums, int val) {
        int k = 0;

        // Traverse the array to move the elements that are not equal to the 'val' to the front
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != val) {
                // If the current element is not equal to the 'val', place it at the next available position
                nums[k++] = nums[i];
            }
        }

        // Return the length of the array after removing all elements
        return k;
    }
}

26. Remove Duplicates from Sorted Array

SHOW PROBLEM

Problem Statement

You are given an integer array nums sorted in non-decreasing order. Your task is to remove the duplicates in-place such that each unique element appears only once. The relative order of the elements should be kept the same. Then, return the number of unique elements in nums.

Consider the number of unique elements in nums to be k. To get accepted, you need to do the following things:

  1. Change the array nums such that the first k elements of nums contain the unique elements in the order they were present in nums initially.
  2. The remaining elements of nums are not important and can be ignored.
  3. Return k.

Example 1:

  • Input: nums = [1,1,2]
  • Output: 2, nums = [1,2,_]
  • Explanation: Your function should return k = 2, with the first two elements of nums being 1 and 2 respectively. It does not matter what you leave beyond the returned k (hence they are underscores).

Example 2:

  • Input: nums = [0,0,1,1,1,2,2,3,3,4]
  • Output: 5, nums = [0,1,2,3,4,_,_,_,_,_]
  • Explanation: Your function should return k = 5, with the first five elements of nums being [0,1,2,3,4]. It does not matter what you leave beyond the returned k (hence they are underscores).

Constraints:

  • 1 <= nums.length <= 3 * 10^4
  • -100 <= nums[i] <= 100
  • nums is sorted in non-decreasing order.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int removeDuplicates(int[] nums) {
        // Track the position for the next unique element
        int nextUnique = 1;
        for (int i = 1; i < nums.length; i++) {
            // If the current element is different from the previous unique element
            if (nums[nextUnique - 1] != nums[i]) {
                // Move the current element to the next unique position
                nums[nextUnique++] = nums[i];
            }
        }

        return nextUnique;
    }
}

SHOW NOTES

Visulization:

Remove Duplicates From Duplicate Array


88. Merge Sorted Array

SHOW PROBLEM

Problem Statement

You are given two integer arrays nums1 and nums2, both sorted in non-decreasing order, and two integers m and n, which represent the number of elements in nums1 and nums2, respectively.

Your task is to merge nums1 and nums2 into a single array sorted in non-decreasing order. However, the merged array should not be returned by the function. Instead, it should be stored inside the array nums1. To accommodate this, nums1 has a length of m + n, where the first m elements represent the elements to be merged, and the last n elements are set to 0 and should be ignored. nums2 has a length of n.

Example 1:

  • Input: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
  • Output: [1,2,2,3,5,6]
  • Explanation:
    • The arrays we are merging are [1,2,3] and [2,5,6]. The result of the merge is [1,2,2,3,5,6] with the underlined elements coming from nums1.

Example 2:

  • Input: nums1 = [1], m = 1, nums2 = [], n = 0
  • Output: [1]
  • Explanation:
    • The arrays we are merging are [1] and []. The result of the merge is [1].

Example 3:

  • Input: nums1 = [0], m = 0, nums2 = [1], n = 1
  • Output: [1]
  • Explanation:
    • The arrays we are merging are [] and [1]. The result of the merge is [1].
    • Note that because m = 0, there are no elements in nums1. The 0 is only there to ensure the merge result can fit in nums1.

Constraints:

  • nums1.length == m + n
  • nums2.length == n
  • 0 <= m, n <= 200
  • 1 <= m + n <= 200
  • -10^9 <= nums1[i], nums2[j] <= 10^9

Follow up: Can you come up with an algorithm that runs in O(m + n) time?


Go to Leetcode 🔗
SHOW CODE
class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m - 1, j = n - 1;
        int k = m + n - 1;

        // Merge nums1 and nums2 from the end
        while (i >= 0 && j >= 0) {
            if (nums1[i] > nums2[j]) {
                nums1[k--] = nums1[i--];
            } else {
                nums1[k--] = nums2[j--];
            }
        }

        // If there are remaining elements in nums2, copy them to nums1
        while (j >= 0) {
            nums1[k--] = nums2[j--];
    }

        // If there are remaining elements in nums1 (i >= 0), they are already sorted
    }
}

15. 3Sum

SHOW PROBLEM

Problem Statement

Given an integer array nums, return all unique triplets [nums[i], nums[j], nums[k]] such that:

  • i != j, i != k, and j != k
  • nums[i] + nums[j] + nums[k] == 0

The solution set must not contain duplicate triplets.


Example 1:

  • Input: nums = [-1, 0, 1, 2, -1, -4]
  • Output: [[-1, -1, 2], [-1, 0, 1]]
  • Explanation:
    • nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0
    • nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0
    • nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0

The distinct triplets are [-1, 0, 1] and [-1, -1, 2].

Note: The order of the output and the order of the triplets does not matter.

Example 2:

  • Input: nums = [0, 1, 1]
  • Output: []
  • Explanation: The only possible triplet does not sum up to 0.

Example 3:

  • Input: nums = [0, 0, 0]
  • Output: [[0, 0, 0]]
  • Explanation: The only possible triplet sums up to 0.

Constraints:

  • 3 <= nums.length <= 3000
  • 10^5 <= nums[i] <= 10^5

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public List threeSum(int[] nums) {
        List triplets = new ArrayList<>();

        // Sort the array to enable the two-pointer approach
        Arrays.sort(nums);

        for (int i = 0; i < nums.length - 2; i++) {
            // Skip duplicates for 'i' to ensure unique triplets
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            // Find pairs that sum up to the negative of the current number
            findTwoSum(nums, -nums[i], i + 1, nums.length - 1, triplets);
        }

        return triplets;
    }

    private static void findTwoSum(int[] nums, int target, int left, int right, List triplets) {
        while (left < right) {
            int currentSum = nums[left] + nums[right];
            if (target == currentSum) {
                triplets.add(Arrays.asList(-target, nums[left], nums[right]));
                left++;
                right--;

                // Skip duplicates for 'left' and 'right' to avoid duplicate triplets
                while (left < right && nums[left] == nums[left - 1]) {
                    left++;
                }
                while (left < right && nums[right] == nums[right + 1]) {
                    right--;
                }
            } else if (currentSum < target) {
                // Move 'left' pointer to the right to increase the sum
                left++;
            } else {
                // Move 'right' pointer to the left to decrease the sum
                right--;
            }
        }
    }
}

SHOW NOTES

Removing Duplicates: To ensure the uniqueness of the triplets, it is essential to skip duplicates during the target determination process. For example, in a nums array [-1, -1, 0, 0, 1, 1], the same element (-1) should be skipped in the for loop to find a different triplet. Similarly, when finding the target sum, duplicates for the left [0, 0] and right [1, 1] pointers must be skipped to avoid adding duplicate triplets.

Algorithm Walkthrough:

  1. Initialization: [2, -1, -2, 3, -5]
  2. Sort the Array:
    • Input: [2, -1, -2, 3, -5]
    • Sorted: [-5, -2, -1, 2, 3]
  3. Iteration:
    • Iteration 1:
      • Fix -5 (at index 0), left pointer at -2 (index 1), right pointer at 3 (index 4):
      • Target Sum: -(-5) = 5
      • Sum = -2 + 3 = 1 (less than targetSum), move left pointer to -1 (index 2)
      • Sum = -1 + 3 = 2 (less than targetSum), move left pointer to 2 (index 3)
      • Sum = 2 + 3 = 5, found triplet [-5, 2, 3]
      • Move left pointer to 4 and right pointer to 3 (end of this iteration).
    • Iteration 2:
      • Fix -2 (at index 1), left pointer at -1 (index 2), right pointer at 3 (index 4):
      • Target Sum: -(-2) = 2
      • Sum = -1 + 3 = 2, found triplet [-2, -1, 3]
      • Move left pointer to 2 and right pointer to 3 (end of this iteration).
    • Iteration 3: Fix -1 (at index 2), left pointer at 2 (index 3), right pointer at 3 (index 4):
      • Target Sum: -(-1) = 1
      • Sum = 2 + 3 = 5 (greater than targetSum), move right pointer to 2 (end of this iteration).
    • End of Loop: All elements are processed.
  4. Result: [[ -5, 2, 3], [ -2, -1, 3]]

Visulization:

3Sum


11. Container With Most Water

SHOW PROBLEM

Problem Statement

You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the i-th line are (i, 0) and (i, height[i]).

Your task is to 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 the container can store.

Notice: You may not slant the container.

Example 1: Container With Most Water

  • Input: height = [1,8,6,2,5,4,8,3,7]
  • Output: 49
  • Explanation: The vertical lines are represented by the array [1,8,6,2,5,4,8,3,7]. In this case, the maximum area of water the container can contain is 49.

Example 2:

  • Input: height = [1,1]
  • Output: 1
  • Explanation: The container formed by lines at indices 0 and 1 can only hold 1 unit of water.

Constraints:

  • n == height.length
  • 2 <= n <= 10^5
  • 0 <= height[i] <= 10^4

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int maxArea(int[] height) {
        int left = 0, right = height.length - 1;

        int maxVolume = 0;

        while (left < right) {
            // Calculate the width and height of the current container
            int width = right - left;
            int heightOfContainer = Math.min(height[left], height[right]);

            // Update the maximum volume if the current one is larger
            maxVolume = Math.max(maxVolume, width * heightOfContainer);

            // Move the pointer pointing to the shorter height to find a potentially larger height
            if (height[left] <= height[right]) {
                left++;
            } else {
                right--;
            }
        }

        return maxVolume;
    }
}

75. Sort Colors

SHOW PROBLEM

Problem Statement

You are given an array nums with n objects colored red, white, or blue. Sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white, and blue.

We will use the integers 0, 1, and 2 to represent the color red, white, and blue, respectively.

Your task is to sort the array without using the library’s sort function.

Example 1:

  • Input: nums = [2,0,2,1,1,0]
  • Output: [0,0,1,1,2,2]
  • Explanation: After sorting, the array should have all 0s (red) first, then 1s (white), followed by 2s (blue).

Example 2:

  • Input: nums = [2,0,1]
  • Output: [0,1,2]
  • Explanation: After sorting, the array should be [0, 1, 2].

Constraints:

  • n == nums.length
  • 1 <= n <= 300
  • nums[i] is either 0, 1, or 2.

Follow-up: Can you come up with a one-pass algorithm that uses only constant extra space?


Go to Leetcode 🔗
SHOW CODE
class Solution {
    public void sortColors(int[] nums) {
        int left = 0, right = nums.length - 1;
        int i = 0;
        while (i <= right) {
            if (nums[i] == 0) {
                // Swap 0 to the left side and move both 'left' and 'i' pointer
                swap(nums, i, left);
                left++;
                i++;
            } else if (nums[i] == 1) {
                i++;
            } else {
                // Swap 2 to the right side and move the 'right' pointer
                swap(nums, i, right);
                right--;
            }
        }
    }

    private static void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

SHOW NOTES

Visulization:

Sort Colors


Fast & Slow Pointers

The Fast and Slow Pointers pattern, also known as the Tortoise and Hare Algorithm, invloves using two pointers that move at different speeds over an array or linked list. It is commonly used to solve problems related to cycle detection or finding the middle element in linked lists or arrays.

Linked List Cycle

SHOW PROBLEM

Problem Statement

Given the head of a singly linked list, write a function to determine whether the list contains a cycle.

Liked List Cycle

Constraints:

  • The number of nodes in the list is between 0 and 10,000.
  • Node values are in the range of [-10^5, 10^5].

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        // Initialize slow and fast pointers at the head of the list
        ListNode slow = head, fast = head;

        // Traverse the list with fast and slow pointers
        while (fast != null && fast.next != null) {
            // Move slow pointer by one step and fast pointer by two steps
            slow = slow.next;
            fast = fast.next.next;

            // If the two pointers meet, a cycle is detected
            if (slow == fast) {
                return true;
            }
        }

        // If fast pointer reaches the end, there's no cycle
        return false;
    }
}

SHOW NOTES

The fast and slow pointers start from the same point. The fast pointer moves two steps at a time, while the slow pointer moves one step at a time. If the linked list has no cycle, the fast pointer will reach the end before the slow pointer. If the linked list has a cycle, the fast pointer will enter the cycle first, and both pointers will continue moving in the cycle. Eventually, they will meet at some point, indicating the presence of a cycle. There are two possible scenerios when both pointers are in the cycle:

  1. The fast pointer is one step behind the slow pointer.
  2. The fast pointer is two steps behind the slow pointer.

In scenerio 1, after the next move, the two pointers will meet at the same position. This is the condition that confirms the presence of a cycle, as shown in the following illustration.

Linked List Cycle: Slow &amp; Fast Pointer


Middle of the LinkedList

SHOW PROBLEM

Problem Statement

Given the head of a singly linked list, write a method to return the middle node of the linked list.

If the total number of nodes in the list is even, return the second middle node.

Example 1:

  • Input: 1 → 2 → 3 → 4 → 5 → null
  • Output: 3

Example 2:

  • Input: 1 → 2 → 3 → 4 → 5 → 6 → null
  • Output: 4

Example 3:

  • Input: 1 → 2 → 3 → 4 → 5 → 6 → 7 → null
  • Output: 4

Constraints:

  • The number of nodes in the list is between 1 and 100.
  • Node values are between 1 and 100.

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode middleNode(ListNode head) {
        // Initialize slow and fast pointers at the head of the list
        ListNode slow = head, fast = head;
        
        while (fast != null && fast.next != null) {
            slow = slow.next;          // Move slow pointer by one step
            fast = fast.next.next;     // Move fast pointer by two steps
        }

        // When fast pointer reaches the end, slow pointer will be at the middle
        return slow;
    }
}

SHOW NOTES

Middle of Linked List


If the total number of nodes in the list is even, and return the first middle node is required, then

while (fast != null && fast.next != null) {
    fast = fast.next.next;     // Move fast pointer by two steps
    if (fast != null) {
        break;
    }
    slow = slow.next;          // Move slow pointer by one step
}


Remove Nth Node From End of List

SHOW PROBLEM

Problem Statement

Given the head of a singly linked list, remove the nth node from the end of the list and return the head of the updated list.

Example 1:

Remove Nth Node From End of List

  • Input: head = [1, 2, 3, 4, 5], n = 2
  • Output: [1, 2, 3, 5]

Example 2:

  • Input: head = [1], n = 1
  • Output: []

Example 3:

  • Input: head = [1, 2], n = 1
  • Output: [1]

Constraints:

  • The number of nodes in the list is between 1 and 10^5.
  • 1 <= n <= the number of nodes in the list.

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode() {}
 * ListNode(int val) { this.val = val; }
 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // Create a dummy node to handle edge cases (e.g., removing the head)
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        // Initialize slow and fast pointers at the dummy node
        ListNode slow = dummy, fast = dummy;

        // Move the fast pointer n+1 steps ahead from the dummy node
        for (int i = 0; i <= n; i++) {
            fast = fast.next;
        }

        // Move both slow and fast pointers one step at a time until fast reaches the
        // end
        while (fast != null) {
            slow = slow.next;
            fast = fast.next;
        }

        // Remove the nth node from the end by skipping it
        slow.next = slow.next.next;

        // Return the updated head
        return dummy.next;
    }
}

SHOW NOTES

The goal is to remove the nth node from the end of the list. The first step in solving this problem is to determine the position of the nth node from the end. This can be achieved using the slow and fast pointers techique. Initially, two pointer, slow and fast, are both set to the head of the list. The fast pointer is then moved n steps ahead from the head node.

Once the fast pointer is n steps ahead, both the slow and fast pointers move one step at a time simultaneously. When the fast pointer reaches the end of the list, the slow pointer will be positioned at the nth node from the end. Since the task is to remove this node, the node before it must be idetified.

However, handling the edge case where the node to be removed is the head node requires special attention. To address this issue, a dummy node is introduced. The dummy node points to the head of the list, ensuring that there is always a valid node before the one to be deleted, even if the head node itself is the target.

To find the node just before the nth node from the end, the fast pointer is moved n+1 steps ahead from the dummy node. This places the fast pointer n+1 steps ahead of the slow pointer. Both pointers are then moved one step at a time until the fast pointer reaches the end of the list. At this point, the slow pointer will be positioned just before the nth node from the end.

The removal of the nth node is accomplished by updating the slow pointer’s next reference slow.next = slow.next.next;. When the node to be removed is the head, the slow pointer remains at the dummy node without moving. After executing slow.next = slow.next.next;, the node after the dummy node becomes the new head of the list.


Illustration

Remove Nth Node From End of List


Illustration: Remove Head Node

Remove Nth Node From End of List: Remove Head Node


Sliding Window

The Sliding Window Pattern is a technique used to solve problems involving arrays or sequences when computing something over a continguous subarray or subsequence is required. There are two types of sliding window: fixed-size and dynamic. The fixed-size window involves calculating something over a contiguous subarray of a given size, while the dynamic window adjusts its size based on specific constraints to handle problems over a contiguous subarray or subsequence. The key to solving sliding window problems is determining when to shrink the window size.

Maximum Sum Subarray of Size K

SHOW PROBLEM

Problem Statement

Given an array of positive numbers and a positive integer k, find the maximum sum of any contiguous subarray of size k.


Example 1:

  • Input:
    arr = [2, 1, 5, 1, 3, 2], k = 3

  • Output:
    9

  • Explanation:
    The subarray with the maximum sum is [5, 1, 3], which has a sum of 9.

Example 2:

  • Input:
    arr = [2, 3, 4, 1, 5], k = 2

  • Output:
    7

  • Explanation:
    The subarray with the maximum sum is [3, 4], which has a sum of 7.


SHOW CODE
public class Solution {
    public int findMaxSumSubArray(int k, int[] arr) {
        int windowStart = 0;
        int windowSum = 0, maxSum = Integer.MIN_VALUE;
        for (int windowEnd = 0; windowEnd < arr.length; windowEnd++) {
            windowSum += arr[windowEnd]; // Update the window sum

            // Iterate the array to expand the window
            if (windowEnd >= k - 1) {
                maxSum = Math.max(windowSum, maxSum); // Update the maximum sum
                windowSum -= arr[windowStart]; // Remove the element going out of the window
                windowStart++; // Shink the window
            }
        }

        return maxSum;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println("Maximum sum of a subarray of size K: "
                + sol.findMaxSumSubArray(3, new int[]{2, 1, 5, 1, 3, 2}));
        System.out.println("Maximum sum of a subarray of size K: "
                + sol.findMaxSumSubArray(2, new int[]{2, 3, 4, 1, 5}));
    }
}
SHOW OUTPUT
Maximum sum of a subarray of size K: 9
Maximum sum of a subarray of size K: 7

SHOW NOTES

Algorithm Walkthrough

  • arr = [2, 1, 5, 1, 3, 2]
  • k = 3
  1. Initialization:
    • windowSum = 0
    • maxSum = 0
    • windowStart = 0
  2. Iteration:
    • windowEnd = 0: Add arr[0] (2) to windowSum: windowSum = 2
    • windowEnd = 1: Add arr[1] (1) to windowSum: windowSum = 3
    • windowEnd = 2: Add arr[2] (5) to windowSum: windowSum = 8
      • Since windowEnd >= k - 1:
      • Update maxSum = Math.max(0, 8) = 8
      • Subtract arr[0] = 2 from windowSum: windowSum = 6
      • Increment windowStart: windowStart = 1
    • windowEnd = 3: Add arr[3] (1) to windowSum: windowSum = 7
      • Since windowEnd >= k - 1:
      • Update maxSum = Math.max(8, 7) = 8
      • Subtract arr[1] = 1 from windowSum: windowSum = 6
      • Increment windowStart: windowStart = 2
    • windowEnd = 4: Add arr[4] (3) to windowSum: windowSum = 9
      • Since windowEnd >= k - 1:
      • Update maxSum = Math.max(8, 9) = 9
      • Subtract arr[2] = 5 from windowSum: windowSum = 4
      • Increment windowStart: windowStart = 3
    • windowEnd = 5: Add arr[5] (2) to windowSum: windowSum = 6
      • Since windowEnd >= k - 1:
      • Update maxSum = Math.max(9, 6) = 9
      • Subtract arr[3] = 1 from windowSum: windowSum = 5
      • Increment windowStart: windowStart = 4
  3. Result: The final maxSum is 9, which is the maximum sum of any subarray of size k = 3.

Visualization

Maximum Sum Subarray of Size K


1052. Grumpy Bookstore Owner

SHOW PROBLEM

Problem Statement

A bookstore owner operates their store for n minutes. You are given an integer array customers of length n, where customers[i] represents the number of customers entering the store at the start of the i-th minute. All these customers leave after the end of that minute.

Additionally, you are given a binary array grumpy of length n, where grumpy[i] is 1 if the owner is grumpy during the i-th minute and 0 otherwise.

  • If the owner is not grumpy, all customers entering that minute are satisfied.
  • If the owner is grumpy, all customers entering that minute are not satisfied.

The owner has a special technique that allows them to remain not grumpy for minutes consecutive minutes, but this technique can only be used once.

Your task is to return the maximum number of satisfied customers that can be achieved throughout the day.

Example 1:

  • Input: customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], minutes = 3
  • Output: 16
  • Explanation:
    • The owner uses their technique for the last 3 minutes ([7, 5]).
    • The maximum number of satisfied customers = 1 + 1 + 1 + 1 + 7 + 5 = 16.

Example 2:

  • Input: customers = [1], grumpy = [0], minutes = 1
  • Output: 1
  • Explanation: The owner is never grumpy, so all customers are satisfied.

Constraints:

  • n == customers.length == grumpy.length
  • 1 <= minutes <= n <= 2 * 10^4
  • 0 <= customers[i] <= 1000
  • grumpy[i] is either 0 or 1.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int maxSatisfied(int[] customers, int[] grumpy, int minutes) {
        int totalSatisfied = 0;
        // Calculate initially satisfied customers
        for (int i = 0; i < customers.length; i++) {
            if (grumpy[i] == 0) {
                totalSatisfied += customers[i];
            }
        }

        int windowStart = 0;
        int maxAdditionalSatisfied = 0;
        int windowSatisfied = 0;
        for (int windowEnd = 0; windowEnd < customers.length; windowEnd++) {
            // Add the new element to the sliding window if the bookstore owner is grumpy
            if (grumpy[windowEnd] == 1) {
                windowSatisfied += customers[windowEnd];
            }

            // Matain a window of size 'minutes'
            if (windowEnd - windowStart + 1 > minutes) {
                if (grumpy[windowStart] == 1) {
                    windowSatisfied -= customers[windowStart];
                }
                windowStart++;
            }

            maxAdditionalSatisfied = Math.max(maxAdditionalSatisfied, windowSatisfied);
        }

        return totalSatisfied + maxAdditionalSatisfied;
    }
}

340. Longest Substring with At Most K Distinct Characters

SHOW PROBLEM

Problem Statement

Given a string, find the length of the longest substring that contains no more than K distinct characters.

It is guaranteed that K will be less than or equal to the length of the given string.


Example 1:

  • Input: String = "araaci", K = 2
  • Output: 4
  • Explanation: The longest substring with no more than 2 distinct characters is "araa".

Example 2:

  • Input: String = "araaci", K = 1
  • Output: 2
  • Explanation: The longest substring with no more than 1 distinct character is "aa".

Example 3:

  • Input: String = "cbbebi", K = 3
  • Output: 5
  • Explanation: The longest substrings with no more than 3 distinct characters are "cbbeb" and "bbebi".

Constraints:

  • 1 <= str.length <= 50,000
  • 0 <= K <= 50

🔒 Go to Leetcode 🔗
SHOW CODE
import java.util.HashMap;
import java.util.Map;

class Solution {
    public int findLength(String str, int k) {
        Map<Character, Integer> charFrequencyMap = new HashMap<>();
        int windowStart = 0;
        int maxLength = 0;

        // Iterate the char sequence to expand the window
        for (int windowEnd = 0; windowEnd < str.length(); windowEnd++) {
            char endChar = str.charAt(windowEnd);
            charFrequencyMap.put(endChar, charFrequencyMap.getOrDefault(endChar, 0) + 1);

            // Shrink the window if there are more than 'k' distinct characters
            while (charFrequencyMap.size() > k) {
                char startChar = str.charAt(windowStart);
                charFrequencyMap.put(startChar, charFrequencyMap.get(startChar) - 1);
                if (charFrequencyMap.get(startChar) == 0) {
                    charFrequencyMap.remove(startChar);
                }
                windowStart++; // Shrink the window
            }

            // Update the maximum length of the valid substring found
            maxLength = Math.max(maxLength, windowEnd - windowStart + 1);
        }

        return maxLength;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println("Length of the longest substring: "
                + sol.findLength("araaci", 2));
        System.out.println("Length of the longest substring: "
                + sol.findLength("araaci", 1));
        System.out.println("Length of the longest substring: "
                + sol.findLength("cbbebi", 3));
    }
}

SHOW NOTES

Algorithm Walkthrough:

  • str = "araaci"
  • K = 2
  1. Initialize: windowStart = 0, windowEnd = 0
  2. Iteration:
    • windowEnd = 0
      • Add 'a' to the hash map: {'a': 1}
      • Maximum length = 1
    • windowEnd = 1
      • Add 'r' to the hash map: {'a': 1, 'r': 1}
      • Maximum length = 2
    • windowEnd = 2
      • Add 'a' to the hash map: {'a': 2, 'r': 1}
      • Maximum length = 3
    • windowEnd = 3
      • Add 'a' to the hash map: {'a': 3, 'r': 1}
      • Maximum length = 4
    • windowEnd = 4
      • Add 'c' to the hash map: {'a': 3, 'r': 1, 'c': 1}
      • Since the hash map has more than K = 2 distinct characters, shrink the window from the left:
      • Remove one 'a': {'a': 2, 'r': 1, 'c': 1}
      • Move windowStart = 1
      • Remove one 'r': {'a': 2, 'c': 1}
      • Move windowStart = 2
      • Maximum length = 4
    • windowEnd = 5
      • Add 'i' to the hash map: {'a': 2, 'c': 1, 'i': 1}
      • Since the hash map has more than K = 2 distinct characters, shrink the window from the left:
      • Remove one 'a': {'a': 1, 'c': 1, 'i': 1}
      • Move windowStart = 3
      • Remove one 'a': {'c': 1, 'i': 1}
      • Move windowStart = 4
      • Maximum length = 4
  3. Result: The maximum length of the substring with at most 2 distinct characters is 4.

Visulization

Longest Substring with At Most K Distinct Characters


3. Longest Substring Without Repeating Characters

SHOW PROBLEM

Problem Statement

Given a string s, find the length of the longest substring without duplicate characters.

Example 1:

  • Input: s = "abcabcbb"
  • Output: 3
  • Explanation: The longest substring without repeating characters is "abc", which has a length of 3.

Example 2:

  • Input: s = "bbbbb"
  • Output: 1
  • Explanation: The longest substring without repeating characters is "b", which has a length of 1.

Example 3:

  • Input: s = "pwwkew"
  • Output: 3
  • Explanation: The longest substring without repeating characters is "wke", which has a length of 3.

Note: "pwke" is a subsequence, not a substring.

Constraints:

  • 0 <= s.length <= 5 * 10^4
  • s consists of English letters, digits, symbols, and spaces.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int lengthOfLongestSubstring(String s) {
        Set<Character> windowSet = new HashSet<>();

        int windowStart = 0, maxLength = 0;
        for (int windowEnd = 0; windowEnd < s.length(); windowEnd++) {
            char endChar = s.charAt(windowEnd);
            
            // If encounter a duplicate, shrink the window
            while (windowSet.contains(endChar)) {
                windowSet.remove(s.charAt(windowStart));
                windowStart++;
            }

            // Add the current character to the window set
            windowSet.add(endChar);
            
            // Update the maximum length
            maxLength = Math.max(maxLength, windowSet.size());
        }

        return maxLength;
    }
}

209. Minimum Size Subarray Sum

SHOW PROBLEM

Problem Statement

You are given an array of positive integers nums and a positive integer target. Your task is to return the minimal length of a contiguous subarray whose sum is greater than or equal to target. If no such subarray exists, return 0.

Example 1:

  • Input: target = 7, nums = [2,3,1,2,4,3]
  • Output: 2
  • Explanation: The subarray [4,3] has the minimal length that satisfies the condition.

Example 2:

  • Input: target = 4, nums = [1,4,4]
  • Output: 1
  • Explanation: The number 4 alone meets the condition, so the minimal length is 1.

Example 3:

  • Input: target = 11, nums = [1,1,1,1,1,1,1,1]
  • Output: 0
  • Explanation: There is no subarray with a sum of at least 11, so the function returns 0.

Constraints:

  • 1 <= target <= 10^9
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^4

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int windowStart = 0, minLength = Integer.MAX_VALUE;
        int windowSum = 0;
        for (int windowEnd = 0; windowEnd < nums.length; windowEnd++) {
            // Expand the window by adding the current element
            windowSum += nums[windowEnd];

            // Shrink the window while the window sum at least the target
            while (windowSum >= target) {
                // Update the minimum length
                minLength = Math.min(minLength, windowEnd - windowStart + 1);
                // Remove the leftmost element
                windowSum -= nums[windowStart];
                // Shrink the window by incrementing window start pointer
                windowStart++;
            }

        }

        return minLength == Integer.MAX_VALUE ? 0 : minLength;
    }
}

76. Minimum Window Substring

SHOW PROBLEM

Problem Statement

You are given two strings, s and t, of lengths m and n respectively. Your task is to return the minimum window substring of s such that every character in t (including duplicates) is included in the window. If there is no such substring, return the empty string "".

The test cases are generated such that the answer is unique.

Example 1:

  • Input: s = "ADOBECODEBANC", t = "ABC"
  • Output: "BANC"
  • Explanation: The minimum window substring "BANC" includes ‘A’, ‘B’, and ‘C’ from string t.

Example 2:

  • Input: s = "a", t = "a"
  • Output: "a"
  • Explanation: The entire string s is the minimum window.

Example 3:

  • Input: s = "a", t = "aa"
  • Output: ""
  • Explanation: The string s contains only one ‘a’, but t requires two. Since s cannot form the required substring, return "".

Constraints:

  • m == s.length
  • n == t.length
  • 1 <= m, n <= 10^5
  • s and t consist of uppercase and lowercase English letters.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public String minWindow(String s, String t) {
        if (s.length() < t.length()) {
            return "";
        }

        Map<Character, Integer> freqMap = new HashMap<>();
        // Populate the frequency of characters in 't'
        for (char c : t.toCharArray()) {
            freqMap.put(c, freqMap.getOrDefault(c, 0) + 1);
        }


        int windowStart = 0, subStart = 0;
        int matched = 0; // Count of characters matched with 't'
        int minLength = s.length() + 1;
        for (int windowEnd = 0; windowEnd < s.length(); windowEnd++) {
            char endChar = s.charAt(windowEnd);

            if (freqMap.containsKey(endChar)) {
                freqMap.put(endChar, freqMap.get(endChar) - 1);

                // Count a valid match only if the character's frequency in the window does not exceed its need
                if (freqMap.get(endChar) >= 0) {
                    matched++;
                }
            }

            // Shrink the window as soon as possible when all characters in 't' are matched
            while (matched == t.length()) {
                if (minLength > windowEnd - windowStart + 1) {
                    minLength = windowEnd - windowStart + 1;
                    subStart = windowStart;
                }

                char startChar = s.charAt(windowStart++);

                if (freqMap.containsKey(startChar)) {
                    // Decrease 'matched' if a character fully removed
                    if (freqMap.get(startChar) == 0) {
                        matched--;
                    }
                    freqMap.put(startChar, freqMap.get(startChar) + 1);
                }
            }
        }

        return minLength > s.length() ? "" : s.substring(subStart, subStart + minLength);
    }
}

SHOW NOTES

Visulization:

Minimum Window Substring


Merge Intervals

The Merge Interval Pattern is a technique used to solve problems involving a list of intervals, where each interval is represented by a pair $[\text{start}_i, \text{end}_i]$. Common tasks following this pattern include merging overlapping intervals, inserting a new interval into an existing list, or finding the intersection of two interval lists.

To solve problems related to the merge interval pattern, the first step is to understand how two disjoint intervals can relate to each other. Given two intervals “a” and “b”, there are six possible relationships between them, as illustrated in the following image.

Merge Interval Pattern

Assuming a.start <= b.start (which can be achieved by sorting the intervals based on their starting points), there are only four possible scenatios:

Merge Interval Pattern: a.start &lt;= b.start

By observing the above image, for two intervals to overlap, the condition a.start <= b.start && b.start <= a.end must hold true. In this case, the merged interval becomes $[\text{a.start}, \text{max}\{\text{a.end, b.end}\}]$.

Merge Intervals

SHOW PROBLEM

Problem Statement

Given a list of intervals, merge all overlapping intervals to produce a list of mutually exclusive intervals.


Example 1:

  • Input: [[1, 4], [2, 5], [7, 9]]
  • Output: [[1, 5], [7, 9]]
  • Explanation: The first two intervals [1, 4] and [2, 5] overlap, so they are merged into one interval [1, 5].

Merge Intervals

Example 2:

  • Input: [[6, 7], [2, 4], [5, 9]]
  • Output: [[2, 4], [5, 9]]
  • Explanation: The intervals [6, 7] and [5, 9] overlap, so they are merged into [5, 9].

Example 3:

  • Input: [[1, 4], [2, 6], [3, 5]]
  • Output: [[1, 6]]
  • Explanation: All the given intervals overlap, so they are merged into one interval [1, 6].

Constraints:

  • $1 <= \text{intervals.length} <= 10^4$
  • $\text{intervals[i].length} == 2$
  • $0 <= \text{start}_i <= \text{end}_i <= 10^4$

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[][] merge(int[][] intervals) {
        // Sort intervals by their start point
        Arrays.sort(intervals, (a, b) -> a[0] - b[0]);

        // List to store merged intervals
        List<int[]> mergedIntervals = new ArrayList<>();

        // Initialize the first interval as the current interval
        mergedIntervals.add(intervals[0]);

        // Iterate through the intervals
        for (int i = 1; i < intervals.length; i++) {
            int[] currentInterval = mergedIntervals.get(mergedIntervals.size() - 1);
            int[] nextInterval = intervals[i];

            // If the next interval overlaps with the current interval
            if (nextInterval[0] <= currentInterval[1]) {
                // Merge by updating the end point of the current interval
                currentInterval[1] = Math.max(currentInterval[1], nextInterval[1]);
            } else {
                // No overlap, add the next interval as a new interval
                mergedIntervals.add(nextInterval);
            }
        }

        // Convert the list to a 2D array and return
        return mergedIntervals.toArray(new int[mergedIntervals.size()][]);
    }
}

SHOW NOTES

Algorithm Walkthrough

  • Input: [[1, 4], [2, 5], [7, 9]]
  1. Sort Intervals:

    • Original intervals: [[1, 4], [2, 5], [7, 9]]
    • Sorted intervals: [[1, 4], [2, 5], [7, 9]] (already sorted)
  2. Initialize Merged List:

    • mergedIntervals = [[1, 4]]
  3. Iterate Through Intervals:

    • First Iteration:

      • Current interval: [1, 4]
      • Next interval: [2, 5]
      • Check overlap: 2 <= 4 (True)
      • Merged intervals: [[1, 5]]
    • Second Iteration:

      • Current interval: [1, 5]
      • Next interval: [7, 9]
      • Check overlap: 7 <= 5 (False)
      • Merged intervals: [[1, 5], [7, 9]]
  4. Result:

    • Result: [[1, 5], [7, 9]]

Visulization

Merge Intervals


Insert Interval

SHOW PROBLEM

Problem Statement

Given a list of non-overlapping intervals sorted by their start time, insert a given interval at the correct position and merge all overlapping intervals to produce a list of mutually exclusive intervals.

Example 1:

  • Input: Intervals = [[1, 3], [5, 7], [8, 12]], New Interval = [4, 6]
  • Output: [[1, 3], [4, 7], [8, 12]]
  • Explanation: After inserting [4, 6], it overlaps with [5, 7], so they are merged into [4, 7].

Example 2:

  • Input: Intervals = [[1, 3], [5, 7], [8, 12]], New Interval = [4, 10]
  • Output: [[1, 3], [4, 12]]
  • Explanation: After inserting [4, 10], it overlaps with [5, 7] and [8, 12], so they are merged into [4, 12].

Example 3:

  • Input: Intervals = [[2, 3], [5, 7]], New Interval = [1, 4]
  • Output: [[1, 4], [5, 7]]
  • Explanation: After inserting [1, 4], it overlaps with [2, 3], so they are merged into [1, 4].

Constraints:

  • 1 <= intervals.length <= 10^4
  • intervals[i].length == 2
  • 0 <= start_i <= end_i <= 10^5
  • intervals is sorted by start_i in ascending order.
  • newInterval.length == 2
  • 0 <= start <= end <= 10^5

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[][] insert(int[][] intervals, int[] newInterval) {
        List<int[]> mergedIntervals = new ArrayList<>();

        int i = 0;
        // Add all intervals that ends before the new interval starts
        while (i < intervals.length && intervals[i][1] < newInterval[0]) {
            mergedIntervals.add(intervals[i]);
            i++;
        }

        // Merge the overlapping intervals with the new interval
        while (i < intervals.length && intervals[i][0] <= newInterval[1]) {
            newInterval[0] = Math.min(intervals[i][0], newInterval[0]);
            newInterval[1] = Math.max(intervals[i][1], newInterval[1]);
            i++;
        }

        // Add the merged new interval to the result list
        mergedIntervals.add(newInterval);

        // Add all intervals that don't overlap with the new interval to the result list
        while (i < intervals.length) {
            mergedIntervals.add(intervals[i]);
            i++;
        }

        // Convert the list to a 2-D array and return
        return mergedIntervals.toArray(new int[mergedIntervals.size()][]);
    }
}

SHOW NOTES

Since the interval list is sorted, the first step is to skip all intervals that satisfy the condition intervals[i].end < newInterval.start. After that, there are five possible ways the new interval can relate to the next interval in the list (where “a” represents newInterval, and “b” represents the next interval in the list), as shown in the following image.

Insert Interval

By observing the above image, for two intervals to overlap, the condition interval.start <= newInterval.end must hold true. In this case, the merged interval will be $[\text{min\{a.start, b.start\}}, \text{max\{a.end, b.end\}}]$.

Algorithm Walkthrough

  • Intervals = [[1, 3], [5, 7], [8, 12]]
  • New Interval = [4, 10]
  1. Initialize Merged List:
    mergedIntervals = []
  2. Iterate Through Intervals:
    1. Process the first interval [1, 3]:
    • Ends before [4, 10] starts.
    • Add to mergedIntervals: [[1, 3]]
    1. Process the second interval [5, 7]:
      • Overlaps with [4, 10].
      • No change to the new interval: [4, 10] -> [4, 10]
      • No change to mergedIntervals.
    2. Process the third interval [8, 12]:
      • Overlaps with [4, 10].
      • Adjust new interval: [4, 10] -> [4, 12]
      • Add the merged interval [4, 12] to mergedIntervals: [[1, 3], [4, 12]]
  3. No more intervals left to process.
  4. Final merged intervals: [[1, 3], [4, 12]]

Visulization

Insert Interval


Intervals Intersection

SHOW PROBLEM

Problem Statement

Given two lists of disjoint intervals, find their intersection. Each list is sorted by the start time of the intervals.

Example 1:

  • Input: arr1 = [[1, 3], [5, 6], [7, 9]], arr2 = [[2, 3], [5, 7]]
  • Output: [[2, 3], [5, 6], [7, 7]]
  • Explanation: The output list contains the common intervals between the two lists.

Example 2:

  • Input: arr1 = [[1, 3], [5, 7], [9, 12]], arr2 = [[5, 10]]
  • Output: [[5, 7], [9, 10]]
  • Explanation: The output list contains the common intervals between the two lists.

Constraints:

  • 0 <= arr1.length, arr2.length <= 1000
  • arr1.length + arr2.length >= 1
  • $0 \le \text{start}_i < \text{end}_i \le 10^9$
  • $end_i < \text{start}_{i+1}$ for all intervals in arr1
  • $0 \le \text{start}_j < \text{end}_j \le 10^9$
  • $end_j < \text{start}_{j+1}$ for all intervals in arr2

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[][] intervalIntersection(int[][] arr1, int[][] arr2) {
        List<int[]> intersections = new ArrayList<>();
        int i = 0, j = 0;

        // Iterate over both interval lists to find all intersections
        while (i < arr1.length && j < arr2.length) {
            // Check if both intervals are overlapped
            if ((arr1[i][0] >= arr2[j][0] && arr1[i][0] <= arr2[j][1])
                    || (arr2[j][0] >= arr1[i][0] && arr2[j][0] <= arr1[i][1])) {
                
                // Store the intersection part
                intersections.add(new int[]{Math.max(arr1[i][0], arr2[j][0]),
                        Math.min(arr1[i][1], arr2[j][1])});
            }

            // Move the pointer of the interval that ends first
            if (arr1[i][1] < arr2[j][1]) {
                i++;
            } else {
                j++;
            }
        }

        // Return the intersections as a 2D array
        return intersections.toArray(new int[intersections.size()][]);
    }
}
class Solution {
    public int[][] intervalIntersection(int[][] arr1, int[][] arr2) {
        List<int[]> intersections = new ArrayList<>();
        int i = 0, j = 0;
        
        // Iterate over both interval lists to find all intersections
        while (i < arr1.length && j < arr2.length) {
            int start = Math.max(arr1[i][0], arr2[j][0]);
            int end = Math.min(arr1[i][1], arr2[j][1]);

            // Add the intersection to the result if valid
            if (start <= end) {
                intersections.add(new int[] { start, end });
            }

            // Move the pointer of the interval that ends first
            if (arr1[i][1] < arr2[j][1]) {
                i++;
            } else {
                j++;
            }
        }

        // Return the result as a 2D array
        return intersections.toArray(new int[intersections.size()][]);
    }
}

SHOW NOTES

Two Interval List: arr1 and arr2

Pointer Initialization: Initialize two pointers i and j, both set to 0, to track the current positions in arr1 and arr2, respectively.

Overlapping Check: For two intervals a and b to overlap, at least one of the following conditions must hold true:

  1. a.start <= b.start && b.start <= a.end
  2. b.start <= a.start && a.start <= b.end

Interval Calculation: If a and b overlap (follows overlapping check), the intersection is calculated as:

  • $[\text{max}\{\text{a.start, b.start}\}, \text{min}\{\text{a.end, b.end}\}]$

Interval Caculation (Optimization): Based on the Overlapping Check and Interval Calculation, the calculation can be optimized as follows:

  1. maxStart = max{a.start, b.start}
  2. minEnd = min{a.end, b.end}
  3. An intersection [maxStart, minEnd] exists if maxStart <= minEnd

Pointer Movement: Increment the pointer that ends first. For example, if arr1[i].end < arr2[j].end, increment i; otherwise increment j.


In-place Reversal of a Linked List

The In-place Reversal of a Linked List pattern is a commonly used approach to solve linked-list-related problems efficiently without extra space. It relies on maintaining three pointers:

  1. prev: Points to the previous node. Initially set to null since there is no node before the head.
  2. current: Points to the node currently being processed.
  3. next: Points to the next node, ensuring the remaining list is not lost during the reversal process.

This pattern typically involves the following three steps:

  1. Initialization: Set prev = null and current = head to start processing from the head of list.
  2. Iteration:
    • Save the next node (next = current.next) to keep track of the remaining list.
    • Reverse the link by updating current.next to point to prev.
    • Move the prev pointer forward (prev = current).
    • Move the current pointer forward (current = next);
  3. Termination: When current becomes null, the prev pointer will point to the new head of the reversed linked list.

Reverse Linked List

SHOW PROBLEM

Problem Statement
Given the head of a singly linked list, reverse the list in-place and return the new head of the reversed list.

Reverse Linked List


Constraints

  • The number of nodes in the list is in the range: $[0, 5000]$.
  • Each node’s value is within the range: $[-5000, 5000]$.

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode() {}
 * ListNode(int val) { this.val = val; }
 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null; // Tracks the previous node
        ListNode current = head; // Tracks the current node

        // Traverse and reverse the list
        while (current != null) {
            ListNode next = current.next; // Save the next node
            current.next = prev; // Reverse the link
            prev = current; // Move prev forward
            current = next; // Move current forward
        }

        return prev; // New head of the reversed list
    }
}

SHOW NOTES

Visulization

Reverse Linked List Illustration


Reverse a Sublist

SHOW PROBLEM

Given the head of a LinkedList and two positions, left and right, reverse the portion of the LinkedList from position left to position right.

Reverse a Sub List

Constraints:

  • The number of nodes in the list is n.
  • 1 <= n <= 500
  • -500 <= Node.val <= 500
  • 1 <= left <= right <= n

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode prev = null, current = head;
        
        // Skip the first left - 1 node to reach the node at position left
        for (int i = 1; i < left && current != null; i++) {
            prev = current;
            current = current.next;
        }

        // Save the node at position left - 1 to reconnect the reversed sublist
        ListNode lastNodeOfFirstPart = prev;
        // After reversing, the left node will become the last node of the reversed sublist
        ListNode lastNodeOfReversedList = current;

        // Reverse the node from left to right
        ListNode next = null;
        for (int i = 1; i <= right - left + 1 && current != null; i++) {
            next = current.next; // Save the next node
            current.next = prev; // Reverse the link
            prev = current; // Move the 'prev' node forward
            current = next; // Move the 'current' node forward
        }

        // Reconnect the reversed sublist with the rest of the list
        if (lastNodeOfFirstPart != null) {
            // Connect with the new head of the sublist
            lastNodeOfFirstPart.next = prev;
        } else { // left == 1 in this case
            head = prev;
        }

        // Connect the tail of the reversed sublist to the remaining list
        lastNodeOfReversedList.next = current;

        return head;
    }
}

SHOW NOTES
  1. Skip the first left - 1 nodes to reach the node at position left
  2. Save the node at position left - 1 to later reconnect it with the reversed sublist.
  3. Save the node at position left before reversing, after reversing, the node will become the last node of the reversed sublist.
  4. Reverse the nodes from position left to right
  5. Reconnect the node at position left - 1 to the head of the reversed sublist, and connect the node at position right + 1 to the tail of the reversed sublist.


Reverse Nodes in k-Group

SHOW PROBLEM

Problem Statement:

Given the head of a linked list, reverse the nodes of the list in groups of k at a time, and return the modified list.

  • k is a positive integer and is less than or equal to the length of the linked list.
  • If the number of nodes is not a multiple of k, then the remaining nodes at the end should remain as they are (i.e., not reversed).
  • You are not allowed to alter the values of the nodes in the list; only the nodes themselves may be rearranged.

Example 1:

  • Input: head = [1, 2, 3, 4, 5], k = 2
  • Output: [2, 1, 4, 3, 5]

Reverse Nodes in k-Group: Example 1

Example 2:

  • Input: head = [1, 2, 3, 4, 5], k = 3
  • Output: [3, 2, 1, 4, 5]

Reverse Nodes in k-Group: Example 2


Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode() {}
 * ListNode(int val) { this.val = val; }
 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        // Return the original list if the list is empty or k is 1
        if (head == null || k == 1) {
            return head;
        }

        // Count the total number of the list
        ListNode temp = head;
        int count = 0;
        while (temp != null) {
            count++;
            temp = temp.next;
        }

        ListNode prev = null, current = head, next = null;
        while (current != null && count >= k) {
            ListNode lastNodeOfPrevGroup = prev;
            // After reversing, the current node will become the last node of the reversed list
            ListNode lastNodeOfReversedList = current;

            // Reverse the list in groups of size k
            for (int i = 0; i < k; i++) {
                next = current.next;
                current.next = prev;
                prev = current;
                current = next;
                count--;
            }

            // Connect the last node of the reversed sublist to the start node of the
            // remaining list
            lastNodeOfReversedList.next = current;

            // Connect with the previous group
            if (lastNodeOfPrevGroup != null) {
                // After reversing, the prev node is the head node of the sublist
                lastNodeOfPrevGroup.next = prev;
            } else {
                // When previous group is null, the prev node becomes the head of the original list
                head = prev;
            }

            // Break the loop when current is null or count is less than k
            if (current == null || count < k) {
                break;
            }

            // Update the prev node for next iteration
            prev = lastNodeOfReversedList;
        }

        return head;
    }
}

SHOW NOTES

The first step to solve this problem is to track the last node of the previous group and the last node of the reversed sublist. When the previous group is null, the head of the reversed sublist becomes the new head of the original list.

Before reversing, the current node points to the head of the sublist. After reversing, the head of the sublist becomes the last node of the reversed segment. Therefore, the last node of the reversed list can be tracked by setting lastNodeOfReversedList = current. To track the last node of the previous group, simply setting lastNodeOfPreviousGroup = prev.

After the reversal, the prev node will point to the head of the reversed sublist, and the current node (if it’s not null) will point to the head of the next sublist. To maintain the list structure, There are two key steps need to do, the first step is connecting the last node of the previous group to the head (prev after reversal) of the reversed sublist, the second step is connecting the last node of the reversed sublist to the remaining node (if any).

Finally, update the prev node to be the last node of the reversed sublist, preparing it for the next iteration.


Hash Maps

Hash Map Data Structure

A Hash Map is a data structure that stores key-value pairs, enabling efficient lookup, insertion, and deletion operations. Under most conditions, it provides constant time complexity (O(1)) for these operations. The Hash Map Pattern takes advantage of the hash map’s efficiency to store and retrive data quickly. It is commonly used to solve problems involving the calculation of element frequencies in a sequency or array.


1. Two Sum

SHOW PROBLEM

Problem Statement

You are given an array of integers nums and an integer target.

Your task is to find two distinct indices in nums such that the sum of the corresponding elements equals target.

You may assume that:

  • Each input has exactly one solution.
  • You may not use the same element twice.
  • The answer can be returned in any order.

Example 1:

  • Input: nums = [2,7,11,15], target = 9
  • Output: [0,1]
  • Explanation: nums[0] + nums[1] == 9, so we return [0,1].

Example 2:

  • Input: nums = [3,2,4], target = 6
  • Output: [1,2]
  • Explanation: nums[1] + nums[2] == 6, so we return [1,2].

Example 3:

  • Input: nums = [3,3], target = 6
  • Output: [0,1]
  • Explanation: nums[0] + nums[1] == 6, so we return [0,1].

Constraints:

  • 2 <= nums.length <= 10^4
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9
  • Only one valid answer exists.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> frequencyMap = new HashMap<>();

        // for (int i = 0; i < nums.length; i++) {
        for (int i = 0; ; i++) {
            int complement = target - nums[i];

            // If the complement exists in the map
            if (frequencyMap.containsKey(complement)) {
                return new int[]{frequencyMap.get(complement), i};
            }

            // Store the current element's index for future reference
            frequencyMap.put(nums[i], i);
        }

        // return new int[]{};
    }
}

SHOW NOTES

Visuliation:

Two Sum


242. Valid Anagram

SHOW PROBLEM

Problem Statement

You are given two strings s and t. Return true if t is an anagram of s, and false otherwise.

Example 1:

  • Input: s = "anagram", t = "nagaram"
  • Output: true
  • Explanation: Both strings contain the same characters with the same frequencies, so they are anagrams.

Example 2:

  • Input: s = "rat", t = "car"
  • Output: false
  • Explanation: The two strings do not contain the same characters, so they are not anagrams.

Constraints:

  • 1 <= s.length, t.length <= 5 * 10^4
  • s and t consist of lowercase English letters.

Follow-up: What if the inputs contain Unicode characters? How would you adapt your solution to such a case?


Go to Leetcode 🔗
SHOW CODE
class Solution {
    public boolean isAnagram(String s, String t) {
        if (s.length() != t.length()) {
            return false;
        }

        // Store the frequency of each character
        Map<Character, Integer> freqMap = new HashMap<>();

        // Iterate through both strings to build the frequency map
        for (int i = 0; i < s.length(); i++) {
            // Increment the frequency of the character from string s
            freqMap.put(s.charAt(i), freqMap.getOrDefault(s.charAt(i), 0) + 1);
            // Decrement the frequency of the character from string t
            freqMap.put(t.charAt(i), freqMap.getOrDefault(t.charAt(i), 0) - 1);
        }

        for (int v: freqMap.values()) {
            // If any characters' frequency is non-zero, the strings are not anagram
            if (v != 0) {
                return false;
            }
        }
        
        // If all characters' frequency is zero, the strings are anagram
        return true;
    }
}

SHOW NOTES

Visulization:

Valid Anagram


349. Intersection of Two Arrays

SHOW PROBLEM

Problem Statement

You are given two integer arrays nums1 and nums2. Return an array of their intersection, where each element in the result must be unique. You may return the result in any order.

Example 1:

  • Input: nums1 = [1,2,2,1], nums2 = [2,2]
  • Output: [2]
  • Explanation: The only element that appears in both arrays is 2.

Example 2:

  • Input: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
  • Output: [9,4]
  • Explanation: The intersection of the two arrays is [4,9], but the order may vary, so [9,4] is also accepted.

Constraints:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        List<Integer> result = new ArrayList<>();
        Map<Integer, Integer> freqMap = new HashMap<>();

        for (int i = 0; i < nums1.length; i++) {
            freqMap.put(nums1[i], freqMap.getOrDefault(nums1[i], 0) + 1);
        }

        for (int i = 0; i < nums2.length; i++) {
            if (freqMap.getOrDefault(nums2[i], 0) != 0) {
                result.add(nums2[i]);
                freqMap.put(nums2[i], 0);
            }
        }

        return result.stream().mapToInt(Integer::intValue).toArray();
    }
}

First Unique Character in a String

SHOW PROBLEM

Problem Statement

Given a string, find the position of the first character that appears only once. If no such character exists, return -1.


Example 1:

  • Input: "apple"
  • Expected Output: 0
  • Justification: The first character 'a' appears only once in the string, and it is located at index 0.

Example 2:

  • Input: "abcab"
  • Expected Output: 2
  • Justification: The first character that appears only once is 'c', which is located at index 2.

Example 3:

  • Input: "abab"
  • Expected Output: -1
  • Justification: All characters in the string appear more than once, so there is no character that appears only once.

Constraints:

  • 1 <= s.length <= 10^5
  • s consists of only lowercase English letters.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int firstUniqChar(String s) {
        // Use a HashMap to store the frequency of each character
        Map<Character, Integer> charFrequencyMap = new HashMap<>();

        // Populate the map with the frequency of each character
        for (char c : s.toCharArray()) {
            charFrequencyMap.put(c, charFrequencyMap.getOrDefault(c, 0) + 1);
        }

        // Iterate over the string to find the index of the first unique character
        for (int i = 0; i < s.length(); i++) {
            if (charFrequencyMap.get(s.charAt(i)) == 1) {
                return i; // Return the index of the first unique character
            }
        }

        // No unique character found, return -1
        return -1;
    }
}

SHOW NOTES

Virsulization:

First Unique Character in a String


Maximum Number of Balloons

SHOW PROBLEM

Problem Statement

Given a string, determine the maximum number of times the word “balloon” can be formed using the characters from the string. Each character in the string can be used only once.


Example 1:

  • Input: "balloonballoon"
  • Expected Output: 2
  • Justification: The word “balloon” can be formed twice from the given string.

Example 2:

  • Input: "bbaall"
  • Expected Output: 0
  • Justification: The word “balloon” cannot be formed because the character ‘o’ is missing twice.

Example 3:

  • Input: "balloonballoooon"
  • Expected Output: 2
  • Justification: The word “balloon” can be formed twice, even though there are extra ‘o’ characters.

Constraints:

  • 1 <= text.length <= 10^4
  • text consists of lowercase English letters only.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int maxNumberOfBalloons(String text) {
        // Define the required frequencies of characters to form 'balloon'
        Map<Character, Integer> requiredCharMap = Map.of(
                'b', 1,
                'a', 1,
                'l', 2,
                'o', 2,
                'n', 1);

        // Calculate the frequencies of characters that are part of 'balloon'
        Map<Character, Integer> availableCharMap = new HashMap<>();
        for (char c : text.toCharArray()) {
            if (requiredCharMap.containsKey(c)) {
                availableCharMap.put(c, availableCharMap.getOrDefault(c, 0) + 1);
            }
        }
        
        // Calculate the maximum available count of the word 'balloon'
        int maxCount = Integer.MAX_VALUE;
        if (availableCharMap.size() == requiredCharMap.size()) {
            for (char c : availableCharMap.keySet()) {
                int requiredCount = requiredCharMap.get(c);
                int availableCount = availableCharMap.get(c);
                maxCount = Math.min(maxCount, availableCount / requiredCount);
            }
        }

        return (maxCount == Integer.MAX_VALUE) ? 0 : maxCount;
    }
}

SHOW NOTES

Virsulization:

Maximum Number of Balloons


Longest Palindrome

SHOW PROBLEM

Problem Statement:

Given a string, determine the length of the longest palindrome that can be constructed using the characters from the string. Return the maximum possible length of the palindromic string.


Example 1:

  • Input: "applepie"
  • Expected Output: 5
  • Justification: The longest palindrome that can be formed is "pepep", which has a length of 5. Other valid palindromes are also of length 5.

Example 2:

  • Input: "aabbcc"
  • Expected Output: 6
  • Justification: We can form the palindrome "abccba", which has a length of 6.

Example 3:

  • Input: "bananas"
  • Expected Output: 5
  • Justification: The longest palindrome that can be formed is "anana", which has a length of 5.

Constraints:

  • 1 <= s.length <= 2000
  • s consists of lowercase and/or uppercase English letters only.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int longestPalindrome(String s) {
        // Calculate the frequency of each character in the string
        Map<Character, Integer> frequencyMap = new HashMap<>();
        for (char c : s.toCharArray()) {
            frequencyMap.put(c, frequencyMap.getOrDefault(c, 0) + 1);
        }

        // Flag to track if any character has an odd frequency
        boolean oddFound = false;
        int maxLength = 0;

        // Calculate the maximum possible length of the palindrome
        for (int count : frequencyMap.values()) {
            if (count % 2 == 0) {
                // Add even counts directly to the length of the palindorme
                maxLength += count;
            } else {
                maxLength += count - 1; // Add the even parts of odd counts
                oddFound = true; // Mark an odd frequency character exists
            }
        }

        // For characters has an odd count, one can be placed in the middle of the palindrome
        return oddFound ? maxLength + 1 : maxLength;
    }
}

SHOW NOTES

To solve this problem, the first step is to calculate the frequency of each character in the original string. If no character has an odd frequency, simply add the frequency of each character to the length of the palindrome. If there are characters with an odd frequency, one of those characters can be placed in the middle of the palindrome.

Therefore, to get the maximum length of the palindrome, two key steps are involved. First, add the even part of each character’s frequency to the palindrome length. Then, if there is any character with an odd count, increment the length by 1, representing the character placed in the middle of the palindrome.


Virsulization:

Longest Palindrome


219. Contains Duplicate II

SHOW PROBLEM

Problem Statement

You are given an integer array nums and an integer k. Your task is to return true if there are two distinct indices i and j in the array such that:

  • nums[i] == nums[j]
  • abs(i - j) <= k

Otherwise, return false.

Example 1:

  • Input: nums = [1,2,3,1], k = 3
  • Output: true
  • Explanation: The indices i = 0 and j = 3 satisfy nums[i] == nums[j] and abs(i - j) = 3, which is less than or equal to k.

Example 2:

  • Input: nums = [1,0,1,1], k = 1
  • Output: true
  • Explanation: The indices i = 2 and j = 3 satisfy nums[i] == nums[j] and abs(i - j) = 1, which is less than or equal to k.

Example 3:

  • Input: nums = [1,2,3,1,2,3], k = 2
  • Output: false
  • Explanation: There are no indices i and j such that nums[i] == nums[j] and abs(i - j) <= 2.

Constraints:

  • 1 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
  • 0 <= k <= 10^5

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        // A map stores the element with its most recent index;
        Map<Integer, Integer> map= new HashMap<>();

        for (int i = 0; i < nums.length; i++) {
            if (map.containsKey(nums[i]) && i - map.get(nums[i]) <= k) {
                return true;
            }

            // Update the map with the current index of the element
            map.put(nums[i], i);
        }

        return false;
    }
}

Prefix Sum

A prefix sum refers to the cumulative sum of an array from the begining up to a specified index. The prefix sum pattern is a programming technique commonly used to solve problems related to subarrays, such as calculating the range sum, range maximum/minimum, or frequency counting.

To calculate the prefix sum, the steps typically include:

  1. Initialze a prefix array with the same length as the original array.
  2. Assign prefix[0] = arr[0].
  3. Iterate through the array from index 1 to n - 1, set prefix[i] = arr[i] + prefix[i - 1]

Once the prefix sum array is constructed, the time complexity for calculating the sum of any subarray is O(1), makeing this technique very efficient.


Find the Middle Index in Array

SHOW PROBLEM

Problem Statement:

Given an integer array nums, return the leftmost middleIndex (i.e., the smallest index among all possible middle indices).

A middleIndex is an index where the sum of the numbers to the left of this index is equal to the sum of the numbers to the right of this index.

  • You can consider the left sum to be 0 when middleIndex == 0, and the right sum to be 0 when middleIndex == nums.length - 1.
  • If no middle index exists in the array, return -1.

Example 1:

  • Input: nums = [1, 7, 3, 6, 5, 6]
  • Expected Output: 3
  • Justification: The sum of the numbers to the left of index 3 (1 + 7 + 3 = 11) is equal to the sum of the numbers to the right of index 3 (5 + 6 = 11).

Example 2:

  • Input: nums = [2, 1, -1]
  • Expected Output: 0
  • Justification: The sum of the numbers to the left of index 0 is considered 0. The sum of the numbers to the right of index 0 (1 + -1 = 0) is also 0.

Example 3:

  • Input: nums = [2, 3, 5, 5, 3, 2]
  • Expected Output: -1
  • Justification: There is no index in the array where the left and right sums are equal.

Constraints:

  • 1 <= nums.length <= 100
  • -1000 <= nums[i] <= 1000

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int findMiddleIndex(int[] nums) {
        // Calculate the total sum of the array
        int totalSum = Arrays.stream(nums).sum();

        int leftSum = 0;
        // Iterate through the array to find the middle index
        for (int i = 0; i < nums.length; i++) {
            // Calculate the right sum
            int rightSum = totalSum - leftSum - nums[i];

            // Found the middle index if left sum equals to right sum
            if (leftSum == rightSum) {
                return i;
            }

            // Update the left sum for next iteration
            leftSum += nums[i];
        }

        // Return -1 if middle index is found
        return -1;
    }
}

SHOW NOTES

Visualization:

Find the middle index in array


Left and Right Sum Differences

SHOW PROBLEM

Problem Statement

Given an integer array nums, you need to find a new integer array called differenceArray, where each element at index i in differenceArray is the absolute difference between the sum of all elements to the left of index i and the sum of all elements to the right of index i in the original array nums.

Formally, for each index i in the array:

$$ \text{differenceArray}[i] = | \text{leftSumi} - \text{rightSumi} | $$

Where:

  • $\text{leftSum}_i$ is the sum of all elements to the left of index i (i.e., nums[0] + nums[1] + ... + nums[i-1]).
  • $\text{rightSum}_i$ is the sum of all elements to the right of index i (i.e., nums[i+1] + nums[i+2] + ... + nums[nums.length-1]).
  • If there are no elements to the left or right of i, treat the sum as 0.

Example 1:

  • Input: nums = [2, 5, 1, 6, 1]
  • Expected Output: [13, 6, 0, 7, 14]
  • Explanation:
    • For i = 0: | (0) - (5 + 1 + 6 + 1) | = | 0 - 13 | = 13
    • For i = 1: | (2) - (1 + 6 + 1) | = | 2 - 8 | = 6
    • For i = 2: | (2 + 5) - (6 + 1) | = | 7 - 7 | = 0
    • For i = 3: | (2 + 5 + 1) - (1) | = | 8 - 1 | = 7
    • For i = 4: | (2 + 5 + 1 + 6) - (0) | = | 14 - 0 | = 14

Example 2:

  • Input: nums = [3, 3, 3]
  • Expected Output: [6, 0, 6]
  • Explanation:
    • For i = 0: | (0) - (3 + 3) | = | 0 - 6 | = 6
    • For i = 1: | (3) - (3) | = | 3 - 3 | = 0
    • For i = 2: | (3 + 3) - (0) | = | 6 - 0 | = 6

Example 3:

  • Input: nums = [1, 2, 3, 4, 5]
  • Expected Output: [14, 11, 6, 1, 10]
  • Explanation:
    • For i = 0: | (0) - (2 + 3 + 4 + 5) | = | 0 - 14 | = 14
    • For i = 1: | (1) - (3 + 4 + 5) | = | 1 - 12 | = 11
    • For i = 2: | (1 + 2) - (4 + 5) | = | 3 - 9 | = 6
    • For i = 3: | (1 + 2 + 3) - (5) | = | 6 - 5 | = 1
    • For i = 4: | (1 + 2 + 3 + 4) - (0) | = | 10 - 0 | = 10

Constraints:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 10^5

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[] leftRightDifference(int[] nums) {
        int n = nums.length;

        // Calculate the total sum of nums
        int totalSum = Arrays.stream(nums).sum();

        // Construct prefix sum array based on nums
        int[] prefix = new int[n];
        prefix[0] = nums[0];
        for (int i = 1; i < n; i++) {
            prefix[i] = nums[i] + prefix[i - 1];
        }

        int[] answer = new int[n];
        // Iterate over nums to calculate the absolute difference between left and right
        // sums for each index
        for (int i = 0; i < n; i++) {
            int leftSum = prefix[i] - nums[i];
            int rightSum = totalSum - prefix[i];

            answer[i] = Math.abs(leftSum - rightSum);
        }

        return answer;
    }
}

Subarray Sums Divisible by K

SHOW PROBLEM

Problem Statement:

Given an integer array nums and an integer k, return the number of non-empty subarrays whose sum is divisible by k.

A subarray is a contiguous part of an array.


Example 1:

  • Input: nums = [3, 1, 2, -2, 5, -1], k = 3
  • Output: 7
  • Explanation: There are 7 subarrays whose sum is divisible by k = 3:
    • [3], [1, 2], [3, 1, 2], [-2, 2], [3, 1, 2, -2, 5], [1, 2, -2, 5], [-2, 5].

Example 2:

  • Input: nums = [4, 5, 0, -2, -3, 1], k = 5
  • Output: 7
  • Explanation: There are 7 subarrays whose sum is divisible by k = 5:
    • [4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3].

Example 2:

  • Input: nums = [5], k = 9
  • Output: 0.

Constraints:

  • 1 <= nums.length <= 30,000
  • -10,000 <= nums[i] <= 10,000
  • 2 <= k <= 10,000

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int subarraysDivByK(int[] nums, int k) {
        // Store frequency of prefix sum modulo k
        Map<Integer, Integer> prefixSumModCount = new HashMap<>();
        
        // Initialize with mod 0 having a frequency of 1 
        // (for the case where the prefix sum itself is divisible by k)
        prefixSumModCount.put(0, 1);

        // Initialize the prefix sum and the count of valid subarrays
        int prefixSum = 0, count = 0;
        for (int num : nums) {
            // Update the current prefix sum
            prefixSum += num;
            
            // Ensure the modulo is always positive
            int mod = ((prefixSum % k) + k) % k;

            // If the same mod has been seen before, 
            // it means there are subarrays whose sum is divisible by k
            count += prefixSumModCount.getOrDefault(mod, 0);

            // Update frequency of current mod
            prefixSumModCount.put(mod, prefixSumModCount.getOrDefault(mod, 0) + 1);
        }

        return count;
    }
}

SHOW NOTES

If the sum of a subarray form index $i$ to $j$ can be divisable by $k$, then: $$ (a[0] + a[1] + \dots + a[i - 1] + a[i]) \;\text{mod} \;k $$

equals to

$$ (a[0] + a[1] + \dots + a[i] + \dots + a[j-1] + a[j]) \;\text{mod} \;k $$


Algorithm Walkthrough

  • Input: nums = [3, 1, 2, -2, 5, -1] and k = 3.
  1. Initialization:

    • remainder_count = {0: 1} (We start by initializing the count of remainders with 0, assuming a subarray sum of 0 is divisible by k.)
    • cumulative_sum = 0
    • count = 0
  2. Iteration 1 (num = 3):

    • cumulative_sum = 3
    • remainder = 3 % 3 = 0
    • count += remainder_count[0] = 1count = 1
    • Update remainder_count: {0: 2}
  3. Iteration 2 (num = 1):

    • cumulative_sum = 4
    • remainder = 4 % 3 = 1
    • count += remainder_count.get(1, 0) = 0count = 1
    • Update remainder_count: {0: 2, 1: 1}
  4. Iteration 3 (num = 2):

    • cumulative_sum = 6
    • remainder = 6 % 3 = 0
    • count += remainder_count[0] = 2count = 3
    • Update remainder_count: {0: 3, 1: 1}
  5. Iteration 4 (num = -2):

    • cumulative_sum = 4
    • remainder = 4 % 3 = 1
    • count += remainder_count[1] = 1count = 4
    • Update remainder_count: {0: 3, 1: 2}
  6. Iteration 5 (num = 5):

    • cumulative_sum = 9
    • remainder = 9 % 3 = 0
    • count += remainder_count[0] = 3count = 7
    • Update remainder_count: {0: 4, 1: 2}
  7. Iteration 6 (num = -1):

    • cumulative_sum = 8
    • remainder = 8 % 3 = 2
    • count += remainder_count.get(2, 0) = 0count = 7
    • Update remainder_count: {0: 4, 1: 2, 2: 1}
  8. The total count of subarrays whose sum is divisible by k is 7.


Stack

The Stack Coding Pattern leverages the LIFO (Last In, First Out) property of a stack to efficiently solve problems that exhibit LIFO behavior. It is particularly useful for matching problems (e.g., valid parentheses), monotonic stack scenarios (e.g., next greater element), and bracktracking problems (e.g., DFS), among others.

Stack Operations


20. Valid Parentheses

SHOW PROBLEM

Problem Statement

You are given a string s consisting of only the characters '(', ')', '{', '}', '[', and ']'.

Your task is to determine whether the input string is valid.

A string is considered valid if:

  • Every open bracket has a corresponding matching close bracket of the same type.
  • Open brackets must be closed in the correct order.
  • Every closing bracket has a previously opened counterpart.

Example 1:

  • Input: s = "()"
  • Output: true

Example 2:

  • Input: s = "()[]{}"
  • Output: true

Example 3:

  • Input: s = "(]"
  • Output: false

Example 4:

  • Input: s = "([])"
  • Output: true

Constraints:

  • 1 <= s.length <= 10^4
  • s consists only of the characters '(', ')', '{', '}', '[', and ']'.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public boolean isValid(String s) {
        // Track opening parenthses
        Stack<Character> stack = new Stack<>();

        for (char c : s.toCharArray()) {
            if (isOpenParenthesis(c)) {
                stack.push(c); // Push opening parenthesis onto the stack
            } else {
                // If stack is empty or the top of stack doesn't match the closing parenthesis, return false
                if (stack.isEmpty() || !isMatched(stack.pop(), c)) {
                    return false;
                }
            }
        }

        // If stack is empty, all parenthses are balanced
        return stack.isEmpty();
    }

    private static boolean isOpenParenthesis(char c) {
        return c == '(' || c == '[' || c == '{';
    }

    private static boolean isMatched(char open, char closed) {
        return (open == '(' && closed == ')')
                || (open == '[' && closed == ']')
                || (open == '{' && closed == '}');
    }
}

71. Simplify Path

SHOW PROBLEM

Problem Statement

You are given an absolute path for a Unix-style file system, which always begins with a slash '/'.

Your task is to simplify the given path into its canonical form, following these rules:

  • A single period '.' represents the current directory and should be ignored.
  • A double period '..' represents moving one level up (to the parent directory). If already at the root, '..' has no effect.
  • Multiple consecutive slashes ('//', '///') should be treated as a single slash '/'.
  • Any sequence of periods that does not match the special cases above (e.g., '...', '....') should be treated as a valid directory or file name.
  • The simplified canonical path should:
    • Start with a single slash '/'.
    • Have directory names separated by exactly one slash '/'.
    • Not end with a slash '/', unless it is the root directory '/'.
    • Not contain '.' or '..' for current or parent directories.

Return the simplified canonical path as a string.

Example 1:

  • Input: path = "/a//b////c/d//././/.."
  • Output: "/a/b/c"
  • Explanation: Consecutive slashes are replaced by a single slash; '..' moves up one level to remove "Documents".

Example 2:

  • Input: path = "/home//foo/"
  • Output: "/home/foo"
  • Explanation: Consecutive slashes are replaced by a single slash.

Example 3:

  • Input: path = "/home/user/Documents/../Pictures"
  • Output: "/home/user/Pictures"
  • Explanation: '..' moves up one level to remove "Documents".

Example 4:

  • Input: path = "/../"
  • Output: "/"
  • Explanation: Moving up from the root is not possible, so the path remains "/".

Example 5:

  • Input: path = "/.../a/../b/c/../d/./"
  • Output: "/.../b/d"
  • Explanation: "..." is a valid directory name, and '..' removes "a" and "c".

Constraints:

  • 1 <= path.length <= 3000
  • path consists of English letters, digits, period '.', slash '/', or underscore '_'.
  • path is always a valid absolute Unix path.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public String simplifyPath(String path) {
        // Store the simplified path components
        Stack<String> stack = new Stack<>();

        // Split the path string using "/" as a delimeter
        for (String s : path.split("/")) {
            if ("..".equals(s)) {
                // If the component is "..", pop the last component from the stack
                if (!stack.isEmpty()) {
                    stack.pop();
                }
            } else if (!s.isEmpty() && !".".equals(s)) {
                // If the component is not empty and not ".", push it onto the stack
                stack.push(s);
            }
        }

        StringBuilder builder = new StringBuilder();
        for (String s : stack) {
            builder.append("/").append(s);
        }

        return builder.isEmpty() ? "/" : builder.toString();
    }
}

SHOW NOTES

Visulization:

Simplify Path


150. Evaluate Reverse Polish Notation

SHOW PROBLEM

Here is the modified problem statement following your format:


Problem Statement

You are given an array of strings tokens that represents an arithmetic expression in Reverse Polish Notation (RPN).

Your task is to evaluate the expression and return an integer that represents the result.

Rules:

  • The valid operators are '+', '-', '*', and '/'.
  • Each operand may be an integer or another expression.
  • Integer division truncates toward zero (e.g., -7 / 2 = -3).
  • There will be no division by zero.
  • The input is guaranteed to be a valid Reverse Polish Notation expression.
  • The result and all intermediate calculations can be represented in a 32-bit integer.

Example 1:

  • Input: tokens = ["2","1","+","3","*"]
  • Output: 9
  • Explanation: ((2 + 1) * 3) = 9.

Example 2:

  • Input: tokens = ["4","13","5","/","+"]
  • Output: 6
  • Explanation: (4 + (13 / 5)) = 6.

Example 3:

  • Input: tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
  • Output: 22
  • Explanation:
    ((10 * (6 / ((9 + 3) * -11))) + 17) + 5  
    = ((10 * (6 / (12 * -11))) + 17) + 5  
    = ((10 * (6 / -132)) + 17) + 5  
    = ((10 * 0) + 17) + 5  
    = (0 + 17) + 5  
    = 17 + 5  
    = 22
    

Constraints:

  • 1 <= tokens.length <= 10^4
  • tokens[i] is either an operator ("+", "-", "*", or "/") or an integer in the range [-200, 200].

Go to Leetcode 🔗
SHOW CODE
import java.util.Stack;

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();

        for (String token : tokens) {
            if (isOperator(token)) {
                int b = stack.pop(); // Second operand (popped first)
                int a = stack.pop(); // First operand (popped second)
                stack.push(compute(a, b, token)); // Push result back to stack
            } else {
                stack.push(Integer.parseInt(token)); // Convert operand to integer and push
            }
        }

        return stack.pop(); // Final result
    }

    private boolean isOperator(String s) {
        return "+".equals(s) || "-".equals(s) || "*".equals(s) || "/".equals(s);
    }

    private int compute(int a, int b, String operator) {
        switch (operator) {
            case "+":
                return a + b;
            case "-":
                return a - b;
            case "*":
                return a * b;
            case "/":
                return a / b; // Integer division truncates toward zero
            default:
                throw new IllegalArgumentException("Invalid operator: " + operator);
        }
    }
}

394. Decode String

SHOW PROBLEM

Problem Statement

You are given an encoded string s. Your task is to return its decoded version.

The encoding rule follows the format: k[encoded_string], where the substring inside the square brackets is repeated exactly k times. The integer k is always a positive number.

You may assume that:

  • The input string s is always valid (i.e., well-formed brackets, no extra spaces).
  • The original data does not contain digits; digits only appear as repetition counts (k).
  • The test cases are designed so that the output length does not exceed 10^5.

Example 1:

  • Input: s = "3[a]2[bc]"
  • Output: "aaabcbc"

Example 2:

  • Input: s = "3[a2[c]]"
  • Output: "accaccacc"

Example 3:

  • Input: s = "2[abc]3[cd]ef"
  • Output: "abcabccdcdcdef"

Constraints:

  • 1 <= s.length <= 30
  • s consists of lowercase English letters, digits, and square brackets (’[’ and ‘]’).
  • s is guaranteed to be a valid input.
  • All integers in s are in the range [1, 300].

Go to Leetcode 🔗
SHOW CODE
import java.util.Stack;

class Solution {
    public String decodeString(String s) {
        Stack<Integer> countStack = new Stack<>();
        Stack<StringBuilder> stringStack = new Stack<>();
        StringBuilder currentString = new StringBuilder();
        int currentCount = 0;

        for (char c : s.toCharArray()) {
            if (Character.isDigit(c)) {
                // Build the repeat count (handles multiple digits)
                currentCount = currentCount * 10 + (c - '0');

            } else if (c == '[') {
                // Push the current count and string to their respective stacks
                countStack.push(currentCount);
                stringStack.push(currentString);
                // Reset for the new encoded block
                currentString = new StringBuilder();
                currentCount = 0;

            } else if (c == ']') {
                // Decode the string inside the brackets
                int repeatTimes = countStack.pop();
                StringBuilder decodedSubstring = new StringBuilder();
                for (int i = 0; i < repeatTimes; i++) {
                    decodedSubstring.append(currentString);
                }

                // Append the decoded string to the previous stored string
                currentString = stringStack.pop().append(decodedSubstring);

            } else { // Normal characters
                currentString.append(c);
            }
        }

        return currentString.toString();
    }
}

KS34 计算器

SHOW PROBLEM

问题描述

请编写一个整数计算器,支持 加法(+)、减法(-)、乘法(*) 三种运算,并且可以正确处理 括号(( )) 的优先级。

输入描述:
一个包含 0-9 数字、运算符 (+、-、*) 和括号 ( ) 的数学表达式。

输出描述:
计算该表达式的结果,并输出 整数 结果。

示例 1

  • 输入
    1+1
    
  • 输出
    2
    

示例 2

  • 输入
    3+2*3*4-1
    
  • 输出
    26
    

示例 3

  • 输入
    (2*(3-4))*5
    
  • 输出
    -10
    

约束条件:

  • 输入表达式 合法,无需考虑错误输入情况。
  • 计算结果为 整数,不会出现小数或浮点数运算。
  • 乘法 * 的优先级高于加法 + 和减法 -,括号 () 可调整运算优先级。

Go to Nowcoder 🔗
SHOW CODE
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        String s = in.nextLine().trim();

        // Evaluate the given expression
        int result = evaluate(s);

        // Output result
        System.out.println(result);
    }

    private static int evaluate(String s) {
        Stack<Integer> numStack = new Stack<>();
        Stack<Character> opStack = new Stack<>();
        int num = 0;
        boolean isParsingNumber = false;

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            if (Character.isDigit(c)) {
                // Construct multi-digit number
                num = num * 10 + (c - '0');
                isParsingNumber = true;
            } else {
                if (isParsingNumber) {
                    numStack.push(num);
                    num = 0;
                    isParsingNumber = false;
                }

                if (c == '(') {
                    opStack.push(c);
                } else if (c == ')') {
                    // Process until '(' encountered
                    while (opStack.peek() != '(') {
                        numStack.push(calculate(numStack.pop(), numStack.pop(), opStack.pop()));
                    }
                    opStack.pop(); // Remove '('
                } else if (isOperator(c)) {
                    // Process operators with higher or equal precedence
                    while (!opStack.isEmpty() &&
                            precedence(opStack.peek()) >= precedence(c)) {
                        numStack.push(calculate(numStack.pop(), numStack.pop(), opStack.pop()));
                    }
                    opStack.push(c);
                }
            }
        }

        // Push last number if any
        if (isParsingNumber) {
            numStack.push(num);
        }

        // Process remaining operators
        while (!opStack.isEmpty()) {
            numStack.push(calculate(numStack.pop(), numStack.pop(), opStack.pop()));
        }

        return numStack.pop();
    }

    private static int calculate(int a, int b, char operator) {
        switch (operator) {
            case '+' :
                return b + a;
            case '-' :
                return b - a;
            case '*' :
                return a * b;
            default:
                throw new IllegalArgumentException("Invalid Operator" + operator);
        }
    }

    private static int precedence(char c) {
        if (c == '+' || c == '-') {
            return 1;
        } else if (c == '*') {
            return 2;
        }
        return 0;
    }

    private static boolean isOperator(char c) {
        return c == '+' || c == '-' || c == '*';

    }
}

SHOW NOTES

When processing numbers in a string, it is important to note that num = num * 10 + (c - '0') can be used to construct multi-digit numbers. This is necessary because numbers larger than 9 are composed of two or more characters. After iterating through the string expression, the last number, if any, must be pushed onto the number stack. Additionally, if the operator stack is not empty, the top operator should be repeatedly popped and calculations performed until the stack is empty.


HW: 栈数据合并

SHOW PROBLEM

问题描述

给定一个空栈,你需要按照顺序向其中压入一串正整数,并在每次压入新整数时,遵循以下合并规则:

  1. 相邻合并:如果栈顶两个元素相等(即 n1 = n2),则将它们全部出栈,并压入新整数 m = 2 * n1
  2. 区间合并:如果栈顶若干个元素的和等于最新压入的元素(即 n1 = n2 + ... + ny,其中 y ∈ [3, x]),则将这些元素全部出栈,并压入新整数 m = 2 * n1
  3. 无操作:如果上述规则均不满足,则不进行任何合并操作。

输入描述
一个由单个空格隔开的正整数字符串(如 "5 6 7 8"),左侧数字先入栈。

  • 每个正整数的取值范围为 [1, 2^31 - 1]
  • 数字个数范围为 [1, 1000]

输出描述
最终栈中存留的元素值,元素间用单个空格隔开,按从栈顶到栈底的顺序输出。

示例 1

  • 输入:
    10 20 50 80 1 1
    
  • 输出:
    2 160
    
  • 解释:
    1. 压入 10, 20, 50, 80,此时 10 + 20 + 50 = 80,合并后 80 → 160,栈为 [160]
    2. 压入 1, 1,由于 1 = 1,合并为 2,栈为 [2, 160]

SHOW CODE
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        int[] nums = Arrays.stream(in.nextLine().trim().split(" "))
                .mapToInt(Integer::parseInt)
                .toArray();

        Stack<Integer> stack = new Stack<>();
        pushAndMerge(nums, stack); // Process numbers and apply merging rules

        formatOutput(stack); // Print the final stack
    }

    private static void pushAndMerge(int[] nums, Stack<Integer> stack) {
        for (int num : nums) {
            stack.push(num); // Push the current number onto the stack

           /*
             1. Rule 1: Merge if top two elements are equal.
             2. Rule 2: Merge if sum of from the second-to-top to certain bottom
                element equal to top.
             3. Merge rule 1 & rule 2: Starting from the top and moving downward,
                if the first element equals the sum of any number of elements below
                it, they can be merged.
           */
            // Apply merging rules until no further merges are possible
            while (stack.size() >= 2) {
                int sum = 0;
                int count = 0;
                boolean merged = false;

                for (int i = stack.size() - 2; i >= 0; i--) {
                    int topNum = stack.peek();
                    sum += stack.get(i);
                    count++;

                    if (sum == topNum) {
                        stack.pop(); // Remove the top element
                        // Remove elements contributing to sum
                        while (count-- > 0) {
                            stack.pop();
                        }
                        stack.push(topNum * 2);
                        merged = true;
                        break;
                    } else if (sum > topNum) {
                        break;
                    }
                }

                if (!merged) {
                    break;
                }
            }
        }
    }

    private static void formatOutput(Stack<Integer> stack) {
        for (int i = stack.size() - 1; i >= 0; i--) {
            System.out.print(stack.get(i) + " ");
        }
    }
}

SHOW NOTES

Starting from the top and moving downward, if the first element equals the sum of any number of elements below it, they can be merged.

Monotonic Stack

A monotonic stack pattern is a technique commonly used to solve problems that involve finding the next greater or next smaller element in a sequence or array, or finding the largest rectangle in a histogram. There are two types of monotonic stacks: the increasing monotonic stack and the decreasing monotonic stack.

  1. An increasing monotonic stack maintains elements in increasing order from bottom to top (i.e., each element is greater than or equal to the element below it). Monotonically Increasing Stack
  2. An decreasing monotonic stack maintains elements in decreasing order from bottom to top (i.e., each element is smaller than or equal to the element below it). Monotonically Decreasing Stack

Creating a monotonic stack commonly involves the following steps (using increasing monotonic stack as an example):

  1. Initialize an empty stack.
  2. Iterate over the array.
  3. Compare the current element with the top element of the stack (if the stack is not empty).
  4. Pop all elements from the stack that are smaller than the current element. This ensures that the stack only contains elements greater than or equal to the current element.
  5. Push the current element onto the stack.

Next Greater Element I

SHOW PROBLEM

The next greater element for an element x in an array is the first element greater than x that appears to the right of x in the same array.

You are given two distinct 0-indexed integer arrays, nums1 and nums2, where nums1 is a subset of nums2.

For each 0 <= i < nums1.length, find the index j such that nums1[i] == nums2[j], and then determine the next greater element of nums2[j] in nums2. If there is no next greater element, return -1 for that query.

Return an array ans of length nums1.length such that ans[i] is the next greater element for nums1[i] as described above.


Example 1:

  • Input: nums1 = [4, 1, 2], nums2 = [1, 3, 4, 2]
  • Output: [-1, 3, -1]
  • Explanation:
    • The next greater element for 4 is not found in nums2, so the answer is -1.
    • The next greater element for 1 is 3.
    • The next greater element for 2 is not found, so the answer is -1.

Example 2:

  • Input: nums1 = [2, 4], nums2 = [1, 2, 3, 4]
  • Output: [3, -1]
  • Explanation:
    • The next greater element for 2 is 3.
    • The next greater element for 4 is not found, so the answer is -1.

Constraints:

  • 1 <= nums1.length <= nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 10⁴
  • All integers in nums1 and nums2 are unique.
  • All integers of nums1 are also present in nums2.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        // Initialize an empty stack to track elements in nums2
        Stack<Integer> stack = new Stack<>();
        // Map to store the next greater element for each number in nums2
        Map<Integer, Integer> nextGreaterMap = new HashMap<>();

        // Traverse nums2 from right to left to populate the nextGreaterMap
        for (int i = nums2.length - 1; i >= 0; i--) {
            int num = nums2[i];

            // Pop elements from stack that are less than or equal to the current number
            while (!stack.isEmpty() && stack.peek() <= num) {
                stack.pop();
            }

            // If stack is empty, no greater element exists, otherwise top of stack is the next greater element
            nextGreaterMap.put(num, stack.isEmpty() ? -1 : stack.peek());

            // Push the current number onto the stack for future comparisons
            stack.push(num);
        }

        // Prepare the result array for nums1 based on nextGreaterMap
        int[] result = new int[nums1.length];
        // Find the next greater element for each number in nums1
        for (int i = 0; i < nums1.length; i++) {
            result[i] = nextGreaterMap.get(nums1[i]);
        }

        return result;
    }
}

SHOW NOTES

When iterating over an increasing monotonic stack from right to left, if the current element is greater than the top of the stack, it means the current element could be the next greater element for the previous ones. In this case, pop the stack.


2487. Remove Nodes From Linked List

SHOW PROBLEM

Problem Statement:

You are given the head of a linked list.

Remove every node which has a node with a greater value anywhere to its right side.

Return the head of the modified linked list after removing the necessary nodes.

Example 1:

  • Input: head = [5, 2, 13, 3, 8]
  • Output: [13, 8]
  • Explanation: The nodes that should be removed are 5, 2, and 3.
    • Node 13 is to the right of node 5.
    • Node 13 is to the right of node 2.
    • Node 8 is to the right of node 3.

Example 2:

  • Input: head = [1, 1, 1, 1]
  • Output: [1, 1, 1, 1]
  • Explanation: Every node has value 1, so no nodes are removed.

Constraints:

  • The number of nodes in the given list is in the range [1, 10^5].
  • 1 <= Node.val <= 10^5.


Original List:

flowchart LR
    5((5)) --> 2((2)) --> 13((13)) --> 3((3)) --> 8((8))

After Removing:

flowchart LR
    13((13)) --> 8((8))

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNodes(ListNode head) {
        Stack<ListNode> stack = new Stack<>();

        ListNode current = head;
        while (current != null) {
            while (!stack.isEmpty() && stack.peek().val < current.val) {
                stack.pop(); // Remove nodes from the stack that are samller than the current node
            }

            if (!stack.isEmpty()) {
                // Update the next pointer of the top node in the stack
                stack.peek().next = current;
            }

            // Push the current node onto the stack
            stack.push(current);
            current = current.next;
        }

        return stack.get(0);
    }
}

739. Daily Temperatures

SHOW PROBLEM

Problem Statement:

Given an array temperatures where each element represents the temperature of a particular day, return an array answer such that answer[i] represents the number of days you need to wait after the i-th day to get a warmer temperature. If no future day has a warmer temperature, set answer[i] = 0.

Example 1:

  • Input: temperatures = [73,74,75,71,69,72,76,73]
  • Output: [1,1,4,2,1,1,0,0]

Example 2:

  • Input: temperatures = [30,40,50,60]
  • Output: [1,1,1,0]

Example 3:

  • Input: temperatures = [30,60,90]
  • Output: [1,1,0]

Constraints:

  • 1 <= temperatures.length <= 100,000
  • 30 <= temperatures[i] <= 100

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        Stack<Integer> stack = new Stack<>();
        int[] answer = new int[n];

        for (int i = 0; i < n; i++) {
            // Pop elements from the stack as long as the current temperature is higher than
            // the temperature at the index of the stack's top
            while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
                int topIndex = stack.pop();
                answer[topIndex] = i - topIndex; // Calculate the difference in days
            }

            // Push current day's index onto the stack
            stack.push(i);
        }

        return answer;
    }
}

1047. Remove All Adjacent Duplicates In String

SHOW PROBLEM

Problem Statement:

You are given a string s consisting of lowercase English letters. A duplicate removal is defined as removing two adjacent and equal letters.

You need to repeatedly perform duplicate removals on the string s until no more such removals are possible. Once all duplicate removals are completed, return the resulting string.

It can be proven that the final answer is unique.

Example 1:

  • Input: s = "abbaca"
  • Output: "ca"
  • Explanation: Initially, we can remove the pair “bb” to get "aaca". Then, we can remove the pair “aa” to get "ca". No more duplicate removals are possible, so the final string is "ca".

Example 2:

  • Input: s = "azxxzy"
  • Output: "ay"
  • Explanation: We can remove the pair “xx” to get "azy". Then, no more removals are possible, so the final string is "ay".

Constraints:

  • 1 <= s.length <= 10^5
  • s consists of lowercase English letters.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public String removeDuplicates(String s) {
        Stack<Character> stack = new Stack<>();

        for (char c : s.toCharArray()) {
            if (!stack.isEmpty() && stack.peek() == c) {
                stack.pop();
            } else {
                stack.push(c);
            }
        }

        StringBuilder builder = new StringBuilder();
        for (char c : stack) {
            builder.append(c);
        }

        return builder.toString();
    }
}

// class Solution {
//     public String removeDuplicates(String s) {
//         StringBuilder builder = new StringBuilder();

//         for (char c : s.toCharArray()) {
//             if (!builder.isEmpty() && builder.charAt(builder.length() - 1) == c) {
//                 builder.setLength(builder.length() - 1);
//             } else {
//                 builder.append(c);
//             }
//         }

//         return builder.toString();
//     }
// }

Remove All Adjacent Duplicates in String II

SHOW PROBLEM

You are given a string s and an integer k. A k-duplicate removal consists of selecting k adjacent and identical letters from s and removing them, causing the left and right sides of the deleted substring to merge together.

You must repeatedly perform k-duplicate removals on s until no further removals are possible.

Return the final string after all such duplicate removals have been made. It is guaranteed that the answer is unique.


Example 1:

  • Input: s = "abcd", k = 2
  • Output: "abcd"
  • Explanation:
    • There are no adjacent duplicate characters, so no removal happens.

Example 2:

  • Input: s = "deeedbbcccbdaa", k = 3
    Output: "aa"
  • Explanation:
    • First, delete “eee” and “ccc”, which results in "ddbbbdaa".
    • Then, delete “bbb”, resulting in "dddaa".
    • Finally, delete “ddd”, resulting in "aa".

Example 3:

Input: s = "pbbcggttciiippooaais", k = 2
Output: "ps"


Constraints:

  • 1 <= s.length <= 10^5
  • 2 <= k <= 10^4
  • s only contains lowercase English letters.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public String removeDuplicates(String s, int k) {
        // Stack to store characters along with their consecutive count
        Stack<int[]> stack = new Stack<>();

        // Iterate over each character in the string
        for (char c : s.toCharArray()) {
            // Check if the stack is not empty and the current character is the same as the top character
            if (!stack.isEmpty() && stack.peek()[0] == c) {
                // Increment the count of top character by 1
                stack.peek()[1]++;
            } else {
                // Otherwise, push the current character onto the stack with count 1
                stack.push(new int[]{c, 1});
            }

            // If the top character's count reaches k, remove it from the stack
            if (stack.peek()[1] == k) {
                stack.pop();
            }
        }

        // Construct result string from the stack
        StringBuilder builder = new StringBuilder();
        for (int[] entry : stack) {
            builder.append(String.valueOf((char) entry[0]).repeat(entry[1]));
        }

        return builder.toString();
    }
}

Largest Rectangle in Histogram

SHOW PROBLEM

Problem Statement:

Given an array of integers heights representing the heights of the histogram’s bars, where the width of each bar is 1, return the area of the largest rectangle that can be formed in the histogram.


Example 1:

  • Input: heights = [2, 1, 5, 6, 2, 3]
  • Output: 10
  • Explanation:
    • The above is a histogram where each bar has a width of 1.
    • The largest rectangle is formed by the bars of height 5 and 6, with a width of 2, yielding an area of 10 units.

Example 2:

  • Input: heights = [2, 4]
  • Output: 4
  • Explanation:
    • The largest rectangle is formed by the bar of height 4, yielding an area of 4 units.

Constraints:

  • 1 <= heights.length <= 10^5
  • 1 <= heights[i] <= 10^4

Go to Leetcode 🔗
SHOW CODE
import java.util.Stack; class Solution { public int largestRectangleArea(int[] heights) { // Stack to store indices of the bars Stack<Integer> stack = new Stack<>(); int maxArea = 0; // Inerate through all the bars for (int i = 0; i < heights.length; i++) { // While the stack is not empty and the current bar is shorter than the bar at the stack's top while (!stack.isEmpty() && heights[i] < heights[stack.peek()]) { // Pop the top index from the stack int height = heights[stack.pop()]; // Calculate the width of the rectangle formed by the popped bar // If the stack is empty, it means the popped bar was the smallest so far, // so the width is 'i' int width = stack.isEmpty() ? i : i - stack.peek() - 1; // Calculate the area with the popped bar as the shortest bar maxArea = Math.max(maxArea, height * width); } // Push the current bar's index onto the stack stack.push(i); } // Process the remaining bars in the stack while (!stack.isEmpty()) { int height = heights[stack.pop()]; // If the stack is empty, it means the popped bar was the smallest so far int width = stack.isEmpty() ? heights.length : heights.length - stack.peek() - 1; maxArea = Math.max(maxArea, height * width); } return maxArea; } }

SHOW NOTES

To solve this problem, an increasing monotonic stack can be used. When the height of the current bar is less than the height of the bar at the top of the stack, the index of the top bar is popped to calculate the maximal area. Since the elements in the stack are in increasing order, the bar at the top always forms a rectangle with the bar below it, as illustrated in the follow image.

Largest Rectangel in Histogram

For example, consider an increasing monotonic stack containing the elements 2, 5, 6, and the current element is 2 (less than 6) with index 4. The indices of the three elements in the stack are 0, 2, 3, respectively. First, pop the bar with height 6. The area is calculated as height x width = 6 x (4 - 2 - 1). In this case, 2 is the index of element 5, because after popping the element 6, the top of the stack becomes index 2. Therefore, the width is calculated as the index of the current element minus stack.peek() minus 1, if the stack is not empty after popping.


Tree Breadth First Search

The Tree Breadth First Search Pattern is a programming technique used for traversing a tree or graph level by level. It explores all nodes at the current depth before moving to the next level. This approach leverages the FIFO (First-In-First-Out) principle of the queue data structure to ensure that all nodes at a given level are processed before their child nodes. It is commonly used to solve problems that require level-wise processing, such as finding the shortest path or performing level-order operations.

The Tree Breadth First Search Pattern typically involves the following steps:

  1. Empty Check: Check if the tree or graph is null.
  2. Queue Initialization: Initialize an empty queue and enqueue the root node.
  3. Process and Enqueue: While the queue is not empty, compute the current queue size, dequeue nodes one by one (dequeue times equal to the size of the current level), process each node, and enqueue its children if they exist.
  4. Repeat: Continue processing nodes until the queue is empty.

Binary Tree Level Order Traversal

SHOW PROBLEM

Problem Statement:

Given the root of a binary tree, return the level order traversal of its nodes’ values. (i.e., traverse the tree from left to right, level by level).


Example 1:

Binary Tree Level Order Traversal

  • Input: root = [3, 9, 20, null, null, 15, 7]
  • Output: [[3], [9, 20], [15, 7]]

Example 2:

  • Input: root = [1]
  • Output: [[1]]

Example 3:

  • Input: root = []
  • Output: []

Constraints:

  • The number of nodes in the tree is in the range [0, 2000].
  • -1000 <= Node.val <= 1000.

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode() {}
 * TreeNode(int val) { this.val = val; }
 * TreeNode(int val, TreeNode left, TreeNode right) {
 * this.val = val;
 * this.left = left;
 * this.right = right;
 * }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();

        // Return an empty list if the tree is empty
        if (root == null) {
            return result;
        }

        // Initialize a queue for level order traversal
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);  // Add the root node to the queue

        while (!queue.isEmpty()) {
            int levelSize = queue.size();  // Get the number of nodes at the current level
            List<Integer> currentLevel = new ArrayList<>();

            // Process all nodes at the current level
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                currentLevel.add(node.val);

                // Add left and right children to the queue if they exist
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }

            // Add the current level's result to the overall result
            result.add(currentLevel);
        }

        return result;  // Return the level order traversal result
    }
}

Binary Tree Zigzag Level Order Traversal

SHOW PROBLEM

Problem Statement:

Given the root of a binary tree, return the zigzag level order traversal of its nodes’ values. That is, traverse the tree from left to right for the first level, then right to left for the next level, and alternate between the two directions for each subsequent level.


Example 1:

Binary Tree Zigzag Level Order Traversal

  • Input: root = [3, 9, 20, null, null, 15, 7]
  • Output: [[3], [20, 9], [15, 7]]

Example 2:

  • Input: root = [1]
  • Output: [[1]]

Example 3:

  • Input: root = []
  • Output: []

Constraints:

  • The number of nodes in the tree is in the range [0, 2000].
  • -100 <= Node.val <= 100.

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();

        // Return empty result if tree is empty
        if (root == null) {
            return result;
        }

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);  // Add the root node to the queue

        boolean leftToRight = true;  // Flag to alternate direction of traversal

        while (!queue.isEmpty()) {
            int levelSize = queue.size();
            List<Integer> currentLevel = new LinkedList<>();

            // Process each node at the current level
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();

                // Add node value based on the current direction
                if (leftToRight) {
                    currentLevel.add(node.val);  // Left to right
                } else {
                    currentLevel.add(0, node.val);  // Right to left
                }

                // Enqueue left and right children if they exist
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }

            result.add(currentLevel);
            leftToRight = !leftToRight;  // Toggle the direction for the next level
        }

        return result;
    }
}

SHOW NOTES

Visualization

Binary Tree Zigzag Level Order Traversal


Populating Next Right Pointers in Each Node

SHOW PROBLEM

Problem Statement:

You are given a perfect binary tree where all leaves are on the same level, and every parent node has two children. The binary tree is defined as follows:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

Populate each next pointer to point to its next right node. If there is no next right node, the next pointer should be set to NULL.

Initially, all next pointers are set to NULL.


Example 1:

  • Input: root = [1,2,3,4,5,6,7]
  • Output: [1,#,2,3,#,4,5,6,7,#]
  • Explanation: Given the above perfect binary tree (Figure A), your function should populate each next pointer to point to its next right node, just like in Figure B. The serialized output is in level order as connected by the next pointers, with # signifying the end of each level.

Example 2:

  • Input: root = []
  • Output: []

Constraints:

  • The number of nodes in the tree is in the range [0, 2^12 - 1].
  • -1000 <= Node.val <= 1000.

Go to Leetcode 🔗
SHOW CODE
/*
// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;
    public Node next;

    public Node() {}
    
    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, Node _left, Node _right, Node _next) {
        val = _val;
        left = _left;
        right = _right;
        next = _next;
    }
};
*/

class Solution {
    public Node connect(Node root) {
        // If the root is null, return null as there's nothing to connect
        if (root == null) {
            return root;
        }

        // Initialize a queue for level order traversal
        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);

        // Process nodes level by level
        while (!queue.isEmpty()) {
            int levelSize = queue.size(); // Get the number of nodes at the current level

            // Iterate through nodes at the current level
            for (int i = 0; i < levelSize; i++) {
                Node node = queue.poll();

                // Set the next pointer for all nodes except the last node at the level
                if (i < levelSize - 1) {
                    node.next = queue.peek();  // Next node at the same level
                } else {
                    node.next = null;  // Last node points to null
                }

                // Enqueue left and right children for the next level
                if (node.left != null) {
                    queue.offer(node.left);
                }

                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
        }

        // Return the root of the tree with updated next pointers
        return root;
    }
}

SHOW NOTES Populating Next Right Pointers in Each Node


111. Minimum Depth of Binary Tree

SHOW PROBLEM

Problem Statement:

You are given the root of a binary tree. Implement an algorithm to return the minimum depth of the tree.

The minimum depth of a binary tree is the number of nodes along the shortest path from the root node down to the nearest leaf node.

Note: A leaf is defined as a node with no children (both left and right children are null).

Example 1:

  • Input: root = [3,9,20,null,null,15,7] Minimum Depth of Binary Tree
  • Output: 2
  • Explanation: The shortest path is from the root node 3 to the leaf node 9, which consists of 2 nodes: 3 -> 9.

Example 2:

  • Input: root = [2,null,3,null,4,null,5,null,6]
  • Output: 5
  • Explanation: The shortest path is from the root node 2 to the leaf node 6, which consists of 5 nodes: 2 -> 3 -> 4 -> 5 -> 6.

Constraints:

  • The number of nodes in the tree is in the range [0, 10^5].
  • -1000 <= Node.val <= 1000

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int depth = 0;
        while (!queue.isEmpty()) {
            int levelSize = queue.size();
            depth++;

            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                
                // Check if the current node is a leaf node
                if (node.left == null && node.right == null) {
                    return depth;
                }

                // Insert the children of the current node to the queue
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
        }

        return depth;
    }
}

967. Numbers With Same Consecutive Differences

SHOW PROBLEM

Problem Statement

You are given two integers n and k. Your task is to return an array of all n-digit integers where the absolute difference between every two consecutive digits is exactly k.

The numbers in the result must not have leading zeros. For example, numbers like 02 and 043 are not valid.

You may return the answer in any order.

Example 1:

  • Input: n = 3, k = 7
  • Output: [181, 292, 707, 818, 929]
  • Explanation: Numbers like 070 are not valid because they have leading zeros.

Example 2:

  • Input: n = 2, k = 1
  • Output: [10,12,21,23,32,34,43,45,54,56,65,67,76,78,87,89,98]

Constraints:

  • 2 <= n <= 9
  • 0 <= k <= 9

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[] numsSameConsecDiff(int n, int k) {
        List<Integer> result = new ArrayList<>();
        Queue<Integer> queue = new LinkedList<>();

        // Start numbers from 1 to 9 (no leading zeros)
        for (int i = 1; i <= 9; i++) {
            queue.offer(i);
        }

        while (!queue.isEmpty()) {
            int num = queue.poll();

            // A n-digit built, add it to the result
            if (String.valueOf(num).length() == n) {
                result.add(num);
                continue;
            }

            int lastDigit = num % 10;

            // Try adding lastDigit + k if it's a valid digit (0-9)
            if (lastDigit + k <= 9) {
                queue.offer(num * 10 + (lastDigit + k));
            }

            // Try adding lastDigit - k if it's a valid digit (0-9)
           if (k > 0 && lastDigit - k >= 0) {
                queue.offer(num * 10 + (lastDigit - k));
            }
        }

        return result.stream().mapToInt(Integer::intValue).toArray();
    }
}

Tree Depth First Search

The Tree Depth-First Search Pattern is a programming technique commonly used to explore tree or graph data structures. It begins at the root node and explores all nodes in a single branch as far as possible before backtracking. This approach is especially useful for problems that involve tree traversal or handling hierarchical data.

Implementing the Tree Depth-First Search Pattern commonly involves the following steps:

  1. Empty Check: If the current node is null, return.
  2. Leaf Node Check: If both the left and right children of the current node are null, then the current node is a leaf node.
  3. Recursive Traversal: Recursively call the function on the left and right children of the current node.
  4. Result Computation: Compute the result based on the results obtained from the left and right branches.

94. Binary Tree Inorder Traversal

SHOW PROBLEM

Problem Statement:

You are given the root of a binary tree. Implement an algorithm to return the inorder traversal of its nodes’ values. Inorder traversal involves visiting the left subtree, then the node itself, and finally the right subtree.

The output should be an array of node values visited during the inorder traversal.

Example 1:

  • Input: root = [1,null,2,3] Binary Tree Inorder Traversal Example 1
  • Output: [1, 3, 2]
  • Explanation: In the binary tree, the nodes are visited in the following order: 1 -> 3 -> 2.

Example 2:

  • Input: root = [1,2,3,4,5,null,8,null,null,6,7,9] Binary Tree Inorder Traversal Example 2
  • Output: [4, 2, 6, 5, 7, 1, 3, 9, 8]
  • Explanation: In the binary tree, the nodes are visited in the following order: 4 -> 2 -> 6 -> 5 -> 7 -> 1 -> 3 -> 9 -> 8.

Example 3:

  • Input: root = []
  • Output: []
  • Explanation: The tree is empty, so the result is an empty list.

Example 4:

  • Input: root = [1]
  • Output: [1]
  • Explanation: The tree consists of only one node, so the result is the node itself.

Constraints:

  • The number of nodes in the tree is in the range [0, 100].
  • -100 <= Node.val <= 100

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        inorderHelper(root, result);

        return result;
    }

    private static void inorderHelper(TreeNode node, List<Integer> result) {
        // Base case
        if (node == null) {
            return;
        }

        // Traverse the left subtree
        inorderHelper(node.left, result);
        // Visit the current node
        result.add(node.val);
        // Traverse the right subtree
        inorderHelper(node.right, result);
    }
}
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode current = root;

        while (current != null || !stack.isEmpty()) {
            // Reach the leftmost node of the current node
            while (current != null) {
                stack.push(current);
                current = current.left;
            }

            // Pop the node from the stack when current is null
            current = stack.pop();
            result.add(current.val);

            // Move to the right subtree
            current = current.right;
        }

        return result;
    }
}

144. Binary Tree Preorder Traversal

SHOW PROBLEM

Problem Statement:

You are given the root of a binary tree. Implement an algorithm to return the preorder traversal of its nodes’ values. Preorder traversal involves visiting the node first, then recursively visiting the left subtree, and finally visiting the right subtree.

Preorder Traversal Steps:

  1. Visit the root node.
  2. Traverse the left subtree.
  3. Traverse the right subtree.

The result should be an array of node values visited during the preorder traversal.

Example 1:

  • Input: root = [1,null,2,3] Binary Tree Preorder Traversal Example 1
  • Output: [1, 2, 3]
  • Explanation: In the binary tree, the nodes are visited in the following order: 1 -> 2 -> 3.

Example 2:

  • Input: root = [1,2,3,4,5,null,8,null,null,6,7,9] Binary Tree Preorder Traversal Example 2
  • Output: [1, 2, 4, 5, 6, 7, 3, 8, 9]
  • Explanation: In the binary tree, the nodes are visited in the following order: 1 -> 2 -> 4 -> 5 -> 6 -> 7 -> 3 -> 8 -> 9.

Example 3:

  • Input: root = []
  • Output: []
  • Explanation: The tree is empty, so the result is an empty list.

Example 4:

  • Input: root = [1]
  • Output: [1]
  • Explanation: The tree consists of only one node, so the result is the node itself.

Constraints:

  • The number of nodes in the tree is in the range [0, 100].
  • -100 <= Node.val <= 100

Follow up: Recursive solution is trivial, could you do it iteratively?


Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        preorderHelper(root, result);
        
        return result;
    }

    private static void preorderHelper(TreeNode node, List<Integer> result) {
        // Base case
        if (node == null) {
            return;
        }

        // Visit the current node
        result.add(node.val);
        // Traverse the left subtree
        preorderHelper(node.left, result);
        // Traverse the right subtree
        preorderHelper(node.right, result);
    }
}

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();

        if (root == null) {
            return result;
        }

        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);

        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();

            // Push the right child first so that the left child processed first
            if (node.right != null) {
                stack.push(node.right);
            }
            if (node.left != null) {
                stack.push(node.left);
            }

            result.add(node.val);
        }

        return result;
    }
}

145. Binary Tree Postorder Traversal

SHOW PROBLEM

Problem Statement:

You are given the root of a binary tree. Implement an algorithm to return the postorder traversal of its nodes’ values. Postorder traversal involves visiting the left subtree, then the right subtree, and finally the node itself.

Postorder Traversal Steps:

  1. Traverse the left subtree.
  2. Traverse the right subtree.
  3. Visit the root node.

The result should be an array of node values visited during the postorder traversal.

Example 1:

  • Input: root = [1,null,2,3] Binary Tree Inorder Traversal Example 1
  • Output: [3, 2, 1]
  • Explanation: In the binary tree, the nodes are visited in the following order: 3 -> 2 -> 1.

Example 2:

  • Input: root = [1,2,3,4,5,null,8,null,null,6,7,9] Binary Tree Inorder Traversal Example 2
  • Output: [4, 6, 7, 5, 2, 9, 8, 3, 1]
  • Explanation: In the binary tree, the nodes are visited in the following order: 4 -> 6 -> 7 -> 5 -> 2 -> 9 -> 8 -> 3 -> 1.

Example 3:

  • Input: root = []
  • Output: []
  • Explanation: The tree is empty, so the result is an empty list.

Example 4:

  • Input: root = [1]
  • Output: [1]
  • Explanation: The tree consists of only one node, so the result is the node itself.

Constraints:

  • The number of nodes in the tree is in the range [0, 100].
  • -100 <= Node.val <= 100

Follow-up: A recursive solution is trivial. Could you implement the solution iteratively?


Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode() {}
 * TreeNode(int val) { this.val = val; }
 * TreeNode(int val, TreeNode left, TreeNode right) {
 * this.val = val;
 * this.left = left;
 * this.right = right;
 * }
 * }
 */
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        postorderHelper(root, result);
        return result;
    }

    private static void postorderHelper(TreeNode root, List<Integer> result) {
        // Base case
        if (root == null) {
            return;
        }

        // Traverse the left subtree
        postorderHelper(root.left, result);
        // Traverse the right subtree
        postorderHelper(root.right, result);
        // Visit the current node
        result.add(root.val);
    }
}
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();

        if (root == null) {
            return result;
        }

        Stack<TreeNode> stack1 = new Stack<>();
        Stack<TreeNode> stack2 = new Stack<>();

        stack1.push(root);

        while (!stack1.isEmpty()) {
            TreeNode node = stack1.pop();
            stack2.push(node);

            if (node.left != null) {
                stack1.push(node.left);
            }
            if (node.right != null) {
                stack1.push(node.right);
            }
        }

        while (!stack2.isEmpty()) {
            result.add(stack2.pop().val);
        }

        return result;
    }
}

226. Invert Binary Tree

SHOW PROBLEM

Problem:

Given the root of a binary tree, invert (mirror) the tree and return its root.

The tree should be inverted such that:

  • The left and right children of all nodes are swapped.
  • You should perform this transformation in-place, meaning no new tree should be created.

Example 1:

  • Input: root = [4,2,7,1,3,6,9]
  • Output: [4,7,2,9,6,3,1]

Example 2:

  • Input: root = [2,1,3]
  • Output: [2,3,1]

Example 3:

  • Input: root = []
  • Output: []

Constraints:

  • The number of nodes in the tree is in the range [0, 100].
  • Node values are integers, where -100 <= Node.val <= 100.

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode invertTree(TreeNode root) {
        // Base case
        if (root == null) {
            return root;
        }

        // Swap the left and right children
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;

        // Recursively invert the left and right subtrees
        invertTree(root.left);
        invertTree(root.right);

        return root;
    }
}

104. Maximum Depth of Binary Tree

SHOW PROBLEM

Problem Statement:

You are given the root of a binary tree. Implement an algorithm to return the maximum depth of the tree.

The maximum depth of a binary tree is the number of nodes along the longest path from the root node down to the farthest leaf node.

Example 1:

  • Input: root = [3,9,20,null,null,15,7] Maximum Depth of Binary Tree
  • Output: 3
  • Explanation: The longest path is from the root node 3 to the leaf nodes 15 or 7, which consists of 3 nodes: 3 -> 20 -> 15 or 3 -> 20 -> 7.

Example 2:

  • Input: root = [1,null,2]
  • Output: 2
  • Explanation: The longest path is from the root node 1 to the leaf node 2, which consists of 2 nodes: 1 -> 2.

Constraints:

  • The number of nodes in the tree is in the range [0, 10^4].
  • -100 <= Node.val <= 100

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        // Base case
        if (root == null) {
            return 0;
        }

        // Recursively calculate the left subtree depth
        int leftDepth = maxDepth(root.left);
        // Recursively calculate the right subtree depth
        int rightDepth = maxDepth(root.right);

        // The current node depth equals to the maximum left or right subtree depth plus 1
        return Math.max(leftDepth, rightDepth) + 1;
        
    }
}

SHOW NOTES

Visulization:

Maximum Depth of Binary Tree


98. Validate Binary Search Tree

SHOW PROBLEM

Problem Statement:

You are given the root of a binary tree. Implement an algorithm to determine if it is a valid binary search tree (BST).

A valid BST is defined as follows:

  1. The left subtree of a node contains only nodes with keys less than the node’s key.
  2. The right subtree of a node contains only nodes with keys greater than the node’s key.
  3. Both the left and right subtrees must also be valid binary search trees.

Example 1:

  • Input: root = [2,1,3] Validate Binary Search Tree Example 1
  • Output: true

Example 2:

  • Input: root = [5,1,4,null,null,3,6] Validate Binary Search Tree Example 2
  • Output: false
  • Explanation: The tree is not a valid BST because the value of the right child 4 is less than the root node’s value 5, violating the BST property.

Constraints:

  • The number of nodes in the tree is in the range [1, 10^4].
  • -2^31 <= Node.val <= 2^31 - 1

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
        return isValidBSTHelper(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    private static boolean isValidBSTHelper(TreeNode node, long min, long max) {
        // Base case 
        if (node == null) {
            return true;
        }

        if (node.val <= min || node.val >= max) {
            return false;
        }

        // Recursively check the left (with updated upper limit) and right (with updated lower limit)
        return isValidBSTHelper(node.left, min, node.val) && isValidBSTHelper(node.right, node.val, max);
    }
}

SHOW NOTES

For every left child, the upper limit of its permissible range is updated to the current node’s value. Similarly, for every right child, the lower limit is updated to the current node’s value.


Path Sum

SHOW PROBLEM

Problem Description:

Given the root of a binary tree and an integer targetSum, return true if the tree has a root-to-leaf path such that the sum of all the node values along the path equals targetSum.

  • A leaf is a node with no children.

Example 1:

Path Sum

  • Input: root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
  • Output: true
  • Explanation: The root-to-leaf path that sums to the target value is: 5 -> 4 -> 11 -> 2. This path sums to 22.

Example 2:

Path Sum

  • Input: root = [1,2,3], targetSum = 5
  • Output: false
  • Explanation: There are two root-to-leaf paths:
    • (1 → 2): The sum is 3.
    • (1 → 3): The sum is 4.
    • Neither of these paths has a sum equal to 5.

Example 3:

  • Input: root = [], targetSum = 0
  • Output: false
  • Explanation: Since the tree is empty, there are no root-to-leaf paths.

Constraints:

  • The number of nodes in the tree is in the range [0, 5000].
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode() {}
 * TreeNode(int val) { this.val = val; }
 * TreeNode(int val, TreeNode left, TreeNode right) {
 * this.val = val;
 * this.left = left;
 * this.right = right;
 * }
 * }
 */
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        // Return false if the node is null
        if (root == null) {
            return false;
        }

        // Check if current node is a leaf node
        if (root.left == null && root.right == null) {
            return root.val == targetSum;
        }

        // Recursively check left and right subtrees with the reduced target
        targetSum -= root.val; // Reduce the target sum by the current node's value
        return hasPathSum(root.left, targetSum) || hasPathSum(root.right, targetSum);
    }
}

SHOW NOTES

Visulization

Path Sum


Path Sum II

SHOW PROBLEM

Problem Statement:

Given the root of a binary tree and an integer targetSum, return all root-to-leaf paths where the sum of the node values in the path equals targetSum. Each path should be returned as a list of node values, not node references.

A root-to-leaf path is a path starting from the root and ending at any leaf node. A leaf is a node with no children.


Example 1:

Path Sum II Example 1

  • Input: root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
  • Output: [[5,4,11,2],[5,8,4,5]]
  • Explanation: There are two paths whose sum equals targetSum:
    • 5 + 4 + 11 + 2 = 22
    • 5 + 8 + 4 + 5 = 22

Example 2:

Path Sum II Example 2

  • Input: root = [1,2,3], targetSum = 5
  • Output: []

Example 3:

  • Input: root = [1,2], targetSum = 0
  • Output: []

Constraints:

  • The number of nodes in the tree is in the range [0, 5000].
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<List<Integer>> result = new ArrayList<>();
        findPaths(root, targetSum, new ArrayList<>(), result);
        return result;
    }

    private void findPaths(TreeNode node, int remainingSum, List<Integer> currentPath, List<List<Integer>> result) {
        // Base case: return if the node is null
        if (node == null) return;

        // Add the current node's value to the path
        currentPath.add(node.val);

        // If it's a leaf node and the remaining sum equals the node's value, add the path to the result
        if (node.left == null && node.right == null && node.val == remainingSum) {
            result.add(new ArrayList<>(currentPath));  // Add a copy of current path
        } else {
            // Recursively explore left and right subtrees, updating the remaining sum
            findPaths(node.left, remainingSum - node.val, currentPath, result);
            findPaths(node.right, remainingSum - node.val, currentPath, result);
        }

        // Backtrack: remove the current node from the path
        currentPath.remove(currentPath.size() - 1);
    }
}

SHOW NOTES

Visulization:

Path Sum II Visulization


Path Sum III

SHOW PROBLEM

Problem Statement:

Given the root of a binary tree and an integer targetSum, return the number of paths where the sum of the values along the path equals targetSum.

A path is a sequence of nodes where each consecutive node in the path is the child of the previous node. A path does not need to start or end at the root or a leaf, but it must go downwards (i.e., traveling only from parent nodes to child nodes).


Example 1:

Path Sum Example 1

  • Input: root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
  • Output: 3
  • Explanation: The paths that sum to 8 are:
    • Path 1: 5 → 3 → 2
    • Path 2: 5 → 3
    • Path 3: -3 → 11

Example 2:

  • Input: root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
  • Output: 3
  • Explanation: The paths that sum to 22 are:
    • Path 1: 5 → 4 → 11 → 2
    • Path 2: 5 → 8 → 4 → 5
    • Path 3: 8 → 4 → 7

Constraints:

  • The number of nodes in the tree is in the range [0, 1000].
  • -10^9 <= Node.val <= 10^9
  • -1000 <= targetSum <= 1000

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode() {}
 * TreeNode(int val) { this.val = val; }
 * TreeNode(int val, TreeNode left, TreeNode right) {
 * this.val = val;
 * this.left = left;
 * this.right = right;
 * }
 * }
 */
class Solution {
    public int pathSum(TreeNode root, int targetSum) {
        HashMap<Long, Integer> prefixSumMap = new HashMap<>();
        // Base case: a path with current sum matches target sum exitst at the root
        prefixSumMap.put(0L, 1);

        return pathSumRecursive(root, 0, targetSum, prefixSumMap);
    }

    private static int pathSumRecursive(TreeNode node, long currentSum, int targetSum, Map<Long, Integer> prefixSumMap) {
        if (node == null) {
            return 0;
        }

        // Update the current sum
        currentSum += node.val;

        // Calculate the number of valid paths ending at this node
        int pathCount = prefixSumMap.getOrDefault(currentSum - targetSum, 0);

        // Add the current sum to the map
        prefixSumMap.put(currentSum, prefixSumMap.getOrDefault(currentSum, 0) + 1);

        // Recur for left and right sub-trees
        pathCount += pathSumRecursive(node.left, currentSum, targetSum, prefixSumMap);
        pathCount += pathSumRecursive(node.right, currentSum, targetSum, prefixSumMap);

        // Remove the current path sum to backtrack
        prefixSumMap.put(currentSum, prefixSumMap.get(currentSum) - 1);

        return pathCount;
    }
}

HW: 商品推荐

SHOW PROBLEM

题目描述

某电商 APP 希望基于用户的商品浏览历史记录,按照商品的热度值,给用户推荐最合适的商品序列。用户浏览过的商品和其关联的商品构成了一棵 三叉树(每个商品最多关联 3 个其他商品)。每个商品都有一个热度值,系统根据购买量、搜索热度等计算得出。

请根据用户浏览的商品关联信息,找到商品热度值总和最高的推荐商品序列,并返回该热度值

输入描述:

  1. 第一行:一个整数 n,表示商品关联数组的长度,1 ≤ n ≤ 10000
  2. 第二行:一个整数数组,表示商品的关联关系:
    • 第一个元素 为用户浏览的起始商品的热度值。
    • 其后,每连续 3 个元素 代表上一个商品的 最多 3 个 关联商品的热度值(-1 表示该位置无关联商品)。
    • 商品的热度值范围 [-2000, 2000]-1 仅用于占位,不表示实际的商品。

注意

  • 商品关联关系无环,即不会形成闭环结构。
  • 每个商品只能在推荐序列中出现一次
  • 目标是找到热度值总和最高的商品推荐序列

输出描述: 找到商品热度值总和最高的 推荐商品序列,并返回该 热度值总和

输入:

19  
20 12 30 15 -1 -1 -1 -1 -1 -1 15 5 25 -1 -1 -1 16 -1 22  

解析

  • 构造出的三叉树如下:
        20
      / | \
     12 30 15
          / | \
        15  5 25
          / | \
        16 -1 22
  • 从根节点 20 开始,找到热度值总和最高的路径
20 -> 15 -> 5 -> 22 = 62

输出 62


SHOW CODE
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        int n = Integer.parseInt(in.nextLine().trim());
        int[] nums = Arrays.stream(in.nextLine().trim().split(" "))
                .mapToInt(Integer::parseInt)
                .toArray();

        // Build a ternary tree based on nums array
        TreeNode root = buildeTernaryTree(nums);

        // Calculate max heat value
        int maxHeatValue = getMaxHeatValue(root);

        // Output result
        System.out.println(maxHeatValue);

    }

    private static int getMaxHeatValue(TreeNode node) {
        return dfs(node, 0);
    }

    private static int dfs(TreeNode node, int currentSum) {
        if (node == null) {
            return 0; // Return 0 for null nodes
        }

        // Add the current node's value to the running sum
        currentSum += node.val;

        // If it's a leaf node, return the current sum
        if (node.left == null && node.mid == null && node.right == null) {
            return currentSum;
        }

        // Recursively compute the maximum left, mid, and right subtree
        int leftSum = dfs(node.left, currentSum);
        int midSum = dfs(node.mid, currentSum);
        int rightSum = dfs(node.right, currentSum);

        return Math.max(leftSum, Math.max(midSum, rightSum));

    }

    private static TreeNode buildeTernaryTree(int[] nums) {
        if (nums.length == 0 || nums[0] == -1) {
            return null;
        }

        TreeNode root = new TreeNode(nums[0]);
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        int i = 1;
        while (i < nums.length) {
            TreeNode current = queue.poll();

            if (nums[i] != -1) {
                current.left = new TreeNode(nums[i]);
                queue.offer(current.left);
            }
            i++;

            if (i < nums.length && nums[i] != -1) {
                current.mid = new TreeNode(nums[i]);
                queue.offer(current.mid);
            }
            i++;

            if (i < nums.length && nums[i] != -1) {
                current.right = new TreeNode(nums[i]);
                queue.offer(current.right);
            }
            i++;
        }

        return root;
    }
}

class TreeNode {
    int val;
    TreeNode left, mid, right;

    public TreeNode(int val) {
        this.val = val;
        this.left = this.mid = this.right = null;
    }
}

Greedy Algorithm

The greedy algorithm is a problem-solving technique that assumes choosing the locally optimal solution at each step will lead to the globally optimal solution. It is particularly effective in scenarios where the problem exhibits the greedy choice property or optimal substructure.

  • The greedy choice property states that a locally optimal choice at each step will lead to the best overall solution.
  • Optimal substructure means that if a problem can be broken down into smaller subproblems, solving these subproblems optimally will result in an optimal solution to the entire problem.

Note that in a greedy algorithm, the decisions made are irrevocable. Once a choice is made, it cannot be undone, which distinguishes it from techniques like backtracking where decisions can be revisited and changed.


860. Lemonade Change

SHOW PROBLEM

Problem Statement

You are running a lemonade stand, where each lemonade costs $5. Customers stand in a queue and buy one lemonade at a time, paying with either a $5, $10, or $20 bill. You must provide the correct change for each customer so that the net transaction results in the customer paying exactly $5 for the lemonade.

Initially, you have no change in hand.

Given an integer array bills, where bills[i] represents the bill given by the i-th customer, return true if you can provide the correct change to every customer, or false otherwise.

Example 1:

  • Input: bills = [5,5,5,10,20]
  • Output: true
  • Explanation:
    • The first three customers pay with $5 bills, so we collect three $5 bills.
    • The fourth customer pays with a $10 bill, so we give back one $5 bill.
    • The fifth customer pays with a $20 bill, so we give back one $10 bill and one $5 bill.
    • Since all customers received the correct change, we return true.

Example 2:

  • Input: bills = [5,5,10,10,20]
  • Output: false
  • Explanation:
    • The first two customers pay with $5 bills, so we collect two $5 bills.
    • The next two customers pay with $10 bills, so we give back one $5 bill for each.
    • The last customer pays with a $20 bill, but we cannot give $15 in change (we only have two $10 bills).
    • Since we cannot provide correct change, we return false.

Constraints:

  • 1 <= bills.length <= 10^5
  • bills[i] is either 5, 10, or 20.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public boolean lemonadeChange(int[] bills) {
        int five = 0, ten = 0;

        for (int bill : bills) {
            if (bill == 5) {
                five++;  // Collect 5 dollar bill
            } else if (bill == 10) {
                if (five == 0) {
                    return false;
                }
                five--;  // 5 dollars for change
                ten++; // Collect 10 dollar bill
            } else if (bill == 20) {
                if (five > 0 && ten > 0) {
                    five--; // Give one 5 dollar bill
                    ten--;  // Give one 10 dollar bill
                } else if (five >= 3) {
                    five -= 3; // Give three five dollar bills
                } else {
                    return false;
                }
            }
        }

        return true;
    }
}

SHOW NOTES


455. Assign Cookies

SHOW PROBLEM

Problem Statement

You are given two integer arrays:

  • g, where g[i] represents the greed factor of the i-th child (the minimum cookie size that will satisfy them).
  • s, where s[j] represents the size of the j-th cookie.

Each child can receive at most one cookie, and a child will be content if they receive a cookie of size greater than or equal to their greed factor.

Your task is to determine the maximum number of content children you can achieve.

Example 1:

  • Input: g = [1,2,3], s = [1,1]
  • Output: 1
  • Explanation:
    • You have 3 children with greed factors [1,2,3] and 2 cookies of size 1.
    • Only 1 child (with greed factor 1) can be satisfied.

Example 2:

  • Input: g = [1,2], s = [1,2,3]
  • Output: 2
  • Explanation:
    • You have 2 children with greed factors [1,2] and 3 cookies of sizes [1,2,3].
    • Both children can be satisfied, so the output is 2.

Constraints:

  • 1 <= g.length <= 3 * 10^4
  • 0 <= s.length <= 3 * 10^4
  • 1 <= g[i], s[j] <= 2^{31} - 1

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g); // Sort greed factors
        Arrays.sort(s); // Sort cookie sizes
        
        int i = 0, j = 0, count = 0;
        while (i < g.length && j < s.length) {
            // Assign cookie j to child i if it satisfies greed factor
            if (g[i] <= s[j]) {
                count++;
                i++; // Move to next child
            }
            j++;
            // if (g[i] <= s[j]) {
            //     count++;
            //     i++;
            //     j++;
            // } else {
            //     j++;
            // }
        }

        return count;
    }
}

881. Boats to Save People

SHOW PROBLEM

Problem Statement

You are given an array people where each element people[i] represents the weight of the i-th person. There are an infinite number of boats, and each boat can carry at most two people at the same time, provided the sum of their weights does not exceed a given limit limit. Your task is to return the minimum number of boats required to carry all the people.

Example 1:

  • Input: people = [1,2], limit = 3
  • Output: 1
  • Explanation: One boat can carry both people with weights 1 and 2, as their sum is 3, which is equal to the limit.

Example 2:

  • Input: people = [3,2,2,1], limit = 3
  • Output: 3
  • Explanation: Three boats are required. The boat distribution would be: (1, 2), (2), (3).

Example 3:

  • Input: people = [3,5,3,4], limit = 5
  • Output: 4
  • Explanation: Four boats are needed: (3), (3), (4), (5).

Constraints:

  • 1 <= people.length <= 5 * 10^4
  • 1 <= people[i] <= limit <= 3 * 10^4

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int numRescueBoats(int[] people, int limit) {
        // Sort the array in an ascending order to facilitate pairing
        Arrays.sort(people);

        // Initialize two pointers
        int left = 0, right = people.length - 1;
        int boats = 0; // Record the number of boats
        while (left <= right) {
            // The current lightest and the heaviest person can share the boat
            if (people[left] + people[right] <= limit) {
                left++;
                // right--;
                // boat++;
            }
            right--;
            boats++;
        }

        return boats;
    }
}

SHOW NOTES

A boat can hold at most two people. Therefore, pairing the lightest and heaviest person at each step minimize the number of boats required.

Visulization:

Boats to Save People


680. Valid Palindrome II

SHOW PROBLEM

Problem Statement

Given a string s, return true if it can become a palindrome by deleting at most one character. Otherwise, return false.

Example 1:

  • Input: s = "aba"
  • Output: true

Example 2:

  • Input: s = "abca"
  • Output: true
  • Explanation: By deleting the character 'c', the string becomes a palindrome.

Example 3:

  • Input: s = "abc"
  • Output: false

Constraints:

  • 1 <= s.length <= 10^5
  • s consists of lowercase English letters.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public boolean validPalindrome(String s) {
        // Initialize two pointers
        int left = 0, right = s.length() - 1;
        while (left <= right) {
            // If characters don't match, try skipping either left character or the right character
            if (s.charAt(left) != s.charAt(right)) {
                return isPalindrome(left + 1, right, s) || isPalindrome(left, right - 1, s);
            }
            left++;
            right--;
        }

        return true;
    }

    private static boolean isPalindrome(int left, int right, String s) {
        while (left <= right) {
            if (s.charAt(left) != s.charAt(right)) {
                // If any characters don't match, it's not a palindrome
                return false;
            }
            left++;
            right--;
        }

        return true;
    }
}

SHOW NOTES

Vidualization

Valid Palindrome II


646. Maximum Length of Pair Chain

SHOW PROBLEM

Problem Statement

You are given an array of n pairs, where each pair pairs[i] = [left_i, right_i] satisfies left_i < right_i.

A pair p2 = [c, d] follows a pair p1 = [a, b] if b < c. In other words, a chain of pairs can be formed if the right endpoint of one pair is less than the left endpoint of the next pair.

Your task is to return the length of the longest chain that can be formed. You do not need to use all the given intervals, and you can select the pairs in any order.

Example 1:

  • Input: pairs = [[1,2],[2,3],[3,4]]
  • Output: 2
  • Explanation: The longest chain is [1,2] -> [3,4].

Example 2:

  • Input: pairs = [[1,2],[7,8],[4,5]]
  • Output: 3
  • Explanation: The longest chain is [1,2] -> [4,5] -> [7,8].

Constraints:

  • n == pairs.length
  • 1 <= n <= 1000
  • -1000 <= left_i < right_i <= 1000

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int findLongestChain(int[][] pairs) {
        // Sort the pairs in an ascending order based on the second element of each pair
        Arrays.sort(pairs, (a, b) -> a[1] - b[1]);

        // Initialize the second element of the last pair
        int lastEnd = Integer.MIN_VALUE;
        int maxLength = 0;
        for (int[] pair : pairs) {
            if (pair[0] > lastEnd) {
                maxLength++; // Increase the maximum length
                lastEnd = pair[1]; // Update the lastEnd for next loop
            }
        }

        return maxLength;
    }
}

SHOW NOTES


921. Minimum Add to Make Parentheses Valid

SHOW PROBLEM

Problem Statement

A parentheses string is valid if and only if:

  1. It is the empty string,
  2. It can be written as AB (where A and B are valid strings),
  3. It can be written as (A) (where A is a valid string).

You are given a parentheses string s. In one move, you can insert either an opening parenthesis ( or a closing parenthesis ) at any position of the string.

Your task is to return the minimum number of moves required to make the string valid.

Example 1:

  • Input: s = "())"
  • Output: 1

Example 2:

  • Input: s = "((("
  • Output: 3

Constraints:

  • 1 <= s.length <= 1000
  • s[i] is either '(' or ')'.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int minAddToMakeValid(String s) {
        int unmatchedOpenCount = 0; // Tracks unmatched opening parentheses
        int neededOpenCount = 0; // Tracks needed opening parentheses to balance closing parentheses

        for (char c : s.toCharArray()) {
            if (c == '(') {
                // Found an opening parenthesis
                unmatchedOpenCount++;
            } else {
                if (unmatchedOpenCount > 0) {
                    // Balance the opening parenthesis
                    unmatchedOpenCount--;
                } else {
                    // Need an opening parenthesis to balance the closing parenthesis
                    neededOpenCount++;
                }
            }
        }

        // Return the minimum number of moves required
        return unmatchedOpenCount + neededOpenCount;
    }
}

SHOW NOTES

Visualization:

Minimum Add to Make Parentheses Valid


316. Remove Duplicate Letters

SHOW PROBLEM

Problem Statement

Given a string s, remove duplicate letters so that every letter appears once and only once. The resulting string must be the smallest in lexicographical order among all possible results.

Example 1:

  • Input: s = "bcabc"
  • Output: "abc"

Example 2:

  • Input: s = "cbacdcbc"
  • Output: "acdb"

Constraints:

  • 1 <= s.length <= 10^4
  • s consists of lowercase English letters.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public String smallestSubsequence(String s) {
        Map<Character, Integer> charMap = new HashMap<>();
        Set<Character> charSet = new HashSet<>();
        Stack<Character> charStack = new Stack<>();

        // Count the frequency of each character
        for (char c : s.toCharArray()) {
            charMap.put(c, charMap.getOrDefault(c, 0) + 1);
        }

        for (char c : s.toCharArray()) {
            if (!charSet.contains(c)) {
                // Ensure the smallest lexicographical order by removing higher characters that will appear again
                while (!charStack.isEmpty() && c < charStack.peek() && charMap.get(charStack.peek()) > 0) {
                    // Remove the top element from the stack
                    // Character popped = charStack.pop();
                    // Remove the popped element from the charSet
                    // charSet.remove(popped);
                    charSet.remove(charStack.pop());
                }

                charStack.push(c);
                charSet.add(c);
            }

            // Decrease the frequency of current character
            charMap.put(c, charMap.get(c) - 1);
        }

        // Build the result from the char Stack
        StringBuilder builder = new StringBuilder();
        for (char c : charStack) {
            builder.append(c);
        }

        return builder.toString();
    }
}

SHOW NOTES

Visualization*:

Remove Duplicate Letters


134. Gas Station

SHOW PROBLEM

Problem Statement

There are n gas stations along a circular route, where the amount of gas at the ith station is given by gas[i].

You have a car with an unlimited gas tank, and it costs cost[i] amount of gas to travel from the ith station to the next (i + 1)th station. You begin the journey with an empty tank at one of the gas stations.

Your task is to determine the starting gas station’s index if it is possible to travel around the circuit once in the clockwise direction. If it is not possible, return -1. If a solution exists, it is guaranteed to be unique.

Example 1:

  • Input: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
  • Output: 3
  • Explanation:
    • Start at station 3 (index 3) and fill up with 4 units of gas. Your tank = 0 + 4 = 4.
      • Travel to station 4: Tank = 4 - 1 + 5 = 8
      • Travel to station 0: Tank = 8 - 2 + 1 = 7
      • Travel to station 1: Tank = 7 - 3 + 2 = 6
      • Travel to station 2: Tank = 6 - 4 + 3 = 5
      • Travel to station 3: The cost is 5, and your gas is just enough to return to station 3.
    • Therefore, return 3 as the starting index.

Example 2:

  • Input: gas = [2,3,4], cost = [3,4,3]
  • Output: -1
  • Explanation:
    • You can’t start at station 0 or 1, as there is not enough gas to travel to the next station.
    • Start at station 2 with 4 units of gas.
      • Travel to station 0: Tank = 4 - 3 + 2 = 3
      • Travel to station 1: Tank = 3 - 3 + 3 = 3
      • You cannot travel back to station 2, as it requires 4 units of gas but you only have 3.
    • Since no valid starting station exists, return -1.

Constraints:

  • n == gas.length == cost.length
  • 1 <= n <= 10^5
  • 0 <= gas[i], cost[i] <= 10^4

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int totalGas = Arrays.stream(gas).sum();
        int totalCost = Arrays.stream(cost).sum();

        if (totalGas < totalCost) {
            return -1;
        }

        int tank = 0, startIndex = 0;
        // Find the starting Index
        for (int i = 0; i < gas.length; i++) {
            tank += gas[i] - cost[i];
            if (tank < 0) {
                // Reset tank and update the startIndex
                tank = 0;
                startIndex = i + 1;
            }
        }

        return startIndex;
    }
}

55. Jump Game

SHOW PROBLEM

Problem Statement

You are given an integer array nums, where each element represents your maximum jump length at that position. You start at index 0, and your goal is to reach the last index of the array.

Return true if you can reach the last index; otherwise, return false.

Example 1:

  • Input: nums = [2,3,1,1,4]
  • Output: true
  • Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index.

Example 2:

  • Input: nums = [3,2,1,0,4]
  • Output: false
  • Explanation: You will always reach index 3, where the jump length is 0, making it impossible to reach the last index.

Constraints:

  • 1 <= nums.length <= 10⁴
  • 0 <= nums[i] <= 10⁵

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public boolean canJump(int[] nums) {
        int n = nums.length;
        // Initialize to the last index, representing the goal
        int goal = n - 1; // Start from the end of the nums
        
        // Traverse from the second-to-last index backwards
        for (int i = n - 2; i >= 0; i--) {
            // Update currentEnd if the current index can reach it
            if (i + nums[i] >= goal) {
                goal = i;
            }
        }

        // Return true if the start index (0) can reach the last index
        return goal == 0;
    }
}

45. Jump Game II

SHOW PROBLEM

Problem Statement

You are given a 0-indexed array of integers nums of length n. Initially, you are positioned at nums[0]. Each element nums[i] represents the maximum length of a forward jump from index i. In other words, if you are at nums[i], you can jump to any index nums[i + j] where:

  • 0 <= j <= nums[i]
  • i + j < n

Your task is to return the minimum number of jumps required to reach the last index, nums[n - 1]. It is guaranteed that you can reach nums[n - 1].

Example 1:

  • Input: nums = [2, 3, 1, 1, 4]
  • Output: 2
  • Explanation: The minimum number of jumps to reach the last index is 2. Jump 1 step from index 0 to index 1, then 3 steps to the last index.

Example 2:

  • Input: nums = [2, 3, 0, 1, 4]
  • Output: 2

Constraints:

  • 1 <= nums.length <= 10^4
  • 0 <= nums[i] <= 1000
  • It is guaranteed that you can reach nums[n - 1].

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int jump(int[] nums) {
        int jumps = 0; // Track the number of jumps
        int currentEnd = 0; // Track the end of the range for the current jump
        int farthest = 0; // Track the farthest point that can be reached

        for (int i = 0; i < nums.length - 1; i++) {
            // Update the farthest point
            farthest = Math.max(farthest, i + nums[i]);

            if (i == currentEnd) { // Reached the end of current jump range
                jumps++; // Increment the jumps count
                currentEnd = farthest; // Update the end of range for the current jump
            }
        }

        return jumps;
    }
}

SHOW NOTES

Algorithm Walkthrough:

  • Example Input: nums = [2, 3, 1, 2, 4, 1]

  • Initialization:

    • jumps = 0
    • currentEnd = 0
    • farthest = 0
  • Iteration 1:

    • Index i = 0
    • farthest = max(0, 0 + 2) = 2
    • Since i == currentEnd (0 == 0):
      • jumps++jumps = 1
      • currentEnd = farthestcurrentEnd = 2
  • Iteration 2:

    • Index i = 1
    • farthest = max(2, 1 + 3) = 4
    • Since i != currentEnd (1 != 2), do nothing.
  • Iteration 3:

    • Index i = 2
    • farthest = max(4, 2 + 1) = 4
    • Since i == currentEnd (2 == 2):
    • jumps++jumps = 2
    • currentEnd = farthestcurrentEnd = 4
  • Iteration 4:

    • Index i = 3
    • farthest = max(4, 3 + 2) = 5
    • Since i != currentEnd (3 != 4), do nothing.
  • Iteration 5:

    • Index i = 4
    • farthest = max(5, 4 + 4) = 8
    • Since i == currentEnd (4 == 4):
      • jumps++jumps = 3
      • currentEnd = farthestcurrentEnd = 8
  • Return Result:

    • The minimum number of jumps required to reach the last index is 3.
  • Summary:

    • After 3 jumps, you can reach the last index in the array. Therefore, the result is 3.


Matrix Traversal

The Matrix Traversal Pattern involves traversing 2D arrays where each cell represents either land (1) or water (0). The most common traversal methods are Depth First Search (DFS) and Breadth First Search (BFS). The generall steps for traversing a matrix are as follows:

  1. Traverse the matrix row by row.
  2. Upon encountering a land cell, mark all connected land cells in the island as visited (e.g., by changing 1 to 0).
  3. When using DFS or BFS, the first step is to check for out-of-bounds conditions.
  4. Check if the current cell is water (0).
  5. Explore all neighboring cells of the current cell.

200. Number of Islands

SHOW PROBLEM

Problem Statement

You are given an m x n 2D binary grid grid which represents a map of ‘1’s (land) and ‘0’s (water). Your task is to return the number of islands.

An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are surrounded by water.

Example 1:

  • Input:
    grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ]
  • Output: 1
  • Explanation: There is one island formed by adjacent ‘1’s.

Example 2:

  • Input:
    grid = [ ["1","1","0","0","0"], ["1","1","0","0","0"], ["0","0","1","0","0"], ["0","0","0","1","1"] ]
  • Output: 3
  • Explanation: There are three islands formed by the groups of adjacent ‘1’s.

Constraints:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] is either ‘0’ or ‘1’.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    // top-left is (0, 0), x represents row, y represents column
    // Direction representing movement: up, down, left, right
    private static final int[][] DIRECTIONS = new int[][] { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };

    public int numIslands(char[][] grid) {
        int rows = grid.length;
        int cols = grid[0].length;

        int totalIslands = 0;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                // If a land cell is found, it represents a new island
                if (grid[i][j] == '1') {
                    totalIslands++;
                    // Recursively mark all cells in this island as visited
                    visitIslandDFS(grid, i, j);
                }
            }
        }

        return totalIslands;
    }

    private static void visitIslandDFS(char[][] grid, int x, int y) {
        // Check for out-of-bounds
        if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length) {
            return;
        }

        // Check if it's a water cell
        if (grid[x][y] == '0') {
            return;
        }

        // Mark the cell as visited by changing it to '0'
        grid[x][y] = '0';

        // Explore all four adjacent directions
        for (int[] direction : DIRECTIONS) {
            visitIslandDFS(grid, x + direction[0], y + direction[1]);
        }
    }
}
class Solution {
    // top-left is (0, 0), x represents row, y represents column
    // Direction representing movement: up, down, left, right
    private static final int[][] DIRECTIONS = new int[][] { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };

    public int numIslands(char[][] grid) {
        int rows = grid.length;
        int cols = grid[0].length;

        int totalIslands = 0;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                // If a land is found, it represents a new land
                if (grid[i][j] == '1') {
                    totalIslands++;
                    visitIslandBFS(grid, i, j);
                }
            }
        }

        return totalIslands;
    }

    private static void visitIslandBFS(char[][] grid, int x, int y) {
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{x, y});

        while (!queue.isEmpty()) {
            int[] coord = queue.poll();
            int row = coord[0];
            int col = coord[1];

            // Check for out-of-bounds
            if (row < 0 || row >= grid.length || col < 0 || col >= grid[0].length) {
                continue;
            }

            // Check if it is a water cell
            if (grid[row][col] == '0') {
                continue;
            }

            // Mark the cell as visited by changing it to '0'
            grid[row][col] = '0';

            // Add all adjacent neighbors to the queue
            for (int[] direction : DIRECTIONS) {
                queue.offer(new int[]{row + direction[0], col + direction[1]});
            }
        }     
    }
}
class Solution {
    // top-left is (0, 0), x represents row, y represents column
    // Direction representing movement: up, down, left, right
    private static final int[][] DIRECTIONS = new int[][] { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };

    public int numIslands(char[][] grid) {
        int rows = grid.length;
        int cols = grid[0].length;
        boolean[][] visited = new boolean[rows][cols];

        int totalIslands = 0;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                // If a land cell is found, it represents a new land
                if (!visited[i][j] && grid[i][j] == '1') {
                    totalIslands++;
                    // Mark all lands in this island as visited
                    visitIslandBFS(grid, visited, i, j);
                }
            }
        }

        return totalIslands;
    }

    private static void visitIslandBFS(char[][] grid, boolean[][] visited, int x, int y) {
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{x, y});

        while (!queue.isEmpty()) {
            int[] coord = queue.poll();
            int row = coord[0];
            int col = coord[1];

            // Check for out-of-bound
            if (row < 0 || row >= grid.length || col < 0 || col >= grid[0].length) {
                continue;
            }

            // Check if it is a water cell or visited
            if (grid[row][col] == '0' || visited[row][col]) {
                continue;
            }

            // Mark the cell as visited
            visited[row][col] = true;

            // Add all adjacent neighbors to the queue
            for (int[] direction : DIRECTIONS) {
                queue.offer(new int[] {row + direction[0], col + direction[1]});
            }
        }
    }
}

SHOW NOTES

Defining DIRECTIONS constant allows for simplifing the code. private static final int[][] DIRECTIONS = new int[][] { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };

Note that the condition for finding land in the BFS with a visited array implementation is if (!visited[i][j] && grid[i][j] == '1'). Otherwise, it will recalculate the number of lands.


695. Max Area of Island

SHOW PROBLEM

Problem Statement

You are given an m x n binary matrix grid. An island is a group of 1’s (representing land) connected 4-directionally (horizontal or vertical). You may assume all four edges of the grid are surrounded by water.

The area of an island is the number of cells with a value 1 in the island.

Your task is to return the maximum area of an island in the grid. If there is no island, return 0.

Example 1: Max Area of Island

  • Input:
    grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0], [0,0,0,0,0,0,0,1,1,1,0,0,0], [0,1,1,0,1,0,0,0,0,0,0,0,0], [0,1,0,0,1,1,0,0,1,0,1,0,0], [0,1,0,0,1,1,0,0,1,1,1,0,0], [0,0,0,0,0,0,0,0,0,0,1,0,0], [0,0,0,0,0,0,0,1,1,1,0,0,0], [0,0,0,0,0,0,0,1,1,0,0,0,0]]
  • Output: 6
  • Explanation: The maximum area of an island is 6, which is the connected group of 1’s in the middle of the grid.

Example 2:

  • Input:
    grid = [[0,0,0,0,0,0,0,0]]
  • Output: 0
  • Explanation: There is no island, so the area is 0.

Constraints:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • grid[i][j] is either 0 or 1.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    // top-left is (0, 0), x represents row, y represents column
    // Direction representing movement: up, down, left, right
    private static final int[][] DIRECTIONS = new int[][] { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };
    
    public int maxAreaOfIsland(int[][] grid) {
        int rows = grid.length;
        int cols = grid[0].length;

        int maxIslandArea = 0;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                // If a land is found, it represents a new island
                if (grid[i][j] == 1) {
                    maxIslandArea = Math.max(maxIslandArea, visitIslandDFS(grid, i, j));
                }
            }
        }

        return maxIslandArea;
    }

    private static int visitIslandDFS(int[][] grid, int x, int y) {
        // Check for out-of-bounds
        if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length) {
            return 0;
        }

        // Check if it is a water cell or visited
        if (grid[x][y] == 0) {
            return 0;
        }

        // Mark the current cell as visited by changing it to water cell
        grid[x][y] = 0;

        int area = 1;
        // Recursively visit all neighboring cells
        for (int[] direction : DIRECTIONS) {
            area += visitIslandDFS(grid, x + direction[0], y + direction[1]);
        }

        return area;
    }
}
class Solution {
    // top-left is (0, 0), x represents row, y represents column
    // Direction representing movement: up, down, left, right
    private static final int[][] DIRECTIONS = new int[][] { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };

    public int maxAreaOfIsland(int[][] grid) {
        int rows = grid.length;
        int cols = grid[0].length;

        int maxIslandArea = 0;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                // If a land is found, it represents a new land
                if (grid[i][j] == 1) {
                    maxIslandArea = Math.max(maxIslandArea, visitIslandBFS(grid, i, j));
                }
            }
        }

        return maxIslandArea;
    }

    private static int visitIslandBFS(int[][] grid, int x, int y) {
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[] { x, y });
        grid[x][y] = 0; // Mark the current cell as visited

        int area = 0;
        while (!queue.isEmpty()) {
            int[] coord = queue.poll();
            area++;

            // Check all four neighboring cells
            for (int[] direction : DIRECTIONS) {
                int newRow = coord[0] + direction[0];
                int newCol = coord[1] + direction[1];

                // Check for out-of-bounds and if it is a land
                if (newRow >= 0 && newRow < grid.length && newCol >= 0 && newCol < grid[0].length
                        && grid[newRow][newCol] == 1) {
                    queue.offer(new int[] { newRow, newCol });
                    grid[newRow][newCol] = 0;
                }
            }
        }

        return area;
    }
}

Backtracking

Backtracking is an algorithmic technique used to solve problems by exploring all possible options and eliminating infeasible ones early based on certain contraints. The general steps involved in backtracking are Exploration, Constraint Checking, Pruning, and Goal State.

  1. Exploration: Start from an initial state and explore all possible options step by step.
  2. Constant Checking: After each choice, check if it satisfies the problem’s constraints.
  3. Pruning: If a choice leads to an invalid state, backtracking to the previous valid state and try a different option.
  4. Goal State: If a valid solution is found, accept it as a valid answer.

Backtracking uses a brute-force approach to solve a problem. The key difference is that it eliminates invalid options early, rather than trying all possibilities without pruning. It can be seen as an optimized version of brute force.


39. Combination Sum

SHOW PROBLEM

Problem Statement

You are given an array of distinct integers candidates and a target integer target. Your task is to return a list of all unique combinations of numbers from candidates where the chosen numbers sum to target. The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different.

The test cases are generated such that the number of unique combinations that sum up to target is less than 150 combinations for the given input.

Example 1:

  • Input: candidates = [2,3,6,7], target = 7
  • Output: [[2,2,3], [7]]
  • Explanation:
    • 2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times.
    • 7 is a candidate, and 7 = 7.
    • These are the only two combinations.

Example 2:

  • Input: candidates = [2,3,5], target = 8
  • Output: [[2,2,2,2], [2,3,3], [3,5]]

Example 3:

  • Input: candidates = [2], target = 1
  • Output: []
  • Explanation: There are no combinations that sum up to 1.

Constraints:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • All elements of candidates are distinct.
  • 1 <= target <= 40

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        
        backtrack(candidates, target, 0, new ArrayList<>(), result);
        
        return result;
    }

    private static void backtrack(int[] candidates, int target, int start, List<Integer> combination, List<List<Integer>> result) {
        // Found a combination if the remaining target matches 0
        if (target == 0) {
            result.add(new ArrayList<>(combination));
            return;
        }

        if (target < 0) {
            return;
        }

        // Iterate the candidates array to find the combination
        for (int i = start; i < candidates.length; i++) {
            // Skip the candidate greater than the remaining target
            if (candidates[i] > target) {
                continue;
            }

            // Add the current element to the combination array
            combination.add(candidates[i]);

            // Recur with the updated combination array and the remaining target
            backtrack(candidates, target - candidates[i], i, combination, result);

            // Backtrack to explore other possibilities
            combination.remove(combination.size() - 1);
        }
    }
}

HW: 碳中和

SHOW PROBLEM

问题描述

服务器的能源消耗与算力可以近似表示为 1 单位能源消耗提供 1 单位算力。为了支持碳中和,公司希望使用最少数量的服务器来提供所需算力。

现给定不同服务器的算力,以及目标任务的总算力需求,请找出刚好满足所需算力的最少服务器组合

如果存在多个最优方案,请按字典序升序输出。如果无法满足需求,则返回 -1

输入格式

  • 第一行输入服务器算力列表无重复,范围 [1,1000])。
  • 第二行输入目标算力(不超过 10000)。

输出格式

  • 输出刚好满足所需算力的最优服务器组合,每行为一种组合,算力按升序排列
  • 如果没有符合要求的组合,返回 -1

示例 1

输入

13 5
11

输出

1 5 5
3 3 5

解释

  • 最少使用 3 台服务器可满足 11 单位算力,存在 2 种方案:
    1. 使用 1 台算力 1 的服务器和 2 台算力 5 的服务器(1 5 5)。
    1. 使用 2 台算力 3 和 1 台算力 5 的服务器(3 3 5)。

示例 2

输入

2 4 5
3

输出

-1

解释
无法找到恰好满足 3 单位算力的服务器组合。

示例 3

输入

1 2 3 4 5
17

输出

2 5 5 5
3 4 5 5
4 4 4 5

解释

  • 最少使用 4 台服务器可满足 17 单位算力,存在 3 种方案:
    1. 2 5 5 5(分别选取 1 台算力 23 台算力 5)。
    1. 3 4 5 5(分别选取 1 台算力 31 台算力 42 台算力 5)。
    1. 4 4 4 5(分别选取 3 台算力 41 台算力 5)。

SHOW CODE
import java.util.*;
import java.util.stream.Collectors;

public class Main {
    static int minServer = 0;

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        // Read input and transform it to a decreasing array
        int[] powers = Arrays.stream(in.nextLine().trim().split(" "))
                .map(Integer::parseInt)
                .sorted(Comparator.reverseOrder())
                .mapToInt(Integer::intValue)
                .toArray();

        // Read the target power
        int target = Integer.parseInt(in.nextLine().trim());

        // Generate all power combinations with minimum servers
        List result = generatePowerCombinations(powers, target);

        // Sort the combination result
        sortCombinations(result);

        // Format and print the result
        formatOutput(result);
    }

    private static List generatePowerCombinations(int[] nums, int target) {
        List result = new ArrayList<>();

        backtrack(nums, target, 0, new ArrayList<>(), result);

        return result;
    }

    private static void backtrack(int[] nums, int target, int start, List<Integer> current, List result) {
        if (target == 0) {
            // The nums is in decreasing order, the first combinations must be with minimum servers
            if (result.isEmpty()) {
                minServer = current.size();
            }
            result.add(new ArrayList<>(current));
            return;
        }

        // Prune branches
        if (target < 0 || (!result.isEmpty() && current.size() >= minServer)) {
            return;
        }


        for (int i = start; i < nums.length; i++) {
            current.add(nums[i]); // Add the current number to the current subset

            // Recursively explore further with the reduced target
            backtrack(nums, target - nums[i], i, current, result);

            // Backtrack to explore other options
            current.remove(current.size() - 1);
        }
    }

    private static void sortCombinations(List result) {
        for (List<Integer> list : result) {
            list.sort(Comparator.comparingInt(a -> a));
        }
    }

    private static void formatOutput(List result) {
        if (result.isEmpty()) {
            System.out.println("-1");
            return;
        }

        for (List<Integer> list : result) {
            String joinedString = list.stream()
                    .map(Object::toString) // Convert each integer to string
                    .collect(Collectors.joining(" ")); // Join with a space delimiter

            System.out.println(joinedString);
        }
    }
}

SHOW PROBLEM

Problem Statement

You are given an m x n grid of characters board and a string word. Your task is to return true if the word exists in the grid.

The word can be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once.

Example 1: Word Search Example 1

  • Input: board = [["A","B","C","E"], ["S","F","C","S"], ["A","D","E","E"]], word = "ABCCED"
  • Output: true

Example 2: Word Search Example 2

  • Input: board = [["A","B","C","E"], ["S","F","C","S"], ["A","D","E","E"]], word = "SEE"
  • Output: true

Example 3: Word Search Example 3

  • Input: board = [["A","B","C","E"], ["S","F","C","S"], ["A","D","E","E"]], word = "ABCB"
  • Output: false

Constraints:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 6
  • 1 <= word.length <= 15
  • board and word consist of only lowercase and uppercase English letters.

Follow-up:
Could you use search pruning to make your solution faster with a larger board?


Go to Leetcode 🔗
SHOW CODE
class Solution {
    // top-left is (0, 0), x represents row, y represents column
    // Direction representing movement: up, down, left, right
    private static final int[][] DIRECTIONS = new int[][] { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };

    public boolean exist(char[][] board, String word) {
        int rows = board.length;
        int cols = board[0].length;

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (visitBoardDFS(board, word, i, j, 0)) {
                    return true;
                }
            }
        }

        return false;
    }

    private static boolean visitBoardDFS(char[][] board, String word, int x, int y, int index) {
        // Check for out-of-bounds
        if (x < 0 || x >= board.length || y < 0 || y >= board[0].length) {
            return false;
        }

        // Check if the current cell matches the character at current index
        if (board[x][y] != word.charAt(index)) {
            return false;
        }

        // Check if it reach end of the word
        if (index == word.length() - 1) {
            return true;
        }

        char temp = board[x][y];
        board[x][y] = '#'; // Mark the current cell as visited by changing it to '#'

        // boolean result = false;
        // Recursively check all 4 adjacent cells
        for (int[] direction : DIRECTIONS) {
            // result = result || visitBoardDFS(board, word, x + direction[0], y + direction[1], index + 1);
            if (visitBoardDFS(board, word, x + direction[0], y + direction[1], index + 1)) {
                return true; // If a valid path is found, no need to check other drections
            }
        }

        // Backtrack by replacing the cell with its original cell
        board[x][y] = temp;

        // return result;
        return false;
    }
}

1593. Split a String Into the Max Number of Unique Substrings

SHOW PROBLEM

Problem Statement

You are given a string s. Your task is to return the maximum number of unique substrings that the given string can be split into.

You can split string s into any list of non-empty substrings, where the concatenation of the substrings forms the original string. However, you must split the substrings such that all of them are unique.

A substring is a contiguous sequence of characters within a string.

Example 1:

  • Input: s = "ababccc"
  • Output: 5
  • Explanation: One way to split maximally is ['a', 'b', 'ab', 'c', 'cc']. Splitting like ['a', 'b', 'a', 'b', 'c', 'cc'] is not valid as you have ‘a’ and ‘b’ multiple times.

Example 2:

  • Input: s = "aba"
  • Output: 2
  • Explanation: One way to split maximally is ['a', 'ba'].

Example 3:

  • Input: s = "aa"
  • Output: 1
  • Explanation: It is impossible to split the string any further, as both substrings ‘a’ are not unique.

Constraints:

  • 1 <= s.length <= 16
  • s contains only lowercase English letters.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int maxUniqueSplit(String s) {
        return splitAndCount(s, 0, new HashSet<>());
    }

    private static int splitAndCount(String s, int start, Set<String> set) {
        // Base case, return the size of set when reach the end of the string
        if (start == s.length()) {
            return set.size();
        }

        int count = 0;
        for (int i = start + 1; i <= s.length(); i++) {
            String subStr = s.substring(start, i);

            // Only proceed if the substring is unique
            if (set.add(subStr)) {
                count = Math.max(count, splitAndCount(s, i, set));

                // Backtrack by removing the subStr from the set
                set.remove(subStr);
            }
        }

        return count;
    }
}

HW:排列组合的回文字符串

SHOW PROBLEM

题目描述

如果一个字符串与其反转后的字符串相同,则称其为回文字符串。例如:"aba" 是回文字符串,而 "abb" 不是(长度为 1 的字符串也是回文字符串)。

现在,给定一个字符串 s,请计算其所有不同的回文排列的个数。如果 s 不能通过重新排列形成任何回文字符串,则返回 0

输入描述

  • 输入为一个字符串 s,其长度满足 1 ≤ s.length < 1000,例如 "aabb"

输出描述

  • 返回 s 经过重新排列后能形成的不同回文字符串的个数

示例 1:

  • 输入: "aabb"
  • 输出: 2
  • 解释: 可能的回文排列为 "abba""baab"

示例 2:

  • 输入: "abc"
  • 输出: 0
  • 解释: 无法重新排列形成回文字符串。

约束:

  • 1 ≤ s.length < 1000
  • s 仅包含小写英文字母 ('a'-'z')。

SHOW CODE (Time Limit Exceeded)
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        String s = in.nextLine().trim();

        List<String> palindromes = generatePalindromes(s);

        System.out.println(palindromes);
    }

    private static List<String> generatePalindromes(String s) {
        // Count the frequency of each character
        Map<Character, Integer> charCount = new HashMap<>();
        for (char c : s.toCharArray()) {
            charCount.put(c, charCount.getOrDefault(c, 0) + 1);
        }

        // Check if the string can form a palindrome
        String mid = "";
        List<Character> halfChars = new ArrayList<>();
        for (Map.Entry<Character, Integer> entry : charCount.entrySet()) {
            char key = entry.getKey();
            int count = entry.getValue();

            if (count % 2 == 1) {
                // If mid is not empty, meaning the odd count of characters is greater than 1
                if (!mid.isEmpty()) {
                    return new ArrayList<>();
                }
                mid = String.valueOf(key);
            }

            for (int i = 0; i < count / 2; i++) {
                halfChars.add(key);
            }
        }

        List<String> result = new ArrayList<>();
        boolean[] used = new boolean[halfChars.size()];
        backtrack(halfChars, mid, new StringBuilder(), used, result);

        return result;
    }

    private static void backtrack(List<Character> halfChars, String mid, StringBuilder path, boolean[] used, List<String> result) {
        if (path.length() == halfChars.size()) {
            result.add(new String(path) + mid + new StringBuilder(path).reverse());
            return;
        }

        for (int i = 0; i < halfChars.size(); i++) {
            if (used[i] || (i > 0 && halfChars.get(i).equals(halfChars.get(i - 1)) && !used[i - 1])) {
                continue; // Skip duplicates
            }

            // Add the current character to the path
            path.append(halfChars.get(i));
            // Mark the current character as used
            used[i] = true;

            backtrack(halfChars, mid, path, used, result);

            // Backtrack: Remove the last element of path for further exploration
            path.setLength(path.length() - 1);
            used[i] = false;
        }
    }
}


Subsets

The Subsets Pattern is a commonly used approach for solving problems that involve generating all possible combinations (subsets) of a given array or list. For an array of size $n$, the total number of subsets is $2^n$. When solving problems related to subsets, two common techniques are used: Backtracking and Iteration.

  • Backtracking: This technique explores all possible combinations by building subsets step by step and “backtracking” when a certain option leads to an invalid solution.
  • Iteration: This technique iterates through the list of elements and for each elements, adding it to the existing subsets (in the result) to form new subsets.

78. Subsets

SHOW PROBLEM

Problem Statement

You are given an integer array nums of unique elements. Your task is to return all possible subsets (the power set).

The solution set must not contain duplicate subsets. Return the solution in any order.

Example 1:

  • Input: nums = [1,2,3]
  • Output: [[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]

Example 2:

  • Input: nums = [0]
  • Output: [[], [0]]

Constraints:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • All the numbers of nums are unique.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> subsets = new ArrayList<>();
        backtrack(nums, 0, new ArrayList<>(), subsets);
        return subsets;
    }

    private static void backtrack(int[] nums, int start, List<Integer> current, List<List<Integer>> subsets) {
        // Add the current subset to the result
        subsets.add(new ArrayList<>(current));

        for (int i = start; i < nums.length; i++) {
            // Include the nums[i] in the current subset
            current.add(nums[i]);

            // Recurse to explore further elements
            backtrack(nums, i + 1, current, subsets);

            // Backtrack by removing the last added element
            current.remove(current.size() - 1);
        }
    }
}
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> subsets = new ArrayList<>();

        // Add the empty subset
        subsets.add(new ArrayList<>());

        for (int num : nums) {
            // Get the current size of the subsets
            int size = subsets.size();

            // For each existing subset, creating a new subset by adding the current number
            for (int i = 0; i < size; i++) {
                List<Integer> subset = new ArrayList<>(subsets.get(i));
                subset.add(num);
                subsets.add(subset);
            }
        }

        return subsets;
    }
}

SHOW NOTES

Visulization of Iteration:

Visulization of Iteration


90. Subsets II

SHOW PROBLEM

Problem Statement

You are given an integer array nums that may contain duplicates. Your task is to return all possible subsets (the power set).

The solution set must not contain duplicate subsets. Return the solution in any order.

Example 1:

  • Input: nums = [1,2,2]
  • Output: [[], [1], [1,2], [1,2,2], [2], [2,2]]

Example 2:

  • Input: nums = [0]
  • Output: [[], [0]]

Constraints:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> subsets = new ArrayList<>();
        // Sort the array to ensure duplicate elements are adjacent
        Arrays.sort(nums);
        backtrack(nums, 0, new ArrayList<>(), subsets);
        return subsets;
    }

    private static void backtrack(int[] nums, int start, List<Integer> current, List<List<Integer>> subsets) {
        // Add the current subset to the subsets
        subsets.add(new ArrayList<>(current));

        for (int i = start; i < nums.length; i++) {
            // Skip if the current number is equal to the previous number
            if (i > start && nums[i] == nums[i - 1]) {
                continue;
            }

            // Include the current number to the current subset
            current.add(nums[i]);

            backtrack(nums, i + 1, current, subsets);

            // Backtrack to explore other options
            current.remove(current.size() - 1);
        }
    }
}
class Solution {
    public Lis<List<Integer>> subsetsWithDup(int[] nums) {
        // Sort the array to ensure duplicates are adjacent
        Arrays.sort(nums);

        Lis<List<Integer>> subsets = new ArrayList<>();
        // Start with the empty set
        subsets.add(new ArrayList<>());

        int start = 0, end = 0;
        for (int i = 0; i < nums.length; i++) {
            // If current number is same as the previous one, start from end to avoid duplicates
            start = (i > 0 && nums[i] == nums[i - 1]) ? end + 1 : 0;
            end = subsets.size() - 1;

            // Generate new subsets by adding the current number to previous subsets
            for (int j = start; j <= end; j++) {
                List<Integer> subset = new ArrayList<>(subsets.get(j));
                subset.add(nums[i]);
                subsets.add(subset);
            }
        }

        return subsets;
    }
}

SHOW NOTES

Subsets II (Iteration):

  • To ensure all subsets are unique, sorting the array first so that duplicate elements are next to each other.
  • When the current number is equal to the previous number, just add it to the subsets that are created in the previous step.

Algorithm Walkthrough (Itertion):

  • given set = [1, 5, 3, 3], sorted set = [1, 3, 3, 5]
    1. Start with an empty set: [[]].
    2. Add the first number (1) to all the existing subsets to create new subsets: [[], [1]].
    3. Add the second number (3) to all the existing subsets: [[], [1], [3], [1,3]].
    4. The next number (3) is a duplicate. If we add it to all existing subsets we will get: [[], [1], [3], [1,3], [3], [1,3], [3,3], [1,3,3]]. (Instead of adding (3) to all the existing subsets, only add it to the new subsets which were created in the previous (3rd) step)
    5. Finally, add the fourth number (5) to all the existing subsets: [[], [1], [3], [1,3], [3,3], [1,3,3], [5], [1,5], [3,5], [1,3,5], [3,3,5], [1,3,3,5]].

Visulization of Iteration:

Visulization of Iteration


Note that in the backtracking method, the condition for skipping duplicate elements is if (i > start && nums[i] == nums[i - 1]), not if (i > 0 && nums[i] == nums[i - 1]).


46. Permutations

SHOW PROBLEM

Problem Statement

You are given an array nums of distinct integers. Your task is to return all the possible permutations of the array.

You can return the answer in any order.

Example 1:

  • Input: nums = [1,2,3]
  • Output: [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]

Example 2:

  • Input: nums = [0,1]
  • Output: [[0,1], [1,0]]

Example 3:

  • Input: nums = [1]
  • Output: [[1]]

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> permutations = new ArrayList<>();

        boolean[] used = new boolean[nums.length];
        backtrack(nums, used, new ArrayList<>(), permutations);

        return permutations;
    }

    private static void backtrack(int[] nums, boolean[] used, List<Integer> current, List<List<Integer>> permutations) {
        // Base case
        if (current.size() == nums.length) {
            permutations.add(new ArrayList<>(current));
            return;
        }

        // Try each number at each position
        for (int i = 0; i < nums.length; i++) {
            // Skip used elements
            if (used[i]) {
                continue;
            }

            // Include the current element in the current permutation
            current.add(nums[i]);
            // Mark the current element as used
            used[i] = true;

            // Recurse to build the next part of the permutation
            backtrack(nums, used, current, permutations);

            // Backtrack: remove the last element and mark it as used
            current.remove(current.size() - 1);
            used[i] = false;
        }
    }
}
class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> permutations = new ArrayList<>();
        Queue<List<Integer>> queue = new LinkedList<>();

        // Start with the empty set
        queue.offer(new ArrayList<>());

        for (int num : nums) {
            int size = queue.size();

            // Process each existing subset in the queue
            for (int i = 0; i < size; i++) {
                List<Integer> oldSubset = queue.poll();

                // Create new subsets by inserting current number at every position of oldSubset
                for (int j = 0; j <= oldSubset.size(); j++) {
                    List<Integer> newSubset = new ArrayList<>(oldSubset);
                    newSubset.add(j, num);

                    // Found a permutation, add it to the result
                    if (newSubset.size() == nums.length) {
                        permutations.add(newSubset);
                    } else { 
                        // Add it to the queue for further processing
                        queue.offer(newSubset);
                    }

                }
            }
        }

        return permutations;
    }
}

SHOW NOTES

Permutations (Iteration): Take each permutation of the previous step and insert the new number in every position to generate the new permutations.

Visulization:

Permutation (Iteration)


Nowcoder: MGJ14 字符串的排列

SHOW PROBLEM

问题描述

给定一个字符串 s,按字典序打印出该字符串中字符的所有排列

注意事项:

  • 输入字符串长度不超过 9,可能包含重复字符。
  • 字符仅包括大小写字母。

输入描述:

  • 一个字符串 s,长度 ≤ 9,仅包含大小写字母。

输出描述:

  • 按字典序排序的所有不同排列,以列表形式返回。

示例 1

  • 输入: "acc"
  • 输出: ["acc", "cac", "cca"]

Go to Nowcoder 🔗
SHOW CODE
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        // Read input string
        String s = in.nextLine().trim();

        // Generate Unique permutations
        String[] result = generateUniquePermutations(s);

        // Output result
        System.out.println(Arrays.deepToString(result));
    }

    private static String[] generateUniquePermutations(String s) {
        // Use TreeSet to keep result sorted
        Set<String> result =  new TreeSet<>();
        boolean[] used = new boolean[s.length()];
        backtrack(s, used, new StringBuilder(), result);

        return result.toArray(new String[0]);
    }

    private static void backtrack(String s, boolean[] used, StringBuilder current,
                                  Set<String> result) {
        // Store complete permutation
        if (current.length() == s.length()) {
            result.add(new String(current));
            return;
        }

        for (int i = 0; i < s.length(); i++) {
            // Skip used characters
            if (used[i]) {
                continue;
            }

            // Choose current character
            current.append(s.charAt(i));
            used[i] = true;

            // Explore further
            backtrack(s, used, current, result);

            // backtrack
            used[i] = false;
            current.setLength(current.length() - 1);
        }
    }
}

SHOW NOTES


Dynamic Programming

Those who cannot remember the past are condemned to repeat it.

Dynamic Programming is an algorithmic technique for solving problems by breaking them down into smaller subproblems and storing their solutions to avoid redundant work. It is particularly useful for problems that exhibit two key properties: overlapping subproblems and optimial substructure.

  • Overlapping Subproblems: The problem can be broken down into subproblems that are solved multiple times.
  • Optimal Substructure : The solution to the problem can be constructed from the solutions to its subproblems.

The general steps for solving DP-related problems include:

  1. Determine how to break the problem into smaller subproblems and how the solutions of these subproblems can be combined to form the solution to the larger problem.
  2. Define a state that represents the solution of a subproblem.
  3. Establish a recursive relation that expresses the solution of the problem in terms of the solutions to smaller subproblems.
  4. Use Memoization or Tabulation:
    • Memoization (Top-down approach): Solve the problem recursively but store the results of subproblems in a cache to avoid recomputing them.
    • Tabulation (Bottom-up approach): Solve the problem iteratively by solving all subproblems and storing their solutions in a table.

There are five common types of problems that involve Dynamic Programming, including Fibonacci Sequence, 0/1 Knapsack, Unbounded Knapsack, Longest Common Subsequence, and Palindrome Subsequence.


Fibonacci Sequence

509. Fibonacci Number

SHOW PROBLEM

Problem Statement

The Fibonacci numbers, commonly denoted F(n), form a sequence called the Fibonacci sequence, where each number is the sum of the two preceding ones, starting from 0 and 1. That is:

  • F(0) = 0,
  • F(1) = 1,
  • F(n) = F(n - 1) + F(n - 2), for n > 1.

Given an integer n, calculate F(n).

Example 1:

  • Input: n = 2
  • Output: 1
  • Explanation: F(2) = F(1) + F(0) = 1 + 0 = 1.

Example 2:

  • Input: n = 3
  • Output: 2
  • Explanation: F(3) = F(2) + F(1) = 1 + 1 = 2.

Example 3:

  • Input: n = 4
  • Output: 3
  • Explanation: F(4) = F(3) + F(2) = 2 + 1 = 3.

Constraints: - 0 <= n <= 30


Go to Leetcode 🔗
SHOW CODE: BRUTE-FORCE
class Solution {
    public int fib(int n) {
        // Base case
        if (n < 2) {
            return n;
        }

        return fib(n - 2) + fib(n - 1);
    }
}

Recursion Tree Analysis

  • Fibonacci Sequence = [0, 1, 1, 2, 3, 5]
  • n = 5
---
title: Recursion Tree Analysis (Fibonacci Sequence)
---
flowchart TD
    L0((5)) 
    L0 --> L1((4))
    L0 --> R1((3))

    L1 --> L2a((3))
    L1 --> L2b((2))
    
    L2a --> L3a((2))
    L2a --> L3b((1))

    L3a --> L4a((1))
    L3a --> L4b((0))

    style R1 fill:blue
    style L2a fill:blue
    style L2b fill:orange,color:black
    style L3a fill:orange,color:black

SHOW CODE: TOP DOWN WITH MEMOIZATION
class Solution {
    public int fib(int n) {
        int[] dp = new int[n + 1];
        return fibWithDP(n, dp);
    }

    private static int fibWithDP(int n, int[] dp) {
        if (n < 2) {
            return n;
        }

        if (dp[n] == 0) {
            dp[n] = fibWithDP(n - 1, dp) + fibWithDP(n - 2, dp);
        }

        return dp[n];
    }
}

SHOW CODE: BOTTOM UP WITH MEMOIZATION
class Solution {
    public int fib(int n) {
        if (n < 2) {
            return n;
        }

        int[] dp = new int[n + 1];

        dp[0] = 0;
        dp[1] = 1;

        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }

        return dp[n];
    }
}
Bottom Up With Memoization

SHOW CODE: BOTTOM UP (SPACE OPTIMIZATION)
class Solution {
    public int fib(int n) {
        if (n < 2) {
            return n;
        }

        int prev1 = 0, prev2 = 1, current;
        for (int i = 2; i <= n; i++) {
            current = prev1 + prev2;
            prev1 = prev2;
            prev2 = current;
        }

        return prev2;
    }
}

70. Climbing Stairs

SHOW PROBLEM

Problem Statement

You are climbing a staircase. It takes n steps to reach the top.

Each time you can either climb 1 step or 2 steps. In how many distinct ways can you climb to the top?

Example 1:

  • Input: n = 2
  • Output: 2
  • Explanation: There are two ways to climb to the top:
      1. 1 step + 1 step
      1. 2 steps

Example 2:

  • Input: n = 3
  • Output: 3
  • Explanation: There are three ways to climb to the top:
      1. 1 step + 1 step + 1 step
      1. 1 step + 2 steps
      1. 2 steps + 1 step

Constraints:

  • 1 <= n <= 45

Go to Leetcode 🔗
SHOW CODE: BRUTE-FORCE
class Solution {
    public int climbStairs(int n) {
        if (n < 3) {
            return n;
        }

        return climbStairs(n - 1) + climbStairs(n - 2);
    }
}

SHOW CODE: TOP DOWN WITH MEMOIZATION
class Solution {
    public int climbStairs(int n) {
        int[] dp = new int[n + 1];

        return climbStairsRecursive(n, dp);
    }

    private static int climbStairsRecursive(int n, int[] dp) {
        if (n < 3) {
            return n;
        }

        if (dp[n] == 0) {
            dp[n] = climbStairsRecursive(n - 1, dp) + climbStairsRecursive(n - 2, dp);
        }

        return dp[n];
    }
}

SHOW CODE: BOTTOM UP WITH MEMOIZATION
class Solution {
    public int climbStairs(int n) {
        if (n < 3) {
            return n;
        }

        int[] dp = new int[n + 1];
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }

        return dp[n];
    }
}

SHOW CODE: BOTTOM UP (SPACE OPTIMIZATION)
class Solution {
    public int climbStairs(int n) {
        if (n < 3) {
            return n;
        }

        int prev1 = 1, prev2 = 2, current;

        for (int i = 3; i <= n; i++) {
            current = prev1 + prev2;
            prev1 = prev2;
            prev2 = current;
        }

        return prev2;
    }
}

746. Min Cost Climbing Stairs

SHOW PROBLEM

Problem Statement

You are given an integer array cost, where cost[i] represents the cost of the $i^{th}$ step on a staircase. Once you pay the cost, you can either climb one or two steps.

You can either start from the step with index 0, or the step with index 1.

Return the minimum cost to reach the top of the floor.

Example 1:

  • Input: cost = [10,15,20]
  • Output: 15
  • Explanation: You will start at index 1.
    • Pay 15 and climb two steps to reach the top.
    • The total cost is 15.

Example 2:

  • Input: cost = [1,100,1,1,1,100,1,1,100,1]
  • Output: 6
  • Explanation: You will start at index 0.
    • Pay 1 and climb two steps to reach index 2.
    • Pay 1 and climb two steps to reach index 4.
    • Pay 1 and climb two steps to reach index 6.
    • Pay 1 and climb one step to reach index 7.
    • Pay 1 and climb two steps to reach index 9.
    • Pay 1 and climb one step to reach the top.
    • The total cost is 6.

Constraints:

  • 2 <= cost.length <= 1000
  • 0 <= cost[i] <= 999

Go to Leetcode 🔗
SHOW CODE: BRUTE-FORCE
class Solution {

    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;

        return Math.min(minCost(cost, n - 1), minCost(cost, n - 2));
    }

    private int minCost(int[] cost, int i) {
        // Base case
        if (i == 0 || i == 1) {
            return cost[i];
        }

        return cost[i] + Math.min(minCost(cost, i - 1), minCost(cost, i - 2));
    }
}

SHOW CODE: TOP DOWN WITH MEMOIZATION
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        int[] dp = new int[n + 1];
        Arrays.fill(dp, -1);

        return Math.min(minCost(cost, n - 1, dp), minCost(cost, n - 2, dp));
    }

    private static int minCost(int[] cost, int n, int[] dp) {
        // Base case
        if (n == 0 || n == 1) {
            return cost[n];
        }

        if (dp[n] == -1) {
            dp[n] = cost[n] + Math.min(minCost(cost, n - 1, dp), minCost(cost, n - 2, dp));
        }

        return dp[n];
    }
}

SHOW CODE: BOTTOM UP WITH MEMOIZATION
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        int[] dp = new int[n];

        dp[0] = cost[0];
        dp[1] = cost[1];
        for (int i = 2; i < n; i++) {
            dp[i] = cost[i] + Math.min(dp[i - 1], dp[i - 2]);
        }

        return Math.min(dp[n - 1], dp[n - 2]);
    }
}

SHOW CODE: BOTTOM UP (SPACE OPTIMIZATION)
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int prev1 = cost[0], prev2 = cost[1], current;
        for (int i = 2; i < cost.length; i++) {
            current = cost[i] + Math.min(prev1, prev2);
            prev1 = prev2;
            prev2 = current;
        }

        return Math.min(prev1, prev2);
    }
}

198. House Robber

SHOW PROBLEM

Problem Statement

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. The only constraint stopping you from robbing each of them is that adjacent houses have security systems connected, and it will automatically contact the police if two adjacent houses are broken into on the same night.

Given an integer array nums representing the amount of money in each house, return the maximum amount of money you can rob tonight without alerting the police.

Example 1:

  • Input: nums = [1,2,3,1]
  • Output: 4
  • Explanation:
    • Rob house 1 (money = 1) and then rob house 3 (money = 3).
    • Total amount you can rob = 1 + 3 = 4.

Example 2:

  • Input: nums = [2,7,9,3,1]
  • Output: 12
  • Explanation:
    • Rob house 1 (money = 2), rob house 3 (money = 9), and rob house 5 (money = 1).
    • Total amount you can rob = 2 + 9 + 1 = 12.

Constraints:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 400

Go to Leetcode 🔗
SHOW CODE: BRUTE-FORCE (Time Limited Exceeded)
class Solution {
    public int rob(int[] nums) {
        int n = nums.length;

        return maxMoney(nums, n - 1); // Rob form the last house
    }

    private static int maxMoney(int[] nums, int n) {
        // Base case: no house to rob
        if (n < 0) {
            return 0;
        }

        // Either skip the current house or rob it and skip the previous one
        return Math.max(nums[n] + maxMoney(nums, n - 2), maxMoney(nums, n - 1));
    }
}

SHOW CODE: TOP DOWN WITH MEMOIZATION
class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, -1);

        return maxMoney(nums, n - 1, dp); // Rob form the last house
    }

    private static int maxMoney(int[] nums, int n, int[] dp) {
        // Base case: no house to rob
        if (n < 0) {
            return 0;
        }

        if (dp[n] == -1) {
            // Either skip the current house or rob it and skip the previous one
            dp[n] = Math.max(nums[n] + maxMoney(nums, n - 2, dp), maxMoney(nums, n - 1, dp));
        }

        return dp[n];
    }
}

SHOW CODE: BOTTOM UP WITH MEMOIZATION
class Solution {
    public int rob(int[] nums) {
        // If there's only one house, rob it and return the amount
        if (nums.length == 1) {
            return nums[0];
        }

        int n = nums.length;
        int[] dp = new int[n];

        dp[0] = nums[0]; // The maximum money robbed from the first house is just its value
        // The maximum money robbed from the first two houses is the max of the first two houses
        dp[1] = Math.max(nums[0], nums[1]);
        for (int i = 2; i < n; i++) {
            dp[i] = Math.max(nums[i] + dp[i - 2], dp[i - 1]);
        }

        return dp[n - 1];
    }
}

SHOW CODE: BOTTOM UP (SPACE OPTIMIZATION)
class Solution {
    public int rob(int[] nums) {
        if (nums.length == 1) {
            return nums[0];
        }
        
        int prev1 = nums[0];
        int prev2 = Math.max(nums[0], nums[1]);
        int current;

        for (int i = 2; i < nums.length; i++) {
            current = Math.max(nums[i] + prev1, prev2);
            prev1 = prev2;
            prev2 = current;
        }

        return prev2;
    }
}

55. Jump Game

SHOW PROBLEM

Problem Statement

You are given an integer array nums, where each element represents your maximum jump length at that position. You start at index 0, and your goal is to reach the last index of the array.

Return true if you can reach the last index; otherwise, return false.

Example 1:

  • Input: nums = [2,3,1,1,4]
  • Output: true
  • Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index.

Example 2:

  • Input: nums = [3,2,1,0,4]
  • Output: false
  • Explanation: You will always reach index 3, where the jump length is 0, making it impossible to reach the last index.

Constraints:

  • 1 <= nums.length <= 10⁴
  • 0 <= nums[i] <= 10⁵

Go to Leetcode 🔗
SHOW CODE: BRUTE-FORCE (Time Limit Exceeded)
class Solution {
    public boolean canJump(int[] nums) {
        return canJumpFrom(nums, 0);
    }

    private static boolean canJumpFrom(int[] nums, int index) {
        // Base case: return true if reached the end of nums
        if (index >= nums.length - 1) {
            return true;
        }

        int maxJump = Math.min(index + nums[index], nums.length - 1);
        for (int nextIndex = index + 1; nextIndex <= maxJump; nextIndex++) {
            if (canJumpFrom(nums, nextIndex)) {
                // Return ture if any path leads to last index
                return true;
            }
        }

        // Return false if all paths fail
        return false;
    }
}

SHOW CODE: TOP DOWN WITH MEMOIZATION
class Solution {
    public boolean canJump(int[] nums) {
        Boolean[] dp = new Boolean[nums.length];
        return canJumpFrom(nums, 0, dp);
    }

    private static boolean canJumpFrom(int[] nums, int index, Boolean[] dp) {
        // Base case: return true if reached the end of nums
        if (index >= nums.length - 1) {
            return true;
        }

        if (dp[index] != null) {
            return dp[index];
        }

        int maxJump = Math.min(index + nums[index], nums.length - 1);
        for (int nextIndex = index + 1; nextIndex <= maxJump; nextIndex++) {
            if (canJumpFrom(nums, nextIndex, dp)) {
                dp[index] = true;
                return true; // Return ture if any path leads to last index
            }
        }

        // Return false if all paths fail
        dp[index] = false; // Mark as unreachable
        return false;
    }
}

45. Jump Game II

SHOW PROBLEM

Problem Statement

You are given a 0-indexed array of integers nums of length n. Initially, you are positioned at nums[0]. Each element nums[i] represents the maximum length of a forward jump from index i. In other words, if you are at nums[i], you can jump to any index nums[i + j] where:

  • 0 <= j <= nums[i]
  • i + j < n

Your task is to return the minimum number of jumps required to reach the last index, nums[n - 1]. It is guaranteed that you can reach nums[n - 1].

Example 1:

  • Input: nums = [2, 3, 1, 1, 4]
  • Output: 2
  • Explanation: The minimum number of jumps to reach the last index is 2. Jump 1 step from index 0 to index 1, then 3 steps to the last index.

Example 2:

  • Input: nums = [2, 3, 0, 1, 4]
  • Output: 2

Constraints:

  • 1 <= nums.length <= 10^4
  • 0 <= nums[i] <= 1000
  • It is guaranteed that you can reach nums[n - 1].

Go to Leetcode 🔗
SHOW CODE: BRUTE-FORCE (Time Limit Exceeded)
class Solution {
    public int jump(int[] nums) {
        return jumpRecursive(nums, 0);
    }

    private static int jumpRecursive(int[] nums, int currentIndex) {
        if (currentIndex >= nums.length - 1) {
            return 0;
        }

        // Unreachable state
        if (nums[currentIndex] == 0) {
            return nums.length;
        }

        int minJumps = nums.length;
        for (int i = 1; i <= nums[currentIndex]; i++) {
            int nextIndex = currentIndex + i;
            if (nextIndex < nums.length) {
                minJumps = Math.min(minJumps, 1 + jumpRecursive(nums, nextIndex));
            }
        }

        return minJumps;
    }
}

SHOW CODE: TOP DOWN WITH MEMOIZATION
class Solution {
    public int jump(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp, nums.length);
        return jumpRecursive(nums, 0, dp);
    }

    private static int jumpRecursive(int[] nums, int currentIndex, int[] dp) {
        if (currentIndex >= nums.length - 1) {
            return 0;
        }

        // Unreachable state
        if (nums[currentIndex] == 0) {
            return nums.length;
        }

        if (dp[currentIndex] == nums.length) {
            int minJumps = nums.length;
            for (int i = 1; i <= nums[currentIndex]; i++) {
                int nextIndex = currentIndex + i;
                minJumps = Math.min(minJumps, 1 + jumpRecursive(nums, nextIndex, dp));
            }

            dp[currentIndex] = minJumps;
        }

        return dp[currentIndex];
    }
}

0/1 Knapsack

494. Target Sum

SHOW PROBLEM

Problem Statement

You are given an integer array nums and an integer target.

Your task is to determine the number of different expressions that can be formed by adding either a '+' or '-' sign before each number in nums and concatenating them, such that the resulting expression evaluates to target.

Example 1:

  • Input: nums = [1,1,1,1,1], target = 3
  • Output: 5
  • Explanation: There are 5 ways to assign symbols to make the sum equal to 3:
    • -1 + 1 + 1 + 1 + 1 = 3
    • +1 - 1 + 1 + 1 + 1 = 3
    • +1 + 1 - 1 + 1 + 1 = 3
    • +1 + 1 + 1 - 1 + 1 = 3
    • +1 + 1 + 1 + 1 - 1 = 3

Example 2:

  • Input: nums = [1], target = 1
  • Output: 1

Constraints:

  • 1 <= nums.length <= 20
  • 0 <= nums[i] <= 1000
  • 0 <= sum(nums) <= 1000
  • -1000 <= target <= 1000

Go to Leetcode 🔗
SHOW CODE: BRUTE-FORCE
class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        return findTargetSumWaysRecursive(nums, target, 0, 0);
    }

    private static int findTargetSumWaysRecursive(int[] nums, int target, int sum, int currentIndex) {
        // Base case: reached the end of the array
        if (currentIndex == nums.length) {
            return sum == target ? 1 : 0;
        }

        // Plus the current element
        int plusCurrent = findTargetSumWaysRecursive(nums, target, sum + nums[currentIndex], currentIndex + 1);
        // Subtract the current element
        int minusCurrent = findTargetSumWaysRecursive(nums, target, sum - nums[currentIndex], currentIndex + 1);

        return plusCurrent + minusCurrent;
    }
}

SHOW CODE: TOP DOWN WITH MEMOIZATION
class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        Map<String, Integer> dp = new HashMap<>();
        return findTargetSumWaysRecursive(nums, target, 0, 0, dp);
    }

    private static int findTargetSumWaysRecursive(int[] nums, int target, int sum, int currentIndex,
            Map<String, Integer> dp) {
        // Base case: reached the end of the array
        if (currentIndex == nums.length) {
            return sum == target ? 1 : 0;
        }

        String key = currentIndex + "," + sum;
        if (dp.get(key) == null) {
            // Plus the current element
            int plusCurrent = findTargetSumWaysRecursive(nums, target, sum + nums[currentIndex], currentIndex + 1, dp);
            // Subtract the current element
            int minusCurrent = findTargetSumWaysRecursive(nums, target, sum - nums[currentIndex], currentIndex + 1, dp);

            dp.put(key, plusCurrent + minusCurrent);
        }

        return dp.get(key);
    }
}

416. Partition Equal Subset Sum

SHOW PROBLEM

Problem Statement

You are given an integer array nums. Your task is to determine whether it is possible to partition the array into two subsets such that the sum of the elements in both subsets is equal. If possible, return true; otherwise, return false.

Example 1:

  • Input: nums = [1,5,11,5]
  • Output: true
  • Explanation: The array can be partitioned into [1, 5, 5] and [11], both summing to 11.

Example 2:

  • Input: nums = [1,2,3,5]
  • Output: false
  • Explanation: The array cannot be partitioned into two subsets with equal sum.

Constraints:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

Go to Leetcode 🔗
SHOW CODE: BRUTE-FORCE (Time Limit Exceeded)
class Solution {
    public boolean canPartition(int[] nums) {
        int totalSum = Arrays.stream(nums).sum();

        // If the total sum is odd, it's impossible to partition it into two equal
        // subsets
        if (totalSum % 2 == 1) {
            return false;
        }

        int target = totalSum / 2;

        return canPartitionRecursive(nums, target, 0);
    }

    private static boolean canPartitionRecursive(int[] nums, int target, int currentIndex) {
        // Base case
        if (target == 0) {
            return true;
        }

        if (currentIndex >= nums.length || target < 0) {
            return false;
        }

        // Include the current element
        boolean include = canPartitionRecursive(nums, target - nums[currentIndex], currentIndex + 1);

        // Exclude the current element
        boolean exclude = canPartitionRecursive(nums, target, currentIndex + 1);

        return include || exclude;
    }
}

SHOW CODE: TOP DOWN WITH MEMOIZATION
class Solution {
    public boolean canPartition(int[] nums) {
        int totalSum = Arrays.stream(nums).sum();

        // If the total sum is odd, it's impossible to partition it into two equal
        // subsets
        if (totalSum % 2 == 1) {
            return false;
        }

        int target = totalSum / 2;
        Boolean[][] dp = new Boolean[nums.length][target + 1];

        return canPartitionRecursive(nums, target, 0, dp);
    }

    private static boolean canPartitionRecursive(int[] nums, int target, int currentIndex, Boolean[][] dp) {
        // Base case
        if (target == 0) {
            return true;
        }

        if (currentIndex >= nums.length || target < 0) {
            return false;
        }

        if (dp[currentIndex][target] == null) {
            // Include the current element
            boolean include = canPartitionRecursive(nums, target - nums[currentIndex], currentIndex + 1, dp);
            // Exclude the current element
            boolean exclude = canPartitionRecursive(nums, target, currentIndex + 1, dp);

            // Store the result to the cache
            dp[currentIndex][target] = include || exclude;
        }

        return dp[currentIndex][target];
    }
}

Unbounded Knapsack

322. Coin Change

SHOW PROBLEM

Problem Statement

You are given an integer array coins, where each element represents a different coin denomination, and an integer amount, which represents a total amount of money.

Your task is to determine the fewest number of coins needed to make up the given amount. If it is not possible to form the amount using any combination of the coins, return -1.

You may assume that you have an infinite number of each type of coin.

Example 1:

  • Input: coins = [1,2,5], amount = 11
  • Output: 3
  • Explanation: The minimum number of coins required is 3 (11 = 5 + 5 + 1).

Example 2:

  • Input: coins = [2], amount = 3
  • Output: -1
  • Explanation: It is impossible to form 3 using only coins of denomination 2.

Example 3:

  • Input: coins = [1], amount = 0
  • Output: 0
  • Explanation: No coins are needed to make up 0.

Constraints:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 2^{31} - 1
  • 0 <= amount <= 10^4

Go to Leetcode 🔗
SHOW CODE: BRUTE-FORCE (Time Limit Exceeded)
class Solution {
    public int coinChange(int[] coins, int amount) {
        int result = coinChangeRecursive(coins, amount, 0);

        return result == Integer.MAX_VALUE ? -1 : result;
    }

    private int coinChangeRecursive(int[] coins, int amount, int currentIndex) {
        // Base case: If amount is 0, no coins are needed
        if (amount == 0) {
            return 0;
        }

        // If index is out of bounds or amount becomes negative, return an impossible
        // value
        if (currentIndex >= coins.length || amount < 0) {
            return Integer.MAX_VALUE;
        }

        // Include the current coin
        int include = coinChangeRecursive(coins, amount - coins[currentIndex], currentIndex);
        if (include != Integer.MAX_VALUE) {
            include += 1; // Adding one coin to the count
        }

        // Exclude the current coin
        int exclude = coinChangeRecursive(coins, amount, currentIndex + 1);

        return Math.min(include, exclude);
    }
}

SHOW CODE: TOP DOWN WITH MEMOIZATION
class Solution {
    public int coinChange(int[] coins, int amount) {
        Map<String, Integer> dp = new HashMap<>();
        int result = coinChangeRecursive(coins, amount, 0, dp);

        return result == Integer.MAX_VALUE ? -1 : result;
    }

private int coinChangeRecursive(int[] coins, int amount, int currentIndex, Map<String, Integer> dp) {
        // Base case: If amount is 0, no coins are needed
        if (amount == 0) {
            return 0;
        }

        // If index is out of bounds or amount becomes negative, return an impossible value
        if (currentIndex >= coins.length || amount < 0) {
            return Integer.MAX_VALUE;
        }

        String key = currentIndex + "," + amount;
        if (!dp.containsKey(key)) {
            // Include the current coin
            int include = coinChangeRecursive(coins, amount - coins[currentIndex], currentIndex, dp);
            if (include != Integer.MAX_VALUE) {
                include += 1; // Adding one coin to the count
            }

            // Exclude the current coin
            int exclude = coinChangeRecursive(coins, amount, currentIndex + 1, dp);

            dp.put(key, Math.min(include, exclude));
        }

        return dp.get(key);
    }
}

Longest Common Substring

1143. Longest Common Subsequence

SHOW PROBLEM

Problem Statement

You are given two strings, text1 and text2.

Your task is to determine the length of their longest common subsequence. If there is no common subsequence, return 0.

A subsequence of a string is a new string generated from the original string by deleting some characters (or none) without changing the relative order of the remaining characters.

A common subsequence of two strings is a subsequence that appears in both strings.

Example 1:

  • Input: text1 = "abcde", text2 = "ace"
  • Output: 3
  • Explanation: The longest common subsequence is "ace", which has a length of 3.

Example 2:

  • Input: text1 = "abc", text2 = "abc"
  • Output: 3
  • Explanation: The longest common subsequence is "abc", which has a length of 3.

Example 3:

  • Input: text1 = "abc", text2 = "def"
  • Output: 0
  • Explanation: There is no common subsequence, so the result is 0.

Constraints:

  • 1 <= text1.length, text2.length <= 1000
  • text1 and text2 consist of only lowercase English letters.

Go to Leetcode 🔗
SHOW CODE: BRUTE-FORCE (Time Limit Exceeded)
class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        return findLCSRecursive(text1, text2, 0, 0);
    }

    private static int findLCSRecursive(String text1, String text2, int i1, int i2) {
        // Base case
        if (i1 == text1.length() || i2 == text2.length()) {
            return 0;
        }

        // The characters in 'text1' and 'text2' are matched
        if (text1.charAt(i1) == text2.charAt(i2)) {
            return 1 + findLCSRecursive(text1, text2, i1 + 1, i2 + 1);
        }

        // Skip the character in 'text2'
        int c1 = findLCSRecursive(text1, text2, i1, i2 + 1);
        // Skip the character in 'text1'
        int c2 = findLCSRecursive(text1, text2, i1 + 1, i2);

        return Math.max(c1, c2);
    }
}

SHOW CODE: TOP DOWN WITH MEMOIZATION
class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        Integer[][] dp = new Integer[text1.length()][text2.length()];

        return findLCSRecursive(text1, text2, 0, 0, dp);
    }

    private static int findLCSRecursive(String text1, String text2, int i1, int i2, Integer[][] dp) {
        // Base case
        if (i1 == text1.length() || i2 == text2.length()) {
            return 0;
        }

        if (dp[i1][i2] == null) {
            // The characters in 'text1' and 'text2' are matched
            if (text1.charAt(i1) == text2.charAt(i2)) {
                dp[i1][i2] = 1 + findLCSRecursive(text1, text2, i1 + 1, i2 + 1, dp);
            } else {
                // Skip the character in 'text2'
                int c1 = findLCSRecursive(text1, text2, i1, i2 + 1, dp);
                // Skip the character in 'text1'
                int c2 = findLCSRecursive(text1, text2, i1 + 1, i2, dp);
                dp[i1][i2] = Math.max(c1, c2);
            }
        }

        return dp[i1][i2];
    }
}

300. Longest Increasing Subsequence

SHOW PROBLEM

Problem Statement

You are given an integer array nums. Your task is to return the length of the longest strictly increasing subsequence.

A strictly increasing subsequence is a subsequence where each element is strictly greater than the preceding one.

Example 1:

  • Input: nums = [10,9,2,5,3,7,101,18]
  • Output: 4
  • Explanation: The longest increasing subsequence is [2,3,7,101], so the length is 4.

Example 2:

  • Input: nums = [0,1,0,3,2,3]
  • Output: 4
  • Explanation: The longest increasing subsequence is [0,1,2,3], so the length is 4.

Example 3:

  • Input: nums = [7,7,7,7,7,7,7]
  • Output: 1
  • Explanation: There is no strictly increasing subsequence longer than 1.

Constraints:

  • 1 <= nums.length <= 2500
  • -10^4 <= nums[i] <= 10^4

Follow up: Can you come up with an algorithm that runs in O(n log n) time complexity?


Go to Leetcode 🔗
SHOW CODE: BRUTE-FORCE (Time Limit Exceeded)
class Solution {
    public int lengthOfLIS(int[] nums) {
        return findLISLengthRecursive(nums, 0, -1);
    }

    private static int findLISLengthRecursive(int[] nums, int currentIndex, int previousIndex) {
        // Base case
        if (currentIndex == nums.length) {
            return 0;
        }

        // Include the current element if it is larger than the last included element
        int c1 = 0;
        if (previousIndex == -1 || nums[currentIndex] > nums[previousIndex]) {
            c1 = 1 + findLISLengthRecursive(nums, currentIndex + 1, currentIndex);
        }

        // Exlclude the current element
        int c2 = findLISLengthRecursive(nums, currentIndex + 1, previousIndex);

        return Math.max(c1, c2);
    }
}

SHOW CODE: TOP DOWN WITH MEMOIZATION
class Solution {
    public int lengthOfLIS(int[] nums) {
        Integer[][] dp = new Integer[nums.length + 1][nums.length];
        return findLISLengthRecursive(nums, 0, -1, dp);
    }

    private static int findLISLengthRecursive(int[] nums, int currentIndex, int previousIndex, Integer[][] dp) {
        // Base case
        if (currentIndex == nums.length) {
            return 0;
        }

        if (dp[previousIndex + 1][currentIndex] == null) {
            // Include the current element if it is larger than the last included element
            int c1 = 0;
            if (previousIndex == -1 || nums[currentIndex] > nums[previousIndex]) {
                c1 = 1 + findLISLengthRecursive(nums, currentIndex + 1, currentIndex, dp);
            }

            // Exlclude the current element
            int c2 = findLISLengthRecursive(nums, currentIndex + 1, previousIndex, dp);

            dp[previousIndex + 1][currentIndex] = Math.max(c1, c2);
        }

        return dp[previousIndex + 1][currentIndex];
    }
}

Binary Search

Binary Search is an algorithmic technique used to find a target value in a sorted array or list. It works by repeatedly dividing the search interval in hald. If the target value is smaller than the middle value, the search continues in the left half; if it is larger, the search continues in the right half.

Steps of Binary Search:

  1. Initialize two pointers: left and right.
  2. Calculate the middle index using the formula mid = left + (right - left) / 2. (DON’T USE the formula mid = (left + right) / 2 to find the middle index, because it may lead to overflow when left and right are large.)
  3. Compare the target value with the element at the mid index:
    • If the target equals to the middle value, return the mid index.
    • If the target is smaller than the middle value, update right = middle - 1 to search the left half.
    • If the target is larger than the middle value, update left = middle + 1 to search the right half.
  4. If no match is found (left > right), return -1.

Binary search can be implemented using two approaches: Iterative and Recursive. Below are the code implementations for both:

SHOW CODE
public int binarySearch(int[] arr, int target) {
    int low = 0;
    int high = arr.length - 1;
    
    while (low <= high) {
        int mid = low + (high - low) / 2; // Prevent overflow
        
        if (arr[mid] == target) {
            return mid; // Target found at index mid
        } else if (arr[mid] < target) {
            low = mid + 1; // Target is in the right half
        } else {
            high = mid - 1; // Target is in the left half
        }
    }
    
    return -1; // Target not found
}
public int binarySearchRecursive(int[] arr, int target, int low, int high) {
    if (low > high) {
        return -1; // Base case: target is not found
    }
    
    int mid = low + (high - low) / 2; // Prevent overflow
    
    if (arr[mid] == target) {
        return mid; // Target found
    } else if (arr[mid] < target) {
        return binarySearchRecursive(arr, target, mid + 1, high); // Search in the right half
    // } else if (nums[mid] > target) { Using else if here leads to missing return complaint
    } else {
        return binarySearchRecursive(arr, target, low, mid - 1); // Search in the left half
    }
}

SHOW PROBLEM

Problem Statement:

Given an array of integers nums which is sorted in ascending order, and an integer target, write a function to search target in nums. If target exists, then return its index. Otherwise, return -1.

You must write an algorithm with O(log n) runtime complexity.

Example 1:

  • Input: nums = [-1,0,3,5,9,12], target = 9
  • Output: 4
  • Explanation: 9 exists in nums and its index is 4

Example 2:

  • Input: nums = [-1,0,3,5,9,12], target = 2
  • Output: -1
  • Explanation: 2 does not exist in nums so return -1

Constraints:

  • 1 <= nums.length <= 104
  • -104 < nums[i], target < 104
  • All the integers in nums are unique.
  • nums is sorted in ascending order.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int search(int[] nums, int target) {
        int left = 0, right = nums.length - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2; // prevent overflow

            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            }
        }

        return -1;
    }
}
class Solution {
    public int search(int[] nums, int target) {
        return searchRecursive(nums, target, 0, nums.length - 1);
    }

    private static int searchRecursive(int[] nums, int target, int left, int right) {
        if (left > right) {
            return -1;
        }

        int mid = left + (right - left) / 2; // Prevent overflow

        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            return searchRecursive(nums, target, left + 1, right);
        // } else if (nums[mid] > target) { Using else if here lead to missing return complaint
        } else {
            return searchRecursive(nums, target, left, right - 1);
        }
    }
}

SHOW NOTES

Visulization:

Binary Search


35. Search Insert Position

SHOW PROBLEM

Problem Statement

You are given a sorted array of distinct integers nums and a target value target. Your task is to return the index of target if it is found in nums. If target is not found, return the index where it would be if it were inserted in order.

You must implement an algorithm with O(log n) runtime complexity.

Example 1:

  • Input: nums = [1,3,5,6], target = 5
  • Output: 2
  • Explanation: The number 5 exists in nums at index 2.

Example 2:

  • Input: nums = [1,3,5,6], target = 2
  • Output: 1
  • Explanation: The number 2 does not exist in nums, but it would be inserted at index 1.

Example 3:

  • Input: nums = [1,3,5,6], target = 7
  • Output: 4
  • Explanation: The number 7 does not exist in nums, but it would be inserted at index 4.

Constraints:

  • 1 <= nums.length <= 10^4
  • -10^4 <= nums[i] <= 10^4
  • nums contains distinct values sorted in ascending order.
  • -10^4 <= target <= 10^4

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0, right = nums.length - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2; // Prevent overflow

            if (nums[mid] == target) {
                return mid; // Target found, return it's index
            } else if (nums[mid] < target) {
                left = mid + 1; // Process the right half
            } else if (nums[mid] > target) {
                right = mid - 1; // Process the left half
            }
        }

        // Target not found, return its insertion index
        return left;
    }
}

34. Find First and Last Position of Element in Sorted Array

SHOW PROBLEM

Problem Statement

You are given an array of integers nums sorted in non-decreasing order. Your task is to find the starting and ending position of a given target value. If the target is not found in the array, return [-1, -1].

You must implement an algorithm with O(log n) runtime complexity.

Example 1:

  • Input: nums = [5,7,7,8,8,10], target = 8
  • Output: [3,4]
  • Explanation: The target 8 appears starting at index 3 and ending at index 4.

Example 2:

  • Input: nums = [5,7,7,8,8,10], target = 6
  • Output: [-1,-1]
  • Explanation: The target 6 is not present in the array, so the function returns [-1,-1].

Example 3:

  • Input: nums = [], target = 0
  • Output: [-1,-1]
  • Explanation: The array is empty, so the target cannot be found.

Constraints:

  • 0 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
  • nums is a non-decreasing array.
  • -10^9 <= target <= 10^9

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] result = new int[]{-1, -1};
        
        // Find the first occurrence of the target
        result[0] = search(nums, target, false);
            
        // No need to further search if target is not found
        if (result[0] != -1) {
            // Find the last occurrence of the target
            result[1] = search(nums, target, true);
        }

        return result;
    }

    private static int search(int[] nums, int target, boolean findLastIndex) {
        int left = 0, right = nums.length - 1;
        int index = -1;

        while (left <= right) {
            int mid = left + (right - left) / 2; // Prevent overflow

            if (nums[mid] == target) {
                index = mid; // Target found, update the index
                // If searching for the last occurrence, continue searching to the right
                if (findLastIndex) {
                    left = mid + 1;
                } else {
                    // If searching for the first occurrence, continue searching to the left
                    right = mid - 1;
                }
            } else if (nums[mid] < target) {
                left = mid + 1; // Search in the right half
            } else if (nums[mid] > target) {
                right = mid - 1; // Search in the left half
            }
        }

        return index;
    }
}

744. Find Smallest Letter Greater Than Target

SHOW PROBLEM

Problem Statement

You are given an array of characters letters that is sorted in non-decreasing order, and a character target. There are at least two different characters in letters.

Your task is to return the smallest character in letters that is lexicographically greater than target. If such a character does not exist, return the first character in letters.

Example 1:

  • Input: letters = ["c","f","j"], target = "a"
  • Output: "c"
  • Explanation: The smallest character that is lexicographically greater than ‘a’ in letters is ‘c’.

Example 2:

  • Input: letters = ["c","f","j"], target = "c"
  • Output: "f"
  • Explanation: The smallest character that is lexicographically greater than ‘c’ in letters is ‘f’.

Example 3:

  • Input: letters = ["x","x","y","y"], target = "z"
  • Output: "x"
  • Explanation: There are no characters in letters that are lexicographically greater than ‘z’, so we return letters[0].

Constraints:

  • 2 <= letters.length <= 10^4
  • letters[i] is a lowercase English letter.
  • letters is sorted in non-decreasing order.
  • letters contains at least two different characters.
  • target is a lowercase English letter.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public char nextGreatestLetter(char[] letters, char target) {
        int n = letters.length;
        int left = 0, right = n - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2; // Prevent overflow

            if (letters[mid] > target) {
                right = mid - 1;
            } else if (letters[mid] <= target) {
                // Find the rightmost target if there are duplicate target
                left = mid + 1;
            }
        }

        // At the end of the while loop: right == left + 1
        return letters[left % n];
    }
}

875. Koko Eating Bananas

SHOW PROBLEM

Problem Statement

Koko loves to eat bananas. There are n piles of bananas, and the $\text{i}^{\text{th}}$ pile contains piles[i] bananas. The guards have gone and will come back in h hours.

Koko can decide her bananas-per-hour eating speed k. Each hour, she chooses a pile of bananas and eats k bananas from that pile. If the pile has fewer than k bananas, she eats all of them instead and will not eat any more bananas during that hour.

Koko likes to eat slowly but still wants to finish eating all the bananas before the guards return.

Your task is to return the minimum integer k such that she can eat all the bananas within h hours.

Example 1:

  • Input: piles = [3,6,7,11], h = 8
  • Output: 4
  • Explanation: Koko can eat at a rate of 4 bananas per hour to finish all the piles within 8 hours.

Example 2:

  • Input: piles = [30,11,23,4,20], h = 5
  • Output: 30
  • Explanation: To finish within 5 hours, Koko must eat at a rate of 30 bananas per hour.

Example 3:

  • Input: piles = [30,11,23,4,20], h = 6
  • Output: 23
  • Explanation: Koko can finish all the piles in 6 hours with an eating speed of 23 bananas per hour.

Constraints:

  • 1 <= piles.length <= 10^4
  • piles.length <= h <= 10^9
  • 1 <= piles[i] <= 10^9

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        int left = 1;
        int right = Arrays.stream(piles).max().getAsInt();

        while (left <= right) {
            int mid = left + (right - left) / 2; // Prevent overflow

            // If Koko can eat up all bananas at current speed, try eat slowly
            if (eatUp(piles, h, mid)) {
                right = mid - 1;
            } else {
                // If Koko can't eat up all bananas at current speed, try eat quickly
                left = mid + 1;
            }
        }

        // At the end of the while loop, left = right + 1
        return left;
    }

    private static boolean eatUp(int[] piles, int h, int k) {
        int requiredHours = 0;
        for (int pile : piles) {
            requiredHours += Math.ceil((double) pile / k);
        }

        return requiredHours <= h;
    }
}

278. First Bad Version

SHOW PROBLEM

Problem Statement

You are a product manager leading a team to develop a new product. However, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all versions after a bad version are also bad.

You are given n versions [1, 2, ..., n] and need to determine the first bad version, which causes all subsequent versions to be bad.

You are provided with an API bool isBadVersion(version), which returns true if the version is bad, otherwise false. Implement a function to find the first bad version while minimizing the number of calls to the API.

Example 1:

  • Input: n = 5, bad = 4
  • Output: 4
  • Explanation:
    • isBadVersion(3) -> false
    • isBadVersion(5) -> true
    • isBadVersion(4) -> true
    • The first bad version is 4.

Example 2:

  • Input: n = 1, bad = 1
  • Output: 1

Constraints:

  • 1 <= bad <= n <= 2^31 - 1

Go to Leetcode 🔗
SHOW CODE
/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 0, right = n;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (isBadVersion(mid)) {
                right = mid; // Bad version found, search in the left half
            } else {
                left = mid + 1; // Search in the right half
            }
        }

        // Left will be the first bad version
        return left;
    }
}

String

Nowcoder: BD5 字符串匹配

SHOW PROBLEM

问题描述

牛牛有两个字符串 AB

  • A 是一个 01 串,仅包含字符 '0''1'
  • B 可能包含 '0''1''?',其中 '?' 可以表示 '0''1'

牛牛想知道,在所有可能的 B 变体中,有多少种可以作为子串出现在 A 中,即完成字符串匹配

输入描述:

  • 第一行:字符串 A,长度 length,满足 1 ≤ length ≤ 50,仅包含 '0''1'
  • 第二行:字符串 B,长度 length,满足 1 ≤ length ≤ 50,可能包含 '0''1''?'

输出描述:

  • 输出一个整数,表示 B 所有可能变体中,可以在 A 中匹配的个数。

示例 1

  • 输入:
    00010001  
    ??  
    
  • 输出:
    3  
    
  • 解释: B 的可能字符串有 "00", "01", "10", "11",其中 "11" 未出现在 A 中,所以答案是 3

Go to Leetcode 🔗
SHOW CODE
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        String A = in.nextLine().trim();
        String B = in.nextLine().trim();

        // Count the number of unique matchings
        int result = countMatches(A, B);

        // Output result
        System.out.println(result);
    }

    private static int countMatches(String A, String B) {
        int n = A.length(), m = B.length();

        // Use a set to store unique matching substrings
        Set<String> set = new HashSet<>();

        // Iterate over all starting positions at A for a substring of length m
        for (int i = 0; i <= n - m; i++) {
            matches(A, B, i, set);
        }

        // Return the count of unique matches found
        return set.size();
    }

    private static void matches(String A, String B, int start, Set<String> set) {
        boolean matched = true;

        for (int j = 0; j < B.length(); j++) {
            if (B.charAt(j) != '?' && B.charAt(j) != A.charAt(start + j)) {
                matched = false; // If a mismatch found, stop checking further
                break;
            }
        }

        // If the substring matches, add it to the set to ensure uniqueness
        if (matched) {
            set.add(A.substring(start, start + B.length()));
        }
    }
}

Nowcoder: OR69 字符串组合

SHOW PROBLEM

问题描述

给定一个字符串 s,请输出该字符串中相邻字符的所有可能组合。

要求:

  • 输出的组合需要去重
  • 相同长度的组合需要按照字典序排序。
  • 组合之间用空格分隔。

输入描述:

  • 一个字符串 s,仅包含大小写字母。

输出描述:

  • 一行字符串,每个组合以空格分隔。

示例 1

  • 输入: "bac"
  • 输出: "a b c ac ba bac"

Go to Nowcoder 🔗
SHOW CODE
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        String s = in.nextLine().trim();

        // Generate subsets of adjacent characters
        String result = generateSubsets(s);

        // Outout result
        System.out.println(result);
    }

    private static String generateSubsets(String s) {
        // Use a HashSet to store unique subsets
        Set<String> resultSet = new HashSet<>();

        // Generate all adjacent character combinations
        for (int i = 0; i < s.length(); i++) {
            StringBuilder builder = new StringBuilder();
            for (int j = i; j < s.length(); j++) {
                // Append character at index 'j' to the substring
                builder.append(s.charAt(j));
                // Add the substring to the set to ensure uniqueness
                resultSet.add(builder.toString());
            }
        }

        // Convert HashSet to List for sorting
        List<String> resultList = new ArrayList<>(resultSet);

        /**
            Custom sorting:
                1. First by length
                2. If length is equal, then sort by lexicographical order
         */
        resultList.sort((a, b) -> a.length() == b.length() ? a.compareTo(
                           b) : Integer.compare(a.length(), b.length()));

        // Format the result and return it
        return String.join(" ", resultList);
    }

}

Nowcoder: CD130 拼接所有的字符串产生字典序最小的字符串

SHOW PROBLEM

问题描述

给定一个字符串数组 strs,请找到一种拼接顺序,使得所有字符串拼接后组成的字符串在所有可能性中字典序最小,并返回该字符串。

输入描述:

  • 第一行包含一个整数 n,表示字符串数组的长度,满足:
    1 ≤ n ≤ 10^5
  • 接下来的 n 行,每行包含一个字符串 strs[i],保证字符串长度小于 10,且仅由小写字母组成。

输出描述:

  • 输出一行,表示按字典序拼接后得到的最小字符串。

示例 1:

  • 输入:
2  
abc  
de  
  • 输出:
abcde  

示例 2:

  • 输入:
2  
b  
ba  
  • 输出:
bab  

备注:

  • 时间复杂度: O(nlogn)
  • 空间复杂度: O(1)

Go to Nowcoder 🔗
SHOW CODE
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // Read the number of strings
        int n = Integer.parseInt(scanner.nextLine());

        // Read the strings into an array
        String[] strs = new String[n];
        for (int i = 0; i < n; i++) {
            strs[i] = scanner.nextLine().trim();
        }

        // Sort the strings using custom comparator
        Arrays.sort(strs, (s1, s2) -> (s1 + s2).compareTo(s2 + s1));

        // Build the result by concatenating all the sorted strings
        StringBuilder result = new StringBuilder();
        for (String str : strs) {
            result.append(str);
        }

        // Output the result
        System.out.println(result.toString());

        scanner.close();
    }
}

Sorting

Nowcoder: WY24 洗牌

SHOW PROBLEM

题目描述

洗牌在生活中十分常见,现在需要编写一个程序来模拟洗牌的过程。

给定 2n 张牌,初始时按照从上到下的顺序编号为第 1 张、第 2 张,直到第 2n 张。洗牌规则如下:

  1. 将这 2n 张牌分成两堆:

    • 左手 持有前 n 张(上半堆)。
    • 右手 持有后 n 张(下半堆)。
  2. 依次从右手和左手交替放牌:

    • 先放下 右手 的最后一张牌。
    • 再放下 左手 的最后一张牌。
    • 依次重复,直到左手的第一张牌放下。
  3. 重新合并这 2n 张牌,形成新的序列。

重复上述洗牌过程 k 次,最终得到洗牌后的序列。

输入描述

  • 第一行包含一个整数 T(1 ≤ T ≤ 100),表示数据组数。
  • 对于每组数据:
    • 第一行包含两个整数 n, k(1 ≤ n, k ≤ 100),分别表示牌堆大小和洗牌次数。
    • 接下来一行包含 2n 个整数 a₁, a₂, …, a₂n(1 ≤ aᵢ ≤ 10⁹),表示初始牌序列(从上到下)。

输出描述
对于每组数据,输出一行,表示 k 次洗牌后的最终序列。数字之间用 空格 分隔,行末不能有多余空格

示例 1

输入:

3
3 1
1
2
3
4
5
6
3 2
1
2
3
4
5
6
2 2
1
1
1
1

输出:

1 4 2 5 3 6
1 5 4 3 2 6
1 1 1 1

Go to Nowcoder 🔗
SHOW CODE
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        int T = in.nextInt(); // Read the number of test cases
        while (T-- > 0) {
            int n = in.nextInt();
            int k = in.nextInt();
            int[] cards = new int[2 * n];

            // Read the initial deck
            for (int i = 0; i < 2 * n; i++) {
                cards[i] = in.nextInt();
            }

            // Perform k shuffles
            while (k-- > 0) {
                cards = shuffleCards(cards, n);
            }

            // Print the final shuffled deck
            System.out.println(Arrays.stream(cards)
                               .mapToObj(String::valueOf)
                               .collect(Collectors.joining(" ")));
        }

        in.close();
    }

    private static int[] shuffleCards(int[] cards, int n) {
        int[] shuffled = new int[2 * n];
        int index = 0;

        for (int i = 0; i < n; i++) {
            shuffled[index++] = cards[i];
            shuffled[index++] = cards[i + n];
        }

        return shuffled;
    }
}

Math

1154. Day of the Year

SHOW PROBLEM https://leetcode.com/problems/day-of-the-year

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int dayOfYear(String date) {
        // Days in each month
        int[] daysOfMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

        // Parse the date string into year, month, and day
        int[] dates = Arrays.stream(date.split("-"))
                .mapToInt(Integer::parseInt)
                .toArray();
        int year = dates[0];
        int month = dates[1];
        int day = dates[2];

        // Calculate the total days by summing up the days of previous month
        int days = day + Arrays.stream(daysOfMonth, 0, month - 1).sum();

        // Add an extra day if it's a leap year and the date is after February
        if (month > 2 && isLeapYear(year)) {
            days++;
        }

        return days;
    }

    private static boolean isLeapYear(int year) {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }
}

Array

HW: 聚会最大客流量统计

SHOW PROBLEM

问题描述

你需要统计一个 8 小时聚会 在不同时间段内的最大客人数。聚会时间从 12:00 到 20:00,客人需要提前提供 到达时间和离开时间,你的任务是计算 每个小时区间 内的最大客人数量。

要求

  1. 到达和离开的时间以整点计算,输入为两个整数,例如 12,18 表示客人 在 12:00 后 13:00 前到达在 17:00 后 18:00 前离开
  2. 统计每个小时区间 [12,13), [13,14), ..., [19,20)8 个时间段 内的最大客人数量。
  3. 最多邀请 100 名客人

输入格式

  • 每行输入一个 到达时间离开时间,用逗号分隔。

输出格式

  • 每个小时区间的人数,按照 [起始时间, 结束时间): 人数 格式输出。

示例

输入

12,15
16,17
12,20

输出

[12,13):2
[13,14):2
[14,15):2
[15,16):1
[16,17):2
[17,18):1
[18,19):1
[19,20):1

SHOW CODE
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        // Store the number of guests between 12:00 to 20:00
        int[] guestTraffic = new int[8];

        while (in.hasNextLine()) {
            String time = in.nextLine().trim();

            if (time.isEmpty()) {
                break;
            }

            updateGuestTraffic(guestTraffic, time);
        }

        // Format output
        formatOutPut(guestTraffic);
    }

    private static void updateGuestTraffic(int[] guestTraffic, String time) {
        String[] parts = time.split(",");
        int arrive = Integer.parseInt(parts[0]);
        int leave = Integer.parseInt(parts[1]);

        for (int i = arrive; i < leave; i++) {
            guestTraffic[i - 12]++;
        }
    }

    private static void formatOutPut(int[] guestTraffic) {
        for (int i = 0; i < guestTraffic.length; i++) {
            System.out.printf("[%d,%d):%d%n", 12 + i, 13 + i, guestTraffic[i]);
        }
    }
}

Priority Queue

A priority queue is a special type of queue where elements are dequeued based on their priority. Priority queues are often implemented using heaps. There are two types of priority queues: Min Priority Queue and Max Priority Queue.

  • Min Priority Queue: The elements with the lowest priority are dequeued first. It is implemented using a min heap, where the smallest element is at the top.
  • Max Priority Queue: The elements with the highest priority are dequeued first. It is implemented using a max heap, where the largest element is at the top.

Priority queues are particularly helpful for solving problems such as Top K Elements, K-way Merge, and Task Scheduling.

  • Top K Elements: This involves finding the top K largest or smallest elements form a collection of data.
  • K-way Merge: This involves merging K sorted lists or arrays into a single sorted list.
  • Task Scheduling: This involves processing tasks based on their priority or deadline, ensuring the highest-priority task or the task with the earliest deadline is processed first.

215. Kth Largest Element in an Array

SHOW PROBLEM

Problem Statement

Given an integer array nums and an integer k, return the kth largest element in the array.

Note that it is the kth largest element in the sorted order, not the kth distinct element.

Can you solve it without sorting?

Example 1:

  • Input: nums = [3,2,1,5,6,4], k = 2
  • Output: 5
  • Explanation: The 2nd largest element in the sorted array [1, 2, 3, 4, 5, 6] is 5.

Example 2:

  • Input: nums = [3,2,3,1,2,4,5,5,6], k = 4
  • Output: 4
  • Explanation: The 4th largest element in the sorted array [1, 2, 2, 3, 3, 4, 5, 5, 6] is 4.

Constraints:

  • 1 <= k <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int findKthLargest(int[] nums, int k) {
        // The top element in a minHeap is the smallest element
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();

        for (int num:nums) {
            minHeap.offer(num);
            // Maintain a k-size min heap
            if (minHeap.size() > k) {
                minHeap.poll();
            }
        }

        return minHeap.poll();
    }
}

347. Top K Frequent Elements

SHOW PROBLEM

Problem Statement

Given an integer array nums and an integer k, return the k most frequent elements. You may return the answer in any order.

Example 1:

  • Input: nums = [1,1,1,2,2,3], k = 2
  • Output: [1,2]
  • Explanation: The most frequent elements are 1 and 2.

Example 2:

  • Input: nums = [1], k = 1
  • Output: [1]
  • Explanation: The only element is 1.

Constraints:

  • 1 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4
  • k is in the range [1, the number of unique elements in the array].
  • It is guaranteed that the answer is unique.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> numCount = new HashMap<>();

        // Count the frequency of each element in the array
        for (int num : nums) {
            numCount.put(num, numCount.getOrDefault(num, 0) + 1);
        }

        //  Use a min-heap to store the k most frequent elements
        PriorityQueue<Map.Entry<Integer, Integer>> minHeap = new PriorityQueue<>(
            (e1, e2) -> e1.getValue() - e2.getValue()  // Compare based on frequency
        );

        // Add entries to the heap, maintaining only the top k frequent elements
        for (Map.Entry<Integer, Integer> entry : numCount.entrySet()) {
            minHeap.offer(entry);
            // If the heap exceeds size k, remove the element with the smallest frequency
            if (minHeap.size() > k) {
                minHeap.poll();
            }
        }

        int[] result = new int[k];
        for (int i = 0; i < k; i++) {
            result[i] = minHeap.poll().getKey();
        }

        return result;
    }
}

SHOW NOTES


23. Merge k Sorted Lists

SHOW PROBLEM

Problem Statement

You are given an array of k linked-lists lists, each linked-list is sorted in ascending order.

Merge all the linked-lists into one sorted linked-list and return it.

Example 1:

  • Input: lists = [[1,4,5],[1,3,4],[2,6]]
  • Output: [1,1,2,3,4,4,5,6]
  • Explanation: The linked-lists are:
    1->4->5,
    1->3->4,
    2->6
    
    Merging them into one sorted list:
    1->1->2->3->4->4->5->6
    

Example 2:

  • Input: lists = []
  • Output: []
  • Explanation: There are no linked-lists to merge, so the result is an empty list.

Example 3:

  • Input: lists = [[]]
  • Output: []
  • Explanation: The only linked-list is empty, so the result is also an empty list.

Constraints:

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i] is sorted in ascending order.
  • The sum of lists[i].length will not exceed 10^4.

Go to Leetcode 🔗
SHOW CODE
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        // The top element of min heap is the smallest element
        PriorityQueue<ListNode> minHeap = new PriorityQueue<>(
            (n1, n2) -> n1.val - n2.val
        );

        // Put the root of each list into min heap
        for (ListNode root : lists) {
            if (root != null) {
                minHeap.offer(root);
            }
        }

        ListNode head = null, tail = null;
        while (!minHeap.isEmpty()) {
            ListNode node = minHeap.poll();
            if (head == null) {
                head = tail = node;
            } else {
                tail.next = node;
                tail = tail.next;
            }

            // If the top element has a next element add it to the min heap
            if (node.next != null) {
                minHeap.offer(node.next);
            }
        }

        return head;
    }
}

SHOW NOTES

Visulization:

Merge K Sorted Lists


630. Course Schedule III

SHOW PROBLEM

Problem Statement

There are n different online courses numbered from 1 to n. You are given an array courses where courses[i] = [durationᵢ, lastDayᵢ] indicates that the ith course should be taken continuously for durationᵢ days and must be finished on or before lastDayᵢ.

You will start on day 1, and you cannot take two or more courses simultaneously.

Return the maximum number of courses that you can take.

Example 1:

  • Input: courses = [[100,200],[200,1300],[1000,1250],[2000,3200]]
  • Output: 3
  • Explanation:
    • There are 4 courses in total, but you can take at most 3:
    • Take the 1st course (100 days), finish on day 100.
    • Take the 3rd course (1000 days), finish on day 1100.
    • Take the 2nd course (200 days), finish on day 1300.
    • The 4th course cannot be taken, as it would end on day 3300, exceeding the allowed last day.

Example 2:

  • Input: courses = [[1,2]]
  • Output: 1

Example 3:

  • Input: courses = [[3,2],[4,3]]
  • Output: 0

Constraints:

  • 1 <= courses.length <= 10^4
  • 1 <= durationᵢ, lastDayᵢ <= 10^4

Go to Leetcode 🔗
SHOW CODE

SHOW NOTES

First, sort the courses in ascending order based on their last day. Then, use a max heap to track the durations of the selected courses. If adding a new course causes the total duration to exceed its last day, replace the longest-duration course in the heap with the current one if it’s shorter. This approach works because, after sorting, the current course’s deadline is always at least as late as the longest-duration course in the heap. This strategy maximizes the number of courses that can be completed within their deadlines.

Why maxHeap.peek() > duration instead of totalTime + duration - maxHeap.peek() < lastDay?

  1. Since lastDay is non-decreasing after sorting, replacing a longer course with a shorter one is always a safe operation. In other words, when maxHeap.peek() > duration, swapping the longest course with a shorter one ensures that the total duration does not exceed any course deadline while maintaining the number of attended courses.


Bitwise Manipulation

HW:计算座位最大利用数

SHOW PROBLEM

题目描述

一列拥有 m 个座位的火车,在从起点到终点的行程中,共停靠 n 个站点,站点编号从 0n-1。在发车前,共有 x 名乘客预订了座位。由于预订乘客可能超过座位数,为了最大化座位的使用效率,请计算如何安排座位,使座位的总利用率达到最大,并输出最大值。

座位利用率的定义如下:
每个座位的利用率等于它被使用的总站数。例如,假设有两个座位:

  • 第一个座位从 0 站10 站 被使用(即乘客 0 站上车,10 站下车,10 站不占座),则该座位利用率为 10-0=10
  • 第二个座位从 1 站9 站 被使用,则该座位利用率为 9-1=8
    总座位利用率为 10 + 8 = 18

在乘客下车后,该座位立即可用于后续乘客,无需考虑换座问题。同时,确保任意时刻车上乘客总数不超过座位数 m

输入描述

  • 第一行输入三个整数 m、n、x,分别表示:
    • m(1 ≤ m ≤ 9):列车的座位数量
    • n(2 ≤ n ≤ 20):列车停靠的站点数量
    • x(1 ≤ x ≤ 9):预订乘客数量
  • 接下来 x 行,每行包含两个整数 a、b(0 ≤ a < b ≤ n),表示一个乘客的上车站点 a 和下车站点 b

输出描述: 输出一个整数,表示座位利用率的最大值。

输入示例

2 11 4
0 1
1 9
0 10
3 8

输出示例

19

示例解析

  • 可选乘客的组合如下:
    • 选择乘客 (0,1), (1,9), (0,10),座位利用率最大,为 (1-0) + (9-1) + (10-0) = 19
    • 若选择乘客 (0,10), (3,8),则利用率为 (10-0) + (8-3) = 15
    • 若选择全部乘客,则在 3 到 8 站 时会有 3 名乘客同时在车上,超过座位数 2,不符合条件。
  • 最终,最大座位利用率为 19

SHOW CODE
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        // Read input
        int seats = in.nextInt();
        int stations = in.nextInt();
        int bookings = in.nextInt();

        int[][] reservations = new int[bookings][2];
        for (int i = 0; i < bookings; i++) {
            reservations[i][0] = in.nextInt(); // Boarding station
            reservations[i][1] = in.nextInt(); // Destination station
        }

        // Compute the maximum seat utilization
        int maxUtilization = getMaxUtilization(reservations, seats, stations, bookings);

        // Output the result
        System.out.println(maxUtilization);
    }

    private static int getMaxUtilization(int[][] reservations, int seats, int stations, int bookings) {
        int maxUtilization = 0;

        // Enumerate all possible combinations: 2^{bookings}
        for (int i = 0; i < (1 << bookings); i++) {
            // Track the number of passengers at each station
            int[] passengers = new int[stations];
            // Track if the current combination is valid
            boolean valid = true;
            // Track total utilization for the current combination
            int totalUtilization = 0;

            for (int j = 0; j < bookings; j++) {
                // Check if the current combination contains j-th booking
                // For example, i = 5 = 101, j = 1, as the j-th digit is 0,
                // (i >> j) & 1 = (10 & 1) = 0, meaning the j-th booking is
                // not included
                if (((i >> j) & 1) == 1) {
                    // If the j-th booking included, update the passengers array
                    for (int k = reservations[j][0]; k < reservations[j][1]; k++) {
                        passengers[k]++;
                    }

                    // Update the total utilization 
                    totalUtilization += reservations[j][1] - reservations[j][0];
                }
            }

            // Check if the passengers at each station exceeds the seats
            for (int j = 0; j < stations; j++) {
                if (passengers[j] > seats) {
                    valid = false;
                    break;
                }
            }

            // If the current combination is valid, update the maxUtilization
            if (valid) {
                maxUtilization = Math.max(maxUtilization, totalUtilization);
            }
        }

        return maxUtilization;
    }
}

Graph

Graph Theory

1971. Find if Path Exists in Graph

SHOW PROBLEM

Problem Statement

You are given a bi-directional graph with n vertices, labeled from 0 to n - 1. The edges of the graph are represented as a 2D integer array edges, where:

  • edges[i] = [ui, vi] represents an undirected edge between vertex ui and vertex vi.
  • Each vertex pair is connected by at most one edge, and no vertex has an edge to itself.

Your task is to determine if there is a valid path from a given source vertex to a destination vertex.

Return true if such a path exists, otherwise return false.

Example 1 Find if Path Exists in Graph

  • Input:
    n = 3  
    edges = [[0,1],[1,2],[2,0]]  
    source = 0  
    destination = 2  
    
  • Output: true
  • Explanation:
    There are two valid paths from vertex 0 to vertex 2:
    • 0 → 1 → 2
    • 0 → 2

Example 2 Find if Path Exists in Graph

  • Input:
    n = 6  
    edges = [[0,1],[0,2],[3,5],[5,4],[4,3]]  
    source = 0  
    destination = 5  
    
  • Output: false
  • Explanation:
    There is no path connecting vertex 0 to vertex 5, so the output is false.

Constraints

  • 1 <= n <= 2 × 10⁵
  • 0 <= edges.length <= 2 × 10⁵
  • edges[i].length == 2
  • 0 <= ui, vi <= n - 1
  • ui != vi (No self-edges)
  • 0 <= source, destination <= n - 1
  • No duplicate edges exist.

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public boolean validPath(int n, int[][] edges, int source, int destination) {
        Map<Integer, List<Integer>> graph = new HashMap<>();
        // Initialize the graph
        for (int i = 0; i < n; i++) {
            graph.put(i, new ArrayList<>());
        }

        // Populate the graph from edges
        for (int[] edge : edges) {
            graph.get(edge[0]).add(edge[1]);
            graph.get(edge[1]).add(edge[0]); // Undirected graph
        }

        boolean[] visited = new boolean[n];
        return dfs(graph, source, destination, visited);
    }

    private static boolean dfs(Map<Integer, List<Integer>> graph, int start, int end, boolean[] visited) {
        // Found the path
        if (start == end) {
            return true;
        }
        
        // Mark the current node as visited
        visited[start] = true;

        // Iterate through all the neighbors
        for (int neighbor : graph.get(start)) {
            if (!visited[neighbor] && dfs(graph, neighbor, end, visited)) {
                return true;
            }
        }

        return false;
    }
}

SHOW NOTES

Visulization:

Find if Path exists in Graph


997. Find the Town Judge

SHOW PROBLEM

Problem Statement

In a town, there are n people labeled from 1 to n. There is a rumor that one of these people is secretly the town judge.

If the town judge exists, they must satisfy the following conditions:

  1. The town judge trusts nobody.
  2. Everybody (except the town judge) trusts the town judge.
  3. There is exactly one person who satisfies both conditions.

You are given an array trust, where trust[i] = [ai, bi] represents that person ai trusts person bi. If a trust relationship is not present in the trust array, then it does not exist.

Your task is to determine the label of the town judge.

  • If a unique town judge exists, return their label.
  • Otherwise, return -1.

Example 1

  • Input:
    n = 2  
    trust = [[1,2]]  
    
  • Output: 2
  • Explanation:
    • Person 1 trusts 2, but 2 trusts nobody.
    • Since everyone (except 2) trusts 2, and 2 trusts nobody, 2 is the judge.

Example 2

  • Input:
    n = 3  
    trust = [[1,3],[2,3]]  
    
  • Output: 3
  • Explanation:
    • Person 1 and 2 both trust 3, but 3 trusts nobody.
    • Since 3 is trusted by everyone except themselves, 3 is the judge.

Example 3

  • Input:
    n = 3  
    trust = [[1,3],[2,3],[3,1]]  
    
  • Output: -1
  • Explanation:
    • 3 is trusted by 1 and 2, but 3 also trusts 1, violating the judge rule.
    • No valid judge exists, so the output is -1.

Constraints

  • 1 <= n <= 1000
  • 0 <= trust.length <= 10⁴
  • trust[i].length == 2
  • All trust pairs are unique.
  • ai != bi (A person does not trust themselves).
  • 1 <= ai, bi <= n

Go to Leetcode 🔗
SHOW CODE
class Solution {
    public int findJudge(int n, int[][] trust) {
        if (trust.length < n - 1) {
            return -1;
        }

        // Initialize trust score array
        int[] trustScores = new int[n + 1];

        // Process the trust array to update trust scores
        for (int[] edge : trust) {
            trustScores[edge[0]]--;
            trustScores[edge[1]]++;
        }

        // Identify the town judge
        for (int i = 1; i <= n; i++) {
            if (trustScores[i] == n - 1) {
                return i;
            }
        }

        return -1;
    }
}

SHOW NOTES

Visulization:

Find the Town Judge


循环依赖

SHOW PROBLEM

题目描述

给定一组元素及其依赖关系,一个元素可以依赖多个元素(不包括自己,被依赖的元素不会重复),一个元素也可以被多个元素依赖。已知依赖关系中必定存在唯一的循环依赖,请找出并输出该循环依赖。

输入描述

  • 第一行输入一个正整数 N,表示依赖关系的个数。
  • 接下来的 N 行,每行描述一个依赖关系:
    • 第一个数 n 表示后面有 n 个元素。
    • 第二个数 a 为该元素的编号。
    • 后续 n 个数为 a 依赖的元素编号。

输出描述

  • 一串数字,表示该循环依赖。从最小编号的元素开始,按照依赖顺序输出,并以该元素结尾,形成闭环。

**说明:**编号小于或等于10000。

示例

输入:

3
3 1 2 5
3 2 3 4
2 3 1

输出:

1 2 3 1

说明:

  • 元素 1 依赖 2, 5
  • 元素 2 依赖 3, 4
  • 元素 3 依赖 1
  • 存在唯一的循环依赖 1 → 2 → 3 → 1,且起点选取最小编号的元素 1

示例 2

输入:

4
3 1 2 3
2 3 4
4 2 3 4 5
2 5 1

输出:

1 2 5 1

示例 3

输入:

4
2 1 2
2 2 3
2 3 4
2 4 1

输出:

1 2 3 4 1 

示例 4

输入:

4
2 1 2
2 2 3
2 3 4
2 4 2

输出:

2 3 4 2 

示例 5

输入:

3
2 1 5
2 5 2
2 2 1

输出:

1 5 2 1

SHOW CODE
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        // Read the number of dependencies
        int N = Integer.parseInt(in.nextLine().trim());

        // Build the graph from input
        Map<Integer, List<Integer>> graph = new HashMap<>();
        for (int i = 0; i < N; i++) {
            int n = in.nextInt();
            int start = in.nextInt();
            graph.computeIfAbsent(start, k -> new ArrayList<>());
            for (int j = 0; j < n - 1; j++) {
                int end = in.nextInt();
                graph.get(start).add(end);
            }
        }

        // Track visited nodes
        Set<Integer> visited = new HashSet<>();
        // Track the current DFS path
        Stack<Integer> stack = new Stack<>();
        // Sotre the detected cycle path
        List<Integer> result = new ArrayList<>();

        boolean hasCycle = detectCycle(graph, visited, stack, result);

        if (hasCycle && !result.isEmpty()) {
            printCyclePath(result);
        }
    }

    private static boolean detectCycle(Map<Integer, List<Integer>> graph,
                                       Set<Integer> visited,
                                       Stack<Integer> stack,
                                       List<Integer> result) {
        for (int start : graph.keySet()) {
            if (!visited.contains(start) && dfs(graph, start, visited, stack, result)) {
                return true;
            }
        }

        return false;
    }

    private static boolean dfs(Map<Integer, List<Integer>> graph,
                               int vertex,
                               Set<Integer> visited,
                               Stack<Integer> stack,
                               List<Integer> result) {
        // Check if the current vertex has been accessed
        if (visited.contains(vertex)) {
            // Check if the current vertex is in the stack
            if (stack.contains(vertex)) {
                // Found a cycle
                while (!stack.isEmpty() && stack.peek() != vertex) {
                    result.add(stack.pop());
                }
                // Add the current vertex to the stack
                result.add(stack.pop());
                return true;
            }
            return false;

        }

        // Mark the current vertex as visited
        visited.add(vertex);
        // Add the current vertex to the current path
        stack.push(vertex);

        // Iterate all the neighbors of the current vertex
        for (int neighbor : graph.getOrDefault(vertex, new ArrayList<>())) {
            if (dfs(graph, neighbor, visited, stack, result)) {
                return true;
            }
        }

        // Backtrack
        stack.pop();

        return false;
    }

    private static void printCyclePath(List<Integer> result) {
        if (!result.isEmpty()) {
            Collections.reverse(result);
            int minVertex = Collections.min(result);
            int minIndex = result.indexOf(minVertex);

            List<Integer> cyclicPath = new ArrayList<>(result.subList(minIndex, result.size()));
            cyclicPath.addAll(result.subList(0, minIndex + 1));

            for (int vertex : cyclicPath) {
                System.out.print(vertex + " ");
            }

        }
    }
}

Graph BFS

1129. Shortest Path with Alternating Colors

SHOW PROBLEM

Problem Statement

You are given an integer n, representing the number of nodes in a directed graph, where the nodes are labeled from 0 to n - 1. Each edge in this graph is either red or blue, and the graph may contain self-edges and parallel edges.

You are also given two arrays, redEdges and blueEdges, where:

  • redEdges[i] = [ai, bi] represents a red directed edge from node ai to node bi.
  • blueEdges[j] = [uj, vj] represents a blue directed edge from node uj to node vj.

Your task is to return an array answer of length n, where answer[x] is the length of the shortest path from node 0 to node x such that the edge colors alternate along the path. If no such path exists, return -1 for that node.

Example 1

  • Input:
    n = 3  
    redEdges = [[0,1],[1,2]]  
    blueEdges = []
    
  • Output:
    [0,1,-1]
    
  • Explanation:
    • Node 0 is the starting point, so answer[0] = 0.
    • Node 1 can be reached from 0 via a red edge, so answer[1] = 1.
    • Node 2 cannot be reached with alternating colors, so answer[2] = -1.

Example 2

  • Input:
    n = 3  
    redEdges = [[0,1]]  
    blueEdges = [[2,1]]
    
  • Output:
    [0,1,-1]
    
  • Explanation:
    • Node 0 is the starting point, so answer[0] = 0.
    • Node 1 can be reached from 0 via a red edge, so answer[1] = 1.
    • Node 2 cannot be reached with alternating colors, so answer[2] = -1.

Constraints

  • 1 <= n <= 100
  • 0 <= redEdges.length, blueEdges.length <= 400
  • redEdges[i].length == blueEdges[j].length == 2
  • 0 <= ai, bi, uj, vj < n

Go to Leetcode 🔗
SHOW CODE

SHOW NOTES


HW: 超级玛丽过吊桥

SHOW PROBLEM

问题描述

超级玛丽来到新的一关,需要通过一座 长度为 N 的吊桥,到达尽头的 下水管道。然而,吊桥上存在 随机缺失的木板,如果玛丽踩到缺失的木板,会死亡

  • 死亡后,如果仍有剩余的生命 (M),将在原地复活,并且不会再次受该缺失木板的影响,但会消耗一次生命。
  • 如果玛丽跨过了终点 (D) 而未能精确到达,则会跌入悬崖,通关失败。
  • 玛丽从起点 (S) 开始,可以选择以下方式移动:
    • 1
    • 2
    • 3
  • 玛丽必须恰好到达终点 (D),否则算失败。

请计算玛丽有多少种方法可以通过此关。

输入描述

  • 第一行输入 三个整数
    • M:超级玛丽的生命数 (1 <= M <= 5)
    • N:吊桥的长度 (1 <= N <= 32)
    • K:缺失的木板数量 (0 <= K <= N)
  • 第二行输入 K 个整数,表示缺失的木板编号数组 L(编号范围 1 ≤ L ≤ N,空格分隔)。

输出描述

  • 输出一个整数,表示玛丽能通过此关的 有效走法数量。如果无法通关,输出 0

输入示例 1

2 2 1  
2  

输出示例 1

4  

示例解析

  • M = 2,玛丽有 2 条生命。
  • N = 2,吊桥长度为 2。
  • K = 1,木板 2 缺失。
  • 可行的走法:
    1. 走 3 步
    2. 走 1 步,跳 2 步
    3. 跳 2 步(复活),走 1 步
    4. 走 1 步,走 1 步(复活),走 1 步

输入示例 2

1 3 2  
1 3  

输出示例 2

1  

示例解析

  • M = 1,玛丽只有 1 条生命。
  • N = 3,吊桥长度为 3。
  • K = 2,木板 13 缺失。
  • 唯一可行走法:
    • 跳 2 步,跳 2 步

输入示例 3

2 4 2  
1 3  

输出示例 3

9  

示例解析

  • M = 2,玛丽只有 1 条生命。
  • N = 4,吊桥长度为 3。
  • K = 2,木板 13 缺失。
  • 可行走法:
    • 1(复活)-> 1 -> 2 -> 1
    • 1(复活)-> 1 -> 3
    • 1(复活)-> 3 -> 1
    • 2 -> 1(复)-> 1 -> 1
    • 2 -> 1(复)-> 2
    • 2 -> 2(复)-> 1
    • 2 -> 3
    • 3(复活)-> 1 -> 1
    • 3(复活)-> 2

提示

  1. 输入保证合法,无需额外校验。
  2. 玛丽必须从起点开始,且必须恰好走到终点才能通关。
  3. 生命耗尽后若仍无法通关,则返回 0

Go to Leetcode 🔗
SHOW CODE
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        int M = in.nextInt(); // The initial lives Marie has
        int N = in.nextInt(); // The length of suspension bridge
        int K = in.nextInt(); // The number of broken boards

        Set<Integer> brokens = new HashSet<>(); // Store broken boards
        for (int i = 0; i < K; i++) {
            brokens.add(in.nextInt());
        }

        // Calculate the number of ways Marie can cross the bridge
        int result = calculateJumpWays(M, N, brokens);

        // Output the result
        System.out.println(result);
    }

    private static int calculateJumpWays(int M, int N, Set<Integer> brokens) {
        // DP table: board -> (remaining lives -> number of ways)
        Map<Integer, Map<Integer, Integer>> dp = new HashMap<>();

        // Initialize DP table for all boards
        for (int i = 0; i <= N + 1; i++) {
            dp.put(i, new HashMap<>());
        }

        // Base case: Starting point with M lives
        dp.get(0).put(M, 1);

        // Fill the DP table
        for (int i = 1; i <= N + 1; i++) {
            // Check the previous three boards
            for (int j = Math.max(0, i - 3); j < i; j++) {
                for (Map.Entry map : dp.get(j).entrySet()) {
                    int remainingLives = map.getKey();
                    int ways = map.getValue();

                    // If the current board is not broken
                    if (!brokens.contains(i)) {
//                        dp.get(i).merge(remainingLives, ways, Integer::sum);
                        dp.get(i).put(remainingLives, dp.get(i).getOrDefault(remainingLives, 0) + ways);
                    }
                    // If the current board is broken and Marie has enough lives
                    else if (remainingLives > 1) {
                        dp.get(i).put(remainingLives - 1, dp.get(i).getOrDefault(remainingLives - 1, 0) + ways);
                    }
                }
            }
        }

        int totalWays = 0;
        // Sum all possible ways to reach the destination
        for (int ways : dp.get(N + 1).values()) {
            totalWays += ways;
        }
//        int totalWays = dp.get(N + 1).values().stream().mapToInt(Integer::intValue).sum();

        return totalWays;
    }
}

SHOW NOTES

When Marie has only one life and there are no broken boards, the number of ways to reach the n-th board follows the recurrence relation: dp[n] = dp[n - 1] + dp[n - 2] + dp[n - 3] for n >= 3. This is similar to the climbing stairs problem, where Marie can jump 1, 2, or 3 boards forward, leading to the same combinatorial logic.