Lecture 1 (Basic and Easy problems)¶
Reverse a string¶
input = "piyush"
output = "hsuyip
Approach 1 (Brute Force)¶
def reverse(s):
chars = list(s)
n = len(chars)
left, right = 0,n-1
while left < right:
chars[left],chars[right] = chars[right],chars[left]
left +=1
right -=1
s = "".join(chars)
return s
print(reverse("piyush"))
hsuyip
Approach 2 (Built in method)¶
def reverse(s):
chars = list(s)
reversed_chars = chars[::-1]
return "".join(reversed_chars)
print(reverse("piyush"))
hsuyip
Complexity¶
O(N) : N = len(s)
O(1)
Remove extra spaces from beg of string¶
input = " piyush"
output = "piyush"
input = " preeti is good "
output = "preeti is good "
Approach 1 (Brute Force)¶
- find the count of spaces in the beg
- left shift the str by the count
def trim_front(s):
count =0
n = len(s)
chars = list(s)
for ch in chars:
if ch ==" ":
count +=1
else:
break
if count == n:
return ""
## left shift the list by count
for i in range(n- count):
chars[i] = chars[i+count]
return "".join(chars[0:n-count])
print(trim_front(" piyush"))
print(trim_front(" preeti is good "))
piyush preeti is good
Approach 2 (Use built in)¶
def trim_front(s):
return s.lstrip()
print(trim_front(" piyush"))
print(trim_front(" preeti is good "))
piyush preeti is good
Approach 3 (manual but cleaner)¶
def trim_front(s):
count =0
for ch in s:
if ch != " ":
break
count +=1
return s[count:]
print(trim_front(" piyush"))
print(trim_front(" preeti is good "))
piyush preeti is good
Complexity¶
O(N) : N = len(s)
O(1)
Trim spaces from the right¶
input = "piyush "
output ="piyush"
input=" preeti is good "
output = " preeti is good"
Approach 1 (Brute Force)¶
- count the spaces from end
- right shift the list by count
def trim_end(s):
count =0
n = len(s)
for i in range(n-1,-1,-1):
ch = s[i]
if ch != " ":
break
count +=1
return s[0:n-count]
print(trim_end("piyush "))
print(trim_end(" preeti is good "))
piyush preeti is good
Approach 2 (built in method)¶
def trim_end(s):
return s.rstrip()
print(trim_end("piyush "))
print(trim_end(" preeti is good "))
piyush preeti is good
Approach 3 (directly modify n)¶
def trim_end(s):
n = len(s)
for i in range(n-1,-1,-1):
ch = s[i]
if ch != " ":
break
n -=1
return s[0:n]
print(trim_end("piyush "))
print(trim_end(" preeti is good "))
piyush preeti is good
Complexity¶
O(N) : N = len(s)
O(1)
Remove Outermost Parentheses¶
A valid parentheses string is defined by the following rules:
- It is the empty string "".
- If A is a valid parentheses string, then so is "(" + A + ")".
- If A and B are valid parentheses strings, then A + B is also valid. A primitive valid parentheses string is a non-empty valid string that cannot be split into two or more non-empty valid parentheses strings.
Given a valid parentheses string s, your task is to remove the outermost parentheses from every primitive component of s and return the resulting string.
Input: s = "(()())(())"
Output: "()()()"
Input: s = "(()())(())(()(()))"
Output: "()()()()(())"
Input: s = "()()"
Output: ""
Approach 1 (Brute Force)¶
- from all three conditions , a string is only made from "(" or ")"
- while traversing , we need to keep track of level starting from 0
- we must not push the ch if it is in level 0 (outermost parenthesis)
- if we encounter "(" : only push it when level >0 and always increse the level later
- if we encounter ")" : always decrease the level first and if the level>0 then push it to result
def remove_outer(s):
result = ""
level =0
for ch in s:
if ch == "(":
# when it is not outer most "("
if level >0:
result += ch
level +=1
else:
level -=1
# when it is not outer most ")"
if level >0:
result += ch
return result
print(remove_outer("(()())(())"))
print(remove_outer("(()())(())(()(()))"))
print(remove_outer("()()"))
()()() ()()()()(())
Complexity¶
O(N) : N= len(s)
O(N) : N = len(s)
Reverse Words in a String¶
Input: s=”this is an amazing program”
Output: “program amazing an is this”
Input: s=”This is decent”
Output: “decent is This”
Approach 1 (Brute Force)¶
- split the string into words
- then reverse the order
def reverse_words(s):
words = []
word = ""
for ch in s:
if ch == " ":
words.append(word)
word = ""
else:
word += ch
words.append(word)
if len(words) ==0:
return words
left , right = 0,len(words) -1
while left < right:
words[left],words[right] = words[right],words[left]
left +=1
right -=1
return " ".join(words)
print(reverse_words("this is an amazing program"))
print(reverse_words("This is decent"))
program amazing an is this decent is This
Complexity¶
O(N) : N = len(s)
O(N) : N = len(s)
Approach2 (Optimise Space)¶
- We will build the helper function to reverse a string for a range
- First reverse the entire string
- In a forward pass , if we find the word , reverse it again
def reverse_words(s):
chars = list(s)
n = len(chars)
def reverse(start,end):
while start < end:
chars[start],chars[end] = chars[end],chars[start]
start +=1
end -=1
reverse(0,n-1)
begin =0
for i in range(n):
if chars[i] == " ":
reverse(begin,i-1)
begin = i+1
reverse(begin,n-1)
return "".join(chars)
print(reverse_words("this is an amazing program"))
print(reverse_words("This is decent"))
program amazing an is this decent is This
Complexity¶
O(N) : N = len(s)
O(1)
Reverse the words when words are seprated by atleast a space¶
Input: s = "the sky is blue"
Output: "blue is sky the"
Input: s = " hello world "
Output: "world hello"
Input: s = "a good example"
Output: "example good a"
Approach 1 (Brute Force manual)¶
- remove leading and trailing spaces
- compress multiple spaces into single space
- extract words manually
- reverse words manually
- join manually
def reverse_words(s):
n = len(s)
chars = list(s)
## find the start idx
start =0
while start <n and chars[start]== " ":
start +=1
## empty string
if start ==n:
return ""
## find the end idx
end =n-1
while end >=0 and chars[end] == " ":
end -=1
## We have already handled empty string
## split the words manually
words = []
current_word= ""
for i in range(start,end +1):
ch = chars[i]
if ch != " ":
current_word += ch
else:
if current_word != "":
words.append(current_word)
current_word = ""
words.append(current_word)
## reverse words
left , right = 0 , len(words)-1
while left < right:
words[left],words[right] = words[right],words[left]
left +=1
right -=1
return " ".join(words)
print(reverse_words("the sky is blue"))
print(reverse_words(" hello world "))
print(reverse_words("a good example"))
blue is sky the world hello example good a
Approach 2 (Dont use extra space ) (Recommended)¶
- build a helper method to reverse list
- first calculate the start pos
- calculate end pod
- compress the extra space to 1
Above 3 steps can be taken care by built in str.split()
- split the space by words by making use of write pos
def reverse_words(s):
chars = list(s)
n = len(chars)
def start_pos():
start =0
while start < n and chars[start] == " ":
start +=1
return start
def end_pos():
end = n-1
while end >=0 and chars[end] == " ":
end -=1
return end
# it compress the middle spaces into 1 and remove spaces from beg
def compress_spaces(start,end):
write_pos = 0
i = start
while i <=end:
if chars[i] != " ":
chars[write_pos] = chars[i]
write_pos +=1
i+=1
else:
chars[write_pos] = " "
write_pos +=1
while i <= end and chars[i] == " ":
i+=1
return write_pos
def reverse(left , right):
while left < right:
chars[left],chars[right] = chars[right],chars[left]
left +=1
right -=1
def reverse_words(new_length):
start =0
for i in range(new_length):
if chars[i] == " ":
reverse(start,i-1)
start = i+1
reverse(start,new_length-1)
# start idx of non empty char
start = start_pos()
if start == n:
return ""
# end idx of non empty char
end = end_pos()
# compress the spaces and get new length
new_length = compress_spaces(start,end)
# reverse the entire string
reverse(0,new_length-1)
# reverse the words
reverse_words(new_length)
# form the result
return "".join(chars[:new_length])
# Tests
print(reversed := reverse_words("the sky is blue"))
print(reversed := reverse_words(" hello world "))
print(reversed := reverse_words("a good example"))
blue is sky the world hello example good a
Approach 3 (Using built in methods)¶
Split already handles multiple spaces
use built in list.reverse()
def reverse_words(s):
words = s.split()
reversed = words[::-1]
return " ".join(reversed)
print(reverse_words("the sky is blue"))
print(reverse_words(" hello world "))
print(reverse_words("a good example"))
blue is sky the world hello example good a
Complexity¶
O(N) : N = len(s)
O(N) : N = len(s) # to store the words and reversed
Approach 4 (1 line solution)¶
def reverse_words(s):
return " ".join(s.split()[::-1])
print(reverse_words("the sky is blue"))
print(reverse_words(" hello world "))
print(reverse_words("a good example"))
Complexity¶
O(N) : N = len(s)
O(1)
Largest Odd Number in a String.¶
Given a string s, representing a large integer, the task is to return the largest-valued odd integer (as a string) that is a substring of the given string s. The number returned should not have leading zero's. But the given input string may have leading zero.
Input: s = "5347"
Output: "5347"
Input: s = "0214638"
Output: "21463"
Approach 1 (Brute Force)¶
- calculate the value by value = 10*val + digit
- save values in the array
- result = max(odd value)
def large_odd(s):
result = 0
value =0
values = []
for ch in s:
num = ord(ch) - ord('0')
value = value * 10 + num
values.append(value)
for val in values:
if val > result and val%2 ==1:
result = val
return result
print(large_odd("5347"))
print(large_odd("0214638"))
5347 21463
Complexity¶
O(N) : N = len(s)
O(N) : N = len(s)
Approach 2 (Optimise space)¶
- No need for creating values
- calculate running max odd value every iteration
def large_odd(s):
result = 0
value =0
for ch in s:
num = ord(ch) - ord('0')
value = value * 10 + num
if value > result and value % 2==1:
result = value
return result
print(large_odd("5347"))
print(large_odd("0214638"))
5347 21463
Complexity¶
O(N) : N = len(s)
O(1)
The above method does not scale because¶
- lets say for a very large string (4300 + digits)
- building an entire number as an integer cannot be done
- so a number is odd decided from right to left
- first find the start (non 0 value)
- backward pass till we encounter first odd
def large_odd(s):
start =0
n = len(s)
while start < n and s[start] == "0":
start +=1
## end idx for odd via backward pass
end = n-1
while end >=0 and int(s[end]) %2 ==0:
end -=1
return s[start:end+1]
print(large_odd("5347"))
print(large_odd("0214638"))
5347 21463
Complexity¶
O(N) : N = len(s)
O(1)
Longest Common Prefix¶
Write a function to find the longest common prefix string amongst an array of strings. If there is no common prefix, return an empty string "".
Input: str = ["flower", "flow", "flight"]
Output: "fl"
Input: str = ["apple", "banana", "grape", "mango"]
Output: ""
Approach 1 (Brute Force)¶
- find the word with min characters
- search through each and every char
import sys
def longest_prefix(s):
pattern_length = sys.maxsize
for word in s:
pattern_length = min(pattern_length,len(word))
if pattern_length ==0:
return ""
pattern = s[0][0:pattern_length]
match_count = pattern_length
for word in s:
count =0
for i in range(pattern_length):
if pattern[i] == word[i]:
count +=1
match_count = min(match_count,count)
return pattern[:match_count]
print(longest_prefix(["flower", "flow", "flight"]))
print(longest_prefix(["apple", "banana", "grape", "mango"]))
fl
Complexity¶
O(WC * L) : WC : number of words = len(s) and L : shortest length of word
O(L) : L : length of shortest word
Approach 2 (Optimise space)¶
- early return when s is empty
- pattern_len = min_len => can be done without sys
- get rid of saving patten
def longest_prefix(s):
if not s:
return ""
min_len = min(len(word) for word in s)
for word in s:
count =0
for i in range(min_len):
if word[i] != s[0][i]:
break
count +=1
min_len = min(min_len,count)
return s[0][:min_len]
print(longest_prefix(["flower", "flow", "flight"]))
print(longest_prefix(["apple", "banana", "grape", "mango"]))
fl
Complexity¶
O(WORD_COUNT x MIN_LEN) : WORD_COUNT = len(s) and MIN_LEN = min(word)
O(1)
Isomorphic String¶
Given two strings s and t, determine if they are isomorphic. Two strings s and t are isomorphic if the characters in s can be replaced to get t. All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character, but a character may map to itself.
Input: s = "paper", t = "title"
Output: true
Explanation: The characters in "s" can be mapped one-to-one to characters in "t":
'p' → 't', 'a' → 'i', 'e' → 'l', 'r' → 'e'
Since the mapping is consistent and unique for each character, the strings are isomorphic.
Input: s = "foo", t = "bar"
Output: false
Explanation: 'f' → 'b' is fine, 'o' → 'a' for the first 'o', But the second 'o' in "s" would need to map to 'r' in "t", which conflicts with the earlier mapping of 'o' → 'a'
This inconsistency makes it impossible to convert "s" to "t" using a one-to-one character mapping.
Approach 1 (Two ptr with map)¶
- length of s and t must be same
- create 2 map , s-> t and t->s
- check bidirectional maps
def is_isomorphic(s, t):
if len(s) != len(t):
return False
map1 = {}
map2 = {}
for i in range(len(s)):
ch1 = s[i]
ch2 = t[i]
if ch1 in map1 and map1[ch1] != ch2:
return False
if ch2 in map2 and map2[ch2] != ch1:
return False
map1[ch1] = ch2
map2[ch2] = ch1
return True
# Test cases
print(is_isomorphic("paper", "title")) # True
print(is_isomorphic("foo", "bar")) # False
True False
Complexity¶
O(N) : N = len(s) = len(t)
O(N) : N = len(s) = len(t)