Implement Dynamic Programming in this algorithm? - python

I was provided with an algorithm that would help me solve the following problem:
Take an NxN grid. Start at the top left and traverse to the bottom right only going down or right from each node. For example:
grid = [[0,2,5],[1,1,3],[2,1,1]]
Imagine this list as a grid:
0 2 5
1 1 3
2 1 1
Each number you visit you have to add to the running total. In this case there are six different ways to get to the bottom that provide you with a total sum. The algorithm I was given that would work for this and return a list of possible sums is as follows:
def gridsums(grid, x, y, memo):
if memo[x][y] is not None:
return memo[x][y]
if x == 0 and y == 0:
sums = [0]
elif x == 0:
sums = gridsums(grid, x, y-1, memo)
elif y == 0:
sums = gridsums(grid, x-1, y, memo)
else:
sums = gridsums(grid, x-1, y, memo) + gridsums(grid, x, y-1, memo)
sums = [grid[x][y] + s for s in sums]
memo[x][y] = sums
return sums
def gridsumsfast(grid):
memo = []
for row in grid:
memo.append([])
for cell in row:
memo[-1].append(None)
return gridsums(grid, len(grid[0]) - 1, len(grid) - 1, memo)
Indentation is not correct but you get the idea. This works however for a much larger value of N, it does not work well at all. For example up to a N value of 20, it takes to long and on some tests I ran it times out.
Obviously there is a lot of repeat work being done by the main function so how exactly can I implement memoization/dynamic programming with this algorithm? The work is repeated a lot of times giving me grounds to say dynamic programming needs to be implemented however the line: sum = [grid[x][y] = s for s in sums] trips me up because x and y change for each value so I would only have to commit the previous sums into a memo however I cannot quite wrap my head around doing so.
Any guidance in the right direction is appreciated, thank you.

All paths arrive in a cell either from above or from left. If you loop through the grid left to right and top to bottom, you can accumulate the sums from cells you have computed already. The idea is the same as in TravisJ's answer.
def gridsums(grid):
previous_row_sums = [set() for _ in grid[0]]
previous_row_sums[0].add(0) #seed
for row in grid:
left_sums = set()
row_sums = []
for value, above_sums in zip(row, previous_row_sums):
old_sums = left_sums | above_sums
sums = {value + old for old in old_sums}
row_sums.append(sums)
left_sums = sums
previous_row_sums = row_sums
return sums

Related

Top-down approach poorly implemented

I have been kinda practicing some recursive / dynamic programming exercises, but I have been struggling trying to convert this recursive problem into a top-down one, as for example, for f(4,4), the recursive one yields 5, whereas my top-down yields 2.
I can see the base cases being the same, so I might be messing up with the logic somehow, but I haven't found where.
the code is as written:
def funct(n, m):
if n == 0:
return 1
if m == 0 or n < 0:
return 0
return funct(n-m, m) + funct(n, m-1)
and the top-down approach is:
def funct_top_down(n, m):
if n == 0:
return 1
if m == 0 or n < 0:
return 0
memo = [[-1 for x in range(m+1)] for y in range(n+1)]
return funct_top_down_imp(n, m, memo)
def funct_top_down_imp(n, m, memo):
if memo[n][m] == -1:
if n == 0:
memo[n][m] = 1
elif m == 0 or n < 0:
memo[n][m] = 0
else:
memo[n][m] = funct_top_down_imp(n-m, m, memo) + funct_top_down_imp(n, m-1, memo)
return memo[n][m]
Thanks!
The basic idea is to compute all possible values of func instead of just one. Sounds like a lot, but since func needs all previous values anyways, this would be actually the same amount of work.
We place computed values in a matrix N+1 x M+1 for given N, M so that it holds that
matrix[n][m] === func(n, m)
Observe that the first row of the matrix will be all 1, since f(0,m) returns 1 for every m. Then, starting from the second row, compute the values left to right using your recursive dependency:
matrix[n][m] = matrix[n-m][m] + matrix[n][m-1]
(don't forget to check the bounds!)
Upon completion, your answer will be in the bottom right corner of the matrix. Good luck!

Guidance on removing a nested for loop from function

I'm trying to write the fastest algorithm possible to return the number of "magic triples" (i.e. x, y, z where z is a multiple of y and y is a multiple of x) in a list of 3-2000 integers.
(Note: I believe the list was expected to be sorted and unique but one of the test examples given was [1,1,1] with the expected result of 1 - that is a mistake in the challenge itself though because the definition of a magic triple was explicitly noted as x < y < z, which [1,1,1] isn't. In any case, I was trying to optimise an algorithm for sorted lists of unique integers.)
I haven't been able to work out a solution that doesn't include having three consecutive loops and therefore being O(n^3). I've seen one online that is O(n^2) but I can't get my head around what it's doing, so it doesn't feel right to submit it.
My code is:
def solution(l):
if len(l) < 3:
return 0
elif l == [1,1,1]:
return 1
else:
halfway = int(l[-1]/2)
quarterway = int(halfway/2)
quarterIndex = 0
halfIndex = 0
for i in range(len(l)):
if l[i] >= quarterway:
quarterIndex = i
break
for i in range(len(l)):
if l[i] >= halfway:
halfIndex = i
break
triples = 0
for i in l[:quarterIndex+1]:
for j in l[:halfIndex+1]:
if j != i and j % i == 0:
multiple = 2
while (j * multiple) <= l[-1]:
if j * multiple in l:
triples += 1
multiple += 1
return triples
I've spent quite a lot of time going through examples manually and removing loops through unnecessary sections of the lists but this still completes a list of 2,000 integers in about a second where the O(n^2) solution I found completes the same list in 0.6 seconds - it seems like such a small difference but obviously it means mine takes 60% longer.
Am I missing a really obvious way of removing one of the loops?
Also, I saw mention of making a directed graph and I see the promise in that. I can make the list of first nodes from the original list with a built-in function, so in principle I presume that means I can make the overall graph with two for loops and then return the length of the third node list, but I hit a wall with that too. I just can't seem to make progress without that third loop!!
from array import array
def num_triples(l):
n = len(l)
pairs = set()
lower_counts = array("I", (0 for _ in range(n)))
upper_counts = lower_counts[:]
for i in range(n - 1):
lower = l[i]
for j in range(i + 1, n):
upper = l[j]
if upper % lower == 0:
lower_counts[i] += 1
upper_counts[j] += 1
return sum(nx * nz for nz, nx in zip(lower_counts, upper_counts))
Here, lower_counts[i] is the number of pairs of which the ith number is the y, and z is the other number in the pair (i.e. the number of different z values for this y).
Similarly, upper_counts[i] is the number of pairs of which the ith number is the y, and x is the other number in the pair (i.e. the number of different x values for this y).
So the number of triples in which the ith number is the y value is just the product of those two numbers.
The use of an array here for storing the counts is for scalability of access time. Tests show that up to n=2000 it makes negligible difference in practice, and even up to n=20000 it only made about a 1% difference to the run time (compared to using a list), but it could in principle be the fastest growing term for very large n.
How about using itertools.combinations instead of nested for loops? Combined with list comprehension, it's cleaner and much faster. Let's say l = [your list of integers] and let's assume it's already sorted.
from itertools import combinations
def div(i,j,k): # this function has the logic
return l[k]%l[j]==l[j]%l[i]==0
r = sum([div(i,j,k) for i,j,k in combinations(range(len(l)),3) if i<j<k])
#alaniwi provided a very smart iterative solution.
Here is a recursive solution.
def find_magicals(lst, nplet):
"""Find the number of magical n-plets in a given lst"""
res = 0
for i, base in enumerate(lst):
# find all the multiples of current base
multiples = [num for num in lst[i + 1:] if not num % base]
res += len(multiples) if nplet <= 2 else find_magicals(multiples, nplet - 1)
return res
def solution(lst):
return find_magicals(lst, 3)
The problem can be divided into selecting any number in the original list as the base (i.e x), how many du-plets we can find among the numbers bigger than the base. Since the method to find all du-plets is the same as finding tri-plets, we can solve the problem recursively.
From my testing, this recursive solution is comparable to, if not more performant than, the iterative solution.
This answer was the first suggestion by #alaniwi and is the one I've found to be the fastest (at 0.59 seconds for a 2,000 integer list).
def solution(l):
n = len(l)
lower_counts = dict((val, 0) for val in l)
upper_counts = lower_counts.copy()
for i in range(n - 1):
lower = l[i]
for j in range(i + 1, n):
upper = l[j]
if upper % lower == 0:
lower_counts[lower] += 1
upper_counts[upper] += 1
return sum((lower_counts[y] * upper_counts[y] for y in l))
I think I've managed to get my head around it. What it is essentially doing is comparing each number in the list with every other number to see if the smaller is divisible by the larger and makes two dictionaries:
One with the number of times a number is divisible by a larger
number,
One with the number of times it has a smaller number divisible by
it.
You compare the two dictionaries and multiply the values for each key because the key having a 0 in either essentially means it is not the second number in a triple.
Example:
l = [1,2,3,4,5,6]
lower_counts = {1:5, 2:2, 3:1, 4:0, 5:0, 6:0}
upper_counts = {1:0, 2:1, 3:1, 4:2, 5:1, 6:3}
triple_tuple = ([1,2,4], [1,2,6], [1,3,6])

Make to 2d arrays equal using backtracking

I'm having problems trying to use backtracking in python.
Here is the problem: I have to make two matrices equal by only moving the zero(up, down, left and right), for exemple:
Matrix1: [ [1,2],
[3,0]]
Matrix2: [ [1,0],
[3,2]]
number of moves: 3 (you can't use more than x moves)
The matrix1 needs to be equal to matrix2.
The anwser should be:
'u' (move zero upwards)
I have no idea how to begin :c
PS: Sorry, this isn’t my first language, so feel free to correct me.
Matrix size:
Number of movements:
First Matrice:
Final Matrice:'
If the inputs are:
3
3
1 2 3
4 5 6
7 8 0
1 2 3
4 0 5
7 8 6
the output should be:
ul (zero change position with 6, moving upwards, and then zero changes position with 5, moving to the left).
I don't how to use backtrack.
I'm trying to do something like this:
def safe(x, y, n):
if x>=0 and x<n and y>=n and y<n:
return True
return False
def whereszero(maze):
for x in range(len(maze)):
for y in range(len(maze)):
if maze[x][y]==0:
return x, y
def main():
n=int(input("Matrice size:")) #matrice dimensions
matrice1=[int(x) for x in input().split()] for j in range(n)]
matrice2=[int(x) for x in input().split()] for j in range(n)]
max=int(input("maximum movements:"))
zerox, zeroy=whereszero(matrice1) #returns zero index
if solved(matrice1, matrice2, zeroX, zeroY, zeroX, zeroY, 0, max, b, list)==False:
print("No solution")
return False
print("Anwser:", list)
return True
def solved(mat1, mat2, zeroinx, zeroiny, x, y, i, max, movements, list):
if mat1==mat2:
return True
if safe(x, y, len(mat1)) and i<=max
mat1[x][y], mat1[zeroinx][zeroiny]=mat1[zeroinx][zeroiny], mat1[x][y]
zeroinx=x
zeroint=y
list.append("movements")
if solved(mat1, mat2, zeroinx, zeroiny, x+1, y, i+1, max, d, list): #try moving downwards
return True
if solved(mat1, mat2, zeroinx, zeroiny, x-1, y, i+1, max, u, list): #try moving upwards...
return True
if solve(mat1, mat2, zeroinx, zeroiny, x, y-1, i+1, max, l, list): #left
return True
if solve(mat1, mat2, zeroinx, zeroiny, x, y+1, i+1, max, r, list): #right, i is the number of moviments tried, max is maximum number of movements
return True
return False # How do I backtrack?
main() `
Backtracking is a technique, where - to be short - your algorithm make some choice of possible ones, but if that choice doesn't lead to success, it comes back (hence the backtracking name) and try next possible choice, until success or all choices will be tried. Typical solution in pseudo-code:
func run(state):
if success: return true
for choice in possible-choices(state):
new_state = execute_choice(state, choice)
if run(new_state):
return true
return false
Things to keep in mind:
typical solution is recursive, so there's a limit of maximum depth, that you can check (based on memory).
you need to stop checking new 'possible-choices' at some point. For example in your task given matrix 2x2 you can imagine going with 0 left, right, left, right in neverending cycle. You need to either set a hard limit or detect previously encounered state.
new_state is named like this for a reason - you need copy. You need to remember 'old' state, so you could come back to it. How do you keep it in memory is another story.

Is the time complexity of this code O(n^2)?

The problem finds two items in the array that add up to target value.
It returns an array w/ the index of the correct values.
I think the time complexity is n^2 because the while loop runs through array once so n time. And in the worst case, it has to repeat this n times. So n*n running time.
Even though the number of elements it has to iterate through decreases each time, we drop the constants when calc. time complexity.
Is this analysis correct?
Any recommendations for bringing it down to n?
def twoSum(nums, target):
indx = []
size = len(nums)
if (size < 2):
return indx
x = 0
y = size - 1
while(x < y):
if( (nums[x] + nums[y]) == target):
indx[0] = x
indx[1] = y
break
elif ( (y - 1) == x):
x = x + 1
y = size - 1
else:
y = y -1
return indx
You can do O(n), this is a google interview question that they have a video on YouTube for I believe. Or at least they had a very similar problem:
def twoSum(nums, target):
values = dict()
for index, n in enumerate(nums):
if target - n in values:
return values[target - n], index
else:
values[n] = index
print(twoSum([4, 5, 2, 1, 3], 4)) # (3, 4)
- Edit -
Per the comments below, this solution technically still has a worst case of O(n^2) do to hash collisions. For most cases you should get close to O(n) but if you are working with large numbers (negative or positive) you will see an increase in collisions which will result n * log(n) to n^2 time (especially if the test set given to you tries to target hash collisions).

Recursive generator on list of lists

I have 2 demotion list that looks like that:
[[1,2,3,],
[4,5,6],
[7,8,9]]
I'm trying to write a generator that yield the sum of a 'path'.
A 'path' starts from left-top corner and goes only on x+1 and y+1 until it's get to it's last element(the right bottom).
For example, a valid path is 1=>2=>5=>6=>9 (sum=23).
None-valid path could be 1=>2=>5=>**4**=>...
So far I have this code:
my_list = [[0, 2, 5], [1, 1, 3], [2, 1, 1]]
def gen(x, y, _sum):
if x + 1 <= len(my_list):
for i1 in gen(x + 1, y, _sum + my_list[y][x]):
yield _sum
if y + 1 <= len(my_list):
for i2 in gen(x, y + 1, _sum + my_list[y][x]):
yield _sum
yield _sum + my_list[y][x]
g = gen(0, 0, 0)
total = 0
for elm in g:
total += elm
print total
I get the error:
for i2 in gen(x, y+1, _sum+my_list[y][x]):
IndexError: list index out of range
The reason for this error is a simple off-by-one error.*
I think what you wanted here is x <= len(my_list) or, equivalently, x+1 < len(my_list); you've doubled-up the +1-ness, causing you to run past the end of the list.
Consider a concrete case:
len(my_list) is 3. x is 2. So, x+1 <= len(my_list) is 3 <= 3, which is true. So you call yourself recursively with gen(3, …).
In that recursive call, 4 <= 3 is false, so, depending on the value of y, you call either:
gen(x, y + 1, _sum + my_list[y][3]), or
_sum + my_list[y][3]
… either of which will raise an IndexError.
Obviously you need to fix the same problem with y as with x.
You can see it running without errors here.
Of course it doesn't actually print out the right result, because there are other problems in your code. Off the top of my head:
total = + elm replaces whatever's in total with the value of elm. You probably wanted +=, not = + here.
Yielding _sum over and over and ignoring the values yielded by the recursive generators can't possibly be doing any good. Maybe you wanted to yield i1 and i2 instead?
I can't guarantee that those are the only problems in your code, just that they are problems.
* I'm assuming here that this is a silly bug, not a fundamental error—you clearly know that indexes are 0-based, since you called the function with gen(0, 0, 0) rather than gen(1, 1, 0).
If you really wanted to brute-force all permissible paths through an N x M matrix, then simply generate all permutations of N - 1 moves to the right plus M - 1 moves down, then use those moves to sum the values along the path:
from itertools import permutations
def gen_path_sum(matrix):
N, M = len(matrix), len(matrix[0])
for path in permutations([(1, 0)] * (N - 1) + [(0, 1)] * (M - 1)):
sum = matrix[0][0]
x = y = 0
for dx, dy in path:
x += dx; y += dy
sum += matrix[x][y]
yield sum
This'll produce (N + M)! paths; there are 720 such paths for a 3 by 3 matrix.
However, if you are trying to find the maximum path through the matrix, you are going about it the inefficient way.
You can instead calculate the maximum path for any cell in the matrix; it is simply the greatest of the maximum path values of the cell above and to the left, plus value of the current cell. So for the cell in the top left (with no cells above or to the right), the maximum path value is the value of the cell.
You can calculate all those values with a N X M loop:
def max_path_value(matrix):
totals = [row[:] for row in matrix]
for x, row in enumerate(totals):
for y, cell in enumerate(row):
totals[x][y] += max(
totals[x - 1][y] if x else 0,
totals[x][y - 1] if y else 0
)
return totals[-1][-1]
This only takes N X M steps, or 9 steps in total for your 3 by 3 matrix. That's a factor of 80 better than the brute-force approach.
The contrast only increases as your matrix sizes increase; a 10x10 matrix, brute forced, requires examining 2432902008176640000 paths (== 20!), or you can just calculate the maximum path with 100 steps instead.

Categories

Resources