├── 001 ├── Readme.md └── Solution.md ├── 002 ├── Readme.md └── Solution.md ├── 003 ├── Readme.md └── Solution.md ├── 004 ├── Readme.md └── Solution.md ├── 005 ├── Readme.md └── Solution.md ├── 006 ├── Readme.md └── Solution.md ├── 007 ├── Readme.md └── Solution.md ├── 008 ├── Readme.md └── Solution.md ├── 009 ├── Readme.md └── Solution.md ├── 010 ├── Readme.md └── Solution.md ├── 011 ├── Readme.md └── Solution.md ├── 012 ├── Readme.md └── Solution.md ├── 013 ├── Readme.md └── Solution.md ├── 014 ├── Readme.md └── Solution.md ├── 015 ├── Readme.md └── Solution.md ├── 016 ├── Readme.md └── Solution.md ├── 017 ├── Readme.md └── Solution.md ├── 018 ├── Readme.md └── Solution.md ├── 019 ├── Readme.md └── Solution.md ├── 020 ├── Readme.md └── Solution.md ├── 021 ├── Readme.md └── Solution.md ├── 022 ├── Readme.md └── Solution.md ├── 023 ├── Readme.md └── Solution.md ├── 024 ├── Readme.md └── Solution.md ├── 025 ├── Readme.md └── Solution.md ├── 026 ├── Readme.md └── Solution.md ├── 027 [Easy] ├── Readme.md └── Solution.md ├── 028 [Medium] ├── Readme.md └── Solution.md ├── 029 [Easy] ├── Readme.md └── Solution.md ├── 030 [Medium] ├── Readme.md └── Solution.md ├── 031 [Easy] ├── Readme.md └── Solution.md ├── 032 [Hard] ├── Readme.md └── Solution.md ├── 033 [Easy] ├── Readme.md └── Solution.md ├── 034 [Medium] ├── Readme.md └── Solution.md ├── 035 [Hard] ├── Readme.md └── Solution.md ├── 036 [Medium] ├── Readme.md └── Solution.md ├── 037 [Easy] ├── Readme.md └── Solution.md ├── 038 [Hard] ├── Readme.md └── Solution.md ├── 039 [Medium] ├── Readme.md └── Solution.md ├── 040 [Hard] ├── Readme.md └── Solution.md ├── 041 [Medium] ├── Readme.md └── Solution.md ├── 042 [Hard] ├── Readme.md └── Solution.md ├── 043 [Easy] ├── Readme.md └── Solution.md └── README.md /001/Readme.md: -------------------------------------------------------------------------------- 1 | ### Daily Coding Problem #1 2 | 3 | This problem was recently asked by Google. 4 | 5 | Given a list of numbers and a number `k`, return whether any two numbers from the list add up to `k`. 6 | 7 | For example, given `[10, 15, 3, 7]` and `k` of `17`, return true since `10 + 7` is `17`. 8 | 9 | Bonus: Can you do this in one pass? 10 | -------------------------------------------------------------------------------- /001/Solution.md: -------------------------------------------------------------------------------- 1 | 2 | **Solution** 3 | 4 | This problem can be solved in several different ways. 5 | 6 | Brute force way would involve a nested iteration to check for every pair of numbers: 7 | 8 | def two_sum(lst, k): 9 | for i in range(len(lst)): 10 | for j in range(len(lst)): 11 | if i != j and lst[i] + lst[j] == k: 12 | return True 13 | return False 14 | 15 | 16 | This would take O(N2). Another way is to use a set to remember the numbers we've seen so far. Then for a given number, we can check if there is another number that, if added, would sum to k. This would be O(N) since lookups of sets are O(1) each. 17 | 18 | def two_sum(lst, k): 19 | seen = set() 20 | for num in lst: 21 | if k - num in seen: 22 | return True 23 | seen.add(num) 24 | return False 25 | 26 | 27 | Yet another solution involves sorting the list. We can then iterate through the list and run a binary search on `K - lst[i]`. Since we run binary search on N elements, this would take O(N log N) with O(1) space. 28 | 29 | from bisect import bisect_left 30 | 31 | 32 | def two_sum(lst, K): 33 | lst.sort() 34 | 35 | for i in range(len(lst)): 36 | target = K - lst[i] 37 | j = binary_search(lst, target) 38 | 39 | # Check that binary search found the target and that it's not in the same index 40 | # as i. If it is in the same index, we can check lst[i + 1] and lst[i - 1] to see 41 | # if there's another number that's the same value as lst[i]. 42 | if j == -1: 43 | continue 44 | elif j != i: 45 | return True 46 | elif j + 1 < len(lst) and lst[j + 1] == target: 47 | return True 48 | elif j - 1 >= 0 and lst[j - 1] == target: 49 | return True 50 | return False 51 | 52 | def binary_search(lst, target): 53 | lo = 0 54 | hi = len(lst) 55 | ind = bisect_left(lst, target, lo, hi) 56 | 57 | if 0 <= ind < hi and lst[ind] == target: 58 | return ind 59 | return -1 60 | -------------------------------------------------------------------------------- /002/Readme.md: -------------------------------------------------------------------------------- 1 | ### Daily Coding Problem #2 2 | 3 | This problem was asked by Uber. 4 | 5 | Given an array of integers, return a new array such that each element at index `i` of the new array is the product of all the numbers in the original array except the one at `i`. 6 | 7 | For example, if our input was `[1, 2, 3, 4, 5]`, the expected output would be `[120, 60, 40, 30, 24]`. If our input was `[3, 2, 1]`, the expected output would be `[2, 3, 6]`. 8 | 9 | Follow-up: what if you can't use division? 10 | -------------------------------------------------------------------------------- /002/Solution.md: -------------------------------------------------------------------------------- 1 | 2 | **Solution** 3 | 4 | This problem would be easy with division: an optimal solution could just find the product of all numbers in the array and then divide by each of the numbers. 5 | 6 | Without division, another approach would be to first see that the ith element simply needs the product of numbers before i and the product of numbers after i. Then we could multiply those two numbers to get our desired product. 7 | 8 | In order to find the product of numbers before i, we can generate a list of prefix products. Specifically, the ith element in the list would be a product of all numbers including i. Similarly, we would generate the list of suffix products. 9 | 10 | def products(nums): 11 | # Generate prefix products 12 | prefix_products = [] 13 | for num in nums: 14 | if prefix_products: 15 | prefix_products.append(prefix_products[-1] * num) 16 | else: 17 | prefix_products.append(num) 18 | 19 | # Generate suffix products 20 | suffix_products = [] 21 | for num in reversed(nums): 22 | if suffix_products: 23 | suffix_products.append(suffix_products[-1] * num) 24 | else: 25 | suffix_products.append(num) 26 | suffix_products = list(reversed(suffix_products)) 27 | 28 | # Generate result 29 | result = [] 30 | for i in range(len(nums)): 31 | if i == 0: 32 | result.append(suffix_products[i + 1]) 33 | elif i == len(nums) - 1: 34 | result.append(prefix_products[i - 1]) 35 | else: 36 | result.append(prefix_products[i - 1] * suffix_products[i + 1]) 37 | return result 38 | 39 | 40 | This runs in O(N) time and space, since iterating over the input arrays takes O(N) time and creating the prefix and suffix arrays take up O(N) space. -------------------------------------------------------------------------------- /003/Readme.md: -------------------------------------------------------------------------------- 1 | ### Daily Coding Problem #3 2 | 3 | This problem was asked by Google. 4 | 5 | Given the root to a binary tree, implement `serialize(root)`, which serializes the tree into a string, and `deserialize(s)`, which deserializes the string back into the tree. 6 | 7 | For example, given the following `Node` class 8 | 9 | class Node: 10 | def __init__(self, val, left=None, right=None): 11 | self.val = val 12 | self.left = left 13 | self.right = right 14 | 15 | The following test should pass: 16 | 17 | node = Node('root', Node('left', Node('left.left')), Node('right')) 18 | assert deserialize(serialize(node)).left.left.val == 'left.left' 19 | -------------------------------------------------------------------------------- /003/Solution.md: -------------------------------------------------------------------------------- 1 | 2 | **Solution** 3 | 4 | There are many ways to serialize and deserialize a binary tree, so don't worry if your solution differs from this one. We will be only going through one possible solution. 5 | 6 | We can approach this problem by first figuring out what we would like the serialized tree to look like. Ideally, it would contain the minimum information required to encode all the necessary information about the binary tree. One possible encoding might be to borrow [S-expressions](https://en.wikipedia.org/wiki/S-expression) from Lisp. The tree `Node(1, Node(2), Node(3))` would then look like '(1 (2 () ()) (3 () ()))', where the empty brackets denote nulls. 7 | 8 | To minimize data over the hypothetical wire, we could go a step further and prune out some unnecessary brackets. We could also replace the 2-character '()' with '#'. We can then infer leaf nodes by their form 'val # #' and thus get the structure of the tree that way. Then our tree would look like `1 2 # # 3 # #`. 9 | 10 | def serialize(root): 11 | if root is None: 12 | return '#' 13 | return '{} {} {}'.format(root.val, serialize(root.left), serialize(root.right)) 14 | 15 | def deserialize(data): 16 | def helper(): 17 | val = next(vals) 18 | if val == '#': 19 | return None 20 | node = Node(int(val)) 21 | node.left = helper() 22 | node.right = helper() 23 | return node 24 | vals = iter(data.split()) 25 | return helper() 26 | 27 | 28 | This runs in O(N) time and space, since we iterate over the whole tree when serializing and deserializing. -------------------------------------------------------------------------------- /004/Readme.md: -------------------------------------------------------------------------------- 1 | ### Daily Coding Problem #4 2 | 3 | This problem was asked by Stripe. 4 | 5 | Given an array of integers, find the first missing positive integer in linear time and constant space. In other words, find the lowest positive integer that does not exist in the array. The array can contain duplicates and negative numbers as well. 6 | 7 | For example, the input `[3, 4, -1, 1]` should give `2`. The input `[1, 2, 0]` should give `3`. 8 | 9 | You can modify the input array in-place. 10 | -------------------------------------------------------------------------------- /004/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | Our lives would be easier without the linear time constraint: we would just sort the array, while filtering out negative numbers, and iterate over the sorted array and return the first number that doesn't match the index. However, sorting takes O(n log n), so we can't use that here. 4 | 5 | Clearly we have to use some sort of trick here to get it running in linear time. Since the first missing positive number must be between 1 and len(array) + 1 (why?), we can ignore any negative numbers and numbers bigger than len(array). The basic idea is to use the indices of the array itself to reorder the elements to where they should be. We traverse the array and swap elements between 0 and the length of the array to their value's index. We stay at each index until we find that index's value and keep on swapping. 6 | 7 | By the end of this process, all the first positive numbers should be grouped in order at the beginning of the array. We don't care about the others. This only takes O(N) time, since we swap each element at most once. 8 | 9 | Then we can iterate through the array and return the index of the first number that doesn't match, just like before. 10 | 11 | def first_missing_positive(nums): 12 | if not nums: 13 | return 1 14 | for i, num in enumerate(nums): 15 | while i + 1 != nums[i] and 0 < nums[i] <= len(nums): 16 | v = nums[i] 17 | nums[i], nums[v - 1] = nums[v - 1], nums[i] 18 | if nums[i] == nums[v - 1]: 19 | break 20 | for i, num in enumerate(nums, 1): 21 | if num != i: 22 | return i 23 | return len(nums) + 1 24 | 25 | 26 | Another way we can do this is by adding all the numbers to a set, and then use a counter initialized to 1. Then continuously increment the counter and check whether the value is in the set. 27 | 28 | def first_missing_positive(nums): 29 | s = set(nums) 30 | i = 1 31 | while i in s: 32 | i += 1 33 | return i 34 | 35 | 36 | This is much simpler, but runs in O(N) time and space, whereas the previous algorithm uses no extra space. -------------------------------------------------------------------------------- /005/Readme.md: -------------------------------------------------------------------------------- 1 | ### Daily Coding Problem #5 2 | 3 | This problem was asked by Jane Street. 4 | 5 | `cons(a, b)` constructs a pair, and `car(pair)` and `cdr(pair)` returns the first and last element of that pair. For example, `car(cons(3, 4))` returns `3`, and `cdr(cons(3, 4))` returns `4`. 6 | 7 | Given this implementation of cons: 8 | 9 | def cons(a, b): 10 | def pair(f): 11 | return f(a, b) 12 | return pair 13 | 14 | Implement `car` and `cdr`. 15 | -------------------------------------------------------------------------------- /005/Solution.md: -------------------------------------------------------------------------------- 1 | 2 | **Solution** 3 | 4 | This is a really cool example of using [closures](https://en.wikipedia.org/wiki/Closure_(computer_programming)) to store data. We must look at the signature type of cons to retrieve its first and last elements. cons takes in a and b, and returns a new anonymous function, which itself takes in f, and calls f with a and b. So the input to car and cdr is that anonymous function, which is `pair`. To get a and b back, we must feed it yet another function, one that takes in two parameters and returns the first (if car) or last (if cdr) one. 5 | 6 | def car(pair): 7 | return pair(lambda a, b: a) 8 | 9 | def cdr(pair): 10 | return pair(lambda a, b: b) 11 | 12 | 13 | Fun fact: cdr is pronounced "cudder"! -------------------------------------------------------------------------------- /006/Readme.md: -------------------------------------------------------------------------------- 1 | ### Daily Coding Problem #6 2 | 3 | This problem was asked by Google. 4 | 5 | An XOR linked list is a more memory efficient doubly linked list. Instead of each node holding `next` and `prev` fields, it holds a field named `both`, which is an XOR of the next node and the previous node. Implement an XOR linked list; it has an `add(element)` which adds the element to the end, and a `get(index)` which returns the node at index. 6 | 7 | If using a language that has no pointers (such as Python), you can assume you have access to `get_pointer` and `dereference_pointer` functions that converts between nodes and memory addresses. 8 | -------------------------------------------------------------------------------- /006/Solution.md: -------------------------------------------------------------------------------- 1 | 2 | **Solution** 3 | 4 | For the head, `both` will just be the address of next, and if it's the tail, it should just be the address of prev. And intermediate nodes should have an XOR of `next` and `prev`. 5 | 6 | Here's an example XOR linked list which meets the above conditions: 7 | 8 | A <-> B <-> C <-> D 9 | 10 | B A ⊕ C B ⊕ D C 11 | 12 | 13 | Let's work through `get` first, assuming that the above conditions are maintained. Then, given a node, to go to the next node, we have to XOR the current node's `both` with the previous node's address. And to handle getting the next node from the head, we would initialize the previous node's address as 0. 14 | 15 | So in the above example, `A`'s `both` is `B` which when XOR'd with `0` would become `B`. Then `B`'s `both` is `A ⊕ C`, which when XOR'd with `A` becomes C, etc. 16 | 17 | To implement `add`, we would need to update current tail's `both` to be XOR'd by its current `both` the new node's memory address. Then the new node's `both` would just point to the memory address of the current tail. Finally, we'd update the current tail to be equal to the new node. 18 | 19 | import ctypes 20 | 21 | 22 | # This is hacky. It's a data structure for C, not python. 23 | class Node(object): 24 | def __init__(self, val): 25 | self.val = val 26 | self.both = 0 27 | 28 | 29 | class XorLinkedList(object): 30 | def __init__(self): 31 | self.head = self.tail = None 32 | self.__nodes = [] # This is to prevent garbage collection 33 | 34 | def add(self, node): 35 | if self.head is None: 36 | self.head = self.tail = node 37 | else: 38 | self.tail.both = id(node) ^ self.tail.both 39 | node.both = id(self.tail) 40 | self.tail = node 41 | 42 | # Without this line, Python thinks there is no way to reach nodes between 43 | # head and tail. 44 | self.__nodes.append(node) 45 | 46 | 47 | def get(self, index): 48 | prev_id = 0 49 | node = self.head 50 | for i in range(index): 51 | next_id = prev_id ^ node.both 52 | 53 | if next_id: 54 | prev_id = id(node) 55 | node = _get_obj(next_id) 56 | else: 57 | raise IndexError('Linked list index out of range') 58 | return node 59 | 60 | 61 | def _get_obj(id): 62 | return ctypes.cast(id, ctypes.py_object).value 63 | 64 | 65 | `add` runs in O(1) time and `get` runs in O(N) time. 66 | -------------------------------------------------------------------------------- /007/Readme.md: -------------------------------------------------------------------------------- 1 | ### Daily Coding Problem #7 2 | 3 | This problem was asked by Facebook. 4 | 5 | Given the mapping a = 1, b = 2, ... z = 26, and an encoded message, count the number of ways it can be decoded. 6 | 7 | For example, the message '111' would give 3, since it could be decoded as 'aaa', 'ka', and 'ak'. 8 | 9 | You can assume that the messages are decodable. For example, '001' is not allowed. 10 | -------------------------------------------------------------------------------- /007/Solution.md: -------------------------------------------------------------------------------- 1 | 2 | **Solution** 3 | 4 | This looks like a problem that is ripe for solving with recursion. First, let's try to think of a recurrence we can use for this problem. We can try some cases: 5 | 6 | * "", the empty string and our base case, should return 1. 7 | * "1" should return 1, since we can parse it as "a" + "". 8 | * "11" should return 2, since we can parse it as "a" + "a" + "" and "k" + "". 9 | * "111" should return 3, since we can parse it as: 10 | * "a" + "k" + "" 11 | * "k" + "a" + "" 12 | * "a" + "a" + "a" + "". 13 | * "011" should return 0, since no letter starts with 0 in our mapping. 14 | * "602" should also return 0 for similar reasons. 15 | 16 | We have a good starting point. We can see that the recursive structure is as follows: 17 | 18 | * If string starts with zero, then there's no valid encoding. 19 | * If the string's length is less than or equal to 1, there is only 1 encoding. 20 | * If the first two digits form a number `k` that is less than or equal to 26, we can recursively count the number of encodings assuming we pick `k` as a letter. 21 | * We can also pick the first digit as a letter and count the number of encodings with this assumption. 22 | 23 | def num_encodings(s): 24 | if s.startswith('0'): 25 | return 0 26 | elif len(s) <= 1: # This covers empty string 27 | return 1 28 | 29 | total = 0 30 | 31 | if int(s[:2]) <= 26: 32 | total += num_encodings(s[2:]) 33 | 34 | total += num_encodings(s[1:]) 35 | return total 36 | 37 | 38 | However, this solution is not very efficient. Every branch calls itself recursively twice, so our runtime is O(2n). We can do better by using dynamic programming. 39 | 40 | All the following code does is repeat the same computation as above except starting from the base case and building up the solution. Since each iteration takes O(1), the whole algorithm now takes O(n). 41 | 42 | from collections import defaultdict 43 | 44 | def num_encodings(s): 45 | # On lookup, this hashmap returns a default value of 0 if the key doesn't exist 46 | # cache[i] gives us # of ways to encode the substring s[i:] 47 | cache = defaultdict(int) 48 | cache[len(s)] = 1 # Empty string is 1 valid encoding 49 | 50 | for i in reversed(range(len(s))): 51 | if s[i].startswith('0'): 52 | cache[i] = 0 53 | elif i == len(s) - 1: 54 | cache[i] = 1 55 | else: 56 | if int(s[i:i + 2]) <= 26: 57 | cache[i] = cache[i + 2] 58 | cache[i] += cache[i + 1] 59 | return cache[0] 60 | -------------------------------------------------------------------------------- /008/Readme.md: -------------------------------------------------------------------------------- 1 | ### Daily Coding Problem #8 2 | 3 | This problem was asked by Google. 4 | 5 | A unival tree (which stands for "universal value") is a tree where all nodes under it have the same value. 6 | 7 | Given the root to a binary tree, count the number of unival subtrees. 8 | 9 | For example, the following tree has 5 unival subtrees: 10 | 11 | 0 12 | / \ 13 | 1 0 14 | / \ 15 | 1 0 16 | / \ 17 | 1 1 18 | -------------------------------------------------------------------------------- /008/Solution.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Solution** 4 | 5 | To start off, we should go through some examples. 6 | 7 | a 8 | / \ 9 | a a 10 | /\ 11 | a a 12 | \ 13 | A 14 | 15 | 16 | This tree has 3 unival subtrees: the two 'a' leaves, and the one 'A' leaf. The 'A' leaf causes all its parents to not be counted as a unival tree. 17 | 18 | a 19 | / \ 20 | c b 21 | /\ 22 | b b 23 | \ 24 | b 25 | 26 | 27 | This tree has 5 unival subtrees: the leaf at 'c', and every 'b'. 28 | 29 | We can start off by first writing a function that checks whether a tree is unival or not. Then, perhaps we could use this to count up all the nodes in the tree. 30 | 31 | To check whether a tree is a unival tree, we must check that every node in the tree has the same value. To start off, we could define an `is_unival` function that takes in a root to a tree. We would do this recursively with a helper function. Recall that a leaf qualifies as a unival tree. 32 | 33 | def is_unival(root): 34 | return unival_helper(root, root.value) 35 | 36 | def unival_helper(root, value): 37 | if root is None: 38 | return True 39 | if root.value == value: 40 | return unival_helper(root.left, value) and unival_helper(root.right, value) 41 | return False 42 | 43 | 44 | And then our function that counts the number of subtrees could simply use that function: 45 | 46 | def count_unival_subtrees(root): 47 | if root is None: 48 | return 0 49 | left = count_unival_subtrees(root.left) 50 | right = count_unival_subtrees(root.right) 51 | return 1 + left + right if is_unival(root) else left + right 52 | 53 | 54 | However, this runs in O(n^2) time. For each node of the tree, we're evaluating each node in its subtree again as well. We can improve the runtime by starting at the leaves of the tree, and keeping track of the unival subtree count and value as we percolate back up. This should evaluate each node only once, making it run in O(n) time. 55 | 56 | def count_unival_subtrees(root): 57 | count, _ = helper(root) 58 | return count 59 | 60 | # Also returns number of unival subtrees, and whether it is itself a unival subtree. 61 | def helper(root): 62 | if root is None: 63 | return 0, True 64 | 65 | left_count, is_left_unival = helper(root.left) 66 | right_count, is_right_unival = helper(root.right) 67 | total_count = left_count + right_count 68 | 69 | if is_left_unival and is_right_unival: 70 | if root.left is not None and root.value != root.left.value: 71 | return total_count, False 72 | if root.right is not None and root.value != root.right.value: 73 | return total_count, False 74 | return total_count + 1, True 75 | return total_count, False 76 | 77 | 78 | Show Solution -------------------------------------------------------------------------------- /009/Readme.md: -------------------------------------------------------------------------------- 1 | ### Daily Coding Problem #9 2 | 3 | This problem was asked by Airbnb. 4 | 5 | Given a list of integers, write a function that returns the largest sum of non-adjacent numbers. Numbers can be `0` or negative. 6 | 7 | For example, `[2, 4, 6, 2, 5]` should return `13`, since we pick `2`, `6`, and `5`. `[5, 1, 1, 5]` should return `10`, since we pick `5` and `5`. 8 | 9 | Follow-up: Can you do this in O(N) time and constant space? 10 | -------------------------------------------------------------------------------- /009/Solution.md: -------------------------------------------------------------------------------- 1 | 2 | **Solution** 3 | 4 | This problem seems easy from the surface, but is actually quite tricky. It's tempting to try to use a greedy strategy like pick the largest number (or first), then the 2nd-largest if it's non-adjacent and so on, but these don't work -- there will always be some edge case that breaks it. 5 | 6 | Instead, we should look at this problem recursively. Say we had a function that already returns the largest sum of non-adjacent integers on smaller inputs. How could we use it to figure out what we want? 7 | 8 | Say we used this function on our array from `a[1:]` and `a[2:]`. Then our solution should be `a[1:]` OR `a[0] + a[2:]`, whichever is largest. This is because choosing `a[1:]` precludes us from picking `a[0]`. So, we could write a straightforward recursive solution like this: 9 | 10 | def largest_non_adjacent(arr): 11 | if not arr: 12 | return 0 13 | 14 | return max( 15 | largest_non_adjacent(arr[1:]), 16 | arr[0] + largest_non_adjacent(arr[2:])) 17 | 18 | 19 | However, this solution runs in O(2n) time, since with each call, we're making two further recursive calls. We could memoize the results, or use dynamic programming to store, in an array, the largest sum of non-adjacent numbers from index `0` up to that point. Like so: 20 | 21 | def largest_non_adjacent(arr): 22 | if len(arr) <= 2: 23 | return max(0, max(arr)) 24 | 25 | cache = [0 for i in arr] 26 | cache[0] = max(0, arr[0]) 27 | cache[1] = max(cache[0], arr[1]) 28 | 29 | for i in range(2, len(arr)): 30 | num = arr[i] 31 | cache[i] = max(num + cache[i - 2], cache[i - 1]) 32 | return cache[-1] 33 | 34 | 35 | This code should run in O(n) and in O(n) space. But we can improve this even further. Notice that we only ever use the last two elements of the cache when iterating through the array. This suggests that we could just get rid of most of the array and just store them as variables: 36 | 37 | def largest_non_adjacent(arr): 38 | if len(arr) <= 2: 39 | return max(0, max(arr)) 40 | 41 | max_excluding_last= max(0, arr[0]) 42 | max_including_last = max(max_excluding_last, arr[1]) 43 | 44 | for num in arr[2:]: 45 | prev_max_including_last = max_including_last 46 | 47 | max_including_last = max(max_including_last, max_excluding_last + num) 48 | max_excluding_last = prev_max_including_last 49 | 50 | return max(max_including_last, max_excluding_last) -------------------------------------------------------------------------------- /010/Readme.md: -------------------------------------------------------------------------------- 1 | ### Daily Coding Problem #10 2 | 3 | This problem was asked by Apple. 4 | 5 | Implement a job scheduler which takes in a function `f` and an integer `n`, and calls `f` after `n` milliseconds. 6 | -------------------------------------------------------------------------------- /010/Solution.md: -------------------------------------------------------------------------------- 1 | 2 | **Solution** 3 | 4 | We can implement the job scheduler in many different ways, so don't worry if your solution is different from ours. Here is just one way: 5 | 6 | First, let's try the most straightforward solution. That would probably be to spin off a new thread on each function we want to delay, sleep the requested amount, and then run the function. It might look something like this: 7 | 8 | import threading 9 | from time import sleep 10 | 11 | class Scheduler: 12 | def __init__(self): 13 | pass 14 | 15 | def delay(self, f, n): 16 | def sleep_then_call(n): 17 | sleep(n / 1000) 18 | f() 19 | t = threading.Thread(target=sleep_then_call) 20 | t.start() 21 | 22 | 23 | While this works, there is a huge problem with this method: we spin off a new thread each time we call delay! That means the number of threads we use could easily explode. We can get around this by having only one dedicated thread to call the functions, and storing the functions we need to call in some data structure. In this case, we use a list. We also have to do some sort of polling now to check when to run a function. We can store each function along with a unix epoch timestamp that tells it when it should run by. Then we'll poll some designated tick amount and check the list for any jobs that are due to be run, run them, and then remove them from the list. 24 | 25 | from time import sleep 26 | import threading 27 | 28 | class Scheduler: 29 | def __init__(self): 30 | self.fns = [] # tuple of (fn, time) 31 | t = threading.Thread(target=self.poll) 32 | t.start() 33 | 34 | def poll(self): 35 | while True: 36 | now = time() * 1000 37 | for fn, due in self.fns: 38 | if now > due: 39 | fn() 40 | self.fns = [(fn, due) for (fn, due) in self.fns if due > now] 41 | sleep(0.01) 42 | 43 | def delay(self, f, n): 44 | self.fns.append((f, time() * 1000 + n)) 45 | 46 | 47 | We'll stop here, but you can go much farther with this. Some extra credit work: 48 | 49 | * Extend the scheduler to allow calling delayed functions with variables 50 | * Use a heap instead of a list to keep track of the next job to run more efficiently 51 | * Use a condition variable instead of polling (it just polls lower in the stack) 52 | * Use a threadpool or other mechanism to decrease the chance of starvation (one thread not being able to run because of another running thread) -------------------------------------------------------------------------------- /011/Readme.md: -------------------------------------------------------------------------------- 1 | ### Daily Coding Problem #11 2 | 3 | 4 | This problem was asked by Twitter. 5 | 6 | Implement an autocomplete system. That is, given a query string `s` and a set of all possible query strings, return all strings in the set that have s as a prefix. 7 | 8 | For example, given the query string `de` and the set of strings \[`dog`, `deer`, `deal`\], return \[`deer`, `deal`\]. 9 | 10 | Hint: Try preprocessing the dictionary into a more efficient data structure to speed up queries. 11 | -------------------------------------------------------------------------------- /011/Solution.md: -------------------------------------------------------------------------------- 1 | 2 | **Solution** 3 | 4 | The naive solution here is very straightforward: we need to only iterate over the dictionary and check if each word starts with our prefix. If it does, then add it to our set of results and then return it once we're done. 5 | 6 | WORDS = ['foo', 'bar', ...] 7 | def autocomplete(s): 8 | results = set() 9 | for word in WORDS: 10 | if word.startswith(s): 11 | results.add(word) 12 | return results 13 | 14 | 15 | This runs in O(N) time, where N is the number of words in the dictionary. Let's think about making this more efficient. We can preprocess the words, but what data structure would be best for our problem? 16 | 17 | If we pre-sort the list, we could use binary search to find the first word that includes our prefix and then the last, and return everything in between. 18 | 19 | Alternatively, we could use a tree for this. Not a binary tree, but a tree where each child represents one character of the alphabet. For example, let's say we had the words 'a' and 'dog' in our dictionary. Then the tree would look like this: 20 | 21 | x 22 | / \ 23 | a d 24 | \ 25 | o 26 | \ 27 | g 28 | 29 | 30 | Then, to find all words beginning with 'do', we could start at the root, go into the 'd' child, and then the 'o', child, and gather up all the words under there. We would also some sort of terminal value to mark whether or not 'do' is actually a word in our dictionary or not. This data structure is known as a [trie](https://en.wikipedia.org/wiki/Trie). 31 | 32 | So the idea is to preprocess the dictionary into this tree, and then when we search for a prefix, go into the trie and get all the words under that prefix node and return those. While the worst-case runtime would still be O(n) if all the search results have that prefix, if the words are uniformly distributed across the alphabet, it should be much faster on average since we no longer have to evaluate words that don't start with our prefix. 33 | 34 | ENDS_HERE = '__ENDS_HERE' 35 | 36 | class Trie(object): 37 | def __init__(self): 38 | self._trie = {} 39 | 40 | def insert(self, text): 41 | trie = self._trie 42 | for char in text: 43 | if char not in trie: 44 | trie[char] = {} 45 | trie = trie[char] 46 | trie[ENDS_HERE] = True 47 | 48 | def elements(self, prefix): 49 | d = self._trie 50 | for char in prefix: 51 | if char in d: 52 | d = d[char] 53 | else: 54 | return [] 55 | return self._elements(d) 56 | 57 | def _elements(self, d): 58 | result = [] 59 | for c, v in d.items(): 60 | if c == ENDS_HERE: 61 | subresult = [''] 62 | else: 63 | subresult = [c + s for s in self._elements(v)] 64 | result.extend(subresult) 65 | return result 66 | 67 | trie = Trie() 68 | for word in words: 69 | trie.insert(word) 70 | 71 | def autocomplete(s): 72 | suffixes = trie.elements(s) 73 | return [s + w for w in suffixes] 74 | 75 | 76 | -------------------------------------------------------------------------------- /012/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #12 2 | 3 | 4 | 5 | This problem was asked by Amazon. 6 | 7 | There exists a staircase with N steps, and you can climb up either 1 or 2 steps at a time. Given N, write a function that returns the number of unique ways you can climb the staircase. The order of the steps matters. 8 | 9 | For example, if N is 4, then there are 5 unique ways: 10 | 11 | * 1, 1, 1, 1 12 | * 2, 1, 1 13 | * 1, 2, 1 14 | * 1, 1, 2 15 | * 2, 2 16 | 17 | What if, instead of being able to climb 1 or 2 steps at a time, you could climb any number from a set of positive integers X? For example, if X = {1, 3, 5}, you could climb 1, 3, or 5 steps at a time. -------------------------------------------------------------------------------- /012/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | It's always good to start off with some test cases. Let's start with small cases and see if we can find some sort of pattern. 4 | 5 | * N = 1: \[1\] 6 | * N = 2: \[1, 1\], \[2\] 7 | * N = 3: \[1, 2\], \[1, 1, 1\], \[2, 1\] 8 | * N = 4: \[1, 1, 2\], \[2, 2\], \[1, 2, 1\], \[1, 1, 1, 1\], \[2, 1, 1\] 9 | 10 | What's the relationship? 11 | 12 | The only ways to get to N = 3, is to first get to N = 1, and then go up by 2 steps, or get to N = 2 and go up by 1 step. So f(3) = f(2) + f(1). 13 | 14 | Does this hold for N = 4? Yes, it does. Since we can only get to the 4th step by getting to the 3rd step and going up by one, or by getting to the 2nd step and going up by two. So f(4) = f(3) + f(2). 15 | 16 | To generalize, f(n) = f(n - 1) + f(n - 2). That's just the [Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_number)! 17 | 18 | def staircase(n): 19 | if n <= 1: 20 | return 1 21 | return staircase(n - 1) + staircase(n - 2) 22 | 23 | 24 | Of course, this is really slow (O(2N)) — we are doing a lot of repeated computations! We can do it a lot faster by just computing iteratively: 25 | 26 | def staircase(n): 27 | a, b = 1, 2 28 | for _ in range(n - 1): 29 | a, b = b, a + b 30 | return a 31 | 32 | 33 | Now, let's try to generalize what we've learned so that it works if you can take a number of steps from the set X. Similar reasoning tells us that if X = {1, 3, 5}, then our algorithm should be f(n) = f(n - 1) + f(n - 3) + f(n - 5). If n < 0, then we should return 0 since we can't start from a negative number of steps. 34 | 35 | def staircase(n, X): 36 | if n < 0: 37 | return 0 38 | elif n == 0: 39 | return 1 40 | else: 41 | return sum(staircase(n - x, X) for x in X) 42 | 43 | 44 | This is again, very slow (O(|X|N)) since we are repeating computations again. We can use dynamic programming to speed it up. 45 | 46 | Each entry cache\[i\] will contain the number of ways we can get to step i with the set X. Then, we'll build up the array from zero using the same recurrence as before: 47 | 48 | def staircase(n, X): 49 | cache = [0 for _ in range(n + 1)] 50 | cache[0] = 1 51 | for i in range(1, n + 1): 52 | cache[i] += sum(cache[i - x] for x in X if i - x >= 0) 53 | return cache[n] 54 | 55 | 56 | This now takes O(N \* |X|) time and O(N) space. -------------------------------------------------------------------------------- /013/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #13 2 | 3 | This problem was asked by Amazon. 4 | 5 | Given an integer k and a string s, find the length of the longest substring that contains at most k distinct characters. 6 | 7 | For example, given s = "abcba" and k = 2, the longest substring with k distinct characters is "bcb". 8 | -------------------------------------------------------------------------------- /013/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | The most obvious brute force solution here is to simply try every possible substring of the string and check whether it contains at most `k` distinct characters. If it does and it is greater than the current longest valid substring, then update the current one. This takes O(n2 \* k) time, since we use n2 to generate each possible substring, and then take `k` to check each character. 4 | 5 | def longest_substring_with_k_distinct_characters(s, k): 6 | current_longest_substring = '' 7 | for i in range(len(s)): 8 | for j in range(i + 1, len(s) + 1): 9 | substring = s[i:j] 10 | if len(set(substring)) <= k and len(substring) > len(current_longest_substring): 11 | current_longest_substring = substring 12 | return len(current_longest_substring) 13 | 14 | 15 | We can improve this by instead keeping a running window of our longest substring. We'll keep a dictionary that maps characters to the index of their last occurrence. Then, as we iterate over the string, we'll check the size of the dictionary. If it's larger than k, then it means our window is too big, so we have to pop the smallest item in the dictionary and recompute the bounds. If, when we add a character to the dictionary and it doesn't go over k, then we're safe -- the dictionary hasn't been filled up yet or it's a character we've seen before. 16 | 17 | def longest_substring_with_k_distinct_characters(s, k): 18 | if k == 0: 19 | return 0 20 | 21 | # Keep a running window 22 | bounds = (0, 0) 23 | h = {} 24 | max_length = 0 25 | for i, char in enumerate(s): 26 | h[char] = i 27 | if len(h) <= k: 28 | new_lower_bound = bounds[0] # lower bound remains the same 29 | else: 30 | # otherwise, pop last occurring char 31 | key_to_pop = min(h, key=h.get) 32 | new_lower_bound = h.pop(key_to_pop) + 1 33 | 34 | bounds = (new_lower_bound, bounds[1] + 1) 35 | max_length = max(max_length, bounds[1] - bounds[0]) 36 | 37 | return max_length 38 | 39 | 40 | This takes O(n \* k) time and O(k) space. -------------------------------------------------------------------------------- /014/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #14 2 | 3 | 4 | This problem was asked by Google. 5 | 6 | The area of a circle is defined as πr^2. Estimate π to 3 decimal places using a Monte Carlo method. 7 | 8 | Hint: The basic equation of a circle is x2 + y2 = r2. -------------------------------------------------------------------------------- /014/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | Monte Carlo methods rely on random sampling. In this case, if we take a cartesian plane and inscribe a circle with radius `r` inside a square with lengths `2r`, then the area of the circle will be πr2 while the area of the square will be (2r)2 = 4r2. Then, the ratio of the areas of the circle to the square is `π / 4`. 4 | 5 | So, what we can do is the following: 6 | 7 | * Set r to be 1 (the unit circle) 8 | * Randomly generate points within the square with corners (-1, -1), (1, 1), (1, -1), (-1, 1) 9 | * Keep track of the points that fall inside and outside the circle 10 | * You can check whether a point (x, y) is inside the circle if x2 + y2 < r2, which is another way of representing a circle 11 | * Divide the number of points that fall inside the circle to the total number of points -- that should give us an approximation of π / 4. 12 | 13 | 14 | 15 | from random import uniform 16 | from math import pow 17 | 18 | def generate(): 19 | return (uniform(-1, 1), uniform(-1, 1)) 20 | 21 | def is_in_circle(coords): 22 | return coords[0] * coords[0] + coords[1] * coords[1] < 1 23 | 24 | def estimate(): 25 | iterations = 10000000 26 | in_circle = 0 27 | for _ in range(iterations): 28 | if is_in_circle(generate()): 29 | in_circle += 1 30 | pi_over_four = in_circle / iterations 31 | return pi_over_four * 4 32 | 33 | 34 | Note that this doesn't give a perfect approximation -- we need more iterations to get a closer estimate. We want the digits of pi up to 3 decimal places. This translates to an error of < 10^(-3). The error scales with the square root of the number of guesses, which means we need 10^6 iterations to get to our desired precision. If we want more precision, we'll have to crank up the iterations. 35 | 36 | This problem \_is\_ [embarrassingly parallel](https://en.wikipedia.org/wiki/Embarrassingly_parallel). None of the estimations have any dependent computations, so we can parallelize this problem easily -- divide up the workload into `P` processes you have, and then add up all the points in the circle in the end. Extra credit: make this program multi-process. -------------------------------------------------------------------------------- /015/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #15 2 | 3 | 4 | This problem was asked by Facebook. 5 | 6 | Given a stream of elements too large to store in memory, pick a random element from the stream with uniform probability. -------------------------------------------------------------------------------- /015/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | 4 | Naively, we could process the stream and store all the elements we encounter in a list, find its size, and pick a random element from \[0, size - 1\]. The problem with this approach is that it would take O(N) space for a large N. 5 | 6 | Instead, let’s attempt to solve using loop invariants. On the ith iteration of our loop to pick a random element, let’s assume we already picked an element uniformly from \[0, i - 1\]. In order to maintain the loop invariant, we would need to pick the ith element as the new random element at 1 / (i + 1) chance. For the base case where i = 0, let’s say the random element is the first one. Then we know it works because 7 | 8 | * For i >= 0, before the loop began, any element K in \[0, i - 1\] had 1 / i chance of being chosen as the random element. We want K to have 1 / (i + 1) chance of being chosen after the iteration. This is the case since the chance of having being chosen already but not getting swapped with the ith element is 1 / i _(1 - (1 / (i + 1))) which is 1 / i_ i / (i + 1) or 1 / (i + 1) 9 | 10 | Let’s see how the code would look: 11 | 12 | import random 13 | 14 | def pick(big_stream): 15 | random_element = None 16 | 17 | for i, e in enumerate(big_stream): 18 | if random.randint(1, i + 1) == 1: 19 | random_element = e 20 | return random_element 21 | 22 | 23 | Since we are only storing a single variable, this only takes up constant space! 24 | 25 | By the way, this is called [reservoir sampling](https://en.wikipedia.org/wiki/Reservoir_sampling)! 26 | 27 | -------------------------------------------------------------------------------- /016/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #16 2 | 3 | 4 | 5 | This problem was asked by Twitter. 6 | 7 | You run an e-commerce website and want to record the last `N` `order` ids in a log. Implement a data structure to accomplish this, with the following API: 8 | 9 | * record(order\_id): adds the order\_id to the log 10 | * get\_last(i): gets the ith last element from the log. i is guaranteed to be smaller than or equal to N. 11 | 12 | You should be as efficient with time and space as possible. -------------------------------------------------------------------------------- /016/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | It seems like an array would be the perfect fit for this problem. We can just initialize the array to have size N, and index it in constant time. Then, when we record any orders, we can pop off the first order and append it to the end. Getting the ith last order would then just be indexing the array at `length - i`. 4 | 5 | class Log(object): 6 | def __init__(self, n): 7 | self._log = [] 8 | self.n = n 9 | 10 | def record(self, order_id): 11 | if len(self._log) >= self.n: 12 | self._log.pop(0) 13 | self._log.append(order_id) 14 | 15 | def get_last(self, i): 16 | return self._log[-i] 17 | 18 | 19 | This is one issue with this solution, however: when we have to pop off an element when the array is full, we have to move every other element down by 1. That means `record` takes O(N) time. How can we improve this? 20 | 21 | What we can do to avoid having to moving every element down by 1 is to keep a current index and move it up each time we record something. For `get_last`, we can simply take `current - i` to get the appropriate element. Now, both `record` and `get_last` should take constant time. 22 | 23 | class Log(object): 24 | def __init__(self, n): 25 | self.n = n 26 | self._log = [] 27 | self._cur = 0 28 | 29 | def record(self, order_id): 30 | if len(self._log) == self.n: 31 | self._log[self._cur] = order_id 32 | else: 33 | self._log.append(order_id) 34 | self._cur = (self._cur + 1) % self.n 35 | 36 | def get_last(self, i): 37 | return self._log[self._cur - i] 38 | 39 | 40 | By the way, this is called a ring buffer or [circular buffer](https://en.wikipedia.org/wiki/Circular_buffer)! -------------------------------------------------------------------------------- /017/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #17 2 | 3 | 4 | 5 | 6 | This problem was asked by Google. 7 | 8 | Suppose we represent our file system by a string in the following manner: 9 | 10 | The string `"dir\n\tsubdir1\n\tsubdir2\n\t\tfile.ext"` represents: 11 | 12 | dir 13 | subdir1 14 | subdir2 15 | file.ext 16 | 17 | 18 | The directory `dir` contains an empty sub-directory `subdir1` and a sub-directory `subdir2` containing a file `file.ext`. 19 | 20 | The string `"dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdir1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext"` represents: 21 | 22 | dir 23 | subdir1 24 | file1.ext 25 | subsubdir1 26 | subdir2 27 | subsubdir2 28 | file2.ext 29 | 30 | 31 | The directory `dir` contains two sub-directories `subdir1` and `subdir2`. `subdir1` contains a file `file1.ext` and an empty second-level sub-directory `subsubdir1`. `subdir2` contains a second-level sub-directory `subsubdir2` containing a file `file2.ext`. 32 | 33 | We are interested in finding the longest (number of characters) absolute path to a file within our file system. For example, in the second example above, the longest absolute path is `"dir/subdir2/subsubdir2/file2.ext"`, and its length is 32 (not including the double quotes). 34 | 35 | Given a string representing the file system in the above format, return the length of the longest absolute path to a file in the abstracted file system. If there is no file in the system, return 0. 36 | 37 | Note: 38 | 39 | The name of a file contains at least a period and an extension. 40 | 41 | The name of a directory or sub-directory will not contain a period. -------------------------------------------------------------------------------- /017/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | There are two steps in solving this question: we must first parse the string representing the file system and then get the longest absolute path to a file. 4 | 5 | ###### Step 1: Parsing the file system 6 | 7 | Ideally, we would initially parse the string given into a dictionary of some sort. That would mean a string like: 8 | 9 | dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdir1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext 10 | 11 | 12 | would become: 13 | 14 | { 15 | "dir": { 16 | "subdir1": { 17 | "file1.ext": True, 18 | "subsubdir1": {} 19 | }, 20 | "subdir2": { 21 | "subsubdir2": { 22 | "file2.ext": True 23 | } 24 | } 25 | } 26 | } 27 | 28 | 29 | where each key with a dictionary as its value represents a directory, and a key with `True` as its value represents an actual file. 30 | 31 | To achieve this, we can first split the string by the newline character, meaning each item in our array represents a file or directory. Then, we create an empty dictionary to represent our parsed file system and traverse the file system on each entry. We keep track of the last path we've seen so far in `current_path` because we may need to return to some level in that path, depending on the number of tabs. Once we are at the correct place to put down the new directory or file, we check the name for a `.` and set the correct value to either `True` (if file) or `{}` (if directory). 32 | 33 | def build_fs(input): 34 | fs = {} 35 | files = input.split('\n') 36 | 37 | current_path = [] 38 | for f in files: 39 | indentation = 0 40 | while '\t' in f[:2]: 41 | indentation += 1 42 | f = f[1:] 43 | 44 | current_node = fs 45 | for subdir in current_path[:indentation]: 46 | current_node = current_node[subdir] 47 | 48 | if '.' in f: 49 | current_node[f] = True 50 | else: 51 | current_node[f] = {} 52 | 53 | current_path = current_path[:indentation] 54 | current_path.append(f) 55 | 56 | return fs 57 | 58 | 59 | ###### Step 2: Computing the longest path 60 | 61 | After we've constructed a native representation of the file system, we can write a fairly straightforward recursive function that takes the current root, recursively calculates the `longest_path` of all the subdirectories and files under the root, and returns the longest one. Remember that since we specifically want the longest path to a file to discard any paths that do not have a `.` in them. And if there are no paths starting at this root, then we can simply return the empty string. 62 | 63 | def longest_path(root): 64 | paths = [] 65 | for key, node in root.items(): 66 | if node == True: 67 | paths.append(key) 68 | else: 69 | paths.append(key + '/' + longest_path(node)) 70 | # filter out unfinished paths 71 | paths = [path for path in paths if '.' in path] 72 | if paths: 73 | return max(paths, key=lambda path:len(path)) 74 | else: 75 | return '' 76 | 77 | 78 | ###### Step 3: Putting it together 79 | 80 | Now that the hard part is done, we just need to put the two together: 81 | 82 | def longest_absolute_path(s): 83 | return len(longest_path(build_fs(s))) 84 | 85 | 86 | This runs in O(n), since we iterate over the input string twice to build the file system, and then in the worst case we go through the string again to compute the longest path. -------------------------------------------------------------------------------- /018/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #18 2 | 3 | This problem was asked by Google. 4 | 5 | Given an array of integers and a number k, where 1 <= k <= length of the array, compute the maximum values of each subarray of length k. 6 | 7 | For example, given array = \[10, 5, 2, 7, 8, 7\] and k = 3, we should get: \[10, 7, 8, 8\], since: 8 | 9 | * 10 = max(10, 5, 2) 10 | * 7 = max(5, 2, 7) 11 | * 8 = max(2, 7, 8) 12 | * 8 = max(7, 8, 7) 13 | 14 | Do this in O(n) time and O(k) space. You can modify the input array in-place and you do not need to store the results. You can simply print them out as you compute them. -------------------------------------------------------------------------------- /018/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | Even though the question states O(n), in an interview it's always useful to first write out a brute force solution, which may provide us with some insight on some deeper structure in the problem. 4 | 5 | So let's first write out a naive solution: we can simply take each subarray of k length and compute their maxes. 6 | 7 | def max_of_subarrays(lst, k): 8 | for i in range(len(lst) - k + 1): 9 | print(max(lst[i:i + k])) 10 | 11 | 12 | This takes O(n \* k) time, which doesn't get us quite to where we want. How can we make this faster? 13 | 14 | One possible idea is this: we could use a max-heap of size k and add the first k elements to the heap initially, and then pop off the max and add the next element for the rest of the array. This is better, but adding and extracting from the heap will take O(log k), so this algorithm will take O(n \* log k), which is still not enough. How can we do better? 15 | 16 | Notice that, for example, the input \[1, 2, 3, 4, 5, 6, 7, 8, 9\] and k = 3, after evaluating the max of first range, since 3 is at the end, we only need to check whether 4 is greater than 3. If it is, then we can print 4 immediately, and if it isn't, we can stick with 3. 17 | 18 | On the other hand, for the input \[9, 8, 7, 6, 5, 4, 3, 2, 1\] and k = 3, after evaluating the max of the first range, we can't do the same thing, since we can't use 9 again. We have to look at 8 instead, and then once we move on to the next range, we have to look at 7. 19 | 20 | These two data points suggest an idea: we can keep a double-ended queue with max size k and only keep what we need to evaluate in it. That is, if we see \[1, 3, 5\], then we only need to keep \[5\], since we know that 1 and 3 cannot possibly be the maxes. 21 | 22 | So what we can do is maintain an ordered list of indices, where we only keep the elements we care about, that is, we will maintain the loop invariant that our queue is always ordered so that we only keep the indices we care about (i.e, there are no elements that are greater after, since we would just pick the greater element as the max instead). 23 | 24 | It will help to go over an example. Consider our test input: \[10, 5, 2, 7, 8, 7\] and k = 3. Our queue at each step would look like this (recall that these are indices): 25 | 26 | ### Preprocessing 27 | 28 | After processing 10: \[0\] After processing 5: \[0, 1\] # 5 is smaller than 10, and 10 is still valid until we hit the 3rd index After processing 2: \[0, 1, 2\] # 2 is smaller than 5, and 10 is still valid 29 | 30 | ### Main Loop 31 | 32 | Print value of first element in our queue: **10** 33 | 34 | After processing 7: \[4\] # 10 is no longer valid (we can tell since the current index - 0 > k), so we dequeue from the front. 7 is bigger than 5 and 2, so we get rid of them from the back and replace it with the 7 35 | 36 | Print value of first element in our queue: **7** 37 | 38 | After processing 8: \[5\] # 8 is bigger than 7, so no point in keeping 7 around. We get rid of it from the back and replace it with the 8 39 | 40 | Print value of first element in our queue: **8** 41 | 42 | After processing 7: \[5, 4\] # 7 is smaller than 8, so we enqueue it from the back 43 | 44 | Print value of first element in our queue: **8** 45 | 46 | ### Code 47 | 48 | from collections import deque 49 | 50 | def max_of_subarrays(lst, k): 51 | q = deque() 52 | for i in range(k): 53 | while q and lst[i] >= lst[q[-1]]: 54 | q.pop() 55 | q.append(i) 56 | 57 | # Loop invariant: q is a list of indices where their corresponding values are in descending order. 58 | for i in range(k, len(lst)): 59 | print(lst[q[0]]) 60 | while q and q[0] <= i - k: 61 | q.popleft() 62 | while q and lst[i] >= lst[q[-1]]: 63 | q.pop() 64 | q.append(i) 65 | print(lst[q[0]]) 66 | 67 | -------------------------------------------------------------------------------- /019/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #19 2 | 3 | This problem was asked by Facebook. 4 | 5 | A builder is looking to build a row of N houses that can be of K different colors. He has a goal of minimizing cost while ensuring that no two neighboring houses are of the same color. 6 | 7 | Given an N by K matrix where the nth row and kth column represents the cost to build the nth house with kth color, return the minimum cost which achieves this goal. -------------------------------------------------------------------------------- /019/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | The brute force solution here would be to generate all possible combinations of houses and colors, filter out invalid combinations, and keep track of the lowest cost seen. This would take O(N^K) time. 4 | 5 | We can solve this problem faster using dynamic programming. We can maintain a matrix cache where every entry \[i\]\[j\] represents the minimum cost of painting house i the color j, as well as painting every house < i. We can calculate this by looking at the minimum cost of painting each house < i - 1, and painting house i - 1 any color except j, since that would break our constraint. We'll initialize the first row with zeroes to start. Then, we just have to look at the smallest value in the last row of our cache, since that represents the minimum cost of painting every house. 6 | 7 | def build_houses(matrix): 8 | n = len(matrix) 9 | k = len(matrix[0]) 10 | solution_matrix = [[0] * k] 11 | 12 | # Solution matrix: matrix[i][j] represents the minimum cost to build house i with color j. 13 | for r, row in enumerate(matrix): 14 | row_cost = [] 15 | for c, val in enumerate(row): 16 | row_cost.append(min(solution_matrix[r][i] for i in range(k) if i != c) + val) 17 | solution_matrix.append(row_cost) 18 | return min(solution_matrix[-1]) 19 | 20 | 21 | This runs in O(N _K^2) time and O(N_ K) space. Can we do even better than this? 22 | 23 | First off, notice that we're only ever looking at the last row when computing the next row's cost. That suggests that we only need to keep track of one array of size K instead of a whole matrix of size N \* K: 24 | 25 | def build_houses(matrix): 26 | k = len(matrix[0]) 27 | soln_row = [0] * k 28 | 29 | for r, row in enumerate(matrix): 30 | new_row = [] 31 | for c, val in enumerate(row): 32 | new_row.append(min(soln_row[i] for i in range(k) if i != c) + val) 33 | soln_row = new_row 34 | return min(soln_row) 35 | 36 | 37 | Now we're only using O(K) space! Can we improve this any more? 38 | 39 | Hold on a second. When we're looking at the previous row's total cost, it looks like we're almost computing the same thing each time: the minimum of the previous row that isn't the current index. 40 | 41 | For every element that **isn't** that index, it will be the same value. When it **is** that index, it will be the second-smallest value. 42 | 43 | Now, armed with this insight, we only need to keep track of three variables: 44 | 45 | * The lowest cost of the current row 46 | * The index of the lowest cost 47 | * The second lowest cost 48 | 49 | Then, when looking at the value at each row, we only need to do the following: 50 | 51 | * Check if the index is the index of the lowest cost of the previous row. If it is, then we can't use this color -- we'll use the second lowest cost instead. Otherwise, use the lowest cost of the previous row 52 | * Calculate the minimum cost if we painted this house this particular color 53 | * Update our new lowest cost/index or second lowest cost if appropriate 54 | 55 | Now we'll always have our lowest cost in a variable, and once we've gone through the matrix we can just return that. 56 | 57 | from math import inf 58 | 59 | def build_houses(matrix): 60 | lowest_cost, lowest_cost_index = 0, -1 61 | second_lowest_cost = 0 62 | 63 | for r, row in enumerate(matrix): 64 | new_lowest_cost, new_lowest_cost_index = inf, -1 65 | new_second_lowest_cost = inf 66 | for c, val in enumerate(row): 67 | prev_lowest_cost = second_lowest_cost if c == lowest_cost_index else lowest_cost 68 | cost = prev_lowest_cost + val 69 | if cost < new_lowest_cost: 70 | new_second_lowest_cost = new_lowest_cost 71 | new_lowest_cost, new_lowest_cost_index = cost, c 72 | elif cost < new_second_lowest_cost: 73 | new_second_lowest_cost = cost 74 | lowest_cost = new_lowest_cost 75 | lowest_cost_index = new_lowest_cost_index 76 | second_lowest_cost = new_second_lowest_cost 77 | 78 | return lowest_cost 79 | 80 | 81 | Now the runtime is only O(N \* K) and the space complexity is O(1) - constant, since we keep track of only three variables! 82 | 83 | Thanks to Alexander Shirkov for pointing out these optimizations! 84 | -------------------------------------------------------------------------------- /020/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #20 2 | 3 | 4 | This problem was asked by Google. 5 | 6 | Given two singly linked lists that intersect at some point, find the intersecting node. The lists are non-cyclical. 7 | 8 | For example, given A = 3 -> 7 -> 8 -> 10 and B = 99 -> 1 -> 8 -> 10, return the node with value 8. 9 | 10 | In this example, assume nodes with the same value are the exact same node objects. 11 | 12 | Do this in O(M + N) time (where M and N are the lengths of the lists) and constant space. -------------------------------------------------------------------------------- /020/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | We might start this problem by first ignoring the time and space constraints, in order to get a better grasp of the problem. 4 | 5 | Naively, we could iterate through one of the lists and add each node to a set or dictionary, then we could iterate over the other list and check each node we're looking at to see if it's in the set. Then we'd return the first node that is present in the set. This takes O(M + N) time but also O(max(M, N)) space (since we don't know initially which list is longer). How can we reduce the amount of space we need? 6 | 7 | We can get around the space constraint with the following trick: first, get the length of both lists. Find the difference between the two, and then keep two pointers at the head of each list. Move the pointer of the larger list up by the difference, and then move the pointers forward in conjunction and check if they match. 8 | 9 | def length(head): 10 | if not head: 11 | return 0 12 | return 1 + length(head.next) 13 | 14 | def intersection(a, b): 15 | m, n = length(a), length(b) 16 | cur_a, cur_b = a, b 17 | 18 | if m > n: 19 | for _ in range(m - n): 20 | cur_a = cur_a.next 21 | else: 22 | for _ in range(n - m): 23 | cur_b = cur_b.next 24 | 25 | while cur_a != cur_b: 26 | cur_a = cur_a.next 27 | cur_b = cur_b.next 28 | return cur_a 29 | -------------------------------------------------------------------------------- /021/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #20 2 | 3 | 4 | This problem was asked by Snapchat. 5 | 6 | Given an array of time intervals (start, end) for classroom lectures (possibly overlapping), find the minimum number of rooms required. 7 | 8 | For example, given \[(30, 75), (0, 50), (60, 150)\], you should return 2. -------------------------------------------------------------------------------- /021/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | 4 | First, notice that the minimum number of classroom halls is the maximum number of overlapping intervals. 5 | 6 | Now let's consider the naive approach. We could go through each interval and check every other interval and see if it overlaps, keeping track of the largest number of overlapping intervals. 7 | 8 | def overlaps(a, b): 9 | start_a, end_a = a 10 | start_b, end_b = b 11 | # It doesn't overlap if it's like this: 12 | # |start_a .... end_a| <---> |start_b ... end_b| 13 | # or like this: 14 | # |start_b .... end_b| <---> |start_a ... end_a| 15 | # so return not or either of these 16 | return not (end_a < start_b or start_a > end_b) 17 | 18 | def max_overlapping(intervals): 19 | current_max = 0 20 | for interval in intervals: 21 | num_overlapping = sum(overlaps(interval, other_interval) 22 | for other_interval in intervals 23 | if interval is not other_interval) 24 | current_max = max(current_max, num_overlapping) 25 | return current_max 26 | 27 | 28 | This would take O(n^2) time, since we're checking each interval pairwise. Can we do any better? 29 | 30 | One solution is to extract the start times and end times of all the intervals and sort them. Then we can start two pointers on each list, and consider the following: 31 | 32 | * If the current start is before the current end, then we have a new overlap. Increment the start pointer. 33 | * If the current start is after the current end, then our overlap closes. Increment the end pointer. 34 | 35 | All that's left to do is keep a couple variables to keep track of the maximum number of overlaps we've seen so far and the current number of overlaps. 36 | 37 | def max_overlapping(intervals): 38 | starts = sorted(start for start, end in intervals) 39 | ends = sorted(end for start, end in intervals) 40 | 41 | current_max = 0 42 | current_overlap = 0 43 | i, j = 0, 0 44 | while i < len(intervals) and j < len(intervals): 45 | if starts[i] < ends[j]: 46 | current_overlap += 1 47 | current_max = max(current_max, current_overlap) 48 | i += 1 49 | else: 50 | current_overlap -= 1 51 | j += 1 52 | return current_max 53 | 54 | 55 | This runs in O(n log n) time, since we have to sort the intervals. -------------------------------------------------------------------------------- /022/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #22 2 | 3 | 4 | 5 | This problem was asked by Microsoft. 6 | 7 | Given a dictionary of words and a string made up of those words (no spaces), return the original sentence in a list. If there is more than one possible reconstruction, return any of them. If there is no possible reconstruction, then return null. 8 | 9 | For example, given the set of words 'quick', 'brown', 'the', 'fox', and the string "thequickbrownfox", you should return \['the', 'quick', 'brown', 'fox'\]. 10 | 11 | Given the set of words 'bed', 'bath', 'bedbath', 'and', 'beyond', and the string "bedbathandbeyond", return either \['bed', 'bath', 'and', 'beyond\] or \['bedbath', 'and', 'beyond'\]. -------------------------------------------------------------------------------- /022/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | 4 | We might be initially tempted to take a greedy approach to this problem, by for example, iterating over the string and checking if our current string matches so far. However, you should immediately find that that can't work: consider the dictionary {'the', 'theremin'} and the string 'theremin': we would find 'the' first, and then we wouldn't be able to match 'remin'. 5 | 6 | So this greedy approach doesn't work, since we would need to go back if we get stuck. This gives us a clue that we might want to use [backtracking](https://dailycodingproblem.com/blog/an-introduction-to-backtracking/) to help us solve this problem. We also have the following idea for a recurrence: If we split up the string into a prefix and suffix, then we can return the prefix extended with a list of the rest of the sentence, but only if they're both valid. So what we can do is the following: 7 | 8 | * Iterate over the string and split it into a prefix and suffix 9 | * If the prefix is valid (appears in the dictionary), then recursively call on the suffix 10 | * If that's valid, then return. Otherwise, continue searching. 11 | * If we've gone over the entire sentence and haven't found anything, then return empty. 12 | 13 | We'll need a helper function to tell us whether the string can actually be broken up into a sentence as well, so let's define `find_sentence_helper` that also returns whether or not the sentence is valid. 14 | 15 | def find_sentence(dictionary, s): 16 | sentence, valid = find_sentence_helper(dictionary, s) 17 | if valid: 18 | return sentence 19 | 20 | def find_sentence_helper(dictionary, s): 21 | if len(s) == 0: 22 | return [], True 23 | 24 | result = [] 25 | for i in range(len(s) + 1): 26 | prefix, suffix = s[:i], s[i:] 27 | if prefix in dictionary: 28 | rest, valid = find_sentence_helper(dictionary, suffix) 29 | if valid: 30 | return [prefix] + rest, True 31 | return [], False 32 | 33 | 34 | This will run in O(2^N) time, however. This is because in the worst case, say, for example, s = "aaaaab" and dictionary = \["a", "aa", "aaa", "aaaa", "aaaaa"\], we will end up exploring every single path, or every combination of letters, and the total number of combinations of characters is 2^N. 35 | 36 | We can improve the running time by using dynamic programming to store repeated subcomputations. This reduces the running time to just O(N^2). We'll keep a dictionary that maps from indices to the last word that can be made up to that index. We'll call these starts. Then, we just need to do two nested for loops, one that iterates over the whole string and tries to find a start at that index, and a loop that checks each start to see if a new word can be made from that start to the current index. 37 | 38 | Now we can simply take the start at the last index and build our sentence backwards: 39 | 40 | def find_sentence(s, dictionary): 41 | starts = {0: ''} 42 | for i in range(len(s) + 1): 43 | new_starts = starts.copy() 44 | for start_index, _ in starts.items(): 45 | word = s[start_index:i] 46 | if word in dictionary: 47 | new_starts[i] = word 48 | starts = new_starts.copy() 49 | 50 | result = [] 51 | current_length = len(s) 52 | if current_length not in starts: 53 | return None 54 | while current_length > 0: 55 | word = starts[current_length] 56 | current_length -= len(word) 57 | result.append(word) 58 | 59 | return list(reversed(result)) 60 | 61 | 62 | Now this runs in O(N^2) time and O(N) space. 63 | -------------------------------------------------------------------------------- /023/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #23 2 | 3 | 4 | This problem was asked by Google. 5 | 6 | You are given an M by N matrix consisting of booleans that represents a board. Each True boolean represents a wall. Each False boolean represents a tile you can walk on. 7 | 8 | Given this matrix, a start coordinate, and an end coordinate, return the minimum number of steps required to reach the end coordinate from the start. If there is no possible path, then return null. You can move up, left, down, and right. You cannot move through walls. You cannot wrap around the edges of the board. 9 | 10 | For example, given the following board: 11 | 12 | [[f, f, f, f], 13 | [t, t, f, t], 14 | [f, f, f, f], 15 | [f, f, f, f]] 16 | 17 | 18 | and start = `(3, 0)` (bottom left) and end = `(0, 0)` (top left), the minimum number of steps required to reach the end is 7, since we would need to go through `(1, 2)` because there is a wall everywhere else on the second row. 19 | -------------------------------------------------------------------------------- /023/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | The idea here is to use either BFS or DFS to explore the board, starting from the start coordinate, and keep track of what we've seen so far as well as the steps from the start until we find the end coordinate. 4 | 5 | In our case, we'll use BFS. We'll create a queue and initialize it with our start coordinate, along with a count of 0. We'll also initialize a `seen` set to ensure we only add coordinates we haven't seen before. 6 | 7 | Then, as long as there's something still in the queue, we'll dequeue from the queue and first check if it's our target coordinate -- if it is, then we can just immediately return the count. Otherwise, we'll get the valid neighbours of the coordinate we're working with (valid means not off the board and not a wall), and enqueue them to the end of the queue. 8 | 9 | To make sure the code doesn't get too messy, we'll define some helper functions: `walkable`, which returns whether or not a tile is valid, and `get_walkable_neighbours` which returns the valid neighbours of a coordinate. 10 | 11 | from collections import deque 12 | 13 | # Given a row and column, returns whether that tile is walkable. 14 | def walkable(board, row, col): 15 | if row < 0 or row >= len(board): 16 | return False 17 | if col < 0 or col >= len(board[0]): 18 | return False 19 | return not board[row][col] 20 | 21 | # Gets walkable neighbouring tiles. 22 | def get_walkable_neighbours(board, row, col): 23 | return [(r, c) for r, c in [ 24 | (row, col - 1), 25 | (row - 1, col), 26 | (row + 1, col), 27 | (row, col + 1)] 28 | if walkable(board, r, c) 29 | ] 30 | 31 | def shortest_path(board, start, end): 32 | seen = set() 33 | queue = deque([(start, 0)]) 34 | while queue: 35 | coords, count = queue.popleft() 36 | if coords == end: 37 | return count 38 | seen.add(coords) 39 | neighbours = get_walkable_neighbours(board, coords[0], coords[1]) 40 | queue.extend((neighbour, count + 1) for neighbour in neighbours 41 | if neighbour not in seen) 42 | 43 | board = [[False, False, False, False], 44 | [True, True, True, True], 45 | [False, False, False, False], 46 | [False, False, False, False]] 47 | 48 | print(shortest_path(board, (3, 0), (0, 0))) 49 | 50 | 51 | This code should run in O(M \* N) time and space, since in the worst case we need to examine the entire board to find our target coordinate. 52 | -------------------------------------------------------------------------------- /024/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #24 2 | 3 | 4 | This problem was asked by Google. 5 | 6 | Implement locking in a binary tree. A binary tree node can be locked or unlocked only if all of its descendants or ancestors are not locked. 7 | 8 | Design a binary tree node class with the following methods: 9 | 10 | * `is_locked`, which returns whether the node is locked 11 | * `lock`, which attempts to lock the node. If it cannot be locked, then it should return false. Otherwise, it should lock it and return true. 12 | * `unlock`, which unlocks the node. If it cannot be unlocked, then it should return false. Otherwise, it should unlock it and return true. 13 | 14 | You may augment the node to add parent pointers or any other property you would like. You may assume the class is used in a single-threaded program, so there is no need for actual locks or mutexes. Each method should run in O(h), where h is the height of the tree. 15 | -------------------------------------------------------------------------------- /024/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | A relatively easy way to implement this would be to augment each node with an `is_locked` attribute as well as a parent pointer. We can then implement the methods in a straightforward manner: 4 | 5 | * `is_locked` simply returns the node's attribute 6 | * `lock` searches the node's children and parents for a true `is_locked` attribute. If it is set to true on any of them, then return false. Otherwise, set the current node's `is_locked` to true and return true. 7 | * `unlock` simply changes the node's attribute to false. If we want to be safe, then we should search the node's children and parents as in `lock` to make sure we can actually unlock the node, but that shouldn't ever happen. 8 | 9 | While `is_locked` is O(1) time, `lock` and `unlock` will take O(m + h) time where m is the number of nodes in the node's subtree (since we have to traverse through all its descendants) and h is the height of the node (since we have to traverse through the node's ancestors). 10 | 11 | We can improve the performance of `lock` and `unlock` by adding another field to the node that keeps tracks of the count of locked descendants. That way, we can immediately see whether any of its descendants are locked. This will reduce our `lock` and `unlock` functions to only O(h). We can maintain this field by doing the following: 12 | 13 | * When locking, if the locking succeeds, traverse the node's ancestors and increment each one's count 14 | * When unlocking, traverse the node's ancestors and decrement each one's count 15 | 16 | The code will look something like the following: 17 | 18 | class LockingBinaryTreeNode(object): 19 | def __init__(self, val, left=None, right=None, parent=None): 20 | self.val = val 21 | self.left = left 22 | self.right = right 23 | self.parent = parent 24 | self.is_locked = False 25 | self.locked_descendants_count = 0 26 | 27 | def _can_lock_or_unlock(self): 28 | if self.locked_descendants_count > 0: 29 | return False 30 | 31 | cur = self.parent 32 | while cur: 33 | if cur.is_locked: 34 | return False 35 | cur = cur.parent 36 | return True 37 | 38 | def is_locked(self): 39 | return self.is_locked 40 | 41 | 42 | def lock(self): 43 | if self.is_locked: 44 | return False # node already locked 45 | 46 | if not self._can_lock_or_unlock(): 47 | return False 48 | 49 | # Not locked, so update is_locked and increment count in all ancestors 50 | self.is_locked = True 51 | 52 | cur = self.parent 53 | while cur: 54 | cur.locked_descendants_count += 1 55 | cur = cur.parent 56 | return True 57 | 58 | def unlock(self): 59 | if not self.is_locked: 60 | return False # node already unlocked 61 | 62 | if not self._can_lock_or_unlock(): 63 | return False 64 | 65 | self.is_locked = False 66 | 67 | # Update count in all ancestors 68 | cur = self.parent 69 | while cur: 70 | cur.locked_descendants_count -= 1 71 | cur = cur.parent 72 | return True 73 | 74 | 75 | Now, `is_locked` is still O(1), but `lock` and `unlock` are both O(h) instead of O(m + h). -------------------------------------------------------------------------------- /025/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #25 2 | 3 | This problem was asked by Facebook. 4 | 5 | Implement regular expression matching with the following special characters: 6 | 7 | * `.` (period) which matches any single character 8 | * `*` (asterisk) which matches zero or more of the preceding element 9 | 10 | That is, implement a function that takes in a string and a valid regular expression and returns whether or not the string matches the regular expression. 11 | 12 | For example, given the regular expression "ra." and the string "ray", your function should return true. The same regular expression on the string "raymond" should return false. 13 | 14 | Given the regular expression ".\*at" and the string "chat", your function should return true. The same regular expression on the string "chats" should return false. -------------------------------------------------------------------------------- /025/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | 4 | This problem should strike you as recursive. The string should match the regex if we can match the head of the string with the head of the regex and the rest of the string with the rest of the regex. The special characters `.` and `*` make implementing this a bit trickier, however, since the `*` means we can match 0 or any number of characters in the beginning. 5 | 6 | The basic idea then is to do the following. Let's call the string we want to match `s` and the regex `r`. 7 | 8 | * Base case: if `r` is empty, then return whether `s` is empty or not. 9 | * Otherwise, if the first thing in `r` is not proceeded by a `*`, then match the first character of both `r` and `s`, and if they match, return `match(r[1:], s[1:])`. If they don't, then return false. 10 | * If the first thing in `r` \_is\_ proceeded by a `*`, then try every suffix substring of `s` on `r[2:]` and return true if any suffix substring works. 11 | 12 | The code should look something like this: 13 | 14 | def matches_first_char(s, r): 15 | return s[0] == r[0] or (r[0] == '.' and len(s) > 0) 16 | 17 | def matches(s, r): 18 | if r == '': 19 | return s == '' 20 | 21 | if len(r) == 1 or r[1] != '*': 22 | # The first character in the regex is not proceeded by a *. 23 | if matches_first_char(s, r): 24 | return matches(s[1:], r[1:]) 25 | else: 26 | return False 27 | else: 28 | # The first character is proceeded by a *. 29 | # First, try zero length. 30 | if matches(s, r[2:]): 31 | return True 32 | # If that doesn't match straight away, then try globbing more prefixes 33 | # until the first character of the string doesn't match anymore. 34 | i = 0 35 | while matches_first_char(s[i:], r): 36 | if matches(s[i+1:], r[2:]): 37 | return True 38 | i += 1 39 | 40 | 41 | This takes O(len(s) \* len(r)) time and space, since we potentially need to iterate over each suffix substring again for each character. 42 | 43 | Fun fact: Stephen Kleene introduced the `*` operator in regular expressions and as such, it is sometimes referred to as the Kleene star. 44 | -------------------------------------------------------------------------------- /026/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #25 2 | 3 | 4 | This problem was asked by Google. 5 | 6 | Given a singly linked list and an integer k, remove the kth last element from the list. k is guaranteed to be smaller than the length of the list. 7 | 8 | The list is very long, so making more than one pass is prohibitively expensive. 9 | 10 | Do this in constant space and in one pass. -------------------------------------------------------------------------------- /026/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | 4 | If we didn't have the constraint of needing only to make one pass, this problem would be trivial to implement. We could simply iterate over the whole list to find out the total length N of the list, and then restart from the beginning and iterate N - k steps and remove the node there. That would take constant space as well. 5 | 6 | However, given that we have the constraint of needing to make only one pass, we have to find some way of getting the N - kth node in the list in one shot. 7 | 8 | What we can do, then, is this: 9 | 10 | * Set up two pointers at the head of the list (let's call them `fast` and `slow`) 11 | * Move `fast` up by `k` 12 | * Move both `fast` and `slow` together until `fast` reaches the end of the list 13 | * Now `slow` is at the N - kth node, remove it 14 | 15 | That only makes one pass and is constant time. The code should look something like this: 16 | 17 | class Node: 18 | def __init__(self, val, next=None): 19 | self.val = val 20 | self.next = next 21 | 22 | def __str__(self): 23 | current_node = self 24 | result = [] 25 | while current_node: 26 | result.append(current_node.val) 27 | current_node = current_node.next 28 | return str(result) 29 | 30 | def remove_kth_from_linked_list(head, k): 31 | slow, fast = head, head 32 | for i in range(k): 33 | fast = fast.next 34 | 35 | prev = None 36 | while fast: 37 | prev = slow 38 | slow = slow.next 39 | fast = fast.next 40 | 41 | prev.next = slow.next 42 | 43 | head = Node(1, Node(2, Node(3, Node(4, Node(5))))) 44 | print(head) 45 | remove_kth_from_linked_list(head, 3) 46 | print(head) 47 | -------------------------------------------------------------------------------- /027 [Easy]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #25 2 | 3 | 4 | This problem was asked by Facebook. 5 | 6 | Given a string of round, curly, and square open and closing brackets, return whether the brackets are balanced (well-formed). 7 | 8 | For example, given the string "(\[\])\[\]({})", you should return true. 9 | 10 | Given the string "(\[)\]" or "((()", you should return false. -------------------------------------------------------------------------------- /027 [Easy]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | In this case, it's easy to start with a simplified case of the problem, which is dealing with only round brackets. Notice that in this case, we just need to keep track of the current number of open brackets -- each closing bracket should be matched with the rightmost open bracket. So we can keep a counter and increment it for every open bracket we see and decrement it on every closing bracket. If we get to the end of the string and have a non-zero number, then it means it's unbalanced. A negative number would indicate more closing brackets than open ones, and a positive number would indicate the opposite. 4 | 5 | In the case of round, curly, and square brackets, we need to also keep track of what _kind_ of brackets they are as well, because we can't match a round open bracket with a curly square. In this case, we can use a stack to keep track of the actual bracket character and push onto it whenever we encounter an open bracket, and pop if we encounter a matching closing bracket to the top of the stack. If the stack is empty or it's not the correct matching bracket, then we'll return false. If, by the end of the iteration, we have something left over in the stack, then it means it's unbalanced -- so we'll return whether it's empty or not. 6 | 7 | def balance(s): 8 | stack = [] 9 | for char in s: 10 | if char in ["(", "[", "{"]: 11 | stack.append(char) 12 | else: 13 | # Check character is not unmatched 14 | if not stack: 15 | return False 16 | 17 | # Char is a closing bracket, check top of stack if it matches 18 | if (char == ")" and stack[-1] != "(") or \ 19 | (char == "]" and stack[-1] != "[") or \ 20 | (char == "}" and stack[-1] != "{"): 21 | return False 22 | stack.pop() 23 | 24 | return len(stack) == 0 25 | 26 | 27 | Fun fact: "(())" is not a palindrome, nor is "()()". "())(" is a palindrome, though. 28 | -------------------------------------------------------------------------------- /028 [Medium]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #28 2 | 3 | 4 | 5 | This problem was asked by Palantir. 6 | 7 | Write an algorithm to justify text. Given a sequence of words and an integer line length k, return a list of strings which represents each line, fully justified. 8 | 9 | More specifically, you should have as many words as possible in each line. There should be at least one space between each word. Pad extra spaces when necessary so that each line has exactly length k. Spaces should be distributed as equally as possible, with the extra spaces, if any, distributed starting from the left. 10 | 11 | If you can only fit one word on a line, then you should pad the right-hand side with spaces. 12 | 13 | Each word is guaranteed not to be longer than k. 14 | 15 | For example, given the list of words \["the", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"\] and k = 16, you should return the following: 16 | 17 | ["the quick brown", # 1 extra space on the left 18 | "fox jumps over", # 2 extra spaces distributed evenly 19 | "the lazy dog"] # 4 extra spaces distributed evenly 20 | 21 | -------------------------------------------------------------------------------- /028 [Medium]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | It seems like the justification algorithm is independent from the groupings, so immediately we should figure out two things: 4 | 5 | * How to group lines together so that it is as close to k as possible (without going over) 6 | * Given a grouping of lines, justifying the text by appropriately distributing spaces 7 | 8 | To solve the first part, let's write a function `group_lines` that takes in all the words in our input sequence as well as out target line length k, and return a list of list of words that represents the lines that we will eventually justify. Our main strategy will be to iterate over all the words, keep a list of words for the current line, and because we want to fit as many words as possible per line, estimate the current line length, assuming only one space between each word. Once we go over `k`, then save the word and start a new line with it. So our function will look something like this: 9 | 10 | def min_line(words): 11 | return ' '.join(words) 12 | 13 | def group_lines(words, k): 14 | ''' 15 | Returns groupings of |words| whose total length, including 1 space in between, 16 | is less than |k|. 17 | ''' 18 | groups = [] 19 | current_sum = 0 20 | current_line = [] 21 | for i, word in enumerate(wordwordss): 22 | # Check if adding the next word would push it over 23 | # the limit. If it does, then add |current_line| to 24 | # group. Also reset |current_line| properly. 25 | if len(min_line(current_line + [word])) > k: 26 | groups.append(current_line) 27 | current_line = [] 28 | current_line.append(word) 29 | 30 | # Add the last line to groups. 31 | groups.append(current_line) 32 | return groups 33 | 34 | 35 | Then, we'll want to actually justify each line. We know for sure each line we feed from `group_lines` is the maximum number of words we can pack into a line and no more. What we can do is first figure out how many spaces we have available to distribute between each word. Then from that, we can calculate how much base space we should have between each word by dividing it by the number of words minus one. If there are any leftover spaces to distribute, then we can keep track of that in a counter, and as we rope in each new word we'll add the appropriate number of spaces. We can't add more than one leftover space per word. 36 | 37 | def justify(words, length): 38 | ''' 39 | Precondition: |words| can fit in |length|. 40 | Justifies the words using the following algorithm: 41 | - Find the smallest spacing between each word (available_spaces / spaces) 42 | - Add a leftover space one-by-one until we run out 43 | ''' 44 | if len(words) == 1: 45 | word = words[0] 46 | num_spaces = length - len(word) 47 | spaces = ' ' * num_spaces 48 | return word + spaces 49 | spaces_to_distribute = length - sum(len(word) for word in words) 50 | number_of_spaces = len(words) - 1 51 | smallest_space = floor(spaces_to_distribute / number_of_spaces) 52 | leftover_spaces = spaces_to_distribute - (number_of_spaces * smallest_space) 53 | justified_words = [] 54 | for word in words: 55 | justified_words.append(word) 56 | current_space = ' ' * smallest_space 57 | if leftover_spaces > 0: 58 | current_space += ' ' 59 | leftover_spaces -= 1 60 | justified_words.append(current_space) 61 | return ''.join(justified_words).rstrip() 62 | 63 | 64 | The final solution should just combine our two functions: 65 | 66 | def justify_text(words, k): 67 | return [justify(group, k) for group in group_lines(words, k)] 68 | 69 | -------------------------------------------------------------------------------- /029 [Easy]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #29 2 | 3 | 4 | This problem was asked by Amazon. 5 | 6 | Run-length encoding is a fast and simple method of encoding strings. The basic idea is to represent repeated successive characters as a single count and character. For example, the string "AAAABBBCCDAA" would be encoded as "4A3B2C1D2A". 7 | 8 | Implement run-length encoding and decoding. You can assume the string to be encoded have no digits and consists solely of alphabetic characters. You can assume the string to be decoded is valid. 9 | -------------------------------------------------------------------------------- /029 [Easy]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | We can implement `encode` by iterating over our input string and keeping a current count of whatever the current character is, and once we encounter a different one, appending the count (as a string) and the actual character to our result string. 4 | 5 | def encode(s): 6 | if not s: 7 | return '' 8 | 9 | result = '' 10 | current_char = s[0] 11 | current_count = 1 12 | for i, char in enumerate(s, 1): 13 | if char == current_char: 14 | current_count += 1 15 | else: 16 | result += str(current_count) + current_char 17 | current_char = char 18 | current_count = 1 19 | result += str(current_count) + current_char 20 | return result 21 | 22 | 23 | We can implement `decode` by iterating over the encoded string and checking each character for a digit. If it is, then calculate the correct count, and once we find its corresponding character, extend the result with the character count number of times and then reset the count. 24 | 25 | def decode(s): 26 | count = 0 27 | result = '' 28 | for char in s: 29 | if char.isdigit(): 30 | count = count * 10 + int(char) 31 | else: 32 | # char is alphabetic 33 | result += char * count 34 | count = 0 35 | return result 36 | 37 | -------------------------------------------------------------------------------- /030 [Medium]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #30 2 | 3 | This problem was asked by Facebook. 4 | 5 | You are given an array of non-negative integers that represents a two-dimensional elevation map where each element is unit-width wall and the integer is the height. Suppose it will rain and all spots between two walls get filled up. 6 | 7 | Compute how many units of water remain trapped on the map in O(N) time and O(1) space. 8 | 9 | For example, given the input \[2, 1, 2\], we can hold 1 unit of water in the middle. 10 | 11 | Given the input \[3, 0, 1, 3, 0, 5\], we can hold 3 units in the first index, 2 in the second, and 3 in the fourth index (we cannot hold 5 since it would run off to the left), so we can trap 8 units of water. 12 | -------------------------------------------------------------------------------- /030 [Medium]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | Notice that the amount of water that can be filled up at a certain index i is the smaller of the largest height to the left and the largest height to the right minus the actual value at that point, because it will be trapped by the smaller of the two sides. So what we can do is to create two arrays that represent the running maximum heights, one from the left and one from the right. Then to count the total capacity, we can run through the both arrays and add up the smaller of the two arrays at that index. 4 | 5 | def capacity(arr): 6 | n = len(arr) 7 | left_maxes = [0 for _ in range(n)] 8 | right_maxes = [0 for _ in range(n)] 9 | 10 | current_left_max = 0 11 | for i in range(n): 12 | current_left_max = max(current_left_max, arr[i]) 13 | left_maxes[i] = current_left_max 14 | 15 | current_right_max = 0 16 | for i in range(n - 1, -1, -1): 17 | current_right_max = max(current_right_max, arr[i]) 18 | right_maxes[i] = current_right_max 19 | 20 | total = 0 21 | for i in range(n): 22 | total += min(left_maxes[i], right_maxes[i]) - arr[i] 23 | return total 24 | 25 | 26 | This is O(N) time, but also O(N) space, and we want constant space. So instead, we can do this. We can find the largest element in the array, and then when we're looking on the left of it, we only need to keep the running total to the left (since we know the largest element on the array is on the right). And then do a similar thing, but starting from the right side. So the general gist is this: 27 | 28 | * Find the maximum element in the array -- let's say it's at index i 29 | * Initialize a running maximum on the left to arr\[0\] 30 | * Iterate from index 1 to i. At each step, update the running maximum if necessary and then increment a variable counter with the running maximum minus the value at that array. 31 | * Do the same thing but from len(arr) - 2 to i backwards, and keep the running maximum on the right. 32 | 33 | def capacity(arr): 34 | if not arr: 35 | return 0 36 | 37 | total = 0 38 | max_i = arr.index(max(arr)) 39 | 40 | left_max = arr[0] 41 | for num in arr[1:max_i]: 42 | total += left_max - num 43 | left_max = max(left_max, num) 44 | 45 | right_max = arr[-1] 46 | for num in arr[-2:max_i:-1]: 47 | total += right_max - num 48 | right_max = max(right_max, num) 49 | 50 | return total -------------------------------------------------------------------------------- /031 [Easy]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #30 2 | 3 | This problem was asked by Google. 4 | 5 | The edit distance between two strings refers to the minimum number of character insertions, deletions, and substitutions required to change one string to the other. For example, the edit distance between “kitten” and “sitting” is three: substitute the “k” for “s”, substitute the “e” for “i”, and append a “g”. 6 | 7 | Given two strings, compute the edit distance between them. 8 | -------------------------------------------------------------------------------- /031 [Easy]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | 4 | First, notice that we can probably define this problem recursively. How can we notice this? If we look at the example (kitten -> sitting) and its solution path (kitten -> sitten -> sittin -> sitting), we can see that it's the minimum distance between sitten and sitting plus one. 5 | 6 | The recurrence, then, looks like this: 7 | 8 | * If either `s1` or `s2` are empty, then return the size of the larger of the two strings (since we can trivially turn an empty string into a string by inserting all its characters) 9 | * Otherwise, return the minimum between: 10 | * The edit distance between each string and the last n - 1 characters of the other plus one 11 | * If the first character in each string is the same, then the edit distance between s1\[1:\] and s2\[1:\], otherwise the same edit distance + 1 12 | 13 | So, the naive recursive solution would look like this: 14 | 15 | def distance(s1, s2): 16 | if len(s1) == 0 or len(s2) == 0: 17 | return max(len(s1), len(s2)) 18 | 19 | return min(distance(s1[1:], s2) + 1, 20 | distance(s1, s2[1:]) + 1, 21 | distance(s1[1:], s2[1:]) if s1[0] == s2[0] 22 | else distance(s1[1:], s2[1:]) + 1) 23 | 24 | 25 | However, this runs very slowly due to repeated subcomputations. We can speed it up by using dynamic programming and storing the subcomputations in a 2D matrix. The index at i, j will contain the edit distance between `s1[:i]` and `s2[:j]`. Then, once we fill it up, we can return the value of the matrix at A\[-1\]\[-1\]. 26 | 27 | def distance(s1, s2): 28 | x = len(s1) + 1 # the length of the x-coordinate 29 | y = len(s2) + 1 # the length of the y-coordinate 30 | 31 | A = [[-1 for i in range(x)] for j in range(y)] 32 | for i in range(x): 33 | A[0][i] = i 34 | 35 | for j in range(y): 36 | A[j][0] = j 37 | 38 | for i in range(1, y): 39 | for j in range(1, x): 40 | if s1[j- 1] == s2[i - 1]: 41 | A[i][j] = A[i - 1][j - 1] 42 | else: 43 | A[i][j] = min( 44 | A[i - 1][j] + 1, 45 | A[i][j - 1] + 1, 46 | A[i - 1][j - 1] + 1 47 | ) 48 | return A[y - 1][x - 1] # return the edit distance between the two strings 49 | 50 | 51 | This now takes O(N \* M) time and space, where N and M are the lengths of the strings. 52 | -------------------------------------------------------------------------------- /032 [Hard]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #32 2 | 3 | This problem was asked by Jane Street. 4 | 5 | Suppose you are given a table of currency exchange rates, represented as a 2D array. Determine whether there is a possible arbitrage: that is, whether there is some sequence of trades you can make, starting with some amount A of any currency, so that you can end up with some amount greater than A of that currency. 6 | 7 | There are no transaction costs and you can trade fractional quantities. 8 | -------------------------------------------------------------------------------- /032 [Hard]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | In this question, we can model the currencies and the exchange rates as a graph, where the nodes are the currencies and the edges are the exchange rates between each commodity. Since our table is complete, the graph is also complete. Then, to solve this problem, we need to find a cycle whose edge weights product is greater than 1. 4 | 5 | This seems hard to do faster than brute force, so let's try to reduce it down to a problem we already know we can solve faster than brute force. Hint: `log(a * b) = log(a) + log(b)`. So if we take the negative log of the edge weights, the problem of finding a cumulative product that's greater than 1 turns into the problem of finding a negative sum cycle. 6 | 7 | The Bellman-Ford algorithm can detect negative cycles. So if we run Bellman-Ford on our graph and discover one, then that means its corresponding edge weights multiply out to more than 1, and thus we can perform an arbitrage. 8 | 9 | As a refresher, the Bellman-Ford algorithm is commonly used to find the shortest path between a source vertex and each of the other vertices. If the graph contains a negative cycle, however, it can detect it and throw an exception (or, in our case, return true). The main idea of Bellman-Ford is this: 10 | 11 | Since the longest path in any graph has at most |V| - 1 edges, if we take all the direct edges from our source node, then we have all the one-edged shortest paths; once we take edges from there, we have all the two-edged shortest paths; all the way until |V| - 1 sized paths. 12 | 13 | If, after |V| - 1 iterations of this, we can still find a smaller path, then there must be a negative cycle in the graph. 14 | 15 | from math import log 16 | 17 | def arbitrage(table): 18 | transformed_graph = [[-log(edge) for edge in row] for row in graph] 19 | 20 | # Pick any source vertex -- we can run Bellman-Ford from any vertex and 21 | # get the right result 22 | source = 0 23 | n = len(transformed_graph) 24 | min_dist = [float('inf')] * n 25 | 26 | min_dist[source] = 0 27 | 28 | # Relax edges |V - 1| times 29 | for i in range(n - 1): 30 | for v in range(n): 31 | for w in range(n): 32 | if min_dist[w] > min_dist[v] + transformed_graph[v][w]: 33 | min_dist[w] = min_dist[v] + transformed_graph[v][w] 34 | 35 | # If we can still relax edges, then we have a negative cycle 36 | for v in range(n): 37 | for w in range(n): 38 | if min_dist[w] > min_dist[v] + transformed_graph[v][w]: 39 | return True 40 | 41 | return False 42 | 43 | 44 | Because of the triply-nested foor loop, this runs in O(N^3) time. 45 | -------------------------------------------------------------------------------- /033 [Easy]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #33 2 | 3 | This problem was asked by Microsoft. 4 | 5 | Compute the running median of a sequence of numbers. That is, given a stream of numbers, print out the median of the list so far on each new element. 6 | 7 | Recall that the median of an even-numbered list is the average of the two middle numbers. 8 | 9 | For example, given the sequence \[2, 1, 5, 7, 2, 0, 5\], your algorithm should print out: 10 | 11 | 2 12 | 1.5 13 | 2 14 | 3.5 15 | 2 16 | 2 17 | 2 18 | -------------------------------------------------------------------------------- /033 [Easy]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | For this problem, the trick is to use two heaps: a min-heap and a max-heap. We keep all elements smaller than the median in the max-heap and all elements larger than the median in the min-heap. We'll keep these heaps balanced so that the median is always either the root of the min-heap or the max-heap (or both). 4 | 5 | When we encounter a new element from the stream, we'll first add it to one of our heaps: the max-heap if the element is smaller than the median, or the min-heap if it's bigger. We can make the max-heap the default heap if they're equal or there are no elements. 6 | 7 | Then we re-balance if necessary by moving the root of the larger heap to the smaller one. It's only necessary if the a heap is larger than the other by more than 1 element. 8 | 9 | Finally, we can print out our median: it will just be the root of the larger heap, or the average of the two roots if they're of equal size. 10 | 11 | Since Python has really terrible support for heaps, we'll pretend we have some heap objects that have the standard interface: 12 | 13 | def get_median(min_heap, max_heap): 14 | if len(min_heap) > len(max_heap): 15 | return min_heap.find_min() 16 | elif len(min_heap) < len(max_heap): 17 | return max_heap.find_max() 18 | else: 19 | min_root = min_heap.find_min() 20 | max_root = max_heap.find_max() 21 | return (min_root + max_root) / 2 22 | 23 | def add(num, min_heap, max_heap): 24 | # If empty, then just add it to the max heap. 25 | if len(min_heap) + len(max_heap) <= 1: 26 | max_heap.insert(num) 27 | return 28 | 29 | median = get_median(min_heap, max_heap) 30 | if num > median: 31 | # add it to the min heap 32 | min_heap.insert(num) 33 | else: 34 | max_heap.insert(num) 35 | 36 | def rebalance(min_heap, max_heap): 37 | if len(min_heap) > len(max_heap) + 1: 38 | root = min_heap.extract_min() 39 | max_heap.insert(root) 40 | elif len(max_heap) > len(min_heap) + 1: 41 | root = max_heap.extract_max() 42 | min_heap.insert(root) 43 | 44 | def print_median(min_heap, max_heap): 45 | print(get_median(min_heap, max_heap)) 46 | 47 | def running_median(stream): 48 | min_heap = minheap() 49 | max_heap = maxheap() 50 | for num in stream: 51 | add(num, min_heap, max_heap) 52 | rebalance(min_heap, max_heap) 53 | print_median(min_heap, max_heap) 54 | 55 | 56 | This runs in O(N) space. In terms of time, each new element takes O(log N) time to manipulate the heaps, so this will run in O(N log N) time. 57 | -------------------------------------------------------------------------------- /034 [Medium]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #34 2 | 3 | This problem was asked by Quora. 4 | 5 | Given a string, find the palindrome that can be made by inserting the fewest number of characters as possible anywhere in the word. If there is more than one palindrome of minimum length that can be made, return the lexicographically earliest one (the first one alphabetically). 6 | 7 | For example, given the string "race", you should return "ecarace", since we can add three letters to it (which is the smallest amount to make a palindrome). There are seven other palindromes that can be made from "race" by adding three letters, but "ecarace" comes first alphabetically. 8 | 9 | As another example, given the string "google", you should return "elgoogle". -------------------------------------------------------------------------------- /034 [Medium]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | Notice that whenever we add a character, it should ideally match the one on the other side of the string. We can use the following recurrence to solve this problem: 4 | 5 | * If `s` is already a palindrome, then just return `s` -- it's already the shortest palindrome we can make 6 | * If the first character of `s` (let's call it `a`) is the same as the last, then return `a + make_palindrome(s[1:-1]) + a` 7 | * If the first character of `s` is different from the last (let's call this `b`), then return the minimum between: 8 | * `a + make_palindrome(s[1:]) + a` 9 | * `b + make_palindrome(s[:-1]) + b` or the lexicographically earliest one if their lengths are equal. 10 | 11 | So a naive recursive solution might look like this: 12 | 13 | def is_palindrome(s): 14 | return s == s[::-1] 15 | 16 | def make_palindrome(s): 17 | if is_palindrome(s): 18 | return s 19 | if s[0] == s[-1]: 20 | return s[0] + make_palindrome(s[1:-1]) + s[-1] 21 | else: 22 | one = s[0] + make_palindrome(s[1:]) + s[0] 23 | two = s[-1] + make_palindrome(s[:-1]) + s[-1] 24 | if len(one) < len(two): 25 | return one 26 | elif len(one) > len(two): 27 | return two 28 | else: 29 | return min(one, two) 30 | 31 | 32 | Recall that the min of two strings in python will return the lexicographically earliest one! 33 | 34 | However, this algorithm runs in O(2^N) time, since we could potentially make two recursive calls each time. We can speed up using dynamic programming, as usual. We can either [memoize](https://en.wikipedia.org/wiki/Memoization) our results so that we don't duplicate any work, or use a table and do bottom-up programming. 35 | 36 | Let's start with memoization. We can keep a cache and store all our results when we compute them in the cache. If we come across a string we've seen before, then we just need to look it up in the cache. 37 | 38 | cache = {} 39 | 40 | def is_palindrome(s): 41 | return s == s[::-1] 42 | 43 | def make_palindrome(s): 44 | if s in cache: 45 | return cache[s] 46 | 47 | if is_palindrome(s): 48 | cache[s] = s 49 | return s 50 | if s[0] == s[-1]: 51 | result = s[0] + make_palindrome(s[1:-1]) + s[-1] 52 | cache[s] = result 53 | return result 54 | else: 55 | one = s[0] + make_palindrome(s[1:]) + s[0] 56 | two = s[-1] + make_palindrome(s[:-1]) + s[-1] 57 | cache[s] = min(one, two) 58 | return min(one, two) 59 | 60 | 61 | However, this is inefficient due to buildup in the call stack. We can build a 2D table instead. We'll store, in each index, the shortest palindrome that can be made in the substring defined from `i` to `i + j`. Then instead of calling ourselves recursively, we'll just look up the values in our table: 62 | 63 | def make_palindrome(s): 64 | if len(s) <= 1: 65 | return s 66 | table = [['' for i in range(len(s) + 1)] for j in range(len(s) + 1)] 67 | 68 | for i in range(len(s)): 69 | table[i][1] = s[i] 70 | 71 | for j in range(2, len(s) + 1): 72 | for i in range(len(s) - j + 1): 73 | term = s[i:i + j] 74 | first, last = term[0], term[-1] 75 | if first == last: 76 | table[i][j] = first + table[i + 1][j - 2] + last 77 | else: 78 | one = first + table[i + 1][j - 1] + first 79 | two = last + table[i][j - 1] + last 80 | if len(one) < len(two): 81 | table[i][j] = one 82 | elif len(one) > len(two): 83 | table[i][j] = two 84 | else: 85 | table[i][j] = min(one, two) 86 | 87 | return table[0][-1] 88 | 89 | 90 | Because we store a part of our input string in each index of our matrix, the time and space complexity for this solution is O(N^3). 91 | -------------------------------------------------------------------------------- /035 [Hard]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #35 2 | 3 | This problem was asked by Google. 4 | 5 | Given an array of strictly the characters 'R', 'G', and 'B', segregate the values of the array so that all the Rs come first, the Gs come second, and the Bs come last. You can only swap elements of the array. 6 | 7 | Do this in linear time and in-place. 8 | 9 | For example, given the array \['G', 'B', 'R', 'R', 'B', 'R', 'G'\], it should become \['R', 'R', 'R', 'G', 'G', 'B', 'B'\]. -------------------------------------------------------------------------------- /035 [Hard]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | It may be easier to first consider an easier problem: one with only two possible values, say 'R' and 'G'. Then we could maintain the following loop invariant quite easily: 4 | 5 | * Maintain three sections of the array using two indices, `low` and `high`: 6 | * Strictly 'R's: array\[:low\] 7 | * Unknown: array\[low:high\] 8 | * Strictly 'G's: array\[high:\] 9 | 10 | Initially, low will be 0 and high will be `len(array) - 1`, since the whole array is unknown. As we iterate over the array, we'll swap any 'G's we see to the third section and decrement `high`. If we see an 'R', then we just need to increment `low`, since that's where it belongs. We can terminate once `low` crosses `high`. So we can gradually shrink our unknown section through the following algorithm: 11 | 12 | def partition(arr): 13 | low, high = 0, len(arr) - 1 14 | while low <= high: 15 | if arr[low] == 'R': 16 | low += 1 17 | else: 18 | arr[low], arr[high] = arr[high], arr[low] 19 | high -= 1 20 | 21 | 22 | This correctly partitions our array into two separate categories. How can we extend this to three partitions? Let's maintain four sections using 3 indices, `low`, `mid`, and `high`: 23 | 24 | * Strictly 'R's: array\[:low\] 25 | * Strictly 'G's: array\[low:mid\] 26 | * Unknown: array\[mid:high\] 27 | * Strictly 'B's: array\[high:\] 28 | 29 | We'll initialize `low` and `mid` both to 0, and `high` to `len(array) - 1` so that our unknown section is the whole array, as before. To maintain this invariant, we should do the following: 30 | 31 | * Look at array\[mid\]: 32 | * If it's `R`, then swap `array[low]` with `array[mid]` and increment `low` and `mid` 33 | * If it's `G`, then just increment `mid`; it's where it should be 34 | * If it's `B`, then swap `array[mid]` with `array[high]` and decrement `high` 35 | 36 | Once `mid` crosses over with `high`, then our unknown section is gone and we can terminate. 37 | 38 | Our solution looks like this: 39 | 40 | def partition(arr): 41 | low, mid, high = 0, 0, len(arr) - 1 42 | while mid <= high: 43 | if arr[mid] == 'R': 44 | arr[low], arr[mid] = arr[mid], arr[low] 45 | low += 1 46 | mid += 1 47 | elif arr[mid] == 'G': 48 | mid += 1 49 | else: 50 | arr[mid], arr[high] = arr[high], arr[mid] 51 | high -= 1 52 | 53 | 54 | P.S. This problem is also called the [Dutch national flag problem](https://en.wikipedia.org/wiki/Dutch_national_flag_problem)! 55 | -------------------------------------------------------------------------------- /036 [Medium]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #36 2 | 3 | This problem was asked by Dropbox. 4 | 5 | Given the root to a binary search tree, find the second largest node in the tree. 6 | -------------------------------------------------------------------------------- /036 [Medium]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | An in-order traversal of the binary search tree would give us all the nodes of the tree in sorted order. So the naive solution here might be do an in-order traversal of the tree, store it in an array, and return the second-to-last element in the array. 4 | 5 | This takes O(N) time and space since we have to go through and store every node in the tree. 6 | 7 | We can do better. Notice that the in-order traversal explores always the left node first before the current node. We could do something similar to that by exploring the right node first. 8 | 9 | Let's do a reverse in-order traversal, where we first call ourselves recursively on the right node. Because it's reversed, that should give us the binary tree in reverse sorted order. 10 | 11 | So we can keep a counter, and once we start processing the current node we can increment the counter. Once it hits 2, that means the current node we're looking at is the second largest, so we can stuff it in a variable and eventually return that. 12 | 13 | def second_largest(root): 14 | def inorder(node): 15 | if not node or count[0] == 2: 16 | return 17 | 18 | if node.right: 19 | inorder(node.right) 20 | 21 | count[0] += 1 22 | if count[0] == 2: 23 | val.append(node.val) 24 | return 25 | 26 | if node.left: 27 | inorder(node.left) 28 | 29 | count = [0] 30 | val = [] 31 | inorder(root) 32 | return val[0] 33 | 34 | 35 | Unfortunately because of Python's [demented scoping rules](https://stackoverflow.com/questions/4851463/python-closure-write-to-variable-in-parent-scope), we have to wrap `count` and `val` in a list. Ugly! -------------------------------------------------------------------------------- /037 [Easy]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #37 2 | 3 | This problem was asked by Google. 4 | 5 | The power set of a set is the set of all its subsets. Write a function that, given a set, generates its power set. 6 | 7 | For example, given the set `{1, 2, 3}`, it should return `{{}, {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}}`. 8 | 9 | You may also use a list or array to represent a set. -------------------------------------------------------------------------------- /037 [Easy]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | 4 | To gain some intuition about this problem, let's try some examples: 5 | 6 | * If we're given the empty set (`{}`), then the power set is a set with only the empty set in it: `{{}}` 7 | * If we're given a set with one element in it (`{a}`), then the power set is a set with two sets: an empty set and a set with the element in it: `{{}, {a}}` 8 | * If we're given a set with two elements in it (`{a, b}`), then the power is has four sets: `{{}, {a}, {b}, {a, b}}` 9 | 10 | What's the pattern? 11 | 12 | Notice that going from the empty set to `{a}`, that we still keep the empty set in our result and have another set with `a` in it. Similarly, when going from one element to two, we keep the same result set with one element (`{}, {a}`), but we also have a duplicate set with the `b` in it (`{b}, {a ,b}`). 13 | 14 | So we can use the following recursive formula to generate the power set: 15 | 16 | * If the input set is empty, return a set with an empty set in it 17 | * Otherwise, take an element from our set. Let's call it `x`. 18 | * Generate the power set of our input set without x. Let's call it `result`, for lack of a better name. 19 | * Return the union of `name` with `name + x` 20 | 21 | def power_set(s): 22 | if not s: 23 | return [[]] 24 | result = power_set(s[1:]) 25 | return result + [subset + [s[0]] for subset in result] 26 | 27 | 28 | This runs in O(2^N) time and space, since that's how many subsets there are. 29 | -------------------------------------------------------------------------------- /038 [Hard]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #38 2 | 3 | 4 | This problem was asked by Microsoft. 5 | 6 | You have an N by N board. Write a function that, given N, returns the number of possible arrangements of the board where N queens can be placed on the board without threatening each other, i.e. no two queens share the same row, column, or diagonal. -------------------------------------------------------------------------------- /038 [Hard]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | 4 | If we were to attempt to solve this problem using brute force, we would quickly find out that it would be prohibitively expensive. Consider a typical 8 by 8 board: we have 64 spots to place 8 queens, so that's 64 choose 8 possible placements. In general, that's factorial in runtime! 5 | 6 | This problem is ripe for solving with backtracking. In backtracking, we can visualize the search space like a tree, and we would explore it depth-first. Each node would be a possible configuration. If the configuration contains eight queens and is valid, then we're done and we can add it to our count. Otherwise, we can try to place another queen somewhere on the board and search from there. If we encounter an invalid board, then we can just prune the entire subtree from our search -- there's no point in exploring a board that we know won't work. 7 | 8 | Notice we can pare down the search space by ensuring we only place queens in distinct rows, since we know that two queens can never occupy the same row. 9 | 10 | Now we can just represent the board as a one-dimensional array of max size N, where each value represents which column the queen is on. For example, one solution for N = 4 would just be \[1, 3, 0, 2\]. 11 | 12 | def n_queens(n, board=[]): 13 | if n == len(board): 14 | return 1 15 | 16 | count = 0 17 | for col in range(n): 18 | board.append(col) 19 | if is_valid(board): 20 | count += n_queens(n, board) 21 | board.pop() 22 | return count 23 | 24 | def is_valid(board): 25 | current_queen_row, current_queen_col = len(board) - 1, board[-1] 26 | # Iterate over all already-placed queens and check if any of them can attack 27 | # each other. 28 | for row, col in enumerate(board[:-1]): 29 | diff = abs(current_queen_col - col) 30 | if diff == 0 or diff == current_queen_row - row: 31 | return False 32 | return True 33 | 34 | 35 | If you're interested in optimizing this problem even further, check out [this paper](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.51.7113&rep=rep1&type=pdf) that uses constant space by representing all columns and diagonals simply with integers! However, this depends on n being smaller than the number of bits in your integer. 36 | -------------------------------------------------------------------------------- /039 [Medium]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #39 2 | 3 | 4 | This problem was asked by Dropbox. 5 | 6 | Conway's Game of Life takes place on an infinite two-dimensional board of square cells. Each cell is either dead or alive, and at each tick, the following rules apply: 7 | 8 | * Any live cell with less than two live neighbours dies. 9 | * Any live cell with two or three live neighbours remains living. 10 | * Any live cell with more than three live neighbours dies. 11 | * Any dead cell with exactly three live neighbours becomes a live cell. 12 | 13 | A cell neighbours another cell if it is horizontally, vertically, or diagonally adjacent. 14 | 15 | Implement Conway's Game of Life. It should be able to be initialized with a starting list of live cell coordinates and the number of steps it should run for. Once initialized, it should print out the board state at each step. Since it's an infinite board, print out only the relevant coordinates, i.e. from the top-leftmost live cell to bottom-rightmost live cell. 16 | 17 | You can represent a live cell with an asterisk (`*`) and a dead cell with a dot (`.`). 18 | -------------------------------------------------------------------------------- /039 [Medium]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | This is a straightforward implementation problem, so your solution may differ. Since our board is infinite, we can't create a matrix that represents our whole board. 4 | 5 | Instead, we'll represent each cell simply as a pair of cartesian coordinates (row, col). In this solution, we keep the set of cells as a property on our class. Each tick, we create a new set of cells that represents the next generation. We pretty much have to do this so that changing the board doesn't affect the future cells we process from the current generation. 6 | 7 | We look at each live cell, compute the number of neighbours for each one, and preserve it according to the rules. 8 | 9 | Similarly, we look at all the neighbouring cells of all the live cells, since any of them could potentially become alive due to rule #4. If any of them have exactly 3 neighbours, then we should add them to the set of new cells. 10 | 11 | For printing the board, we need to find the top-leftmost cell and the bottom-rightmost cell. These are our boundaries for the board. Then we can print out each row and cell one by one and checking if the current spot is in our set of cells. 12 | 13 | It's useful to create some helper functions here. In our case, we have: 14 | 15 | * `get_number_of_live_neighbours` 16 | * `get_neighbouring_cells` 17 | * `get_boundaries` 18 | 19 | class GameOfLife: 20 | def __init__(self, n, cells=set()): 21 | # Each cell will be a tuple (row, col) 22 | self.cells = cells 23 | for _ in range(n): 24 | self.print_board() 25 | self.next() 26 | 27 | def get_number_of_live_neighbours(self, row, col): 28 | count = 0 29 | for cell_row, cell_col in self.cells: 30 | if abs(cell_row - row) > 1: 31 | continue 32 | if abs(cell_col - col) > 1: 33 | continue 34 | if cell_row == row and cell_col == col: 35 | continue 36 | count += 1 37 | return count 38 | 39 | def get_neighbouring_cells(self, row, col): 40 | return set([ 41 | (row - 1, col - 1), 42 | (row, col - 1), 43 | (row + 1, col - 1), 44 | (row - 1, col), 45 | (row + 1, col), 46 | (row - 1, col + 1), 47 | (row, col + 1), 48 | (row + 1, col + 1), 49 | ]) 50 | 51 | def next(self): 52 | new_cells = set() 53 | # Go through each cell, look for neighbours, decide whether to append to new list 54 | for row, col in self.cells: 55 | num_of_neighbours = self.get_number_of_live_neighbours(row, col) 56 | if 2 <= num_of_neighbours <= 3: 57 | new_cells.add((row, col)) 58 | 59 | potential_live_cells = set() 60 | for row, col in self.cells: 61 | potential_live_cells = potential_live_cells.union(self.get_neighbouring_cells(row, col)) 62 | potential_live_cells = potential_live_cells - self.cells 63 | 64 | # Go through each potential live cell, get the number of neighbours, and add if = 3 65 | for row, col in potential_live_cells: 66 | num_of_neighbours = self.get_number_of_live_neighbours(row, col) 67 | if num_of_neighbours == 3: 68 | new_cells.add((row, col)) 69 | 70 | self.cells = new_cells 71 | 72 | def get_boundaries(self): 73 | top = min(self.cells, key=lambda cell: cell[0])[0] 74 | left = min(self.cells, key=lambda cell: cell[1])[1] 75 | bottom = max(self.cells, key=lambda cell: cell[0])[0] 76 | right = max(self.cells, key=lambda cell: cell[1])[1] 77 | return top, left, bottom, right 78 | 79 | def print_board(self): 80 | top, left, bottom, right = self.get_boundaries() 81 | print('--------------------------------------') 82 | for i in range(top, bottom + 1): 83 | for j in range(left, right + 1): 84 | if (i, j) in self.cells: 85 | print('*', end='') 86 | else: 87 | print('.', end='') 88 | print('') 89 | print('--------------------------------------') 90 | -------------------------------------------------------------------------------- /040 [Hard]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #40 2 | 3 | 4 | This problem was asked by Google. 5 | 6 | Given an array of integers where every integer occurs three times except for one integer, which only occurs once, find and return the non-duplicated integer. 7 | 8 | For example, given \[6, 1, 3, 3, 3, 6, 6\], return 1. Given \[13, 19, 13, 13\], return 19. 9 | 10 | Do this in O(N) time and O(1) space. -------------------------------------------------------------------------------- /040 [Hard]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | 4 | We can find the unique number in an array of _two_ duplicates by XORing all the numbers in the array. What this does is cancel out all the bits that have an even number of 1s, leaving only the unique (odd) bits out. 5 | 6 | Let's try to extend this technique to three duplicates. Instead of cancelling out all the bits with an even number of bits, we want to cancel those out that have a number of bits that are multiple of three. 7 | 8 | Let's assume all integers fit in 32 bits. Then let's create an array 32 zeroes long, and when iterating over each number in our array, we can add up all the bits to its proper spot in the array. Finally, we'll go over each bit in the array and make it equal to itself modulo 3. This means that any bit that has been set some multiple of 3 times will effectively be cleared, leaving only the bit from the unique number. 9 | 10 | def find_unique(arr): 11 | result_arr = [0] * 32 12 | for num in arr: 13 | for i in range(32): 14 | bit = num >> i & 1 15 | result_arr[i] = (result_arr[i] + bit) % 3 16 | 17 | result = 0 18 | for i, bit in enumerate(result_arr): 19 | if bit: 20 | result += 2 ** i 21 | 22 | return result 23 | 24 | 25 | This runs in linear time, since we iterate over the array once, and in constant space, since we initialize an array of constant size. 26 | -------------------------------------------------------------------------------- /041 [Medium]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #41 2 | 3 | 4 | 5 | This problem was asked by Facebook. 6 | 7 | Given an unordered list of flights taken by someone, each represented as (origin, destination) pairs, and a starting airport, compute the person's itinerary. If no such itinerary exists, return null. If there are multiple possible itineraries, return the lexicographically smallest one. All flights must be used in the itinerary. 8 | 9 | For example, given the list of flights \[('SFO', 'HKO'), ('YYZ', 'SFO'), ('YUL', 'YYZ'), ('HKO', 'ORD')\] and starting airport 'YUL', you should return the list \['YUL', 'YYZ', 'SFO', 'HKO', 'ORD'\]. 10 | 11 | Given the list of flights \[('SFO', 'COM'), ('COM', 'YYZ')\] and starting airport 'COM', you should return null. 12 | 13 | Given the list of flights \[('A', 'B'), ('A', 'C'), ('B', 'C'), ('C', 'A')\] and starting airport 'A', you should return the list \['A', 'B', 'C', 'A', 'C'\] even though \['A', 'C', 'A', 'B', 'C'\] is also a valid itinerary. However, the first one is lexicographically smaller. 14 | -------------------------------------------------------------------------------- /041 [Medium]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | This problem is similar to the N queens problem a few days ago: we have a desired final state (all the flights are used up), we can construct partial itineraries and reject them, and at each step we have potentially multiple avenues to explore. That suggests that backtracking is again a very likely candidate for solving our problem. 4 | 5 | In particular, we can do the following: 6 | 7 | * Keep a list of itinerary candidates 8 | * Keep a current itinerary initialized with our starting airport 9 | * Then, recursively: 10 | * Iterate over all the flights that start from the last airport in our itinerary 11 | * For each flight, temporarily add the destination to our itinerary and remove it from the flight list. Then call ourselves recursively with the new itinerary and flight list. 12 | * Concatenate all the results to our list of itinerary candidates. 13 | * Sort our itinerary candidates and pick the lexicographically smallest one. 14 | 15 | To speed this up, we'll store all the flights into a dictionary with the origin as a key and a list of flight destinations from that origin as the value. Then we can look up our options in O(1) time instead of O(N) time. 16 | 17 | from collections import defaultdict 18 | 19 | def get_itinerary(flights, start): 20 | # Store all the flights into a dictionary key:origin -> val:list of destinations 21 | flight_map = defaultdict(list) 22 | for origin, destination in flights: 23 | flight_map[origin] += [destination] 24 | 25 | def visit(flight_map, total_flights, current_itinerary): 26 | # If our itinerary uses up all the flights, we're done here. 27 | if len(current_itinerary) == total_flights + 1: 28 | return [current_itinerary[:]] 29 | 30 | last_stop = current_itinerary[-1] 31 | # If we haven't used all the flights yet but we have no way 32 | # of getting out of this airport, then we're stuck. Backtrack out. 33 | if not flight_map[last_stop]: 34 | return [] 35 | 36 | # Otherwise, let's try all the options out of the current stop recursively. 37 | # We temporarily take them out of the mapping once we use them. 38 | potential_itineraries = [] 39 | for i, flight in enumerate(flight_map[last_stop]): 40 | flight_map[last_stop].pop(i) 41 | current_itinerary.append(flight) 42 | potential_itineraries.extend(visit(flight_map, total_flights, current_itinerary)) 43 | flight_map[last_stop].insert(i, flight) 44 | current_itinerary.pop() 45 | return potential_itineraries 46 | 47 | valid_itineraries = visit(flight_map, len(flights), [start]) 48 | if valid_itineraries: 49 | return sorted(valid_itineraries)[0] 50 | -------------------------------------------------------------------------------- /042 [Hard]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #42 2 | 3 | 4 | This problem was asked by Google. 5 | 6 | Given a list of integers S and a target number k, write a function that returns a subset of S that adds up to k. If such a subset cannot be made, then return null. 7 | 8 | Integers can appear more than once in the list. You may assume all numbers in the list are positive. 9 | 10 | For example, given S = \[12, 1, 61, 5, 9, 2\] and k = 24, return \[12, 9, 2, 1\] since it sums up to 24. 11 | -------------------------------------------------------------------------------- /042 [Hard]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | Let's consider the brute force method: selecting all subsets, summing them, and checking if they equal k. That would take O(2^N \* N) time, since generating all subsets takes O(2^N) and we need to sum everything in the subset. 4 | 5 | We can do a little better by implicitly computing the sum. That is, for each call, we can basically choose whether to pick some element (let's say the last) in our set and recursively looking for `k - last` in the remaining part of the list, or exclude the last element and keep on looking for `k` in the remaining part of the list recursively. 6 | 7 | def subset_sum(nums, k): 8 | if k == 0: 9 | return [] 10 | if not nums and k != 0: 11 | return None 12 | 13 | nums_copy = nums[:] 14 | last = nums_copy.pop() 15 | 16 | with_last = subset_sum(nums_copy, k - last) 17 | without_last = subset_sum(nums_copy, k) 18 | if with_last is not None: 19 | return with_last + [last] 20 | if without_last is not None: 21 | return without_last 22 | 23 | 24 | This will run in O(2^N) theoretically, but practically, since we copy the whole array on each call, it's still O(2^N \* N), which is worse than exponential. 25 | 26 | Let's try to improve the running time by using dynamic programming. We have the recursive formula nailed down. How can we use bottom-up dynamic programming to improve the runtime? 27 | 28 | We can construct a table `A` that's size `len(nums) + 1` by `k + 1`. At each index `A[i][j]`, we'll keep a subset of the list from `0..i` (including lower, excluding upper bound) that can add up to `j`, or null if no list can be made. Then we will fill up the table using pre-computed values and once we're done, we should be able to just return the value at `A[-1][-1]`. Let's first initialize the list: 29 | 30 | A = [[None for _ in range(k + 1)] for _ in range(len(nums) + 1)] 31 | 32 | 33 | To begin, we can initialize each element of the first row (`A[i][0] for i in range(len(nums) + 1)`) with the empty list, since any subset of the list can make 0: just don't pick anything! 34 | 35 | for i in range(len(nums) + 1): 36 | A[i][0] = [] 37 | 38 | 39 | Each element of the first column (`A[0][j]` for j in range(1, len(nums))\`) starting from the first row should be null, since we can't make anything other than 0 with the empty set. Since we've initialized our whole table to be null, then we don't need to do anything here. 40 | 41 | [], [], [], [], [], ... 42 | None, None, None, ... 43 | None, None, None, ... 44 | None, None, None, ... 45 | ... 46 | 47 | 48 | Now we can start populating the table. Iterating over each row starting at 1, and then each column starting at 1, we can use the following formula to compute `A[i][j]`: 49 | 50 | * First, let's consider the last element of the list we're looking at: `nums[i - 1]`. Let's call this `last`. 51 | 52 | * If `last` is greater than `j`, then we definitely can't make `j` with `nums[:i]` including `last` (since it would obviously go over). So let's just copy over whatever we had from `A[i - 1][j]`. If we can make `j` without `last`, then we can still make `j`. If we can't, then we still can't. 53 | 54 | * If `last` smaller than or equal to `j`, then we still might be able to make `j` using `last` 55 | * If we can make `j` without `last` by looking up the value at `A[i - 1][j]` and if it's not null, then use that. 56 | * Else, if we can't make `j` without `last`, check if we can make it _with_ `last` by looking up the value at `A[i - 1][j - last]`. If we can, then copy over the list from there and append the last element to it. 57 | * Else, we can't make it with or without `j`, so set `A[i][j]` to null. 58 | 59 | for i in range(1, len(nums) + 1): 60 | for j in range(1, k + 1): 61 | last = nums[i - 1] 62 | if last > j: 63 | A[i][j] = A[i - 1][j] 64 | else: 65 | if A[i - 1][j] is not None: 66 | A[i][j] = A[i - 1][j] 67 | elif A[i - 1][j - last] is not None: 68 | A[i][j] = A[i - 1][j - last] + [last] 69 | else: 70 | A[i][j] = None 71 | 72 | 73 | Putting it all together: 74 | 75 | def subset_sum(nums, k): 76 | A = [[None for _ in range(k + 1)] for _ in range(len(nums) + 1)] 77 | 78 | for i in range(len(nums) + 1): 79 | A[i][0] = [] 80 | 81 | for i in range(1, len(nums) + 1): 82 | for j in range(1, k + 1): 83 | last = nums[i - 1] 84 | if last > j: 85 | A[i][j] = A[i - 1][j] 86 | else: 87 | if A[i - 1][j] is not None: 88 | A[i][j] = A[i - 1][j] 89 | elif A[i - 1][j - last] is not None: 90 | A[i][j] = A[i - 1][j - last] + [last] 91 | else: 92 | A[i][j] = None 93 | 94 | return A[-1][-1] 95 | 96 | 97 | This runs in O(k \* N) time and space. 98 | -------------------------------------------------------------------------------- /043 [Easy]/Readme.md: -------------------------------------------------------------------------------- 1 | ##Daily Coding Problem #43 2 | 3 | This problem was asked by Amazon. 4 | 5 | Implement a stack that has the following methods: 6 | 7 | * push(val), which pushes an element onto the stack 8 | * pop(), which pops off and returns the topmost element of the stack. If there are no elements in the stack, then it should throw an error or return null. 9 | * max(), which returns the maximum value in the stack currently. If there are no elements in the stack, then it should throw an error or return null. 10 | 11 | Each method should run in constant time. 12 | -------------------------------------------------------------------------------- /043 [Easy]/Solution.md: -------------------------------------------------------------------------------- 1 | **Solution** 2 | 3 | Implementing the stack part (push and pop) of this problem is easy -- we can just use a typical list to implement the stack with `append` and `pop`. However, getting the max in constant time is a little trickier. We could obviously do it in linear time if we popped off everything on the stack while keeping track of the maximum value, and then put everything back on. 4 | 5 | We can use a secondary stack that _only_ keeps track of the max values at any time. It will be in sync with our primary stack, as in it will have the exact same number of elements as our primary stack at any point in time, but the top of the stack will always contain the maximum value of the stack. 6 | 7 | We can then, when pushing, check if the element we're pushing is greater than the max value of the secondary stack (by just looking at the top), and if it is, then push that instead. If not, then maintain the previous value. 8 | 9 | class MaxStack: 10 | def __init__(self): 11 | self.stack = [] 12 | self.maxes = [] 13 | 14 | def push(self, val): 15 | self.stack.append(val) 16 | if self.maxes: 17 | self.maxes.append(max(val, self.maxes[-1])) 18 | else: 19 | self.maxes.append(val) 20 | 21 | def pop(self): 22 | if self.maxes: 23 | self.maxes.pop() 24 | return self.stack.pop() 25 | 26 | def max(self): 27 | return self.maxes[-1] 28 | 29 | 30 | Everything should run in O(1) time. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Challenging Programming Problems and Solutions 2 | 3 | Get exceptionally good at coding interviews by solving a problem every day. 4 | 5 | ### What is Daily Coding Problem? 6 | 7 | Daily Coding Problem is a simple and very useful site that emails you a coding problem every morning. This ensures that you practice consistently and often enough to stay in shape over a long period of time. 8 | 9 | Practicing just one problem a day is enough to make a huge impact on your skill-set and confidence, especially when faced with a task that you might be facing in a top-tier tech company in the near future. 10 | 11 | Daily Coding Problem contains a wide variety of questions inspired by real programming interviews, with in-depth solutions that clearly take you through each core concept. You'll learn about: 12 | 13 | - Arrays 14 | - Strings 15 | - Linked Lists 16 | - Trees 17 | - Hash Tables 18 | - Binary Search Trees 19 | - Tries 20 | - Heaps 21 | - Stacks and Queues 22 | - Graphs 23 | - Randomized Algorithms 24 | - Dynamic Programming 25 | - Backtracking 26 | - Bit Manipulation 27 | - Pathfinding 28 | - Recursion 29 | - Data Structure Design 30 | - System Design 31 | 32 | ### Which Programming Languages? 33 | 34 | Currently, DCP provides solutions in Python as it is very well-known and similar to pseudo-code, as well as being simple to learn. This seems to be a good choice for solutions since Python code is fairly easy to translate to other languages, given its simple syntax and straight-forward programming style. 35 | --------------------------------------------------------------------------------