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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions GroupAnagrams.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// GroupAnagrams.swift
// DSA-Practice
//
// Created by Paridhi Malviya on 1/3/26.
//

//Same characters with same frequence but the order can be jumbeled
class GroupAnagrams {

init() {

groupAnagrams()
}

/*
In hash map, hashing is done for strings which are stored as keys. Under the hood, hashing can use multiple techniques
1. naive approach - perform operations on ascii values for chars. However, bat, abt, tab will have the same value. And other uncountable no of strings will also have the same value.
2. assign prime numbers to all characters. a - 3, b - 7, t - 11. Then only the anagrams will have the same value. We don't need to sort the strings in this case, thus can eliminate O(n logn) time complexity.
It will be only O(log n) time complexity.
In hashmap, strings are also hashed using the same way we hash strings using "frequency, position and character"
can associate prime numbers with characters
contains() function also uses O(n) time complexity
*/
func groupAnagrams() {

let input = ["bat", "ant", "abt", "aet", "eat", "nat"]

//fist sort all element s and then add into the values array for a particular group.
// //create a global map. Keep distinct sorted values of all strings as key and add an element in value array
var map = [String: [String]]()

for i in 0..<input.count {
let str = input[i]
//O(nLogn)
let sortedStr = String(str.sorted(by: <))
if (map[sortedStr] != nil) {
//means a key with these exact no of characters already exist in the map. So add the actual string in it's value's array
map[sortedStr]?.append(str)
} else {
//if it doesn't exist then add this entry in value's array
map[sortedStr] = [str]
}
}
//print all groups
for value in map.values {
print("value ** \(value)")
}
}


//Space complexity - O(nk) -> n - no of words, k - length of each word. Hash map is taking the space
func GroupanagramsUsingPrimeProduct(str: String) {
var map = [Int: [String]] ()
let input = ["bat", "ant", "abt", "aet", "eat", "nat"]
for i in 0..<input.count {
let primeProductOfStr = primeProduct(str: input[i])
if (map[primeProductOfStr] != nil) {
map[primeProductOfStr]?.append(input[i])
} else {
map[primeProductOfStr] = [input[i]]
}
}
for value in map.values {
print("value is \(value)")
}
}

private func primeProduct(str: String) -> Int {
let prime: [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, 103]

var result = 1
for i in 0..<str.count {
let ch = str[str.index(str.startIndex, offsetBy: i)]
let asciiOfa = "a".unicodeScalars.first!.value % 265
let asciiOfCh = ch.unicodeScalars.first!.value % 256
let index = asciiOfa - asciiOfCh
result = result * prime[Int(index)]
}
return result
}
}
108 changes: 108 additions & 0 deletions IsomorphicStrings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//
// IsomorphicStrings.swift
// DSA-Practice
//
// Created by Paridhi Malviya on 1/3/26.
//

/*
use map to keep track of characters mapping. because it's search complexcity is O(1)
In space complexity analysis- we always consider the auxilliary space / computational space.
auxilliary / computational space -> the space created for computational purposes.
eg if you are given 2 input strings- will those be considered as extra spaxe. No, the input is not considered as extra space. The same will output. Input and output are not considered as auxilliary space.
space complexity includes - extra data structure that is maintained for computational purpose.

In isomorphic strings problems - 2 hash maps can be used to maintain the characters mapping. Even if I have a string of 1000 charcaters which has all small letters.
then the maximum entries in the hash map would be 26 which is constant.
space complexity will be O(n)- if the space needed is increasing with the size of the input. But in chars, it wouldn't increase beyond 26 charas.
Even if we are maintaining 2 maps - 1 map can have 26 characters and other map also, 26 characters. Not more than these. Hence, O(1) space complexity.
When we want to search - use hashing based data structure (for optimal search capability)

Time complexity - depends on the lenght of strings. We will go oer all characters of the string
*/

class IsomorphicStrings {

init() {
let isIsomorphic = isIsomorphicStrings(s: "egg", t: "add")
print("isIsomorphic \(isIsomorphic)")

let isIso = isIsomorphicStringsSolutionUsingAscii(s: "eggb", t: "adde")
print("isIso \(isIso)")
}

//egg -add
func isIsomorphicStrings(s: String, t: String) -> Bool {
let sLength = s.count
let tLength = t.count

if (sLength != tLength) {
//not an isomorphic string
return false
}

var sMap = [Character: Character]()
var tMap = [Character: Character]()
for i in 0..<sLength {

let sChar: Character = s[s.index(s.startIndex, offsetBy: i)]
print("sChar \(sChar) *** i \(i)")
let tChar = t[t.index(t.startIndex, offsetBy: i)]
print("tChar: \(tChar) *** i \(i)")
if (sMap[sChar] != nil) {
if (sMap[sChar] != tChar) {
//breach in isomorphibility
return false
}
} else {
sMap[sChar] = tChar
}
if (tMap[tChar] != nil) {
if (tMap[tChar] != sChar) {
return false
}
} else {
tMap[tChar] = sChar
}
}
return true
}

func isIsomorphicStringsSolutionUsingAscii(s: String, t: String) -> Bool {
let sL = s.count
let tL = t.count
// If lengths are not equal, return false
if sL != tL { return false }

// Use last-seen position arrays for ASCII (0..255). Initialize with 0 meaning unseen.
var sLast = Array(repeating: 0, count: 256)
var tLast = Array(repeating: 0, count: 256)

// Iterate over characters by index
for i in 0..<sL {
let sIndex = s.index(s.startIndex, offsetBy: i)
let tIndex = t.index(t.startIndex, offsetBy: i)
let sChar = s[sIndex]
let tChar = t[tIndex]

// Convert Character to ASCII code point (UInt8) safely; if non-ASCII, fall back to unicodeScalar value modulo 256
let sCode = Int(sChar.unicodeScalars.first!.value % 256)
let tCode = Int(tChar.unicodeScalars.first!.value % 256)

print("sChar unicode scalars *** \(sChar.unicodeScalars) *** \(sChar.unicodeScalars.count) *** \(sChar.unicodeScalars.first!.value)")
print("tChar unicode scalars *** \(tChar.unicodeScalars) *** \(tChar.unicodeScalars.count) *** \(tChar.unicodeScalars.first!.value)")
print("ASCII *** \(sCode) **** \(tCode)")

// We store i+1 to distinguish unseen (0) from position 0
if sLast[sCode] != tLast[tCode] {
return false
}
sLast[sCode] = i + 1
tLast[tCode] = i + 1
}
return true
}

//third solution: Using a set

}
60 changes: 60 additions & 0 deletions MultipleStringsPatternMatch.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// MultipleStringsPatternMatch.swift
// DSA-Practice
//
// Created by Paridhi Malviya on 1/8/26.
//

import Foundation

/*
Use two maps
store character to string mapping in charMap map
Store the string to character mapping in strMap map
If the mapping is not consistent at any point then the pattern is not matching with the string. If no discrepency is found, then at the end of the string, true will be returned.
if charArray length is not equal to str array length, then pattern not matching
Space complexity - O(1) characters can;t be more than 26 while mapping.
Time complexity -> O(n), length of the pattern string. We would have to iterate through these many no of times.
*/

class MultipleStringsPatternMatch {

init() {
/*
Input - pattern = "abba", str = "dog cat cat dog"
output- true

Input - pattern = "abba", str = "dog cat cat fish"
output - false //it doesn't match the pattern
*/
let isPatternMatching = multipleStringsPatternMatch(pattern: "abba", str: "dog cat cat dog")
print("is pattern matching \(isPatternMatching)")
}

func multipleStringsPatternMatch(pattern: String, str: String) -> Bool {

let stringArray = str.components(separatedBy: " ")
let charArray = Array(pattern)

if (stringArray.count != charArray.count) {
return false
}
print("string array \(stringArray)")
print("char array \(charArray)")

var charMap: [Character: String] = [:]
var strMap: [String: Character] = [:]

for i in 0..<charArray.count {
//if the key and value are not present in any of the map then store the 2-way mapping in both maps
if (charMap[charArray[i]] == nil && strMap[stringArray[i]] == nil) {
charMap[charArray[i]] = stringArray[i]
strMap[stringArray[i]] = charArray[i]
} else if ((charMap[charArray[i]] != stringArray[i]) || (strMap[stringArray[i]] != charArray[i])){
//If both or only 1 mapping is oresent then check if the mapping is consistent in both maps.. if not, return false
return false
}
}
return true
}
}