3

What is the best way to find nth occurrence of a number in ArrayList?

What I know already?

  1. To find lastIndexOf the number there is method in List interface, which is implemented in ArrayList class.
  2. To find first occurence there is indexOf method.

What I was solving?

In a problem there was a list with different numbers and I have to return index of two numbers whose sum is equal to target number. Ex: List = (1,2,1) & target = 2; Now 1 + 1 =2 and answer will be index of first 1 and second 1.

Note: I have solved this problem & I need answer to the question at the top. Check Solution

What I did?

  public static void main(String[] args)
  {
    List<Integer> list = new ArrayList<Integer>();
    list.add(1);
    list.add(2);
    list.add(1);
    int length = list.size();
    int firstIndex = list.indexOf(1) + 1;
    int secondIndex = firstIndex + list.subList(firstIndex, length).indexOf(1) + 1;
    System.out.println(firstIndex);
    System.out.println(secondIndex);
  }
Naseer Mohammad
  • 389
  • 4
  • 14
Amit Kumar
  • 2,685
  • 2
  • 37
  • 72
  • Assuming by best you mean efficient, you want to move from a time complexity of O(n^2) to a less time complexity. Check my answer for O(n*logn) – Diego Martinoia Sep 08 '15 at 15:18
  • 1
    Your solution is not most efficient. See [My Solution](http://stackoverflow.com/a/32462157/823393) which implements @DiegoMartinola's idea. Honestly I didn't steal your idea Diego, I came up with it independently. – OldCurmudgeon Sep 08 '15 at 15:54

7 Answers7

2

"a list with all equal numbers" --> {n,n,...,n,n}.

"I have to return index of first two numbers whose sum is equal to target number" Let's suppose target=x. As your list is full of equal numbers, if x/2=n your indexes ll be 0 and 1, if x/2 !=n you wont have any match


AFTER QUESTION EDITION


    int length=10;
    int target=100;
    int[] tab1= new int[length];
    Object[] tab2= new Object[length];
    Object[] tab2Sorted= new Object[length];

    for (int i = 0; i < tab2Sorted.length; i++) {
        for (int j = i; j < tab2Sorted.length; j++) {
            if(tab2Sorted[i]+tab2Sorted[j]==target){
                //do what you want on objects to get back indexes
            }

        }
        //As tab3 is sorted you dont have to read all the array
        if(tab2Sorted[i]>target/2){
            break;
        }
    }

You just have to change tab2 and tab2Sorted types from Object to a custom type saving the int and his index from first tab

Erwan C.
  • 709
  • 5
  • 18
1

Suppose that your list is not a list, but an array (an arraylist is basically an array once you are done inserting the stuff).

Suppose also that you didn't want to find the index of the first two nums that sum to X, but rather just the two nums (if any).

There is a trivial solution that takes O(n^2) time, where you just iterate each number with all the ones that come after it, and check for the sum.

A better way is to sort the array (which takes O(n*logn)). Now you can, for each number, do a binary search in the array for its complement, i.e. the number which, if summed to it, would result in X. This takes n (each number) * log n (binary search its complement).

But we can't sort, because we want the indexes! Or can't we?

What if we create a copy of the array that, instead of just the value, stores a pair value + originalPosition:

class PosAndValue {
  public final int value;
  public final int pos;
  public PosAndValue(int v, int p) {
    value = v;
    pos = p;
  }
} 

We can now sort an array of these PosAndValue by its value, execute the algorithm just mentioned, and then retrieve the original positions (i.e. indexes), all in n*logn time complexity (and n space complexity).

I'm fairly confident you can't make it much "faster", in terms of complexity. Note that this doesn't mean the code will be faster in any case, but rather that, for sufficiently large inputs (i.e. "big enough" arrays), it will be! for small inputs, as your example, all this overhead may actually make the code slower!

You could make it even better if you knew that the input was limited in range, and then do a boolean bitmap O(n) over the values, but this is a non-specified constraint!

Diego Martinoia
  • 4,592
  • 1
  • 17
  • 36
  • The original problem was solved in O(n) time complexity. Check my code here http://pastebin.com/HwV5w0cW. Pls check else part of the code that is to handle the cases where the numbers that sum up to be target are equal. – Amit Kumar Sep 08 '15 at 15:25
  • 1
    @amit_yo I think you are underestimating the fact that indexOf is actuall a O(n) complexity call, and you are calling it inside a for loop over all the values, thus making it O(n^2). Additionally, you have a double nested for over the input, which is also O(n^2) – Diego Martinoia Sep 08 '15 at 15:30
  • Yes, you are right, but indexOf() call is made two times and inner for loop runs once and code breaks afterwards. So, I think that make it O(n). – Amit Kumar Sep 08 '15 at 15:38
  • 1
    @amit_yo You have a list.add(a.indexOf(diff) + 1); immediately inside your foreach loop. That's n^2, as it will be called for every number (n) and will cost you n. The ONLY way to solve this problem in O(n) is if you know the range of the values in the input and use a bitmap. It is kinda of a known problem, really (classic interview question). – Diego Martinoia Sep 08 '15 at 15:43
  • Would the downvoter care to comment on his downvote? – Diego Martinoia Sep 08 '15 at 15:44
  • 2
    @amit_yo - `indexOf` is `O(n)` so your solution is at least `O(n^2)`. Diego's idea (and my implementation) uses `binarySearch` on a sorted array for the inner search for the partner and so is `O(n log(n))`. – OldCurmudgeon Sep 08 '15 at 16:13
  • @OldCurmudgeon, Diego, That is not always true that if a for loop is inside another for loop it will make it O(n^2). See http://stackoverflow.com/questions/526728/time-complexity-of-nested-for-loop. My outer for loop will keep on executing and when condition will be true inner for loop will run and it will break. It will never run inner for loop (or indexOf : its called twice) more than once. Your solution is good but will take O(nlogn) which is no better than O(n). – Amit Kumar Sep 09 '15 at 06:16
  • @amit_yo, apparently no argument can convince you :) try it yourself: the input list is [6,6,6,6,6,6,6,6,1,4] Target is 5. Your code will run quadratically. Your code is not (in the general case), O(n). Btw, in the question you linked, OP's code is also quadratic. This problem CAN'T be solved in linear time without using a bitmap and knowing the range of the input. Even if you did find the nth occurrence of 6, that would cost you a full scan complexity, still quadratic. – Diego Martinoia Sep 09 '15 at 09:51
1

You can build something like this

List<Integer> numbers = new ArrayList<>(); // your list of numbers. Let's say it's {1, 1, 4, 5, 4}
numbers.add(1);
numbers.add(1);
numbers.add(4);
numbers.add(5);
numbers.add(4);
SparseArray<Set<Integer>> occurrences = new SparseArray<>();
for (int i = 0; i < numbers.size(); i++) {
    if (occurrences.indexOfKey(numbers.get(i)) < 0) {
        occurrences.put(numbers.get(i), new HashSet<Integer>());
    }
    Set<Integer> ints = occurrences.get(numbers.get(i)); // get current set
    ints.add(i); // add your new found one
    occurrences.put(numbers.get(i), ints); // put it back into your occurrences set
}

which then will give you a SparseArray of original numbers and sets of these numbers occurences indexes from your original arraylist.

{1=[1, 0], 4=[4, 2], 5=[3]}
1

This should be most efficient. It uses a sorted version of your array (maintaining references to the original offset) and Collections.binarySearch which should offer best performance.

private Integer[] addsUp(List<Integer> numbers, int value) {
    // Hold the value and it's original offset.
    class No implements Comparable<No> {

        final int n;
        final int o;

        public No(int n, int o) {
            this.n = n;
            this.o = o;
        }

        public No(int n) {
            this(n, -1);
        }

        @Override
        public int compareTo(No o) {
            return Integer.compare(n, o.n);
        }

        @Override
        public String toString() {
            return "{" + n + " @ " + o + "}";
        }
    };
    // Build my list.
    List<No> myNumbers = new ArrayList(numbers.size());
    for (int i = 0; i < numbers.size(); i++) {
        myNumbers.add(new No(numbers.get(i), i));
    }
    // Start sorted.
    Collections.sort(myNumbers);
    // Upper limit of my search.
    int max;
    // Find the value in the numbers.
    No no = new No(value);
    int spot = Collections.binarySearch(myNumbers, no);
    // Did we find it?
    if (spot < 0) {
        // Not found - but this number is nearest to value.
        max = -spot - 1;
    } else {
        // Found ! No number higher than that is relevant.
        max = spot;
    }
    // For each start number.
    for (int first = 0; first < max; first++) {
        No remainder = new No(value - myNumbers.get(first).n);
        // Does that number appear in the list.
        int second = Collections.binarySearch(myNumbers, remainder);
        if (second >= 0) {
            // Found second number! Return original offsets.
            return new Integer[]{myNumbers.get(first).o, myNumbers.get(second).o};
        }
    }
    return null;
}

public void test() {
    List<Integer> numbers = Arrays.asList(1, 2, 1);
    Integer[] two = addsUp(numbers, 2);
    System.out.println("two = " + Arrays.toString(two));
    Integer[] three = addsUp(numbers, 3);
    System.out.println("three = " + Arrays.toString(three));
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
0

For the question in your title, you can just use a loop together with the subList method of List:

public static void main(String[] args)
{
    List<Integer> list = new ArrayList<Integer>();
    list.add(1);
    list.add(1);
    list.add(1);

    int n = 2;
    int index = -1;
    List<Integer> subList = list;

    for( int i = 0; i < n; i++)
    {
        index = subList.indexOf( 1);

        if( index == -1)
            break;

        System.out.println( i + "th index: " + index);
        subList = subList.subList( index+1, subList.size());
    }
}

For the real problem, finding the nth occurence of a number in a list is not a reliable way of doing it, you are assuming the repeating array element is a divisor of the target number.

uoyilmaz
  • 3,035
  • 14
  • 25
0
public static int nthIndexOf(List<Integer> list, int needle, int n){
    for (int i = 0; i < list.size(); i++) {
        if (list.get(i) == needle) {
            n--;
            if (n == 0) {
                return i;
            }
        }
    }
    return -1;
}

Props to Jon Skeet

Community
  • 1
  • 1
McNultyyy
  • 992
  • 7
  • 12
  • 1
    This code returns the position of the nth occurrence of a given integer, doesn't look much related to OP's question. – Diego Martinoia Sep 08 '15 at 15:21
  • Yes, but that's a misleading title. In the question he specifies what is the "real" problem. This is an example of a red/blue question imho (i.e. one where he asks about something but the problem is something else). – Diego Martinoia Sep 08 '15 at 15:32
0
  public static void main(String[] args)
  {
    Integer[] numbersList = {1,2,3,4,5,6,7,8,9,10}; //Load your Array here
    List<Integer> list = Arrays.asList(numbersList);
    int targetNumber = 8;  //Load your targetNumber here

    int currrentVal = 0;
    int nextVal = 0;
    int i = 0;
    int j = 0;
    boolean found = false;
    for(i=0;i<list.size();i++) {
        currrentVal = list.get(i);          
        for(j=currrentVal+1;j<list.size();j++) {
            nextVal = list.get(j);              
            if(targetNumber == currrentVal+nextVal) {
                found = true;
                break;
            }
        }
        if(found) {
            break;
        }
    }
    if(found) {
        System.out.println("firstIndex  : "+i);
        System.out.println("secondIndex : "+j);
    } else {
        System.out.println(" No match found");
    }
  }
Vasu
  • 21,832
  • 11
  • 51
  • 67