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.
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".
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].
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 };
}
}
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?
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;
}
}
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?
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;
}
}
}
}
You are given an integer array nums and an integer val. Your task is to remove all occurrences of val in numsin-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:
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.
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).
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;
}
}
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:
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.
The remaining elements of nums are not important and can be ignored.
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).
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;
}
}
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?
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
}
}
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:
Initialization:[2, -1, -2, 3, -5]
Sort the Array:
Input:[2, -1, -2, 3, -5]
Sorted:[-5, -2, -1, 2, 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).
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:
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.
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;
}
}
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?
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;
}
}
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.
/**
* 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:
The fast pointer is one step behind the slow pointer.
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.
/**
* 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
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 stepsif (fast !=null) {
break;
}
slow = slow.next; // Move slow pointer by one step}
/**
* 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.
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.
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
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.
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".
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
Initialize:windowStart = 0, windowEnd = 0
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
Result: The maximum length of the substring with at most 2 distinct characters is 4.
Visulization
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.
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;
}
}
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 totarget. 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.
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;
}
}
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.
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);
}
}
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.
Assuming a.start <= b.start (which can be achieved by sorting the intervals based on their starting points), there are only four possible scenatios:
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}\}]$.
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()][]);
}
}
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.
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.
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]
Initialize Merged List: mergedIntervals = []
Iterate Through Intervals:
Process the first interval [1, 3]:
Ends before [4, 10] starts.
Add to mergedIntervals: [[1, 3]]
Process the second interval [5, 7]:
Overlaps with [4, 10].
No change to the new interval: [4, 10] -> [4, 10]
No change to mergedIntervals.
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]]
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:
a.start <= b.start && b.start <= a.end
b.start <= a.start && a.start <= b.end
Interval Calculation: If a and b overlap (follows overlapping check), the intersection is calculated as:
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:
prev: Points to the previous node. Initially set to null since there is no node before the head.
current: Points to the node currently being processed.
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:
Initialization: Set prev = null and current = head to start processing from the head of list.
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);
Termination: When current becomes null, the prev pointer will point to the new head of the reversed linked list.
/**
* 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
}
}
/**
* 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
Skip the first left - 1 nodes to reach the node at position left
Save the node at position left - 1 to later reconnect it with the reversed sublist.
Save the node at position left before reversing, after reversing, the node will become the last node of the reversed sublist.
Reverse the nodes from position left to right
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.
/**
* 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.
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.
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[]{};
}
}
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;
}
}
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.
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;
}
}
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.
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;
}
}
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.
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.
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;
}
}
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:
Initialze a prefix array with the same length as the original array.
Assign prefix[0] = arr[0].
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.
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;
}
}
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.
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;
}
}
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
$$
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
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();
}
}
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);
}
}
}
You are given an encoded strings. 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 ‘]’).
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();
}
}
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.
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.
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.
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).
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).
Creating a monotonic stack commonly involves the following steps (using increasing monotonic stack as an example):
Initialize an empty stack.
Iterate over the array.
Compare the current element with the top element of the stack (if the stack is not empty).
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.
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.
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.
/**
* 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);
}
}
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.
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;
}
}
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".
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"
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();
}
}
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.
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.
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.
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:
Empty Check: Check if the tree or graph is null.
Queue Initialization: Initialize an empty queue and enqueue the root node.
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.
Repeat: Continue processing nodes until the queue is empty.
/**
* 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
}
}
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:
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].
/**
* 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;
}
}
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:
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].
/*
// 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;
}
}
/**
* 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;
}
}
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.
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();
}
}
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:
Empty Check: If the current node is null, return.
Leaf Node Check: If both the left and right children of the current node are null, then the current node is a leaf node.
Recursive Traversal: Recursively call the function on the left and right children of the current node.
Result Computation: Compute the result based on the results obtained from the left and right branches.
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]
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]
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].
/**
* 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;
}
}
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:
Visit the root node.
Traverse the left subtree.
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]
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]
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?
/**
* 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;
}
}
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:
Traverse the left subtree.
Traverse the right subtree.
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]
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]
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?
/**
* 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;
}
}
/**
* 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.
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.
/**
* 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);
}
}
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.
/**
* 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);
}
}
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).
/**
* 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;
}
}
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;
}
}
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.
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.
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;
}
}
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).
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.
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;
}
}
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].
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;
}
}
It can be written as AB (where A and B are valid strings),
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.
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;
}
}
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.
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();
}
}
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.
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;
}
}
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.
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;
}
}
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.
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 = farthest → currentEnd = 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 = farthest → currentEnd = 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 = farthest → currentEnd = 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.
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:
Traverse the matrix row by row.
Upon encountering a land cell, mark all connected land cells in the island as visited (e.g., by changing 1 to 0).
When using DFS or BFS, the first step is to check for out-of-bounds conditions.
Check if the current cell is water (0).
Explore all neighboring cells of the current cell.
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.
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.
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.
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 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.
Exploration: Start from an initial state and explore all possible options step by step.
Constant Checking: After each choice, check if it satisfies the problem’s constraints.
Pruning: If a choice leads to an invalid state, backtracking to the previous valid state and try a different option.
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.
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.
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);
}
}
}
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);
}
}
}
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:
Input: board = [["A","B","C","E"], ["S","F","C","S"], ["A","D","E","E"]], word = "ABCCED"
Output: true
Example 2:
Input: board = [["A","B","C","E"], ["S","F","C","S"], ["A","D","E","E"]], word = "SEE"
Output: true
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?
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.
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;
}
}
现在,给定一个字符串 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;
}
}
}
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.
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;
}
}
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]
Start with an empty set: [[]].
Add the first number (1) to all the existing subsets to create new subsets: [[], [1]].
Add the second number (3) to all the existing subsets: [[], [1], [3], [1,3]].
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)
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:
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]).
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.
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:
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.
Define a state that represents the solution of a subproblem.
Establish a recursive relation that expresses the solution of the problem in terms of the solutions to smaller subproblems.
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.
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:
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.
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);
}
}
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).
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;
}
}
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.
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;
}
}
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.
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:
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);
}
}
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.
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];
}
}
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.
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);
}
}
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.
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];
}
}
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 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:
Initialize two pointers: left and right.
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.)
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.
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
}
}
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
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);
}
}
}
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.
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.
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;
}
}
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.
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];
}
}
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.
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;
}
}
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.
/* 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;
}
}
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()));
}
}
}
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);
}
}
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();
}
}
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;
}
}
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);
}
}
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]);
}
}
}
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.
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();
}
}
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;
}
}
/**
* 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;
}
}
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 beforelastDayᵢ.
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.
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.
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.
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;
}
}
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.
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;
}
}
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:
The town judge trusts nobody.
Everybody (except the town judge) trusts the town judge.
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.
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;
}
}
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 + " ");
}
}
}
}
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 xsuch 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.
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.