diff --git a/Problem1.java b/Problem1.java new file mode 100644 index 00000000..df347d49 --- /dev/null +++ b/Problem1.java @@ -0,0 +1,81 @@ +// Problem 1 : Group Anagrams + +//Approach 1: +//Time Complexity: O(n * k log k), where n is the number of strings and k is the average length of a string +//Space Complexity: O(n * k), where n is the number of strings and k is the average length of a string +class Solution { + /** + * Groups anagrams by sorting each string to create a unique key. + * All anagrams will result in the same sorted string, allowing them to be grouped in a HashMap. + */ + public List> groupAnagrams(String[] strs) { + + HashMap > map = new HashMap<>(); + for (int i = 0; i < strs.length ; i++){ + String currentString = strs[i]; + char [] charArray = currentString.toCharArray(); + // Sort characters: Anagrams like "eat" and "tea" both become "aet" + Arrays.sort(charArray); // added complexity O(k log k) + String sortedString = String.valueOf(charArray); + // Use the sorted string as a key to group original strings + if(!map.containsKey(sortedString)){ + map.put(sortedString, new ArrayList<>()); + } + List list = map.get(sortedString); + list.add(currentString); + map.put(sortedString,list); + } + return new ArrayList<>(map.values()); + } + +} + + +// Approach 2: + +import java.math.BigInteger; + +//Time Complexity: O(n * k) where n is the number of strings and k is the average length of a string +//Space Complexity: O(n * k) where n is the number of strings +class Solution { + /** + * Groups anagrams using Prime Factorization (Fundamental Theorem of Arithmetic). + * Each lowercase letter is mapped to a unique prime number. The product of these primes + * for a word is unique to its set of characters, regardless of their order. + */ + public List> groupAnagrams(String[] strs) { + + HashMap > map = new HashMap<>(); + + for (int i = 0; i < strs.length ; i++){ + String currentString = strs[i]; + // Calculate a unique numeric key based on prime products + BigInteger primeProduct = primeProduct(currentString); + + if(!map.containsKey(primeProduct)){ + map.put(primeProduct, new ArrayList<>()); + } + List list = map.get(primeProduct); + list.add(currentString); + map.put(primeProduct,list); + } + return new ArrayList<>(map.values()); + } + + /** + * Calculates the product of primes corresponding to each character in the string. + * Uses BigInteger to prevent overflow for long strings. + */ + private BigInteger primeProduct(String s){ + // Mapping 'a' through 'z' to the first 26 prime numbers + int [] prime =new int [] {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101,103}; + BigInteger result = BigInteger.ONE; + for(int i=0;i < s.length();i++){ + char ch = s.charAt(i); + // Multiply the running total by the prime number associated with the current character + result = result.multiply(BigInteger.valueOf( prime[ch - 'a'])); + } + return result; + } + +} diff --git a/Problem2.java b/Problem2.java new file mode 100644 index 00000000..10947985 --- /dev/null +++ b/Problem2.java @@ -0,0 +1,88 @@ +//Problem 2: Isomorphic Strings + +// Approach 1: Using two hashmaps to map characters from one string to another, ensuring one-to-one correspondence. +class Solution { + /** + * Determines if two strings are isomorphic using two HashMaps. + * This approach maintains a bidirectional mapping: one from string 's' characters to 't', + * and another from string 't' characters back to 's'. This ensures a strict 1:1 relationship (bijection), + * preventing multiple characters from mapping to the same character. + * + * Time Complexity: O(n) - We iterate through the strings once, where n is the length of the strings. + * Space Complexity: O(1) - While we use HashMaps, the number of possible characters (like ASCII) is fixed, + * resulting in constant auxiliary space. + */ + public boolean isIsomorphic(String s, String t) { + int sl = s.length(); + int tl = t.length(); + + if(sl != tl){ + return false; + } + HashMap s_map = new HashMap<>(); + HashMap t_map = new HashMap<>(); + + for(int i = 0 ; i < sl ; i++ ){ + Character s_char = s.charAt(i); + Character t_char = t.charAt(i); + + if(s_map.containsKey(s_char)){ + if(s_map.get(s_char) != t_char){ + return false; + } + }else{ + s_map.put(s_char, t_char); + } + if(t_map.containsKey(t_char)){ + if(t_map.get(t_char) != s_char){ + return false; + } + }else{ + t_map.put(t_char, s_char); + } + } + return true; + } + + // Approach 2: Using HashMap and a HashSet for one-to-one correspondence + + /** + * Determines if two strings are isomorphic using one HashMap and one HashSet. + * This approach maps characters from 's' to 't' using a HashMap. To ensure no two characters + * from 's' map to the same character in 't', a HashSet is used to keep track of characters + * in 't' that have already been assigned a mapping. + * + * Time Complexity: O(n) - We iterate through the strings once. + * Space Complexity: O(1) - The space used by the Map and Set is limited by the fixed size of the character set. + */ + public boolean isIsomorphic_2(String s, String t) { + int sl = s.length(); + int tl = t.length(); + + if(sl != tl){ + return false; + } + HashMap s_map = new HashMap<>(); + HashSet t_set = new HashSet<>(); + + for(int i = 0 ; i < sl ; i++ ){ + Character s_char = s.charAt(i); + Character t_char = t.charAt(i); + + if(s_map.containsKey(s_char)){ + if(s_map.get(s_char) != t_char){ + return false; + } + }else{ + if(t_set.contains(t_char)){ + return false; + }else{ + s_map.put(s_char, t_char); + t_set.add(t_char); + } + } + + } + return true; + } +} \ No newline at end of file diff --git a/Problem3.java b/Problem3.java new file mode 100644 index 00000000..68fa8b34 --- /dev/null +++ b/Problem3.java @@ -0,0 +1,81 @@ +// Problem 3: word pattern matching + +class Solution { + + /** + * Approach 1: Using two hashmaps to map characters to words and vice versa, ensuring one-to-one correspondence. + * Determines if a string follows a given word pattern using two HashMaps. + * This approach maintains a two-way mapping: one from characters to words and another from words to characters. + * This ensures a strict bijection where each unique character maps to exactly one unique word and vice versa. + + * Time Complexity: O(n) where n is the length of the string + * Space Complexity: O(n) where n is the length of the string + */ + public boolean wordPattern(String pattern, String s) { + if(pattern == null && s == null) return true; + if(pattern == null || s == null) return false; + String[] tString = s.split(" "); + if(pattern.length() != tString.length)return false; + + HashMap smap = new HashMap<>(); + HashMap tmap = new HashMap<>(); + + ArrayList tString1 = new ArrayList<>(Arrays.asList(tString)); + + for(int i = 0; i < pattern.length(); i++ ){ + char sChar = pattern.charAt(i); + + if(smap.containsKey(sChar)){ + if(!smap.get(sChar).equals( tString[i])) return false; + }else{ + smap.put(sChar, tString[i]); + } + if(tmap.containsKey(tString[i])){ + if(!tmap.get(tString[i]).equals(sChar)) return false; + }else{ + tmap.put(tString[i], sChar); + } + + } + return true; + } + + + // Approach 2: Using HashMap and HashSet for one-to-one correspondence + /** + * Determines if a string follows a given word pattern using a HashMap and a HashSet. + * The HashMap stores the character-to-word mapping. The HashSet tracks words that have already + * been assigned to a character. If a new character maps to a word already in the HashSet, + * it indicates a violation of the bijection. + * Time Complexity: O(n) where n is the length of the string + * Space Complexity: O(n) where n is the length of the string + */ + + public boolean wordPattern_2(String pattern, String s) { + if(pattern == null && s == null) return true; + if(pattern == null || s == null) return false; + String[] tString = s.split(" "); + if(pattern.length() != tString.length)return false; + + HashMap smap = new HashMap<>(); + HashSet tset = new HashSet<>(); + + for(int i = 0; i < pattern.length(); i++ ){ + char sChar = pattern.charAt(i); + String currentWord = tString[i]; + + if(smap.containsKey(sChar)){ + if(!smap.get(sChar).equals(currentWord)){ + return false; + } + }else{ + if(tset.contains(currentWord)){ + return false; + } + } + smap.put(sChar,currentWord); + tset.add(currentWord); + } + return true; + } +} \ No newline at end of file