Fastest to find the joint

-1

0

The input is an array (or space-separated string, as you wish), zero-indexed, consisting of one strictly increasing array and one strictly decreasing array (in this order, not other vice versa), each having a length of 2 at least. Example is:

-10 -2 4 18 38 46 28 19 10

The goal is to write the fastest algorithm to find the joint of two sorted arrays.

In other words, the output is the index of the greatest element. In the provided case the output should be 5.

The input is guaranteed not to contain repeating numbers.

It's obvious that this can be solved with O(n) with ease, so please provide more interesting faster answers :)


Other test cases (first line is input, second is output):

0 1 4 3
2

1 10 9 8 7 6 5 4 3 1
1

1 3 4 6 7 8 0 -10 -20
5

1 3 5 3 1
2

You could also accept the length of the array as additional input, if needed

nicael

Posted 2018-03-18T20:56:01.163

Reputation: 4 585

Do we get the length of the array for free? Is arbitrary access O(1)? (I'm guessing yes to both, but they are not stated.) – Jonathan Allan – 2018-03-18T21:10:23.653

@Jon yep right, sorry for not mentioning – nicael – 2018-03-18T21:11:09.433

5I believe the fastest will be O(log n) by divide & conquer. – Jonathan Allan – 2018-03-18T21:15:34.217

8Well, too easy for a [tag:fastest-algorithm], IMO. Now the challenge is essentially closed. – user202729 – 2018-03-19T01:08:05.903

@JonathanAllan, well, there's a bit faster solution (I mean, it exists, but not posted here yet) :) – nicael – 2018-03-19T01:22:03.133

@nicael average or worst-case? – ngn – 2018-03-19T01:44:40.103

1@nicael: You've chosen to score this question by asymptotic complexity (fastest-algorithm tag description: "Fastest-algorithm competitions are won by the answer with the smallest asymptotic time complexity. For challenges based on actual runtime, use [fastest-code] instead.") If you're thinking of some constant-factor improvement, it doesn't matter for this challenge. – user2357112 supports Monica – 2018-03-19T01:54:39.037

@ngn the fastest algorithm to solve this task that I know is O(log(m)), where m is the position of the found max element, i.e. the answer. – nicael – 2018-03-19T06:22:51.790

@user2357112 please see the above comment, I'd say it's not a constant factor. – nicael – 2018-03-19T06:23:50.420

1That's average-case O(log(n)), and using the position of the max as a parameter in the time complexity opens things up to too much ambiguity - you could just as easily make the algorithm O(log(n-m)), or O(log(distance of max from array center)), or any number of other ways of prioritizing the search. – user2357112 supports Monica – 2018-03-19T06:44:50.007

1@nicael "Fastest" could mean fastest on average or fastest in the worst case. If it's worst-case, O(log(m)) is indistinguishable from O(log(n)), as m can be anywhere in 0...n-1. If it's on average, O(log(m)) could be asymptotically better, provided there's a strong bias towards smaller m. – ngn – 2018-03-19T11:06:35.033

Answers

3

C (gcc)

int find_joint(int *array, int length) {
	int left_bound = 0, right_bound = length - 1, joint = length / 2;

	while (array[joint-1] > array[joint] || array[joint] < array[joint+1]) {
		if (array[joint-1] < array[joint])
			left_bound = joint + 2;
		else
			right_bound = joint - 2;

		joint = left_bound + (right_bound - left_bound) / 2;
	}

	return joint;
}

Try it online!

Jonathan Frech

Posted 2018-03-18T20:56:01.163

Reputation: 6 681

You can improve by replacing left_bound + (right_bound - left_bound) / 2 by (right_bound + left_bound) / 2, which is equivalent. – Angelorf – 2018-03-22T16:35:28.167

@Angelorf I would assume that such an optimisation could be performed by the compiler. If it is not, since this challenge is about finding a well performing algorithm, altering the formula in this manner does not change the algorithm's asymptotic time complexity. – Jonathan Frech – 2018-03-22T17:31:35.920

My suggestion can't make it slower and it could make it faster for some compilers in some interpretation of the word 'fast'. It's just a suggested edit. – Angelorf – 2018-03-26T21:49:59.773

2

JavaScript (ES6), O(log n)

Now assuming that accessing Array.length is always O(1) (as suggested by @12Me21 and @ETHproductions) and therefore not requiring the length to be passed as input anymore.

Returns a string describing the result (index + number of iterations).

f = (a, n = 1, min = 0, max = a.length - 1) => {
  let res = x => `Found index ${x} in ${n} iteration(s)`,
      mid;

  return (
    // only one slot left?
    max == min ? res(max) :

    // only two slots left?
    max == min + 1 ? res(a[max] > a[min] ? max : min) :

    // is the middle slot greater than its two neighbors?
    a[mid = min + max >> 1] > a[mid + 1] && a[mid] > a[mid - 1] ? res(mid) :

    // recursive call required
    a[mid] > a[mid + 1] ?
      f(a, n + 1, min, mid - 1)
    :
      f(a, n + 1, mid + 1, max)
  );
}

Try it online!

Arnauld

Posted 2018-03-18T20:56:01.163

Reputation: 111 334

Are you allowed to take the length as input, even though it isn't needed? – 12Me21 – 2018-03-18T23:35:38.503

@12Me21 Testing Array.length is probably O(1) in most - if not all - JS engines, but I don't think this is part of the specification (in other words, it could be O(n) just as well). Since taking the length as input was explicitly allowed by the OP, I guess it doesn't hurt to do so. – Arnauld – 2018-03-19T00:03:44.240

I would assume that Array.length is an actual property on all platforms (thus accessing it is O(1)), simply due to the fact that the length cannot be calculated from the number of items in the array or the index of the last item. – ETHproductions – 2018-03-19T03:24:46.303

@ETHproductions Thanks for your feedback. I went ahead and updated it accordingly. – Arnauld – 2018-03-19T09:42:02.097

0

JavaScript (ES7)

function find_join(a, l) {
    p = 1.25 ** .5 - .5;
    n = 1;
    i = 0;
    j = l * p + .5 | 0;
    k = j * p + .5 | 0;
    x = a[j];
    y = a[k];
    while (l > 2) {
        n++;
        if (x > y) {
            l = j;
            j = k;
            k = j * p + .5 | 0;
            x = y;
            y = a[i + k];
        } else {
            i += k;
            l -= k;
            k = j - k;
            j = l * p + .5 | 0;
            y = x;
            x = a[i + j];
        }
    }
    return [n, a.indexOf(Math.max(...a.slice(i, i + l)), i)];
}

I was too lazy to write out the full l < 3 case.

This algorithm optimises the number of accesses of the array a. In practice this is not very useful, since the binary search always tests consecutive elements, which is not likely to be much slower than testing a single element.

Neil

Posted 2018-03-18T20:56:01.163

Reputation: 95 035