I have n (n between 1 and 100) sorted number arrays, each with m elements (m around 1000 in my case). I want to merge them into a single sorted array.
I can think of two possibilities for doing this:
1.Use a two arrays merging algo (like merge() function below from http://www.nczonline.net/blog/2012/10/02/computer-science-and-javascript-merge-sort/) and applying it iteratively (1st and 2nd, then merge of 1st-2nd and 3rd, etc)
function merge(left, right) {
var result = [],
il = 0,
ir = 0;
while (il < left.length && ir < right.length){
if (left[il] < right[ir]){
result.push(left[il++]);
} else {
result.push(right[ir++]);
}
}
return result.concat(left.slice(il)).concat(right.slice(ir));
}
Generalize merge() function to n arrays simultaneously. At each iteration, I would pick the min value of the n first values not yet processed and append it to the result.
Are these two algo equivalent in terms of complexity ? I have the feeling that both algo are in o(m*n). Am I right ?
Are there any performance consideration to take one algo rather than the other ? I have the feeling that 1 is simpler than 2.
Merge n arrays using priority queue (based on binary heap, for example).
Overall element count is m*n, so algorithm complexity is O(m * n * Log(n)).
algorithm sketch:
Add numbers 1..n to priority queue, using 1st element of every
array as sorting key
(you may also use pairs (first element/array number).
At every step -
J = pop_minimum
add current head of Jth array to result
move head of Jth array to the right
if Jth array is not exhausted, insert J in queue (with new sorting key)
1st algoritm complexity is
2*m + 3*m+ 4*m+...+n*m = m * (n*(n-1)/2-1) = O(n^2 * m)
That's an old question, but for the sake of posterity:
Both algos are indeed O(n*m). In algo 1, you have to remerge for each m array. In algo 2, you do just one big merge, but picking out the minimum from m arrays is still linear.
What I did instead was implement a modified version of merge sort to get O(mlogn).
The code is there on GitHub https://github.com/jairemix/merge-sorted if anyone needs it.
Here's how it works
The idea is to modify algo 1 and merge each array pairwise instead of linearly.
So in the first iteration you would merge array1 with array2, array3 with array4, etc.
Then in the second iteration, you would merge array1+array2 with array3+array4, array5+array6 with array7+array8, etc.
For example:
// starting with:
[1, 8], [4, 14], [2, 5], [3, 7], [0, 6], [10, 12], [9, 15], [11, 13]
// after iteration 1:
[1, 4, 8, 14], [2, 3, 5, 7], [0, 6, 10, 12], [9, 11, 13, 15]
// after iteration 2
[1, 2, 3, 4, 5, 7, 8, 14], [0, 6, 9, 10, 11, 12, 13, 15]
// after iteration 3
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
In JS:
function mergeSortedArrays(arrays) {
// while there are still unmerged arrays
while (arrays.length > 1) {
const result = [];
// merge arrays in pairs
for (let i = 0; i < arrays.length; i += 2) {
const a1 = arrays[i];
const a2 = arrays[i + 1];
// a2 can be undefined if arrays.length is odd, so let's do a check
const mergedPair = a2 ? merge2SortedArrays(a1, a2) : a1;
result.push(mergedPair);
}
arrays = result;
}
// handle the case where no arrays is input
return arrays.length === 1 ? arrays[0] : [];
}
Notice the similarity to merge sort. In fact in merge sort, the only difference is that n = m, so you're starting further back with m presorted arrays of 1 item each. Hence the O(mlogm) complexity of merge sort.
Related
I have an array of 3 value arrays, and I need to convert them into a set of arrays where each array is made up of the values which were common between arrays, so my input of
[[2,3,9], [1,4,7], [3,6,9], [1,7,5], [7,5,8], [9,6,10], [3,6,10], [1,8,5]]
becomes
[[2,3,6,9,10],[1,4,5,7,8]]
Order is not important. I've found similar questions such as Group same values in an array within an array in JS but it seems slightly different to my case, but I imagine using reduce is the way to go, but I don't entirely understand how. I have tried creating an object using the following format, but couldn't get from there to an answer:
{
vertex: 3,
triangles: [2,3,9], [3,6,9], [3,6,10]
}
Here is one algorithm. Take first item from array and check first item array has any common array. If they have common item, then merge it and move the merged array to first item of array. If no common item, then add to result array.
const merge = (arr) => {
const result = [];
while (arr.length > 0) {
let first = arr.shift();
const idx = arr.findIndex((items) =>
items.some((item) => first.includes(item))
);
if (idx !== -1) {
first = first.concat(arr[idx]);
arr.splice(idx, 1);
arr.unshift(first);
} else {
result.push(first);
}
}
return result.map(items => [...new Set(items)]);
};
const data = [
[2, 3, 9],
[1, 4, 7],
[3, 6, 9],
[1, 7, 5],
[7, 5, 8],
[9, 6, 10],
[3, 6, 10],
[1, 8, 5],
];
console.log(merge(data));
i know this is an annoying question, but can someone explain me why splice method is executing in a weird way. Please explain me why the expected output is different from the actual result.
let numbers = [15, 12, 15, 3, 5, 4, 6];
// Get the indexes of the numbers greater than 5
let indexes = numbers.reduce((arr, current, index) => {
if (current > 5) {
arr.push(index);
}
return arr;
}, []);
// Loop through the indexes while removing the indexes from the numbers array
indexes.forEach((element) => {
numbers.splice(element, 1);
});
// expected result: numbers = [ 3 , 5, 4 ];
// actual result: numbers = [ 12, 3, 4, 6 ]
.splice() changes the array it is used on. You might have already known this, but if you debug your code using a console.log, you'll see what's happening; in short, your first number > 5 is 15. 15 is at index 0, so you remove index 0. However, as splice changes the array it is used on, 12 becomes index 0, and then the second 15 index 1, and so on and so forth. So for example, your code has the following indexes: 0, 1, 2, 6.
The first time you remove index 0: [12, 15, 3, 5, 4, 6]
Then you remove index 1: [12, 3, 5, 4, 6]
Then you remove index 2: [12, 3, 4, 6]
Then you remove index 6, which doesn't exist: [12, 3, 4, 6]
The better way of accomplishing that goal is with .filter(). Filter creates a new array of all items that pass the test given in the callback, so:
numbers = numbers.filter((num) => num < 6);
That's the arrow function expression shorthand to return only numbers less than 6.
splice actually removes the item in place. It does not create any copy of array. In your case after reduce operation, indexes would be
[0, 1, 2, 6]
and then while iterating and splicing, in first iteration array with position 0 is removed so array becomes
numbers = [12, 15, 3, 5, 4, 6];
and its length is also reduced. On next iteration of forEach array element with index position 1 is removed which is 15 in our case. So after second iteration array becomes
numbers = [12, 3, 5, 4, 6];
Similarly in next subsequent iteration you will have result like
[12, 3, 4, 6]
As someone has mentioned the problem is with applying changes over an array that is mutated in every iteration.
I assume the example is for learning purposes as it would have been easier to write it like:
let numbers = [15, 12, 15, 3, 5, 4, 6]
numbers.filter(elem => elem <= 5)
In any case, and following the demonstration code, it would be good to stress the dangerous of mutations that is prone to spooky effects. I have rewritten the code in a more functional style:
let numbers = [15, 12, 15, 3, 5, 4, 6];
// Get the indexes of the numbers greater than 5
let indexes = numbers.reduce((arr, current, index) => {
if (current > 5) {
return arr.concat(index);
}
return arr;
}, []);
// Instead of removing we create a new array filtering out the elements we dont want
let filteredNumbers = numbers.filter((_,index) => indexes.indexOf(index) === -1)
console.log(filteredNumbers)
// expected result: numbers = [ 3 , 5, 4 ];
// actual result: numbers = [ 3, 5, 4 ]
Here's the problem statement
Given a sequence of n integers arr, determine the lexicographically smallest sequence which may be obtained from it after performing at most k element swaps, each involving a pair of consecutive elements in the sequence.
Note: A list x is lexicographically smaller than a different equal-length list y if and only if, for the earliest index at which the two lists differ, x's element at that index is smaller than y's element at that index.
I'm trying to wrap my head around the what the phrase "lexicographically smaller" implies based on the above note. As I understand the English meaning of it, we are basically talking about a dictionary order. Let me explain my question with an example.
Here an example
Example 1
n = 3
k = 2
arr = [5, 3, 1]
output = [1, 5, 3]
We can swap the 2nd and 3rd elements, followed by the 1st and 2nd elements, to end up with the sequence [1, 5, 3]. This is the lexicographically smallest sequence achievable after at most 2 swaps.
The above example came with the problem statement. But wouldn't the lexicographically smallest sequence instead be [1, 3 , 5] instead of the provided answer (output) [1, 4, 3]?
Here's another
Example 2
n = 5
k = 3
arr = [8, 9, 11, 2, 1]
output = [2, 8, 9, 11, 1]
We can swap [11, 2], followed by [9, 2], then [8, 2].
Again, the answer I can see in this case is [1, 2, 8, 11, 9] (after three swaps), which is the smallest lexicographic ally and the provided answer is output = [2, 8, 9, 11, 1].
Am I reading the the problem statement incorrectly?
The problem statement says that we are allowed to make at most k swaps of consecutive elements in the process to get a lexicographically smallest sequence. The following explanation can help us understand it better. [NOTE: keep in mind that you can only swap consecutive elements]
n = 3
k = 2
arr = [5, 3, 1]
output = [1, 5, 3]
Approach:
swap 1: swap 3 and 1 (3<->1) ====> [5,1,3]
swap 2: swap 5 and 1 (5<->1) ====> [1,5,3] #RESULT
n = 5
k = 3
arr = [8, 9, 11, 2, 1]
output = [2, 8, 9, 11, 1]
Approach:
swap 1: swap 11 and 2 (11<->2) ===> [8, 9, 2, 11, 1]
swap 2: swap 9 and 2 (9<->2) ===> [8, 2, 9, 11, 1]
swap 2: swap 8 and 2 (9<->2) ===> [2, 8, 9, 11, 1] #RESULT
So, you can never get [1, 2, 8, 11, 9] with 3 swaps of consecutive elements. 2 is the smallest element that you can move to the first index with at most 3 swaps of consecutive elements, but yes if k=4 then we can bring 1 to the first position.
So, the thing that you are missing is that the rule is you are allowed to swap at most k elements but the elements those you swap should be consecutive to each other.
Given an array of integers, where the values should be sorted in the following order:
if we have an array
[1, -1, -3, 9, -2, -5, 4, 8,]
we must rearrange it this way: largest number, smallest number, 2nd largest number, 2nd smallest number, ...
[9, -5, 8, -3, 4, -2, 1, -1 ]
I get the first largest and smallest numbers, but can't figure out how to make it dynamic for all values in the array.
I know that I must take two variables, say firstSmallest and firstLargest and point them to the first and last index of the array respectively, run a loop, which I do already in the code below, and store value into new array by incrementing firstSmallest and decrementing firstLargest, but couldn't implement into code.
let unsortedArr = [1, 5, 8 , 7, 6, -1, -5, 4, 9, 5]
let output = [];
function meanderArray(unsorted){
let sorted = unsorted.sort((a, b) => a-b);
let firstSmallest = sorted[0];
let firstLargest = sorted[unsorted.length-1];
for(let i = 0; i <= sorted.length; i++){
//I should increment firstSmallest and decrement firstLargest numbers and store in output
}
return output;
}
meanderArray(unsortedArr);
console.log(output);
You could take a toggle object which takes the property of either the first item or last from an array and iterate until no more items are available.
function meanderArray([...array]) {
const
result = [],
toggle = { shift: 'pop', pop: 'shift' };
let fn = 'shift';
array.sort((a, b) => a - b);
while (array.length) result.push(array[fn = toggle[fn]]());
return result;
}
console.log(...meanderArray([1, 5, 8, 7, 6, -1, -5, 4, 9, 5]));
You can sort an array by descending, then logic is the following: take first from start and first from end, then second from start-second from end, etc.
let unsortedArr = [1, 5, 8 , 7, 6, -1, -5, 4, 9, 5]
let output = [];
function meanderArray(unsorted){
let sorted = unsorted.sort((a, b) => b-a);
let output = []
for(let i = 0; i < sorted.length/2; i++){
output.push(sorted[i])
if(i !== sorted.length - 1 - i){
output.push(sorted[sorted.length - 1 - i])
}
}
return output;
}
let result = meanderArray(unsortedArr);
console.log(result);
You can sort, then loop and extract the last number with pop() and extract the first number with shift().
let unsortedArr = [1, -1, -3, 9, -2, -5, 4, 8,]
let output = [];
function meanderArray(unsorted){
let sorted = unsorted.sort((a, b) => a - b);
for(let i = 0; i < unsortedArr.length + 2; i++){
output.push(sorted.pop());
output.push(sorted.shift());
}
console.log(output);
return output;
}
meanderArray(unsortedArr);
Fastest Meandering Array method among all solutions mentioned above.
According to the JSBench.me, this solution is the fastest and for your reference i have attached a screenshot below.
I got a different approach, but i found that was very close to one of above answers from elvira.genkel.
In my solution for Meandering Array, First I sorted the given array and then i tried to find the middle of the array. After that i divided sorted array in to two arrays, which are indices from 0 to middle index and other one is from middle index to full length of sorted array.
We need to make sure that first half of array's length is greater than the second array. Other wise when applying for() loop as next step newly created array will contains some undefined values. For avoiding this issue i have incremented first array length by one.
So, always it should be firstArr.length > secondArr.length.
And planned to create new array with values in meandering order. As next step I created for() loop and try to push values from beginning of the first array and from end of the second array. Make sure that dynamically created index of second array will receive only zero or positive index. Other wise you can find undefined values inside newly created Meandering Array.
Hope this solution will be helpful for everyone, who loves to do high performance coding :)
Your comments and suggestions are welcome.
const unsorted = [1, 5, 8, 7, 6, -1, -5, 4, 9, 5];
const sorted = unsorted.sort((a,b)=>a-b).reverse();
const half = Math.round(Math.floor(sorted.length/2)) + 1;
const leftArr = sorted.slice(0, half);
const rightArr = sorted.slice(half, sorted.length);
const newArr = [];
for(let i=0; i<leftArr.length; i++) {
newArr.push(leftArr[i]);
if (rightArr.length-1-i >= 0) {
newArr.push(rightArr[rightArr.length-1-i]);
}
}
In order to do this, the insert function will need to make room for value by moving items that are greater than value to the right. It should start at rightIndex, and stop when it finds an item that is less than or equal to value, or when it reaches the beginning of the array. Once the function has made room for value, it can write value to the array.
var insert = function(array, rightIndex, value) {
var key = value;
for(var i = rightIndex; array[i] > value ; i = i - 1)
{
array[rightIndex + 1] = array[rightIndex];
}
array[i+1] = value;
};
Why My this function doesn`t work correctly after I input this array !
var array = [3, 5, 7, 11, 13, 2, 9, 6];
It Shows this result:
insert(array, 4, 2);
2,5,7,11,13,13,9,6
The line that shuffles items to the right needs editing
Change your line that currently reads:
array[rightIndex + 1] = array[rightIndex];
to read as follows:
array[i + 1] = array[i];
In your code as currently written, the rightIndex'th item is repeatedly being pasted into the rightIndex+1'th position. This is why you are getting two 13s in your result.
So your code is almost right already!
Explicitly check for the beginning of the array
You can change the for loop to ensure you never go left of the start of the array.
for(var i = rightIndex; i>=0 && array[i] > value ; i = i - 1)
Inserting the i>=0 && means that when i falls below 0, Javascript knows to end the loop. Otherwise it will attempt to read the element array[-1], which is undefined. Luckily the test will still work, because a comparison of any number (even negative) with "undefined", will be false. But it is much better style to explicitly test for this rather than rely on the quirk of the language. The reason is that if you were to apply the same algorithm in another language, the array[-1] might be an error.
inserting value into array works correctly with sorted array, in this case your array is not sorted.
var array = [3, 5, 7, 11, 13, 2, 9, 6];
you need to first sort the array, for that use this
var array = [3, 5, 7, 11, 13, 2, 9, 6];
array = arra.sort(function (a, b) { return a - b; });
array = array.sort((a, b) => a - b);
//outputs: 2, 3, 5, 6 , 7, 9, 11, 13
then you can insert into this array as sorted
For that you can try something like this
var array = [2, 3, 5, 6 , 7, 9, 11, 13];
var element = 2;
function insert(element, array) {
array.push(element);
array.sort(function(a, b) {
return a - b;
});
return array;
}
//outputs: 2, 2, 3, 5, 6 , 7, 9, 11, 13