Skip to content

Commit 03beeb0

Browse files
committed
intermediary commit for new notebooks
1 parent 9051a5d commit 03beeb0

File tree

4 files changed

+476
-0
lines changed

4 files changed

+476
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Technique - Recursion (Backtracking)\n",
8+
"\n",
9+
"Backtracking is a recursive technique that can be used for exhaustive searching."
10+
]
11+
},
12+
{
13+
"cell_type": "markdown",
14+
"metadata": {},
15+
"source": [
16+
"## Example [Restore IP Addresses](https://leetcode.com/problems/restore-ip-addresses/submissions/)\n",
17+
"\n",
18+
"This problem is an exhaustive search - we need to find all possible IP addresses from a given string of ints. We can solve it with backtracking, or with straightforward recursion.\n",
19+
"\n",
20+
"### Identifying a recurrence\n",
21+
"A valid IPv4 address is of the form `x.x.x.x` where `0 <= x 255`, and x has no leading `0` (unless `x == 0`). Let's say that we have three types of IPs:\n",
22+
"- A valid IP address, e.g. `24.123.54.4`\n",
23+
"- An invalid IP address:\n",
24+
" - `525.1.2.4` (field exceeds 255)\n",
25+
" - `01.1.24.5` (field has leading zero)\n",
26+
" - `1..3.4` (empty field)\n",
27+
" - `1.2.3.4.5` (too many fields)\n",
28+
"- An incomplete IP address, e.g. an IP that is only invalid because it has too few fields:\n",
29+
" - `1.2.3` \n",
30+
" - `1.2`\n",
31+
" - `1.`\n",
32+
" - `(empty string)`\n",
33+
"\n",
34+
"For any IP address `s` and a character `x` s.t. `0 <= x <= 9`, we can either try adding `x` as the final character of s, or appending `x` to the end of `s` as a new field:\n",
35+
"- If `s` is valid:\n",
36+
" - We cannot append `x` as a new field, e.g. if `s = 1.2.3.4`, `1.2.3.4.x` is not valid. \n",
37+
" - We can possibly appending `x` as a final character: `1.2.3.4x` is valid.\n",
38+
"- If `s` is invalid, there is no way to append `x` to produce a valid or incomplete IP. \n",
39+
"- If `s` is incomplete:\n",
40+
" - Appending `x` as a field produces either a valid IP (`s = 1.2.3, x = 4, s.x = 1.2.3.4`) or another incomplete IP (`s = 1.2, x = 3, s.x = 1.2.3`)\n",
41+
" - Appending `x` as a final character produces an incomplete IP (`s = 1.2.1, x = 4, sx = 1.2.14`) or an invalid IP (`s = 1.2.99, x = 1, sx = 1.2.991`). \n",
42+
" \n",
43+
"So given an input `string` of int characters, our recurrence relationship is:\n",
44+
"```\n",
45+
"all valid IPs ending at string[i] =\n",
46+
" any valid result of appending s[i] as a character to all valid IPs ending at string[i-1], \n",
47+
" any valid result of appending s[i] as a character to all incomplete IPs ending at string[i-1],\n",
48+
" any valid result of appending s[i] as a field to all incomplete IPs ending at string[i-1]\n",
49+
"```\n",
50+
"\n",
51+
"## Base case and recursive case\n",
52+
"Because the problem says to use all characters in `string`, our base case will be: `current IP address is invalid, or current IP address is valid and i == len(string)`. The recursive case is `current IP is incomplete or i < len(string)`, and tries `append string[i] as character, go to i+1` and `append string[i] as a field, go to i+1)`."
53+
]
54+
},
55+
{
56+
"cell_type": "code",
57+
"execution_count": 24,
58+
"metadata": {},
59+
"outputs": [],
60+
"source": [
61+
"from typing import List\n",
62+
"\n",
63+
"def ip_invalid(ip_list):\n",
64+
" if not ip_list:\n",
65+
" return False\n",
66+
" return (len(ip_list) > 4) or \\\n",
67+
" not (0 <= int(ip_list[-1]) <= 255) or \\\n",
68+
" (ip_list[-1][0] == '0' and len(ip_list[-1]) > 1)\n",
69+
" \n",
70+
"class Solution:\n",
71+
" def restoreIpAddresses(self, s: str) -> List[str]:\n",
72+
" valid_addrs = []\n",
73+
" \n",
74+
" def find_valid(i, curr):\n",
75+
" if ip_invalid(curr):\n",
76+
" return\n",
77+
" if i >= len(s):\n",
78+
" valid_addrs.append('.'.join(curr)) if len(curr) == 4 else None\n",
79+
" return\n",
80+
" find_valid(i+1, curr + [s[i]])\n",
81+
" find_valid(i+1, curr[:-1] + [curr[-1] + s[i]]) if curr else None\n",
82+
"\n",
83+
" find_valid(0, [])\n",
84+
" return valid_addrs\n",
85+
"\n",
86+
"sol = Solution()\n",
87+
"cases = [\n",
88+
" (\"25525511135\", [\"255.255.11.135\",\"255.255.111.35\"]),\n",
89+
" (\"0000\", [\"0.0.0.0\"]),\n",
90+
" (\"1111\", [\"1.1.1.1\"]),\n",
91+
" (\"010010\", [\"0.10.0.10\",\"0.100.1.0\"]),\n",
92+
" (\"101023\", [\"1.0.10.23\",\"1.0.102.3\",\"10.1.0.23\",\"10.10.2.3\",\"101.0.2.3\"]) \n",
93+
"]\n",
94+
"for s, expected in cases:\n",
95+
" actual = sol.restoreIpAddresses(s)\n",
96+
" assert sorted(actual) == sorted(expected), f\"{s}: {sorted(expected)} != {sorted(actual)}\""
97+
]
98+
},
99+
{
100+
"cell_type": "code",
101+
"execution_count": null,
102+
"metadata": {},
103+
"outputs": [],
104+
"source": []
105+
}
106+
],
107+
"metadata": {
108+
"kernelspec": {
109+
"display_name": "Python 3",
110+
"language": "python",
111+
"name": "python3"
112+
},
113+
"language_info": {
114+
"codemirror_mode": {
115+
"name": "ipython",
116+
"version": 3
117+
},
118+
"file_extension": ".py",
119+
"mimetype": "text/x-python",
120+
"name": "python",
121+
"nbconvert_exporter": "python",
122+
"pygments_lexer": "ipython3",
123+
"version": "3.8.5"
124+
}
125+
},
126+
"nbformat": 4,
127+
"nbformat_minor": 4
128+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Technique - Recursion (Dynamic Programming)\n",
8+
"\n",
9+
"DP gets a bad rap because a lot of people struggle to conceptualize it. [Oz](https://bradfieldcs.com/) once told me `don't assume you're bad at DP until you've solved 20 problems with it`. DP requires understanding recursion, overlapping subproblems, optimal substructure, and memoization. Recursion is discussed in `Technique - Recursion`. "
10+
]
11+
},
12+
{
13+
"cell_type": "markdown",
14+
"metadata": {},
15+
"source": [
16+
"## [Partition to K Equal Sum Subsets](https://leetcode.com/problems/partition-to-k-equal-sum-subsets/)\n",
17+
"\n",
18+
"(Original solution [here](https://leetcode.com/problems/partition-to-k-equal-sum-subsets/discuss/146579/Easy-python-28-ms-beats-99.5))\n",
19+
"\n",
20+
"Each subset must be equal to `sum(nums)/k`, and there is only a solution if `sum(nums) % k == 0`. To start, we sort the numbers into descending order. \n",
21+
"\n"
22+
]
23+
},
24+
{
25+
"cell_type": "code",
26+
"execution_count": 1,
27+
"metadata": {},
28+
"outputs": [],
29+
"source": [
30+
"class Solution(object):\n",
31+
" def canPartitionKSubsets(self, nums, k):\n",
32+
" nums.sort(reverse=True) \n",
33+
" buck, kSum = [0] * k, sum(nums) // k\n",
34+
" \n",
35+
" def dfs(idx):\n",
36+
" if idx == len(nums): \n",
37+
" return len(set(buck)) == 1 # every bucket sums to target\n",
38+
" for i in range(k):\n",
39+
" buck[i] += nums[idx]\n",
40+
" if buck[i] <= kSum and dfs(idx + 1): \n",
41+
" return True\n",
42+
" buck[i] -= nums[idx]\n",
43+
" if buck[i] == 0: \n",
44+
" break\n",
45+
" return False\n",
46+
" return dfs(0)\n",
47+
"\n",
48+
" \n",
49+
"# Annotated and slower-but-clearer version of @jingkuan's solution based on @chemikadze's\n",
50+
"# solution using @chengyuge925's solution.\n",
51+
"class Solution:\n",
52+
" def canPartitionKSubsets(self, nums, k):\n",
53+
" buckets = [0]*k\n",
54+
" target = sum(nums) // k\n",
55+
"\n",
56+
" # We want to try placing larger numbers first\n",
57+
" nums.sort(reverse=True)\n",
58+
"\n",
59+
"\n",
60+
" # DFS determines which bucket to put the 'current element' (nums[idx] ) into\n",
61+
" def dfs(idx):\n",
62+
" # If we've placed all of the items, we're done;\n",
63+
" # check if we correctly made k equal subsets of \n",
64+
" # size sum(nums) // k\n",
65+
" if idx == len(nums):\n",
66+
" return set(buckets) == set([target])\n",
67+
"\n",
68+
" # For each bucket\n",
69+
" for i in range(k):\n",
70+
" # Try adding the current element to it\n",
71+
" buckets[i] += nums[idx]\n",
72+
"\n",
73+
" # If it's a valid placement and we correctly placed the next element, we're\n",
74+
" # done placing the current element.\n",
75+
" if buckets[i] <= target and dfs(idx + 1):\n",
76+
" return True\n",
77+
"\n",
78+
" # Otherwise, remove the current element from the ith bucket and \n",
79+
" # try the next one. \n",
80+
" buckets[i] -= nums[idx]\n",
81+
"\n",
82+
" # This is an optimization that is not strictly necessary. \n",
83+
" # If bucket[i] == 0, it means:\n",
84+
" # - We put nums[idx] into an empty bucket\n",
85+
" # - We tried placing every other element after and failed.\n",
86+
" # - We took nums[idx] out of the bucket, making it empty again. \n",
87+
" # So trying to put nums[idx] into a _different_ empty bucket will not produce\n",
88+
" # a correct solution; we will just waste time (we place elements left to right,\n",
89+
" # so if this bucket is now empty, every one after it is too).\n",
90+
" #\n",
91+
" # Otherwise (bucket[i] > 0), we just go to the next bucket and \n",
92+
" # try placing nums[idx] there. If none of them work out, we wind up\n",
93+
" # breaking out of the loop when range(k) ends and returning False. \n",
94+
" if buckets[i] == 0:\n",
95+
" break\n",
96+
"\n",
97+
" # We couldn't place the current element anywhere that \n",
98+
" # leads to a valid solution, so we will need to backtrack\n",
99+
" # and try something else. \n",
100+
" return False\n",
101+
"\n",
102+
" # Start by trying to place nums[0]\n",
103+
" return dfs(0)"
104+
]
105+
},
106+
{
107+
"cell_type": "code",
108+
"execution_count": null,
109+
"metadata": {},
110+
"outputs": [],
111+
"source": []
112+
}
113+
],
114+
"metadata": {
115+
"kernelspec": {
116+
"display_name": "Python 3",
117+
"language": "python",
118+
"name": "python3"
119+
},
120+
"language_info": {
121+
"codemirror_mode": {
122+
"name": "ipython",
123+
"version": 3
124+
},
125+
"file_extension": ".py",
126+
"mimetype": "text/x-python",
127+
"name": "python",
128+
"nbconvert_exporter": "python",
129+
"pygments_lexer": "ipython3",
130+
"version": "3.8.5"
131+
}
132+
},
133+
"nbformat": 4,
134+
"nbformat_minor": 4
135+
}

0 commit comments

Comments
 (0)