Two Pointers Questions Part 1 =============================== 138. (LC 167) Two Sum II - Input Array Is Sorted ---------------------------------------------------- `167. Two Sum II - Input Array Is Sorted `_ Medium **Key:** If you are given a sorted array, consider moving from both ends toward the center. **Solution 1** [:ref:`10 `] O(n) :: class Solution: def twoSum(self, numbers: List[int], target: int) -> List[int]: l, r = 0, len(numbers) - 1 while l < r: curSum = numbers[l] + numbers[r] if curSum > target: r -= 1 elif curSum < target: l += 1 else: return [l + 1, r + 1] **Explained:** :: if curSum > target: r -= 1 | How do we know to decrease the right pointer. | Consider our sorted a=[2,7,11,15], t=9 | 2+15=17 >9 | So we know we need a smaller sum. | If we moved lp, 7+15 we would only be making the sum greater. | So we must move the right pointer. :: ### My V (LC accepted 70, 80) class Solution: def twoSum(self, numbers: List[int], target: int) -> List[int]: p1 = 0 p2 = len(numbers)-1 while p1 < p2: sum_ = numbers[p1] + numbers[p2] if sum_ == target: return [p1+1, p2+1] elif sum_ > target: p2 -= 1 else: p1 +=1 139. (LC 42) Trapping Rain Water ------------------------------------ `42. Trapping Rain Water `_ Hard | 2 approaches. | Both are O(n) time. | One is O(n) space, optimization with pointers is O(1) space. | | Keys (O(n) space) | -find running max for right side | -find running max for left side (while calculating water trapped) | Water trapped = min(current_left_max, current_right_max) - current_height | -If water trapped > 0, then add to ans. | | # The one with O(n) space, linear traversal. | We traverse the given array height=[0,1,0,2,1,0,1,3,2,1,2,1] .. admonition:: The formula for trapped water is: min(maxLeft, maxRight) - height[i] | So we calculate for each index, including here at index 1, h=[1,2,3] | min(mL,mR) - h[i] = min(1,3)-h[1] = 1-2=-1 (no water trapped) | | Calculating maxL, maxR for a current index gives us the closest max left and max right walls. | E.g. :: # Illustration # height: 0,1,0,2,1,0,1,3,2,1,2,1 # maxLeft: 0 0 1 1 2 2 2 2 3 3 3 3 --> # maxRight: 3 3 3 3 3 3 3 2 2 2 1 0 <-- # min(mL,mR) 0 0 1 1 2 2 2 2 2 2 1 0 ^V # trapped w: 0 0 1 0 1 2 1 0 0 1 0 0 (some zeros are actually -N, but we make sure we make it 0) :: ### My V O(N) (LC accepted 25, 70%) class Solution: def trap(self, height: List[int]) -> int: # Calculating running max for values at the right side max_right = [] max_v = height[-1] for i in reversed(range(len(height))): max_v = max(max_v, height[i]) max_right.append(max_v) max_right = max_right[::-1] #Calculating running max for left side. At the same time as calculating water trapped. max_left_v = height[0] ans=0 for j in range(len(height)): max_left_v = max(max_left_v, height[j]) min_v = min(max_left_v, max_right[j]) water = min_v - height[j] if water > 0: ans += water return ans | **Logic when using pointers** | -Initiate pointers. | -Initiate leftMax, rightMax | Iteration: | -Compare leftMax and rightMax, we will be moving that pointer, which max is smaller. | (If they have the same value, we move whichever.) | height=[0,1,0,2,1,0,1,3,2,1,2,1] | L R | leftMax=0, rightMax=1 | mL int: if not height: return 0 l, r = 0, len(height) - 1 leftMax, rightMax = height[l], height[r] res = 0 while l < r: if leftMax < rightMax: l += 1 leftMax = max(leftMax, height[l]) res += leftMax - height[l] else: r -= 1 rightMax = max(rightMax, height[r]) res += rightMax - height[r] return res ### My V3 def trap_water(a): lp = 0 rp = len(a) - 1 maxL = a[lp] maxR = a[rp] water = 0 while lp < rp: if maxL < maxR: #From this we already know we're dealing with the result of minH=min(maxL, MaxR) lp += 1 maxL = max(a[lp], maxL) water += maxL - a[lp] else: rp -= 1 maxR = max(a[rp], maxR) water += maxR - a[rp] return water 140. (LC 259) 3Sum Smaller ----------------------------- `259. 3Sum Smaller `_ (Locked content) Given an array of n integers nums and an integer target, find the number of index triplets i, j, k with 0 <= i < j < k < n that satisfy the condition nums[i] + nums[j] + nums[k] < target. | Example 1: | Input: nums = [-2,0,1,3], target = 2 | Output: 2 | Explanation: Because there are two triplets which sums are less than 2: | [-2,0,1] | [-2,0,3] **Keys:** If we need 3 pointers, make the third P the index in array. | -Sort the input array. | -initiate pointers within the loop len(a) | leftP=i+1 | So e.g.: :: # Illustration # [-2,0,1,3] # i L R | So L is actually our middle pointer, i index is our leftmost pointer. | -while L=t | otherwise, ATTENTION add to answer rp-lp, and lp+=1 AFTER that | (by rp-lp we add all combinations between rp and lp) | -move on to the next i, assign L, R anew: :: # [-2,0,1,3] # i L R :: ### Solution def f(a, t) -> int: a.sort() ans = 0 for i in range(len(a)): lp = i + 1 rp = len(a) - 1 while lp < rp: s = a[i] + a[lp] + a[rp] if s >= target: rp -= 1 else: ans += rp - lp #<=== lp += 1 return ans nums = [-2, 0, 1, 3] target = 2 print(f(nums, target)) #2 nums2 = [0, 1] target2 = 0 print(f(nums2, target2)) #0 141. (LC 844) Backspace String Compare ----------------------------------------- `844. Backspace String Compare `_ Easy | Solution ideas: | -Initiate 2 pointers, both at the end of each string. | -Initiate vars skip1, skip2 to keep track of the encountered backspace #. | -Main while loop while i >= 0 or j >= 0 | -inner 2 while loops for each pointer 'while pointer >=0', check for #, use break | -at the end of the main while loop check if one of the pointers reached the end of string. :: class Solution: def backspaceCompare(self, s: str, t: str) -> bool: i, j, skip1, skip2 = len(s) - 1, len(t) - 1, 0, 0 while i >= 0 or j >= 0: while i >= 0: if s[i] == '#': skip1 += 1 i -= 1 #<==1 elif skip1: skip1 -= 1 i -= 1 #<==2 else: break while j >= 0: if t[j] == '#': skip2 += 1 j -= 1 elif skip2: skip2 -= 1 j -= 1 else: break if i >= 0 and j >= 0: #AND if s[i] != t[j]: return False elif i >= 0 or j >= 0: #OR return False i, j = i - 1, j - 1 return True **My V easy**, using space (LC accepted 5, 70). Using stack over string does not improve according to LC. :: # STRING SLICING class Solution: def backspaceCompare(self, s: str, t: str) -> bool: new_s = "" new_t = "" for i in range(len(s)): if s[i] != '#': new_s += s[i] else: if len(new_s) >= 1: new_s = new_s[:-1] for j in range(len(t)): if t[j] != '#': new_t += t[j] else: if len(new_t) >= 1: new_t = new_t[:-1] return new_s == new_t # STACK class Solution: def backspaceCompare(self, s: str, t: str) -> bool: def backspace(s): stack = [] for i in range(len(s)): if s[i] != '#': stack.append(s[i]) else: if stack: stack.pop() return stack return backspace(s) == backspace(t) | **My V difficult** (LC accepted 87, 35; 22,35; 5, 95) | DESIGN (TWO POINTERS AND STACK): | -Use a helper function that uses STACK. Call this function when value at p is '#'. | Function will move the pointer till it passes all accumulated '#'. | Including cases like "bxo#j##tw". | -Main solution: | /account when one of the pointers ran off its string | /when at both pointers '#' | /when at one or both pointers '#' :: class Solution: def backspaceCompare(self, s: str, t: str) -> bool: # HELPER THAT USES STACK def backspace(_str, p): stk = ['#'] p-=1 while p >= 0 and stk or _str[p] == '#': if _str[p] == '#': stk.append('#') p-=1 else: stk.pop() p-=1 return p ps, pt = len(s) - 1, len(t) - 1 while ps >= 0 or pt >= 0: # ONE OF THE POINTERS RAN OFF THE STRING if ps < 0: if t[pt] == '#': pt = backspace(t, pt) return ps < 0 and pt < 0 elif pt < 0: if s[ps] == '#': ps = backspace(s, ps) return ps < 0 and pt < 0 # BOTH POINTERS ARE NOT AT '#' elif s[ps] != '#' and t[pt] != '#': if s[ps] != t[pt]: return False else: #values at pointers are the same, then move on ps -= 1 pt -= 1 # ONE OR BOTH POINTERS ARE AT '#' else: if s[ps] == '#': ps = backspace(s, ps) if t[pt] == '#': pt = backspace(t, pt) return ps < 0 and pt < 0 C++ ^^^^^^ .. code-block:: cpp //My V1 (LC accepted 100, 7) //Create new strings, iterate forward. Use helper to create each new string. class Solution { string build_new_string(string str){ string newS; for(auto b = str.begin(); b != str.end(); ++b){ if(*b != '#'){ newS += *b; } else { if(!newS.empty()){ newS = string(newS.begin(), newS.end() - 1); } } } return newS; } public: bool backspaceCompare(string s, string t) { return build_new_string(s) == build_new_string(t); } }; //My V2 (LC accepted 100, 12-19) //Create 2 stacks, iterate forward. Use helper to create each stack. class Solution { stack remove_backspace(string str){ stack stk; for(auto b = str.begin(); b != str.end(); ++b){ if(*b != '#') stk.push(*b); else { if(!stk.empty()) stk.pop(); } } return stk; } public: bool backspaceCompare(string s, string t) { return remove_backspace(s) == remove_backspace(t); } }; 142. (LC 977) Squares of a Sorted Array --------------------------------------------- `977. Squares of a Sorted Array `_ Easy | [:ref:`10 `] | # Time: O(n), one pass using two pointers. | # Space: O(1), output array is not considered for space complexity. | Idea: | Recognize that sorted array like [-4, -1, 0, 3, 10], squares are [16,1,0,9,100]. | An array like that will have greatest values at its far ends. | ===> We can iterate with two pointers from the far ends of the squared array. | Check which value at the pointer is greater, put that value into a new 'ans' array | in reverse order, e.g. ans=[0,0,0,0,100]. Or put from front and reverse when giving the final answer. :: ### Solution V1 + my V def f(a): lp, rp = 0, len(a) - 1 ans = [] a = [x**2 for x in a] while lp <= rp: if a[rp] > a[lp]: ans.append(a[rp]) rp -= 1 else: ans.append(a[lp]) lp += 1 return ans[::-1] ### My V def f(a): lp, rp = 0, len(a) - 1 ans = [0] * len(a) a = [x**2 for x in a] i = 1 while lp <= rp: if a[rp] > a[lp]: ans[-i] = a[rp] rp -= 1 else: ans[-i] = a[lp] lp += 1 i += 1 return ans a = [-4, -1, 0, 3, 10] print(f(a)) #[0, 1, 9, 16, 100] ### Solution V2 class Solution: def sortedSquares(self, nums: List[int]) -> List[int]: n = len(nums) res = [0] * n l, r = 0, n - 1 while l <= r: left, right = abs(nums[l]), abs(nums[r]) if left > right: res[r - l] = left * left l += 1 else: res[r - l] = right * right r -= 1 return res **My solution.** If you forgot to traverse from opposite ends. (LC accepted: 72, 78%) (Traversing from left if all positives, from right if all negatives, from somewhere in the middle if both pos and neg). :: class Solution: def sortedSquares(self, nums: List[int]) -> List[int]: if len(nums) == 1: return [nums[0] ** 2] if nums[0] >= 0 and nums[len(nums) - 1] >= 0: lp, rp = -1, 0 elif nums[0] < 0 and nums[len(nums) - 1] < 0: lp, rp = len(nums) - 1, len(nums) else: for i in range(1, len(nums)): if nums[i] >= 0: lp = i - 1 rp = i break ans = [] while lp >= 0 or rp < len(nums): if rp >= len(nums): res = nums[lp] ** 2 lp -= 1 elif lp < 0: res = nums[rp] ** 2 rp += 1 elif abs(nums[lp]) < nums[rp]: res = nums[lp] ** 2 lp -= 1 else: res = nums[rp] ** 2 rp += 1 ans.append(res) return ans 143. (LC 283) Move Zeroes ---------------------------- `283. Move Zeroes `_ Easy | My V: | -I first look for a zero pointer, i.e. where is the first zero in nums array. | ([0,1,2], [1,0,2] etc.) | -Then start the iteration in the main loop from the found . :: ### My V (LC accepted 80, 6) class Solution: def moveZeroes(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ if len(nums) == 1: return nums # search for zero pointer zp = -1 for i in range(len(nums)): if nums[i] == 0: zp = i break #we didn't find a zero pointer (all nums are nonzero) if zp == -1: return nums # move zeros for p in range(zp+1, len(nums)): if nums[p] !=0: nums[zp], nums[p] = nums[p], nums[zp] zp +=1 | Idea: | -at p1 we keep track of values 0. | -loop that moves p2+=1 | -if value at p2 !=0 and at p1 value is ==0, we swap. | -if at p1 value !=0, we move p1+=1 :: ### V1 def f(a): p1 = 0 for p2 in range(len(a)): if a[p2] != 0 and a[p1] == 0: a[p1], a[p2] = a[p2], a[p1] if a[p1] != 0: p1 += 1 return a ### V2 def f(a): zi = 0 for i in range(len(a)): if a[i] != 0: a[zi] = a[i] if zi != i: a[i] = 0 zi += 1 return a nums = [0, 1, 0, 3, 12] print(f(nums)) nums2 = [0] print(f(nums2)) nums3 = [4, 1, 0, 0, 3, 12, 0] print(f(nums3)) [1, 3, 12, 0, 0] [0] [4, 1, 3, 12, 0, 0, 0] 144. (LC 392) Is Subsequence ------------------------------ `392. Is Subsequence `_ Easy :: ### Solution 1 class Solution: def isSubsequence(self, s: str, t: str) -> bool: i, j = 0, 0 while i < len(s) and j < len(t): if s[i] == t[j]: i += 1 j += 1 return i == len(s) ### My V (LC accepted 90,50%) class Solution: def isSubsequence(self, s: str, t: str) -> bool: if (len(t)==0 and len(s)==0) or (len(s)==0): return True if len(t)==0: return False pt=ps=0 for pt in range(len(t)): if t[pt]==s[ps]: ps+=1 if ps == len(s): return True return False 145. (LC 713) Subarray Product Less Than K -------------------------------------------- `713. Subarray Product Less Than K `_ Medium :: ### Solution def f(a, k): lp = 0 ans = 0 prod = 1 for rp in range(len(a)): prod *= a[rp] while prod >= k and lp <= rp: #* prod /= a[lp] lp += 1 ans += rp - lp + 1 return ans nums = [10, 5, 2, 6] k = 100 print(f(nums, k)) #8 # [10], [5], [2], [6], [10, 5], [5, 2], [2, 6], [5, 2, 6] #* My edit lp<=rp not just <, when e.g. nums=[200,10], i.e. we shouldn't count the first num. | Logic: | We start Lp and Rp at index 0. | Move Rp in the main loop. | Note: we go into the while loop only when prod >=k. How it looks:: # [10, 5, 2, 6] # L,R 10