How to find index of element in javascript if it present? - javascript

Could you please tell me how to find the index. I have a problem
Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.
You may assume no duplicates in the array.
Example 1:
Input: [1,3,5,6], 5
Output: 2
Example 2:
Input: [1,3,5,6], 2
Output: 1
Example 3:
Input: [1,3,5,6], 7
Output: 4
I tried like this
var searchInsert = function(nums, target) {
if (target > nums[nums.length - 1]) return nums.length;
if (target < nums[0]) return 0;
let start = 0,
end = nums.length - 1,
mid = Math.floor((start + end) / 2);
while (start < end) {
if(nums[mid] == target) return mid
if(nums[mid] > target) end = mid -1;
if(nums[mid] < target) start = mid +1;
mid = Math.floor((start + end) / 2);
}
return nums.length %2 == 0 ? mid +1 : mid
};
my test case fails
Input
[1,3]
2
Output
2
Expected
1
my test case only fail when an element is not found I want to insert in perfect index
updated Answer
var searchInsert = function (nums, target) {
if (target > nums[nums.length - 1]) return nums.length;
if (target === nums[nums.length - 1]) return nums.length-1;
if (target < nums[0]) return 0;
if (target ===nums[0]) return 0;
let start = 0,
end = nums.length - 1,
mid = Math.floor((start + end) / 2);
while (start <= end) {
if(nums[mid] == target) return mid
if(nums[mid] > target) end = mid -1;
if(nums[mid] < target) start = mid +1;
mid = Math.floor((start + end) / 2);
}
return nums[mid] > target ? mid : mid + 1;
};
Thanks for the help this code works perfectly all test case

Problem is with your last statement
return nums.length %2 == 0 ? mid +1 : mid
So in the last case your mid = 1 as since the length of array is two you're adding mid + 1
One approach is to return the value from while itself in case your target isn't found in array, on condition when start === end
const searchInsert = function(nums, target) {
if (target > nums[nums.length - 1]) return nums.length;
if (target <= nums[0] || nums.length === 0) return 0;
let start = 0,
end = nums.length - 1,
mid = Math.floor((start + end) / 2);
while (start < end) {
if (nums[mid] == target) return mid
if (nums[mid] > target) end = mid - 1;
if (nums[mid] < target) start = mid + 1;
if(start >= end){
return nums[mid] > target ? mid : mid + 1
}
mid = Math.floor((start + end) / 2);
}
};
console.log(searchInsert( [1,3,5,6], 5)) // 2
console.log(searchInsert([1,3,5,6], 2)) // 1
console.log(searchInsert([1,3,5,6], 7)) // 4
console.log(searchInsert([1,3], 2)) // 1
console.log(searchInsert([1,2,3,5], 4)) // 3
console.log(searchInsert([1,2,3,4,5,7,9,10,15], 8)) //6
console.log(searchInsert([0, 1, 3, 4], 2)) // 2
console.log(searchInsert([1],1)) // 0
console.log(searchInsert([0],1)) // 1
console.log(searchInsert([],1)) // 0

What you can try is using the <Array>.indexOf(n) and <Array>.sort() like so:
let arr = [1,3,5,6]
const needle = 0
function searchInsert (arr, needle) {
let index = arr.indexOf(needle)
if (index === -1) {
arr.push(needle)
arr = arr.sort((x, y) => x - y)
index = arr.indexOf(needle)
}
return index
}

Related

How to make the checking more robust and shorter?

I wrote this code, but I dont uderstand why it works this way, especially using the third and fourth examples as input. Why the 'middle' position remains so behind? -in the number 5 (or index 2) using the [1, 3, 5, 6] array and the number 7 as target??
And how to make it better??
I cant think of a shorter or better way to check the if/elses when the target value is not in the array, especially if the input is an array with only one value and the target to find is 0.
Maybe a better way to check the possible different scenarios.
Or how to better check the correct place of the target without so many if/elses.
For example, is this code good enough to a coding interview? What can I do better?
from LeetCode:
Search Insert Position
Given a sorted array of distinct integers and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.
You must write an algorithm with O(log n) runtime complexity.
Example 1:
Input: nums = [1,3,5,6], target = 5
Output: 2
Example 2:
Input: nums = [1,3,5,6], target = 2
Output: 1
Example 3:
Input: nums = [1,3,5,6], target = 7
Output: 4
And especially this one:
Example 4:
Input: nums=[1], target= 0
Constraints:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums contains distinct values sorted in ascending order.
-104 <= target <= 104
this is my code:
/**
* #param {number[]} nums
* #param {number} target
* #return {number}
*/
var searchInsert = function(nums, target) {
let left = 0;
let right = nums.length -1;
let middle;
while(left <= right){
middle = nums.length>1 ? (Math.floor(left + (right - left)/2)) : 0;
if(nums[middle] === target){
return middle;
} else if(target < nums[middle]){
right = middle -1;
} else {
left = middle + 1;
}
}
console.log(`Middle: ${middle}`);
console.log(`Middle-1: ${nums[middle-1]}`);
if(nums.lenght === 1){
return 0;
} else {
if((target < nums[middle] && target > nums[middle-1] )|| (target < nums[middle] && nums[middle-1] === undefined)){ /*
No more items to the left ! */
return middle;
} else if(target<nums[middle] && target<nums[middle-1]){
return middle-1;
} else if(target > nums[middle] && target > nums[middle + 1]) {
return middle + 2; /* Why the 'middle' is so behind here? using the THIRD example as input?? */
} else {
return middle + 1;
}
}
};
Problem
The issue lies in the variable you are checking for after the while loop.
In a "classical" binary search algorithm, reaching beyond the while loop would indicate the needle isn't present in the haystack. In case of this problem, though, we simply need to return right + 1 in this place in the code (rather than checking the middle).
Your code adjusted for this:
var searchInsert = function(nums, target) {
let left = 0;
let right = nums.length -1;
let middle;
while(left <= right){
middle = nums.length>1 ? (Math.floor(left + (right - left)/2)) : 0;
if(nums[middle] === target){
return middle;
} else if(target < nums[middle]){
right = middle -1;
} else {
left = middle + 1;
}
}
return right + 1;
};
console.log(
searchInsert([1,3,5,6], 5),
searchInsert([1,3,5,6], 2),
searchInsert([1,3,5,6], 7),
searchInsert([1], 0)
);
Side note
Also, the below is redundant...
middle = nums.length>1 ? (Math.floor(left + (right - left)/2)) : 0;
...and can be shortened to:
middle = Math.floor((left + right) / 2);
Revised variant
const searchInsertProblem = (arr, n) => {
let start = 0;
let end = arr.length - 1;
while (start <= end) {
const middle = Math.floor((start + end) / 2);
if (arr[middle] === n) { return middle; } // on target
if (arr[middle] > n) { end = middle - 1; } // overshoot
else { start = middle + 1; } // undershoot
}
return end + 1;
};
console.log(
searchInsertProblem([1,3,5,6], 5),
searchInsertProblem([1,3,5,6], 2),
searchInsertProblem([1,3,5,6], 7),
searchInsertProblem([1], 0)
);

Binary Search for multiple items within a Range (Log time filter)

I have an Array of Log items, already sorted by their timestamp (number of milliseconds since 1970). Now I want to filter them by a specific time range, so I think of Binary Search, however this variant is different than all variants I knew before as I need to find a range within a range. Note that there may be none or multiple items at the value edges.
I came up with this to reduce one range requirement but still don't know how to get to the first/last edge items:
filterByTime(min: number, max: number): LogItem[] {
const items = this.items;
const len = items.length;
if (min === undefined && max === undefined) {
return items.slice(0, len - 1);
}
min = min || Number.MIN_VALUE;
max = max || Number.MAX_VALUE;
const fn = (a: LogItem) => {
if (a.time < min) {
return -1;
} else if (a.time > max) {
return 1;
} else {
return 0;
}
};
const lowerBound = this.binarySearchBound(fn, 0, len, true);
if (lowerBound == -1) { return []; }
const upperBound = this.binarySearchBound(fn, 0, len, false);
return items.slice(lowerBound, upperBound + 1);
}
binarySearchBound(compareFn: (a: LogItem) => number, left: number, right: number, isLowerBound: boolean): number {
const items = this.items;
if (items.length == 0) {
return -1;
}
if (isLowerBound && compareFn(items[0]) == 0) {
return 0;
} else if (!isLowerBound && compareFn(items[items.length - 1]) == 0) {
return items.length - 1;
}
while (left <= right) {
const mid = (left + right) / 2;
const item = this.items[mid];
const compare = compareFn(item);
if (compare < 0) {
left = mid + 1;
} else if (compare > 0) {
right = mid - 1;
} else {
// What to do now?
}
}
return -1;
}
Worst case scenario, I can just do a linear search from the edge since I can assume there are not that much items at the edge but surely there is a better way I didn't think of but then I may have to iterate through the whole result set if mid falls in the middle of the result set.
EDIT for adding a note: It's possible for min or max is undefined (and could be both, in which case I can just set an if and return the copy of the whole array). Is it better to just substitute it with MIN_VALUE and MAX_VALUE if they are undefined, or is there a better way to handle that case?
I would suggest the following:
Write two binary search functions, as the execution time is then not hampered by passing and checking the isLowerBound boolean.
Make the returned upperBound to mean the next index after the potential last index that belongs to the range. This corresponds with how arguments work with native functions like slice.
Don't use -1 as a special index. If coded well, an empty range will come out of the two binary searches any way and give an empty array as result
Make the compare function to work with 2 parameters, so you can actually search for either the min or the max value.
Yes, I would use MIN_VALUE and MAX_VALUE as defaults and not test for boundary cases. If boundary cases happen often, it might be worth to include those checks, but in general be aware that these checks will then be executed for every filter, which may bring down the average execution time.
Here is the suggested implementation with integer data (instead of objects) to keep it simple. In order to have it run in a snippet I also removed the type references:
function filterByTime(min=Number.MIN_VALUE, max=Number.MAX_VALUE) {
const fn = (a, b) => a - b; // simplified (should be a.time - b.time)
const lowerBound = this.binarySearchLowerBound(fn, 0, this.items.length, min);
const upperBound = this.binarySearchUpperBound(fn, lowerBound, this.items.length, max);
return this.items.slice(lowerBound, upperBound);
}
function binarySearchLowerBound(compareFn, left, right, target) {
while (left < right) {
const mid = (left + right) >> 1;
if (compareFn(this.items[mid], target) < 0) {
left = mid + 1;
} else { // Also when equal...
right = mid;
}
}
return left;
}
function binarySearchUpperBound(compareFn, left, right, target) {
while (left < right) {
const mid = (left + right) >> 1;
if (compareFn(this.items[mid], target) <= 0) { // Also when equal...
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
// Demo with simplified data (instead of objects with time property)
this.items = [1, 2, 2, 2, 3, 4, 4, 5, 5, 5, 6, 7, 8, 8];
console.log(this.filterByTime(2, 4));
console.log(this.filterByTime(4, 5));
Combined the variants on this article, I merged first and last code into a single function:
filterByTime(items: LogItem[], min: number, max: number): LogItem[] {
const len = items.length;
if (len == 0) {
return [];
}
if (min === undefined && max === undefined) {
return items.slice(0, len - 1);
}
min = min || Number.MIN_VALUE;
max = max || Number.MAX_VALUE;
const fn = (a: LogItem) => {
if (a.time < min) {
return -1;
} else if (a.time > max) {
return 1;
} else {
return 0;
}
};
const lowerBound = this.binarySearchBound(fn, 0, len, true);
if (lowerBound == -1) { return []; }
const upperBound = this.binarySearchBound(fn, 0, len, false);
return items.slice(lowerBound, upperBound + 1);
}
binarySearchBound(compareFn: (a: LogItem) => number, left: number, right: number, isLowerBound: boolean): number {
const items = this.items;
if (items.length == 0) {
return -1;
}
if (isLowerBound && compareFn(items[0]) == 0) {
return 0;
} else if (!isLowerBound && compareFn(items[items.length - 1]) == 0) {
return items.length - 1;
}
let result = -1;
while (left <= right) {
const mid = (left + right) / 2;
const item = this.items[mid];
const compare = compareFn(item);
if (compare < 0) {
left = mid + 1;
} else if (compare > 0) {
right = mid - 1;
} else {
result = mid;
if (isLowerBound) {
right = mid - 1;
} else {
left = mid + 1;
}
}
}
return result;
}

Stuck on a two-sum algorithm

I am working through a two-sum problem where I pass in an unsorted array, and a target, k, and I return the the highest sum of any two numbers that are less than k. If there's no possible sum less than k, then return -1.
I think I am on the right path by sorting the array and then using a 2-pointer technique but I am stuck now. If my sum of numbers is greater than the target, then I decrement the end pointer...that seems definitive. The else though, I am not sure if I am doing correctly.
var twoSumLessThanK = function(nums, k) {
// [1,8,23,23,33,34,54,75] 60
nums.sort((a, b) => a - b)
let start = 0;
let end = 0;
let max = -1;
while (start < end) {
if (nums[start] + nums[end] >= k) {
end--
} else if (nums[start] + nums[end] < k) {
max = Math.max(max, nums[start] + nums[end])
start++
}
}
return max;
};
console.log(twoSumLessThanK([1,8,23,23,33,34,54,75], 60));
You could check the sum of two values and decrement the right index if greater or equal than k or store the sum, if greater than the max value and increment the left index.
1 8 23 23 33 34 54 75 sum max < 60
> < 76
> < 55 -> max
> < 62
> < 42
> < 57 -> max
> < 57
> < 67
const
twoSumLessThanK = function(nums, k) {
nums.sort((a, b) => a - b);
let left = 0,
right = nums.length -1,
max = -Number.MAX_VALUE;
while (left < right) {
let sum = nums[left] + nums[right];
if (sum >= k) {
right--;
continue;
}
if (max < sum) max = sum;
left++;
}
return max;
};
console.log(twoSumLessThanK([1, 8, 23, 23, 33, 34, 54, 75], 60));
An alternative could be a nested for loop, this way you do't have to handle start and end manually
const twoSumLessThanK = function(nums, k)
{
let max = -1;
const len = nums.length - 1
for (let start = 0; start < len; start++)
{
for (let end = len; end > start; end--)
{
if (nums[start] + nums[end] < k) max = Math.max(max, nums[start] + nums[end])
}
}
return max;
};
console.log(twoSumLessThanK([1,8,23,23,33,34,54,75], 60)); // Logs 57
A different option would be the following:
const twoSumLessThanK = function(nums, k)
{
nums.sort((a, b) => a - b)
let max = -1;
let greatest = null
while (nums.length > 1 && max < k - 1)
{
greatest = nums.pop()
for (let i = nums.length - 1; i >= 0; i--)
{
if (greatest + nums[i] < k)
{
max = Math.max(max, greatest + nums[i])
break
}
}
}
return max;
};
Or you can just fix your version if you want:
const twoSumLessThanK = function(nums, k)
{
// [1,8,23,23,33,34,54,75] 60
nums.sort((a, b) => a - b)
const len = nums.length - 1
let start = 0;
let max = -1;
let end = len;
while (start < len)
{
if (nums[start] + nums[end] < k)
{
max = Math.max(max, nums[start] + nums[end])
start++
end = len;
}
else end--
}
return max;
};
it wasn't so hard to code...?
const twoSumLessThanK = (nums, k) =>
{
let max = -1
, arr = nums.reduce((a,c)=> // decrase order values < k
{
if (c < k)
{
let p = a.findIndex(x=>x < c)
if (p<0) p = a.length
a.splice( p,0,c)
}
return a
}
,[])
;
if (arr.length<2) return max
for(i=arr.length;--i;)
for(j=i;j--;)
{
let sum = arr[i] + arr[j]
if (sum >= k && i === j+1) return max
if (sum < k && sum > max ) max = sum
}
return max
}
console.log(twoSumLessThanK([33,1,8,23,23,34,54,75], 60))

binary Search to find closest float value to target number

I have some question and Maybe One of you can help me,
I have a float array and I won't find the closest float value to the target number(float)
I use a simple binary Search With javascript And from here I got stuck.
function binarySearch(arr, val, start = 0, end = arr.length - 1) {
const mid = Math.floor((start + end) / 2);
if(Math.abs(val - arr[mid]) < Math.abs(val - arr[mid + 1])){
return mid;
}
if (start >= end) {
return -1;
}
return val < arr[mid]
? binarySearch(arr, val, start, mid - 1)
: binarySearch(arr, val, mid + 1, end);
}
let array = [0,0.03336667683886884,0.06673335367773768,0.10010003051660653,0.13346670735547536,0.1668333841943442,0.20020006103321306]
let result = binarySearch(array,'0.166833') //=> This value needs to be returned 0.1668333841943442
Your condition Math.abs(val - arr[mid]) < Math.abs(val - arr[mid + 1]) is wrong, you are always checking the left value is closer than the right value, so even for value 1 in array [10, 20, 30, 40], when you check number 20, 1 is closer to 20 than to 30 - incorrect result is then returned.
Also, you need to check the edge cases, the index mid + 1 may not be available. Also the value may be smaller than the minimum or larger than the maximum, so an infinite loop may occur.
Let me offer you this solution instead:
function binarySearch(arr, val, start, end) {
// edge case: value of smaller than min or larger than max
if(array[0] >= val) return 0;
if(array[array.length - 1] <= val) return array.length - 1;
while (start <= end) {
let mid = Math.floor((end + start) / 2);
// value is in interval from previous to current element
if(val >= arr[mid - 1] && val <= arr[mid]) {
return Math.abs(val - arr[mid - 1]) < Math.abs(val - arr[mid]) ? mid - 1 : mid;
}
else {
if(arr[mid] < val) {
start = mid + 1;
}
else {
end = mid - 1;
}
}
}
return -1;
}
Your stop condition is broken. In your example, if you feed the following array:
[0.3, 0.5, 0.8, 2.0]
You have at initiation:
arr[mid] = 0.5
arr[mid + 1] = 0.8
So if you feed val = 0.1 to your algorithm, you will have:
Math.abs(val - arr[mid]) = 0.4
Math.abs(val - arr[mid + 1]) = 0.7
Therefore, you will return the index of 0.5 although you were expecting the index of 0.3.
A pseudo idea to solve your problem would be:
if array contains 1 element => return element
else if array contains 2 elements => return element closest to val.
else if val < array[mid] => recurse on first half
else if val > array[mid] => recurse on second half
It looks you have a typo. You should not return mid index, but item of array from array[mid]:
function binarySearch(arr, val, start = 0, end = arr.length - 1) {
const mid = Math.floor((start + end) / 2);
if(Math.abs(val - arr[mid]) < Math.abs(val - arr[mid + 1])){
return arr[mid];
}
if (start >= end) {
return -1;
}
return val < arr[mid]
? binarySearch(arr, val, start, mid - 1)
: binarySearch(arr, val, mid + 1, end);
}
let array = [0, 0.03336667683886884, 0.06673335367773768, 0.10010003051660653,
0.13346670735547536, 0.1668333841943442, 0.20020006103321306];
console.log(binarySearch(array,'0.166833')) // 0.1668333841943442
I found this solution but I need to return the index of The right value
function binarySearch(arr, target, lo = 0, hi = arr.length - 1) {
if (target < arr[lo]) {return arr[0]}
if (target > arr[hi]) {return arr[hi]}
const mid = Math.floor((hi + lo) / 2);
return hi - lo < 2
? (target - arr[lo]) < (arr[hi] - target) ? arr[lo] : arr[hi]
: target < arr[mid]
? binarySearch(arr, target, lo, mid)
: target > arr[mid]
? binarySearch(arr, target, mid, hi)
: arr[mid]
}
let array = [0, 0.03336667683886884, 0.06673335367773768, 0.10010003051660653,0.13346670735547536, 0.1668333841943442, 0.20020006103321306,30.01];
console.log(binarySearch(array,0.03336))

How to "summarize" an array of integers to a string with ranges?

Lets say you have input Array=[1,2,3,5,7,9,10,11,12,15]
The output should be 1-3,5,7,9-12,15
Im looking for feedback on my attempt and other possible solutions.
Heres my attempt in javascript:
var min = 0;
var max = -1;
function summarize(array) {
var sumString = "";
var prevVal = -1;
array.forEach(function(currVal, index) {
if (index > 0) {
prevVal = array[index - 1];
}
if (index === 0) {
min = currVal;
max = currVal;
} else if (currVal - prevVal === 1) {
max = currVal;
} else if (min !== max && max !== -1) {
sumString += min + "-" + max + (index < array.length - 1 ? "," : "");
min = currVal;
max = -1;
} else {
sumString += min + (index < array.length - 1 ? "," : "");
}
if (index === array.length - 1) {
if (max === -1) {
sumString += "," + min;
} else {
sumString += min + "-" + max;
}
}
});
return sumString;
}
Here is a slightly shorter implementation:
var i = 0, prev, arr = [1,2,3,5,7,9,10,11,12,15], out = [];
for(i=0; i<arr.length; prev = arr[i], i++) {
// if the current number is not prev+1, append it to out
// Note that we are adding it as a string, to ensure that
// subsequent calls to `split()` (see else part) works
if(prev !== arr[i] - 1) out.push(String(arr[i]));
// if the current number is prev+1, modify the last value
// in out to reflect it in the RHS of - (hyphen)
else out[out.length - 1] = [out[out.length - 1].split('-')[0], String(arr[i])].join('-');
}
// out => ["1-3", "5", "7", "9-12", "15"]
Another possible solution for positive numbers in ascending order. It features Array.prototype.reduce.
var array = [1, 2, 3, 5, 7, 9, 10, 11, 12, 15, 23, 24],
result = [];
array.reduce(function (r, a) {
result.push(r + 1 - a ? String(a) : result.pop().split('-')[0] + '-' + String(a));
return a;
}, array[0]);
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
Another possible solution :
var points = [1,2,3,5,6,31,7,9,10,11,12,15];
points.sort(function(a, b){return a-b}); //sort array in asc
var resultArr=[];
var max; var min;
for(i=0;i<points.length;i++) //loop
{
if(i==0)
{
min=points[i]; //lowest number in arr
max=points[i]+1; //assign next value
}
else
{
if(max==points[i]) //if value matches continue
max=points[i]+1;
else //next value is not an incremental one so push it to result arr
{
max=max-1;
resultArr.push(min+(min!=max? "-"+ max :""));
min=points[i];
max=points[i]+1;
}
if(i==points.length-1) //last element of the arr so push it to result arr
{
max=max-1;
resultArr.push(min+(min!=max? "-"+ max :""));
}
}
}
alert(resultArr);
First step uses dashes to separate sequential numbers and commas if they aren't. Second step replaces -#- with -.
var X = [1,2,3,5,7,9,10,11,12,15];
var S = '' + X[0];
for (var i = 1; i < X.length; i++) {
S += (X[i] == X[i - 1] + 1)? '-': ',';
S += X[i];
}
while (/-[0-9]+-/.test(S))
S = S.replace(/-[0-9]+-/g, '-');
alert(S);
For a sequence like 1,2,5,6 will output 1-2,5-6 which might not be what you're looking for, so an optional third step would be to replace #-#+1 with #,#+1, i.e. restore the comma:
for (var i = 1; i < X.length; i++)
S = S.replace(X[i - 1] + '-' + X[i], X[i - 1] + ',' + X[i]);
I ran into this problem recently, after some reflection, I noticed 3 different transformations: (1) Group consecutive numbers; (2) Transform groups into strings representing the ranges; (3) Join range strings on comma.
function summarizeRange(items) {
const sorted = items.slice(0).sort((a, b) => a - b);
return sorted
.slice(1)
.reduce((range, item) => {
const rangedIndex = range.reduce((ranged, rangedCollection, index) =>
rangedCollection.indexOf(item - 1) > -1 ? index : ranged,
-1
);
if (rangedIndex > -1) {
range[rangedIndex] = range[rangedIndex].concat(item);
return range;
}
return range.concat([
[item]
]);
}, [
[sorted[0]]
])
.map(range => range.length > 1 ?
'' + range[0] + '-' + range[range.length - 1] :
'' + range[0]
)
.join(',');
}
console.log(summarizeRange([0,3,2,6,19,20,22,21,1]));

Categories