Interview prep summary
maxint = sys.maxsize
min = -sys.maxsize
The goal is 20 leetcode questions in 1 week. 15 easy 5 medium
-
Find the majority element in an array [https://leetcode.com/problems/majority-element/] So any number that appears more than n/2 times is the answer. We can just count how often each item happpens and return the first that is greater than n/2 times.
Appearing more than n/2 times means it always takes up more than half of the array.
Boyers voting algorithm of counting majority/minority items using +/-1
- For each element count its occurence and check if its > n/2.
O(n^2) O(1)
def majorityElement(self, nums): majority_count = len(nums)//2 for num in nums: count = sum(1 for elem in nums if elem == num) if count > majority_count: return num
- Sort and return item at n/2 location (also n/2 + 1 if n is even)
O(nlog(n)) O(1)
def majorityElement(self, nums): nums.sort() return nums[len(nums)//2]
- Use a hashmap to count and return element with largest count. You can avoid 2 loops by tracking the max while buidling the count_map.
O(n) O(n)
def majorityElement(self, nums): counts = collections.Counter(nums) return max(counts.keys(), key=counts.get) # major! the comparison is done on count.get(k) but the k is returned.
- Randomly select an item, check its count in the array. If its more than n/2 then return.
O(inf) but bounded still cause the item appears n/2 times. O(1)
def majorityElement(self, nums): majority_count = len(nums)//2 while True: candidate = random.choice(nums) if sum(1, for elem in nums if elem == candidate) > majority_count return candidate
- Boyer-Moore Voting algorithm.
Imagine assuming every element is the majority element at first. If it is, add 1 to a count if it is not add -1 to the count. You know it is not whenever the count reaches 0 so you start again by assuming the current item is the majority!
[7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7] count becomes 0 at evry |
We are unable to reach negative count becuase it is imposible to have more none majority items than majority items!
[7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 5, 5, 5, 5]
O(n) O(1)
def majorityElement(self, nums): count = 0 candidate = nums[0] for i in nums: if count == 0: candidate = i count += 1 if i==candidate else -1 return candidate
-
Valid anagram [https://leetcode.com/problems/valid-anagram/]
Consider strings s and t. An anagram is produced by rearranging the letters of s into t. s and t must be the same lenght and contain the exact same characters and frequencies.
- We can sort both string and compare them. They are anagarams if equal!
O(nlogn) O(n)
def isAnagram(self, s: str, t: str) -> bool: if len(s) != len(t): return False s = sorted(s) t = sorted(t) return t == s # string comparison is O(n). sorting time dominates so overall is O(nlogn). # using sorted() requires copying the array. This is implementation specific so could be O(1)
- We can maintain frequency count of the characters. We increment the freqnecy for s and decrement it. If we ever reach a frequency of -1 for any character they are not anagrams.
O(n) O(1)
def isAnagram(self, s: str, t: str) -> bool: if len(s) != len(t): return False count = [0 for i in range(26)] a = ord('a') # this is how we convert a to its ascii numeric representation. this way we dont need a hashmap for i in range(len(s)): count[ord(s[i])-a]+=1 count[ord(t[i])-a]-=1 for i in count: if i < 0: return False return True
- If the problem included non ascii characters, then we need an array of size 1114112. This is too large! we will need to use a hashmap so we only use memory for characters that exist in the string
O(n) O(n)
def isAnagram(self, s: str, t: str) -> bool: if len(s) != len(t): return False count_map = {} for i in range(len(s)): count_map[s[i]] = count_map.get(s[i], 0)+1 count_map[t[i]] = count_map.get(t[i], 0)-1 for i in count_map.values(): if i < 0: return False return True
-
Contains duplicate [https://leetcode.com/problems/contains-duplicate/]
With O(n^2) we can search for duplicate occurence of eahc item in the list.
- A better approach is to sort the list and then check neighboring elems.
O(nlogn), O(1)
a.sort() for i in range(len(a)-1): if a[i] == a[i+1]: return True return False
- The best approach is to use a set to store the items. This gives O(1) search time and O(1) insertion time. Unlike a self balancing tree which would be O(log n) for both operations.
O(n), O(n)
def containsDuplicate(self, nums: List[int]) -> bool: nums_set = set() for i in nums: if i in nums_set: return True nums_set.add(i) return False
-
Roman to integer [https://leetcode.com/problems/roman-to-integer/] We define a value map for roman numerals and then loop from behind adding up the values. If the current value is smaller than the previous then we subtract it form the total.
O(n)m O(1)
def romanToInt(self, s: str) -> int: value_map = { "I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000 } prev = value_map[s[-1]] total = prev for i in reversed(range(len(s)-1)): val = value_map[s[i]] if val < prev: total-=val else: total+=val prev = val return total
-
Best time to buy and sell stock II [https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/solution/] The key here is that we are allowed to buy and sell on the same day. This simplifies the solution alot The only restriction is that we wave to sell before buying again. We cannot buy or sell 2 times in a row.
Very simple!! The best approach is just to buy and sell whenever there is a profit! The freedom to buy and sell on the same day makes this possible. Skipping any possible profit will always lead to less total profit at the end.
O(n), O(1)
def maxProfit(self, prices: List[int]) -> int: max_profit = 0 if prices: prev = prices[0] for i in range(1, len(prices)): curr = prices[i] if prev < curr: max_profit += curr-prev prev = curr return max_profit
-
Convert sorted array to Binary Search Tree [https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/] For this problem, a height-balanced binary tree is defined as a binary tree in which the depth of the two subtrees of every node never differ by more than 1.
-
The recursize approach is quite simple and staright forward.
- Imagine 2 base cases. A list with 3 items, and a list with no item.
- A empty list should simply return None.
- A list with 3 items will have one parent and 2 children.
- Identifiy the middle index.
- Create a node with that index.
- Now we need to call the function again on the left and right halves to do the same thing and return its own mid node when done.
- Now, test the logic with a list with just 1 item. both left and right nodes will be set to None as expected
O(n), O(n) recursive stack of depth n
def sortedArrayToBST(sum, nums: List[int]) -> TreeNode: if not nums: return None left = 0 length = len(nums) mid = (left + length)//2 node = TreeNode(nums[mid]) node.left = self.sortedArrayToBST(nums[start:mid]) # nums[:mid] node.right = self.sortedArrayToBST(nums[mid+1:length]) # nums[mid+1:] return node
-
Iterative solution??
-
-
First unique character. [https://leetcode.com/problems/first-unique-character-in-a-string/]
Best approach is to build a count map in O(n) time and then loop though the string one more time checking for the first count of 1.
O(n), O(n)
def firstUniqChar(self, s: str) -> int: count_map = collections.Counter(s) for i in range(len(s)): if count_map[s[i]] == 1: return i return -1
-
Missing number [https://leetcode.com/problems/missing-number/] Pay attentin to what the description of this question. At first glance you may not notice some key details that will help in solving the problem using XOR
The array contains n distinct numbers. ie 10 distinct numbers. The numbers are take from 0, 1, 2, ...., n finf the one that is missing from the array.
let n be 5 so array wil be [0, 1, 2, 3, 5] or [0, 2, 3, 4, 5], or [1, 2, 4, 5, 3] (array may not be sorted)
-
The array is always of size n but it would be of size n+1 had the missing number be there.
-
The array should contain all the numbers from 0-n idealy. This would lead to a lenght of n+1! [0, 1, 2, 3, 4 ,5] but instead on is missing so [1, 2, 3, 4, 5] or [0, 1, 2, 3, 4] The question here is what is missing?
-
Pay attention to the index of all the numbers
Most obvious approahc is to sort and just check for the missing item. We know all the numbers that should have been there. 0-n!
O(n), O(1)
def missingNumber(self, nums: List[int]) -> int: nums.sort() count = 0 # start counting from 0 to n for i in nums: if i != count: return count count += 1 return count
Another nice approach is to creat a hashset of the numbers in the array. This makes it possible to search of items in constant time. Rather than searching the original array
O(n), O(n)
def missingNumber(self, nums: List[int]) -> int: nums_set = set(nums) count = 0 # start counting from 0 to n for i in range(len(nums+1)): if i != nums_set: return i
The most interesteing soluttion which is to use XOR. At first glance you might not see how. The trick is to use the damn indeces! say arr is [0, 1, 2, 3, 5]. The missing guy is 5. You might wonder how to use the whole exclusive idea ay first.
arr[0]: 0
arr[1]: 1
arr[2]: 2
arr[3]: 3
arr[4]: 5If you consider both the indeces and the values, all the numbers occur 2 times! err the missinf number and n do not!
But they do in the ideal array!arr[0]: 0
arr[1]: 1
arr[2]: 2
arr[3]: 3
arr[4]: 5
arr[5]: 4
So what we will do here is to include n ourselves! and then XOR the whole lot!
Including n ourselves is still fine, because if it was the missing guy, The one we inserted will be the only one. So it is stil exclusive. If it was not the missing guy, we have introduced it matching pair. The array never comes with it, so we need to introduce it for the solution to work. n is a missing index, and our answer is a missing value!!O(n), O(1)
def missingNumber(self, nums: List[int]) -> int: ans = len(nums) for i in range(len(nums)): ans = ans ^ i ^ nums[i] return ans
-
-
In order traversal successor in BST [https://leetcode.com/problems/inorder-successor-in-bst/submissions/] We want the Node after the target node when a binary tree is present inorder. This is technically the node immediately greater that the querry node.
Off the Bat we can just do in order traversal and store the result in a list then search for the target and return the node after it.
O(H), O(n), where H is the height of the tree
class Solution: result = [] def inorderSuccessor(self, root: 'TreeNode', p: 'TreeNode') -> 'TreeNode': self.inorder(root) for i in range(len(self.result)-1): if self.result[i] == p: return self.result[i+1] return None def inorder(self, root): if not root: return self.inorder(root.left) self.result.append(root) self.inorder(root.right)
We are actually able to do this without any extra space. We just need to notice the following.
- If the target node has a right subtree, the sucessor is the minimum element in that sub tree.
- Else, the successor is its most recent ancestor for which its a left child.
class Solution: result = [] def inorderSuccessor(self, root: 'TreeNode', p: 'TreeNode') -> 'TreeNode': last_left_anc = None while root: if p == root: if root.right: return self.minimum(root.right) else: return last_left_anc else: if p.val < root.val: last_left_anc = root root = root.left else: root = root.right def minimum(self, root): while root: if root.left: root = root.left else: break return root
We can do better tho. Notice that the successor is greater than the target so we can update the succ value whenever we encounter a value greater than target. We keep progressing towards the minimum value greater than the target.
O(H), O(1)
class Solution:
result = []
def inorderSuccessor(self, root: 'TreeNode', p: 'TreeNode') -> 'TreeNode':
succ = None
while root:
if p.val<root.val:
succ = root # update it anytime you go left because thats closer to p in inorder
root = root.left
else:
root = root.right
return succ
- Merge sorted linked list [https://leetcode.com/problems/merge-two-sorted-lists/] The idea is to create an new list with its initial parent as int minimum.. -1 if input lists are all +ves
Iterative approach. While BOTH lists still have items. Check which needs to be added to the new list. Add it and advance its pointer to the next item. Move the pointer of the new lists every time.
O(n+m), O(1)
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
head=ListNode(-sys.maxsize)
p = head
while l1 and l2:
if l1.val<l2.val:
p.next=l1
l1=l1.next
else:
p.next=l2
l2 = l2.next
p = p.next # p now points to last guy
p.next = l1 if l1 is not None else l2
return head.next
Recursive approach. Base case is when either lists is None, you just return the other.
The remaining logic is then The smaller of the two lists' heads plus the result of a merge on the rest of the elements.
Specifically, if either of l1 or l2 is initially null, there is no merge to perform, so we simply return the non-null list. Otherwise, we determine which of l1 and l2 has a smaller head, and recursively set the next value for that head to the next merge result. Given that both lists are null-terminated, the recursion will eventually terminate.
O(n+m), O(n+m)
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
if not l1:
return l2
if not l2:
return l1
if l1.val < l2.val:
l1.next = self.mergeTwoLists(l1.next, l2)
return l1
else:
l2.next = self.mergeTwoLists(l1, l2.next)
return l2
- Intersection between two aarays. [https://leetcode.com/problems/intersection-of-two-arrays-ii/]
Given 2 arrays return an array containing the intersection of the 2 arrays.
a = [1, 0, 0, 4, 2]
b= [4, 0, 0, 4]
result = [0, 0, 4]
First thing to do is sort the list. This should be one of the first things you consider with problems like this.
Sorting will reveal some clues.
a = [0, 0, 1, 2, 4]
b = [0, 0, 4, 4]
result = [0, 0, 4]
Nice, so we can use 2 pointers to loop though both arrays checking in both items at the indexes are qual.
We need to account for the difference in lengths.
O(n) O(1)
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1.sort()
nums2.sort()
res = []
p1, p2 = 0 , 0
while p1 < len(nums1) and p2 < len(nums2):
if nums1[p1] == nums2[p2]:
res.append(nums1[p1])
p1+=1
p2+=1
else:
if nums1[p1] < nums2[p2]:
p1+=1
else:
p2+=1
return res
We could also use a hash map to store the frequencies of each number in liat 1. Then we iterate through the 2nd list and insert all the element that are in the 2nd list and have a count above 0 in the map. We also decrement the count in the map for each match found. This prevents us from adddind duplicates wrongly.
O(n+m), O(n)
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
count_map = collections.Counter(nums1)
res = []
for i in nums2:
if count_map.get(i, 0) > 0:
res.append(i)
count_map[i] = count_map[i]-1
return res
- Best time to buy and sell stock. [https://leetcode.com/problems/best-time-to-buy-and-sell-stock/]
The objective is to figure out the single best time to buy and single best time to sell to maximize profit. Just a single transaction. Buy and sell
For these type of questions, try drawing the peaks and valleys.
Brute force approach is to have a nested loop. For each price, check the remaining of the array for the max possible profit.
O(n^2), O(1)
class Solution:
def maxProfit(self, prices: List[int]) -> int:
profit = 0
for i in range(len(prices)):
for j in range(i+1, len(prices)):
profit = max(profit, prices[j]-prices[i])
return profit
The optimal solution is to keep track for the min price seen so far. As you progress down the array you check to see if the current price will yielf to more profit than seen before. Remember to always keep track of the minimum price seen at the end of each iteration.
O(n), O(1)
class Solution:
def maxProfit(self, prices: List[int]) -> int:
profit = 0
mnp = sys.maxsize
for i in range(len(prices)):
profit = max(profit, prices[i] - mnp) # check
if prices[i] < mnp: # update
mnp = prices[i]
return profit
- Excel sheet column number [https://leetcode.com/problems/excel-sheet-column-number/]
A -> 1
B -> 2
C -> 3
Z -> 26
AA -> 27: 26+1
AB -> 28 (26)+(2)
AZ -> 52: (26) + (26)
BA -> 53: (262)+(1)
CC -> 81: (263)+(3)
ZY -> 701
ZZ -> 702
AAA -> 703: (1*(2626)) + (1(26)) + (1)
AAB -> 704
The approach here is to write out as many examples as possible to detect a pattern. The pattern here is simple.
A-Z map to 1-26 from left to right, the significance of the value increases For example AB A has a larger significance than B. Its Value is 26 and B is 2. 26+2 = 28 The pattern here is that the more significant digits have a higher exponent for 26. the least exponent is 0. which is for single characters. A -> (126^0), B -> (226^0)
So the component to figure out is the expoenent value. From right to left of the array, it starts at 0 and increases by one every time.
O(n) O(1)
class Solution:
def titleToNumber(self, s: str) -> int:
number = 0
n = len(s)
A = ord('A')
exp = 0
for i in range(n-1, -1, -1):
char = ord(s[i]) - A + 1 # because a maps to 1 not 0
number+= (char) * pow(26,exp)
exp+=1
return number
- Merge sorted arrays in place. [https://leetcode.com/problems/merge-sorted-array/submissions/]
The optimal solution takes advatage of the fact that they sorted to get O(n+m) runtime.
The fact that nums1 is large enough for both nums1 and nums2 data to fit in is major key. It means we can look at nums1[:m] as input1, and the nums1[:] as the result array. Now we can just fill nums1 with the data input1 and nums2 in sorted order. Makinf sure to handle over flows/ when one array still has data and the other is done...we just append whats left to the result.
Now this leads to na O(n+m) time and O(m) space complexity where m is the number of initialized items in nums1.
We can do better. WITH 3 POINTERS! we can loop backwards, p1 = m-1, p2 = n-1 and p = m+n-1.
p is used to fill the result array.
p1 is used to itertate nums1 (starting from the last intitialized index and walking back)
p2 is used to iterate nums2 (starting form the last initialized index)
One of the 2 pointers will reach the end before the other. this is expected and gauranteed. If it is p2, then that's fine. It means all of nums2 items have been inserted into the correct locations in nums1. This also implies that whatever is still left in nums1 does not need to be moved, since nums1 is sorted. The items in p1 then we have a probelem. it means there are items in nums2 that are smaller than all the items in p1 so they need to handled explicitly. They'd effectovely be inserted in fron of nums1.
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
p = m+n-1
p1 = m-1
p2 = n-1
while p1>=0 and p2>=0:
if nums1[p1]>nums2[p2]:
nums1[p] = nums1[p1]
p1-=1
else:
nums1[p] = nums2[p2]
p2-=1
p-=1
while p2>=0:
nums1[p] = nums2[p2]
p2-=1
p-=1
Since p2 is the real terminator of the while loops here, we can be smart and use on while loop. This means we will need to check explicitly for p1 in the loop before using it.
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
p = m+n-1
p1 = m-1
p2 = n-1
while p2>=0:
if p1 >=0 and nums1[p1]>nums2[p2]: # check that p1 is still safe!!
nums1[p] = nums1[p1]
p1-=1
else: # just dump nums2 items in
nums1[p] = nums2[p2]
p2-=1
p-=1
- Implement a min stack. [https://leetcode.com/problems/min-stack/]
Requirement: O(1) time for push, pop, top and getMin
There are 3 ways to attack this problem.
- Use to stacks. One for the actual data, the other 2 store the minimum of the items currently in the other stack.
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = collections.deque()
self.minstack = collections.deque()
def push(self, x: int) -> None:
self.stack.appendleft(x)
if not self.minstack:
self.minstack.appendleft(x)
else:
if x<=self.minstack[0]:
self.minstack.appendleft(x)
def pop(self) -> None:
item = self.stack.popleft()
if self.minstack and item == self.minstack[0]:
self.minstack.popleft()
def top(self) -> int:
if self.stack:
return self.stack[0]
def getMin(self) -> int:
return self.minstack[0] if self.minstack else None
Discovered this later.
When using deque from collections.
For queue behaviour, use append() and popleft()
q = collections.deque()
q.append(1)
q.append(2)
q.append(3)
q.append(4)
q.popleft() -> 1
q.popleft() -> 2
q.popleft() -> 3
For stack behaviour really consider just using []
if yous must, use append() and pop() like you will with []. same interface
st = collections.deque()
st = collections.deque()
st.append(1)
st.append(2)
st.append(3)
st.pop() -> 3
st.pop() -> 2
st.pop() -> 1
- Use an extra filed that maintains the current min. It gets updated and pushed into the stack whenever it is no longer the minimum. The idea is that whenever the minimum item is popped out of the stack. We have to pop a second time to obtain the new minimum which walsy follow its replcement. (rememebr what happend when a push smaller number is pushed in). Beatiful solution, but the lenght of the stack is warped!
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = collections.deque()
self.min=sys.maxsize
def push(self, x: int) -> None:
if x<=self.min:
self.stack.appendleft(self.min)
self.min = x
self.stack.appendleft(x)
def pop(self) -> None:
item = self.stack.popleft()
if item==self.min and self.stack:
self.min = self.stack.popleft()
def top(self) -> int:
if self.stack:
return self.stack[0]
def getMin(self) -> int:
return self.min
- Store items in the stack as tuples or a data structure that always has a reference to the current min item in the stack. So during push, this value is always set. It changes whenever a the item being pushed in is smaller than the current min.
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = collections.deque()
def push(self, x: int) -> None:
if not self.stack:
self.stack.appendleft((x, x))
else:
self.stack.appendleft((x, min(x, self.stack[0][1])))
def pop(self) -> None:
self.stack.popleft()
def top(self) -> int:
if self.stack:
return self.stack[0][0]
def getMin(self) -> int:
return self.stack[0][1] if self.stack else None
- Remove duplicates from sorted array. [https://leetcode.com/problems/remove-duplicates-from-sorted-array/]
2 Pointers baby. One to use as index for insertion, one to skip duplicates.
O(n), O(1)
def removeDuplicates(self, nums: List[int]) -> int:
if not nums:
return 0
p1 = 0
p2 = 0
n = len(nums)
while p1<n and p2<n:
while p2<n and nums[p1] == nums[p2]:
p2+=1
if p2<n:
p1+=1
nums[p1] = nums[p2]
return p1+1
Another approach which shows that a simple for loop is fine. Less boundary check.
def removeDuplicates(self, nums: List[int]) -> int:
if not nums:
return 0
i = 0
n = len(nums)
for j in range(n):
if nums[j] != nums[i]:
i+=1
nums[i] = nums[j]
return i + 1
- Valid palindrome. [https://leetcode.com/problems/valid-palindrome/]
Normaly, we can just use the 2 pointer technique and loop through from both ends while p1 > p2 checking if characters are the same. This one is a little different. We need to ignore non alpha numeric characters!!!
Consider madam: This is a palindrome.
madam; normally will not be a palindrome, but in this case it is.
Now the challenge is determining characters that are alphanumeric. These are A-Z, a-z 1-9 and I think a few other weird ones. Anyways, python has a cool funciton for determining this. If not, we would have had to do some ord(char) stuff and check that it is within a range! probably, 65-b to something since A is 65 and a is 97, Also we ignore case, which is trivial really, just do comparisons on the lowercases
O(n), O(1)
class Solution:
def isPalindrome(self, s: str) -> bool:
if not s:
return True
p1 = 0
p2 = len(s)-1
while p1<p2:
if not s[p1].isalnum():
p1+=1
continue
if not s[p2].isalnum():
p2 -=1
continue
if s[p1].lower() != s[p2].lower():
return False
p1 +=1
p2 -=1
return True
- Longest common prefix. [https://leetcode.com/problems/longest-common-prefix/]
Many ways to go about this problem, but in the end its actually very simple and brute force sorta is the most optimal. Well not weyrey brute force, but the solution that jumps at your first is pretty good. Get the length of the smallest string in the list. This is the upper bound on the length of the longest substring!
Approach 1: Divide and conquer. Realise that for arr [s1, s2, s3, s3] the lcp(arr) = LCP[lcp(arr[0:1]) and lcp (arr[1:4])]. lcp of left half and lcp of right half is the lcp of left half + right half.
O(S) where S is the sum of all the characters in the strings. O(n) space. recursive calls. could be log(n) because we finish the lefts stack before we start the rights stack.
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if len(strs)==0:
return "" # obvious base case
if len(strs) == 1:
return strs[0] # critical base case
n = len(strs)
mid = n//2 # we use // to get mid n items
left = self.longestCommonPrefix(strs[0:mid])
right = self.longestCommonPrefix(strs[mid:n])
return self.getLongestPrefix(left, right)
def getLongestPrefix(self, str1, str2):
n = min(len(str1), len(str2))
for i in range(n):
if str1[i] != str2[i]:
return str1[0:i]
return str1[0:n]
Approach 2: Preferred!
As usuall we get the upper bound of the longest common prefix which is lenght of the smallest string in the list.
The idea is to start from index 0 and compare every character in the smallest string to the character at the same index in the the other strings. Once we encounter a a character that is different at the same index in any of the other strings, we terminate.
The index where this happens is the length of the longest common prefix. We essentially assume the lcp is the smallest str and then ve validate each character from index zero terminating once we encounter a non matching character for the same index in any of the other strings. if we go all the way to the end of the smallest string, we return it as the lcp
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
smallest = ""
n = sys.maxsize
for i in range(len(strs)):
if len(strs[i])<n:
smallest = strs[i]
n = len(smallest)
for i in range(n)):
for other in strs:
if other[i] !=smallest[i]:
return smallest[:i] # everything from the beingin up to i(exclusive) is the lcp
return smallest
- Remove Nth Node form the end of a linked list [https://leetcode.com/problems/remove-nth-node-from-end-of-list/]
Return its head after removing the nth node from the end. Dummy head is key to solving these they o problems!
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
if not n :
return head
dummy = ListNode(0)
dummy.next = head
p1 = head
i = 0
while p1:
p1 = p1.next
i+=1
i-=n
p2 = dummy # use dummy when you need to relink/skip
while i:
p2 = p2.next
i-=1
p2.next = p2.next.next
return dummy.next
- Remove duplicates from sorted linkedlist [https://leetcode.com/problems/remove-duplicates-from-sorted-list/] You really just need to iterate to one item before the end. For each node, compare its value to that of the node after it. OO(n) O(1)
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
curr = head
while curr and curr.next:
if curr.val == curr.next.val:
curr.next = curr.next.next
else:
curr = curr.next
return head
Another approach
def deleteDuplicates(self, head):
cur = head
while cur:
while cur.next and cur.next.val == cur.val:
cur.next = cur.next.next # skip duplicated node
cur = cur.next # not duplicate of current node, move to next node
return head
- Linked list cycle. Detect a cycle. [https://leetcode.com/problems/linked-list-cycle/]
The fats guy, slow guy approach
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head:
return False
fast_guy = head
slow_guy = head
while fast_guy and slow_guy:
if not fast_guy.next:
return False
fast_guy = fast_guy.next.next
slow_guy = slow_guy.next
if fast_guy == slow_guy:
return True
return False
22.Remove element from linked list wit value val [https://leetcode.com/problems/remove-linked-list-elements/]
Whenevr you need to delete an item form a linkedlist and its possibe you have to delete the head, use a dummy head
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
dummy = ListNode(0)
dummy.next = head
p1 = head
p2 = dummy
while p1:
if p1.val == val:
p2.next = p2.next.next
p1 = p2.next
else:
p1 = p1.next
p2= p2.next
return dummy.next
- Middle element in linked list. [https://leetcode.com/problems/middle-of-the-linked-list/] You just need to find the length and then n//2 is the mid element. If the length of the list is even, we will end up with m being the 2nd of the 2 middle elements n = 4, n//2 = 2 [1, 2, 3, 4] which is element mid elements are 2 and 3. if we were to return the first, then we jsut need to reduce m by one if n is even.
O(n) O(1)
class Solution:
def middleNode(self, head: ListNode) -> ListNode:
n = 0
p = head
while p:
p = p.next
n+=1
m = n//2
p = head
while m:
print(m)
p = p.next
m-=1
return p
We can also use a fast and slow pointer. Slow pointer will be at the mid or 2nd mid when fast is at the end.
O(n) O(1)
class Solution:
def middleNode(self, head: ListNode) -> ListNode:
fast = slow = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
return slow
- Plus one linked list. [https://leetcode.com/problems/plus-one-linked-list/]
Just like the array question, you just need to add one to the number. With the array we are able to loop backwards starting from the last number and mantaining a carry which is set to 1 when the current sum is 10. so carry=1 sum=9.
To be able to loop backwards, we can just revers the linked list, add 1 to the head, handle the carry if any do the sum and then unreverse it at the end. We create and extra node with value 1 and put it at the tail if there is still a carry after adding just 1. 999->0001 then we reverse it to get 1000
4->2 => 2->4 => add 1 => 3->4 => 35 9->9 => 9-9 => add 1 => 0->0->1 => 100
O(n), O(1)
class Solution:
def plusOne(self, head: ListNode) -> ListNode:
curr = head
def reverse(node):
prev = None
while node:
nxt = node.next
node.next = prev
prev=node
node=nxt
return prev
r_head = reverse(curr)
r_head.val+=1
if r_head.val==10:
r_head.val = 0
carry=1
curr = r_head.next
while curr and carry:
curr.val+=carry
if curr.val==10:
curr.val = 0
carry=1
else:
carry=0
curr = curr.next
if carry: # still a carry and no curr, so end of list
curr = r_head
while curr.next: # because we need to guy before the end
curr = curr.next
curr.next = ListNode(1)
return reverse(r_head)
25 Copy list with random Pointer. [https://leetcode.com/problems/copy-list-with-random-pointer/] Deep copy means a brand new object that just contains the same values for primitive types. All references to objects are also recreated to point to depp copies
Great thing here is that Node objects can be used as keys for the hashmap
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return None
copy_by_org = {}
curr = head
while curr:
copy_by_org[curr] = Node(curr.val, None, None)
curr = curr.next
curr = head
while curr:
new_node = copy_by_org[curr]
nxt = curr.next
rand = curr.random
if nxt:
new_node.next = copy_by_org[nxt]
if rand:
new_node.random = copy_by_org[rand]
curr = curr.next
return copy_by_org[head]
-
Clone Graph. [https://leetcode.com/problems/clone-graph/]
Same logic to the above, I even learnt a better approach to reconstruction. We can just iterate over the key value pairs in the hashmap. To be honest this question was very straightforward. Especially after solving the linkedlist one. Its quite simple once you know how to keep track of items already seen!
O(n), O(n)
class Solution:
def cloneGraph(self, node: 'Node') -> 'Node':
if not node:
return None
st = [node]
d = {}
seen = set([])
while st:
old = st.pop()
if old not in seen:
seen.add(old)
copy = Node(old.val, [])
d[old] = copy
for n in old.neighbors:
st.append(n) # this is key, so we can visit them later. stack is used, so dfs
for old , new in d.items():
for n in old.neighbors:
new.neighbors.append(d[n])
return d[node]
- Add two numbers presented as 2 linkedlists. [https://leetcode.com/problems/add-two-numbers-ii/]
One approach is to reverse and add like you would a normal array. Handing all possible None exceptions as you traverse the lists. Reversing makes it a lot easier because this is really how we appraoch suming numbers on paper
O(n), O(n)
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
def reverse(node):
prev = None
while node:
front = node.next
node.next = prev
prev = node
node = front
return prev
n1 = reverse(l1)
n2 = reverse(l2)
carry = 0
sm = n1.val+n2.val
if sm >9:
sm = sm%10
carry=1
ans = ListNode(sm)
ps = ans
c1, c2 = n1.next, n2.next
while c1 or c2:
sm = carry
if c1:
sm +=c1.val
c1=c1.next
if c2:
sm+=c2.val
c2=c2.next
if sm>9:
sm = sm%10
carry=1
else:
carry=0
ps.next = ListNode(sm)
ps = ps.next
if carry:
ps.next = ListNode(1)
return reverse(ans)
- Partition list. [https://leetcode.com/problems/partition-list/]
Given in a linked list and a value x, partition the linked list such that nodes less than x come before nodes that are greater than or equal to x. Preserve the original order of the the nodes.
We just need to create 2 lists. one for the items less than x the other for the items greater than x. At the end we merge them. We need dummy heads for the new lists. We need to have access to the next without overriding values in the original list. we just need to re point the nodes nexts. Its 10 times easier with the dummy nodes
O(n), O(1)
class Solution:
def partition(self, head: ListNode, x: int) -> ListNode:
h1 = l1 = ListNode(0)
h2 = l2 = ListNode(0)
p = head
while p:
if p.val<x:
l1.next = p
l1=l1.next
else:
l2.next = p
l2=l2.next
p = p.next
l2.next=None # the end of the list or else itd point to whatever p.next pointed to
l1.next=h2.next # connect both lists
return h1.next # skip the dummy head
- Swap Nodes in Pairs [https://leetcode.com/problems/swap-nodes-in-pairs/] Given a linked list, swap every two adjacent nodes and return the head
1-2-3 => 2->1->3 1->2->3->4 2->1->4->3
Notice the swapping is done in pairs! I.e only when we 2 complete nodes. so base case is return head when we dont have up to 2 nodes in the list. Questions like this are best easily solved by considering easier solutions and building from that. Consider a list with 1 , 2, then 3 items. You'd see the recursion on the 3rd
O(n), O(n/2)
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
if not head:
return head
if not head.next:
return head
front = head.next # we need a reference to this guys so we can return it as the head later :)
head.next= self.swapPairs(head.next.next)
front.next = head # the actual swapping
return front
Another approach is to do it iteratively using s dummy head as the curr and working on the curr.next and curr.next .next
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummy = ListNode(0)
dummy.next = head
prev, cur = dummy, head
while cur and cur.next:
front = cur.next.next
prev.next = cur.next
cur.next.next = cur
cur.next = front
prev=cur
cur=front
return dummy.next
- Flowers planting with no adjacent. Graph question. [https://leetcode.com/problems/flower-planting-with-no-adjacent/]
This question is very simple once you understand what it is asking for. You are give N gardens and a grid that represents the paths between the gardens. paths = [[X, y],[y, a], [b, c]] This means garden x and y are connected, y and a are connected, b and c are connected.
From this information we can build an adjency list using a graph. From the adjanecy list we can make sure not to plant a flower in a garden if any of its neighbours already have that flower. There are only 4 types of flowers
O(n), O(n)
class Solution:
def gardenNoAdj(self, N: int, paths: List[List[int]]) -> List[int]:
res = [1 for i in range(N)]
adj_list = collections.defaultdict(set)
for x, y in paths:
adj_list[x].add(y)
adj_list[y].add(x)
for grd, neis in adj_list.items():
used = set()
for n in neis:
used.add(res[n-1])
for f in range(1, 5):
if f not in used:
res[grd-1]=f
break
return res
- Find the Town judge. [https://leetcode.com/problems/find-the-town-judge/] Just need to ensure that after building the graph, only one person is not a key. This is guy is likely to be the judge. we then need to verify that he is trusted by all. If there is more than one person without a key in the graph, then we return -1 because more than one person does not trust anyone which means there is no judge.
O(n), O(n)
class Solution:
def findJudge(self, N: int, trust: List[List[int]]) -> int:
adj = collections.defaultdict(set)
for a, b in trust:
adj[a].add(b)
judges = set([])
for j in range(1, N+1):
if j not in adj.keys():
judges.add(j)
if len(judges)!=1:
return -1
j = judges.pop()
for k, v in adj.items():
if j not in v:
return -1
return j
- Reconstruct itinerary. [https://leetcode.com/problems/reconstruct-itinerary/]
Depth first search after sorting the edges in the graph in reverse lexical order. Return the result reversed, because wev'e appended the first last based on dfs using a stack.
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
graph = collections.defaultdict(list)
for c, d in tickets:
graph[c].append(d)
for k in graph.keys():
graph[k].sort(reverse=True)
print(k, graph[k])
res = []
key = "JFK"
stack = [key]
while stack:
key=stack[-1]
# print(stack)
while(graph[key]):
print("loop",stack)
key = graph[key].pop()
stack.append(key)
res.append(stack.pop())
return res[::-1]
- Binary Tree paths. [https://leetcode.com/problems/binary-tree-paths/]
We want to get all the paths that lead from head to a leaf node. We can use dfs recursively and iteratively.
The main trick here is to send a copy of the paths so far not a reference, so that each node has its own paths and you are'nt updating the global paths for everyone.
class Solution:
def binaryTreePaths(self, root: TreeNode) -> List[str]:
if not root:
return []
paths = []
head= root
stack = [(head, [])]
while stack:
node, path = stack.pop()
path.append(str(node.val))
if not node.left and not node.right:
paths.append(path)
if node.left:
stack.append((node.left, path.copy()))
if node.right:
stack.append((node.right, path.copy()))
for i in range(len(paths)):
paths[i] = "->".join(paths[i])
return paths
Same trick, pass down the copyof the list of the nodes parents
class Solution:
def binaryTreePaths(self, root: TreeNode) -> List[str]:
def dfs(node, path, paths):
if node:
path.append(str(node.val))
if not node.left and not node.right:
paths.append(path)
else:
dfs(node.left, path.copy(), paths)
dfs(node.right, path.copy(), paths)
a = []
b = []
dfs(root, a, b)
for i in range(len(b)):
b[i]="->".join(b[i])
return b
- Balance Binary Tree. [https://leetcode.com/problems/balanced-binary-tree/]
The concept is quite interesting. It depends on the ability to compute the height of a tree giving the node and using dfs.
Approach 1: Will calculate the height of the left sub tree and right subtree of every node, starting from the head. If at any point the Balanced tree variant fails, ie abs(left-right)>1 we return False. So the result is an and between the recursive result on the left and right and the terminating case is the variants failure.
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
def geth(node):
if not node:
return 0
left = geth(node.left)+1
right = geth(node.right)+1
return max(left, right) # only one of the 1s added makes it through to the top
if not root: # base case
return True
if abs(geth(root.left)-geth(root.right))>1: # balanced tree defintion. False if fails
return False
return self.isBalanced(root.left) and self.isBalanced(root.right)
# check sub trees because they need to satisfy independently
Approach 2 avoids lots of repeated calculations done by approach 1. We calculate the high of the leaf nodes and verify that they balanced before checking parent nodes.
class Solution:
def isBalancedHelper(self, root):
if not root: # an empty tree is balanced
return True, 0
# check the subtrees to see if they are balanced
l_balanced, lh = self.isBalancedHelper(root.left) # we go deeper and deeper
if not l_balanced:
return False, 0 # does not matter the heigt
r_balanced, rh = self.isBalancedHelper(root.right) # we go right, then left depeer and deeper
if not r_balanced: # quick terminate
return False, 0
rh+=1
lh+=1
return abs(lh-rh)<=1, max(lh, rh) # this is not where recursion happens
def isBalanced(self, root: TreeNode) -> bool:
return self.isBalancedHelper(root)[0]
- Minimum depth of binary tree. [https://leetcode.com/problems/minimum-depth-of-binary-tree/] At first this problem seems easy. lol but it is not. You cant just calculate the height of the left sub tree and the right subtree and return the minimum. That will be wrong in some case.
3
/ \
9 20
/ \
15 7
The anser is 2. 3->9 so 2 nodes. this case will work if we just retured the min of the left and right heights of the
root node.
3
/
9
The minum of the 2 heights here is 0 so we return 0+1 but the anser is 2. It has to be a leaf node.
This solution using a stack and dfs accounts for that.
O(n) and O(n)
class Solution:
def minDepth(self, root: TreeNode) -> int:
if not root:
return 0
if not root.left and not root.right:
return 1
stack = [(root, 1)]
mind = sys.maxsize
while stack:
node, depth = stack.pop()
if not node.left and not node.right:
mind = min(depth, mind)
depth+=1
if node.left:
stack.append((node.left, depth))
if node.right:
stack.append((node.right, depth))
return mind
We can also use bfs. We use a que instead. This guys is more optimal because it terminates at the level with the shortest path and does not traverse the whole tree.
so with tree
3
/ \
9 20
/ \
15 7
We will only visit node 3, 2, and 20. Once we enounter a leaf we terminated.
level order makes the most sense for this question.
from collections import deque
class Solution:
def minDepth(self, root: TreeNode) -> int:
if not root:
return 0
if not root.left and not root.right:
return 1
q = deque()
depth = 1
q.append((root, depth))
while q:
node, depth = q.popleft()
if not node.left and not node.right:
return depth
depth+=1
if node.left:
q.append((node.left, depth))
if node.right:
q.append((node.right, depth))
return depth
- Binary tree path sum. [https://leetcode.com/problems/path-sum/solution/]
Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum.
This is a pretty straight forward question. My first approach worked. use a que or stack and store the current sum together with the node in the stack or que
from collections import deque
class Solution:
def hasPathSum(self, root: TreeNode, sm: int) -> bool:
if not root:
return False
if not root.left and not root.right:
return root.val == sm
q = deque()
q.append((root, 0))
while q:
node, tsm = q.popleft()
tsm+=node.val
if not node.left and not node.right:
if tsm == sm:
return True
if node.left:
q.append((node.left, tsm))
if node.right:
q.append((node.right, tsm))
return False
we can also use use dfs i.e a stack
class Solution:
def hasPathSum(self, root: TreeNode, sm: int) -> bool:
if not root:
return False
if not root.left and not root.right:
return root.val == sm
st = [(root, 0)]
while st:
node, tsm = st.pop()
tsm+=node.val
if not node.left and not node.right:
if tsm == sm:
return True
if node.left:
st.append((node.left, tsm))
if node.right:
st.append((node.right, tsm))
return False
Another interesting approach is to use recursion and keep decreasing the sum value as we recur down.
from collections import deque
class Solution:
def hasPathSum(self, root: TreeNode, sm: int) -> bool:
if not root:
return False
if not root.left and not root.right:
return root.val == sm
sm-=root.val
return self.hasPathSum(root.left, sm) or self.hasPathSum(root.right, sm)
- Path Sum III. [https://leetcode.com/problems/path-sum-iii/]
Find the number of paths that sum to a given value.
The path does not need to start or end at the root or a leaf, but it must go downwards (traveling only from parent nodes to child nodes).
O(n) O(n*n)
class Solution:
def pathSum(self, root: TreeNode, s: int) -> int:
def check(node, target, n):
if not node:
return
if node.val == target:
n[0]+=1
# dont return because we can still get nodes that sum up to the target -ves and +vs
if node.left:
check(node.left, target-node.val, n)
if node.right:
check(node.right, target-node.val, n)
def dfs(node, target, n):
if not node:
return
check(node, target, n)
if node.left:
dfs(node.left, target, n)
if node.right:
dfs(node.right, target, n)
n = [0]
dfs(root, s, n)
return n[0]
Another Approach. [https://leetcode.com/problems/path-sum-iii/]
We maintain a dictionary of past sub trees that if remoevd from the current subtree could lead to it being a sub path that sums up to target K. Look at quesiton 38 below if you do not understand the approach. It is similar to sub array sums
class Solution:
n = 0 # class variable bu accessed through self
def pathSum(self, root: TreeNode, s: int) -> int:
def dfs(node, target, cum, mp):
if not node:
return
cum+= node.val
x = cum-target
if x in mp:
self.n+=mp[x]
mp[cum] = mp.get(cum, 0)+1
if node.left:
dfs(node.left, target, cum, mp)
if node.right:
dfs(node.right, target, cum, mp)
mp[cum]-=1
dfs(root, s, 0, {0:1})
return self.n
- Subarray equals k. [https://leetcode.com/problems/subarray-sum-equals-k/]
We need to count the number of sub arrays whose sume equals k. Quite nice question. You need to think deeply to see that memoization helps a lot.
consider array [4, 0, 3, 0, 4, 3, 7, 0] and k = 7
[4, 0, 3] =>7
[4, 0, 3, 0] =>7
[0, 3, 0, 4] =>7
[3, 0, 4] =>7
[0, 4, 3] =>7
[4, 3] =>7
[7] =>7
[7, 0] =>7
8 sub arrays
Notice that zeroes and also negatives have a weird impact on the size and number fo sub arrays.
The optimal approach is to keep track of the count of all sums see as we travers the array
{0:1} starting with 0 because it is always available as a prefix. meaning no other items is to be used to make up the
sum at the current element if the current element =k meh that may be difficult to understand.
Alright. At every index, we have the cumulative sum. We will like to know if there is an x such that
cum_sum-x = k. That is there is a sub array prior to this item whose sum is x and if removed from the current
cumulative summ will lead to the target.
at index 2 arr[3] =0
There is a subarray whose sum when subtracted from the current summlative subarray will lead to 7
i.e sub array []. if you remove this sub array from sub array [4, 0, 3, 0] you end hav a subaarray whose sum is the
target.
Another case. at index 4 arr[4] = 4 sub array [4, 0, 3, 0, 4]. There is another subarray if removed from the sub
array willresult in a subarray whose sum is 7. That subarry is [4]. hmmm there are 2 such subarrays, [4] and [4, 0]
leaving us with [0, 3, 0, 4] and [3, 0, 4] respectively.
so at everypoint, we need to update the map with the count of the current cumulative sum, so if any subarrays need
that difference taken out to give them the target, they can use it the number of times it has appeared.
curr_sum -x = target. if x is in the map then yippy, we can use x map[x] times, we increment count this number of times
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
count = 0
# sums = [0 for _ in range(len(nums))]
map = collections.defaultdict(int)
map[0]=1
sm = 0
for i in range(len(nums)):
sm+=nums[i]
if sm-k in map:
count+=map[sm-k]
map[sm]+=1
print(map)
print(i, count)
return count
- Path sum iii. [https://leetcode.com/problems/path-sum-iii/]
Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum.
Remember to pass a copy of the dame paths
O(n) O(n)
class Solution:
def pathSum(self, root: TreeNode, m: int) -> List[List[int]]:
if not root:
return []
st = [(root, 0, [])]
paths =[]
while st:
node, sm, path = st.pop()
curr_sum = node.val+sm
path.append(node.val)
if not node.left and not node.right and curr_sum == m:
paths.append(path)
if node.left:
st.append((node.left, curr_sum, path.copy()))
if node.right:
st.append((node.right, curr_sum, path.copy()))
return paths
- Nested List weight Sum. [https://leetcode.com/problems/nested-list-weight-sum/]
Given a nested list of integers, return the sum of all integers in the list weighted by their depth.
Each element is either an integer, or a list -- whose elements may also be integers or other lists.
Input: [1,[4,[6]]]
Output: 27
Explanation: One 1 at depth 1, one 4 at depth 2, and one 6 at depth 3; 1 + 42 + 63 = 27.
We can use dfs iteratvely and recursively.
class Solution:
def depthSum(self, nestedList: List[NestedInteger]) -> int:
if not nestedList:
return 0
st = [(n,1) for n in nestedList]
sm = 0
while st:
n, d = st.pop()
if n.isInteger():
sm+=d*n.getInteger()
else:
d+=1
for l in n.getList():
st.append((l, d))
return sm
Recursively
class Solution:
def depthSum(self, nestedList: List[NestedInteger]) -> int:
if not nestedList:
return 0
def dfs(nl, depth):
if not nl:
return 0
sm = 0
for n in nl:
if n.isInteger():
sm+=n.getInteger()*depth
else:
sm+=dfs(n.getList(), depth+1)
return sm
return dfs(nestedList, 1)
- Decode String. [https://leetcode.com/problems/decode-string/]
You may assume that the input string is always valid; No extra white spaces, square brackets are well-formed, etc.
s = "3[a]2[bc]", return "aaabcbc".
s = "3[a2[c]]", return "accaccacc".
s = "2[abc]3[cd]ef", return "abcabccdcdcdef".
We just iterate through every character and evelauate every inner []. To do this we look for ] and then pop until we see a [
class Solution:
def decodeString(self, s: str) -> str:
st = []
for c in s:
if c =="]":
word = ""
while st:
w = st.pop()
if w == "[":
break
word = w+word
num = ""
while st:
d = st.pop()
if d.isdigit():
num = d+num
else:
st.append(d)
break
if num:
print(num)
word = int(num)*word
st.append(word)
else:
st.append(c)
return "".join(st)
- Max Area of Island. [https://leetcode.com/problems/max-area-of-island/]
Given a non-empty 2D array grid of 0's and 1's, an island is a group of 1's (representing land) connected 4-directionally (horizontal or vertical.) You may assume all four edges of the grid are surrounded by water.
Find the maximum area of an island in the given 2D array. (If there is no island, the maximum area is 0.)
Really simple when you handle all boundaries and a bit of recursion.
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
gmax = 0
if not grid:
return gmax
rows = len(grid)
cols = len(grid[0])
def getArea(i, j):
count=0
r = i+1
while r<rows: # go down
if grid[r][j]==1:
count+=1
grid[r][j]=None
count+=getArea(r,j)
else:
break
r+=1
r=i-1
while r>=0:
if grid[r][j]==1:
count+=1
grid[r][j]=None
count+=getArea(r,j)
else:
break
r-=1
c = j+1
while c<cols:
if grid[i][c]==1:
count+=1
grid[i][c]=None
count+=getArea(i,c)
else:
break
c+=1
c = j-1
while c>=0:
if grid[i][c]==1:
count+=1
grid[i][c]=None
count+=getArea(i,c)
else:
break
c-=1
return count
for i in range(rows):
for j in range(cols):
if grid[i][j]==1:
grid[i][j] = None
count= getArea(i, j)+1
gmax = max(count, gmax)
return gmax
- Convert Sorted List to Binary Search Tree. [https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/]
Given a singly linked list where elements are sorted in ascending order, convert it to a height balanced BST. For this problem, a height-balanced binary tree is defined as a binary tree in which the depth of the two subtrees of every node never differ by more than 1.
The middle item is the root node. That's all you need to solve this problem.
n <=3 are base cases that can be handled real quick. n=4 too.
class Solution:
def sortedListToBST(self, head: ListNode) -> TreeNode:
if not head:
return None
def getMid(node):
slow = fast = node
p=node
size = 0
while p:
p = p.next
size+=1
prev = slow
while fast and fast.next:
prev = slow
slow = slow.next
fast = fast.next.next
prev.next = None # disconnect
return slow, size
def bst(node, n=None):
if n==3:
top = TreeNode(node.next.val)
top.left = TreeNode(node.val)
top.right = TreeNode(node.next.next.val)
return top
if n==2:
top = TreeNode(node.next.val)
top.left = TreeNode(node.val)
return top
if n==1:
return TreeNode(node.val)
if n==0:
return None
mid, n = getMid(node)
if n%2:
mid.right = bst(mid.next, n//2)
mid.left = bst(node, n//2)
else:
mid.right = bst(mid.next, (n//2)-1)
mid.left = bst(node, n//2)
return mid
return bst(head)
- Binary Tree Level Order Traversal II
Given a binary tree, return the bottom-up level order traversal of its nodes' values. (ie, from left to right, level by level from leaf to root). BFS with control on the level.
from collections import deque
class Solution:
def levelOrderBottom(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
paths = []
q = deque()
q.append(root)
while q:
size = len(q)
level = []
for i in range(size):
node = q.popleft()
level.append(node.val)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
paths.append(level)
paths.reverse()
return paths
- Employee importance. https://leetcode.com/problems/employee-importance/
Now given the employee information of a company, and an employee id, you need to return the total importance value of this employee and all his subordinates.
"""
# Employee info
class Employee:
def __init__(self, id, importance, subordinates):
# It's the unique id of each node.
# unique id of this employee
self.id = id
# the importance value of this employee
self.importance = importance
# the id of direct subordinates
self.subordinates = subordinates
"""
from collections import deque
class Solution:
def getImportance(self, employees, id):
"""
:type employees: Employee
:type id: int
:rtype: int
"""
emp_by_id = {}
empl = None
for emp in employees:
if emp.id == id:
empl = emp
continue
emp_by_id[emp.id] = emp
total = empl.importance
q = deque(empl.subordinates)
while q:
e = q.popleft()
emp = emp_by_id[e]
subs = emp.subordinates
for i in subs:
q.append(i)
total+=emp.importance
return total
# recursive bfs
class Solution(object):
def getImportance(self, employees, query_id):
emap = {e.id: e for e in employees}
def dfs(eid):
employee = emap[eid]
return (employee.importance +
sum(dfs(eid) for eid in employee.subordinates))
return dfs(query_id)
- Maximum Depth of N-ary Tree
Given a n-ary tree, find its maximum depth.
The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.
Pretty standard, we can solve it iteratively or recursively. The main deal is that we loop over the children of each node as we do not know how many they are. left, right will not cut it
from collections import deque
class Solution:
def maxDepth(self, root: 'Node') -> int:
if not root:
return 0
q = deque()
q.append((root, 1))
max_d = -1
while q:
node, d = q.popleft()
if node.children:
for ch in node.children:
q.append((ch, d+1))
else:
max_d = max(d, max_d)
return max_d
class Solution:
def maxDepth(self, root: 'Node') -> int:
if not root:
return 0
max_d=1
for ch in root.children:
h = self.maxDepth(ch)+1
max_d = max(h, max_d)
return max_d
- Cousins in binary tree. https://leetcode.com/problems/cousins-in-binary-tree/
Two nodes of a binary tree are cousins if they have the same depth, but have different parents. Return true if and only if the nodes corresponding to the values x and y are cousins.
from collections import deque
class Solution:
def isCousins(self, root: TreeNode, x: int, y: int) -> bool:
if not root:
return False
q = deque()
q.append((root, None))
while q:
n = len(q)
parent_x = parent_y = None
for _ in range(n):
node, parent= q.popleft()
if node.left:
q.append((node.left, node))
if node.right:
q.append((node.right, node))
if node.val == x:
parent_x = parent
if node.val == y:
parent_y = parent
if parent_x and parent_y:
if parent_x != parent_y:
return True
else:
return False
return False
- Binary Tree Right Side View. https://leetcode.com/problems/binary-tree-right-side-view/
Given a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you can see ordered from top to bottom.
Loved this question. The trick is to visit the right most node of each level first. Then for each level, add this right most node to the result array. BFS to the rescue with an inner loop to separate levels.
class Solution:
def rightSideView(self, root: TreeNode) -> List[int]:
if not root:
return []
q = deque()
q.append(root)
right = []
while q:
node = q.popleft()
n = len(q) # after weve removed the righ most guy.. so the rest
right.append(node.val)
if node.right:
q.append(node.right)
if node.left:
q.append(node.left)
for _ in range(n): # just this level
node = q.popleft()
if node.right:
q.append(node.right)
if node.left:
q.append(node.left)
return right
- 3 SUM. https://leetcode.com/problems/3sum/submissions/ Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note: The solution set must not contain duplicate triplets.
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
ans = []
n = len(nums)
nums.sort()
for i in range(n-2):
if i and nums[i] == nums[i-1]:
continue
else:
j = i+1
k = n-1
while j<k:
print(i, j, k)
if k<n-1 and nums[k] == nums[k+1]:
k-=1
continue
if nums[i]+nums[j]+nums[k]>0:
k-=1
continue
elif nums[i]+nums[j]+nums[k]<0:
j+=1
continue
else:
ans.append([nums[i],nums[j], nums[k]])
k-=1
j+=1
return ans
- Product of array except self. https://leetcode.com/problems/product-of-array-except-self/
Given an array nums of n integers where n > 1, return an array output such that output[i] is equal to the product of all the elements of nums except nums[i].
It seems simple but it is not when you consider inputs like [1,0] or [0,0, 1, 2]
It gets even more complex when you arent allowed to use the division operation.
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
if not nums:
return []
left = [1 for _ in nums]
right =[1 for _ in nums]
ans = [0 for _ in nums]
n = len(nums)
for i in range(1, n): # start from 2nd element
left[i] = nums[i-1]*left[i-1]
for i in range(-2, -(n+1), -1): # start from 2nd to last
right[i] = nums[i+1] * right[i+1]
for i in range(n):
ans[i] = right[i] * left[i]
return ans
Another approach with less space. R or L can be computed on the fly.. Weve done R but L might actually be easier. no need for the reverse iteration.
-1, -(n+1), -1 => -1 all the way to the first element.
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
if not nums:
return []
left = [1 for _ in nums]
r = 1
ans = [0 for _ in nums]
n = len(nums)
for i in range(1, n): # start from 2nd element
left[i] = nums[i-1]*left[i-1]
for i in range(-1, -(n+1), -1): # -1 to -(n+1) so all items!
ans[i] = r * left[i]
r = r*nums[i]
return ans
- Maximum product array. https://leetcode.com/problems/maximum-product-subarray/
Given an integer array nums, find the contiguous subarray within an array (containing at least one number) which has the largest product
Think about negative numbers and their impact on sub array products. also remember its contiguos subarray.
class Solution:
def maxProduct(self, nums: List[int]) -> int:
imax = imin = mx = nums[0]
n = len(nums)
for i in range(1, n):
tmp=imax
imax = max(nums[i], nums[i]*imax, nums[i]*imin)
imin = min(nums[i], nums[i]*imin, nums[i]*tmp)
mx = max(mx, imax)
return mx
- Max consecutive ones. https://leetcode.com/problems/max-consecutive-ones-iii/
Given an array A of 0s and 1s, we may change up to K values from 0 to 1.
Return the length of the longest (contiguous) subarray that contains only 1s.
class Solution:
def longestOnes(self, A: List[int], K: int) -> int:
if not A:
return 0
n= len(A)
mx = 0
i =j =0
while j<n and i<n:
if A[j]==1:
j+=1
elif A[j]==0:
if K>0:
j+=1
K-=1
else:
mx = max(mx, j-i)
while K==0:
if A[i]==0:
K+=1
i+=1
mx = max(mx, j-i)
return mx