N-Queens II using backtracking is slow - python

The n-queens puzzle is the problem of placing n queens on an n x n chessboard such that no two queens attack each other.
Given an integer n, return the number of distinct solutions to the n-queens puzzle.
https://leetcode.com/problems/n-queens-ii/
My solution:
class Solution:
def totalNQueens(self, n: int) -> int:
def genRestricted(restricted, r, c):
restricted = set(restricted)
for row in range(n): restricted.add((row, c))
for col in range(n): restricted.add((r, col))
movements = [[-1, -1], [-1, 1], [1, -1], [1, 1]]
for movement in movements:
row, col = r, c
while 0 <= row < n and 0 <= col < n:
restricted.add((row, col))
row += movement[0]
col += movement[1]
return restricted
def gen(row, col, curCount, restricted):
count, total_count = curCount, 0
for r in range(row, n):
for c in range(col, n):
if (r, c) not in restricted:
count += 1
if count == n: total_count += 1
total_count += gen(row + 1, 0, count, genRestricted(restricted, r, c))
count -= 1
return total_count
return gen(0, 0, 0, set())
It fails at n=8. I can't figure out why, and how to have less iterations. It seems I am already doing the minimum iterations possible.

The restricted set seems wasteful, both time- and space-wise. At the end of the successful recursion, n levels deep it grows to n^2 size, which drives the total complexity to O(n^3). And it is not really needed. It is much easier to check the availability of the square by looking at the queens already placed (please forgive the chess lingo; file stand for vertical, and rank for horizontal):
def square_is_safe(file, rank, queens_placed):
for queen_rank, queen_file in enumerate(queens_placed):
if queen_file == file: # vertical attack
return false
if queen_file - file == queen_rank - rank: # diagonal attack
return false
if queen_file - file == rank - queen_rank: # anti-diagonal attack
return false
return true
to be used in
def place_queen_at_rank(queens_placed, rank):
if rank == n:
total_count += 1
return
for file in range(0, n):
if square_is_safe(file, rank, queens_placed):
queens_placed.append(file)
place_queen_at_rank(queens_placed, rank + 1)
queens_placed.pop()
And there is a plenty of room for the optimization. For example, you may want to special-case the first rank: due to a symmetry, you only need to inspect a half of it (cutting execution time by the factor of 2).

You can avoid checking for horizontal conflicts by only placing one queen per row. This also allows you to reduce the size of the diagonal conflict matrix by only flagging subsequent rows. Using a simple boolean flag list for column conflicts is also a time saver (as opposed to flagging multiple entries in a matrix)
here's an example as a generator of solutions:
def genNQueens(size=8):
# setup queen coverage from each position {position:set of positions}
reach = { (r,c):[] for r in range(size) for c in range(0,size) }
for R in range(size):
for C in range(size):
for h in (1,-1): # diagonals on next rows
reach[R,C].extend((R+i,C+h*i) for i in range(1,size))
reach[R,C] = [P for P in reach[R,C] if P in reach]
reach.update({(r,-1):[] for r in range(size)}) # for unplaced rows
# place 1 queen on each row, with backtracking
cols = [-1]*size # column of each queen (start unplaced)
usedCols = [False]*(size+1) # column conflict detection
usedDiag = [[0]*(size+1) for _ in range(size+1)] # for diagonal conflicts
r = 0
while r >= 0:
usedCols[cols[r]] = False
for ur,uc in reach[r,cols[r]]: usedDiag[ur][uc] -= 1
cols[r] = next((c for c in range(cols[r]+1,size)
if not usedCols[c] and not usedDiag[r][c]),-1)
usedCols[cols[r]] = True
for ur,uc in reach[r,cols[r]]: usedDiag[ur][uc] += 1
r += 1 if cols[r]>=0 else -1 # progress or backtrack
if r<size : continue # continue until all rows placed
yield [*enumerate(cols)] # return result
r -= 1 # backtrack to find more
output:
from timeit import timeit
for n in range(3,13):
t = timeit(lambda:sum(1 for _ in genNQueens(n)), number=1)
c = sum(1 for _ in genNQueens(n))
print(f"solutions for {n}x{n}:", c, "time:",f"{t:.4g}")
solutions for 3x3: 0 time: 0.000108
solutions for 4x4: 2 time: 0.0002044
solutions for 5x5: 10 time: 0.0004365
solutions for 6x6: 4 time: 0.0008741
solutions for 7x7: 40 time: 0.003386
solutions for 8x8: 92 time: 0.009881
solutions for 9x9: 352 time: 0.03402
solutions for 10x10: 724 time: 0.1228
solutions for 11x11: 2680 time: 0.5707
solutions for 12x12: 14200 time: 2.77

For n ≤ 9 (the bound in the linked puzzle), it's enough to enumerate all valid positions for rooks and verify that there are no attacking diagonal moves.
import itertools
def is_valid(ranks):
return not any(
abs(f1 - f2) == abs(r1 - r2)
for f1, r1 in enumerate(ranks)
for f2, r2 in enumerate(ranks[:f1])
)
def count_valid(n):
return sum(map(is_valid, itertools.permutations(range(n))))
print(*(count_valid(i) for i in range(1, 10)), sep=",")

Just one change (remove r loop in gen) can make your solution AC.
The main reason is that your gen has argument row, and it will call itself using row + 1, so there is no need to iterate using for r in range(row, n):. It's unnecessary. Just Removing it, your solution is quite acceptable.(we need to need add else before nested call)
Following is the result:
Before change:
1 1 1.8358230590820312e-05
2 0 5.7697296142578125e-05
3 0 0.00036835670471191406
4 2 0.0021448135375976562
5 10 0.02212214469909668
6 4 0.23602914810180664
7 40 3.0731561183929443
After change:
1 1 1.6450881958007812e-05
2 0 3.1948089599609375e-05
3 0 0.0001366138458251953
4 2 0.0002281665802001953
5 10 0.0008234977722167969
6 4 0.0028502941131591797
7 40 0.01242375373840332
8 92 0.05443763732910156
9 352 0.2279810905456543
It just uses 0.4% time of original version for the n = 7 case, and n = 8 can absolutely work.
class Solution:
def totalNQueens(self, n: int) -> int:
def genRestricted(restricted, r, c):
restricted = set(restricted)
for row in range(n): restricted.add((row, c))
for col in range(n): restricted.add((r, col))
movements = [[-1, -1], [-1, 1], [1, -1], [1, 1]]
for movement in movements:
row, col = r, c
while 0 <= row < n and 0 <= col < n:
restricted.add((row, col))
row += movement[0]
col += movement[1]
return restricted
def gen(row, col, curCount, restricted):
count, total_count = curCount, 0
for c in range(col, n):
if (row, c) not in restricted:
count += 1
if count == n: total_count += 1
else: total_count += gen(row + 1, 0, count, genRestricted(restricted, row, c))
count -= 1
return total_count
return gen(0, 0, 0, set())
if __name__ == '__main__':
import time
s = Solution()
for i in range(1, 8):
t0 = time.time()
print(i, s.totalNQueens(i), '\t', time.time() - t0)
Of course, there are other enhancements can be made. But this is the biggest one.
For instance, you updated and created a new restricted/forbidden points after adding each point.
BTW, I don't agree #user58697 for restricted, it's necessary based on your solution, as you need to clone and update to get a new one to avoid restore it in the recursive call loop.
BTW, following is my solution, just for your reference:
class Solution:
def solveNQueens_n(self, n): #: int) -> List[List[str]]:
cols = [-1] * n # index means row index
self.res = 0
usedCols = set() # this and cols can avoid vertical and horizontal conflict
def dfs(r): # current row to fill in
def valid(c):
for r0 in range(r):
# (r0, c0), (r1, c1) in the (back-)diagonal, |r1 - r0| = |c1 - c0|
if abs(c - cols[r0]) == abs(r - r0):
return False
return True
if r == n: # valid answer
self.res += 1
return
for c in range(n):
if c not in usedCols and valid(c):
usedCols.add(c)
cols[r] = c
dfs(r + 1)
usedCols.remove(c)
cols[r] = -1
dfs(0)
return self.res

In this kind of problem, you have to focus on the algorithm first, not on the code.
In the following, I will focus on the algorithm, just giving an example in C++ to illustrate it.
One main issue is to be able to detect fast if a given position is already controlled or not by an existing Queen.
One simple possibility is to index the diagonals (for 0 to 2N-1), and to keep track in a array if the corresponding diagonals, antidiagonals or the columns are already controlled. Any way to index the diagonals or the antidiagonals will do the jobs. For a given (row, column) point, I use:
diagonal index = row + column
antidiagonal index = n-1 + col - row
In addition, I use a simple symmetry: it is only necessary to calculate the number of possibilities
for a row index from 0 to n/2-1 (or n/2 if n is odd).
It is certainy possible to speed it up a little bit, by using other symmmetries. However, as it is, it looks quite fast enough for n values less or equal to 9.
Result:
2 : 0 time : 0.001 ms
3 : 0 time : 0.001 ms
4 : 2 time : 0.001 ms
5 : 10 time : 0.002 ms
6 : 4 time : 0.004 ms
7 : 40 time : 0.015 ms
8 : 92 time : 0.05 ms
9 : 352 time : 0.241 ms
10 : 724 time : 0.988 ms
11 : 2680 time : 5.55 ms
12 : 14200 time : 31.397 ms
13 : 73712 time : 188.12 ms
14 : 365596 time : 1046.43 ms
Here is the code in C++. As the code is quite simple, you should easily be able to convert it in Python.
#include <iostream>
#include <chrono>
constexpr int N_MAX = 14;
constexpr int N_DIAG = 2*N_MAX + 1;
class Solution {
public:
int n;
int Col[N_MAX] = {0};
int Diag[N_DIAG] = {0};
int AntiDiag[N_DIAG] = {0};
int totalNQueens(int n1) {
n = n1;
if (n <= 1) return n;
int count = 0;
for (int col = 0; col < n/2; ++col) {
count += sum_from (0, col);
}
count *= 2;
if (n%2) count += sum_from (0, n/2);
return count;
}
int sum_from (int row, int col) {
if (Col[col]) return 0;
int diag = row + col;
if (Diag[diag]) return 0;
int antidiag = n-1 + col - row;
if(AntiDiag[antidiag]) return 0;
if (row == n-1) return 1;
int count = 0;
Col[col] = 1;
Diag[diag] = 1;
AntiDiag[antidiag] = 1;
for (int k = 0; k < n; ++k) {
count += sum_from (row+1, k);
}
Col[col] = 0;
Diag[diag] = 0;
AntiDiag[antidiag] = 0;
return count;
}
};
int main () {
int n = 1;
while (n++ < N_MAX) {
auto start = std::chrono::high_resolution_clock::now();
Solution Sol;
std::cout << n << " : " << Sol.totalNQueens (n) << " time : ";
auto diff = std::chrono::high_resolution_clock::now() - start;
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(diff).count();
std::cout << double(duration)/1000 << " ms" << std::endl;
}
return 0;
}

Ok, one thing I missed was that each row must have a queen. Very important observation. gen method has to be modified like this:
def gen(row, col, curCount, restricted):
if row == n: return 0
count, total_count = curCount, 0
for c in range(col, n):
if (row, c) not in restricted:
if count + 1 == n: total_count += 1
total_count += gen(row + 1, 0, count + 1, genRestricted(restricted, row, c))
return total_count
It beats only ~20% submissions, so it's not perfect at all. Far from it.

Related

Last Digit of the Sum of Fibonacci Numbers

I am trying to find the last digit of sum of Fibonacci Series. I calculate the sum as F(n+2) - 1. The below code is working fine but it is slow for large numbers (e.g 99999).
How can I optimize this?
n = int(input())
def last_digit(n):
a, b = 0, 1
for i in range(n+2):
a, b = b, a + b
return (a-1) % 10
print(last_digit(n))
Look at this table: http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibtable.html
notice that fib(60) last digit is 0 and fib(61) last digit is 1, that is same as fib(0) and fib(1), thus starting at 60 last digits starts to repeat, so you can calculate last digit for fib(n%60) rather than fib(n).
For example last digit is same for fib(115) and fib(55) and equal to 5.
The series of final digits of Fibonacci numbers repeats with a cycle of 60. Therefore, you can optimize the calculation of the sum of n terms to F((n+2) % 60) - 1. Also, to stay in the integer range, you can keep only the last digit of each term:
def last_digit(n):
a, b = 0, 1
for i in range((n + 2) % 60):
a, b = b, (a + b) % 10
return 9 if a == 0 else a - 1
print([last_digit(n) for n in range(1, 11)])
Output:
[1, 2, 4, 7, 2, 0, 3, 4, 8, 3]
Here's code optimized specifically for the last digit:
def fib(n):
a, b = 0, 1
r = 1
if n < 1:
return 0
for i in range(n - 1):
a, b = b, (a + b)%10
r += b
r %= 10
return r
It works by getting only the last digit of the next term and adding that to the result. It then gets the last digit of the result and sets it to itself. It repeats until it gets to the term number and returns a one-digit number :D
Fun Fact:
Try the above function on 99. Returns 0. What about 999? 0. 9999? 0. Continue this :D
Try to use the Pisano Period property. If you want to compute the last digit, Pisano Period of 10 will be 60. Knowing this, you can have a function similar to:
def fibonacci_sum(n):
pisano = 60
if n < 2: return n
n %= pisano
fib_arr = [1,1]
for _ in range(n):
fib_arr.append((fib_arr[-1] + fib_arr[-2]) % 10)
return (fib_arr[-1] - 1) % 10
For more information refer to saveriogzz Github CS_Curriculum repo.
Cheers!
Here is a simple C++ program
//outputs last digit of ( sum of fib number till n)
#include<iostream>
using namespace std;
int64_t fib_sum_digit(int64_t n)
{
int fl[60] ={0};
fl[0] = 0;
fl[1] = 1;
// int64_t sum60 = 1;
for(int i = 2 ; i<60 ; i++)
{
fl[i] = (fl[i-1] +fl[i-2])%10 ;
//sum60 += fl[i];
}
int64_t sum = 0;
// sum += (sum60*(n/60)); ///sum60%10 always = 0 ;
for(int i = 1; i<=(n%60); i++ )
{
sum += (fl[i]);
//cout<<i<<","<<sum<<"->"; ///debug
}
return sum%10;
}
int main()
{
int64_t n;
cin>>n;
int64_t ans = fib_sum_digit(n);
cout<<ans;
return 0;
}

how to make a sum of submatrices

i need to make for every sub-matrix the sum of the values.
for example if i have [[1,1,2],[2,3,4]] the resulting matrix will be:
M[0][0] = 1 M[0][1] = 1+1 = 2 M[0][2] = 1+1+2 = 4
M[1][0] = 1+2 = 3 M[1][1] = 1+1+2+3 = 7 M[1][2] = 1+1+2+2+3+4 = 13
or
M = [[1,2,4],[3,7,13]]
and i made this code
`N = []
M = []
summ = 0
n= list(map(int, input().split()))
while n != []:
N.append(n)
n = list(map(int, input().split()))
for i in range(len(N)):
M.append([0 for i in range(len(N[0]))])
summ = 0
for j in range(len(N[0])):
summ += N[i][j]
M[i][j] = M[i-1][j] + summ `
the problem is that when the matrix is big becomes reeeeally slow.
i need to solve a 100x100 matrix at max in 0.5 sec
can anybody help me? WITHOUT IMPORTING PACKAGES!!
`
For speed, you really want to be using NumPy which will be significantly faster than base Python in addition to giving cleaner code for matrices. From your small example, you can use numpy.cumsum() twice across different axes:
import numpy as np
arr = np.array([[1,1,2],[2,3,4]])
out = arr.cumsum(axis=1).cumsum(axis=0)
print(out)
Gives:
array([[ 1, 2, 4],
[ 3, 7, 13]], dtype=int32)
Side note: on Windows, the default int type is 32 Bit and cumsum() is liable to silently overflow on large matrices/large numbers so you'll probably want arr = np.array([[1,1,2],[2,3,4]]).astype(np.int64) if on Windows.
Timings:
arr = np.arange(10000).reshape(100, 100)
%timeit out = arr.cumsum(axis=1).cumsum(axis=0)
56.3 µs ± 4.96 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
So, thousands of times faster than your requirement.
We can compute the sum of all sub-matrix by using a dp matrix. In this we compute the prefix sum of the given matrix.We create a new matrix and store the prefix sum and then use the formula
tlx: Top left x coordinate
tly: Top Left y coordinate
brx: Bottom right x coordinate
bry: Bottom Right y coordinate
Sum between (tlx, tly) and (brx, bry) :
sum += dp[brx][bry] - dp[tlx - 1][bry] - dp[brx][tly - 1] + dp[tlx - 1][tly - 1];
Let me explain:
We first have a given matrix suppose Arr :
#define n 3
int row(n), col(n), sum(0);
int arr[row][col] = {{1, 2, 3},
{4, 5, 6},
{7, 8, 9}};
Then we will create a prefix matrix with one size greater and we will fill all values as 0 initially.
int dp[row + 1][col + 1];
memset(dp, 0, sizeof(dp));
Then will create our prefix matrix by:
Copy first row from our arr to dp
for (int j = 1; j < col + 1; j++){dp[1][j] = arr[0][j - 1];}
Run a loop and add values of first row of dp to second col of arr and store it in second col of dp
for (int i = 2; i < row + 1; i++)
{
for (int j = 1; j < col + 1; j++)
{
dp[i][j] += dp[i - 1][j] + arr[i - 1][j - 1];
}
}
Repeat this process till you fill the complete dp.
Then run a loop through col in which we will add each col to their next col and save it just like we create prefix array.
for (int i = 0; i < row + 1; i++) {
for (int j = 1; j < row + 1; j++)
{
dp[i][j] += dp[i][j - 1];
}
}
Now your dp (Prefix Matrix is ready).
arr : {{1, 2, 3},
{4, 5, 6},
{7, 8, 9}};
dp : 0 0 0 0
0 1 3 6
0 5 12 21
0 12 27 45
We took one extra size for our dp array and put value 0 in the first row and col because with this it will be easier for us to use the formula which I mentioned at above.
We divide matrices into rectangles and with the help of top left position and bottom right position we calculate the occurrences in the given matrix.
Now we just have to iterate through each position from top left to bottom right and continuously add the sum and then print it.
for (int tlx = 1; tlx < row + 1; tlx++){
for (int tly = 1; tly < col + 1; tly++){
for (int brx = tlx; brx < row + 1; brx++){
for (int bry = tly; bry < col + 1; bry++){
sum += (dp[brx][bry]) - (dp[tlx - 1][bry]) - (dp[brx][tly - 1]) + (dp[tlx - 1][tly - 1]);
}
}
}
}
OUTPUT: 500

Volume of pile of cubes

I'm trying a challenge. The idea is the following:
"Your task is to construct a building which will be a pile of n cubes.
The cube at the bottom will have a volume of n^3, the cube above will
have volume of (n-1)^3 and so on until the top which will have a
volume of 1^3.
You are given the total volume m of the building. Being given m can
you find the number n of cubes you will have to build? If no such n
exists return -1"
I saw that apparently:
2³ + 1 = 9 = 3² and 3 - 1 = 2
3³ + 2³ + 1 = 36 = 6² and 6 - 3 = 3
4³ + 3³ + 2³ + 1 = 100 = 10² and 10 - 6 = 4
5³ + 4³ + 3³ + 2³ + 1 = 225 = 15² and 15 - 10 = 5
6³ + 5³ + 4³ + 3³ + 2³ + 1 = 441 = 21² and 21 - 15 = 6
So if I thought, if I check that a certain number is a square root I can already exclude a few. Then I can start a variable at 1 at take that value (incrementing it) from the square root. The values will eventually match or the former square root will become negative.
So I wrote this code:
def find_nb(m):
x = m**0.5
if (x%1==0):
c = 1
while (x != c and x > 0):
x = x - c
c = c + 1
if (x == c):
return c
else:
return -1
return -1
Shouldn't this work? What am I missing?
I fail a third of the sample set, per example: 10170290665425347857 should be -1 and in my program it gives 79863.
Am I missing something obvious?
You're running up against a floating point precision problem. Namely, we have
In [101]: (10170290665425347857)**0.5
Out[101]: 3189089316.0
In [102]: ((10170290665425347857)**0.5) % 1
Out[102]: 0.0
and so the inner branch is taken, even though it's not actually a square:
In [103]: int((10170290665425347857)**0.5)**2
Out[103]: 10170290665425347856
If you borrow one of the many integer square root options from this question and verify that the sqrt squared gives the original number, you should be okay with your algorithm, at least if I haven't overlooked some corner case.
(Aside: you've already noticed the critical pattern. The numbers 1, 3, 6, 10, 15.. are quite famous and have a formula of their own, which you could use to solve for whether there is such a number that works directly.)
DSM's answer is the one, but to add my two cents to improve the solution...
This expression from Brilliant.org is for summing cube numbers:
sum of k**3 from k=1 to n:
n**2 * (n+1)**2 / 4
This can of course be solved for the total volume in question. This here is one of the four solutions (requiring both n and v to be positive):
from math import sqrt
def n(v):
return 1/2*(sqrt(8*sqrt(v) + 1) - 1)
But this function also returns 79863.0. Now, if we sum all the cube numbers from 1 to n, we get a slightly different result due to the precision error:
v = 10170290665425347857
cubes = n(v) # 79863
x = sum([i**3 for i in range(cubes+1)])
# x = 10170290665425347857, original
x -> 10170290665425347856
I don't know if your answer is correct, but I have another solution to this problem which is waaaay easier
def max_level(remain_volume, currLevel):
if remain_volume < currLevel ** 3:
return -1
if remain_volume == currLevel ** 3:
return currLevel
return max_level(remain_volume - currLevel**3, currLevel + 1)
And you find out the answer with max_level(m, 0). It takes O(n) time and O(1) memory.
I have found a simple solution over this in PHP as per my requirement.
function findNb($m) {
$total = 0;
$n = 0;
while($total < $m) {
$n += 1;
$total += $n ** 3;
}
return $total === $m ? $n : -1;
}
In Python it would be:
def find_nb(m):
total = 0
n = 0
while (total < m):
n = n + 1
total = total + n ** 3
return n if total == m else -1

Maximum tip calculator - naive solution

I am working through a Geekforgeeks practice question. I have come up with a naive recursive solution to the "maximum tip calculator" problem.
The problem definition is:
Restaurant recieves N orders. If Rahul takes the ith order, gain
$A[i]. If Ankit takes this order, the tip would be $B[i] One order
per person. Rahul takes max X orders. Ankit takes max Y orders.
X + Y >= N. Find out the maximum possible amount of total tip money
after processing all the orders.
Input:
The first line contains one integer, number of test cases. The second
line contains three integers N, X, Y. The third line contains N
integers. The ith integer represents Ai. The fourth line contains N
integers. The ith integer represents Bi.
Output: Print a single integer representing the maximum tip money they
would receive.
My Code and working sample:
def max_tip(N, A, B, X, Y, n= 0):
if n == len(A) or N == 0:
return 0
if X == 0 and Y > 0: # rahul cannot take more orders
return max(B[n] + max_tip(N - 1, A, B, X, Y - 1, n + 1), # ankit takes the order
max_tip(N, A, B, X, Y, n + 1)) # ankit does not take order
elif Y == 0 and X > 0: # ankit cannot take more orders
return max(A[n] + max_tip(N - 1, A, B, X - 1, Y, n + 1), # rahul takes the order
max_tip(N, A, B, X, Y, n + 1)) # rahul does not take order
elif Y == 0 and X == 0: # neither can take orders
return 0
else:
return max(A[n] + max_tip(N - 1, A, B, X - 1, Y, n + 1), # rahul takes the order
B[n] + max_tip(N - 1, A, B, X, Y - 1, n + 1), #ankit takes the order
max_tip(N, A, B, X, Y, n + 1)) # nobody takes the order
T = int(input())
for i in range(T):
nxy = [int(n) for n in input().strip().split(" ")]
N = nxy[0]
X = nxy[1]
Y = nxy[2]
A = [int(n) for n in input().strip().split(" ")]
B = [int(n) for n in input().strip().split(" ")]
print(max_tip(N, A, B, X, Y))
I've annotated my recursive call decisions. Essentially I extended the naive solution for 0-1 knapsack in another dimension two waiters, either one takes, the other takes, or both do not take the order depending on the orders left constraint.
The solution checker is complaining for the following testcase:
Input:
7 3 3
8 7 15 19 16 16 18
1 7 15 11 12 31 9
Its Correct output is:
110
And Your Code's Output is:
106
This confuses me because the optimal solution seems to be what my code is getting (19 + 16 + 18) + (7 + 15 + 31). The immediate issue seems to be that X + Y < N. My thought is my code should work for the case where X + Y < N as well.
What's going on?
you are considering the case, where nobody takes the tip. But that case doesn't exist as X+Y >= n. This java code worked for me, have a look.
private static int getMaxTip(int x, int y, int n, int[] A, int[] B) {
int[][] dp = new int[x + 1][y + 1];
dp[0][0] = 0;
for (int i = 1;i <= x;i++) {
dp[i][0] = (i <= n) ? dp[i - 1][0] + A[i - 1] : dp[i - 1][0];
}
for (int i = 1;i <= y;i++) {
dp[0][i] = (i <= n) ? dp[0][i - 1] + B[i - 1] : dp[0][i - 1];
}
for (int i = 1;i <= x;i++) {
for (int j = 1;j <= y;j++) {
if (i + j <= n) {
dp[i][j] = Math.max(dp[i - 1][j] + A[i + j - 1], dp[i][j - 1] + B[i + j - 1]);
}
}
}
int ans = Integer.MIN_VALUE;
for (int i = 0;i <= x;i++) {
for (int j = 0;j <= y;j++) {
if (i + j == n) {
ans = Math.max(ans, dp[i][j]);
}
}
}
return ans;
}
You are considering a case when nobody takes the order that should not be considered as it is mentioned in the question that x+y>=n always.Removing that condition will work.
I am assuming, this is your source of question:
https://practice.geeksforgeeks.org/problems/maximum-tip-calculator/0
Here is my solution written in Python that passed all case:
https://github.com/Madhu-Guddana/My-Solutions/blob/master/adhoc/max_tip.py
Explanation:
zip corresponding element of tips and create new array.
Sort the new array based on difference amount value for Rahul and Ankit,
Then we can safely consider the elements from 2 ends of the array, which ever end is giving more profit, add the value to count.

Primitive Calculator - Dynamic Approach

I'm having some trouble getting the correct solution for the following problem:
Your goal is given a positive integer n, find the minimum number of
operations needed to obtain the number n starting from the number 1.
More specifically the test case I have in the comments below.
# Failed case #3/16: (Wrong answer)
# got: 15 expected: 14
# Input:
# 96234
#
# Your output:
# 15
# 1 2 4 5 10 11 22 66 198 594 1782 5346 16038 16039 32078 96234
# Correct output:
# 14
# 1 3 9 10 11 22 66 198 594 1782 5346 16038 16039 32078 96234
# (Time used: 0.10/5.50, memory used: 8601600/134217728.)
def optimal_sequence(n):
sequence = []
while n >= 1:
sequence.append(n)
if n % 3 == 0:
n = n // 3
optimal_sequence(n)
elif n % 2 == 0:
n = n // 2
optimal_sequence(n)
else:
n = n - 1
optimal_sequence(n)
return reversed(sequence)
input = sys.stdin.read()
n = int(input)
sequence = list(optimal_sequence(n))
print(len(sequence) - 1)
for x in sequence:
print(x, end=' ')
It looks like I should be outputting 9 where I'm outputting 4 & 5 but I'm not sure why this isn't the case. What's the best way to troubleshoot this problem?
You are doing a greedy approach.
When n == 10, you check and see if it's divisible by 2 assuming that's the best step, which is wrong in this case.
What you need to do is proper dynamic programming. v[x] will hold the minimum number of steps to get to result x.
def solve(n):
v = [0]*(n+1) # so that v[n] is there
v[1] = 1 # length of the sequence to 1 is 1
for i in range(1,n):
if not v[i]: continue
if v[i+1] == 0 or v[i+1] > v[i] + 1: v[i+1] = v[i] + 1
# Similar for i*2 and i*3
solution = []
while n > 1:
solution.append(n)
if v[n-1] == v[n] - 1: n = n-1
if n%2 == 0 and v[n//2] == v[n] -1: n = n//2
# Likewise for n//3
solution.append(1)
return reverse(solution)
One more solution:
private static List<Integer> optimal_sequence(int n) {
List<Integer> sequence = new ArrayList<>();
int[] arr = new int[n + 1];
for (int i = 1; i < arr.length; i++) {
arr[i] = arr[i - 1] + 1;
if (i % 2 == 0) arr[i] = Math.min(1 + arr[i / 2], arr[i]);
if (i % 3 == 0) arr[i] = Math.min(1 + arr[i / 3], arr[i]);
}
for (int i = n; i > 1; ) {
sequence.add(i);
if (arr[i - 1] == arr[i] - 1)
i = i - 1;
else if (i % 2 == 0 && (arr[i / 2] == arr[i] - 1))
i = i / 2;
else if (i % 3 == 0 && (arr[i / 3] == arr[i] - 1))
i = i / 3;
}
sequence.add(1);
Collections.reverse(sequence);
return sequence;
}
List<Integer> sequence = new ArrayList<Integer>();
while (n>0) {
sequence.add(n);
if (n % 3 == 0&&n % 2 == 0)
n=n/3;
else if(n%3==0)
n=n/3;
else if (n % 2 == 0&& n!=10)
n=n/2;
else
n=n-1;
}
Collections.reverse(sequence);
return sequence;
Here's my Dynamic programming (bottom-up & memoized)solution to the problem:
public class PrimitiveCalculator {
1. public int minOperations(int n){
2. int[] M = new int[n+1];
3. M[1] = 0; M[2] = 1; M[3] = 1;
4. for(int i = 4; i <= n; i++){
5. M[i] = M[i-1] + 1;
6. M[i] = Math.min(M[i], (i %3 == 0 ? M[i/3] + 1 : (i%3 == 1 ? M[(i-1)/3] + 2 : M[(i-2)/3] + 3)));
7. M[i] = Math.min(M[i], i%2 == 0 ? M[i/2] + 1: M[(i-1)/2] + 2);
8. }
9. return M[n];
10. }
public static void main(String[] args) {
System.out.println(new PrimitiveCalculator().minOperations(96234));
}
}
Before going ahead with the explanation of the algorithm I would like to add a quick disclaimer:
A DP solution is not reached at first attempt unless you have good
experience solving lot of DP problems.
Approach to solving through DP
If you are not comfortable with DP problems then the best approach to solve the problem would be following:
Try to get a working brute-force recursive solution.
Once we have a recursive solution, we can look for ways to reduce the recursive step by adding memoization, where in we try remember the solution to the subproblems of smaller size already solved in a recursive step - This is generally a top-down solution.
After memoization, we try to flip the solution around and solve it bottom up (my Java solution above is a bottom-up one)
Once you have done above 3 steps, you have reached a DP solution.
Now coming to the explanation of the solution above:
Given a number 'n' and given only 3 operations {+1, x2, x3}, the minimum number of operations needed to reach to 'n' from 1 is given by recursive formula:
min_operations_to_reach(n) = Math.min(min_operations_to_reach(n-1), min_operations_to_reach(n/2), min_operations_to_reach(n/3))
If we flip up the memoization process and begin with number 1 itself then the above code starts to make better sense.
Starting of with trivial cases of 1, 2, 3
min_operations_to_reach(1) = 0 because we dont need to do any operation.
min_operations_to_reach(2) = 1 because we can either do (1 +1) or (1 x2), in either case number of operations is 1.
Similarly, min_operations_to_reach(3) = 1 because we can multiply 1 by 3 which is one operation.
Now taking any number x > 3, the min_operations_to_reach(x) is the minimum of following 3:
min_operations_to_reach(x-1) + 1 because whatever is the minimum operations to reach (x-1) we can add 1 to it to get the operation count to make it number x.
Or, if we consider making number x from 1 using multiplication by 3 then we have to consider following 3 options:
If x is divisible by 3 then min_operations_to_reach(x/3) + 1,
if x is not divisible by 3 then x%3 can be 1, in which case its min_operations_to_reach((x-1)/3) + 2, +2 because one operation is needed to multiply by 3 and another operation is needed to add 1 to make the number 'x'
Similarly if x%3 == 2, then the value will be min_operations_to_reach((x-2)/3) + 3. +3 because 1 operation to multiply by 3 and then add two 1s subsequently to make the number x.
Or, if we consider making number x from 1 using multiplication by 2 then we have to consider following 2 options:
if x is divisible by 2 then its min_operations_to_reach(x/2) + 1
if x%2 == 1 then its min_operations_to_reach((x-1)/2) + 2.
Taking the minimum of above 3 we can get the minimum number of operations to reach x. Thats what is done in code above in lines 5, 6 and 7.
def DPoptimal_sequence(n,operations):
MinNumOperations=[0]
l_no=[]
l_no2=[]
for i in range(1,n+1):
MinNumOperations.append(None)
for operation in operations:
if operation==1:
NumOperations=MinNumOperations[i-1]+1
if operation==2 and i%2==0:
NumOperations=MinNumOperations[int(i/2)]+1
if operation==3 and i%3==0:
NumOperations=MinNumOperations[int(i/3)]+1
if MinNumOperations[i]==None:
MinNumOperations[i]=NumOperations
elif NumOperations<MinNumOperations[i]:
MinNumOperations[i]=NumOperations
if MinNumOperations[i] == MinNumOperations[i-1]+1:
l_no2.append((i,i-1))
elif MinNumOperations[i] == MinNumOperations[int(i/2)]+1 and i%2 == 0:
l_no2.append((i,int(i/2)))
elif MinNumOperations[i] == MinNumOperations[int(i/3)]+1 and i%3 == 0:
l_no2.append((i,int(i/3)))
l_no.append((i,MinNumOperations[i]-1))
#print(l_no)
#print(l_no2)
x=MinNumOperations[n]-1
#print('x',x)
l_no3=[n]
while n>1:
a,b = l_no2[n-1]
#print(a,b)
if b == a-1:
n = n-1
#print('1111111111111')
#print('n',n)
l_no3.append(n)
elif b == int(a/2) and a%2==0:
n = int(n/2)
#print('22222222222222222')
#print('n',n)
l_no3.append(n)
elif b == int(a/3) and a%3==0:
n = int(n/3)
#print('333333333333333')
#print('n',n)
l_no3.append(n)
#print(l_no3)
return x,l_no3
def optimal_sequence(n):
hop_count = [0] * (n + 1)
hop_count[1] = 1
for i in range(2, n + 1):
indices = [i - 1]
if i % 2 == 0:
indices.append(i // 2)
if i % 3 == 0:
indices.append(i // 3)
min_hops = min([hop_count[x] for x in indices])
hop_count[i] = min_hops + 1
ptr = n
optimal_seq = [ptr]
while ptr != 1:
candidates = [ptr - 1]
if ptr % 2 == 0:
candidates.append(ptr // 2)
if ptr % 3 == 0:
candidates.append(ptr // 3)
ptr = min(
[(c, hop_count[c]) for c in candidates],
key=lambda x: x[1]
)[0]
optimal_seq.append(ptr)
return reversed(optimal_seq)
private int count(int n, Map<Integer, Integer> lookup) {
if(lookup.containsKey(n)) {
return lookup.get(n);
}
if(n==1) {
return 0;
} else {
int result;
if(n%2==0 && n%3==0) {
result =1+
//Math.min(count(n-1, lookup),
Math.min(count(n/2, lookup),
count(n/3, lookup));
} else if(n%2==0) {
result = 1+ Math.min(count(n-1, lookup),
count(n/2, lookup));
} else if(n%3==0) {
result = 1+ Math.min(count(n-1, lookup), count(n/3, lookup));
} else {
result = 1+ count(n-1, lookup);
}
//System.out.println(result);
lookup.put(n, result);
return result;
}
}

Categories