The naive approach that tries every possible swap and checks the result takes O(n3) time with respect to the length of the array. The smarter approach of finding the first out-of-order pair and trying to fix it by swapping in every subsequent element takes O(n2) time. The approach of sorting the array and comparing it to the original takes O(n log n) time. Can we do even better?
Yes, we can solve this problem in linear time. We start by looking for an inverted pair of adjacent elements, meaning that the one on the left is bigger than the one on the right. If there is no such pair, the array is already sorted.
Otherwise, we find the leftmost such pair. Let's take the larger element of this pair, namely the one on the left, and call it x
. If x
is the last of a sequence of equal elements, let's take the leftmost such element because when we swap x
with a smaller element y
, we want y
to occur before every element equal to x
.
Now we scan to the right of the inverted pair for the earliest element that is at least as big as x
. Once we find such an element, we take the element immediately before it and call this element y
. If there is no such element, let y
be the last element of the array.
If the array can be sorted in one swap, it is necessary and sufficient to swap x
and y
. Consider the cases:
If all elements to the right of the inverted pair are smaller than x
, it is necessary for x
to be moved past all of them. Therefore, we swap x
with the last element of the array.
Otherwise, consider all elements to the right of x
that are bigger than x
. In a sorted array, x
must occur before all of them, but x
must be moved past elements that are smaller than it. Therefore, we find the earliest element to the right of the inverted pair that is at least as big as x
, and we swap x
into the position immediately before it.
// Returns true if and only if A can be sorted with at most one swap.
function almostSorted(A) {
for (var i = 1; i < A.length; ++i) {
// Look for an inverted adjacent pair.
if (A[i-1] <= A[i]) {
continue;
}
var x = A[i-1],
left = i-1;
// If x is one of a sequence of identical elements, take the leftmost.
while (left-1 >= 0 && A[left-1] == x) {
--left;
}
// Scan past the inverted pair for the earliest element no smaller than x.
for (++i; i < A.length; ++i) {
if (A[i] >= x) {
break; // If we never break here, i will be equal to A.length.
}
}
// Let y be the element before the earliest element no smaller than x.
var right = i-1,
y = A[right];
// Swap x and y.
A[left] = y;
A[right] = x;
// Is the array sorted now?
for (i = (left == 0 ? 1 : left); i < A.length; ++i) {
if (A[i-1] > A[i]) {
return false;
}
}
return true; // One swap was enough to sort the array.
}
return true; // The array is already sorted.
}
// A few tests.
function test(A) {
document.write('['+A.join(', ')+']');
var result = almostSorted(A);
document.write(': <span class="', result, '">', result, '</span>');
if (result) {
document.write(' → ', '['+A.join(', ')+']');
}
document.write('<br />');
}
test([1, 2, 5, 4, 3]);
test([1, 2, 3, 5, 4]);
test([1, 4, 3, 2, 5]);
test([1, 5, 4, 3, 2]);
test([1, 5, 3, 3, 7]);
test([2, 2, 1, 3, 7]);
test([2, 3, 1, 3, 7]);
test([1, 3, 1, 3, 7]);
test([2, 1, 1, 3, 7]);
body {
font-family: sans-serif;
font-size: 17px;
color: #333;
}
.false {
color: #b23c3c;
}
.true {
color: #5c7b51;
}