Skip to content

Commit d632995

Browse files
committed
working on general recursion problems
1 parent dfae587 commit d632995

File tree

1 file changed

+348
-0
lines changed

1 file changed

+348
-0
lines changed
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Technique - Recursion (General)\n",
8+
"\n",
9+
"Recursion refers to self-referential algorithms. Any algorithm that can be solved iteratively can be solved recursively and vice versa (though sometimes the iterative implementation requires a stack, as recursion implicitly invokes the call stack). Some problems are easier to implement with one or the other; go with whatever feels most natural. \n",
10+
"\n",
11+
"When considering a recursive solution, you must identify:\n",
12+
"- A recurrence relationship in the problem. It helps to write this out explicitly.\n",
13+
"- A base case; this is a case where no self-reference is needed to provide a result.\n",
14+
"- A recursive case; the cases where self reference is required. \n",
15+
"\n",
16+
"With this, you can then begin implementing a recursive function. Here's a simple template:\n",
17+
"```\n",
18+
"def recurse(value):\n",
19+
" if base case:\n",
20+
" return base value\n",
21+
" else:\n",
22+
" return recurse(next value) + recurse(other next value)\n",
23+
"\n",
24+
"def handler(input):\n",
25+
" return recurse(first value)\n",
26+
"```\n",
27+
"\n",
28+
"If your language allows it, you can implement the recursive function inside the handler function. I find that this is helpful if the recursive function needs to access a global data structure, but it can complicate testing:\n",
29+
"```\n",
30+
"def handler(input):\n",
31+
" def recurse(value):\n",
32+
" if base case:\n",
33+
" return base value\n",
34+
" else:\n",
35+
" return recurse(next value) + recurse(other next value)\n",
36+
"return recurse(first value)\n",
37+
"```\n",
38+
"\n",
39+
"Some common gotchas:\n",
40+
"- Not identifying the recurrence relationship completely before implementing the recursive function will cause confusion and possibly lead you to miss edge cases. \n",
41+
"- Every recursive call must eventually hit the base case; otherwise you will recurse forever. \n",
42+
"- A recursive function usually should return a concrete value in the base case or a combination of return values for recursive cases. A common mistake is to forget to return a recursive call's return value."
43+
]
44+
},
45+
{
46+
"cell_type": "markdown",
47+
"metadata": {},
48+
"source": [
49+
"## [Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/)\n",
50+
"\n",
51+
"Recurrence: To get all strings for the `i`th number in the input string, combine all of the `i`th numbers digits with all strings associated with the `i-1`th number. \n",
52+
"\n",
53+
"Suppose we have the following functions:\n",
54+
"- `get_chars(i)` - returns characters associated with digit, e.g. `get_chars(2) -> ['a','b','c']`\n",
55+
"- `combine(c, strs)` - given a char `c` and list of strings `strs`, returns a list containing every string in `strs` with `c` appended. \n",
56+
"- `get_combinations(s, i)` - a function that solves the problem, i.e. returns all letter combinations for an int string `s` of length `i`.\n",
57+
"\n",
58+
"Base case: A single number `i`; return `get_chars(i)`\n",
59+
"Recursive case: the `i`th number of our input string. `get_combinations(s, i) = combine(get_combinations(s, i-1), c) for c in get_chars(s[i])`\n",
60+
"\n",
61+
"I actually got this as an interview problem once (during a period of my life when I was incompetent at interviewing) and promptly failed it after attempting a solution on the whiteboard with multiple layers of nested loops. Despite the experience leaving me a scarred and broken man (most medical experts agree that whiteboards are a worldwide leading cause of early death), I somehow got an offer and the interviewer later became my manager."
62+
]
63+
},
64+
{
65+
"cell_type": "code",
66+
"execution_count": 14,
67+
"metadata": {},
68+
"outputs": [
69+
{
70+
"name": "stdout",
71+
"output_type": "stream",
72+
"text": [
73+
"['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf']\n",
74+
"[]\n",
75+
"['a', 'b', 'c']\n"
76+
]
77+
}
78+
],
79+
"source": [
80+
"PHONE_PAD = {\n",
81+
" '2': [\"a\",\"b\", \"c\"],\n",
82+
" '3': [\"d\",\"e\",\"f\"],\n",
83+
" '4': [\"g\",\"h\",\"i\"],\n",
84+
" '5': [\"j\",\"k\",\"l\"],\n",
85+
" '6': [\"m\",\"n\",\"o\"],\n",
86+
" '7': [\"p\",\"q\",\"r\",\"s\"],\n",
87+
" '8': [\"t\",\"u\",\"v\"],\n",
88+
" '9': [\"w\",\"x\",\"y\",\"z\"],\n",
89+
"}\n",
90+
"\n",
91+
"def combine(c, strs):\n",
92+
" return [c+string for string in strs] if strs else [c]\n",
93+
"\n",
94+
"def get_combinations(nums, i=0):\n",
95+
" if i >= len(nums):\n",
96+
" return []\n",
97+
" solution = []\n",
98+
" for c in PHONE_PAD[nums[i]]:\n",
99+
" solution += combine(c, get_combinations(nums, i+1))\n",
100+
" return solution\n",
101+
"\n",
102+
"print(get_combinations(\"23\"))\n",
103+
"print(get_combinations(\"\"))\n",
104+
"print(get_combinations(\"2\"))"
105+
]
106+
},
107+
{
108+
"cell_type": "markdown",
109+
"metadata": {},
110+
"source": [
111+
"## [Validate binary search tree](https://leetcode.com/problems/validate-binary-search-tree/)"
112+
]
113+
},
114+
{
115+
"cell_type": "markdown",
116+
"metadata": {},
117+
"source": [
118+
"For a leaf node `n` and internal node `m`, `m` is a right parent if `n` is in `m`'s right subtree, and a `left` parent if the opposite. In the tree below, `-2`'s `left ancestors` are `4`, `2`, and `0`, its only right ancestor is `-5`:\n",
119+
"```\n",
120+
" 4\n",
121+
" / \\\n",
122+
" 2 6\n",
123+
" / / \\\n",
124+
" -5 5 7\n",
125+
" \\\n",
126+
" 0\n",
127+
" /\n",
128+
" -2 \n",
129+
"```\n",
130+
"Recurrence: A node is valid if the BST property holds for it and its children, and if `max(right ancestors) < node.val < min(left ancestors)`.\n",
131+
"Base case: A root node; check if the BST property holds for the children and the node.\n",
132+
"Recursive case: Check the BST property, `max(right ancestors) < node.val < min(left ancestors)`\n",
133+
"\n",
134+
"An alternative approach is to do an in-order traversal and confirm that the node order is sorted, which is also implemented below."
135+
]
136+
},
137+
{
138+
"cell_type": "code",
139+
"execution_count": 19,
140+
"metadata": {},
141+
"outputs": [],
142+
"source": [
143+
"class TreeNode:\n",
144+
" def __init__(self, val=0, left=None, right=None):\n",
145+
" self.val = val\n",
146+
" self.left = left\n",
147+
" self.right = right\n",
148+
"\n",
149+
"class Solution:\n",
150+
" def isValidBST(self, node: TreeNode, r_ancestor=None, l_ancestor=None) -> bool:\n",
151+
" return not node or \\\n",
152+
" (not node.left or node.left.val < node.val) and \\\n",
153+
" (not node.right or node.val < node.right.val) and \\\n",
154+
" (not r_ancestor or r_ancestor < node.val) and \\\n",
155+
" (not l_ancestor or node.val < l_ancestor) and \\\n",
156+
" self.isValidBST(node.left, r_ancestor, node.val) and \\\n",
157+
" self.isValidBST(node.right, node.val, l_ancestor)\n",
158+
"\n",
159+
"class Solution:\n",
160+
" def isValidBST(self, node: TreeNode) -> bool:\n",
161+
" self.prev = -float('inf')\n",
162+
" def in_order(node):\n",
163+
" if not node: return True\n",
164+
" valid = in_order(node.left)\n",
165+
" valid &= self.prev < node.val\n",
166+
" self.prev = node.val\n",
167+
" valid &= in_order(node.right)\n",
168+
" return valid\n",
169+
" return in_order(node)\n",
170+
" "
171+
]
172+
},
173+
{
174+
"cell_type": "markdown",
175+
"metadata": {},
176+
"source": [
177+
"## [Split BST](https://leetcode.com/problems/split-bst/)\n",
178+
"\n",
179+
"An `O(N)` solution is possible and poorly explained on Leetcode. For a given tree T, we return `left` (every node in T where node.val <= V) and `right` (every node in T where node.val > V). Our base case is a single node - it either winds up in a left or right tree of one node, and the other tree is empty. For more than one node, we look at `root`. If `root` goes into `left`, we don't need to look at `root.left` since every value in the root's left subtree is also less than V, so we go on to look at `root.right`. Opposite logic applies if `root` goes into `right`; we go on to look at `root.left`. \n",
180+
"\n",
181+
"Let's say `V= 57` and we have this tree:\n",
182+
"```\n",
183+
" 0\n",
184+
" / \\ \n",
185+
"-100 100\n",
186+
" / \\ / \\\n",
187+
" ....\n",
188+
"```\n",
189+
"We don't need to examine `root.left` since every value in it is less than 0, so we recurse into `root.right`. We then have three trees: the original `root` at `0`, and the `left` and `right` on our recursive call `split(root.right)`. In this case, we could merge them by setting `root.right = left`; every value in `root`'s original right subtree was greater than `root`, so this preserves the BST property. Then the recursive call's right is `right` and the original root is `left`, and we return both. Again, opposite logic applies if we examine `root.left` in the first case. I won't bother stating the recurrence here since it's more confusing than it's worth."
190+
]
191+
},
192+
{
193+
"cell_type": "code",
194+
"execution_count": 61,
195+
"metadata": {},
196+
"outputs": [],
197+
"source": [
198+
"class Solution: \n",
199+
" def splitBST(self, root: TreeNode, V: int) -> List[TreeNode]:\n",
200+
" if not root: return [None, None]\n",
201+
" if V >= root.val:\n",
202+
" left = root\n",
203+
" sub_left, right = self.splitBST(root.right, V)\n",
204+
" left.right = sub_left\n",
205+
" else:\n",
206+
" right = root\n",
207+
" left, sub_right = self.splitBST(root.left, V)\n",
208+
" right.left = sub_right\n",
209+
" return [left, right]"
210+
]
211+
},
212+
{
213+
"cell_type": "markdown",
214+
"metadata": {},
215+
"source": [
216+
"An alternative, brute-force `O(nlogn)` solution is to pre-order traverse the input tree and insert every node into the new left or right subtree. "
217+
]
218+
},
219+
{
220+
"cell_type": "code",
221+
"execution_count": 55,
222+
"metadata": {},
223+
"outputs": [],
224+
"source": [
225+
"class TreeNode:\n",
226+
" def __init__(self, val=0, left=None, right=None):\n",
227+
" self.val = val\n",
228+
" self.left = left\n",
229+
" self.right = right\n",
230+
"\n",
231+
"def insert(root, new_node):\n",
232+
" if not root:\n",
233+
" return new_node\n",
234+
" if new_node.val <= root.val:\n",
235+
" root.left = insert(root.left, new_node) \n",
236+
" else:\n",
237+
" root.right = insert(root.right, new_node)\n",
238+
" return root\n",
239+
" \n",
240+
"class Solution: \n",
241+
" def splitBST(self, root: TreeNode, V: int) -> List[TreeNode]:\n",
242+
" self.ans = [None, None]\n",
243+
"\n",
244+
" def preorder(root, V):\n",
245+
" if not root: return\n",
246+
" new_node = TreeNode(root.val)\n",
247+
" if root.val <= V: \n",
248+
" self.ans[0] = insert(self.ans[0], new_node)\n",
249+
" else:\n",
250+
" self.ans[1] = insert(self.ans[1], new_node)\n",
251+
" preorder(root.left, V)\n",
252+
" preorder(root.right, V)\n",
253+
" \n",
254+
" preorder(root, V)\n",
255+
" return self.ans"
256+
]
257+
},
258+
{
259+
"cell_type": "markdown",
260+
"metadata": {},
261+
"source": [
262+
"## [Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/)\n",
263+
"\n",
264+
"Can use an iterative mergesort merge here easily. How can we do this recursively?"
265+
]
266+
},
267+
{
268+
"cell_type": "code",
269+
"execution_count": 28,
270+
"metadata": {},
271+
"outputs": [],
272+
"source": [
273+
"class ListNode:\n",
274+
" def __init__(self, val, next):\n",
275+
" self.val = val\n",
276+
" self.next = next\n",
277+
"\n",
278+
"class Solution:\n",
279+
" def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:\n",
280+
" if not (l1 and l2):\n",
281+
" return l1 if not l2 else l2\n",
282+
" if l1.val <= l2.val: \n",
283+
" next_node = l1\n",
284+
" l1 = l1.next\n",
285+
" else:\n",
286+
" next_node = l2\n",
287+
" l2 = l2.next\n",
288+
" next_node.next = self.mergeTwoLists(l1,l2)\n",
289+
" return next_node"
290+
]
291+
},
292+
{
293+
"cell_type": "markdown",
294+
"metadata": {},
295+
"source": [
296+
"## [Swap Nodes in Pairs](https://leetcode.com/problems/swap-nodes-in-pairs/)\n",
297+
"\n",
298+
"For this one, our recurrence is: `swapped list of N nodes = 2nd node + 1st node + swapped list of N-2 nodes`, with base cases for zero and one node."
299+
]
300+
},
301+
{
302+
"cell_type": "code",
303+
"execution_count": null,
304+
"metadata": {},
305+
"outputs": [],
306+
"source": [
307+
"class Solution:\n",
308+
" def swapPairs(self, head: ListNode) -> ListNode:\n",
309+
" if not head:\n",
310+
" return None\n",
311+
" second = head.next\n",
312+
" if not second:\n",
313+
" return head\n",
314+
" head.next = self.swapPairs(second.next)\n",
315+
" second.next = head\n",
316+
" return second"
317+
]
318+
},
319+
{
320+
"cell_type": "markdown",
321+
"metadata": {},
322+
"source": [
323+
"## [All Possible Full Binary Trees](https://leetcode.com/problems/all-possible-full-binary-trees/)"
324+
]
325+
}
326+
],
327+
"metadata": {
328+
"kernelspec": {
329+
"display_name": "Python 3",
330+
"language": "python",
331+
"name": "python3"
332+
},
333+
"language_info": {
334+
"codemirror_mode": {
335+
"name": "ipython",
336+
"version": 3
337+
},
338+
"file_extension": ".py",
339+
"mimetype": "text/x-python",
340+
"name": "python",
341+
"nbconvert_exporter": "python",
342+
"pygments_lexer": "ipython3",
343+
"version": "3.8.5"
344+
}
345+
},
346+
"nbformat": 4,
347+
"nbformat_minor": 4
348+
}

0 commit comments

Comments
 (0)