Lecture -2 Medium String Problems¶
Sort characters by frequency¶
You are given a string s. Return the array of unique characters, sorted by highest to lowest occurring characters. If two or more characters have same frequency then arrange them in alphabetic order.
Input: s = "tree"
Output: ['e', 'r', 't']
Input: s = "raaaajj"
Output: ['a', 'j', 'r']
Approach 1 (Brute Force)¶
- create a freq count of chars as an array
- use sort via custom comparator
while initlization , freq_count = [["",0] for _ in range(62)] instead of freq_count = [["",0]] * 62 (to access each element independenly)
def sort_chars(s):
MAX_CHAR_COUNT = 26 + 26 + 10
freq_count = [("",0) for _ in range(MAX_CHAR_COUNT)]
for ch in s:
# 0 ... 25
if ch.isupper():
idx = ord(ch) - ord("A")
# 26 ... 51
elif ch.islower():
idx = ord(ch) - ord("a") + 26
# 52 .. 61
else:
idx = ord(ch) - ord("0") + 52
_ , count = freq_count[idx]
freq_count[idx] = (ch,count +1)
freq_count.sort(key = lambda x:(-x[1],x[0]))
return "".join([x[0] for x in freq_count])
print(sort_chars("tree"))
print(sort_chars("raaaajj"))
print(sort_chars("Aabb"))
ert ajr bAa
Complexity¶
- L = len(s)
- O(L)
- O(1)
Approach 2 (Char Count Hashmap instead of array) {RECOMMENDED}¶
def sort_chars(s):
freq_count = {}
for ch in s:
freq_count[ch] = freq_count.get(ch,0) + 1
sorted_chars = sorted(freq_count.items(),key=lambda x:(-x[1],x[0]))
return "".join([x[0] for x in sorted_chars])
print(sort_chars("tree"))
print(sort_chars("raaaajj"))
print(sort_chars("Aabb"))
ert ajr bAa
Approach 3 (Max Heap)¶
import heapq
def sort_chars(s):
max_heap = []
freq_count = {}
for ch in s:
freq_count[ch] = freq_count.get(ch,0) + 1
for ch,count in freq_count.items():
heapq.heappush(max_heap,(-count,ch))
result = []
while max_heap:
result.append(heapq.heappop(max_heap)[1])
return "".join(result)
print(sort_chars("tree"))
print(sort_chars("raaaajj"))
print(sort_chars("Aabb"))
ert ajr bAa
Complexity¶
- L = len(s)
- O(L)
- O(1)
Maximum Nesting Depth of Parenthesis¶
Given a valid parentheses string s, return the nesting depth of s. The nesting depth is the maximum number of nested parentheses.
Input: s = "(1+(2*3)+((8)/4))+1"
Output: 3
Input: s = "(1)+((2))+(((3)))"
Output: 3
Appraoch 1 (Use levels)¶
def max_depth(s):
level =0
result =0
for ch in s :
if ch == "(":
level +=1
result = max(result,level)
elif ch == ")":
level -=1
return result
print(max_depth("(1+(2*3)+((8)/4))+1"))
print(max_depth("(1)+((2))+(((3)))"))
3 3
Complexity¶
O(N) : N = len(s)
O(1)
Roman Numerals to Integer¶
Roman numerals are represented by seven different symbols: I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000 For example: 2 is written as II, 12 is written as XII, 27 is written as XXVII.
Roman numerals are usually written largest to smallest from left to right. But in six special cases, subtraction is used instead of addition: I before V or X → 4 and 9, X before L or C → 40 and 90, C before D or M → 400 and 900 Given a Roman numeral, convert it to an integer.
Example 1:
Input: s = "LVIII"
Output: 58
Explanation: L = 50, V= 5, III = 3.
Example 2:
Input: s = "MCMXCIV"
Output: 1994
Explanation: M = 1000, CM = 900, XC = 90 and IV = 4.
Approach1 (Brute Force)¶
- First we will build a map for each roman symbol to int value
- Now for handling the subtractve case
- If the next char is greater in value then current char , we need to subtract it
- other wise add it
def roman_to_int(s):
value_map = {
"I" : 1,
"V" : 5,
"X":10,
"L":50,
"C":100,
"D":500,
"M":1000
}
n = len(s)
result =0
for i in range(n):
if i+1 < n and value_map[s[i]] < value_map[s[i+1]]:
result -= value_map[s[i]]
else:
result +=value_map[s[i]]
return result
print(roman_to_int("LVIII"))
print(roman_to_int("MCMXCIV"))
print(roman_to_int("III"))
print(roman_to_int("IV"))
58 1994 3 4
Complexity¶
O(N) : N = len(s)
O(1)
Rearrange Array Elements by Sign¶
There’s an array ‘A’ of size ‘N’ with an equal number of positive and negative elements. Without altering the relative order of positive and negative elements, you must return an array of alternately positive and negative values.
arr[] = {1,2,-4,-5}, N = 4
[1,-4,2,-5]
arr[] = {3,1,-2,-5,2,-4}, N = 6
[3,-2,1,-5,2,-4]
Approach 1 (Brute Force)¶
- Build positive and negative numbers
- Build the result
def rearrange_list(nums):
result = []
positive_nums = []
for num in nums:
if num > 0 :
positive_nums.append(num)
negative_nums = []
for num in nums:
if num < 0 :
negative_nums.append(num)
for i in range(len(nums)//2):
result.append(positive_nums[i])
result.append(negative_nums[i])
return result
print(rearrange_list([1,2,-4,-5]))
print(rearrange_list([3,1,-2,-5,2,-4]))
[1, -4, 2, -5] [3, -2, 1, -5, 2, -4]
Complexity¶
- N = len(nums)
- O(N)
- O(N)
Approach 2 (Without extra space)¶
- We will make use of write positive pos and write negative pos
- We will increment these pos by 2 every time we find one
def rearrange_list(nums):
n = len(nums)
result = [0] * n
positive_write_pos = 0
negative_write_pos = 1
for num in nums:
if num > 0:
result[positive_write_pos] = num
positive_write_pos +=2
else:
result[negative_write_pos] = num
negative_write_pos +=2
return result
print(rearrange_list([1,2,-4,-5]))
print(rearrange_list([3,1,-2,-5,2,-4]))
[1, -4, 2, -5] [3, -2, 1, -5, 2, -4]
Complexity¶
- N = len(nums)
- O(N)
- O(1)
next_permutation : find next lexicographically greater permutation¶
Given an array Arr[] of integers, rearrange the numbers of the given array into the lexicographically next greater permutation of numbers. If such an arrangement is not possible, it must rearrange to the lowest possible order (i.e., sorted in ascending order).
Input: Arr[] = {1,3,2}
Output: {2,1,3}
Explanation: All permutations of {1,2,3} are {{1,2,3} , {1,3,2}, {2,1,3} , {2,3,1} , {3,1,2} , {3,2,1}}. So, the next permutation just after {1,3,2} is {2,1,3}.
Input : Arr[] = {3,2,1}
Output: {1,2,3}
Explanation : As we see all permutations of {1,2,3}, we find {3,2,1} at the last position. So, we have to return the lowest permutation.
Approach 1 (Brute Force)¶
- TODO find all the possible permutations
Approach 2 (Three Step Strategy)¶
- Step 1: Find the pivot: scan the elemnts from right to left and find element which is smaller than right
- Step 2: Find the swapped element : scan the elemnts from right to left and find the elemnt which is greater than pivot
- Step 3: Reverse the elements right of pivot after swapping
def next_permutation(nums):
n = len(nums)
if n ==1:
return nums
def swap(i,j):
nums[i],nums[j] = nums[j],nums[i]
def reverse(start , end):
while start < end:
swap(start,end)
start +=1
end -=1
right = n-2
pivot = -1
while right >=0:
if nums[right] < nums[right+1]:
pivot = right
break
right -=1
if pivot == -1:
reverse(0,n-1)
return nums
right = n-1
swap_idx = right
while right >=0:
if nums[right] > nums[pivot]:
swap_idx = right
break
right -=1
swap(pivot,swap_idx)
reverse(pivot +1,n-1)
return nums
print(next_permutation([1,3,2]))
print(next_permutation([3,2,1]))
[2, 1, 3] [1, 2, 3]
Complexity¶
- N= len(nums)
- O(N)
- O(1)