One coding problem two different solutions, how to prove is correct? - algorithm

I have a coding problem:
The awards committee of your alma mater (i.e. your college/university) asked for your assistance with a budget allocation problem they’re facing. Originally, the committee planned to give N research grants this year. However, due to spending cutbacks, the budget was reduced to newBudget dollars and now they need to reallocate the grants. The committee made a decision that they’d like to impact as few grant recipients as possible by applying a maximum cap on all grants. Every grant initially planned to be higher than cap will now be exactly cap dollars. Grants less or equal to cap, obviously, won’t be impacted.
Given an array grantsArray of the original grants and the reduced budget newBudget, write a function findGrantsCap that finds in the most efficient manner a cap such that the least number of recipients is impacted and that the new budget constraint is met (i.e. sum of the N reallocated grants equals to newBudget).
Analyse the time and space complexities of your solution.
Example:
input: grantsArray = [2, 100, 50, 120, 1000], newBudget = 190
output: 47
The recommended solution is:
fun findCorrectGrantsCap(grantsArray: DoubleArray, newBudget: Double): Double {
grantsArray.sortDescending()
val grantsArray = grantsArray + 0.0
var surplus = grantsArray.sum() - newBudget
if (surplus <= 0)
return grantsArray[0]
var lastIndex = 0
for(i in 0 until grantsArray.lastIndex) {
lastIndex = i
surplus -= (i+1) * (grantsArray[i] - grantsArray[i+1])
if (surplus <= 0)
break
}
return grantsArray[lastIndex+1] + (-surplus / (lastIndex.toDouble()+1))
}
Compact and complexity is O(nlogn)
I came across with O(n) solution with tiny fractional part difference in the result between suggested solution and my one:
fun DoubleArray.calcSumAndCount(averageCap: Double, round: Boolean): Pair<Double, Int> {
var count = 0
var sum = 0.0
forEach {
if(round && it > round(averageCap))
count++
else if(!round && it > averageCap)
count++
else
sum+=it
}
return sum to count
}
fun Pair<Double, Int>.calcCap(budget: Double) =
(budget-first)/second
fun findGrantsCap(grantsArray: DoubleArray, newBudget: Double): Double {
if(grantsArray.isEmpty())
return 0.0
val averageCap = newBudget/grantsArray.size
if(grantsArray.sum() <= newBudget)
return grantsArray.maxOf { it }
var sumAndCount = grantsArray.calcSumAndCount(averageCap, false)
val cap = sumAndCount.calcCap(newBudget)
val finalSum = grantsArray.sumOf {
if(it > cap)
cap
else it
}
return if(finalSum == newBudget)
cap
else
grantsArray
.calcSumAndCount(averageCap, true)
.calcCap(newBudget)
}
I wonder if any test case to prove that my solution incorrect or vice versa is correct since provided approaches to solve this coding problem completely different.
Original source doesn't provide reach test cases.
UPDATE
As PaulHankin suggested I wrote simple test:
repeat(1000000) {
val grants = (0..Random.nextInt(6)).map { Random.nextDouble(0.0, 9000000000.0) }.toDoubleArray()
val newBudget = Random.nextDouble(0.0, 9000000000.0)
val cap1 = findCorrectGrantsCap(grants, newBudget)
val cap2 = findGrantsCap(grants, newBudget)
if (abs(cap1 - cap2) > .00001)
println("FAILED: $cap1 != $cap2 (${grants.joinToString()}), $newBudget")
}
And it's failed he is right. But when I redesigned my solution:
fun findGrantsCap(grantsArray: DoubleArray, newBudget: Double): Double {
if(grantsArray.isEmpty())
return 0.0
if(grantsArray.sum() <= newBudget)
return grantsArray.maxOf { it }
grantsArray.sort()
var size = grantsArray.size
var averageCap = newBudget/size
var tempBudget = newBudget
for(grant in grantsArray) {
if(grant <= averageCap) {
size--
tempBudget -= grant
averageCap = tempBudget/size
} else break
}
return averageCap
}
After that the test cases pass successfully, the only problem with double precision/overflow error if I use large Doubles if increase limits of input for grants and/or budget (it can be fixed using BigDecimal instead for large inputs).
So the latest solution is correct now? Or is still can be some test cases where it can be failed?

Related

Converting a math problem to dynamic programming

Currently I'm working on an optimization problem for a course I'm doing with a fellow student. It's basically described by three equations.
Where n is an index taking values between 1 and 1180, Pr is a known vector (meaning all values of this vector are known and constant) and we have to find the vector Ps that results in the minimum value of Ef[1180].
Logically, the answer would be to set all values of Ps[n] to infinity. However, there are a few constraints:
Furthermore, the values of Es and Ps must always be a multiple of 1,000 to decrease the state space.
The above is what we figured out from the assignment description. However, we can't seem to figure out how to solve this as a dynamic programming problem. There are lots of examples around for going from a set of equations to a dynamic programming problem. However, those examples all have two or three inputs and use a 2- or 3-dimensional dictionary resp. to facilitate data reuse. We essentially have 1180 inputs. Creating an 1180-dimensional dictionary is not feasible
We tried constituting Bellman equations for this problem, but the professor told us this is wrong. Then we considered brute forcing the state space, but this is an insane job since there are 43^1180 possible combinations of input vectors P_s. Some of our fellow students advised us to checkout the checkerboard example on this wikipedia page:
Wikipedia page on dynamic programming
However, this example seems to traverse through the checkerboard only once. The usage of a cost function would always pick the highest possible value for Ps[n] to minimize Ef[n]. However, to do pick such a positive value we must have Es[n] > 0 which can only happen when previous elements of Ps[i] for i < n take negative values. But the cost function will prevent Ps from having negative values. Since the cost function does not allow negative values and the Es[n] >= 0 constraint does not allow negative values, this will result in a Ps containing only zeroes, which certainly does not result in the lowest value of Ef[1180].
Any hints on how to continue would be nice. We have been staring at this problem for days now and we are completely lost at this point.
You want to minimize E[1180] idem maximize f defined below:
f(P) = \sigma_{i=1}^{1180} P_i
under constraint:
forall n <= 1180
-6.5*10^5 <= \sum_{i=1}^n P_i <= 0
Recurrence formula be like
f(i, sumPs, v) {
if i == 1180
return { s: sumPs, solution: v }
res = { s: -infinity, solution: [] }
# Pr(i) > Ps(i)
for psi in -21:min(Pr[i], 21)
# Es(n-1) = - sumPs
if psi <= -sumPs
tmp = f(i+1, sumPs + psi, v+[psi])
if tmp.s > res.s
res.s = tmp.s
res.v = tmp.v
return res
}
f(0, 0, [])
Dynamic approach be similar:
Initialize the first layer: an associative array for sumPs as key and {s:sum, v:facultative} as value
We could actually just store nothing as value and use a set (stocking the sums), but it is convenient for debugging purpose
initialiaztion
for psi in -21:min(Pr[0], 0)
layer[psi] = {s: psi, v:[psi]}
To build layer i+1, you only need layer i
for i = 2:1180
nextLayer = []
for psi in range(-21, min(Pr[i-1], 21))
for candidate in layer:
if psi <= -candidate.s
maybe = candidate.s + psi
if !nextLayer[maybe]
nextLayer[maybe] = {s: maybe, v:v+[psi]}
layer = nextLayer
NB: I have not handled the 1000 factor, but that should not be a problem
const Pr = [-10,2,-2,4,-1]
function f(i, sumPs, v) {
if (i == 5) {
return { s: sumPs, v }
}
let res = { s: -1e12, v: [] }
for (let psi = -21; psi<=Math.min(Pr[i], 21); ++psi) {
if (psi <= -sumPs) {
let tmp = f(i+1, sumPs + psi, v.concat(psi))
if (tmp.s > res.s) {
res.s = tmp.s
res.v = tmp.v
}
}
}
return res
}
function dp(n, pr){
let layer = new Map
for (let psi = -21; psi <= Math.min(Pr[0], 0); ++psi) {
layer.set(psi, {s: psi, v:[psi]})
}
for (let i = 2; i <= n; ++i) {
let nextLayer = new Map
for (let psi = -21; psi <= Math.min(pr[i-1], 21); ++psi) {
for (let [k, candidate] of layer) {
if (psi <= -candidate.s) {
const maybe = candidate.s + psi
if (!nextLayer.has(maybe)) {
nextLayer.set(maybe, { s: maybe, v: candidate.v.concat(psi) })
}
}
}
}
layer = nextLayer
}
return [...layer.entries()].sort((a,b) => b[0] - a[0])[0][1]
}
console.log(f(0,0,[]))
console.log(dp(5,Pr))

Egg Dropping Puzzle - Suggestion needed

Problem Statement
Egg dropping refers to a class of problems in which it is important to find the correct response without exceeding a (low) number of certain failure states. In a toy example, there is a tower of floors, and an egg dropper with ideal eggs. The physical properties of the ideal egg is such that it will shatter if it is dropped from floor or above, and will have no damage whatsoever if it is dropped from floor or below. The problem is to find a strategy such that the egg dropper can determine the floor in as few egg drops as possible. This problem has many applications in the real world such as avoiding a call out to the slow HDD, or attempting to minimize cache misses, or running a large number of expensive queries on a database.
Problem Statement and Solution Analysis
When we have N number of eggs and K number of floors the following code finds the minimum number of drops using quadratic equation with time complexity of O(N).
(function() {
var eggs = 3, floors = 2;
function findFloor(eggs, floors) {
if (eggs === 1 || floors === 0 || floors === 1) {
return floors;
}
var minDrops = Math.ceil((-1 + Math.sqrt(1 + (8 * floors))) / 2);
return Math.min(minDrops, findFloor(eggs - 1, minDrops));
}
console.log(findFloor(eggs, floors));
})();
I have tested with some test cases but can anyone suggest, will this work for all the scenarios?
No, this will not always produce the correct results. You have used this formula:
But that formula only provides a meaningful result in case the number of eggs is two. Note how the number of eggs is not appearing in it, only the number of floors ( k ).
Counter example
Take for instance the case with 4 floors and 3 eggs. Your function returns 2, but if that were the correct answer, then which floors would you pick in those two attempts?
Let's drop from floor 3: egg breaks. Then throw from floor 1: egg does not break. Now we don't know whether the answer is floor 1 or 2. We would need to drop one more egg to be sure.
Maybe start at floor 2?: egg is OK. Then throw from floor 4: egg breaks. Now we don't know whether the answer is floor 2 or 3. We would need to drop one more egg to be sure.
So, in the worst case we need to drop at least 3 eggs.
Conclusion
Your algorithm is not correct. The article you refer two has correct implementations (although there are some typos with variable names). Here they are in JavaScript:
function getNumDropsRecursive(eggs, floors) {
if (eggs == 1 || floors == 0 || floors == 1) {
return floors
}
let minimum = Infinity;
for (let floor = 1; floor <= floors; floor++) {
minimum = Math.min(
minimum,
1 + Math.max(getNumDropsRecursive(eggs - 1, floor - 1),
getNumDropsRecursive(eggs, floors - floor))
)
}
return minimum;
}
function getNumDropsDP(eggs, floors) {
const numdrops = [
null,
[...Array(floors+1).keys()],
...Array.from(Array(eggs-1), _ => [0, 1])
];
for (let remainingEggs = 2; remainingEggs <= eggs; remainingEggs++) {
for (let choices = 2; choices <= floors; choices++) {
let minimum = Infinity;
for (let dropAt = 1; dropAt <= choices; dropAt++) {
minimum = Math.min(minimum,
1 + Math.max(numdrops[remainingEggs-1][dropAt-1],
numdrops[remainingEggs][choices-dropAt])
);
}
numdrops[remainingEggs][choices] = minimum;
}
}
return numdrops[eggs][floors];
}
Using the first one is not advised as it starts to get really slow with arguments above 20.
I would also name your function differently. The function does not find a floor, but the number of drops you need in the worst case to find the floor. So a name like getNumDrops would be more telling.
I believe the known solution is O(n log k). Here are some mismatches:
/*
W(n,k) = 1 + min{max(W(n − 1, x − 1), W(n,k − x)): x = 1, 2, ..., k }
with W(n,0) = 0 for all n > 0 and W(1,k) = k for all k.
*/
function f(n,k){
if (k == 0 && n > 0)
return 0;
if (n == 1)
return k;
let best = Infinity;
for (let x=1; x<=k; x++)
best = Math.min(best, Math.max(f(n-1, x-1), f(n, k-x)));
return 1 + best;
}
function findFloor(eggs, floors) {
if (eggs === 1 || floors === 0 || floors === 1) {
return floors;
}
var minDrops = Math.ceil((-1 + Math.sqrt(1 + (8 * floors))) / 2);
return Math.min(minDrops, findFloor(eggs - 1, minDrops));
}
for (let i=1; i<10; i++){
for (let j=1; j<10; j++){
let a = f(i,j);
let b = findFloor(i,j);
if (a != b){
console.log(`n,k: ${i},${j}; f: ${a}; findFloors: ${b}`);
}
}
}

Scheduling algorithm for a tournament with large number of participants?

Say, we have 100+ participants and 3 winning places. I need to schedule as least matches as possible to find 3 winners. The rest of places doesn't matter at all.
Round-robin algorithm looks unnecessary expensive.
Here is my solution:
const match = (a, b) => {
if (!a || !b) {
return {winner: a || b}
}
// simulate match
const winner = Math.random() > 0.5 ? a : b
console.log(`Match between ${a} and ${b}: ${winner} wins`)
return {winner, looser: winner === a ? b : a}
}
let participants = {
// [id]: {win: Number, loose: Number}
}
let participantsNumber = 100
let n = 0
// create random participants
while(n < participantsNumber) {
n++
participants[String(n)] = {win: 0, loose: 0}
}
let round = 0
while(participantsNumber > 3) {
let odd = true
let matches = []
_.map(participants, (winLooseStats, id) => {
if (odd) {
odd = false
matches.push({player_1: id})
} else {
odd = true
let opponentFound = false
matches.map(match => {
if (!match.player_2 && !opponentFound) {
opponentFound = true
match.player_2 = id
}
})
}
})
console.log('matches', matches)
// run matches
matches.forEach(({player_1, player_2}) => {
const {winner, looser} = match(player_1, player_2)
participants[winner].win++
if (looser) {
participants[looser].loose++
}
})
// remove those, who has lost 3 times
_.map(participants, ({win, loose}, id) => {
if (loose > 2) {
console.log(`Player ${id} has lose 3 times`)
delete participants[id]
participantsNumber--
}
})
round++
console.log(`Round ${round} complete. ${participantsNumber} players left`)
}
console.log(`3 champions: ${_.map(participants, (wl, id) => id).join(', ')}`)
JSFIDDLE
~12 rounds per 100 participants. Is it possible to decrease number of rounds?
The question is a bit vague, but I think your rules are as follows:
Any pair of players plays no more than one match. If A beats B we assume A will always beat B.
We assume a transitive property: if A beats B and B beats C the we can assume A beats C and we don't have to schedule a match to find that out.
Assuming that's correct and you have n players then you can solve this optimally using a standard single elimination tournament to find the winner and 2nd place. To find 3rd place you have to add one more match between the two people who didn't make it to the final match.

ArrayIndexOutofBoundsException in Knapsack Scala

I am trying to solve Knapsack problem in Scala using dynamic programming .As a part of requirement I also need to show which items are picked to be filled in Knapsack.But I am getting "ArrayIndexOutOfBoundException".
And so far what I have code is like :
availableMoney is equivalent to weight of knapsack.products.channels is equivalent to value[] in knapsack.products.price is equivalent to weight[] in knapsack.
def knapSack(availableMoney: Int, products: List[Product]) : Int = {
var wt = List[Int](products.length)
var value = List[Int](products.length)
for (product <- products) {
value ::= product.channels.length
wt ::= product.price
}
val matrix = Array.fill(2, 2)(0)
val picks = Array.fill(2, 2)(0)
for (i <- 1 to products.length){
for (j <- 0 to availableMoney){
if (wt(i-1)<=j){
matrix(i)(j) = max(matrix(i-1)(j),value(i-1)+matrix(i-1)(j-wt(i-1)));
if (value(i-1)+matrix(i-1)(j-wt(i-1))>matrix(i-1)(j))
picks(i)(j)= 1;
else
picks(i)(j)= -1;
}
else{
picks(i)(j) = -1;
matrix(i)(j) = matrix(i-1)(j);
}
}
}
matrix(products.length)(availableMoney)
}
There are a couple of issues I think:
j runs from 0 to availableMoney, and is then used as an index into picks and matrix which have been initialised to specific sizes, so if availableMoney exceeds those dimensions, it will fail
i runs from 1 to products.length but is also used as an index into picks and matrix, so will miss 0 and if there are more products than the second dimension size, it will fail
Use some println debugging to check more closely what is going on. Looks like an interesting algorithm. Post us a solution once you get it working :)

Algorithm: Determine if a combination of min/max values fall within a given range

Imagine you have 3 buckets, but each of them has a hole in it. I'm trying to fill a bath tub. The bath tub has a minimum level of water it needs and a maximum level of water it can contain. By the time you reach the tub with the bucket it is not clear how much water will be in the bucket, but you have a range of possible values.
Is it possible to adequately fill the tub with water?
Pretty much you have 3 ranges (min,max), is there some sum of them that will fall within a 4th range?
For example:
Bucket 1 : 5-10L
Bucket 2 : 15-25L
Bucket 3 : 10-50L
Bathtub 100-150L
Is there some guaranteed combination of 1 2 and 3 that will fill the bathtub within the requisite range? Multiples of each bucket can be used.
EDIT: Now imagine there are 50 different buckets?
If the capacity of the tub is not very large ( not greater than 10^6 for an example), we can solve it using dynamic programming.
Approach:
Initialization: memo[X][Y] is an array to memorize the result. X = number of buckets, Y = maximum capacity of the tub. Initialize memo[][] with -1.
Code:
bool dp(int bucketNum, int curVolume){
if(curVolume > maxCap)return false; // pruning extra branches
if(curVolume>=minCap && curVolume<=maxCap){ // base case on success
return true;
}
int &ret = memo[bucketNum][curVolume];
if(ret != -1){ // this state has been visited earlier
return false;
}
ret = false;
for(int i = minC[bucketNum]; i < = maxC[bucketNum]; i++){
int newVolume = curVolume + i;
for(int j = bucketNum; j <= 3; j++){
ret|=dp(j,newVolume);
if(ret == true)return ret;
}
}
return ret;
}
Warning: Code not tested
Here's a naïve recursive solution in python that works just fine (although it doesn't find an optimal solution):
def match_helper(lower, upper, units, least_difference, fail = dict()):
if upper < lower + least_difference:
return None
if fail.get((lower,upper)):
return None
exact_match = [ u for u in units if u['lower'] >= lower and u['upper'] <= upper ]
if exact_match:
return [ exact_match[0] ]
for unit in units:
if unit['upper'] > upper:
continue
recursive_match = match_helper(lower - unit['lower'], upper - unit['upper'], units, least_difference)
if recursive_match:
return [unit] + recursive_match
else:
fail[(lower,upper)] = 1
return None
def match(lower, upper):
units = [
{ 'name': 'Bucket 1', 'lower': 5, 'upper': 10 },
{ 'name': 'Bucket 2', 'lower': 15, 'upper': 25 },
{ 'name': 'Bucket 3', 'lower': 10, 'upper': 50 }
]
least_difference = min([ u['upper'] - u['lower'] for u in units ])
return match_helper(
lower = lower,
upper = upper,
units = sorted(units, key = lambda u: u['upper']),
least_difference = min([ u['upper'] - u['lower'] for u in units ]),
)
result = match(100, 175)
if result:
lower = sum([ u['lower'] for u in result ])
upper = sum([ u['upper'] for u in result ])
names = [ u['name'] for u in result ]
print lower, "-", upper
print names
else:
print "No solution"
It prints "No solution" for 100-150, but for 100-175 it comes up with a solution of 5x bucket 1, 5x bucket 2.
Assuming you are saying that the "range" for each bucket is the amount of water that it may have when it reaches the tub, and all you care about is if they could possibly fill the tub...
Just take the "max" of each bucket and sum them. If that is in the range of what you consider the tub to be "filled" then it can.
Updated:
Given that buckets can be used multiple times, this seems to me like we're looking for solutions to a pair of equations.
Given buckets x, y and z we want to find a, b and c:
a*x.min + b*y.min + c*z.min >= bathtub.min
and
a*x.max + b*y.max + c*z.max <= bathtub.max
Re: http://en.wikipedia.org/wiki/Diophantine_equation
If bathtub.min and bathtub.max are both multiples of the greatest common divisor of a,b and c, then there are infinitely many solutions (i.e. we can fill the tub), otherwise there are no solutions (i.e. we can never fill the tub).
This can be solved with multiple applications of the change making problem.
Each Bucket.Min value is a currency denomination, and Bathtub.Min is the target value.
When you find a solution via a change-making algorithm, then apply one more constraint:
sum(each Bucket.Max in your solution) <= Bathtub.max
If this constraint is not met, throw out this solution and look for another. This will probably require a change to a standard change-making algorithm that allows you to try other solutions when one is found to not be suitable.
Initially, your target range is Bathtub.Range.
Each time you add an instance of a bucket to the solution, you reduce the target range for the remaining buckets.
For example, using your example buckets and tub:
Target Range = 100..150
Let's say we want to add a Bucket1 to the candidate solution. That then gives us
Target Range = 95..140
because if the rest of the buckets in the solution total < 95, then this Bucket1 might not be sufficient to fill the tub to 100, and if the rest of the buckets in the solution total > 140, then this Bucket1 might fill the tub over 150.
So, this gives you a quick way to check if a candidate solution is valid:
TargetRange = Bathtub.Range
foreach Bucket in CandidateSolution
TargetRange.Min -= Bucket.Min
TargetRange.Max -= Bucket.Max
if TargetRange.Min == 0 AND TargetRange.Max >= 0 then solution found
if TargetRange.Min < 0 or TargetRange.Max < 0 then solution is invalid
This still leaves the question - How do you come up with the set of candidate solutions?
Brute force would try all possible combinations of buckets.
Here is my solution for finding the optimal solution (least number of buckets). It compares the ratio of the maximums to the ratio of the minimums, to figure out the optimal number of buckets to fill the tub.
private static void BucketProblem()
{
Range bathTub = new Range(100, 175);
List<Range> buckets = new List<Range> {new Range(5, 10), new Range(15, 25), new Range(10, 50)};
Dictionary<Range, int> result;
bool canBeFilled = SolveBuckets(bathTub, buckets, out result);
}
private static bool BucketHelper(Range tub, List<Range> buckets, Dictionary<Range, int> results)
{
Range bucket;
int startBucket = -1;
int fills = -1;
for (int i = buckets.Count - 1; i >=0 ; i--)
{
bucket = buckets[i];
double maxRatio = (double)tub.Maximum / bucket.Maximum;
double minRatio = (double)tub.Minimum / bucket.Minimum;
if (maxRatio >= minRatio)
{
startBucket = i;
if (maxRatio - minRatio > 1)
fills = (int) minRatio + 1;
else
fills = (int) maxRatio;
break;
}
}
if (startBucket < 0)
return false;
bucket = buckets[startBucket];
tub.Maximum -= bucket.Maximum * fills;
tub.Minimum -= bucket.Minimum * fills;
results.Add(bucket, fills);
return tub.Maximum == 0 || tub.Minimum <= 0 || startBucket == 0 || BucketHelper(tub, buckets.GetRange(0, startBucket), results);
}
public static bool SolveBuckets(Range tub, List<Range> buckets, out Dictionary<Range, int> results)
{
results = new Dictionary<Range, int>();
buckets = buckets.OrderBy(b => b.Minimum).ToList();
return BucketHelper(new Range(tub.Minimum, tub.Maximum), buckets, results);
}

Resources