Say I have an ordered array with lots of duplicates:
var array = [ 1, 1, 1, 1, 1,
2, 2, 2, 2, 2,
3, 3, 3, 3, 3,
4, 4, 4, 4, 4,
5, 5, 5, 5, 5, ];
I also have code to perform a binary search for the index of the closest value within a sorted array:
function binaryClosestIndexOf(array, value) {
var mid,
lo = 0,
hi = array.length - 1;
while (hi - lo > 1) {
mid = (lo + hi) >>> 1;
if (array[mid] > value)
hi = mid;
else
lo = mid;
}
if (value - array[lo] <= array[hi] - value)
return lo;
else
return hi;
}
Performing a few example searches unveils my issue:
binaryClosestIndexOf(array, 3.5);
> 14 // array[14] = 3
binaryClosestIndexOf(array, 3.50001);
> 15 // array[15] = 4
binaryClosestIndexOf(array, 3.9);
> 15 // array[15] = 4
binaryClosestIndexOf(array, 4);
> 19 // array[19] = 4
binaryClosestIndexOf(array, 4.49999);
> 19 // array[19] = 4
As we can see, there is no issue with the algorithm, it does return the closest value. But it returns an interesting mixture of indices, from the leftest to the rightest.
I want to get the leftest duplicate index. I could introduce an O(n) search after the binary search, iterating through each value in the array after until a value is found that is smaller than the current value. I don't want to do this.
Is there a way to elegantly perform a binary search that will end up with the leftest duplicate value? Bonus points for an algorithm for the rightest value, too!
Being a binary search, if you search for an exact value, you are not promised any location (rightest or leftest), it could be in the middle.
Since binary search works by having a sorted list and reducing by factors of two finding an edge index could be difficult.
I can think of two approaches
use a loop afterwards, I think you could make that to be expected O(log(n)) using randomness as you could say the final loop would be expected constant time O(1).
Use a second binary search (once you know the value) for the index closest to that number minus 0.000001 (in your list 4 cases this would always result in the second run searching for 3.99999, which would yield 15. Note: You should check in case the number (3.999999) was in the list and move right one place to get your value unless you can ensure a certain degree of rounding in the list. This would be 2*log(n) or O(log(n)).
If your list is long, I think the expected run time for option 2 would actually be longer than option 1 because 2*log(n) would be > log(n) + a constant unless you know there will be lots of duplicates.
You can use Array.prototype.indexOf()
return array.indexOf(array[value - array[lo] <= array[hi] - value ? lo : hi])
Related
I need to write a function to calculate minimal sum of the local maximum of the subarrays of an array where every item is a positive integer with possible duplicates.
For example, we have an array [2, 3, 1, 4, 5, 6] and the number of sub arrays is 3. We want to get the min sum of the local max of the sub arrays. What that means is that, for example, one possible way to divide the current array into 3 sub arrays is [[2,3], [1], [4,5,6]] and the local maxs for each subarray is 3, 1, 6 respectively. And the sum of the local maxs is 3 + 1 + 6 = 10. For this particular array, [2, 3, 1, 4, 5, 6], this is the minimal sum of all its possible sub-array variations.
My approach is to first get all the possible sub-array variations for a given array. and get the min sum of them.
function getSubarrays(array, numOfSubarray) {
const results = []
const recurse = (index, subArrays) => {
if (index === array.length && subArrays.length === numOfSubarray) {
results.push([...subArrays])
return
}
if (index === array.length) return
// 1. push current item to the current subarray
// when the remaining items are more than the remaining sub arrays needed
if (array.length - index - 1 >= numOfSubarray - subArrays.length) {
recurse(
index + 1,
subArrays.slice(0, -1).concat([subArrays.at(-1).concat(array[index])])
)
}
// 2. start a new subarray when the current subarray is not empty
if (subArrays.at(-1).length !== 0)
recurse(index + 1, subArrays.concat([[array[index]]]))
}
recurse(0, [[]], 0)
return results
}
function getMinSum(arrays) {
return arrays.reduce(
(minSum, array) =>
Math.min(
minSum,
array.reduce((sum, subarray) => sum + Math.max(...subarray), 0)
),
Infinity
)
}
getMinSum(getSubarrays([[2,3], [1], [4,5,6]], 3)) // 10
However, I think the time complexity for my solution is really high. My guess is that it is on the order of 2^n (feel free to correct me if I am wrong). I wonder if there is a more efficient way to calculate this.
The first thing that comes to mind is dynamic programming.
Let dp[i][j] be the minimal sum of local maximums of array[0..i] (left border included, right border excluded) divided into j subarrays. dp[0][0] = 0 (this is an initial value for empty array[0..0]), and for simplicity initialise all other dp[i][j] with some large enough number to denote meaningless of not calculated values (larger than sum of elements in array in this case is enough).
Your answer obviously is the value of dp[array.length][numOfSubarray].
How do you calculate values of the dp? Actually pretty easy. dp[i][j] is the minimum among dp[k][j - 1] + max(array[k..i]) (where k < i). Let's analyse this formula:
dp[k][j - 1] + max(array[k..i])
# ^ ^
# This term is the minimal sum of local maximums
# of array[0..k] divided into j-1 subarrays.
# |
# This term is maximum of your new j-th subarray.
Also make sure that all the dp[k][j - 1] were calculated beforehand (for example by calculating dp[i][1] at first, then dp[i][2], then dp[i][3] and so on).
Now let's write it altogether (naive approach just for now).
dp[0][0] = 0
for newSubarrayNumber in range(1, numOfSubarray + 1):
for previousEnd in range(0, array.length):
for newEnd in range(previousEnd + 1, array.length + 1):
# Formula from above.
dp[newEnd][newSubarrayNumber] =
min(dp[newEnd][newSubarrayNumber],
dp[previousEnd][newSubarrayNumber - 1] + max(array[previousEnd..newEnd]))
# Your answer.
print(dp[array.length][numOfSubarray])
As you can see we've got polynomial complexity, now it's O(numOfSubarray * array.length^3) (two array.length for two nested loops and one more because of max(array[previousEnd..newEnd])).
Also we can optimise our algorithm. It makes no sense to always calculate min(array[previousEnd..newEnd]), because previously we did that for newEnd - 1 and we can reuse that value. That brings us to the following algorithm:
for newSubarrayNumber in range(1, numOfSubarray + 1):
for previousEnd in range(0, array.length):
maxElement = 0
for newEnd in range(previousEnd + 1, array.length + 1):
# maxElement replaces max(array[previousEnd..newEnd]).
maxElement = max(array[newEnd - 1], maxElement)
dp[newEnd][newSubarrayNumber] =
min(dp[newEnd][newSubarrayNumber],
dp[previousEnd][newSubarrayNumber - 1] + maxElement)
That's O(numOfSubarray * array.length^2) (just because of loops, no extra complexity multiplier).
I believe one can optimise it even more (maybe using some advanced data structures), feel free to comment. Also even better approach for this particular problem could be some sort of greedy algorithm (like having small subarrays closer to border of an array and one big chunk in the center but that needs to be proven).
We can have O(n^2) by using a dynamic program where the state is (1) the index of the rightmost element considered so far, and (2) the length of the subarray ending at (1).
The information we need to store about those two things is deterministic -- we need the maximum value in subarray (2) and the best solution overall ending at (1).
Then, to consider the next element, we have two choices for each length in (2): either add the element to the subarray, or start a new subarray.
For each element, we would examine O(n) lengths for a total of O(n^2).
I'm doing a kata for Codewars that puts two arrays of numbers up against each other. The "opponent" array always has on average larger numbers than the "codewarrior" array, and both arrays are always the same length. What I need to do is find the most efficient way to get victories (codewarrior[x] > opponent[y]), stalemates (codewarrior[x] == opponent[y]) if a victory isn't possible, or defeats (codewarrior[x] < opponent[y]) if neither a victory or stalemate is possible. Before anyone asks, I don't want the solution to the kata, only how to get my program to work.
function codewarResult(codewarrior, opponent) {
codewarrior = codewarrior.sort(function(a, b){return a - b});
opponent = opponent.sort(function(a, b){return a - b});
console.log("Ordered codewarrior array: ", codewarrior);
console.log("Ordered opponent array:", opponent);
let victories = 0;
let stalemates = 0;
let defeats = 0;
let x = 0;
while(x < codewarrior.length) {
let y = 0;
while(codewarrior[x] > opponent[y]) { // Victory loop (most preferable)
if(codewarrior[x] <= opponent[y + 1]) {
victories++;
codewarrior.splice(codewarrior.indexOf(x), 1, null); // I replace the value to null so the array retains it's length
opponent.splice(opponent.indexOf(y), 1);
console.log(`Codewarrior array after victory: `, codewarrior);
console.log(`Opponent array after defeat: `, opponent);
}
y++;
}
if(codewarrior[x] == opponent[y]) { // Stalemate checker (second most preferable)
stalemates++;
codewarrior.splice(codewarrior.indexOf(x), 1, null);
opponent.splice(opponent.indexOf(y), 1);
console.log(`Codewarrior array after stalemate: `, codewarrior);
console.log(`Opponent array after stalemate: `, opponent);
}
while(codewarrior[x] < opponent[y]) { // Defeat loop (least preferable)
if(codewarrior[x] >= opponent[y + 1]) {
defeats++;
codewarrior.splice(codewarrior.indexOf(x), 1, null);
opponent.splice(opponent.indexOf(y), 1);
console.log(`Codewarrior array after defeat: `, codewarrior);
console.log(`Opponent array after victory: `, opponent);
}
y++;
}
x++;
}
console.log(`victories: ${victories}, stalemates: ${stalemates}, defeats ${defeats}.`);
if(victories > defeats) {
return "Victory";
} else if(victories === defeats) {
return "Stalemate";
} else {
return "Defeat";
}
}
Above I order the two arrays ordered from smallest to largest. I then have one large while loop that iterates the "codewarrior" array, and two while loops and an if statement inside iterating the "opponent" array each time that checks for a possible victory first, a stalemate second, and a defeat last. It checks for the most efficient way to get a victory (the values of codewarrior[x] > opponent[y] as close as possible) a stalemate, or a defeat (the values of codewarrior[x] < opponent[y] as far apart as possible).
When I try
codewarResult([4,3,2,1], [5,4,3,2]);
I expect the two arrays to have 4 "battles" against each other, with the values of "codewarrior" slowly becoming null as the large while loop iterates through it. Instead I get this behavior in the console:
Ordered codewarrior array: [ 1, 2, 3, 4 ]
Ordered opponent array: [ 2, 3, 4, 5 ]
Codewarrior array after stalemate: [ null, 2, 3, 4 ]
Opponent array after stalemate: [ 2, 3, 4 ]
Codewarrior array after victory: [ null, null, 3, 4 ]
Opponent array after defeat: [ 2, 3 ]
Codewarrior array after stalemate: [ null, null, 3, null ]
Opponent array after stalemate: [ 2 ]
victories: 1, stalemates: 2, defeats 0.
Why is there only 3 battles being recorded and the 3 in the "codewarriors" array being skipped? Why does the first battle result in a stalemate when it should be a defeat (1 vs 5)?
Couple of things...
Your outer loop goes through all of your codeWarrior numbers... That makes sense...
Your inner loops for victory and defeat though are odd... it looks like if codeWarrior[0] is greater than opponent[0] then it should be a victory right? But your victories counter only gets incremented if codeWarrior[0] will lose/stalemate against opponent[1]. Same deal for defeat... if codeWarrior[0] loses to opponent[0], it only counts a defeat if codeWarrior[0] would win/stalemate with opponent[1].
As for not having the number of battles you think you should... look here:
if(codewarrior[x] == opponent[y]) { // Stalemate checker (second most preferable)
stalemates++;
codewarrior.splice(codewarrior.indexOf(x), 1, null);
opponent.splice(opponent.indexOf(y), 1);
You are probably meaning to remove a single number from codeWarrior at index x and single number from opponent at index y. By using indexOf() however, you are saying find the index in codeWarrior where the warrior's number == x and remove that element. If the value of X isnt found as a number in the codeWarrior at all,then nothing happens. Same for opponent and indexOf(y). As a result, you are having most likely the wrong items being removed from the arrays or not removed if they are supposed to.
How to check if an element is present in an array or not in JavaScript without using for loop or any method of array like map, reduce?
let numList= [1,2,3,6,9,2,-8,20]; I want to check for 9 and 30, if it exists or not without using for loop or any array method
I suppose one option would be to convert the array to a Set and check Set.has:
let numList= [1,2,3,6,9,2,-8,20];
const set = new Set(numList);
console.log(set.has(9));
console.log(set.has(30));
Checking whether a Set has an element has complexity of O(1), which is lower complexity than any of the array methods or for loops, which are O(N) or O(log N), so when you have a very large array and you want to check whether it has certain elements, converting it to a Set first can be a good idea.
You can convert the array to string using JSON.stringify and use String includes to check if the string contains the specific searched value
let numList = [1, 2, 3, 6, 9, 2, -8, 20];
let m = JSON.stringify(numList)
console.log(m.includes(-8))
If you can use a while loop you may be able to implement a binary search. Since it was for an interview question I wouldn't be surprised if this is along the lines of what they were looking for.
Here is the traditional algorithm in psuedocode, borrowed from Rosetta Code
BinarySearch(A[0..N-1], value) {
low = 0
high = N - 1
while (low <= high) {
// invariants: value > A[i] for all i < low
value < A[i] for all i > high
mid = (low + high) / 2
if (A[mid] > value)
high = mid - 1
else if (A[mid] < value)
low = mid + 1
else
return mid
}
return not_found // value would be inserted at index "low"
}
Please help, I've been looking for an answer for far too long.
I'm trying to create an array using push method to insert the numbers
0 to 10 into positions 0 through 10 of the numbers array you just initialized above.
I did this:
var numbers = [];
for(var i = 0; i < 10; i++) {
numbers.push(i);
console.log(numbers);
And got this result, which I think is correct but not 100% sure:
[ 0 ]
[ 0, 1 ]
[ 0, 1, 2 ]
[ 0, 1, 2, 3 ]
[ 0, 1, 2, 3, 4 ]
[ 0, 1, 2, 3, 4, 5 ]
[ 0, 1, 2, 3, 4, 5, 6 ]
[ 0, 1, 2, 3, 4, 5, 6, 7 ]
[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
0
Then I am to test the array push method by printing the sum of the values at
position 3 and 6 of the array (use the console.log() function to print to the console).
The outputted value should be 9.
I am so stuck on this point and cannot find a sample anywhere of how to accomplish this. I thought it might be something like:
console.log(numbers(sum[3, 6]);
If you want to have a sum() function, then try the following:
function sum(x, y) {
return x + y;
}
console.log(sum(numbers[3], numbers[6]));
Here's a Fiddle: https://jsfiddle.net/7181h1ok/
To sum the values of two indices of an array, you use the + addition operator in the following fashion:
var numbers = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var sum = numbers[3] + numbers[6]; //adds the value in index 3 of the numbers array to the value in index 6 of the numbers array.
console.log(sum); //prints the sum to the console.
As a note, if you are unfamiliar with JavaScript and/or its operators, there's useful documentation at w3schools that can get you started.
First, let's convert your code to a little bit better style:
const numbers = [];
for (let i = 0; i < 10; i++) {
numbers.push(i);
console.log(numbers);
}
Note: I made numbers a const instead of a var, since you don't change it. I also made i a let binding instead of a var. In general, var is a legacy and should never be used. Use const instead if at all possible, otherwise use let.
Also, I inserted a space after the for keyword. It is generally recommended to separate the parentheses which enclose the header of a control structure keyword (if, while, for, etc.) with a space, to make it visually distinct from the parentheses for the argument list of a function call, which has no space.
Secondly: Your result is not correct. (Hint: how many numbers are the numbers 0 to 10?) It should include the numbers 0 to 10, but it only includes the numbers 0 to 9. You have what is generally called an off-by-one-error. These errors are very common when dealing with trying to manage loop indices manually. This is the fix:
const numbers = [];
for (let i = 0; i <= 10; i++) {
// ↑
numbers.push(i);
console.log(numbers);
}
Most modern programming languages have better alternatives than dealing with loop indices manually in the form of higher-level abstractions such as iterators, maps, and folds. Unfortunately, ECMAScript doesn't have a Range datatype, otherwise this could simply be expressed as converting a Range to an Array.
If ECMAScript did have a Range datatype, it could for example look like one of these:
const numbers = Range(0, 10).toArray()
const numbers = Array.from(Range(0, 10))
Here is an alternative for creating the numbers Array that doesn't involve manually managing loop indices, but still requires knowing that 0 to 10 are 11 numbers:
const numbers = Array.from({length: 11}, (_, i) => i)
If you want to add the numbers at indices 3 and 6, you can simply dereference indices 3 and 6 and add the results:
console.log(numbers[3] + numbers[6])
In the comments, you asked how you would add up all numbers in the Array. Combining the elements of a collection using a binary operator is called a fold or reduce, and ECMAScript supports it out-of-the-box:
console.log(numbers.reduce((acc, el) => acc + el));
Note how there is no explicit loop, thus no explicit management of loop indices. It is simply impossible to make an off-by-one-error here.
It will be: console.log((+numbers[3]) + (+numbers[6]));
Typically, it should be console.log(numbers[3] + numbers[6]); but there's sometimes a issue that results in 36 instead of 9. The extra + signs tell javascript that it is a number.
NOTE: Remember that the first number is numbers[0]. The array starts with 0!
I'm wondering if some of you understand how the Fisher-Yates shuffle works and can explain it to me. so I found this Fisher-Yates Shuffle code online:
public function Main() {
var tempArray:Array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
ShuffleArray(tempArray);
trace(tempArray);
}
public function ShuffleArray(input:Array)
{
for (var i:int = input.length-1; i >=0; i--)
{
var randomIndex:int = Math.floor(Math.random()*(i+1));
var itemAtIndex:Object = input[randomIndex];
input[randomIndex] = input[i];
input[i] = itemAtIndex;
}
}
That code works perfectly but I'm still confused
I changed the loop to "input.length" and it doesn't work well, I still got "0" values sometimes. I have no idea why should I use "input.length-1" instead of "input.length"
At the randomize section, why should I randomize the index from 0 to the value (i+1), why don't we just randomize it from 0 to (i) instead?
If some of you understand it, can you please explain it to me?
Thank you so much
Js's array index starts at 0, so array a with length n 's last element is a[n -1].
Math.random return a value from 0 to 0.9999...., but not include 1(range at [0, 1)), so Math.random()* (i + 1), would have a value from 0 to i + 0.999999......, but not i + 1(range [0, i+1)), and use Math.floor to cut the dot parts to get a Integer, so we get a number in range [0, i].
let me explain with an example with negation lets says the array size is 10.
1)if we use index.length line 3 in the for loop will read
input[randomIndex] = input[i] i.e.
input[randomIndex] = input[10];
but since javascript has 0 based arrays ,it has values from index 0 to 9 .index 10 will be out of bounds .Hence we should shuffle from last element(index 9 only)
2)for your second question if we use i instead of i+1.
lets say you are in the 1st iteration of the loop for index 9(will hold true for other iterations also).
Here i is 9 as seen above .we want 9th index to be shuffled from any one of the indices from 0 to 9
Math.random will return from 0 to .999 and Math.floor will lower bound it so in our case,maximum value will be .999 * (9+1) = 9.99 .Math.floor will lower bound it to 9.So range is [0,9]
incase we used i maximum possible value would be 8 i.e, Range[0,8]
Hence we use i+1 since we want values from [0,9]