Here is a simplified version of KIKO Software code, which solves the problem in O(lg(n)) operations.
function fixedPoint(data)
{
const lastIndex = data.length - 1;
if (lastIndex < 0 || data[0] > 0 || data[lastIndex] < lastIndex) return -1;
var left = 0;
var right = lastIndex;
while(left + 1 < right) {
let middle = Math.floor((left + right) / 2);
if (data[middle] >= middle) right = middle;
else left = middle;
}
if(data[left] == left) return left;
else if (data[right] == right) return right;
else return -1;
}
Since we are using binary search all the way we get good worst case behaviour.
Also we have ditched the if statement statement for correct answer, and removed another redudant if statement, which reduces the amount of if statements checked from roughly 3.5/iteration to 2 per iteration (remember the while check).
I also fixed the problem where if there was no solution and you were left with left and right being just next to each other and left was not a valid solution, then you would enter an infinite loop, as left would be continously assigned from the middle, but the middle was rounded down to the left. Note that this could only happen in the case where left was never moved (first value would be negative and the second value would be 2 or higher).
If you want to go even faster than this, you can try to find a reasonable way to estimate a good middle suggestion. This kind of approach may yeild lower worst-case performance, but you might be able to reach O(lg(lg(n))) average case runtime (I have heard about other algorithms of this type claiming such performance, but I am not familiar with the proofs).
Here is example an following:
function fixedPoint(data)
{
const lastIndex = data.length - 1;
if (lastIndex < 0 || data[0] > 0 || data[lastIndex] < lastIndex) return -1;
var left = 0;
var right = lastIndex;
while(left + 1 < right) {
let rightWeight = max(left -data[left],1);
let leftWeight = max(data[right] - right, 1);
if (rightWeight / 3 > leftWeight ) rightWeight = leftWeight * 3;
if (leftWeight / 9 > rightWeight ) leftWeight = rightWeight * 9;
let middle = Math.floor((left * leftWeight + right * rightWeight) / (leftWeight + rightWeight));
if (data[middle] >= middle) right = middle;
else left = middle;
}
if(data[left] == left) return left;
else if (data[right] == right) return right;
else return -1;
}
The idea in the above is to estimate a good middle point based on the predicted crossover point if we draw a line strait between the left and right. I added a max(weight,1), to ensure that the weights are positive and we do not get stuck. To preserve good performance when the right points are valid candidates, we have put a bound on how far to the right we want our middle guess to be, and to preserve worst-case optimal behaviour a less tight boud on how far to the left we allow our guess to be have also been included.
Note that this version may be slower in practice, due to a higher cost of running through each piece of the loop. If you want to use this, then I suggest trying out different values for the bounds on how far to the left or right you allow it to go, and even trying with those bounds turned off entirely.
ary.findIndex((n, i) => n === i)
? \$\endgroup\$