'Infected Fish can eat another fish having size less that its own. Minimum number of operation required
An evil scientist has developed an injection that induces insatiable hunger in a fish. On giving this injection, a fish of size x can eat another fish of smaller size y (y < x) and become a fish of size x + y retaining this hunger. An aquarium has a number of fishes of various sizes. The scientist introduces an injected fish into this aquarium with an objective that eventually only 1 fish remains. In order to achieve this, the scientist is allowed only two types of moves: either add a normal fish of any size or remove an existing normal fish from the aquarium. Given the sizes of other fishes in the aquarium and the size of injected fish, write a program to determine the minimum number of moves needed by the scientist to achieve his objective. For example, suppose there are 5 fishes in the aquarium, the injected fish is of size 10 and the other fishes are of sizes 9, 20, 25, and 100. To ensure that only 1 fish remains in the aquarium the scientist needs to remove the fish of size 100 and add a fish of size 3. So the output is 2. The sequence of steps is shown below. The sizes of fishes in the aquarium at each step are shown in curly braces. The highlighted number is the size of the injected fish.
Input format: {infectedFish} # {a,b,c,d ... } where a,b,c etc are normal fishs
Example:
- 10#9,20,25,100 ===> 2
- 3#25,20,100,400,500 ===> 5
- 50#25,20,9,100 ===>0
I have used following code to solve the problem.
public static int count(int inf, int a[], int i, int op){
//base case
if(i==a.length){
return op;
}
while(i<a.length && inf>a[i]){
inf+=a[i];
i++;
}
if(i==a.length){
return op;
}
int curr = inf+inf-1;
return Math.min(count(curr, a, i, op+1), count(inf, a, i+1, op+1));
}
Calling it using, System.out.println(count(x, a, 0, 0));
x is infected fish and a is given sorted array;
Is this approach correct? If so, seems like its is having comman subproblem, How can we do memoization?
Solution 1:[1]
You can increase the efficiency by slightly modifying your algorithm.
If you have more than one fish to eat, it is not optimal to remove the next one, and then try to eat a fish which is bigger.
When you eat a fish, you become bigger, without any cost.
Therefore, the alternatives are:
- Eat the next fish after growing up, i.e. after adding new fishes
- Get rid of all remaining fishes
Then, the formula can be simplified as follows, other codes remaining unchanged:
return Math.min(count(curr, a, i, op+1), op + a.length -1 - i);
In this case, you don't need memoization: the count
function is following one way only, always increasing its internal values.
Note: I assume here that the fishes are sorted, as mentioned in your code.
Complexity, without taking into account sorting: O(n + log(weight_max/weigth_in))
EDIT: there is another way to speed up the process:
If, at a given time, the number of operation op
is greater than the current optimum value, then you can stop the recursion.
Solution 2:[2]
As others have answered, greedy can work here. After first eating all smaller fish, each choice point is (1) add the largest possible fish smaller than the current hungry fish, or (2) remove all equal or larger fish. Clearly, removing a fish from the middle is unnecessary since if we could consume a larger one, we could consume that one as well.
The state we would like to optimise is num_current_moves + num_removals_needed
. But since we only need a single variable to store the best seen such state, we can update it after every choice, iterating up the sorted list, greedily adding fish. Choice (2) isn't an active change; rather, it's just part of calculating the best seen state at each point.
Iterative JavaScript code:
function f(x, A){
A.sort((a, b) => a - b)
let sum = x
let removals_needed = A.length
let moves = 0
let best = removals_needed
let i = 0
while (i < A.length){
if (sum > A[i]){
sum = sum + A[i]
removals_needed = removals_needed - 1
i = i + 1
// It might not be worth
// trying to grow the fish,
// but the growth rate is
// probably fast enough not
// to add significant time
} else {
sum = sum + (sum - 1)
moves = moves + 1
}
best = Math.min(best, moves + removals_needed)
}
return best
}
var inputs = [
[10, [9,20,25,100]], // 2
[3, [25,20,100,400,500]], // 5
[3, [20,21,22,23,24,25]], // 4
[3, [20,21,22]], // 3
[50, [25,20,9,100]] // 0
]
for (let [x, A] of inputs){
console.log(`${x} -> ${A}`)
console.log(f(x, A))
console.log("")
}
Solution 3:[3]
Let's summarize:
There are two possible interventions:
Remove biggest fish, and
Add a fish just smaller than the infected fish.
The infected fish can gobble up all smaller fishes, each time growing by the eaten fish's size.
You want the minimum number of interventions to leave only the devourer.
A far simpler algorithm is:
- Sort all normal fishes by size.
- The trivial solution is removing all fishes manually.
- You have not yet added any new fishes.
- Gobble up the smallest fishes as long as possible.
- New candidate solution is fishes added plus fishes remaining. Choose the better one.
- If no fishes left, you are done.
- Grow the devourer to 2*size-1 by adding a fish of size size-1 as often as needed to devour the smallest fish.
- Contine with step 4, devouring fishes.
Solution 4:[4]
public static int HungryFish(int infectedFishSize, int[] fishs)
{
Array.Sort(fishs);
int moves = 0;
for (int i = 0; i < fishs.Length; i++)
{
if (fishs[i] < infectedFishSize)
{
infectedFishSize += fishs[i];
}
else
{
if (fishs[i] < (infectedFishSize + infectedFishSize - 1))
{
infectedFishSize += (infectedFishSize - 1);
moves++;
}
else
{
moves += (fishs.Length - i);
break;
}
}
}
return moves;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | גלעד ברקן |
Solution 3 | Deduplicator |
Solution 4 | DKumar |