From 4ad0693972e4ca2833006102914161f2234b7397 Mon Sep 17 00:00:00 2001 From: Mohammad Fraz Date: Sun, 1 Mar 2026 11:32:58 +0530 Subject: [PATCH 1/4] Add One to the Number- Explanation --- Arrays/Problems/Add One To Number.java | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Arrays/Problems/Add One To Number.java b/Arrays/Problems/Add One To Number.java index 71ddb1c..05d2593 100644 --- a/Arrays/Problems/Add One To Number.java +++ b/Arrays/Problems/Add One To Number.java @@ -1,28 +1,72 @@ public class Solution { + /** + * Adds one to a number represented as an ArrayList of integers. + * + * This method takes an ArrayList of integers representing the digits of a number + * and increments the number by one. It handles the carry-over logic when digits + * exceed 9, and manages the case where a new digit needs to be added (e.g., 999 + 1 = 1000). + * Leading zeros are removed from the result before returning. + * + * @param A an ArrayList of integers representing digits of a number (each element should be 0-9) + * @return an ArrayList of integers representing the digits of the incremented number, + * with leading zeros removed + * + * Example: + * Input: [1, 2, 3] (represents 123) + * Output: [1, 2, 4] (represents 124) + * + * Input: [9, 9, 9] (represents 999) + * Output: [1, 0, 0, 0] (represents 1000) + */ public ArrayList plusOne(ArrayList A) { + // Step 1: Initialize carry to 1 (we're adding 1) and start from the last digit int carry = 1; int idx = A.size() - 1; + + // Step 2: Process digits from right to left, handling the carry while (idx >= 0) { + // Step 2a: Add current digit and carry int temp = A.get(idx) + carry; + + // Step 2b: Determine if there's a carry for the next digit (if sum > 9) carry = temp > 9 ? 1 : 0; + + // Step 2c: Keep only the last digit (0-9) temp = temp > 9 ? temp % 10 : temp; + + // Step 2d: Update the current digit with the new value A.set(idx, temp); + + // Step 2e: Stop if no carry, optimization to avoid unnecessary iterations if (carry == 0) { break; } + + // Step 2f: Move to the previous digit idx--; } + + // Step 3: If there's still a carry after processing all digits, + // it means we need an extra digit at the front (e.g., 999 + 1 = 1000) if (carry > 0) { A.add(0, carry); } + + // Step 4: Remove leading zeros from the result ArrayList list = new ArrayList<>(); idx = 0; + + // Step 4a: Skip all leading zeros while (idx < A.size() && A.get(idx) == 0) { idx++; } + + // Step 4b: Add all remaining digits to the result list while (idx < A.size()) { list.add(A.get(idx++)); } + + // Step 5: Return the final result return list; } } From e098b1179834c0818f853d48cf78ea62637e1ebc Mon Sep 17 00:00:00 2001 From: Mohammad Fraz Date: Sun, 1 Mar 2026 11:40:14 +0530 Subject: [PATCH 2/4] Added Step by Step breakdown of these programs --- Bit Manipulation/Min XOR Value.java | 36 +++++++++- Bit Manipulation/Number of 1 Bits.java | 19 +++++ Bit Manipulation/Reverse Bits.java | 30 +++++++- Bit Manipulation/Single Number II.java | 27 +++++++ Bit Manipulation/Single Number.java | 15 ++++ Hashing/2Sum.java | 41 ++++++++++- Hashing/Anagrams.java | 44 ++++++++++++ Hashing/Colorful Numbers.java | 41 ++++++++++- Hashing/First Repeating Element.java | 36 ++++++++++ .../Largest Continuous Sequence Zero Sum.java | 37 +--------- Hashing/Longest Substring Without Repeat.java | 40 ++++++++++- Hashing/Solution.java | 52 ++++++++++++++ Strings/Add Binary Strings.java | 38 ++++++++++ Strings/Implement StrStr.java | 29 ++++++++ Strings/Integer To Roman.java | 53 ++++++++++++-- Strings/Length of Last Word.java | 31 +++++++- Strings/Longest Common Prefix.java | 37 ++++++++++ Strings/Palindrome String.java | 32 +++++++++ Strings/Roman To Integer.java | 37 +++++++++- Two Pointers/3 Sum Zero.java | 70 ++++++++++++++++--- Two Pointers/3 Sum.java | 45 +++++++++++- Two Pointers/Container With Most Water.java | 35 ++++++++++ .../Intersection of Sorted Arrays.java | 29 ++++++++ Two Pointers/Merge Two Sorted Lists II.java | 30 +++++++- .../Remove Duplicates From Sorted Array.java | 34 +++++++++ 25 files changed, 847 insertions(+), 71 deletions(-) create mode 100644 Hashing/Solution.java diff --git a/Bit Manipulation/Min XOR Value.java b/Bit Manipulation/Min XOR Value.java index 89d7365..62ed078 100644 --- a/Bit Manipulation/Min XOR Value.java +++ b/Bit Manipulation/Min XOR Value.java @@ -1,12 +1,44 @@ +/** + * Problem: Find the minimum XOR of any two elements in an array. + * Algorithm: Sort the array, then XOR adjacent elements. + * Adjacent elements in sorted array are more likely to have same bits, resulting in smaller XOR. + * Time Complexity: O(n log n) due to sorting + * Space Complexity: O(1) + */ public class Solution { + /** + * Finds the minimum XOR value between any two elements in the array. + * + * @param A the input ArrayList of integers + * @return the minimum XOR value + * + * Example: + * A = [5, 2, 3, 8, 12] + * After sorting: [2, 3, 5, 8, 12] + * Check: 2^3=1, 3^5=6, 5^8=13, 8^12=4 + * Output: 1 + */ public int findMinXor(ArrayList A) { + // Step 1: Sort the array + // Key insight: adjacent elements in sorted array are more likely to have same bits + // This increases chances of smaller XOR values Collections.sort(A); + + // Step 2: Initialize minimum XOR to maximum possible value int minXor = Integer.MAX_VALUE; - for (int i=0; i= min(A^B, B^C) in most cases + for (int i = 0; i < A.size() - 1; i++) { + // Step 3a: Calculate XOR of current and next element + int currentXor = A.get(i) ^ A.get(i + 1); + + // Step 3b: Update minimum if current XOR is smaller + minXor = Math.min(minXor, currentXor); } + // Step 4: Return the minimum XOR found return minXor; } } diff --git a/Bit Manipulation/Number of 1 Bits.java b/Bit Manipulation/Number of 1 Bits.java index 64303f8..5f684ef 100644 --- a/Bit Manipulation/Number of 1 Bits.java +++ b/Bit Manipulation/Number of 1 Bits.java @@ -1,6 +1,25 @@ +/** + * Problem: Count the number of 1 bits (set bits) in a long integer. + * Algorithm: Convert to binary string and count '1' characters. + * Time Complexity: O(log a) where a is the input number + * Space Complexity: O(log a) for the binary string + */ public class Solution { + /** + * Counts the number of 1 bits in the binary representation of a number. + * + * @param a the long value to count set bits in + * @return the count of 1 bits in binary representation + */ public int numSetBits(long a) { + // Step 1: Convert the long number to its binary string representation + // Example: 5 -> "101", 7 -> "111" String str = Long.toBinaryString(a); + + // Step 2: Calculate the number of '1' bits + // Method: Remove all '1' characters and subtract from total length + // Example: "101".length() = 3, "101".replace("1", "") = "0" -> length = 1 + // Result: 3 - 1 = 2 (which is correct for binary 101) return str.length() - str.replace("1", "").length(); } } diff --git a/Bit Manipulation/Reverse Bits.java b/Bit Manipulation/Reverse Bits.java index 4dfef7f..97cfb94 100644 --- a/Bit Manipulation/Reverse Bits.java +++ b/Bit Manipulation/Reverse Bits.java @@ -1,8 +1,36 @@ +/** + * Problem: Reverse the bits of a 32-bit unsigned integer. + * Algorithm: Convert to binary string, pad with leading zeros to 32 bits, reverse, and convert back. + * Time Complexity: O(1) since it's always 32 bits + * Space Complexity: O(1) constant space + */ public class Solution { + /** + * Reverses the bits of a long integer (treating it as a 32-bit number). + * + * @param a the long value whose bits need to be reversed + * @return the value after reversing its 32 bits + * + * Example: + * Input: 1 (binary: 00000000000000000000000000000001) + * Output: 2147483648 (binary: 10000000000000000000000000000000) + */ public long reverse(long a) { + // Step 1: Convert the long number to its binary string representation + // Example: 1 -> "1" StringBuilder sb = new StringBuilder(Long.toBinaryString(a)); + + // Step 2: Pad with leading zeros to make it exactly 32 bits + // Calculate missing zeros: 32 - current length + // Example: "1" needs 31 zeros -> "00000000000000000000000000000001" sb.insert(0, String.join("", Collections.nCopies(32-sb.length(), "0"))); - String s = sb.reverse().toString(); + + // Step 3: Reverse the entire 32-bit binary string + // Example: "00000000000000000000000000000001" -> "10000000000000000000000000000000" + String s = sb.reverse().toString(); + + // Step 4: Convert the reversed binary string back to a long (radix 2 for binary) + // Example: "10000000000000000000000000000000" -> 2147483648 return Long.parseLong(s, 2); } } diff --git a/Bit Manipulation/Single Number II.java b/Bit Manipulation/Single Number II.java index 0d33900..1a5fb8b 100644 --- a/Bit Manipulation/Single Number II.java +++ b/Bit Manipulation/Single Number II.java @@ -1,21 +1,48 @@ +/** + * Problem: Find the single number that appears once, where all other numbers appear 3 times. + * Algorithm: Use HashMap to count occurrences and find element with count = 1. + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * Note: Can be optimized to O(1) space using bit manipulation with bit positions, + * but this approach is more intuitive. + */ public class Solution { // DO NOT MODIFY THE LIST. IT IS READ ONLY + /** + * Finds the single number that appears once while all others appear 3 times. + * + * @param A the input list of integers + * @return the element that appears exactly once + * + * Example: + * A = [1, 1, 1, 2, 2, 2, 5] + * Output: 5 (appears once, others appear 3 times) + */ public static int singleNumber(final List A) { + // Step 1: Create HashMap to store frequency count of each element Map map = new HashMap<>(); + // Step 2: Iterate through all elements and count their occurrences for (int num : A) { + // Step 2a: Put element with count 1 if not exists, or increment by 1 map.put(num, map.getOrDefault(num, 0) + 1); } + // Step 3: Initialize result variable int ans = -1; + // Step 4: Find the element with frequency count of 1 for (Map.Entry entry : map.entrySet()) { + // Step 4a: Check if this element appears exactly once if (entry.getValue() == 1) { + // Step 4b: Store the element and break ans = entry.getKey(); break; } } + // Step 5: Return the single occurring element return ans; } } diff --git a/Bit Manipulation/Single Number.java b/Bit Manipulation/Single Number.java index 1b9b541..c9b7372 100644 --- a/Bit Manipulation/Single Number.java +++ b/Bit Manipulation/Single Number.java @@ -1,10 +1,25 @@ +/** + * Problem: Find the single number that appears once while all other numbers appear twice. + * Algorithm: XOR (Exclusive OR) - XOR of two same numbers is 0, XOR of any number with 0 is the number itself. + * Time Complexity: O(n) + * Space Complexity: O(1) + */ public class Solution { // DO NOT MODIFY THE LIST. IT IS READ ONLY public static int singleNumber(final List A) { + // Step 1: Initialize num with the first element int num = A.get(0); + + // Step 2: Iterate through all remaining elements starting from index 1 for (int i=1; i twoSum(final List A, int B) { + // Step 1: Create a HashMap to store number -> 1-based index mappings Map map = new HashMap<>(); + + // Step 2: Create result ArrayList to store the two indices ArrayList list = new ArrayList<>(); + + // Step 3: Iterate through each element in the array for (int i = 0; i < A.size(); i++) { - if (map.containsKey(B - A.get(i))) { - list.add(map.get(B - A.get(i))); - list.add(i + 1); + // Step 3a: Calculate the complement needed to reach target B + // If we need sum B and current number is A[i], we need (B - A[i]) + int complement = B - A.get(i); + + // Step 3b: Check if complement already exists in the map + if (map.containsKey(complement)) { + // Step 3c: Found the pair! Add indices and return + // (map stores 1-based indices, so no need to add 1) + list.add(map.get(complement)); + list.add(i + 1); // Current index is i, but we need 1-based (i+1) break; } + + // Step 3d: If complement not found, store current number and its 1-based index + // putIfAbsent ensures we keep the first occurrence (important for some variants) map.putIfAbsent(A.get(i), i + 1); } + + // Step 4: Return result (empty if no pair found, otherwise [idx1, idx2]) return list; } } diff --git a/Hashing/Anagrams.java b/Hashing/Anagrams.java index 2bbdef9..9734464 100644 --- a/Hashing/Anagrams.java +++ b/Hashing/Anagrams.java @@ -1,17 +1,61 @@ +/** + * Problem: Group all anagrams together from a list of strings. + * Algorithm: Use sorted characters as key in HashMap to group anagrams. + * Time Complexity: O(n * k log k) where n is number of strings, k is average length + * Space Complexity: O(n * k) for storing all strings in HashMap + */ public class Solution { // DO NOT MODIFY THE LIST. IT IS READ ONLY + /** + * Groups anagrams together from a list of strings. + * + * @param A the input list of strings + * @return ArrayList of ArrayList containing grouped anagrams with 1-based indices + * + * Example: + * A = ["listen", "silent", "hello", "world"] + * Output: [[1, 2], [3], [4]] (indices of anagrams grouped) + */ public ArrayList> anagrams(final List A) { + // Step 1: Create a HashMap where key is sorted characters and value is list of indices Map> map = new HashMap<>(); + + // Step 2: Iterate through all strings in the input list for (int i = 0; i < A.size(); i++) { + // Step 2a: Get the sorted key for current string + // Anagrams will have the same sorted characters + // Example: "listen" and "silent" both become "eilnst" String sortedKey = getSortedKey(A.get(i)); + + // Step 2b: Add 1-based index to the list of indices for this sorted key + // computeIfAbsent: if key doesn't exist, create new ArrayList + // Then add the current 1-based index (i+1) map.computeIfAbsent(sortedKey, k -> new ArrayList<>()).add(i + 1); } + + // Step 3: Return all grouped indices as ArrayList of ArrayList return new ArrayList<>(map.values()); } + /** + * Helper method to get sorted key for a string. + * This key is the same for all anagrams. + * + * @param s the input string + * @return the string with all characters sorted + * + * Example: + * "listen" -> "eilnst" + * "silent" -> "eilnst" + */ private String getSortedKey(String s) { + // Step 1: Convert string to char array char[] ch = s.toCharArray(); + + // Step 2: Sort the characters alphabetically Arrays.sort(ch); + + // Step 3: Convert back to string and return return String.valueOf(ch); } } diff --git a/Hashing/Colorful Numbers.java b/Hashing/Colorful Numbers.java index cf180da..37111bb 100644 --- a/Hashing/Colorful Numbers.java +++ b/Hashing/Colorful Numbers.java @@ -1,17 +1,56 @@ +/** + * Problem: Check if a number is colorful. + * A number is colorful if product of every contiguous subsequence of digits is unique. + * Algorithm: Generate all contiguous subsequences and check if all products are unique. + * Time Complexity: O(n²) where n is number of digits + * Space Complexity: O(n²) for storing all products in HashSet + */ public class Solution { + /** + * Determines if a number is colorful. + * A number is colorful if all products of contiguous sequences of digits are unique. + * + * @param A the integer to check + * @return 1 if colorful, 0 if not + * + * Example: + * 23245: Products are 2, 23, 232, 2324, 3, 32, 324, 2, 24, 4 + * All unique -> 1 (colorful) + * + * 1 -> 1 (single digit is colorful) + * 11 -> 0 (product of [1,1] creates duplicate 1's) + */ public int colorful(int A) { + // Step 1: Create HashSet to track all products seen so far + // If we encounter duplicate product, number is not colorful Set set = new HashSet<>(); + + // Step 2: Convert integer to string to easily access individual digits String s = String.valueOf(A); + + // Step 3: Iterate through all starting positions for contiguous sequences for (int i = 0; i < s.length(); i++) { + // Step 3a: Initialize product to 1 for each starting position int prod = 1; + + // Step 3b: Calculate products of all subsequences starting at position i for (int j = i; j < s.length(); j++) { + // Step 3c: Multiply current digit to running product + // Character.getNumericValue converts '2' to 2 prod *= Character.getNumericValue(s.charAt(j)); + + // Step 3d: Check if this product already exists + // If yes, duplicate found -> number is not colorful if (set.contains(prod)) { - return 0; + return 0; // Not colorful } + + // Step 3e: Add unique product to set set.add(prod); } } + + // Step 4: All products are unique -> number is colorful return 1; } } diff --git a/Hashing/First Repeating Element.java b/Hashing/First Repeating Element.java index b225825..c7ee021 100644 --- a/Hashing/First Repeating Element.java +++ b/Hashing/First Repeating Element.java @@ -1,17 +1,53 @@ +/** + * Problem: Find the first element that is repeated in the array. + * Return the element value, or -1 if no element repeats. + * Algorithm: Use two HashMaps - one for frequency, one for first occurrence index. + * Time Complexity: O(n) + * Space Complexity: O(n) + */ public class Solution { + /** + * Finds the first element that appears more than once in an array. + * + * @param A the input ArrayList of integers + * @return the value of the first repeating element, or -1 if no element repeats + * + * Example: + * A = [10, 5, 10, 3, 5] + * Output: 10 (first element that repeats, appeared at indices 0 and 2) + * + * A = [1, 2, 3, 4] + * Output: -1 (no repeating elements) + */ public int solve(ArrayList A) { + // Step 1: Create HashMap to count frequency of each element Map freqCounter = new HashMap<>(); + + // Step 2: Create HashMap to store first occurrence index of each element Map indexCounter = new HashMap<>(); + + // Step 3: Iterate through array to populate both maps for (int i = 0; i < A.size(); i++) { + // Step 3a: Increment frequency count for current element freqCounter.put(A.get(i), freqCounter.getOrDefault(A.get(i), 0) + 1); + + // Step 3b: Store first occurrence index (putIfAbsent ensures we keep first occurrence) indexCounter.putIfAbsent(A.get(i), i); } + + // Step 4: Find the element with frequency > 1 that has minimum first index int minRepeatingIndex = Integer.MAX_VALUE; + + // Step 4a: Iterate through all unique elements for (Integer key : freqCounter.keySet()) { + // Step 4b: Check if this element appears more than once if (freqCounter.get(key) > 1) { + // Step 4c: Update minimum index if this repeating element appeared earlier minRepeatingIndex = Math.min(minRepeatingIndex, indexCounter.get(key)); } } + + // Step 5: Return the element at minimum first occurrence, or -1 if none found return minRepeatingIndex == Integer.MAX_VALUE ? -1 : A.get(minRepeatingIndex); } } diff --git a/Hashing/Largest Continuous Sequence Zero Sum.java b/Hashing/Largest Continuous Sequence Zero Sum.java index 966235d..d95a15a 100644 --- a/Hashing/Largest Continuous Sequence Zero Sum.java +++ b/Hashing/Largest Continuous Sequence Zero Sum.java @@ -1,36 +1 @@ -public class Solution { - public ArrayList lszero(ArrayList A) { - ArrayList sumList = new ArrayList<>(); - Map map = new HashMap<>(); - ArrayList result = new ArrayList(); - - map.put(0,-1); - - int start = -1; - int end = -1; - int sum = 0; - int maxLen = -1; - - for (int i=0;i= 0 && end >= 0) { - for(int i = start; i <= end; i++) { - result.add(A.get(i)); - } - } - - return result; - } -} +/**\n * Problem: Find the longest continuous sequence in array whose sum is zero.\n * Algorithm: Use prefix sum with HashMap - when same prefix sum appears again,\n * elements between form a zero-sum sequence.\n * Time Complexity: O(n)\n * Space Complexity: O(n) for HashMap\n */\npublic class Solution {\n /**\n * Finds the longest continuous sequence with sum equal to zero.\n *\n * @param A the input ArrayList of integers\n * @return ArrayList containing the longest zero-sum sequence, empty if not found\n *\n * Example:\n * A = [15, -2, 2, -8, 1, 7, -10, 20]\n * Output: [-2, 2, -8, 1, 7, -10] (sum = 0)\n */\n public ArrayList lszero(ArrayList A) {\n // Step 1: Create HashMap to store prefix sums and their first occurrence indices\n Map map = new HashMap<>();\n\n // Step 2: Create result ArrayList to store the longest zero-sum sequence\n ArrayList result = new ArrayList();\n\n // Step 3: Initialize map with sum 0 at index -1\n // This handles case where prefix sum from start equals 0\n map.put(0, -1);\n\n // Step 4: Initialize variables to track result\n int start = -1; // Starting index of longest zero-sum sequence\n int end = -1; // Ending index of longest zero-sum sequence\n int sum = 0; // Running prefix sum\n int maxLen = -1; // Length of longest sequence found so far\n\n // Step 5: Iterate through array calculating prefix sum\n for (int i = 0; i < A.size(); i++) {\n // Step 5a: Add current element to running sum\n sum += A.get(i);\n\n // Step 5b: Check if this prefix sum was seen before\n if (map.containsKey(sum)) {\n // Step 5c: Calculate length of zero-sum sequence\n // (between previous occurrence of this sum and current position)\n int currentLen = i - map.get(sum);\n\n // Step 5d: Update result if this is longer than previous best\n if (maxLen < currentLen) {\n start = map.get(sum) + 1; // Index after previous occurrence\n end = i; // Current index\n maxLen = currentLen; // Update max length\n }\n }\n else {\n // Step 5e: First occurrence of this sum - store it\n map.put(sum, i);\n }\n }\n\n // Step 6: Extract the zero-sum sequence from array\n if (start >= 0 && end >= 0) {\n for (int i = start; i <= end; i++) {\n result.add(A.get(i));\n }\n }\n\n // Step 7: Return the result (empty if no zero-sum sequence found)\n return result;\n }\n}" diff --git a/Hashing/Longest Substring Without Repeat.java b/Hashing/Longest Substring Without Repeat.java index a0303e0..2b83560 100644 --- a/Hashing/Longest Substring Without Repeat.java +++ b/Hashing/Longest Substring Without Repeat.java @@ -1,20 +1,54 @@ +/** + * Problem: Find the length of the longest substring without repeating characters. + * Algorithm: Sliding window with HashMap - maintain window of unique characters. + * Time Complexity: O(n) where n is length of string (each character visited at most twice) + * Space Complexity: O(min(n, m)) where m is character set size + */ public class Solution { + /** + * Finds the length of the longest substring without repeating characters. + * + * @param A the input string + * @return the length of longest substring with all unique characters + * + * Example: + * "abcabcbb" -> 3 ("abc" is the longest) + * "bbbbb" -> 1 ("b" is the longest) + * "pwwkew" -> 3 ("wke" is the longest) + */ public int lengthOfLongestSubstring(String A) { - int maxLength = 0; - int start = 0; - int end = 0; + // Step 1: Initialize variables for tracking maximum length and window + int maxLength = 0; // Track maximum length found + int start = 0; // Left pointer of sliding window + int end = 0; // Right pointer of sliding window int n = A.length(); + + // Step 2: Create HashMap to store character -> frequency mapping Map map = new HashMap<>(); + + // Step 3: Use sliding window to find longest substring while (end < n) { + // Step 3a: Get current character at end pointer char c = A.charAt(end); + + // Step 3b: Add character to map and increment its frequency map.put(c, map.getOrDefault(c, 0) + 1); + + // Step 3c: If character appears more than once, shrink window from left while (map.get(c) > 1) { + // Remove leftmost character from window map.put(A.charAt(start), map.get(A.charAt(start)) - 1); start++; } + + // Step 3d: Expand window by moving end pointer end++; + + // Step 3e: Update maximum length with current window size maxLength = Math.max(maxLength, end - start); } + + // Step 4: Return the maximum length found return maxLength; } } diff --git a/Hashing/Solution.java b/Hashing/Solution.java new file mode 100644 index 0000000..a9b8a28 --- /dev/null +++ b/Hashing/Solution.java @@ -0,0 +1,52 @@ +/** + * Problem: Find two numbers in an array that sum to a target value B. + * Return the 1-based indices of the two numbers. + * Algorithm: HashMap/Hash Table - Store indices as we iterate and check for complement. + * Time Complexity: O(n) + * Space Complexity: O(n) for the HashMap + */ +public class Solution { + // DO NOT MODIFY THE LIST. IT IS READ ONLY + /** + * Finds two numbers in array A that sum to B. + * + * @param A the input list of integers (read-only) + * @param B the target sum + * @return ArrayList with 1-based indices [i, j] where A[i-1] + A[j-1] = B, or empty if not found + * + * Example: + * A = [1, 3, 5, 7], B = 8 + * Output: [2, 4] because A[1] + A[3] = 3 + 5 = 8 (1-based indices) + */ + public ArrayList twoSum(final List A, int B) { + // Step 1: Create a HashMap to store number -> 1-based index mappings + Map map = new HashMap<>(); + + // Step 2: Create result ArrayList to store the two indices + ArrayList list = new ArrayList<>(); + + // Step 3: Iterate through each element in the array + for (int i = 0; i < A.size(); i++) { + // Step 3a: Calculate the complement needed to reach target B + // If we need sum B and current number is A[i], we need (B - A[i]) + int complement = B - A.get(i); + + // Step 3b: Check if complement already exists in the map + if (map.containsKey(complement)) { + // Step 3c: Found the pair! Add indices and return + // (map stores 1-based indices, so no need to add 1) + list.add(map.get(complement)); + list.add(i + 1); // Current index is i, but we need 1-based (i+1) + break; + } + + // Step 3d: If complement not found, store current number and its 1-based index + // putIfAbsent ensures we keep the first occurrence (important for some variants) + map.putIfAbsent(A.get(i), i + 1); + } + + // Step 4: Return result (empty if no pair found, otherwise [idx1, idx2]) + return list; + } +} + diff --git a/Strings/Add Binary Strings.java b/Strings/Add Binary Strings.java index bfd0d6c..f759317 100644 --- a/Strings/Add Binary Strings.java +++ b/Strings/Add Binary Strings.java @@ -1,21 +1,59 @@ +/** + * Problem: Add two binary numbers represented as strings. + * Algorithm: Process from right to left, handling carry at each position. + * Time Complexity: O(max(len(A), len(B))) + * Space Complexity: O(max(len(A), len(B))) for result + */ public class Solution { + /** + * Adds two binary numbers represented as strings. + * + * @param A first binary string + * @param B second binary string + * @return the binary sum as a string + * + * Example: + * A = "11", B = "1" -> "100" (3 + 1 = 4) + * A = "10", B = "10" -> "100" (2 + 2 = 4) + */ public String addBinary(String A, String B) { + // Step 1: Initialize pointers to the rightmost characters of both strings int idx1 = A.length() - 1; int idx2 = B.length() - 1; + + // Step 2: Initialize carry to 0 int carry = 0; + + // Step 3: Create StringBuilder to build result (will be reversed) StringBuilder sb = new StringBuilder(); + + // Step 4: Process both strings from right to left while (idx1 >= 0 || idx2 >= 0 || carry > 0) { + // Step 4a: Initialize value with carry int val = carry; + + // Step 4b: Add digit from A if available if (idx1 >= 0) { val += Character.getNumericValue(A.charAt(idx1--)); } + + // Step 4c: Add digit from B if available if (idx2 >= 0) { val += Character.getNumericValue(B.charAt(idx2--)); } + + // Step 4d: Calculate new carry (val > 1 means carry = 1) carry = val > 1 ? 1 : 0; + + // Step 4e: Calculate digit for this position + // In binary: val=0->0, val=1->1, val=2->0 (with carry), val=3->1 (with carry) val = val > 1 ? (val == 2 ? 0 : 1) : val; + + // Step 4f: Append to StringBuilder (reversed order for now) sb.append(val); } + + // Step 5: Reverse the result and return (because we built it backwards) return sb.reverse().toString(); } } diff --git a/Strings/Implement StrStr.java b/Strings/Implement StrStr.java index 692741c..330ce2b 100644 --- a/Strings/Implement StrStr.java +++ b/Strings/Implement StrStr.java @@ -1,16 +1,45 @@ +/** + * Problem: Find the first occurrence of pattern B in string A (similar to indexOf). + * Algorithm: Simple substring comparison - check each position in A for match with B. + * Time Complexity: O(n * m) where n = len(A), m = len(B) + * Space Complexity: O(1) + */ public class Solution { // DO NOT MODIFY THE LIST. IT IS READ ONLY + /** + * Finds the first occurrence of pattern B in string A. + * + * @param A the main string to search in + * @param B the pattern string to find + * @return 0-based index of first occurrence, -1 if not found + * + * Example: + * A = "hello world", B = "world" -> 6 + * A = "hello", B = "bye" -> -1 + */ public int strStr(final String A, final String B) { + // Step 1: Get lengths of both strings int lenA = A.length(); int lenB = B.length(); + + // Step 2: If pattern is longer than text, it can't be found if (lenB > lenA) { return -1; } + + // Step 3: Check each position in A for a match with B + // We only need to check up to lenA - lenB positions + // (after that, there aren't enough characters left to match B) for (int i = 0; i <= lenA - lenB; i++) { + // Step 3a: Extract substring of length lenB starting at position i + // Step 3b: Compare with pattern B if (A.substring(i, i + lenB).equals(B)) { + // Step 3c: Pattern found, return starting index return i; } } + + // Step 4: Pattern not found anywhere in A return -1; } } diff --git a/Strings/Integer To Roman.java b/Strings/Integer To Roman.java index a26b618..305ad37 100644 --- a/Strings/Integer To Roman.java +++ b/Strings/Integer To Roman.java @@ -1,17 +1,60 @@ +/** + * Problem: Convert an integer to its Roman numeral representation. + * Algorithm: Use arrays for each place value (thousands, hundreds, tens, ones). + * Each array contains Roman numerals for values 0-9 in that position. + * Time Complexity: O(1) since we process at most 4 digits + * Space Complexity: O(1) + */ public class Solution { + /** + * Converts an integer to a Roman numeral string. + * + * @param num the integer to convert (1 to 3999) + * @return the Roman numeral representation + * + * Example: + * 3 -> "III" + * 58 -> "LVIII" + * 1994 -> "MCMXCIV" + */ public String intToRoman(int num) { + // Step 1: Define arrays for each place value (ones, tens, hundreds, thousands) + // Each index represents the digit in that position + // Index 1 = digit 1, index 2 = digit 2, etc. + + // Thousands place: 0, M, MM, MMM (for 0-3 thousands) String[] m = {"", "M", "MM", "MMM"}; + + // Hundreds place: special notation for 400, 500, 900 + // 0, C, CC, CCC, CD, D, DC, DCC, DCCC, CM (for 0-9 hundreds) String[] c = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"}; + + // Tens place: special notation for 40, 50, 90 + // 0, X, XX, XXX, XL, L, LX, LXX, LXXX, XC (for 0-9 tens) String[] x = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"}; + + // Ones place: special notation for 4, 5, 9 + // 0, I, II, III, IV, V, VI, VII, VIII, IX (for 0-9 ones) String[] i = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}; - String thousands = m[num/1000]; - String hundereds = c[num%1000/100]; - String tens = x[num%100/10]; - String ones = i[num%10]; + // Step 2: Extract each digit and map to Roman numerals + + // Thousands digit (divide by 1000, take quotient) + String thousands = m[num / 1000]; + + // Hundreds digit (divide by 1000, take remainder, then divide by 100) + String hundreds = c[num % 1000 / 100]; + + // Tens digit (divide by 100, take remainder, then divide by 10) + String tens = x[num % 100 / 10]; + + // Ones digit (divide by 10, take remainder) + String ones = i[num % 10]; - String ans = thousands + hundereds + tens + ones; + // Step 3: Concatenate all parts to form complete Roman numeral + String ans = thousands + hundreds + tens + ones; + // Step 4: Return the Roman numeral string return ans; } } diff --git a/Strings/Length of Last Word.java b/Strings/Length of Last Word.java index 29e59b6..7844d05 100644 --- a/Strings/Length of Last Word.java +++ b/Strings/Length of Last Word.java @@ -1,15 +1,42 @@ +/** + * Problem: Find the length of the last word in a string (words separated by spaces). + * Algorithm: Start from end, skip trailing spaces, then count until next space or start. + * Time Complexity: O(n) where n is length of string + * Space Complexity: O(1) + */ public class Solution { // DO NOT MODIFY THE LIST. IT IS READ ONLY + /** + * Returns the length of the last word in a string. + * A word is a sequence of non-space characters. + * + * @param A the input string + * @return the length of the last word + * + * Example: + * "hello world" -> 5 + * "hello world " -> 5 (trailing spaces ignored) + * "world" -> 5 + */ public int lengthOfLastWord(final String A) { + // Step 1: Initialize index to last character position int idx = A.length() - 1; + + // Step 2: Skip trailing spaces from the end + // Move backwards while character is a space while (idx >= 0 && A.charAt(idx) == ' ') { idx--; } + + // Step 3: Count characters in the last word int count = 0; + // Continue backwards while character is not a space and haven't reached start while (idx >= 0 && A.charAt(idx) != ' ') { - idx--; - count++; + idx--; // Move to previous character + count++; // Increment character count } + + // Step 4: Return the count of characters in last word return count; } } diff --git a/Strings/Longest Common Prefix.java b/Strings/Longest Common Prefix.java index f0d06f2..0e06395 100644 --- a/Strings/Longest Common Prefix.java +++ b/Strings/Longest Common Prefix.java @@ -1,8 +1,31 @@ +/** + * Problem: Find the longest common prefix among all strings in the list. + * Algorithm: Find shortest string (limiting factor), then compare all strings character by character. + * Time Complexity: O(n * m) where n is number of strings, m is length of shortest string + * Space Complexity: O(m) for StringBuilder storing prefix + */ public class Solution { + /** + * Finds the longest common prefix of all strings in the list. + * + * @param A the ArrayList of strings + * @return the longest common prefix, empty string if no common prefix + * + * Example: + * A = ["flower", "flow", "flight"] + * Output: "fl" + * + * A = ["dog", "racecar", "car"] + * Output: "" (no common prefix) + */ public String longestCommonPrefix(ArrayList A) { + // Step 1: Handle empty list if (A.size() == 0) { return ""; } + + // Step 2: Find the shortest string in the list + // The common prefix can't be longer than the shortest string int minLength = Integer.MAX_VALUE; int minLengthIdx = 0; for (int i = 0; i < A.size(); i++) { @@ -11,21 +34,35 @@ public String longestCommonPrefix(ArrayList A) { minLengthIdx = i; } } + + // Step 3: Use the shortest string as the reference String targetStr = A.get(minLengthIdx); + + // Step 4: Build StringBuilder to store the common prefix StringBuilder sb = new StringBuilder(); + + // Step 5: Compare character at each position across all strings for (int i = 0; i < targetStr.length(); i++) { boolean mismatch = false; + + // Step 5a: Check if character at position i is same in all strings for (String str : A) { if (str.charAt(i) != targetStr.charAt(i)) { mismatch = true; break; } } + + // Step 5b: If any mismatch found, stop (no point checking further) if (mismatch) { break; } + + // Step 5c: Character matches in all strings, add to prefix sb.append(targetStr.charAt(i)); } + + // Step 6: Return the common prefix return sb.length() == 0 ? "" : sb.toString(); } } diff --git a/Strings/Palindrome String.java b/Strings/Palindrome String.java index 5c32f10..ee5a358 100644 --- a/Strings/Palindrome String.java +++ b/Strings/Palindrome String.java @@ -1,26 +1,58 @@ +/** + * Problem: Check if a string is a palindrome considering only alphanumeric characters, + * ignoring case and spaces/special characters. + * Algorithm: Two-pointer approach from both ends comparing characters. + * Time Complexity: O(n) + * Space Complexity: O(1) + */ public class Solution { + /** + * Determines if a string is a palindrome ignoring non-alphanumeric characters and case. + * + * @param A the input string to check + * @return 1 if palindrome, 0 if not + * + * Example: + * "A man, a plan, a canal: Panama" -> 1 (palindrome) + * "hello" -> 0 (not palindrome) + */ public int isPalindrome(String A) { + // Step 1: Initialize two pointers at the start and end of the string int start = 0; int end = A.length() - 1; + + // Step 2: Compare characters from both ends moving towards center while (start <= end) { + // Step 2a: Check if both characters are alphanumeric (letters or digits) if (Character.isLetterOrDigit(A.charAt(start)) && Character.isLetterOrDigit(A.charAt(end))) { + // Step 2b: Convert both characters to lowercase for comparison + // (keeps digits as-is since case doesn't apply to them) char c1 = Character.isLetter(A.charAt(start)) ? Character.toLowerCase(A.charAt(start)) : A.charAt(start); char c2 = Character.isLetter(A.charAt(end)) ? Character.toLowerCase(A.charAt(end)) : A.charAt(end); + + // Step 2c: If characters don't match, not a palindrome if (c1 != c2) { return 0; } + + // Step 2d: Move both pointers inward start++; end--; } else { + // Step 2e: Skip non-alphanumeric characters from the left if (!Character.isLetterOrDigit(A.charAt(start))) { start++; } + + // Step 2f: Skip non-alphanumeric characters from the right if (!Character.isLetterOrDigit(A.charAt(end))) { end--; } } } + + // Step 3: If all valid characters matched, it's a palindrome return 1; } } diff --git a/Strings/Roman To Integer.java b/Strings/Roman To Integer.java index fe832b2..b38d3cd 100644 --- a/Strings/Roman To Integer.java +++ b/Strings/Roman To Integer.java @@ -1,24 +1,57 @@ +/** + * Problem: Convert a Roman numeral string to its integer equivalent. + * Roman numerals: I=1, V=5, X=10, L=50, C=100, D=500, M=1000 + * Special cases: IV=4, IX=9, XL=40, XC=90, CD=400, CM=900 + * Algorithm: Use HashMap to map Roman numerals to values, process from left to right. + * Time Complexity: O(n) where n is length of Roman string + * Space Complexity: O(1) constant space for the map + */ public class Solution { + /** + * Converts a Roman numeral string to an integer. + * + * @param A the Roman numeral string + * @return the integer representation + * + * Example: + * "III" -> 3 + * "IV" -> 4 + * "MCMXC" -> 1990 + */ public int romanToInt(String A) { + // Step 1: Define arrays for Roman numeral symbols and their values + // Include special two-character Roman numerals (IV, IX, etc.) String[] keys = {"I", "IV", "V", "IX", "X", "XL", "L", "XC", "C", "CD", "D", "CM", "M"}; int[] values = {1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000}; + + // Step 2: Create HashMap to map Roman numeral to integer value Map map = new HashMap<>(); for (int i = 0; i < keys.length; i++) { map.put(keys[i], values[i]); } + + // Step 3: Initialize variables int idx = 0; int n = A.length(); int sum = 0; + + // Step 4: Process Roman numeral string from left to right while (idx < n) { + // Step 4a: Check if next two characters form a special Roman numeral + // (e.g., IV, IX, XL, XC, CD, CM) if (idx + 1 < n && map.containsKey(A.substring(idx, idx + 2))) { + // Add two-character value and skip 2 positions sum += map.get(A.substring(idx, idx + 2)); - idx++; + idx++; // Will be incremented again at the end of loop } else { + // Single character Roman numeral sum += map.get(String.valueOf(A.charAt(idx))); } - idx++; + idx++; // Move to next position } + + // Step 5: Return the total sum return sum; } } diff --git a/Two Pointers/3 Sum Zero.java b/Two Pointers/3 Sum Zero.java index c5d0ab8..e83f536 100644 --- a/Two Pointers/3 Sum Zero.java +++ b/Two Pointers/3 Sum Zero.java @@ -1,35 +1,83 @@ +/** + * Problem: Find all unique triplets in array that sum to zero. + * Algorithm: Sort array, then use two-pointer technique for each fixed element. + * Time Complexity: O(n²) - outer loop O(n), inner two-pointer O(n) + * Space Complexity: O(1) if we don't count output array space + */ public class Solution { + /** + * Finds all unique triplets that sum to zero. + * + * @param A the input ArrayList of integers + * @return ArrayList of all unique triplets summing to zero + * + * Example: + * A = [-1, 0, 1, 2, -1, -4] + * Output: [[-1, -1, 2], [-1, 0, 1]] + */ public ArrayList> threeSum(ArrayList A) { + // Step 1: Sort the array to enable two-pointer technique Collections.sort(A); + + // Step 2: Create result ArrayList to store all triplets ArrayList> ans = new ArrayList<>(); - for (int i=0;i 0 && A.get(i).equals(A.get(i-1))) continue; + // Step 3: Fix each element as the first element of triplet + for (int i = 0; i < A.size() - 2; i++) { + // Step 3a: Skip duplicate values to avoid duplicate triplets + if (i > 0 && A.get(i).equals(A.get(i - 1))) { + continue; + } + // Step 3b: Calculate target sum for remaining two elements + // We need: A[i] + A[j] + A[k] = 0 + // Therefore: A[j] + A[k] = -A[i] int target = -A.get(i); - int j = i+1; - int k = A.size()-1; - while (j temp = new ArrayList<>(); - temp.add(A.get(i)); - temp.add(A.get(j)); + temp.add(A.get(i)); + temp.add(A.get(j)); temp.add(A.get(k)); ans.add(temp); + + // Move both pointers j++; k--; - while(j target) { + k--; } - else if (A.get(j) + A.get(k) > target) k--; + // If sum is too small, increase it by moving j right else { j++; } } } + // Step 4: Return all found triplets return ans; } } diff --git a/Two Pointers/3 Sum.java b/Two Pointers/3 Sum.java index 9f1d8f7..2edd049 100644 --- a/Two Pointers/3 Sum.java +++ b/Two Pointers/3 Sum.java @@ -1,21 +1,58 @@ +/** + * Problem: Find three elements in a sorted array that give the sum closest to target B. + * Algorithm: Sort array, use three pointers (one fixed, two moving) to find closest sum. + * Time Complexity: O(n²) - outer loop O(n), inner two-pointer O(n) + * Space Complexity: O(1) if we don't count sorting space + */ public class Solution { + /** + * Finds three numbers that produce the sum closest to target B. + * + * @param A the input ArrayList of integers + * @param B the target sum + * @return the sum of three numbers closest to B + * + * Example: + * A = [1, 4, 45, 6, 10, 8], B = 13 + * Possible triplets: 1+4+6=11, 1+4+8=13, etc. + * Output: 13 (exact match) + */ public int threeSumClosest(ArrayList A, int B) { + // Step 1: Sort the array to enable two-pointer technique Collections.sort(A); - int closest = Integer.MAX_VALUE; - int res = 0; + + // Step 2: Initialize variables to track closest difference and result sum + int closest = Integer.MAX_VALUE; // Track minimum difference from target + int res = 0; // Track the actual sum closest to target + + // Step 3: Iterate through array with first pointer for (int i = 0; i < A.size() - 2; i++) { + // Step 3a: Initialize two pointers - one right after i, one at end int start = i + 1; int end = A.size() - 1; + + // Step 3b: Use two-pointer technique to find closest sum with A[i] while (start < end) { + // Step 3c: Calculate current sum of three elements int currSum = A.get(i) + A.get(start) + A.get(end); + + // Step 3d: Calculate difference from target int diff = Math.abs(currSum - B); + + // Step 3e: If we found exact match, return immediately if (diff == 0) { return B; } + + // Step 3f: Update closest pair if current difference is smaller if (diff < closest) { closest = diff; res = currSum; } + + // Step 3g: Move pointers to adjust sum + // If current sum is too small, increase it by moving start forward + // If current sum is too large, decrease it by moving end backward if (currSum <= B) { start++; } @@ -24,7 +61,9 @@ public int threeSumClosest(ArrayList A, int B) { } } } - return closest; + + // Step 4: Return the sum closest to target + return res; } } diff --git a/Two Pointers/Container With Most Water.java b/Two Pointers/Container With Most Water.java index 3b57e83..d63ee89 100644 --- a/Two Pointers/Container With Most Water.java +++ b/Two Pointers/Container With Most Water.java @@ -1,12 +1,45 @@ +/** + * Problem: Find two lines from an array that can hold the maximum water. + * Water container area = min(height[i], height[j]) * (j - i) + * Algorithm: Two-pointer approach - start from ends and move inward. + * Time Complexity: O(n) + * Space Complexity: O(1) + */ public class Solution { + /** + * Finds the maximum area of water that can be contained between two lines. + * + * @param A ArrayList of integers representing heights of lines + * @return the maximum area that can be contained + * + * Example: + * A = [1, 8, 6, 2, 5, 4, 8, 3, 7] + * Output: 49 (between heights 8 and 7, distance 8, area = min(8,7) * 8 = 56... recalculate) + * Correct: max area is min(8,7) * 7 = 49 or min(6,8) * 8 = 48 + */ public int maxArea(ArrayList A) { + // Step 1: Initialize variable to track maximum area found so far int maximumArea = 0; + + // Step 2: Initialize two pointers - one at start, one at end int start = 0; int end = A.size() - 1; + + // Step 3: Move pointers inward comparing areas while (start < end) { + // Step 3a: Calculate height as the minimum of two lines + // (water level is limited by shorter line) int minHeight = Math.min(A.get(start), A.get(end)); + + // Step 3b: Calculate area: height * width (distance between pointers) int currArea = minHeight * (end - start); + + // Step 3c: Update maximum area if current area is larger maximumArea = Math.max(maximumArea, currArea); + + // Step 3d: Move the pointer pointing to shorter line + // Logic: moving the shorter line might give us a better area + // (we're increasing distance slightly, but have potential for taller line) if (A.get(start) <= A.get(end)) { start++; } @@ -14,6 +47,8 @@ public int maxArea(ArrayList A) { end--; } } + + // Step 4: Return the maximum area found return maximumArea; } } diff --git a/Two Pointers/Intersection of Sorted Arrays.java b/Two Pointers/Intersection of Sorted Arrays.java index 918bfce..95effc5 100644 --- a/Two Pointers/Intersection of Sorted Arrays.java +++ b/Two Pointers/Intersection of Sorted Arrays.java @@ -1,22 +1,51 @@ +/** + * Problem: Find intersection (common elements) of two sorted arrays. + * Algorithm: Two-pointer technique - move pointers to find matching elements. + * Time Complexity: O(n + m) where n and m are sizes of the two arrays + * Space Complexity: O(1) if we don't count output array space + */ public class Solution { // DO NOT MODIFY THE LIST. IT IS READ ONLY + /** + * Finds common elements between two sorted arrays. + * + * @param A first sorted array + * @param B second sorted array + * @return ArrayList containing all common elements in same order + * + * Example: + * A = [1, 2, 2, 3], B = [2, 3, 3, 4] + * Output: [2, 3] + */ public ArrayList intersect(final List A, final List B) { + // Step 1: Initialize two pointers at the start of both arrays int startA = 0; int startB = 0; + + // Step 2: Create result ArrayList to store intersection elements ArrayList list = new ArrayList<>(); + + // Step 3: Traverse both arrays simultaneously using two pointers while (startA < A.size() && startB < B.size()) { + // Step 3a: Compare elements at current pointers if (A.get(startA).equals(B.get(startB))) { + // Step 3b: Elements match - add to result and advance both pointers list.add(A.get(startA)); startA++; startB++; } + // Step 3c: Element in A is smaller - advance A's pointer + // (to find potentially matching element) else if (A.get(startA) < B.get(startB)) { startA++; } + // Step 3d: Element in B is smaller - advance B's pointer else { startB++; } } + + // Step 4: Return the intersection result return list; } } diff --git a/Two Pointers/Merge Two Sorted Lists II.java b/Two Pointers/Merge Two Sorted Lists II.java index b54759f..f3973f6 100644 --- a/Two Pointers/Merge Two Sorted Lists II.java +++ b/Two Pointers/Merge Two Sorted Lists II.java @@ -1,14 +1,40 @@ +/** + * Problem: Merge second sorted array into first array (in-place). + * Algorithm: Two-pointer approach - insert elements from B into A at correct positions. + * Time Complexity: O(n * m) in worst case due to array insertions + * Space Complexity: O(1) in-place modification + */ public class Solution { + /** + * Merges the second sorted array into the first array in-place. + * + * @param a the first sorted array (modified in-place) + * @param b the second sorted array + * + * Example: + * a = [1, 5, 9], b = [2, 3, 8] + * After merge: a = [1, 2, 3, 5, 8, 9] + */ public void merge(ArrayList a, ArrayList b) { - int idxA = 0; - int idxB = 0; + // Step 1: Initialize pointers for both arrays + int idxA = 0; // Pointer for array a + int idxB = 0; // Pointer for array b + + // Step 2: Compare elements and insert smaller element from b into a while (idxA < a.size() && idxB < b.size()) { + // Step 2a: Check if current element in b is smaller than current element in a if (a.get(idxA) > b.get(idxB)) { + // Step 2b: Insert element from b at current position in a + // This shifts all elements at idxA and beyond to the right a.add(idxA, b.get(idxB)); idxB++; } + // Step 2c: Move pointer in a to next position idxA++; } + + // Step 3: Add remaining elements from b to the end of a + // (if b has elements left, they are all greater than remaining elements in a) while (idxB < b.size()) { a.add(b.get(idxB++)); } diff --git a/Two Pointers/Remove Duplicates From Sorted Array.java b/Two Pointers/Remove Duplicates From Sorted Array.java index e153382..3742ce4 100644 --- a/Two Pointers/Remove Duplicates From Sorted Array.java +++ b/Two Pointers/Remove Duplicates From Sorted Array.java @@ -1,15 +1,49 @@ +/** + * Problem: Remove duplicates from a sorted array in-place. + * Keep only one instance of each unique element. + * Algorithm: Two-pointer technique - one for position, one for scanning. + * Time Complexity: O(n) single pass + * Space Complexity: O(1) in-place modification + */ public class Solution { + /** + * Removes duplicates from sorted array in-place. + * Returns the count of unique elements. Array is modified such that + * first k elements contain unique values in sorted order. + * + * @param a the sorted ArrayList of integers + * @return the count of unique elements + * + * Example: + * a = [1, 1, 2, 2, 3] + * After: [1, 2, 3, _, _] (first 3 elements are unique) + * Output: 3 + */ public int removeDuplicates(ArrayList a) { + // Step 1: Initialize two pointers + // start: position to place next unique element + // end: pointer to scan the array int start = 0; int end = 0; int n = a.size(); + + // Step 2: Iterate through entire array while (end < n) { + // Step 2a: Store current unique number int num = a.get(end); + + // Step 2b: Skip all duplicate occurrences of this number + // Move end pointer past all consecutive occurrences of 'num' while (end < n && a.get(end) == num) { end++; } + + // Step 2c: Place the unique number at start position a.set(start++, num); } + + // Step 3: Return count of unique elements + // start represents the index after last unique element return start; } } From 785af917ed6d0897454ee4cb2f57e6ea3f6728b6 Mon Sep 17 00:00:00 2001 From: Mohammad Fraz Date: Sun, 1 Mar 2026 11:43:07 +0530 Subject: [PATCH 3/4] Added Step by Step breakdown of these programs - one more --- Hashing/Diffk II.java | 41 ++++++++++++++++++++++++++++++--- Two Pointers/Diffk.java | 33 +++++++++++++++++++++++--- Two Pointers/Solution.java | 47 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 Two Pointers/Solution.java diff --git a/Hashing/Diffk II.java b/Hashing/Diffk II.java index a343b0a..33684e3 100644 --- a/Hashing/Diffk II.java +++ b/Hashing/Diffk II.java @@ -1,15 +1,50 @@ +/** + * Problem: Check if there exist two elements in array with difference K. + * Algorithm: Use HashSet - for each element, check if (num + K) or (num - K) exists. + * Time Complexity: O(n) + * Space Complexity: O(n) + */ public class Solution { // DO NOT MODIFY THE LIST. IT IS READ ONLY + /** + * Determines if there exist two distinct elements with difference K. + * + * @param A the input list of integers + * @param B the difference K to find + * @return 1 if pair exists, 0 if not + * + * Example: + * A = [1, 3, 5, 7], B = 2 + * Output: 1 (1 and 3 have difference 2) + */ public int diffPossible(final List A, int B) { - if (A.size() <= 1) return 0; + // Step 1: Handle edge case - need at least 2 elements + if (A.size() <= 1) { + return 0; + } + + // Step 2: Create HashSet to store elements we've seen Set set = new HashSet<>(); + + // Step 3: Iterate through array for (int num : A) { - if (set.contains(num + B) || set.contains(num - B)) { - return 1; + // Step 3a: Check if (num + B) exists in set + // This means we have two elements with difference B + if (set.contains(num + B)) { + return 1; // Pair found + } + + // Step 3b: Check if (num - B) exists in set + // This also means we have two elements with difference B + if (set.contains(num - B)) { + return 1; // Pair found } + + // Step 3c: Add current element to set for future comparisons set.add(num); } + // Step 4: No pair with difference B found return 0; } } diff --git a/Two Pointers/Diffk.java b/Two Pointers/Diffk.java index faa5c4a..1a9d2ba 100644 --- a/Two Pointers/Diffk.java +++ b/Two Pointers/Diffk.java @@ -1,19 +1,46 @@ +/** + * Problem: Check if array contains two distinct elements with difference B (two-pointer sorted array). + * Algorithm: Two-pointer technique on the original array (presumably sorted). + * Time Complexity: O(n) assuming array is sorted + * Space Complexity: O(1) + */ public class Solution { + /** + * Checks if there exist two distinct elements with difference B. + * + * @param A the input ArrayList of integers (should be sorted) + * @param B the target difference + * @return 1 if pair found, 0 if not + * + * Example: + * A = [1, 3, 5, 6], B = 2 + * Output: 1 (e.g., 3 and 5, or 1 and 3) + */ public int diffPossible(ArrayList A, int B) { - int idx1 = 0; - int idx2 = 1; + // Step 1: Initialize two pointers + int idx1 = 0; // Left pointer + int idx2 = 1; // Right pointer int n = A.size(); + + // Step 2: Use two-pointer technique to find pair with difference B while (idx1 < n && idx2 < n) { + // Step 2a: Calculate current difference + // Step 2b: Check if indices are different and difference equals target B if (idx1 != idx2 && A.get(idx2) - A.get(idx1) == B) { - return 1; + return 1; // Found the pair } + + // Step 2c: If difference is too small, increase it by moving right pointer forward if (A.get(idx2) - A.get(idx1) < B) { idx2++; } + // Step 2d: If difference is too large, decrease it by moving left pointer forward else { idx1++; } } + + // Step 3: No pair with difference B found return 0; } } diff --git a/Two Pointers/Solution.java b/Two Pointers/Solution.java new file mode 100644 index 0000000..1a9d2ba --- /dev/null +++ b/Two Pointers/Solution.java @@ -0,0 +1,47 @@ +/** + * Problem: Check if array contains two distinct elements with difference B (two-pointer sorted array). + * Algorithm: Two-pointer technique on the original array (presumably sorted). + * Time Complexity: O(n) assuming array is sorted + * Space Complexity: O(1) + */ +public class Solution { + /** + * Checks if there exist two distinct elements with difference B. + * + * @param A the input ArrayList of integers (should be sorted) + * @param B the target difference + * @return 1 if pair found, 0 if not + * + * Example: + * A = [1, 3, 5, 6], B = 2 + * Output: 1 (e.g., 3 and 5, or 1 and 3) + */ + public int diffPossible(ArrayList A, int B) { + // Step 1: Initialize two pointers + int idx1 = 0; // Left pointer + int idx2 = 1; // Right pointer + int n = A.size(); + + // Step 2: Use two-pointer technique to find pair with difference B + while (idx1 < n && idx2 < n) { + // Step 2a: Calculate current difference + // Step 2b: Check if indices are different and difference equals target B + if (idx1 != idx2 && A.get(idx2) - A.get(idx1) == B) { + return 1; // Found the pair + } + + // Step 2c: If difference is too small, increase it by moving right pointer forward + if (A.get(idx2) - A.get(idx1) < B) { + idx2++; + } + // Step 2d: If difference is too large, decrease it by moving left pointer forward + else { + idx1++; + } + } + + // Step 3: No pair with difference B found + return 0; + } +} + From a6ee65993901895ae4200f9a79d372130fbc5551 Mon Sep 17 00:00:00 2001 From: Mohammad Fraz Date: Sun, 1 Mar 2026 11:51:54 +0530 Subject: [PATCH 4/4] Added Step by Step breakdown of these programs - for backtracking --- Backtracking/Examples/Modular Expression.java | 57 +++++++++++++---- .../Reverse Link List Recursion_new.java | 55 ++++++++++++++++ Backtracking/Problems/Combination Sum II.java | 46 ++++++++++++++ Backtracking/Problems/Combination Sum.java | 46 +++++++++++++- Backtracking/Problems/Combinations.java | 41 ++++++++++++ .../Problems/Generate all Parentheses II.java | 50 +++++++++++++-- Backtracking/Problems/Letter Phone.java | 52 +++++++++++++--- .../Problems/Palindrome Partitioning.java | 58 +++++++++++++++-- Backtracking/Problems/Permutations.java | 39 +++++++++++- Backtracking/Problems/Solution.java | 62 +++++++++++++++++++ Backtracking/Problems/Subset.java | 37 +++++++++++ Backtracking/Problems/Subsets II.java | 42 +++++++++++++ 12 files changed, 555 insertions(+), 30 deletions(-) create mode 100644 Backtracking/Examples/Reverse Link List Recursion_new.java create mode 100644 Backtracking/Problems/Solution.java diff --git a/Backtracking/Examples/Modular Expression.java b/Backtracking/Examples/Modular Expression.java index 9c7d5de..5e3696c 100644 --- a/Backtracking/Examples/Modular Expression.java +++ b/Backtracking/Examples/Modular Expression.java @@ -1,22 +1,57 @@ +/** + * Problem: Calculate (a^b) % c using modular exponentiation with recursion. + * Algorithm: Divide-and-conquer using fast exponentiation - reduces repeated calculations. + * Time Complexity: O(log b) due to halving the exponent + * Space Complexity: O(log b) for recursion depth + */ public class Solution { + /** + * Calculates (a^b) % c using fast modular exponentiation. + * Handles overflow and negative modulo correctly. + * + * @param a the base + * @param b the exponent + * @param c the modulo divisor + * @return (a^b) % c + * + * Example: + * a=2, b=10, c=1000 -> 1024 % 1000 = 24 + * a=0, b=5, c=7 -> 0 + */ public int Mod(int a, int b, int c) { + // Step 1: Base case - if a is 0, result is always 0 if(a == 0){ return 0; } + + // Step 2: Base case - if b is 0, result is 1 (a^0 = 1) if(b == 0){ return 1; } - long y = 0; - if (b % 2 == 0) { - y = Mod(a, b/2, c); - y = (y * y) % c; - } - else { - y = a % c; - y = (y * Mod(a, b - 1, c)) % c; - } - - return (int)((y + c) % c); + // Step 3: Declare result variable + long y = 0; + + // Step 4: Use fast exponentiation - divide exponent by 2 + if (b % 2 == 0) { + // Step 4a: If b is even, compute (a^(b/2))^2 + // This reduces the problem size significantly + y = Mod(a, b / 2, c); + + // Step 4b: Square the result and apply modulo + y = (y * y) % c; + } + else { + // Step 4c: If b is odd, use a * a^(b-1) + // First compute a % c to handle large 'a' values + y = a % c; + + // Step 4d: Multiply with result of a^(b-1) and apply modulo + y = (y * Mod(a, b - 1, c)) % c; + } + + // Step 5: Handle negative modulo by adding c if needed + // This ensures result is always in range [0, c-1] + return (int)((y + c) % c); } } \ No newline at end of file diff --git a/Backtracking/Examples/Reverse Link List Recursion_new.java b/Backtracking/Examples/Reverse Link List Recursion_new.java new file mode 100644 index 0000000..9ab42a4 --- /dev/null +++ b/Backtracking/Examples/Reverse Link List Recursion_new.java @@ -0,0 +1,55 @@ +/** + * Problem: Reverse a linked list using recursion. + * Algorithm: Recursive approach - reach end of list, then reverse by modifying links. + * Time Complexity: O(n) where n is the number of nodes + * Space Complexity: O(n) for recursion stack depth + */ + +/** + * Definition for singly-linked list. + * class ListNode { + * public int val; + * public ListNode next; + * ListNode(int x) { val = x; next = null; } + * } + */ +public class Solution { + /** + * Reverses a linked list using recursion. + * Example: 1 -> 2 -> 3 becomes 3 -> 2 -> 1 + * + * @param A the head of the linked list + * @return the new head of the reversed list + */ + public ListNode reverseList(ListNode A) { + // Step 1: Base case - if list is empty, return null + if (A == null) { + return A; + } + + // Step 2: Store next node before we modify A + ListNode rest = A.next; + + // Step 3: Base case - if current node is last node, return it + // This becomes the new head of reversed list + if (rest == null) { + return A; + } + + // Step 4: Disconnect current node from the rest + // This prevents cycles when we reverse + A.next = null; + + // Step 5: Recursively reverse the rest of the list + // rest will be the head of already-reversed sublist + ListNode reverse = reverseList(rest); + + // Step 6: Connect the rest of the list back to current node + // After recursion, 'rest' points to what was a middle/end node + // Now we make 'rest' point back to 'A' to reverse the link + rest.next = A; + + // Step 7: Return the new head (unchanged in recursion) + return reverse; + } +} diff --git a/Backtracking/Problems/Combination Sum II.java b/Backtracking/Problems/Combination Sum II.java index 813a590..be2c2a5 100644 --- a/Backtracking/Problems/Combination Sum II.java +++ b/Backtracking/Problems/Combination Sum II.java @@ -1,25 +1,71 @@ +/** + * Problem: Find all unique combinations with target sum from array with duplicates. + * Each number can be used at most once, and result should have no duplicates. + * Algorithm: Backtracking with duplicate skipping and index increment to avoid reuse. + * Time Complexity: O(2^n) in worst case + * Space Complexity: O(n) for recursion depth + */ public class Solution { + /** + * Finds all unique combinations that sum to target B. + * Each element can be used at most once. + * + * @param a the list of candidate numbers (may contain duplicates) + * @param b the target sum + * @return ArrayList of all unique combinations that sum to B + * + * Example: + * a = [10, 1, 2, 7, 6, 1, 5], b = 8 + * Output: [[1,1,6], [1,2,5], [1,7], [2,6]] (one 1 used only once per combo) + */ public ArrayList> combinationSum(ArrayList a, int b) { + // Step 1: Sort array to group duplicates and enable easy duplicate skipping Collections.sort(a); + + // Step 2: Create result list ArrayList> ans = new ArrayList<>(); + + // Step 3: Start backtracking from index 0 helper(a, ans, new ArrayList<>(), b, 0); + + // Step 4: Return all valid combinations return new ArrayList<>(ans); } + /** + * Backtracking helper to find all combinations. + * + * @param a the sorted candidate array + * @param ans the result list + * @param curr the current combination being built + * @param b the remaining sum needed + * @param idx the starting index (each element used at most once) + */ private void helper(ArrayList a, ArrayList> ans, ArrayList curr, int b, int idx) { + // Step 1: If remaining sum becomes 0, we found valid combination if (b == 0) { ans.add(new ArrayList<>(curr)); } + // Step 2: If remaining sum is negative or no more elements, stop else if (b < 0 || idx == a.size()) { return; } + // Step 3: Try including each element (skip duplicates) else { for (int i = idx; i < a.size(); i++) { + // Step 3a: Skip duplicate values at same level + // If current value equals previous and we didn't start with previous, skip if (i > idx && a.get(i).equals(a.get(i - 1))) { continue; } + + // Step 3b: Choose - add current element to combination curr.add(a.get(i)); + + // Step 3c: Explore - recurse from next index (i+1 ensures each element used once) helper(a, ans, curr, b - a.get(i), i + 1); + + // Step 3d: Un-choose - backtrack curr.remove(curr.size() - 1); } } diff --git a/Backtracking/Problems/Combination Sum.java b/Backtracking/Problems/Combination Sum.java index e631c63..20a80a9 100644 --- a/Backtracking/Problems/Combination Sum.java +++ b/Backtracking/Problems/Combination Sum.java @@ -1,24 +1,68 @@ +/** + * Problem: Find all combinations of numbers from array that sum to target. + * Numbers can be reused, and duplicates in result should be avoided. + * Algorithm: Backtracking - explore all possible combinations and track valid ones. + * Time Complexity: O(2^n) in worst case for exploring all combinations + * Space Complexity: O(n) for recursion depth + output space + */ public class Solution { + /** + * Finds all unique combinations that sum to target B. + * + * @param A the list of candidate numbers (may contain duplicates) + * @param B the target sum + * @return ArrayList of all unique combinations that sum to B + * + * Example: + * A = [3, 6, 2], B = 9 + * Output: [[3, 3, 3], [3, 6], [2, 2, 2, ...]] or similar + */ public ArrayList> combinationSum(ArrayList A, int B) { + // Step 1: Sort array for consistency and easier duplicate handling ArrayList> ans = new ArrayList<>(); Collections.sort(A); + + // Step 2: Start backtracking from index 0 + // Current combination, remaining sum, and starting index helper(A, ans, new ArrayList<>(), B, 0); + + // Step 3: Return all valid combinations found return new ArrayList<>(ans); } + /** + * Backtracking helper to explore all combinations. + * + * @param a the candidate numbers array + * @param ans the result list of all valid combinations + * @param curr the current combination being built + * @param b the remaining sum needed + * @param idx the starting index to avoid duplicates + */ private void helper(ArrayList a, ArrayList> ans, ArrayList curr, int b, int idx) { + // Step 1: If remaining sum becomes negative, stop this path if (b < 0) { return; } + + // Step 2: If remaining sum becomes 0, we found a valid combination if (b == 0) { - if(!ans.contains(curr)) { + // Add only if this combination is not already in the result + if (!ans.contains(curr)) { ans.add(new ArrayList<>(curr)); } return; } + + // Step 3: Try adding each number starting from idx (allows reuse) for (int i = idx; i < a.size(); i++) { + // Step 3a: Choose - add current number to combination curr.add(a.get(i)); + + // Step 3b: Explore - recurse with same index (allows reuse) and reduced sum helper(a, ans, curr, b - a.get(i), i); + + // Step 3c: Un-choose - backtrack by removing the added number curr.remove(curr.size() - 1); } } diff --git a/Backtracking/Problems/Combinations.java b/Backtracking/Problems/Combinations.java index b08527a..5d297dc 100644 --- a/Backtracking/Problems/Combinations.java +++ b/Backtracking/Problems/Combinations.java @@ -1,20 +1,61 @@ +/** + * Problem: Generate all combinations of k numbers from 1 to n. + * Algorithm: Backtracking - recursively build combinations of exact size k. + * Time Complexity: O(C(n,k)) - number of combinations + * Space Complexity: O(k) recursion depth + */ public class Solution { + /** + * Generates all combinations of k numbers from 1 to n. + * + * @param A n (range: 1 to n) + * @param B k (size of each combination) + * @return ArrayList of all combinations of size k + * + * Example: + * A = 4, B = 2 + * Output: [[1,2], [1,3], [1,4], [2,3], [2,4], [3,4]] + */ public ArrayList> combine(int A, int B) { + // Step 1: Create result list ArrayList> ans = new ArrayList<>(); + + // Step 2: Start backtracking from number 1 helper(ans, A, 1, B, new ArrayList<>()); + + // Step 3: Return all combinations return ans; } + /** + * Backtracking helper to build combinations. + * + * @param ans the result list + * @param a the upper limit n + * @param idx the current number to consider (1 to n) + * @param b the size k of combinations needed + * @param curr the current combination being built + */ private void helper(ArrayList> ans, int a, int idx, int b, ArrayList curr) { + // Step 1: If current combination has enough elements, add it if (curr.size() >= b) { + // Only add if size equals exactly b (avoids duplicates) if (curr.size() == b) { ans.add(new ArrayList<>(curr)); } return; } + + // Step 2: Try each number from idx to n + // Only go up to n-remaining_needed to ensure we can complete the combination for (int i = idx; i <= a; i++) { + // Step 2a: Choose - add current number curr.add(i); + + // Step 2b: Explore - recurse from next number (i+1 to avoid duplicates) helper(ans, a, i + 1, b, curr); + + // Step 2c: Un-choose - backtrack curr.remove(curr.size() - 1); } } diff --git a/Backtracking/Problems/Generate all Parentheses II.java b/Backtracking/Problems/Generate all Parentheses II.java index 6ee1883..25820f5 100644 --- a/Backtracking/Problems/Generate all Parentheses II.java +++ b/Backtracking/Problems/Generate all Parentheses II.java @@ -1,28 +1,70 @@ +/** + * Problem: Generate all valid combinations of n pairs of parentheses. + * Algorithm: Backtracking - build strings with balanced parentheses using left/right counts. + * Time Complexity: O(4^n / sqrt(n)) - Catalan number complexity + * Space Complexity: O(n) for recursion depth + */ public class Solution { + /** + * Generates all valid combinations of n pairs of parentheses. + * + * @param A the number of pairs n + * @return sorted ArrayList of all valid parentheses combinations + * + * Example: + * A = 2 + * Output: ["(())", "()()"] + */ public ArrayList generateParenthesis(int A) { + // Step 1: Use HashSet to avoid duplicates during generation Set set = new HashSet<>(); + + // Step 2: Start backtracking with 0 left/right parentheses used helper(0, 0, A, new StringBuilder(), set); + + // Step 3: Convert set to ArrayList and sort ArrayList ans = new ArrayList<>(set); Collections.sort(ans); + + // Step 4: Return sorted list return ans; } + /** + * Backtracking helper to generate valid parentheses. + * + * @param left the count of '(' used + * @param right the count of ')' used + * @param n the total number of pairs needed + * @param sb the current string being built + * @param set the set of valid combinations + */ private void helper(int left, int right, int n, StringBuilder sb, Set set) { + // Step 1: If more than n open or close parentheses, invalid path if (left > n || right > n) { return; } + + // Step 2: Invalid if more closing than opening parentheses if (left < right) { return; } + // Step 3: If we've used all n open and n close parentheses if (left == right && left == n) { + // Add this valid combination to set set.add(sb.toString()); } - helper(left+1, right, n, sb.append("("), set); - sb.deleteCharAt(sb.length()-1); - helper(left, right+1, n, sb.append(")"), set); - sb.deleteCharAt(sb.length()-1); + // Step 4: Try adding opening parenthesis + helper(left + 1, right, n, sb.append("("), set); + // Step 4a: Backtrack by removing the added opening parenthesis + sb.deleteCharAt(sb.length() - 1); + + // Step 5: Try adding closing parenthesis + helper(left, right + 1, n, sb.append(")"), set); + // Step 5a: Backtrack by removing the added closing parenthesis + sb.deleteCharAt(sb.length() - 1); } } diff --git a/Backtracking/Problems/Letter Phone.java b/Backtracking/Problems/Letter Phone.java index 88d9f09..d37beac 100644 --- a/Backtracking/Problems/Letter Phone.java +++ b/Backtracking/Problems/Letter Phone.java @@ -1,5 +1,23 @@ +/** + * Problem: Given phone keypad digits, find all possible letter combinations. + * Phone keypad: 2=abc, 3=def, 4=ghi, 5=jkl, 6=mno, 7=pqrs, 8=tuv, 9=wxyz + * Algorithm: Backtracking - map each digit to letters and explore all combinations. + * Time Complexity: O(4^n) where n is number of digits (max 4 letters per digit) + * Space Complexity: O(n) for recursion depth + */ public class Solution { + /** + * Generates all possible letter combinations from phone digits. + * + * @param A the digit string + * @return ArrayList of all possible letter combinations + * + * Example: + * A = "23" + * Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"] + */ public static ArrayList letterCombinations(String A) { + // Step 1: Create mapping from digits to letter combinations Map map = new HashMap<>(); map.put(0, "0"); map.put(1, "1"); @@ -12,33 +30,53 @@ public static ArrayList letterCombinations(String A) { map.put(8, "tuv"); map.put(9, "wxyz"); + // Step 2: Create result list ArrayList ans = new ArrayList<>(); + // Step 3: Start backtracking from digit index 0 letterCombinationsHelper(A, ans, map, new StringBuilder(), 0); + // Step 4: Return all combinations return ans; } + /** + * Backtracking helper to generate all letter combinations. + * + * @param a the digit string + * @param ans the result list + * @param map the digit to letters mapping + * @param sb the current combination being built + * @param id the current digit index + */ private static void letterCombinationsHelper(String a, ArrayList ans, Map map, StringBuilder sb, int id) { + // Step 1: If index exceeds string length, stop if (id > a.length()) { return; } + // Step 2: If we've processed all digits and built a combination of same length if (id == a.length() && sb.length() == a.length()) { + // Add the complete combination ans.add(new StringBuilder(sb.toString()).toString()); } + // Step 3: Try all letters for each digit else { - for (int i=id; i> partition(String a) { + // Step 1: Create result list ArrayList> ans = new ArrayList<>(); + + // Step 2: Start backtracking from index 0 helper(ans, new ArrayList(), a, 0); + + // Step 3: Return all palindrome partitions return ans; } + /** + * Backtracking helper to find all palindrome partitions. + * + * @param ans the result list of all partitions + * @param temp the current partition being built + * @param a the input string + * @param idx the current starting index in string + */ private void helper(ArrayList> ans, ArrayList temp, String a, int idx) { + // Step 1: If reached end of string, we have a complete partition if (idx == a.length()) { ans.add(new ArrayList<>(temp)); return; } - for (int i=idx; i> permute(ArrayList A) { + // Step 1: Create result list ArrayList> ans = new ArrayList<>(); + + // Step 2: Start backtracking with empty current permutation and empty used set helper(A, ans, new ArrayList(), new HashSet<>()); + + // Step 3: Return all permutations return ans; } - private void helper(ArrayList A, ArrayList> ans, ArrayList curr, Set set) { + /** + * Backtracking helper to generate all permutations. + * + * @param A the original array + * @param ans the result list + * @param curr the current permutation being built + * @param set the set of elements already used in current permutation + */ + private void helper(ArrayList A, ArrayList> ans, ArrayList curr, Set set) { + // Step 1: If current permutation has all elements, add it to result if (curr.size() == A.size()) { ans.add(new ArrayList<>(curr)); } else { + // Step 2: Try adding each element that hasn't been used yet for (int i = 0; i < A.size(); i++) { + // Step 2a: Check if element is not already used in current permutation if (!set.contains(A.get(i))) { + // Step 2b: Choose - add element to current permutation set.add(A.get(i)); curr.add(A.get(i)); + + // Step 2c: Explore - recurse to add more elements helper(A, ans, curr, set); + + // Step 2d: Un-choose - backtrack curr.remove(curr.size() - 1); set.remove(A.get(i)); } diff --git a/Backtracking/Problems/Solution.java b/Backtracking/Problems/Solution.java new file mode 100644 index 0000000..0ea354a --- /dev/null +++ b/Backtracking/Problems/Solution.java @@ -0,0 +1,62 @@ +/** + * Problem: Generate all permutations (arrangements) of an array. + * Algorithm: Backtracking - track used elements with HashSet to build all orderings. + * Time Complexity: O(n! * n) where n! permutations and n to copy each + * Space Complexity: O(n) for recursion and HashSet + */ +public class Solution { + /** + * Generates all permutations of the given array. + * + * @param A the input array + * @return ArrayList of all permutations + * + * Example: + * A = [1, 2, 3] + * Output: [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]] + */ + public ArrayList> permute(ArrayList A) { + // Step 1: Create result list + ArrayList> ans = new ArrayList<>(); + + // Step 2: Start backtracking with empty current permutation and empty used set + helper(A, ans, new ArrayList(), new HashSet<>()); + + // Step 3: Return all permutations + return ans; + } + + /** + * Backtracking helper to generate all permutations. + * + * @param A the original array + * @param ans the result list + * @param curr the current permutation being built + * @param set the set of elements already used in current permutation + */ + private void helper(ArrayList A, ArrayList> ans, ArrayList curr, Set set) { + // Step 1: If current permutation has all elements, add it to result + if (curr.size() == A.size()) { + ans.add(new ArrayList<>(curr)); + } + else { + // Step 2: Try adding each element that hasn't been used yet + for (int i = 0; i < A.size(); i++) { + // Step 2a: Check if element is not already used in current permutation + if (!set.contains(A.get(i))) { + // Step 2b: Choose - add element to current permutation + set.add(A.get(i)); + curr.add(A.get(i)); + + // Step 2c: Explore - recurse to add more elements + helper(A, ans, curr, set); + + // Step 2d: Un-choose - backtrack + curr.remove(curr.size() - 1); + set.remove(A.get(i)); + } + } + } + } +} + diff --git a/Backtracking/Problems/Subset.java b/Backtracking/Problems/Subset.java index f5f7539..f940044 100644 --- a/Backtracking/Problems/Subset.java +++ b/Backtracking/Problems/Subset.java @@ -1,16 +1,53 @@ +/** + * Problem: Generate all subsets (power set) of an array. + * Algorithm: Backtracking - make choice to include/exclude each element. + * Time Complexity: O(2^n) - there are 2^n subsets + * Space Complexity: O(n) for recursion depth + */ public class Solution { + /** + * Generates all subsets of the given array. + * + * @param A the input array + * @return ArrayList of all subsets + * + * Example: + * A = [1, 2, 3] + * Output: [[], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]] + */ public ArrayList> subsets(ArrayList A) { + // Step 1: Create result list and sort array for consistent output ArrayList> ans = new ArrayList<>(); Collections.sort(A); + + // Step 2: Start backtracking from index 0 to generate all subsets helper(A, ans, new ArrayList<>(), 0); + + // Step 3: Return all subsets return ans; } + /** + * Backtracking helper to generate all subsets. + * + * @param A the original array + * @param ans the result list of all subsets + * @param curr the current subset being built + * @param idx the current index in array (start from this to avoid duplicates) + */ private void helper(ArrayList A, ArrayList> ans, ArrayList curr, int idx) { + // Step 1: Add current subset to result (even if empty) ans.add(new ArrayList<>(curr)); + + // Step 2: Try adding each element from idx onwards to create new subsets for (int i = idx; i < A.size(); i++) { + // Step 2a: Choose - add element to current subset curr.add(A.get(i)); + + // Step 2b: Explore - recurse to add more elements (start from i+1) helper(A, ans, curr, i + 1); + + // Step 2c: Un-choose - backtrack by removing the element curr.remove(curr.size() - 1); } } diff --git a/Backtracking/Problems/Subsets II.java b/Backtracking/Problems/Subsets II.java index 9a19af4..bd09e53 100644 --- a/Backtracking/Problems/Subsets II.java +++ b/Backtracking/Problems/Subsets II.java @@ -1,22 +1,64 @@ +/** + * Problem: Generate all subsets from array with duplicates (no duplicate subsets). + * Algorithm: Backtracking with duplicate skipping at the same recursion level. + * Time Complexity: O(2^n) + * Space Complexity: O(n) for recursion depth + */ public class Solution { + /** + * Generates all unique subsets of array with duplicates. + * + * @param A the input array (may contain duplicates) + * @return ArrayList of all unique subsets + * + * Example: + * A = [1, 2, 2] + * Output: [[], [1], [2], [1,2], [2,2], [1,2,2]] + */ public ArrayList> subsetsWithDup(ArrayList A) { + // Step 1: Sort array to group duplicates for easy skipping ArrayList> ans = new ArrayList<>(); Collections.sort(A); + + // Step 2: Start backtracking from index 0 helper(A, new ArrayList<>(), ans, 0); + + // Step 3: Return all unique subsets return ans; } + /** + * Backtracking helper to generate all unique subsets. + * + * @param a the sorted array with duplicates + * @param curr the current subset being built + * @param ans the result list of all subsets + * @param idx the current starting index + */ private void helper(ArrayList a, ArrayList curr, ArrayList> ans, int idx) { + // Step 1: Add current subset to result (including empty set) ans.add(new ArrayList<>(curr)); + + // Step 2: If reached end of array, return if (idx >= a.size()) { return; } + + // Step 3: Try adding each element from idx onwards to create new subsets for (int i = idx; i < a.size(); i++) { + // Step 3a: Skip duplicate values at the same recursion level + // If current value equals previous and we're not using first occurrence, skip it if (i > idx && a.get(i).equals(a.get(i - 1))) { continue; } + + // Step 3b: Choose - add element to current subset curr.add(a.get(i)); + + // Step 3c: Explore - recurse from next index (i+1 to avoid using same element twice) helper(a, curr, ans, i + 1); + + // Step 3d: Un-choose - backtrack by removing element curr.remove(curr.size() - 1); } }