Half the Work, Same Class

This method checks a single array for duplicate values:

public boolean hasDuplicates(int[] a) {
  for (int i = 0; i < a.length; i++) {
    for (int j = i + 1; j < a.length; j++) {
      if (a[i] == a[j]) {
        return true;
      }
    }
  }

  return false;
}

Take the array to have length . What is the Big-Oh of this method?

The loops are nested again, so you might expect . That is the right answer. But the inner loop here does noticeably less work than in the previous example, and I want to walk through why the answer comes out the same anyway.

Look at where the inner loop starts. It starts at j = i + 1, not at j = 0, so it does not scan the whole array each time. We can draw the same grid as before, with a · for a comparison yet to be made, a for one already made, and a for the one happening now. When i = 0 the inner loop skips the cell where j = i and visits the rest of the row:

      j=0  j=1  j=2  j=3
i=0   ·    ░    ░    █
i=1   ·    ·    ·    ·
i=2   ·    ·    ·    ·
i=3   ·    ·    ·    ·

The next row starts one cell further along, at j = i + 1 = 2:

      j=0  j=1  j=2  j=3
i=0   ·    ░    ░    ░
i=1   ·    ·    ░    █
i=2   ·    ·    ·    ·
i=3   ·    ·    ·    ·

When i = 0 the inner loop runs times; when i = 1 it runs times; and so on down to when i = n - 1. Each row covers only the cells to the right of the diagonal, so the visited cells fill the upper triangle and leave the lower triangle empty:

      j=0  j=1  j=2  j=3
i=0   ·    ░    ░    ░
i=1   ·    ·    ░    ░
i=2   ·    ·    ·    ░
i=3   ·    ·    ·    ·

So the total number of inner iterations is

That sum has a closed form:

It is not hard to derive that formula. Look up the Gauss Summation trick if you want to see it. The total is about half of , so the inner loop does half as many iterations as in the previous example. Still, asymptotically, it is . We drop the constant factor of one-half, and we drop the lower-order term.