10
\$\begingroup\$

I understand that if you don't know the trick, it's not easy create a solution with a complexity of \$O(N)\$. However, I would like to ask you how to solve this tricky question:

You are given two non-empty zero-indexed arrays A and B consisting of N integers. Arrays A and B represent N voracious fish in a river, ordered downstream along the flow of the river. The fish are numbered from 0 to N − 1, fish number P is represented by A[P] and B[P], and if P < Q then fish P is initially upstream of fish Q. Initially, each fish has a unique position. Array A contains the sizes of the fish. All its elements are > unique. Array B contains the directions of the fish. It contains only 0s and/or 1s, where:

0 represents a fish flowing upstream
1 represents a fish flowing downstream

If two fish move in opposite directions and there are no other (living) fish between them, they will eventually meet each other. Then only one fish can stay alive − the larger fish eats the smaller one. More precisely, we say that two fish P and Q meet each other when P < Q, B[P] = 1 and B[Q] = 0, and there are no living fish between them. After they meet:

If A[P] > A[Q] then P eats Q, and P will still be flowing downstream,
If A[Q] > A[P] then Q eats P, and Q will still be flowing upstream.

We assume that all the fish are flowing at the same speed. That is, fish moving in the same direction never meet. The goal is to calculate the number of fish that will stay alive.

For example, consider arrays A and B such that:

A[0] = 4    B[0] = 0
A[1] = 3    B[1] = 1
A[2] = 2    B[2] = 0
A[3] = 1    B[3] = 0
A[4] = 5    B[4] = 0

Initially all the fish are alive and all except fish number 1 are moving upstream. Fish > number 1 meets fish number 2 and eats it, then it meets fish number 3 and eats it too. Finally, it meets fish number 4 and is eaten by it. The remaining two fish, numbers 0 and 4, never meet and therefore stay alive.

Write a function:

class Solution { public int solution(int[] A, int[] B); } 

that, given two non-empty zero-indexed arrays A and B consisting of N integers, returns the number of fish that will stay alive.

For example, given the arrays shown above, the function should return 2, as explained above.

Assume that:

N is an integer within the range [1..100,000], where each element of array A is an integer within the range [0..1,000,000,000], where each element of array B is an integer that can have one of the following values: 0, 1, where the elements of A are all distinct.

Complexity:

expected worst-case time complexity is O(N);
expected worst-case space complexity is O(N), beyond input storage (not counting the storage required for input arguments).

My solution:

import java.util.Stack;

class Solution {
  public int solution(int[] A, int[] B) {
    int n = A.length;
    if (n<1 || n>100000) return -1;
    int alive = n;
    Stack<Integer> down = new Stack<Integer>();

    // array to check if a fish has died
    boolean[] lives = new boolean[n];

    for (int i=0;i<n;i++) lives[i]=true;

    // the first fish with 1 is going to the right and will try
    // to eat other fishes in opposite direction
    // but if the fish is bigger it will comeback and it will eat other fishes in opposite direction

    for (int i=0,k=i+1; i<n-1;i++) {
      if (B[i]==0) continue;

      for (;k<n;k++){

        // I save in a stack all the fishes in the same direction so in this way
        //I don't have to check again back if there is some fish
        //that is bigger than the fish that has been capable to eat the "i" fish

        if (B[i]==B[k] && B[i]==1) down.push(k);

        if (A[i]>A[k] && B[i]!=B[k] && B[i]==1 && lives[k]){
          alive--;
          lives[k]=false;
        } else if (A[i]<A[k]&&B[i]!=B[k]&&B[i]==1 && lives[i]) {
          alive--;
          lives[i]=false;

          //k fish eat i fish and I try to check if exists some i+1 to i-1
          //fish that is opposite to k fish and could eat "k" fish
          while(!down.empty() && i!=down.peek()){
            i=down.pop();
            if (A[i]<A[k] && lives[i]) {
              alive--;
              lives[i]=false;
            } else if (A[i]>A[k] && lives[k]){
              alive--;
              lives[k]=false;
              break;
            }
          }
        }
      }
    }
    return alive;
  }
}

This solution is not completely right! I found the right solution but in \$O(N^2)\$ time. I was trying to reach \$O(N)\$. If you want to test, check this URL.

\$\endgroup\$
4
  • 2
    \$\begingroup\$ Would you mind summarizing what the problem is asking for and a brief explanation of your algorithm? That's way too much junk to try to figure out what's going on for ourselves. \$\endgroup\$ Nov 2, 2013 at 19:53
  • \$\begingroup\$ In bold I tried to explain what I was looking for. Thank you \$\endgroup\$
    – Thor
    Nov 2, 2013 at 21:20
  • \$\begingroup\$ you should avoid naming the variables as A, B, n etc \$\endgroup\$
    – Kinjal
    Nov 3, 2013 at 3:32
  • \$\begingroup\$ I used the same variable of the prototype method \$\endgroup\$
    – Thor
    Nov 3, 2013 at 16:44

2 Answers 2

9
\$\begingroup\$
import java.util.Stack;
class Solution {
    /* A POD struct to aggregate fish that will be eaten or not eaten together */
    /* If the front fish is larger than any fish in the school (ie they are ordered) */
    /* and the front fish is eaten, the rest of the school will also definitely be */
    /* eaten.  Any fish that the front fish can eat will not be able to eat the rest */
    /* of the school. */
    /* Technically this does not help with O(n) since, if all fish are out of order */
    /* there will only be n schools of 1 fish, just like keeping a stack of ints. */
    /* However, in the average case this will cut out a lot of comparisons */
    private class School {
        public int count;
        public int largest;
        public School() {
            this.count = 0;
            this.largest = 0;
        }
        public School(int size) {
            this.count = 1;
            this.largest = size;
        }
    }
    private final int UP = 0, DOWN = 1;
    public int solution(int[] A, int[] B) {
        /* number of fish swimming toward i=0 whose index < i that are alive */
        int alive = 0;
        int i = 0, n = A.length;
        /* All live fish swimming toward i=n whose index < i are accounted for here */
        Stack<School> down = new Stack<School>();
        /* Simpler if down is never empty, so push an empty school */
        down.push(new School());
        /* Fish swimming toward 0 with no opposite-swimming fish are in the clear */
        while (i < n && B[i] == UP) {
            alive++;
            i++;
        }
        while (i < n) {
            if (B[i] == DOWN) {
                School top = down.peek();
                /* is fish i big enough to protect the School behind it? */
                if (A[i] >= top.largest) {
                    top.count++;
                    top.largest = A[i];
                } else {
                    /* or is it on its own? */
                    down.push(new School(A[i]));
                }
            } else {
                School top = down.pop(); 
                /* This inner loop is ok for linear time - see below */
                /* Fish i will meet the stack's top and either eat it or be eaten */
                /* It keeps eating fish from the stack until emptied or it's eaten */
                while (top.largest < A[i] && !down.empty()) {
                    top = down.pop();
                }
                /* If it wasn't eaten by now, it's in the clear */
                if (top.largest < A[i]) {
                    alive++;
                    /* hackish, but it works to ensure that the stack is never empty */
                    down.push(new School());
                } else {
                    down.push(top);
                }
            }
            i++;
        }
        /* All the fish swimming toward increasing i are alive, too */
        while (!down.empty()) {
            alive += down.pop().count;
        }
        return alive;
    }
}

Let \$X(i)\$ be the number of iterations of the inner loop actually performed for iteration \$i\$ of the outer loop. \$X(i)\$ may be at most \$i\$, but \$X(i+1)\$ is at most \$1 + i - X(i)\$ because each additional comparison beyond 1 removes an element from the stack. Therefore, the sum of \$X(0)\$ to \$X(i)\$ can be no greater than \$i\$. That is, the sum of iterations of the inner loop can't exceed \$N\$ for \$N\$ elements. The rest (assuming \$k\$ time allocation, etc etc) is \$k\$ time per iteration, so the outer loop turns out \$O(n)\$.

\$\endgroup\$
6
  • \$\begingroup\$ /* All live fish swimming toward i=n whose index < i are accounted for here */ Stack<School> down = new Stack<School>(); is better :) Great job! Now I will study it! I was thinking why did you need to model an object for that? \$\endgroup\$
    – Thor
    Nov 3, 2013 at 16:43
  • \$\begingroup\$ @200_success I wrote it with the idea in my head that UP was increasing i and DOWN was decreasing i, because that's what made sense to me. If that contradicts which end of the stream i=0 is supposed to be, you are absolutely right. There are probably also a lot of factoring improvements I could make (e.g. put a boolean tryToAddFish(int size) in School, and what you suggested) if I was more into Java. This might be out of scope, but what difference would static make to School?(sqykly == Dave Hand, btw) \$\endgroup\$
    – sqykly
    Nov 4, 2013 at 7:48
  • \$\begingroup\$ @user31565 I made the correction you suggested - good catch! I also added a big comment to clarify my reasoning for using School. Seems like it should reduce unnecessary comparisons in the average case where half of the fish swimming in the i++ direction are ordered and have their fate determined by the fish in front of them. Someone who likes Java and math more than me should be able to verify or disprove me on that. \$\endgroup\$
    – sqykly
    Nov 4, 2013 at 8:06
  • 1
    \$\begingroup\$ A static inner class ensures that the inner class does not depend on any instance variables in the outer class, i.e. it is an independent class that just happens to be defined here because it was convenient, not because it needs to capture any variables from its environment. Adding the static keyword aids readability. \$\endgroup\$ Nov 4, 2013 at 9:04
  • \$\begingroup\$ You are a great coder! So School for you means a number of fish that is moving toward the i=n! You collect it in the stack and see if there is someone that is able to defend it! Am I right? However at the end could you let's have a look on this? bit.ly/1hedWld Thank you \$\endgroup\$
    – Thor
    Nov 4, 2013 at 20:54
10
\$\begingroup\$

Unfortunately, both solutions are wrong as they don't pass the following JUnit tests:

@Test
public void testSolution() {
    FishSurvivor fs = new FishSurvivor();
    int[] a = { 4, 3, 2, 1, 5 };
    int[] b = { 0, 1, 0, 0, 0 };
    assertEquals(2, fs.solution(a, b));
    a = new int[] { 4, 3, 2, 1, 5 };
    b = new int[] { 0, 1, 0, 1, 0 };
    assertEquals(2, fs.solution(a, b));
    a = new int[] { 4, 3, 2, 1, 5 };
    b = new int[] { 0, 0, 0, 0, 0 };
    assertEquals(5, fs.solution(a, b));
    a = new int[] { 4, 3, 2, 1, 5 };
    b = new int[] { 1, 1, 1, 1, 1 };
    assertEquals(5, fs.solution(a, b));
    a = new int[] { 4, 3, 2, 1, 5 };
    b = new int[] { 0, 0, 0, 1, 1 };
    assertEquals(5, fs.solution(a, b));
    a = new int[] { 5, 3, 2, 1, 4 };
    b = new int[] { 1, 0, 0, 0, 0 };
    assertEquals(1, fs.solution(a, b));
    a = new int[] { 1, 2, 3, 4, 5 };
    b = new int[] { 1, 1, 1, 1, 0 };
    assertEquals(1, fs.solution(a, b));
}

The right answer with time and space complexity O(n) is shown below:

package com.atreceno.it.javanese.codility;

import java.util.Stack;

/**
 * You are given two non-empty zero-indexed arrays A and B consisting of N
 * integers. Arrays A and B represent N voracious fish in a river, ordered
 * downstream along the flow of the river...
 * 
 * @author atreceno
 * 
 */
public class FishSurvivor {

    /**
     * Given two non-empty zero-indexed arrays A and B consisting of N integers,
     * this function returns the number of fish that will stay alive.
     * 
     * @param a
     *            array representing the size.
     * @param b
     *            array representing the direction.
     * @return the number of fish that will stay alive.
     */
    public int solution(int[] a, int[] b) {
        int survivors = 0;
        Stack<Integer> ones = new Stack<Integer>();
        for (int i = 0; i < a.length; i++) {
            if (b[i] == 0) {
                if (ones.size() == 0) {
                    survivors++;
                } else { // Duel
                    while (!ones.empty()) {
                        if (a[i] > ones.peek()) { // "One" dies
                            ones.pop();
                        } else { // "Zero" dies
                            break;
                        }
                    }
                    if (ones.empty()) { // "Zero" survives
                        survivors++;
                    }
                }
            } else {
                ones.push(a[i]);
            }
        }
        return survivors + ones.size();
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ It's partly my fault for breaking @DaveHand's solution! His rev 4 worked, but his UP/DOWN usage was backwards and his constants were also misnamed, so two wrongs make a right. I half-fixed it in Rev 5, which broke it. Rev 6 should work now. \$\endgroup\$ Nov 9, 2013 at 0:48
  • 2
    \$\begingroup\$ I'd suggest if (ones.empty()) { survivors++; } ... instead of if (ones.size() == 0) { survivors++; } ..., to be consistent with the two other similar conditions. \$\endgroup\$ Nov 9, 2013 at 0:55

Not the answer you're looking for? Browse other questions tagged or ask your own question.