From 679e95bda11b3a6f6a761410dff3645c7b8afcfc Mon Sep 17 00:00:00 2001 From: anebz Date: Thu, 27 Aug 2020 11:09:09 +0200 Subject: [PATCH] chapter 8 done :heavy_check_mark: --- 08. Recursion/8.2. Robot in grid.md | 87 +++++++++++++++++++ 08. Recursion/8.4. Power set.md | 44 ++++++++++ .../8.7. Permutations without duplicates.md | 52 +++++++++++ .../8.8. Permutations with duplicates.md | 77 ++++++++++++++++ README.md | 5 ++ 5 files changed, 265 insertions(+) create mode 100644 08. Recursion/8.2. Robot in grid.md create mode 100644 08. Recursion/8.4. Power set.md create mode 100644 08. Recursion/8.7. Permutations without duplicates.md create mode 100644 08. Recursion/8.8. Permutations with duplicates.md diff --git a/08. Recursion/8.2. Robot in grid.md b/08. Recursion/8.2. Robot in grid.md new file mode 100644 index 0000000..5253fae --- /dev/null +++ b/08. Recursion/8.2. Robot in grid.md @@ -0,0 +1,87 @@ +# 8.2. Robot in a Grid + +> Imagine a robot sitting on the upper left corner of grid with r rows and c columns. The robot can only move in two directions, right and down, but certain cells are "off limits" such that the robot cannot step on them. Design an algorithm to find a path for the robot from the top left to the bottom right. + +Start going down, if you find some cell that can't be stepped on, go one right and down again. And so on. + +```python +def go_bottom_down(r: int, c: int): + pos = [0, 0] + while pos[0] < r and pos[1] < c: + pos[0] += 1 + step = can_step(pos) + while not step: + pos[1] += 1 + step = can_step(pos) +``` + +## Hints + +> For the robot to reach the last cell, it must find a path to the second-to-last cells. For it to find a path to the second-to-last cells, it must find a path to the tird-to-last cells. + +I'm missing something. + +> Simplify this problem by first figuring out if there's a path. Then, modify your algorithm to track the path. + +But if there's no path to going down, I can go one right and try again. Why check if there's a path and then track it? + +## Solution + +To reach the point (r, c) we have to reach either (r-1, c) or (r, c-1). To reach (r-1, c), we can either reach (r-2, c) or (r-1, c-1). This memoization could be improved by storing the grid in memory, and whether it can be accessed or not. + +You create a maze with True or False for the positions that can be stepped on. + +```python +def get_path(maze): + if not maze or len(maze) == 0: + return None + + path = [] + if is_path(maze, len(maze)-1, len(maze[0])-1, path): + return path + return None + +def is_path(maze, row, col, path): + # if out of bounds or not available, return + if col < 0 or row < 0 or not maze[row][col]: + return False + + isAtOrigin = (row == 0) and (col == 0) + + #if there's a path from the start to here, add my location + if isAtOrigin or isPath(maze, row, col-1, path) or isPath(maze, row-1, col,path): + path.append((row, col)) + return True + return False + +print(getPath([[True, True],[True,True]])) +``` + +This works, but many many locations are visited twice. Using memoization: + +```python +def get_path_memoized(maze): + if maze == None or len(maze) == 0: + return None + + path = [] + visited_points = [] + if is_path_memoized(maze, len(maze) - 1, len(maze[0]) - 1, path, visited_points): + return path + return None + +def is_path_memoized(maze, row, col, path, visited_points): + if row < 0 or col < 0 or not maze[row][col] or (row, col) in visited_points: + return False + + is_at_origin = row == 0 && col == 0 + + if is_at_origin or is_path_memoized(maze, row-1, col, path) or is_path_memoized(maze, row, col-1, path): + path.append((row, col)) + return True + visited_points.append((row, col)) + return False + +print(get_path_memoized([[True,True], [False,True]])) + +``` \ No newline at end of file diff --git a/08. Recursion/8.4. Power set.md b/08. Recursion/8.4. Power set.md new file mode 100644 index 0000000..334c88f --- /dev/null +++ b/08. Recursion/8.4. Power set.md @@ -0,0 +1,44 @@ +# 8.4. Power set + +> Write a method that returns all subsets of a set + +```python +def power_set(lst, subsets=[]): + for el in lst: + if len(el) > 0: + subsets.extend(power_set(el, subsets)) + subsets.append(lst) + return subsets + +lst = [1, 2, [3, 4], [[5], [6,7]]] +subsets = power_set(lst) +``` + +## Hints + +I understood the problem wrong. + +* If you have {a, b}, all the subsets are: a, b, ab +* If you have {a, b, c}, all the subsets are: [a, b, ab], c, ac, bc, abc +* If you have {a, b, c, d}, all the subsets are: [a, b, ab, c, ac, bc, abc], ad, bd, cd, abd, acd, bcd, abcd. + +For any new letter, add the last letter to all the subsets and append. + +> You can also map each subset to a binary number, the `i`th bit could represent a 'boolean' flag for whether an element is in the set. + +I can also use a python set. + +```python +def power_set(lst): + subsets = set() + subsets.add(lst[0]) + for el in lst: + for subs in subsets: + subsets.add(subs + el) + subsets.add(el) + return subsets + +power_set([1, 2, 3, 4]) +``` + +The complexity of this algorithm is O(n * 2^n) in space and time. diff --git a/08. Recursion/8.7. Permutations without duplicates.md b/08. Recursion/8.7. Permutations without duplicates.md new file mode 100644 index 0000000..7271c55 --- /dev/null +++ b/08. Recursion/8.7. Permutations without duplicates.md @@ -0,0 +1,52 @@ +# 8.7. Permutations without duplicates + +> Write a method to compute all permutations of a string of unique characters + +* s = anebz +* s = an, size=2 + - permutations = an, na + - total 4 +* s = ane, size=3 + - permutations = ean, ane, ena, nae + aen + nea + - total 6, 3\*2 +* s = aneb, size=4 + - permutations = aneb, anbe, aben, abne, aenb, aebn + - baen, bane, bean, bena, bnae, bnea + - eabn, eanb, eban, ebna, enab, enba + - nabe, naeb, nbae, nbea, neab, neba + - total 24, 4\*3\*2 + +In each iteration, add the new word in each position of the previous permutations. for 'an' and new letter 'e', it becomes ean, aen, ane. + +```python +def permutations(s): + prm = [s[0]] + for char in s[1:]: + for p in list(prm): + for i in range(len(p)): + prm.append(p[:i] + char + p[i:]) + return prm +``` + +This would have complexity of O(len(s)!) for each character in s. Therefore O(n * n!) = O((n+1)!). Checked example 12 on p51 and it's correct (section 1.9 in the [Introduction](../introduction.md)) + +## Hints + +The hints describe what I have done =) Insert the new letter into each possible location of the previous permutations. You can create all permutations of abcd by computing all permutations of abc and then inserting d into each possible location within those. + +```python +# approach 1: building from permutations of first n-1 characters +def permutations(s): + prm = [] + if len(s) == 0: + prm.append("") + return prm + for word in permutations(s[1:]): + for i in range(len(word)): + prm.append(word[:i] + s[0] + word[i:]) + return prm + +print(permutations("str")) +``` + +For a word like 'str', it goes deep into the function until len(s) == 0, then returns '', we go up to 'r', and adds 'r' to each position. We end up with 'r'. Go up, iterate prm ('r') and add 't' to each position. And so on. diff --git a/08. Recursion/8.8. Permutations with duplicates.md b/08. Recursion/8.8. Permutations with duplicates.md new file mode 100644 index 0000000..ea7b8b8 --- /dev/null +++ b/08. Recursion/8.8. Permutations with duplicates.md @@ -0,0 +1,77 @@ +# 8.8. Permutations with duplicates + +> Write a method to compute all permutations of a string whose characters are not necessarily unique. The list of permutations should not have duplicates. + +solution in 357 + +The easiest solution would be to create a set instead of a list. + +* s = anee +* s = an, size=2 + - permutations = an, na + - total 4 +* s = ane, size=3 + - permutations = ean, ane, ena, nae + aen + nea + - total 6, 3\*2 +* s = anee, size=4 + - permutations = anee, aeen, aene + - eaen, eane, eean, eena, enae, enea + - naee, neae, neea + - total 12 + +```python +# approach 1: building from permutations of first n-1 characters +def permutations(s): + prm = set() + if len(s) == 0: + prm.append("") + return prm + for word in permutations(s[1:]): + for i in range(len(word)): + prm.add(word[:i] + s[0] + word[i:]) + return prm + +print(permutations("str")) +``` + +## Hints + +If there are many duplicate letters, it becomes inefficient to check the set each time. + +> Get the count of each character, abcaac has 3a, 2c and 1b + +How does that help? How can I omit doing all the permutations and checking if that permutation is already there? + +> Pick a starting character. a, b, c. If you start with a, then get all permuations for 2a, 2c and 1b. + +## Solution + +Worst case will be O((n+1)!), but in other cases we want to only create the unique permutations. + +Permutations of (a=3, b=1, c=2) is P(2, 1, 2) + P(3, 0, 2) + P(3, 1, 1) and so on recursively for each starting character. + +```python +def printPerms(string): + result = [] + letterCountMap = defaultdict() + for letter in string: + letterCountMap[letter] += 1 + + printPermsInner(letterCountMap, "", len(string), result) + return result + +def printPermsInner(letterCountMap, prefix, remaining, result): + #base case Permutation has been completed + if remaining == 0: + result.append(prefix) + return + #try remaining letter for next char, and generate remaining permutations + for character in letterCountMap: + count = letterCountMap[character] + if count > 0: + letterCountMap[character] -= 1 + printPermsInner(letterCountMap, prefix + character, remaining - 1, result) + letterCountMap[character] = count + +print(printPerms("aaf")) +``` \ No newline at end of file diff --git a/README.md b/README.md index fa0b8eb..fd83436 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,12 @@ If you can't afford to buy the book, you can find a free pdf [here](http://ahmed ## Chapter 8 Recursion +* Triple step +* Robot in grid * Magic index (index such that A[i] = 1) +* Power set +* Permutations of string with unique characters +* Permutations of string with duplicate characters ## Chapter 10 Sorting and searching