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.
Related
I have integer input: 0 < a, K, N < 10^9
I need to find all b numbers that satisfy:
a + b <= N
(a + b) % K = 0
For example: 10 6 40 -> [2, 8, 14, 20, 26]
I tried a simple brute force and failed (Time Limit Exceeded). Can anyone suggest answer? Thanks
a, K, N = [int(x) for x in input().split()]
count = 0
b = 1
while (a + b <= N):
if ((a + b) % K) == 0:
count+=1
print(b, end=" ")
b+=1
if (count == 0):
print(-1)
The first condition is trivial in the sense that it just poses an upper limit on b. The second condition can be rephrased using the definition of % as
a + b = P * K
For some arbitrary integer P. From this, is simple to compute the smallest b by finding the smallest P that gives you a positive result for P * K - a. In other words
P * K - a >= 0
P * K >= a
P >= a / K
P = ceil(a / K)
So you have
b0 = ceil(a / K) * K - a
b = range(b0, N + 1, K)
range is a generator, so it won't compute the values up front. You can force that by doing list(b).
At the same time, if you only need the count of elements, range objects will do the math on the limits and step size for you conveniently, all without computing the actual values, so you can just do len(b).
To find the list of bs, you can use some maths. First, we note that (a + b) % K is equivalent to a % K + b % K. Also when n % K is 0, that means that n is a multiple of K. So the smallest value of b is n * K - a for the smallest value of n where this calculation is still positive. Once you find that value, you can simply add K repeatedly to find all other values of b.
b = k - a%k
Example: a=19, k=11, b = 11-19%11 = 11-8 =3
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
Examples,
1.Input=4
Output=111
Explanation,
1 = 1³(divisors of 1)
2 = 1³ + 2³(divisors of 2)
3 = 1³ + 3³(divisors of 3)
4 = 1³ + 2³ + 4³(divisors of 4)
------------------------
sum = 111(output)
1.Input=5
Output=237
Explanation,
1 = 1³(divisors of 1)
2 = 1³ + 2³(divisors of 2)
3 = 1³ + 3³(divisors of 3)
4 = 1³ + 2³ + 4³(divisors of 4)
5 = 1³ + 5³(divisors of 5)
-----------------------------
sum = 237 (output)
x=int(raw_input().strip())
tot=0
for i in range(1,x+1):
for j in range(1,i+1):
if(i%j==0):
tot+=j**3
print tot
Using this code I can find the answer for small number less than one million.
But I want to find the answer for very large numbers. Is there any algorithm
for how to solve it easily for large numbers?
Offhand I don't see a slick way to make this truly efficient, but it's easy to make it a whole lot faster. If you view your examples as matrices, you're summing them a row at a time. This requires, for each i, finding all the divisors of i and summing their cubes. In all, this requires a number of operations proportional to x**2.
You can easily cut that to a number of operations proportional to x, by summing the matrix by columns instead. Given an integer j, how many integers in 1..x are divisible by j? That's easy: there are x//j multiples of j in the range, so divisor j contributes j**3 * (x // j) to the grand total.
def better(x):
return sum(j**3 * (x // j) for j in range(1, x+1))
That runs much faster, but still takes time proportional to x.
There are lower-level tricks you can play to speed that in turn by constant factors, but they still take O(x) time overall. For example, note that x // j == 1 for all j such that x // 2 < j <= x. So about half the terms in the sum can be skipped, replaced by closed-form expressions for a sum of consecutive cubes:
def sum3(x):
"""Return sum(i**3 for i in range(1, x+1))"""
return (x * (x+1) // 2)**2
def better2(x):
result = sum(j**3 * (x // j) for j in range(1, x//2 + 1))
result += sum3(x) - sum3(x//2)
return result
better2() is about twice as fast as better(), but to get faster than O(x) would require deeper insight.
Quicker
Thinking about this in spare moments, I still don't have a truly clever idea. But the last idea I gave can be carried to a logical conclusion: don't just group together divisors with only one multiple in range, but also those with two multiples in range, and three, and four, and ... That leads to better3() below, which does a number of operations roughly proportional to the square root of x:
def better3(x):
result = 0
for i in range(1, x+1):
q1 = x // i
# value i has q1 multiples in range
result += i**3 * q1
# which values have i multiples?
q2 = x // (i+1) + 1
assert x // q1 == i == x // q2
if i < q2:
result += i * (sum3(q1) - sum3(q2 - 1))
if i+1 >= q2: # this becomes true when i reaches roughly sqrt(x)
break
return result
Of course O(sqrt(x)) is an enormous improvement over the original O(x**2), but for very large arguments it's still impractical. For example better3(10**6) appears to complete instantly, but better3(10**12) takes a few seconds, and better3(10**16) is time for a coffee break ;-)
Note: I'm using Python 3. If you're using Python 2, use xrange() instead of range().
One more
better4() has the same O(sqrt(x)) time behavior as better3(), but does the summations in a different order that allows for simpler code and fewer calls to sum3(). For "large" arguments, it's about 50% faster than better3() on my box.
def better4(x):
result = 0
for i in range(1, x+1):
d = x // i
if d >= i:
# d is the largest divisor that appears `i` times, and
# all divisors less than `d` also appear at least that
# often. Account for one occurence of each.
result += sum3(d)
else:
i -= 1
lastd = x // i
# We already accounted for i occurrences of all divisors
# < lastd, and all occurrences of divisors >= lastd.
# Account for the rest.
result += sum(j**3 * (x // j - i)
for j in range(1, lastd))
break
return result
It may be possible to do better by extending the algorithm in "A Successive Approximation Algorithm for Computing the Divisor Summatory Function". That takes O(cube_root(x)) time for the possibly simpler problem of summing the number of divisors. But it's much more involved, and I don't care enough about this problem to pursue it myself ;-)
Subtlety
There's a subtlety in the math that's easy to miss, so I'll spell it out, but only as it pertains to better4().
After d = x // i, the comment claims that d is the largest divisor that appears i times. But is that true? The actual number of times d appears is x // d, which we did not compute. How do we know that x // d in fact equals i?
That's the purpose of the if d >= i: guarding that comment. After d = x // i we know that
x == d*i + r
for some integer r satisfying 0 <= r < i. That's essentially what floor division means. But since d >= i is also known (that's what the if test ensures), it must also be the case that 0 <= r < d. And that's how we know x // d is i.
This can break down when d >= i is not true, which is why a different method needs to be used then. For example, if x == 500 and i == 51, d (x // i) is 9, but it's certainly not the case that 9 is the largest divisor that appears 51 times. In fact, 9 appears 500 // 9 == 55 times. While for positive real numbers
d == x/i
if and only if
i == x/d
that's not always so for floor division. But, as above, the first does imply the second if we also know that d >= i.
Just for Fun
better5() rewrites better4() for about another 10% speed gain. The real pedagogical point is to show that it's easy to compute all the loop limits in advance. Part of the point of the odd code structure above is that it magically returns 0 for a 0 input without needing to test for that. better5() gives up on that:
def isqrt(n):
"Return floor(sqrt(n)) for int n > 0."
g = 1 << ((n.bit_length() + 1) >> 1)
d = n // g
while d < g:
g = (d + g) >> 1
d = n // g
return g
def better5(x):
assert x > 0
u = isqrt(x)
v = x // u
return (sum(map(sum3, (x // d for d in range(1, u+1)))) +
sum(x // i * i**3 for i in range(1, v)) -
u * sum3(v-1))
def sum_divisors(n):
sum = 0
i = 0
for i in range (1, n) :
if n % i == 0 and n != 0 :
sum = sum + i
# Return the sum of all divisors of n, not including n
return sum
print(sum_divisors(0))
# 0
print(sum_divisors(3)) # Should sum of 1
# 1
print(sum_divisors(36)) # Should sum of 1+2+3+4+6+9+12+18
# 55
print(sum_divisors(102)) # Should be sum of 2+3+6+17+34+51
# 114
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;
}
}
Say I have a 1D array x with positive and negative values in Python, e.g.:
x = random.rand(10) * 10
For a given positive value of K, I would like to find the offset c that makes the sum of positive elements of the array y = x + c equal to K.
How can I solve this problem efficiently?
How about binary search to determine which elements of x + c are going to contribute to the sum, followed by solving the linear equation? The running time of this code is O(n log n), but only O(log n) work is done in Python. The running time could be dropped to O(n) via a more complicated partitioning strategy. I'm not sure whether a practical improvement would result.
import numpy as np
def findthreshold(x, K):
x = np.sort(np.array(x))[::-1]
z = np.cumsum(np.array(x))
l = 0
u = x.size
while u - l > 1:
m = (l + u) // 2
if z[m] - (m + 1) * x[m] >= K:
u = m
else:
l = m
return (K - z[l]) / (l + 1)
def test():
x = np.random.rand(10)
K = np.random.rand() * x.size
c = findthreshold(x, K)
assert np.abs(K - np.sum(np.clip(x + c, 0, np.inf))) / K <= 1e-8
Here's a randomized expected O(n) variant. It's faster (on my machine, for large inputs), but not dramatically so. Watch out for catastrophic cancellation in both versions.
def findthreshold2(x, K):
sumincluded = 0
includedsize = 0
while x.size > 0:
pivot = x[np.random.randint(x.size)]
above = x[x > pivot]
if sumincluded + np.sum(above) - (includedsize + above.size) * pivot >= K:
x = above
else:
notbelow = x[x >= pivot]
sumincluded += np.sum(notbelow)
includedsize += notbelow.size
x = x[x < pivot]
return (K - sumincluded) / includedsize
You can sort x in descending order, loop over x and compute the required c thus far. If the next element plus c is positive, it should be included in the sum, so c gets smaller.
Note that it might be the case that there is no solution: if you include elements up to m, c is such that m+1 should also be included, but when you include m+1, c decreases and a[m+1]+c might get negative.
In pseudocode:
sortDescending(x)
i = 0, c = 0, sum = 0
while i < x.length and x[i] + c >= 0
sum += x[i]
c = (K - sum) / i
i++
if i == 0 or x[i-1] + c < 0
#no solution
The running time is obviously O(n log n) because it is dominated by the initial sort.