Strings Questions Part 3 ========================= 126. (LC 67) Add Binary ------------------------- `67. Add Binary `_ Easy :: # My V def f(a, b): res = int(a, 2) + int(b, 2) # return bin(res) return bin(res)[2:] # Solution class Solution: def addBinary(self, a: str, b: str) -> str: return bin(int(a, 2) + int(b, 2))[2:] >>> int('1010', 2) 10 >>> int('1011', 2) 11 >>> bin(21) '0b10101' :: # V2 def f(a, b): a = int(a, 2) b = int(b, 2) return bin(a + b)[2:] a = "11" b = "1" print(f(a, b)) # 100 a = "1010" b = "1011" print(f(a, b)) # 10101 127. (LC 415) Add Strings ----------------------------- `415. Add Strings `_ Easy | Keys: | -String slicing :: ### Solution 1 (LC 85, 30) class Solution(object): def addStrings(self, num1, num2): result = [] carry = 0 while num1 or num2 or carry: # takes care of nums being of different len digit = carry if num1: digit += int(num1[-1]) num1 = num1[:-1] if num2: digit += int(num2[-1]) num2 = num2[:-1] carry = digit > 9 # True also means 1, and its not like carry can be >1, but alt. carry=digit//10 result.append(str(digit % 10)) return "".join(result[::-1]) ### My V (LC accepted 6-10, 80) class Solution: def addStrings(self, num1: str, num2: str) -> str: ans = '' carry = 0 while num1 or num2 or carry: n1 = int(num1[-1]) if num1 else 0 n2 = int(num2[-1]) if num2 else 0 res = n1 + n2 + carry carry = res//10 ans += str(res % 10) num1 = num1[:-1] num2 = num2[:-1] return ans[::-1] ### My attempt (LC accepted 30,60%) (a lot of off by 1 gotchas) def add_strings(s1, s2): if len(s2) > len(s1): s1, s2 = s2, s1 ans = [0] * (len(s1) + 1) for i in range(1, len(s1) + 1): res = ans[i - 1] + int(s1[-i]) if i <= len(s2): res += int(s2[-i]) carry = res // 10 ans[i] = carry ans[i - 1] = res % 10 if ans[-1] == 0: ans = ans[:-1] ans = ans[::-1] ans = [str(x) for x in ans] return "".join(ans) 128. (LC 58) Length of Last Word ------------------------------------ `58. Length of Last Word `_ Easy | Task gotcha: | s = " fly me to the moon " | Has 2 spaced after moon. **Python3** :: # A shortcut (LC 55,85%) def f2(s): return len(s.split()[-1]) # LC accepted (33,84%) class Solution: def lengthOfLastWord(self, s: str) -> int: a = s.split() return len(a[-1]) ### Solution 1 (neetcode) def lengthOfLastWord(s: str) -> int: c = 0 for i in s[::-1]: if i == " ": if c >= 1: return c else: c += 1 return c ### Solution 1 v2 def f1(s): cnt = 0 for i in range(len(s) - 1, -1, -1): #OR for i in s[::-1]: if s[i] == " ": if cnt >= 1: return cnt else: cnt += 1 return cnt ### My V class Solution: def lengthOfLastWord(self, s: str) -> int: c = 0 for char in s[::-1]: if char != " ": c += 1 else: # char is a space if c > 0: return c return c ### My V2 (LC accepted 7,12) class Solution: def lengthOfLastWord(self, s: str) -> int: cnt = 0 for i in reversed(range(len(s))): if cnt==0 and s[i] == ' ': continue else: if s[i] == ' ': break cnt +=1 return cnt | **C++** | -STL [:ref:`14 `] .. code-block:: cpp #include //(LC accepted 58, 10) class Solution { public: int lengthOfLastWord(string s) { stringstream ss(s); string word; while(ss >> word){} return word.length(); } }; -Subscripting [:ref:`14 `] .. code-block:: cpp //(LC accepted 100, 10) class Solution { public: int lengthOfLastWord(string s) { int n = s.length(), result = 0; while(n > 0){ if(s[--n] != ' ') result++; else if(result > 0) return result; } return result; } }; -Iterators .. code-block:: cpp //My V (LC accepted 45, 10; 100, 10) class Solution { public: int lengthOfLastWord(string s) { int cnt{0}; for (auto it = s.end()-1; it!=s.begin()-1; --it){ //start not past end; begin-1 because if len(s)=1 if(cnt == 0 && *it == ' ') continue; else { if (*it == ' ') break; ++cnt; } } return cnt; } }; 129. (LC 14) Longest Common Prefix ------------------------------------- `14. Longest Common Prefix `_ Easy :: ### My V2 (LC accepted 17, 45%) class Solution: def longestCommonPrefix(self, strs: List[str]) -> str: pref = strs[0] for w in strs: if w == '': return w pointer = 0 while pointer < len(w) and pointer < len(pref): if pref[pointer] != w[pointer]: pref = pref[:pointer] if pref == '': return pref break pointer += 1 pref = pref[:pointer] return pref ### My V1 (LC accepted 13, 99%) def f(a): common = a[0] for s in a[1:]: for i in range(len(common) + 1): if common[:i] != s[:i]: break else: cur_common = common[:i] common = cur_common return common 130. (LC 43) Multiply Strings ---------------------------------- `43. Multiply Strings `_ Medium | **Keys** | -len(x*y) is at max len(x) + len(y) | -whether carry or not carry, always ans[index] += calculation, not ans[index] = calculation | -take care to index correctly int(num1[j]) * int(num2[i]) :: ### My V (LC accepted 28,18) class Solution: def multiply(self, num1: str, num2: str) -> str: if num1=='0' or num2=='0': return '0' ans = [0] * (len(num1) + len(num2)) for i in reversed(range(len(num2))): for j in reversed(range(len(num1))): ind = i+j+1 calc = ans[ind] + (int(num1[j]) * int(num2[i])) ans[ind] = calc%10 ans[ind-1] += calc//10 ans = [str(n) for n in ans] ans = ''.join(ans) return ans if ans[0] != '0' else ans[1:] class Solution: def multiply(self, num1: str, num2: str) -> str: if "0" in [num1, num2]: return "0" res = [0] * (len(num1) + len(num2)) num1, num2 = num1[::-1], num2[::-1] for i1 in range(len(num1)): for i2 in range(len(num2)): digit = int(num1[i1]) * int(num2[i2]) res[i1 + i2] += digit res[i1 + i2 + 1] += res[i1 + i2] // 10 res[i1 + i2] = res[i1 + i2] % 10 # Filter out prepended 0s res, beg = res[::-1], 0 while beg < len(res) and res[beg] == 0: beg += 1 res = map(str, res[beg:]) return "".join(res) **C++** .. code-block:: cpp //My V (LC accepted 50,60) class Solution { public: string multiply(string num1, string num2) { if (num1 == "0" || num2 == "0") return "0"; vector ans ((num1.size() + num2.size()), 0); for (size_t i = num2.size()-1; i != -1; --i){ for (size_t j = num1.size()-1; j!= -1; --j){ int ind = i+j+1; int calc = ans[ind] + ((num1[j] - '0') * (num2[i] - '0')); ans[ind] = calc % 10; ans[ind-1] += calc/10; } } string res; for(int n: ans){ res += '0' + n; } res = (res[0] == '0') ? string(res.begin()+1, res.end()) : res; //if prepped with 0, start at index 1 return res; } }; | **Notes on C++** | -C++ char/int conversions | Indexing a string gives you a char type. So we have to do char/int conversion. | //int from char | char a = '4'; | int ia = a - '0'; //ascii value of '4' - '0' = 4 | //char from int | int n {6}; | char c = '0' + n; | -Aka slicing res[1:] | ``res = string(res.begin()+1, res.end())`` | We assing a new string to res, and we make that new string from res itself but modified. 131. (LC 496) Next Greater Element I ----------------------------------------- `496. Next Greater Element I `_ Easy | Note, in output, you return values in array2, not indexes. | Attribution [:ref:`10 `]. | **Two approaches.** | **O(n*m)** | n and m our two arrays a1, a2. | ~O(N**2), space O(m) | O(n*m) because we do have a nested loop iterating a2. | **Key things** | 1)Hash map a1. | 2)Iterating over a2, we use hash of a1 in 2 ways: | - To see if n in a2 is at all in a1. | E,g, a1=[4,1,2], a2=[1,3,4,2], 3 is not in a1, so we can skeep it. | - To know where to put n on a2 in res. | E.g. 1 in a2, hash of a1 says that 1 is at index 1 in a1. | So having 3 from a2, we know to put it at index 1 in res. | res=[_,3,_] | 3)Initialize res=[-1]*len(a1) :: #Solution class Solution: def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: # O (n * m) nums1Idx = { n:i for i, n in enumerate(nums1) } res = [-1] * len(nums1) for i in range(len(nums2)): if nums2[i] not in nums1Idx: continue for j in range(i + 1, len(nums2)): #check values after a2[i+1] if nums2[j] > nums2[i]: idx = nums1Idx[nums2[i]] #get index of that N in a1 res[idx] = nums2[j] break return res ### My V (LC accepted 28, 85) class Solution: def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: d = {} for i in range(len(nums2)): d[nums2[i]] = -1 for j in range(i, len(nums2)): if nums2[j] > nums2[i]: d[nums2[i]] = nums2[j] break ans = [] for n in nums1: ans.append(d[n]) return ans | **O(n+m)** | **Keys:** | -Monotonic stack | -ans, initiate as [-1, -1, -1..] | -hash of nums1, {value: index} | -iterate nums2, if value < stack[-1], add to stack, if value is greater, then | that is the next greater value for all values in stack. | Input: nums1 = [4,1,2], nums2 = [1,3,4,2] | Output: [-1,3,-1] :: class Solution: def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: nums1Idx = { n:i for i, n in enumerate(nums1) } # {4:0, 1:1, 2:2} res = [-1] * len(nums1) stack = [] for i in range(len(nums2)): cur = nums2[i] # Mono decreasing stack. If we found in nums2 N that is greater than stack[-1] # than it is the next greater value for ALL values in stack # (and in stack we have all values that are in nums1) while stack and cur > stack[-1]: val = stack.pop() # take top val idx = nums1Idx[val] res[idx] = cur if cur in nums1Idx: #because we can have values in nums2 that are not in nums1 stack.append(cur) return res | **Explained** | Example. | a1=[4,1,2], a2=[2,1,3,4] | In general: | Notice that when we find answer 3, it is the answer for all nums before that, i.e. for 1,2. | 3>1, 3>2. | Walkthrough: | 1)stack=[] | a2=[2..] | If 2 in a1, we add 2 to stack. | stack=[2] | 2)a2=[2,1..] | Next up is 1. | Is 1 greater than any num in stack. No. Add 1 to stack. Move on. | 3) | stack=[2,1] <--Note, stack is always in decreasing order | a2=[2,1,3..] | Looking at 3. | 3 > stack[-1] | So 3 is the answer for all nums in stack. | Start popping from stack. | stack.pop()=1 | We find index of 1 in a1, put value 3 at that index | stack.pop()=2 .. | So: res = [_,3,3] | ->As last thing we check if 3 is in a1, only if it is, put 3 to the stack, otherwise no. 132. (LC 344) Reverse String --------------------------------- `344. Reverse String `_ Easy | Note: | Input: s = ["h","e","l","l","o"] | Input format is important, would need a different solution for string, not array input. | **Solution 1** (most efficient) | Key points: | -change in place | -pointers | -do not return anything :: def reverse_string(s): l = 0 r = len(s) - 1 while l < r: s[l],s[r] = s[r],s[l] l += 1 r -= 1 ### My V (LC accepted 98, 70) class Solution: def reverseString(self, s: List[str]) -> None: """ Do not return anything, modify s in-place instead. """ if len(s) <=1: return rp = len(s)-1 for lp in range(len(s)//2): if s[lp] != s[rp]: s[lp], s[rp] = s[rp], s[lp] rp -= 1 | **Solution 2** | Somewhat less efficient. But good to know if you will need to discuss alternatives. | Uses Stack. O(N). Extra space. | -We put all chars to stack. | -Pop from stack, each time replacing chars in array. | -Again, do not return, we're modifying in place. :: def f(a): stack = [] for c in a: stack.append(c) i = 0 while stack: a[i] = stack.pop() i += 1 s = ["h", "e", "l", "l", "o"] f(s) print(s) # ['o', 'l', 'l', 'e', 'h'] | **Solution 3. Recursion.** | Even less efficient. Time O(N), space O(N). :: class Solution: def reverseString(self, s: List[str]) -> None: def rev_str(l, r): if l < r: s[l], s[r] = s[r], s[l] rev_str(l + 1, r - 1) rev_str(0, len(s) - 1) sol = Solution() sol.reverseString(s) print(s) # ['o', 'l', 'l', 'e', 'h'] # Recursion my V def f(a, l=0, r=len(a) - 1): if l >= r: return a[l], a[r] = a[r], a[l] f(a, l + 1, r - 1) f(s) print(s) # ['o', 'l', 'l', 'e', 'h'] 133. (LC 929) Unique Email Addresses -------------------------------------- `929. Unique Email Addresses `_ Easy **Solution** [:ref:`10 `] :: class Solution: def numUniqueEmails(self, emails: list[str]) -> int: unique_emails: set[str] = set() for email in emails: local_name, domain_name = email.split('@') local_name = local_name.split('+')[0] local_name = local_name.replace('.', '') email = local_name + '@' + domain_name unique_emails.add(email) return len(unique_emails) ### My V1 (Iteration) (LC accepted 8, 72) class Solution: def numUniqueEmails(self, emails: List[str]) -> int: ans = set() for e in emails: s = '' i = 0 while i < len(e): if e[i] == '@': s += e[i:] i = len(e) elif e[i] == '.': i += 1 continue elif e[i] == '+': while e[i] != '@': i+=1 else: s += e[i] i += 1 ans.add(s) return len(ans) 134. (LC 680) Valid Palindrome II ----------------------------------- `680. Valid Palindrome II `_ Easy | **Solution** [:ref:`10, 7 `] | **Logic** | **V1** Having met s[L] != s[R], build 2 subarrays that skip left letter (s[l + 1 : r + 1] and s[l:r] that skips right letter. Reverse and see if after this one skip the rest of the array will be a valid palindrome. (+O(N) of space because we build subarrays.) :: ### V1 def f(s): l, r = 0, len(s) - 1 while l < r: if s[l] != s[r]: skipL, skipR = s[l + 1 : r + 1], s[l:r] # python of L+1->R, L->R-1 return skipL == skipL[::-1] or skipR == skipR[::-1] l, r = l + 1, r - 1 return True | **V2** | Helper function | (checks palindromicity, we give it string with L+1, or R-1 string.) | Having met chars at L, R that ar not equal. -> aaaz | We remove char on left, and see if the remaining string is a palindrome (with classic alg for that). | We remove char on the right, see if the remaining string is a palindrome. :: class Solution: def validPalindrome(self, s: str) -> bool: i, j = 0, len(s) - 1 while i < j: if s[i] != s[j]: return check(i, j - 1) or check(i + 1, j) i, j = i + 1, j - 1 return True def check(i, j): while i < j: if s[i] != s[j]: return False i, j = i + 1, j - 1 return True 135. (LC 953) Verifying an Alien Dictionary ---------------------------------------------- `953. Verifying an Alien Dictionary `_ Easy | **Solution** [:ref:`10 `] | **Keys** | -Use hash table. | -if 2nd word is prefix of first (e.g. wowe, wow), return false. | /Note that it also works for ["abba", "abc"]. i.e. shorter word after, but still order is correct. | /How to define prefix. When index j==len(w2). | Note, it is the index out of range for w2. (len(w2)=3, j=3, there is no index 3 in w2). | -But it is not just about the len. By the time we reached j==len(w2), we have established | that all chars before j in w1 and w2 are the same, because: :: if w1[j] != w2[j]: if orderInd[w2[j]] < orderInd[w1[j]]: return False break 1)False if different characters, 2)break out of the loop if not the same chars, but in correct order, so we never get to j==len(w2) in that case. :: def f(words, order: str) -> bool: # first differing char # if word A is prefix of word B, word B must be AFTER word A orderInd = {c: i for i, c in enumerate(order)} for i in range(len(words) - 1): w1, w2 = words[i], words[i + 1] for j in range(len(w1)): if j == len(w2): return False if w1[j] != w2[j]: if orderInd[w2[j]] < orderInd[w1[j]]: return False break return True ### My V1 def f(words, order): d = {} for index, letter in enumerate(order): d[letter] = index for i in range(len(words) - 1): lp = 0 while lp < len(words[i]): if lp == len(words[i + 1]) or d[words[i][lp]] > d[words[i + 1][lp]]: return False elif d[words[i][lp]] < d[words[i + 1][lp]]: break lp += 1 return True | Logic: | -put the alien alphabet into the hash (letter:index) | -do not overcomplicate. It does have a flavour of brute force. | Go through each word and compare it with the next one, then nex with the next next. | -keep track of word lens for the case ['abcd', 'ab'] | Iterate while len word1. | False if lp == len word2 (then it is ['abcd', 'ab'], which is wrong) | -break when word1 letter < word2 letter | -when word1 letter == word2 letter, continue, i.e. do nothing 136. (LC 6) Zigzag Conversion ----------------------------------- `6. Zigzag Conversion `_ Medium | **My V4** (LC accepted 60, 28) | -rows = [[], [], ..] | -Just increment/decrement row index where to inser char using direction. | Change ditrection when index==0 or last row. :: class Solution: def convert(self, s: str, numRows: int) -> str: if numRows == 1: return s; #will get index out of range without it rows = [[] for _ in range(numRows)] dir = -1 row_ind = 0 for c in s: rows[row_ind].append(c) if row_ind == 0 or row_ind == (numRows-1): dir *=(-1) row_ind += dir return ''.join(list(itertools.chain(*rows))) | **My V3** (LC accepted 90, 28) | Just two moves. (Initialize rows as [[], []..]) | -Filling in rows down. Normal loop in range(numRows). | -Filling in rows in diagonal upwards (diagonal size has constant dependence on numRows = numRows-2). :: class Solution: def convert(self, s: str, numRows: int) -> str: ans = [[] for _ in range(numRows)] #[[],[]..] dir = -1 #always left to right diagonal, so decreases index diag = numRows - 2 ind=0 #iterating the string while(ind != len(s)): for j in range(numRows): #filling in rows straight down if ind != len(s): ans[j].append(s[ind]) ind+=1 diagonal_index = numRows - 1 + dir #filling in rows up (or in diagonal) for i in range(diag): if ind != len(s): ans[diagonal_index].append(s[ind]) ind+=1 diagonal_index -=1 return ''.join(list(itertools.chain.from_iterable(ans))) #flatten list of lists | **My V1** (LC accepted 20, 10) | Main points. | -Use dict to store data for each row. | -Identify direction of the zigzag using %(totalrowNum-1). Logic:: # Visualize # r0 P I N # r1 A L S I G # r2 Y A H R # r3 P I | Note that we change direction of the zigzag when rowN=0 and rowN=maxRnum. | E.g. r=4. Note indices for rows are 0,1,2,3. | At 0 and 3 we have to change direction. 0%4-1==0, 3%4-1==0 | -Initialize dict R:[], where R is row number | -initialize index=0, direction=-1 | -Loop through string, check if index in srting%rows==0, if yes, change direction. | index+direction. (Will do +1 or -1 depending on the direction.) :: def f(s, r): if numRows == 1: #edge case, to avoid division by zero in %(r-1) return s d = {} for i in range(numRows): d[i] = [] index = 0 direction = -1 for j in range(len(s)): if index % (numRows - 1) == 0: direction *= -1 d[index].append(s[j]) index += direction # print(d) ans = [] for k in range(numRows): ans += d[k] return "".join(ans) s = "PAYPALISHIRING" numRows = 4 print(f(s, numRows)) # {0: ['P', 'I', 'N'], 1: ['A', 'L', 'S', 'I', 'G'], 2: ['Y', 'A', 'H', 'R'], 3: ['P', 'I']} # PINALSIGYAHRPI **My V2** (LC accepted 65, 10) (using not dict but indexing a nested array like [[], [], [], []]) :: def f(s, r): if numRows == 1: return s # Create nested array rows = [] for _ in range(numRows): rows.append([]) direction = -1 row = 0 for i in range(len(s)): if row % (numRows - 1) == 0: #NOTE (numRows-1) direction *= -1 rows[row].append(s[i]) row += direction # return rows #[['P', 'I', 'N'], ['A', 'L', 'S', 'I', 'G'], ['Y', 'A', 'H', 'R'], ['P', 'I']] ans = "" for line in rows: ans += "".join(line) return ans | **Solution** [:ref:`2 `] | (Here we don't use extra space like in the array/dict version.) | We use a different logic. | We will be jumping/skipping values in s. | For the first and last row it will be 6 jumps. (r-1)*2=(4-1)*2 | Middle rows: 4 and 2 jumps, so decreasing by 2 each time. Formula=[(r-1)*2 - 2*r] :: def f(s, numRows): if numRows == 1: return s res = "" for r in range(numRows): increment = (numRows - 1) * 2 # e.g.(4-1)*2 for i in range(r, len(s), increment): res += s[i] if (r > 0 and r < numRows - 1 and i + increment - 2 * r < len(s)): # also check if inbond res += s[i + increment - 2 * r] return res | **C++** | Initialize index of the row, direction of inserting into rows. | We change the direction when index row == 0 or last row. LC accepted (90, 60) [:ref:`7 `] .. code-block:: cpp class Solution { public: string convert(string s, int numRows) { if (numRows == 1) { return s; } vector rows(numRows); int row_index = 0, dir = -1; //index of the row, direction normal increasing or diagonal=decresing for (char c : s) { rows[row_index] += c; if (row_index == 0 || row_index == numRows - 1) { dir = -dir; } row_index += dir; } string ans; for (auto& string_item : rows) { ans += string_item; } return ans; } };