19

The following was my interview question. But I couldn't crack it and even could not think how to get this done.

var arr = [1,4,5,8,3,2,6,9,7,10];

Expected output of alternate sorting:

[10,1,9,2,8,3,7,4,6,5]

What I have tried:
I tried slicing out the Math.max.apply(null,arr) and Math.min.apply(null,arr) alternatively to push into separate empty array. But It was told that the algorithm is not optimal.

9
  • 3
    Does the input follow some kind of order? If it doesn’t, you can’t do better than a regular sort in the worst case, so see if you can figure out how to convert a sorted array into that format. Commented May 4, 2018 at 4:16
  • 4
    arr.sort((a, b) => (Math.abs(b - 5.4) - Math.abs(a - 5.4))); works just fine :D Commented May 4, 2018 at 7:57
  • 2
    @EricDuminil That was my first thought as well. It really leads me to think the problem isn't well defined, though. It's impossible to tell whether your algorithm or the one in many of the answers would give the correct output in general. Prem, any chance you can clarify the requirements? Commented May 4, 2018 at 9:45
  • 4
    My answer would be: "Requirements unclear. Consult with client." ;) They give you an expected output, but they don't explain the process or rules by which they arrived at it. Realistically, you should never guess at these kinds of details on the job. It's one thing to ask you to optimize given specific requirements; it's another to ask you to guess requirements from output. I kind of wonder if it was a trick question to see if you'd push for more info from them rather than accept it at face value. Commented May 4, 2018 at 10:24
  • 2
    @colxi Sorting only arrays with 10 items is pretty much meaningless Commented May 4, 2018 at 15:26

10 Answers 10

20

I would sort the array, and then iterate it, picking values from the begining and the end (inloop calculated offsets), in each iteration. A final check to odd arrays would complete the process.

let a = [1, 4, 5, 8, 3, 2, 6, 9, 7, 10];
a.sort((a, b) => a - b);
let b =[];
    
let l = a.length-1;  // micro optimization
let L = l/2;         // micro optimization
for(var i=0; i<L; i++) b.push( a[l-i] ,a[i] );
if(a.length%2) b.push( a[i] ); // add last item in odd arrays

console.log(b);

Result :

b =  [10, 1, 9, 2, 8, 3, 7, 4, 6, 5]

Algorithm bennefits:

  • Avoiding alterations in the original array (through pop and shift), improves the performance considerably.
  • Precalculating l and L before the loop , prevents the need of being calculated repeatedly in each iteration.
  • A single conditional cheking at the end of the procces, to handle odd arrays, slightly improves the speed.

I've prepared some PERFORMANCE TESTS, with some of the proposed algorithms : Original Array(10 items) and Big Array(1000 items)

Sign up to request clarification or add additional context in comments.

6 Comments

shift is O(n).
Doesn't sort() modify the original array?
you have a bug in second version (not sure about first one). Try inputting array: var a = [1, 2, 3, 4, 5];
@GiorgiMoniava you're right, the first version looks to always work, but the second version only works if there is an even number of items in a.
Regarding the jsPerf tests -- seems like the results are browser dependent. I get different fastest algos in Chrome and Firefox.
|
9

Here is one way to do it:

var arr = [1, 4, 5, 8, 3, 2, 6, 9, 7, 10];

// Sort the source array
arr.sort((a, b) => a - b);

// This will be the final result
var result = [];

// Create two pointers
var a = 0,
  b = arr.length - 1;

while (result.length < arr.length) {
  // Push the elements from start and end to the result array
  result.push(arr[b]);

  // Avoid bug when array is odd lengthed
  if (a !== b) {
    result.push(arr[a]);
  }

  a++;
  b--;
}

console.log(result);

The idea is to have two pointers (a and b) traversing the the sorted original array from both the directions and appending the elements in result.

Comments

3

If you assume that the array will be a set of sequential numbers (a good question to ask about the data) you can do this very quickly with no need to sort or mutate the original array(i.e O(n)):

var arr = [1, 4, 5, 8, 3, 2, 6, 9, 7, 10];

let a = arr.reduce((a, c, i) => {
  a[c > arr.length >> 1 ? (arr.length - c) << 1 : (c << 1) - 1] = c
  return a
}, [])
console.log(a)

2 Comments

This solution provides the best performance in the SEQUENTIAL NUMBERS scenario. Sadly fails with other scenarios.
It’s only sad @colxi if you find yourself in other scenarios. Since the request was for the optimal solution, we need to know as much about the data as possible.
2

Here's my answer, based off the intuition that you're taking from the front then the back repeatedly from the sorted array until you're empty. The trick is avoiding "max" and "min" which evaluate the entire array, and just sorting it once.

Many of the other answers will put an undefined into the array if the original array has an odd length. I would leave a comment on those but I do not have the reputation. This is why I bounds check twice per loop.

var arr = [1,4,5,8,3,2,6,9,7,10];
// Sort numerically (not lexicographically)
arr.sort((a, b) => a - b)
// The output array
var out = []
// Take from the front, then back until original array is empty
while (true) {
  if (arr.length == 0) break
  out.push(arr.pop())
  if (arr.length == 0) break
  out.push(arr.shift())
}
// Output answer
console.log(out)

6 Comments

shift() is O(n).
Ah, I didn't know that. Apparently it's implementation specific and some have implemented it differently while slowing down the indexing, but in the context of a job interview question it's probably O(n)
@Mark_M The one tagged as an answer (stackoverflow.com/a/50167179/1846597) when run in jsFiddle (jsfiddle.net/e4q3k3z1) with an input of ` a = [1, 4, 5, 8, 3, 2, 6, 9, 7, 10, 11];` will output (in google chrome) [11, 1, 10, 2, 9, 3, 8, 4, 7, 5, 6, undefined]
The second highest voted answer (stackoverflow.com/a/50167081/1846597), when placed into JSFiddle and run (jsfiddle.net/n8o3u97r) with an input of a = [1, 4, 5, 8, 3, 2, 6, 9, 7, 10, 11] will output [11, 1, 10, 2, 9, 3, 8, 4, 7, 5, 6, 6] (the second 6 is incorrect).
You're right. I missed the first and didn't notice the second. I
|
2

My solution for readability / no hidden magic:

// Input
var arr = [1,4,5,8,3,2,6,9,7,10];

// Sort
var arr1 = arr.sort((a,b) => (a - b));

// Compose
var arr2 = [];
for (var i = 0; i < arr1.length; i++) {
  arr2.push(arr1[(i % 2) === 0 
    ? arr1.length-1-(i/2)   // get from end half
    : (i-1)/2               // get from begin half
  ])
}

// Output
console.log(arr2); // = [10, 1, 9, 2, 8, 3, 7, 4, 6, 5] 

Their interview answer "that the algorithm is not optimal." is not unexpected ofcourse. I would inquire why they say that, and ask if its really benefitial to spend dollar time on dimes here. (or tens of dollars on cents, actually)

1 Comment

Wouldn’t be a good question to ask of the interviewer. O(n²) runtime is completely impractical in many (even most) real-world situations.
1

Alternative method with only one variable to increment:

var arr = [1, 4, 5, 8, 3, 2, 6, 9, 7, 10];

arr = arr.sort((a, b) => b - a);

var result = [];

var a = 0;
while (result.length < arr.length) {
    result.push(arr[a]);
    result.push(arr[arr.length - a - 1]);
    a++;
}

console.log(result);

Comments

1
var a = [1,4,5,8,3,2,6,9,7,10];
var b = a.sort((a, b) => a - b);
var c = a.sort((a, b) => a - b).reverse();
var d = [];

let e = a.length-1;
let f = e/2;

for(let i=0; i<f; i++)  d.push( b.pop(), c.pop() );

Replace b and c in the for loop with functions to test:

for(let i=0; i<f; i++) d.push( a.sort((a, b) => a - b).pop(), a.sort((a, b) => a - b).reverse().pop() );

Comments

0

sort the array and divide into two parts , now use reduce to put elements from the two arrays

//original array
var arr = [1, 4, 5, 8, 3, 2, 6, 9, 7, 10];
//sorting origina array in ascending order
var m = arr.sort(function(a, b) {
  return a - b;
});

// diving the sorted array in two parts
let getFirstSet = m.splice(0, arr.length / 2);
// now m containleft over items after the splice
// again sorted it in descending order to avoid back looping
let getSecondSet = m.sort(function(a, b) {
  return b - a;
});

//using reduce function
let newArray = getFirstSet.reduce(function(acc, curr, index) {
  // pushing element from second array containing 10,9,8,7,6
  acc.push(getSecondSet[index]);
  // pushing element from first array containing 1,2,3,4,5
  acc.push(getFirstSet[index]);
  return acc;
}, []); // [] is the initial array where elements will be pushed

console.log(newArray)

2 Comments

You can reverse the array for getSecondSet with .reverse() instead of doing a full sort.
@Ry︁ you are right , Using reverse just didn't strike my mind. Thanks for pointing it out
0

Another alternative view ... should this funky sort be done in place, like .sort is?

let input = [1, 4, 5, 8, 3, 2, 6, 9, 7, 10];
input.sort((a, b) => b - a).every((n, i, a) => (a.splice((i * 2 + 1), 0, a.pop()), (i * 2) < a.length));
console.log(input);

2 Comments

This looks O(n²).
still, it's the only "sort in place" solution - and I'd interpret the question to mean in place :p
0

Here is a quick solution, using ternary operators and modulo operator for toggling.

let arr = [1, 4, 5, 8, 3, 2, 6, 9, 7, 10];
let j = 0;
let k = arr.length - 1;

// sort array
arr.sort((a, b) => a - b);

let new_array = [];
for (let i in arr) {
  new_array[i] = i % 2 == 0 ? arr[k--] : arr[j++];
}
// prints array
console.log(new_array);

1 Comment

Shouldn’t use for/in to iterate over arrays. The order is unspecified and it picks up enumerable properties on the prototype. (Also causes i to be a string, which is an easy source of bugs.) Simply map/forEach/for.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.