75 Flashcards

1
Q

Contains Duplicate: Given an integer array nums, return true if any value appears at least twice in the array, and return false if every element is distinct.

A
class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        numSet = set()
        for n in nums:
            if n in numSet:
                return True
            numSet.add(n)
        return False
        #O(n)
        #O(n)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Valid Anagram: Given two strings s and t, return true if t is an anagram of s, and false otherwise.

A
class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        if len(s) != len(t):
            return False
        sCount = {}
        tCount = {}
        for i in range(len(s)):
            sCount[s[i]] = sCount.get(s[i], 0) + 1
            tCount[t[i]] = tCount.get(t[i], 0) + 1
        return tCount == sCount
        #O(n)
        #O(1) -> 26 characters in alphabet
        
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Two Sum: Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

A
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        prevMap = {}
        for i, n in enumerate(nums):
            diff = target - n
            if diff in prevMap:
                return [prevMap[diff], i]
            prevMap[n] = i
        #O(n)
        #O(n)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Group Anagrams: Given an array of strings strs, group the anagrams together. You can return the answer in any order.

An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.

A
class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        #hashmap maps anagrams to strs
        anaMap = {}
        for s in strs:
            curCounts = [0] * 26
            for c in s:
                curCounts[ord(c) - ord('a')] += 1
            curTup = tuple(curCounts)
            if curTup not in anaMap:
                anaMap[curTup] = [s]
            else:
                anaMap[curTup].append(s)
        return anaMap.values()
        #O(N) #all the characters in strs
        #O(N) #the mapping needs all the characters in strs and the anagrams.
        #In other words, we can also say O(nm), where n is number of strs and m is the avg length of each ana.
            
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Top K Frequent Elements: Given an integer array nums and an integer k, return the k most frequent elements. You may return the answer in any order.

A
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        freqs = [[] for i in range(len(nums) + 1)] 
        #default to list comprehension for empty [] inits, or all. 
        counts = {}
        for n in nums:
            counts[n] = counts.get(n, 0) + 1
        
        for n, c in counts.items():
            freqs[c].append(n)
        res = []
        for i in range(len(freqs) - 1, -1, -1):
            for n in freqs[i]:
                res.append(n)
                if len(res) == k:
                    return res
        #O(n) for iterating, hashing, etc
        #O(n) for the hashing, and the counts array for the bucket sort.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Given an integer array nums, return an array answer such that answer[i] is equal to the product of all the elements of nums except nums[i].

The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.

You must write an algorithm that runs in O(n) time and without using the division operation.

A
class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        res = [1] * len(nums)
        prefix = 1
        for i in range(len(nums)):
            res[i] = prefix
            prefix *= nums[i]
        suffix = 1
        for j in range(len(nums) - 1, -1, -1):
            res[j] *= suffix
            suffix *= nums[j]
        return res
        #O(n) number of elts and traverse
        #O(n) keep res variable, tho O(1) if you don't count res as extra space I guess...?
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Given a string s containing just the characters ‘(‘, ‘)’, ‘{‘, ‘}’, ‘[’ and ‘]’, determine if the input string is valid.

A
class Solution:
    def isValid(self, s: str) -> bool:
        closedToOpen = {")": "(", "}": "{", "]":"[" } #map closed to open parentheses
        stack = []
        for c in s:
            if c in closedToOpen:
                if not stack or stack[-1] != closedToOpen[c]:
                    return False
                stack.pop()
            else:
                stack.append(c)
        return not stack
        #O(n)
        #O(n)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Given the head of a singly linked list, reverse the list, and return the reversed list.

A
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        prev = None
        cur = head
        while cur:
            tmp = cur.next
            cur.next = prev
            prev = cur
            cur = tmp
        return prev
        #O(n)
        #O(1)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

You are given the heads of two sorted linked lists list1 and list2.

Merge the two lists in a one sorted list. The list should be made by splicing together the nodes of the first two lists.

Return the head of the merged linked list.

A
# Definition for singly-linked list.
# class ListNode:
#     def \_\_init\_\_(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()
        tail = dummy
        while list1 and list2:
            if list1.val < list2.val:
                tail.next = list1
                list1 = list1.next
            else:
                tail.next = list2
                list2 = list2.next
            tail = tail.next
        if list1:
            tail.next = list1
        if list2:
            tail.next = list2
        return dummy.next
        #O(l1 + l2)
        #O(1)
        
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Given head, the head of a linked list, determine if the linked list has a cycle in it.

There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail’s next pointer is connected to. Note that pos is not passed as a parameter.

Return true if there is a cycle in the linked list. Otherwise, return false.

A
# Definition for singly-linked list.
# class ListNode:
#     def \_\_init\_\_(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        slow, fast = head, head #fine to just set these to the same initially.
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                return True
        return False
        #O(n)
        #O(1)
        
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Given the root of a binary tree, invert the tree, and return its root.

A
# Definition for a binary tree node.
# class TreeNode:
#     def \_\_init\_\_(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if not root:
            return None
        invertedLeft = self.invertTree(root.left)
        invertedRight = self.invertTree(root.right)
        root.left = invertedRight
        root.right = invertedLeft
        return root
        #O(n)
        #O(h) #recursion stack, expected O(log n)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Given the root of a binary tree, return its maximum depth.

A binary tree’s maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.

A
# Definition for a binary tree node.
# class TreeNode:
#     def \_\_init\_\_(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
        #O(n)
        #O(h)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Given the roots of two binary trees p and q, write a function to check if they are the same or not.

Two binary trees are considered the same if they are structurally identical, and the nodes have the same value.

A
# Definition for a binary tree node.
# class TreeNode:
#     def \_\_init\_\_(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        if not p and not q:
            return True
        if not p or not q or p.val != q.val:
            return False
        return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
        #O(|p| + |q|)
        #O(min(height of p, height of q trees))
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Given the roots of two binary trees root and subRoot, return true if there is a subtree of root with the same structure and node values of subRoot and false otherwise.

A subtree of a binary tree tree is a tree that consists of a node in tree and all of this node’s descendants. The tree tree could also be considered as a subtree of itself.

A
# Definition for a binary tree node.
# class TreeNode:
#     def \_\_init\_\_(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
        if not subRoot:
            return True 
        if not root:
            return False
        if self.sameTree(root, subRoot):
            return True
        return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot)
    
    def sameTree(self, t1, t2):
        if not t1 and not t2:
            return True
        if not t1 or not t2 or t1.val != t2.val:
            return False
        return self.sameTree(t1.left, t2.left) and self.sameTree(t1.right, t2.right)
        
    #O(|t1| * |t2|) #check each root for subroot match
    #O(height of t1 + height of t2) #We can potentially be at the bottom of the first tree, and we need to compare against all nodes in t2
        
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given nodes in the BST.

According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).”

A
# Definition for a binary tree node.
# class TreeNode:
#     def \_\_init\_\_(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if p.val < root.val and q.val < root.val:
            return self.lowestCommonAncestor(root.left, p, q)
        elif p.val > root.val and q.val > root.val:
            return self.lowestCommonAncestor(root.right, p, q)
        else:
            return root 
        #O(h) -> we only go level by level, not need to visit all nodes
        #O(h) -> same for recursion stack
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Given the root of a binary tree, return the level order traversal of its nodes’ values. (i.e., from left to right, level by level).

A
# Definition for a binary tree node.
# class TreeNode:
#     def \_\_init\_\_(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root:
            return []
        q = deque([root])
        res = []
        while q:
            curLevel = []
            for _ in range(len(q)):
                cur = q.popleft()
                curLevel.append(cur.val)
                if cur.left:
                    q.append(cur.left)
                if cur.right:
                    q.append(cur.right)
            res.append(curLevel)
        return res
        #O(n) #traverse every node
        #O(n) # queue size
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Given the root of a binary tree, determine if it is a valid binary search tree (BST).

A valid BST is defined as follows:

The left
subtree
of a node contains only nodes with keys less than the node’s key.
The right subtree of a node contains only nodes with keys greater than the node’s key.
Both the left and right subtrees must also be binary search trees.

A
# Definition for a binary tree node.
# class TreeNode:
#     def \_\_init\_\_(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        def dfs(node, left, right):
            if not node: #empty BST is a valid BST 
                return True 
            #It's not binary search. It's the expected left and right values!
            if not (node.val > left and node.val < right):
                return False
            return dfs(node.left, left, node.val) and dfs(node.right, node.val, right)
        return dfs(root, -float('inf'), float('inf'))
        #O(n)
        #O(h)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

Given the root of a binary search tree, and an integer k, return the kth smallest value (1-indexed) of all the values of the nodes in the tree.

A
# Definition for a binary tree node.
# class TreeNode:
#     def \_\_init\_\_(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
        stack = []
        cur = root 
        count = 0
        while cur or stack:
            while cur:
                stack.append(cur)
                cur = cur.left
            cur = stack.pop()
            count += 1
            if count == k:
                return cur.val
            cur = cur.right
        #O(n) #potentially all nodes
        #O(h) #stack at most height size
        
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

A path in a binary tree is a sequence of nodes where each pair of adjacent nodes in the sequence has an edge connecting them. A node can only appear in the sequence at most once. Note that the path does not need to pass through the root.

The path sum of a path is the sum of the node’s values in the path.

Given the root of a binary tree, return the maximum path sum of any non-empty path.

A
# Definition for a binary tree node.
# class TreeNode:
#     def \_\_init\_\_(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def maxPathSum(self, root: Optional[TreeNode]) -> int:
        res = [root.val] #initially, this root can be negative, so don't init to 0

        def dfs(root): #returns max WITHOUT splitting
            if not root:
                return 0
            leftMax = max(dfs(root.left), 0)
            rightMax = max(dfs(root.right), 0)
            res[0] = max(res[0], root.val + leftMax + rightMax)
            return root.val + max(leftMax, rightMax) 
        dfs(root)
        return res[0]
        #Runtime: O(n)
        #Space: O(h)

        
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

A trie (pronounced as “try”) or prefix tree is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker.

Implement the Trie class:

Trie() Initializes the trie object.
void insert(String word) Inserts the string word into the trie.
boolean search(String word) Returns true if the string word is in the trie (i.e., was inserted before), and false otherwise.
boolean startsWith(String prefix) Returns true if there is a previously inserted string word that has the prefix prefix, and false otherwise.

A
class TrieNode:
    def \_\_init\_\_(self):
        self.children = {}
        self.isWord = False
class Trie:

    def \_\_init\_\_(self):
        self.root = TrieNode()
        

    def insert(self, word: str) -> None:
        cur = self.root
        for c in word:
            if c not in cur.children:
                cur.children[c] = TrieNode()
            cur = cur.children[c]
        cur.isWord = True
        

    def search(self, word: str) -> bool:
        cur = self.root
        for c in word:
            if c not in cur.children:
                return False
            cur = cur.children[c]
        return cur.isWord
        

    def startsWith(self, prefix: str) -> bool:
        cur = self.root
        for c in prefix:
            if c not in cur.children:
                return False
            cur = cur.children[c]
        return True
    #O(m) for all operations
    #O(m) insert, O(1) otherwise.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

Design a data structure that supports adding new words and finding if a string matches any previously added string.

Implement the WordDictionary class:

WordDictionary() Initializes the object.
void addWord(word) Adds word to the data structure, it can be matched later.
bool search(word) Returns true if there is any string in the data structure that matches word or false otherwise. word may contain dots ‘.’ where dots can be matched with any letter.

A
class TrieNode:
    def \_\_init\_\_(self):
        self.children = {}
        self.isWord = False
class WordDictionary:

    def \_\_init\_\_(self):
        self.root = TrieNode()
        

    def addWord(self, word: str) -> None:
        cur = self.root
        for c in word:
            if c not in cur.children:
                cur.children[c] = TrieNode()
            cur = cur.children[c]
        cur.isWord = True
        
    def search(self, word: str) -> bool:
        def dfs(j, root):
            cur = root
            #have a loop that loops through everything from i to length of word, even though it's a dfs
            for i in range(j, len(word)):
                c = word[i]            
                if c == ".":
                    for child in cur.children.values():
                        #if any child node are matched, we return True
                        if dfs(i + 1, child): #skip current char
                            return True

                    #if can't matched any, return False
                    return False 
                else:
                    if c not in cur.children: #have this be a return!
                        return False #this is a return that will potentially end the recursion
                    cur = cur.children[c]
            return cur.isWord #return True if reached end of the input string, and is word
        return dfs(0, self.root)
    #Runtime: O(M) per add and init opeartion
        #Search: O(M) if no dots else O(26^M)
    #Space: O(26^ word length) -> O(1) for storage.
        #For Search: O(1) if no dots, else O(M) if recursion needed   
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.

A
class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort()
        res = [intervals[0]]
        for start, end in intervals[1:]:
            prevEnd = res[-1][1]
            if start > prevEnd:
                res.append([start, end])
            else:
                res[-1][1] = max(prevEnd, end)
        return res
        #O(n log n) due to sorting.
        #O(n)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

You are given an array of non-overlapping intervals intervals where intervals[i] = [starti, endi] represent the start and the end of the ith interval and intervals is sorted in ascending order by starti. You are also given an interval newInterval = [start, end] that represents the start and end of another interval.

Insert newInterval into intervals such that intervals is still sorted in ascending order by starti and intervals still does not have any overlapping intervals (merge overlapping intervals if necessary).

Return intervals after the insertion.

A
class Solution:
    def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
        #already told us it's sorted, but would sort here
        res = []
        #This one, you need to check one by one. Can't just arbitarily set res to res[intervals[]]
        for i in range(len(intervals)):
            #if newInterval precedes current interval, just add
            if newInterval[1] < intervals[i][0]:
                res.append(newInterval)
                #NOT i + 1. If newInterval precedes, then add newInterval in, then i onwards
                return res + intervals[i:]
            elif newInterval[0] > intervals[i][1]:
                res.append(intervals[i])
                #can't append newInterval yet - might overlap with something later
            else:
                #if truly overlapping, then we modify our newInterval
                newInterval = [min(newInterval[0], intervals[i][0]), max(newInterval[1], intervals[i][1])]
        #If we're at this point, we haven't returned yet, so we still have an interval.
        res.append(newInterval)
        return res
        #O(n)
        #O(n)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

Given an array of intervals intervals where intervals[i] = [starti, endi], return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping.

A
class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort() #sort by the start time to start out
        prevEnd = intervals[0][1]
        res = 0
        for start, end in intervals[1:]:
            if start >= prevEnd: #they don't overlap
                prevEnd = end
            else:  
                #if they do overlap, we want to MINIMIZE chance of collision
                #So, we should only keep the interval with earlier end time
                res += 1
                prevEnd = min(end, prevEnd)
        return res
        #O(n log n) #for sorting
        #O(1)

       
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
25
Q

Given an array of meeting time intervals where intervals[i] = [starti, endi], determine if a person could attend all meetings.

A
class Solution:
    def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
        intervals.sort()
        for i in range(len(intervals) - 1):
            if intervals[i + 1][0] < intervals[i][1]:
                return False
        return True
        #O(n log n)
        #O(1)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
26
Q

Given an array of meeting time intervals intervals where intervals[i] = [starti, endi], return the minimum number of conference rooms required.

A
class Solution:
    def minMeetingRooms(self, intervals: List[List[int]]) -> int:
        starts = []
        ends = []
        for start, end in intervals:
            starts.append(start)
            ends.append(end)
        starts.sort()
        ends.sort()
        #[0, 5, 15]
        #[30, 10, 20] -> [10, 20, 30] 
        #We sort both so we know when we can close a conference room 
        i, j = 0, 0
        #For counts, we know j won't increment our count, so iteration is just
        #until i < len(starts)
        maxCount = 0
        curCount = 0
        while i < len(starts):
            if starts[i] < ends[j]: #if nothing has ended yet
                curCount += 1
                maxCount = max(maxCount, curCount)
                i += 1
            else: #something has ended
                curCount -= 1
                j += 1
        return maxCount
        #O(nlogn) for sorting
        #O(n) for starts and ends.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
27
Q

You are climbing a staircase. It takes n steps to reach the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

A
class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 3:
            return n

        n1 = 2
        n2 = 3
        for i in range(4, n + 1):
            tmp = n1 + n2
            n1 = n2
            n2 = tmp
        return n2

        #O(n)
        #O(1)
        
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
28
Q

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security systems connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given an integer array nums representing the amount of money of each house, return the maximum amount of money you can rob tonight without alerting the police.

A
class Solution:
    def rob(self, nums: List[int]) -> int:
        n1, n2 = 0, 0
        for n in nums:
            tmp = max(n2, n1 + n) 
            n1 = n2
            n2 = tmp
        return n2
        #O(n)
        #O(1)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
29
Q

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, adjacent houses have a security system connected, and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given an integer array nums representing the amount of money of each house, return the maximum amount of money you can rob tonight without alerting the police.

A
class Solution:
    def rob(self, nums: List[int]) -> int:
        #nums[0] because in single element case, you need to rob something
        return max(nums[0], self.hr1(nums[:-1]), self.hr1(nums[1:]))

    def hr1(self, nums):
        n1, n2 = 0, 0
        for n in nums:
            tmp = max(n2, n1 + n)
            n1 = n2
            n2 = tmp
        return n2
    #Runtime: O(n)
    #Space: O(1)

        
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
30
Q

Given a string s, return the longest
palindromic

substring
in s.

A
class Solution:
    def longestPalindrome(self, s: str) -> str:
        maxLength = 0
        maxL, maxR = 0, 0
        for i in range(len(s)):
            #centers are at elts
            l, r = i, i #do this so you can have centers at in
            while l >= 0 and r < len(s) and s[l] == s[r]:
                if r - l + 1 > maxLength:
                    maxLength = r - l + 1
                    maxL, maxR = l, r
                l -= 1
                r += 1
            #centers are in between elts
            l, r = i, i + 1
            while l >= 0 and r < len(s) and s[l] == s[r]:
                if r - l + 1 > maxLength:
                    maxLength = r - l + 1
                    maxL, maxR = l, r
                l -= 1
                r += 1
        return s[maxL: maxR + 1]
        #O(n^2): O(2*n) centers, O(n) to go through each.
        #O(1)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
31
Q

Given a string s, return the number of palindromic substrings in it.

A string is a palindrome when it reads the same backward as forward.

A substring is a contiguous sequence of characters within the string.

A
class Solution:
    def countSubstrings(self, s: str) -> int:
        count = 0
        for i in range(len(s)): #expanding from centers
            #centers are at elt
            l, r = i, i 
            while l >= 0 and r < len(s) and s[l] == s[r]:
                count += 1
                l -= 1
                r += 1
            l, r = i, i + 1
            while l >= 0 and r < len(s) and s[l] == s[r]:
                count += 1
                l -= 1
                r += 1
        return count
        #O(n ^ 2) #O(2*n) centers, O(n) for each
        #O(1)
32
Q

You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money.

Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.

You may assume that you have an infinite number of each kind of coin.

A
class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [float('inf')] * (amount + 1)
        dp[0] = 0 #0 ways to make 0 coins
        for a in range(1, amount + 1):
            for c in coins:
                if a - c >= 0:
                    dp[a] = min(dp[a], 1 + dp[a - c]) #take at least 1 coin for this, then whatever minimum it is to make the rest
        return dp[amount] if dp[amount] != float('inf') else -1
        #O(ac)
        #O(a)
33
Q

Given a string s and a dictionary of strings wordDict, return true if s can be segmented into a space-separated sequence of one or more dictionary words.

Note that the same word in the dictionary may be reused multiple times in the segmentation.

A
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        dp = [False] * (len(s) + 1)
        dp[len(s)] = True #Just have this out of bounds base case True 
        for i in range(len(s) - 1, -1, -1):
            for w in wordDict:
                if i + len(w) <= len(s) and s[i: i + len(w)] == w: #fine to be len(s)
                    dp[i] = dp[i + len(w)]
                    #We always set the current position to be i + len(w)'s value.
                    if dp[i]: #if we found a successful word wordBreak
                        break #we don't need to try matching any further words anymore. 
                        #Go to the iteration for each i now
        return dp[0]
        #O(nw) #iterate through each elt, and w words
        #O(n) for dp

                
        
34
Q

Given an integer array nums, return the length of the longest strictly increasing subsequence

A
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        dp = [1] * len(nums)
        #the last element has a LIS of just 1.
        #no need to init (len(nums) + 1) and set as dp[len(nums)] = 0

        for i in range(len(nums) - 1, -1, -1):
            for j in range(i + 1, len(nums)):
                if nums[i] < nums[j]:
                    dp[i] = max(dp[i], 1 + dp[j])
        return max(dp)
        #O(n^2) #O(n) iteration through nums, O(n) to look at everything else afterwards
        #O(n)
35
Q

Given an integer array nums, find the
subarray
with the largest sum, and return its sum.

A
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        globalSum = nums[0]
        curSum = 0
        for n in nums:
            curSum = max(curSum + n, n) #Take the sum, or just ignore (this takes care of the 0s)
            globalSum = max(globalSum, curSum)
        return globalSum
        #O(n)
        #O(1)
36
Q

Given an m x n matrix, return all elements of the matrix in spiral order.

A
class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        top, bot = 0, len(matrix)
        left, right = 0, len(matrix[0])
        #bot and right are initialized to be out of bounds
        res = []

        #top < bot and left < right: this properly sets top and 
        #left to AT MOST be the bottom row and the rightmost column
        while top < bot and left < right:
            for c in range(left, right):
                res.append(matrix[top][c])
            top += 1

            for r in range(top, bot):
                res.append(matrix[r][right - 1]) #right - 1 since right is OOB
            right -= 1
            if not (top < bot and left < right): #have to both be true.
                break #Not return []. Just break because we're done.
            
            for c in range(right - 1, left - 1, -1): #don't go until -1. Go until left - 1.
                res.append(matrix[bot - 1][c]) #because bot is OOB
            bot -= 1

            for r in range(bot - 1, top - 1, -1): #go until top - 1!
                res.append(matrix[r][left])
            left += 1
        return res
        #O(mn)
        #O(1)
37
Q

There is a robot on an m x n grid. The robot is initially located at the top-left corner (i.e., grid[0][0]). The robot tries to move to the bottom-right corner (i.e., grid[m - 1][n - 1]). The robot can only move either down or right at any point in time.

Given the two integers m and n, return the number of possible unique paths that the robot can take to reach the bottom-right corner.

A
class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        row = [1] * n #initialize the bottom row 
        for r in range(m - 2, -1, -1): #since the bottom row is all 1s
            newRow = [1] * n #initialize to all 1s in the current row to start. This also 
                            #takes into account the rightmost col being 1
            for c in range(n - 2, -1, -1): #We don't need to compute rightmost col
                newRow[c] = row[c] + newRow[c + 1] #bottom plus right
            row = newRow
        return row[0] #this is dp[0][0] basically
        #O(mn)
        #O(n) #number of columns
38
Q

Given two strings text1 and text2, return the length of their longest common subsequence. If there is no common subsequence, return 0.

A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.

For example, “ace” is a subsequence of “abcde”.
A common subsequence of two strings is a subsequence that is common to both strings.

A
class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        dp = [[0] * (len(text2) + 1) for i in range(len(text1) + 1)]
        for i in range(len(text1) - 1, -1, -1):
            for j in range(len(text2) - 1, -1, -1):
                if text1[i] == text2[j]:
                    #dp[i][j] = max(dp[i][j], 1 + dp[i + 1][j + 1])
                    dp[i][j] = 1 + dp[i + 1][j + 1] #no need to do max here. You know it'll be longest common subsequence.
                else:
                    dp[i][j] = max(dp[i][j + 1], dp[i + 1][j])
        return dp[0][0]
        #O(mn)
        #O(mn)        
39
Q

Given an m x n grid of characters board and a string word, return true if word exists in the grid.

The word can be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once.

A
class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        ROWS, COLS = len(board), len(board[0])
        path = set()
        #always return true or False
        #Order so check for i successfully reaching the end
        #no need for curStr
        #path set is basically visit set.
        def backtrack(r, c, i):
            if i == len(word):
                return True #successfully able to make it to end. Return True
            if r < 0 or r == ROWS or c < 0 or c == COLS or (r, c) in path or board[r][c] != word[i]:
                return False            
            path.add((r, c))
            res = backtrack(r - 1, c, i + 1) or backtrack(r + 1, c, i + 1) or backtrack(r, c - 1, i + 1) or backtrack(r, c + 1, i + 1)
            path.remove((r, c))
            return res
            
        for r in range(ROWS):
            for c in range(COLS):
                if backtrack(r, c, 0):
                    return True
        return False

        #O(mn*4^w) #we start at each potential cell, and go in all 4 directions, for w length
        #O(mn)
40
Q

Write a function that takes an unsigned integer and returns the number of ‘1’ bits it has (also known as the Hamming weight).

A
class Solution:
    def hammingWeight(self, n: int) -> int:
        count = 0
        while n:
            count += n & 1
            n = n >> 1
        #right shift means move 1 to the right so 1 becomes 0
        return count
        #O(n)
        #O(1)
41
Q

Given an unsorted array of integers nums, return the length of the longest consecutive elements sequence.

You must write an algorithm that runs in O(n) time.

A
class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        numSet = set(nums)
        maxLength = 0
        for n in nums:
            #start of sequence
            if n - 1 not in numSet:
                curLength = 0
                #Keep extending this sequence
                while n + curLength in numSet:
                    curLength += 1
                maxLength = max(maxLength, curLength)
        return maxLength
        #O(n)
        #O(n)
42
Q

You are given the head of a singly linked-list. The list can be represented as:

L0 → L1 → … → Ln - 1 → Ln
Reorder the list to be on the following form:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
You may not modify the values in the list’s nodes. Only nodes themselves may be changed.

A
# Definition for singly-linked list.
# class ListNode:
#     def \_\_init\_\_(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reorderList(self, head: Optional[ListNode]) -> None:
        """
        Do not return anything, modify head in-place instead.
        """
        slow, fast = head, head.next
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        cur = slow.next #DO NOT do cur = fast. Fast is not the node after slow
        slow.next = None #break the link 
        prev = None
        while cur:
            tmp = cur.next
            cur.next = prev
            prev = cur 
            cur = tmp
        first, second = head, prev
        while second: #because second is always shorter or equal in length
            tmpOne = first.next
            tmpTwo = second.next
            first.next = second
            second.next = tmpOne

            first = tmpOne
            second = tmpTwo
        return head
        #O(N)
        #O(1)
43
Q

You are given an array prices where prices[i] is the price of a given stock on the ith day.

You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock.

Return the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return 0.

A
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        l = 0
        maxProfit = 0

        for r in range(1, len(prices)):
            if prices[r] - prices[l] > 0:
                maxProfit = max(maxProfit, prices[r] - prices[l])
            else:
                l = r
        return maxProfit
        #O(n)
        #O(1)
44
Q

You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]).

Find two lines that together with the x-axis form a container, such that the container contains the most water.

Return the maximum amount of water a container can store.

Notice that you may not slant the container.

A
class Solution:
    def maxArea(self, height: List[int]) -> int:
        l, r = 0, len(height) - 1
        maxArea = 0
        while l < r: #because doesn't need to calc if they are the same
            curArea = min(height[l], height[r]) * (r - l)
            maxArea = max(curArea, maxArea)
            if height[l] < height[r]:
                l += 1
            else:
                r -= 1
        return maxArea
        #O(n)
        #O(1)
        
45
Q

Design an algorithm to encode a list of strings to a string. The encoded string is then sent over the network and is decoded back to the original list of strings. Implement the encode and decode methods.

A
class Codec:
    def encode(self, strs: List[str]) -> str:
        """Encodes a list of strings to a single string.
        """
        # format is number#string etc..
        res = ""
        for s in strs:
            res += str(len(s)) + '#' + s
        return res

        

    def decode(self, s: str) -> List[str]:
        """Decodes a single string to a list of strings.
        """
        res = []
        i = 0

        while i < len(s):
            j = i
            #don't need while j < len(s), because the way 
            #we encoded it, it will definitely be in bounds.
            while s[j] != '#':
                j += 1
            strLength = int(s[i: j]) #j is where the pound is

            res.append(s[j + 1: j + 1 + strLength])
            i = j + 1 + strLength #advance the pointer here
        return res
    #O(n) for encode and decode
    #O(n for both too)
        

Your Codec object will be instantiated and called as such:
# codec = Codec()
# codec.decode(codec.encode(strs))
46
Q

Given a string s, find the length of the longest
substring
without repeating characters.

A
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        charSet = set()
        l = 0
        maxLength = 0
        for r in range(len(s)):
            while s[r] in charSet:
                charSet.remove(s[l])
                l += 1
            charSet.add(s[r])
            maxLength = max(maxLength, r - l + 1)
        return maxLength
        #O(n)
        #O(n)

        
47
Q

You have a graph of n nodes. You are given an integer n and an array edges where edges[i] = [ai, bi] indicates that there is an edge between ai and bi in the graph.

Return the number of connected components in the graph.

A
class Solution:
    def countComponents(self, n: int, edges: List[List[int]]) -> int:
        rank = [1] * n #n = 5 -> 0 to 4
        par = [i for i in range(n)]

        def find(n1):
            p = par[n1] #find the parent
            while p != par[p]:
                par[p] = par[par[p]] #path compression
                p = par[p]
            return p
        def union(n1, n2):
            p1 = find(n1)
            p2 = find(n2)
            if p1 == p2:
                return 0
            if rank[p1] < rank[p2]:
                par[p1] = p2
                rank[p2] += rank[p1]
            else:
                par[p2] = p1
                rank[p1] += rank[p2]
            return 1 #successful union
        res = n #we start with n connected components
        for n1, n2 in edges:
            res -= union(n1, n2)
        return res #this is the number of components after union.
        #Runtime: O(|E|*ackerman(V)) -> O(|E|) -> basically O(V)
        #Space: O(V) for par and rank arrays
        
48
Q

You are given a string s and an integer k. You can choose any character of the string and change it to any other uppercase English character. You can perform this operation at most k times.

Return the length of the longest substring containing the same letter you can get after performing the above operations.

A
class Solution:
    def characterReplacement(self, s: str, k: int) -> int:
        counts = {}
        l = 0
        maxLength = 0
        for r in range(len(s)):
            counts[s[r]] = counts.get(s[r], 0) + 1
            while r - l + 1 - max(counts.values()) > k:
                counts[s[l]] -= 1
                l += 1
            maxLength = max(maxLength, r - l + 1)
        return maxLength
        #O(26 * n) -> O(n)
        #O(26) -> O(1)
        
49
Q

There is an m x n rectangular island that borders both the Pacific Ocean and Atlantic Ocean. The Pacific Ocean touches the island’s left and top edges, and the Atlantic Ocean touches the island’s right and bottom edges.

The island is partitioned into a grid of square cells. You are given an m x n integer matrix heights where heights[r][c] represents the height above sea level of the cell at coordinate (r, c).

The island receives a lot of rain, and the rain water can flow to neighboring cells directly north, south, east, and west if the neighboring cell’s height is less than or equal to the current cell’s height. Water can flow from any cell adjacent to an ocean into the ocean.

Return a 2D list of grid coordinates result where result[i] = [ri, ci] denotes that rain water can flow from cell (ri, ci) to both the Pacific and Atlantic oceans.

A
class Solution:
    def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
        ROWS, COLS = len(heights), len(heights[0])
        pac, atl = set(), set()
        def dfs(r, c, visit, prevHeight):
            if r < 0 or r == ROWS or c < 0 or c == COLS or (r, c) in visit or heights[r][c] < prevHeight:
                return
            visit.add((r, c))
            dfs(r - 1, c, visit, heights[r][c])
            dfs(r + 1, c, visit, heights[r][c])
            dfs(r, c - 1, visit, heights[r][c])
            dfs(r, c + 1, visit, heights[r][c])
        #first and last row
        #can also pass -1 for the last argument, but this is safe 
        #since we can visit if curHeight is >= prevHeight
        for r in range(ROWS):
            dfs(r, 0, pac, heights[r][0])
            dfs(r, COLS - 1, atl, heights[r][COLS - 1])
        #first and last col
        #can also pass -1 for the last argument, but this is safe 
        #since we can visit if curHeight is >= prevHeight
        for c in range(COLS):
            dfs(0, c, pac, heights[0][c])
            dfs(ROWS - 1, c, atl, heights[ROWS - 1][c])
        return list(pac.intersection(atl))
        #O(mn)
        #O(mn)
        
50
Q

Given the head of a linked list, remove the nth node from the end of the list and return its head.

Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]

A
# Definition for singly-linked list.
# class ListNode:
#     def \_\_init\_\_(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        dummy = ListNode(0, head)
        left = dummy
        right = head
        while n > 0:
            right = right.next
            n -= 1
        while right:
            left = left.next
            right = right.next
        left.next = left.next.next
        return dummy.next
        #Idea: So we want right to skip n steps ahead.
        #BUT, we want left to be right before the node to delete, when right goes OOB.
        #Therefore, we set left to be dummy first, and set
        #Right to be n steps after head. That way left lands
        #right in front of the node to delete.
        #O(n)
        #O(1)
51
Q

You are given an array of k linked-lists lists, each linked-list is sorted in ascending order.

Merge all the linked-lists into one sorted linked-list and return it.

A
# Definition for singly-linked list.
# class ListNode:
#     def \_\_init\_\_(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        if not lists or len(lists) == 0:
            return None
        while len(lists) > 1:
            mergedLists = []
            for i in range(0, len(lists), 2):
                l1 = lists[i]
                l2 = lists[i + 1] if i + 1 < len(lists) else None
                mergedList = self.mergeLists(l1, l2)
                mergedLists.append(mergedList)
            lists = mergedLists
        return lists[0]
    def mergeLists(self, l1, l2):
        dummy = ListNode()
        tail = dummy
        while l1 and l2:
            if l1.val < l2.val:
                tail.next = l1
                l1 = l1.next
            else:
                tail.next = l2
                l2 = l2.next
            tail = tail.next
        if l1:
            tail.next = l1
        if l2:
            tail.next = l2
        return dummy.next
    #Runtime: O(n log k) # N for total number of elts, k for number of linked lists that we split into 2 every time
    #Space: O(n) for result when we store it in merged lists to replace lists with. 

        
        
52
Q

You are given an integer array nums. You are initially positioned at the array’s first index, and each element in the array represents your maximum jump length at that position.

Return true if you can reach the last index, or false otherwise.

A
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        goal = len(nums) - 1
        for i in range(len(nums) - 1, -1, -1):
            if i + nums[i] >= goal:
                goal = i
        return goal == 0
        #Runtime: O(n)
        #Space: O(1)
        
53
Q

Given an array nums containing n distinct numbers in the range [0, n], return the only number in the range that is missing from the array.

A
class Solution:
    def missingNumber(self, nums: List[int]) -> int:
		    n = len(nums)
        #sum from 0 to n same as 1 to n.
        gaussSum = n * (n + 1) // 2
        arrSum = sum(nums)
        return gaussSum - arrSum
        #O(n)
        #O(1)
			  # other solution below
        # ans = 0 
        # #len(nums is n)
        # for i in range(len(nums) + 1):
        #     ans ^= i
        # for n in nums:
        #     ans ^= n
        # return ans
        #O(n)
        #O(1) 
        #XOR solution: 0 ^ x = x 
        # x ^ x = 0, because XOR evaluates to 1 only if different.
        #SO basically... XOR everything from 0 to n with all the numbers
        #Whichever remains is the missing number
				
				

        
54
Q

Reverse bits of a given 32 bits unsigned integer.

A
class Solution:
    def reverseBits(self, n: int) -> int:
        res = 0
        for i in range(32):
            bit = (n >> i) & 1 #Take the n value and shift it to right by i to get the ith place, then AND by 1
            res = res | (bit << (31 - i)) #shift the bit 31 - i places to the left, THEN take the or
        #right shift: 01 -> 00
        #left shift: 01 -> 10
        #Think: how does the 1 shift?
        return res
        #O(32) -> O(1)
        #O(32) -> O(1)
        
55
Q

The median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value, and the median is the mean of the two middle values.

For example, for arr = [2,3,4], the median is 3.
For example, for arr = [2,3], the median is (2 + 3) / 2 = 2.5.
Implement the MedianFinder class:

MedianFinder() initializes the MedianFinder object.
void addNum(int num) adds the integer num from the data stream to the data structure.
double findMedian() returns the median of all elements so far. Answers within 10-5 of the actual answer will be accepted.

A
class MedianFinder:

    def \_\_init\_\_(self):
        self.small = [] #maxHeap of values
        self.large = [] #minHeap of values
        

    def addNum(self, num: int) -> None:
        #1) Add to the heap that makes sense. Potentially add to large if possible
        if self.large and num > self.large[0]:
            heapq.heappush(self.large, num)
        else:
            heapq.heappush(self.small, -num)

        #2) Correct for lengths being messed up
        if len(self.small) > len(self.large) + 1:
            val = -heapq.heappop(self.small)
            heapq.heappush(self.large, val)
        
        if len(self.large) > len(self.small) + 1:
            val = -heapq.heappop(self.large)
            heapq.heappush(self.small, val)

    def findMedian(self) -> float:
        if len(self.small) > len(self.large):
            return -self.small[0]
        elif len(self.large) > len(self.small):
            return self.large[0]
        else:
            return (-self.small[0] + self.large[0]) / 2
    #Runtime: O(log n) for add but O(1) for findMedian
    #Space: O(n) for heap

# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()
56
Q

Given an m x n integer matrix matrix, if an element is 0, set its entire row and column to 0’s.

You must do it in place.

A
class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        ROWS, COLS = len(matrix), len(matrix[0])
        rowZero = False
        #The first row of this matrix indicates whether the COLUMNS need to be 0
        #The first column of the matrix (except origin) indicates whether the ROW needs to be zero 
        #The rowZero variable is for the first row zero

        #1) Get row and cols 0s on top and left 
        for r in range(ROWS):
            for c in range(COLS):
                if matrix[r][c] == 0:
                    if r > 0:
                        matrix[r][0] = 0
                    else:
                        rowZero = True
                    matrix[0][c] = 0
        #2) Zero out the subgrid
        for r in range(1, ROWS):
            for c in range(1, COLS):
                if matrix[r][0] == 0 or matrix[0][c] == 0:
                    matrix[r][c] = 0
        #3 Zero out first column
        #We did the if-conditional check FIRST, since it's possible nothing is needed here and so we don't need to do for loops.

        if matrix[0][0] == 0:
            for r in range(ROWS):
                matrix[r][0] = 0
        #4 Zero out first row
        if rowZero:
            for c in range(COLS):           
                matrix[0][c] = 0
        #Runtime: O(mn)
        #Space: O(1)
        

        
57
Q

You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).

You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

A
class Solution:
    def rotate(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        left, right = 0, len(matrix) - 1
        while left < right:
            top, bot = left, right #because it's a square matrix
            #The number of rotations is range(right - left.)
            #Example: 2 - 0 is 2 so 0 and 1 -> 2 rotations
            for i in range(right - left):
                topLeft = matrix[top][left + i] #increment i to go to the right
                matrix[top][left + i] = matrix[bot - i][left]
                matrix[bot - i][left] = matrix[bot][right - i]
                matrix[bot][right - i] = matrix[top + i][right]
                matrix[top + i][right] = topLeft 
            left += 1
            right -= 1
        #Runtime: O(n^2)
        #Space: O(1) in place

        
58
Q

Given a reference of a node in a connected undirected graph.

Return a deep copy (clone) of the graph.

Each node in the graph contains a value (int) and a list (List[Node]) of its neighbors.

class Node {
public int val;
public List<Node> neighbors;
}</Node>

A
"""
# Definition for a Node.
class Node:
    def \_\_init\_\_(self, val = 0, neighbors = None):
        self.val = val
        self.neighbors = neighbors if neighbors is not None else []
"""

class Solution:
    def cloneGraph(self, node: 'Node') -> 'Node':
        oldToClone = {}
        def dfs(node):
            if node in oldToClone:
                return oldToClone[node]
            clone = Node(node.val)
            oldToClone[node] = clone
            for nei in node.neighbors:
                clone.neighbors.append(dfs(nei))
            return clone
        return dfs(node) if node else None
        #O(V + E) go through all nodes and edges
        #O(V) for hashmap

        
59
Q

Given two strings s and t of lengths m and n respectively, return the minimum window
substring
of s such that every character in t (including duplicates) is included in the window. If there is no such substring, return the empty string “”.

The testcases will be generated such that the answer is unique.

Input: s = “ADOBECODEBANC”, t = “ABC”
Output: “BANC”

A
class Solution:
    def minWindow(self, s: str, t: str) -> str:
        if t == "":
            return ""
        countT = {}
        for c in t:
            countT[c] = countT.get(c, 0) + 1
        window = {}
        have, need = 0, len(countT)
        l = 0
        res, resLength = [-1, -1], float('inf')
        for r in range(len(s)):
            #add to window
            c = s[r]
            window[c] = window.get(c, 0) + 1
            if c in countT and window[c] == countT[c]:
                have += 1
            while have == need:
                #Capture the valid length so far.
                if r - l + 1 < resLength:
                    res = [l, r]
                    resLength = r - l + 1
                window[s[l]] -= 1
                
                if s[l] in countT and window[s[l]] < countT[s[l]]:
                    have -= 1
                l += 1
        l, r = res
        return s[l: r + 1] if resLength != float('inf') else ""
        #O(n)
        #O(26) -> O(1)
60
Q

There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai.

For example, the pair [0, 1], indicates that to take course 0 you have to first take course 1.
Return true if you can finish all courses. Otherwise, return false.

A
class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        prereqMap = {i:[] for i in range(numCourses)} #course to prereq
        for crs, pre in prerequisites:
            prereqMap[crs].append(pre)
        
        visit = set()
        cycle = set()
        #res = []
        
        def dfs(crs):
            if crs in cycle:
                return False
            if crs in visit:
                return True
            cycle.add(crs)
            for pre in prereqMap[crs]:
                if not dfs(pre):
                    return False
            cycle.remove(crs)
            #res.append(crs) 
            visit.add(crs)
            return True

        #Same as course schedule II, just no output list and we return False instead of []
        for crs in range(numCourses):
            if not dfs(crs):
                #return []
                return False
        return True
        #return res
        #O(V + E)
        #O(V + E) for prereq map
61
Q

A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers.

Given a string s, return true if it is a palindrome, or false otherwise.

A
class Solution:
    def isPalindrome(self, s: str) -> bool:
        l, r = 0, len(s) - 1
        while l < r:
            while l < r and not self.isAlphaNum(s[l]):
                l += 1
            while l < r and not self.isAlphaNum(s[r]):
                r -= 1
            if s[l].lower() != s[r].lower():
                return False
            l += 1
            r -= 1
        return True
        #O(n)
        #O(1)
        
    def isAlphaNum(self, c):
        return ord('a') <= ord(c) <= ord('z') or ord('A') <= ord(c) <= ord('Z') or ord('0') <= ord(c) <= ord('9')
62
Q

Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target. You may return the combinations in any order.

The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the
frequency
of at least one of the chosen numbers is different.

The test cases are generated such that the number of unique combinations that sum up to target is less than 150 combinations for the given input.

A
class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        res = []
        curComb = []
        def dfs(i, curTotal):
            #This always acts as our catch-all first
            if curTotal == target:
                res.append(curComb.copy())
                return

            if i == len(candidates) or curTotal > target:
                return

            #1) Includes candidates[i] and potentially try again at candidates[i]
            curComb.append(candidates[i])
            dfs(i, curTotal + candidates[i]) #can reuse i.
            curComb.pop()

            #2) Don't include any candidates[i]. Move on to next candidate. Don't do candidates[i + 1]
            dfs(i + 1, curTotal)
        dfs(0, 0)
        return res
        #Runtime: O(2^|T/M|) #2 choices to include or not include, and we can keep going down based on the target value, divided by the minimum value of candidates.
                    #Also multiply by N for the copy... but we won't say this for interviews unless asked.
        #Space: O(|T/M|) recursion stack, bounded by minimum value of candidates for how deeply we go.

        
63
Q

There is an integer array nums sorted in ascending order (with distinct values).

Prior to being passed to your function, nums is possibly rotated at an unknown pivot index k (1 <= k < nums.length) such that the resulting array is [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]] (0-indexed). For example, [0,1,2,4,5,6,7] might be rotated at pivot index 3 and become [4,5,6,7,0,1,2].

Given the array nums after the possible rotation and an integer target, return the index of target if it is in nums, or -1 if it is not in nums.

You must write an algorithm with O(log n) runtime complexity.

A
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        l, r = 0, len(nums) - 1
        while l <= r:
            mid = l + (r - l) // 2
            if nums[mid] == target:
                return mid
            #In left partition
            if nums[mid] >= nums[l]:
                if target > nums[mid] or target < nums[l]:
                    #go right
                    l = mid + 1
                else:
                    r = mid - 1
            #In right partition
            else:
                #go left
                if target < nums[mid] or target > nums[r]:
                    r = mid - 1
                else:
                    l = mid + 1
        return -1
        #O(log n)
        #O(1)
        
64
Q

Suppose an array of length n sorted in ascending order is rotated between 1 and n times. For example, the array nums = [0,1,2,4,5,6,7] might become:

[4,5,6,7,0,1,2] if it was rotated 4 times.
[0,1,2,4,5,6,7] if it was rotated 7 times.
Notice that rotating an array [a[0], a[1], a[2], …, a[n-1]] 1 time results in the array [a[n-1], a[0], a[1], a[2], …, a[n-2]].

Given the sorted rotated array nums of unique elements, return the minimum element of this array.

You must write an algorithm that runs in O(log n) time.

A
class Solution:
    def findMin(self, nums: List[int]) -> int:
        res = float('inf')
        l, r = 0, len(nums) - 1
        while l <= r:
            if nums[l] <= nums[r]:
                res = min(res, nums[l])
                #We don't just take nums[l]. We compare it to result, since the binary search can do funny things and make it so nums[l] here to not necessary be the minimum
                break 
            mid = l + (r - l) // 2

            #Potentially updated minimum
            res = min(res, nums[mid])
            
            #Left sorted portion
            if nums[mid] >= nums[l]:
                l = mid + 1
            else:
                r = mid - 1
        return res
        #O(log n)
        #O(1)

        
65
Q

Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.

Notice that the solution set must not contain duplicate triplets.

A
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums.sort()

        res = []
        for i, n in enumerate(nums):
            #We don't want duplicate triplets, so ignore the duplicate elts
            #But of course, we don't want to do this for the very first elt, so...
            if i > 0 and nums[i] == nums[i - 1]:
                continue
            #Two sum sorted basically. We are ANCHORING the i, so we are forming
            #all unique triplets for which nums[i] is ALWAYS the first element.
            l, r = i + 1, len(nums) - 1
            while l < r: #Can't be equal.
                total = n + nums[l] + nums[r]
                if total < 0:
                    l += 1
                elif total > 0:
                    r -= 1
                else: #if they are equal
                    res.append([n, nums[l], nums[r]])
                    l += 1
                    #l can also encounter duplicates. So we keep advancing it, while
                    #it's still l < r and duplicate value
                    while l < r and nums[l] == nums[l - 1]:  
                        l += 1
        return res
        #O(n log n + n ^ 2) for sorting, then n anchors of i, for which we do O(n) comparison for two pointer 3-sum
        #O(1) no additional memory except for result.

        
66
Q

Given two integer arrays preorder and inorder where preorder is the preorder traversal of a binary tree and inorder is the inorder traversal of the same tree, construct and return the binary tree.

3. 9. N. N.20.15.N.N.7.N.N (structure)

Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]

A
# Definition for a binary tree node.
# class TreeNode:
#     def \_\_init\_\_(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        if not preorder or not inorder:
            return None
        #1) The first node of the preorder traversal is always the root
        root = TreeNode(preorder[0])

        #2) Inside the inorder traversal, find the idx of that. 
        #The number of elts to the left tells you how many nodes are in left subtree
        #The number of elts to the right tells you have many ndodes are in right subtree
        mid = inorder.index(root.val)
        #3) Example mid value: mid = 1, so left section is "0" and right is "2-4"
        # Left has 1 value in left subtree, right has three values in right subtree

        #4a) ROOT.LEFT: This means that in preorder, the next 1 values is part of left subtree. Therefore, we go from preorder[1:1] but due to Python exclusion, we do [1: 1 + 1]
        #   In inorder, obviously section to the left of mid is which elts are the elts in left subtree. So we go inorder[:mid] #exclude mid.

        #4b) ROOT.RIGHT: This means in preorder, the values 2 and beyond are part of right subtree. Therefore, we go from [1 + 1:] in preorder
        # Inorder, obviously, the portion to the right of mid denotes the elts to the right subtree
        #Therefore, we have preorder[mid + 1:] 

        root.left = self.buildTree(preorder[1: mid + 1], inorder[:mid])
        root.right = self.buildTree(preorder[mid + 1:], inorder[mid + 1:])
        return root
        #Runtime: O(n) for O(n^2) if count indexing and slicing
        #Space: O(H) recursion for height of tree
        #Example to run: preorder [3 9 20 15 7], inorder[ 9 3 15 20 7]
        #mid is 1.

 
        
67
Q

Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.

A
# Definition for a binary tree node.
# class TreeNode(object):
#     def \_\_init\_\_(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Codec:

    def serialize(self, root):
        """Encodes a tree to a single string.
        
        :type root: TreeNode
        :rtype: str
        """
        res = []
        def dfs(root):
            if not root:
                res.append("N")
                return
            res.append(str(root.val))
            dfs(root.left)
            dfs(root.right)
        dfs(root)
        return ",".join(res) #Don't initialize a res and keep adding to it. That is costly since Strings are immutable!
        
        
    # 12NN34NN5NN

    def deserialize(self, data):
        """Decodes your encoded data to tree.
        
        :type data: str
        :rtype: TreeNode
        """
        self.i = 0
        vals = data.split(',')
        def dfs():
            if vals[self.i] == "N":
                self.i += 1
                return None
            root = TreeNode(int(vals[self.i]))
            self.i += 1 #this gets incremented every recursive call
            #we know we'll transition over at some point, because "N" will return.
            root.left = dfs() #Set left to be subtree anchored at left
            root.right = dfs()
            return root
        return dfs()
    #O(n) for both ops
    #O(n) for serialize, O(H) for deserialize recursion stack (or O(n) if you count vals)

        

Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))
68
Q

Given an m x n board of characters and a list of strings words, return all words on the board.

Each word must be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.

A
class TrieNode:
    def \_\_init\_\_(self):
        self.children = {}
        self.isWord = False
    #Do this when you have multiple words added at once, and you need a reference to the root!
    def addWord(self, word):
        cur = self
        for c in word:
            if c not in cur.children:
                cur.children[c] = TrieNode()
            cur = cur.children[c]
        cur.isWord = True
class Solution:
    def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
        root = TrieNode()
        for word in words:
            root.addWord(word)
        
        visit = set()
        ROWS, COLS = len(board), len(board[0])
        res = set() #avoid duplicate words with a set.
        def dfs(r, c, curNode, curString):
            if r < 0 or r == ROWS or c < 0 or c == COLS or (r, c) in visit or board[r][c] not in curNode.children:
                return

            visit.add((r, c))
            curString += board[r][c]
            #When passed in, curNode is NOT yet up to date to the current letter. So we update it now.
            curNode = curNode.children[board[r][c]]
            if curNode.isWord:
                res.add(curString)
            dfs(r - 1, c, curNode, curString)
            dfs(r + 1, c, curNode, curString)
            dfs(r, c - 1, curNode, curString)
            dfs(r, c + 1, curNode, curString)
            #backtrack - no longer have visited board[r][c]
            visit.remove((r, c))
        for r in range(ROWS):
            for c in range(COLS):
                dfs(r, c, root, "")
        return list(res) 
        #O(mn * 4^|longest word|) #mn cells that are the start, and 4^w for recursion
        #O(mn)
69
Q

You have a graph of n nodes labeled from 0 to n - 1. You are given an integer n and a list of edges where edges[i] = [ai, bi] indicates that there is an undirected edge between nodes ai and bi in the graph.

Return true if the edges of the given graph make up a valid tree, and false otherwise.

A
class Solution:
    def validTree(self, n: int, edges: List[List[int]]) -> bool:
        adjMap = {i:[] for i in range(n)}
        for u, v in edges:
            adjMap[u].append(v)
            adjMap[v].append(u)
        visit = set()
        def dfs(node, prevNode):
            if node in visit:
                return False
            visit.add(node)
            for nei in adjMap[node]:
                #If we came from a node, we don't want to visit that node immediately again and call that a cycle.
                if nei == prevNode:
                    continue
                if not dfs(nei, node):
                    return False
            return True
        #Don't need to iterate through from 0 to n - 1 in a for loop.
        #If it's a tree, you're expected to be able to start at 0 and reach
        #everything
        return dfs(0, -1) and len(visit) == n
        #Properties of tree: 1) no cycles 2) can reach every node 3) n - 1 edges [no need to check for this third condition tho]
        #Runtime: O(V + E) for the graph traversal via DFS
        #Space: O(V + E) recursion stack and adjacency list
70
Q

Given an integer array nums, find a
subarray
that has the largest product, and return the product.

The test cases are generated so that the answer will fit in a 32-bit integer.

A
class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        res = nums[0] #at least it's first elt of nums
        curMin, curMax = 1, 1

        for n in nums:
            tmp = curMax * n
            curMax = max(curMax * n, curMin * n, n) #example of third case is if product so far was 0
            #need a tmp variable since we updated curMax already
            curMin = min(tmp, curMin * n, n)
            res = max(res, curMax)
        return res
        #O(n)
        #O(1)
        #Notes: 0s disrupt the chain - but it's ok because we will set it to curMax = n, etc. same with curMin. It's either 0*n, 0*n, or n -> 0 if positive n
        #
        
71
Q

A message containing letters from A-Z can be encoded into numbers using the following mapping:

‘A’ -> “1”
‘B’ -> “2”

‘Z’ -> “26”
To decode an encoded message, all the digits must be grouped then mapped back into letters using the reverse of the mapping above (there may be multiple ways). For example, “11106” can be mapped into:

“AAJF” with the grouping (1 1 10 6)
“KJF” with the grouping (11 10 6)
Note that the grouping (1 11 06) is invalid because “06” cannot be mapped into ‘F’ since “6” is different from “06”.

Given a string s containing only digits, return the number of ways to decode it.

The test cases are generated so that the answer fits in a 32-bit integer.

A
class Solution:
    def numDecodings(self, s: str) -> int:
        #maps the index to the number of ways to decode it
        #Only one way to decode an empty String
        dp = {len(s): 1}
        for i in range(len(s) - 1, -1, -1):
            #If we decode i as single digit...
            if s[i] == "0":
                dp[i] = 0
            else: #valid decoding of single digit. From this index i onwards, the number of decodings is exactly the number of decodings for dp[i + 1] (since this current digit will be a part of every of THOSE encodings)
                dp[i] = dp[i + 1] 
            #Add on the result if we decode as two digits
            if i + 1 < len(s) and (s[i] == "1" or s[i] == "2" and s[i + 1] in "0123456"):
                #Add on the number of decodings if decode current i i+1 as double digit, since this double digit will be a part of THOSE encodings
                dp[i] += dp[i + 2] 
        return dp[0]
        #O(n)
        #O(n)

        
72
Q

Given an integer n, return an array ans of length n + 1 such that for each i (0 <= i <= n), ans[i] is the number of 1’s in the binary representation of i.

A
class Solution:
    def countBits(self, n: int) -> List[int]:
    # 0001 1 + dp[i - 1]
    # 0010 1 + dp[i - 2] #offset is the new significant bit
    # 0011
    # 0100
    # 0101
    # 0110
    # 0111
    # 1000 1 + dp[i - 8] #offset is the new LSB
        dp = [0] * (n + 1)
        #offset is set to the most sig bit (power of 2) we reached so far
        offset = 1
        for i in range(1, n + 1):
            if 2 * offset == i:
                offset = i
            dp[i] = 1 + dp[i - offset]
        return dp
        #O(n)
        #O(n)
        
73
Q

Given two integers a and b, return the sum of the two integers without using the operators + and -.

A
class Solution {
    public int getSum(int a, int b) {
        //Remember: a and b are binary digit strings!
        while (b != 0) {
            int carries = (a & b) << 1;
            a = a ^ b; //a XOR b
            b = carries;
            // Basically to compute the sum of a and b, we need to 
            // 1) Compute the XOR, which means add a and b by bit, save that as a
            // 2) The XOR doesn't take into account carries. That is b, which is (a & b) << 1
        }
        return a;
    }
    // O(1) constant time since a and b are bounded
    // O(1) constant time since a and b are bounded
    // Example:
    //     1001 a (9)
    //     1011 b (11)
// a^b   = 0010
// a&b<<1=10010
// ------------
// a^b   = 10000
// a&b<<1=000100
// ------------
// answer=010100 (20)
}
74
Q

There is a new alien language that uses the English alphabet. However, the order among the letters is unknown to you.

You are given a list of strings words from the alien language’s dictionary, where the strings in words are
sorted lexicographically
by the rules of this new language.

Return a string of the unique letters in the new alien language sorted in lexicographically increasing order by the new language’s rules. If there is no solution, return “”. If there are multiple solutions, return any of them.

Input: words = [“wrt”,”wrf”,”er”,”ett”,”rftt”]
Output: “wertf”

A
class Solution:
    def alienOrder(self, words: List[str]) -> str:
        adj = {c: set() for w in words for c in w} #maps character to list of characters that come AFTER (directed edge neighbors)

        for i in range(len(words) - 1):
            w1, w2 = words[i], words[i + 1]
            minLength = min(len(w1), len(w2))
            #One edge case is w1 longer than w2 but prefix is the same
            if len(w1) > len(w2) and w1[:minLength] == w2[:minLength]:
                return ""
            
            for j in range(minLength):
                if w1[j] != w2[j]:
                    adj[w1[j]].add(w2[j])
                    break
        res = [] #we build the result bottom up
        visit, cycle = set(), set()
        def dfs(c):
            if c in cycle:
                return False
            if c in visit:
                return True
            cycle.add(c)
            for nc in adj[c]:
                if not dfs(nc):
                    return False
            res.append(c)
            cycle.remove(c)
            visit.add(c)
            return True 
        for c in adj:
            if not dfs(c):
                return ""
        res.reverse()
        return "".join(res)
        #O(total number of characters) to build adjacency list.
                #The actual dfs is on length 26 charset, so at most 26^2 if maxed out
        #O(26) chars in charset or O(26^2) maxed out edges -> O(1)
            
        
75
Q

Given a 1-indexed array of integers numbers that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target number. Let these two numbers be numbers[index1] and numbers[index2] where 1 <= index1 < index2 <= numbers.length.

Return the indices of the two numbers, index1 and index2, added by one as an integer array [index1, index2] of length 2.

The tests are generated such that there is exactly one solution. You may not use the same element twice.

Your solution must use only constant extra space.

A
class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        l, r = 0, len(numbers) - 1
        while l < r:
            if numbers[l] + numbers[r] < target:
                l += 1
            elif numbers[l] + numbers[r] > target:
                r -= 1
            else:
                #Due to answer wanting one-indexing
                return [l + 1, r + 1]
        #O(n)
        #O(1)

        
76
Q

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining.

Input: height = [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6
Explanation: The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped.

A
class Solution:
    def trap(self, height: List[int]) -> int:
        if not height:
            return 0
        l, r = 0, len(height) - 1
        maxL, maxR = height[l], height[r]
        #We use the minimum of MAXIMUM heights to our left and right.
        res = 0
        while l < r:
            if maxL < maxR:
                l += 1
                #
                water = max(maxL - height[l], 0)
                res += water
                maxL = max(maxL, height[l])
            else:
                r -= 1
                water = max(maxR - height[r], 0)
                res += water
                maxR = max(maxR, height[r])
        return res
        #O(n)
        #O(1)
#We increment l and decrement r first BEFORE doing the computation, because:
# 1) the boundaries cannot contain any water
# 2) After incrementing, maxL and maxR hasn't been updated yet, so will properly reflect "left" and "right" of the current height tile.In other words... after pushing, we're at a state where maxL is to our left! This makes sense.. initially, zero water trapped at edges
# 3) Compute the water and add to result. It's possible the computation is negative, so max it with 0.
# 4) THEN we updated maxL and maxR. This is because now we have considered this tile of height as potentially max, so now new maxL and maxR will be used in computation. We use the max function here, BECAUSE the amount of water bounded is: we take the minimum of the
#maximum heights to the left and right.