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)
);
Related
I am working on LeetCode code challenge 480. Sliding Window Median:
You are given an integer array nums and an integer k. There is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
Return the median array for each window in the original array. Answers within 10-5 of the actual value will be accepted.
I submitted my code, but it fails on test cases. I suspect that there is a problem in this part of my code:
const medianSlidingWindow = (array, window) => {
let start = 0;
let end = window - 1;
const min = new MinHeap(array);
const max = new MaxHeap(array);
const insert = (index) => {
if(max.size === 0){
max.push(index);
return;
}
(array[index] >= max.peak) ? min.push(index) : max.push(index);
balance();
}
const balance = () => {
if(Math.abs(max.size - min.size) >= 2){
const returned = (max.size > min.size) ? max.pop() : min.pop();
(max.size > min.size) ? min.push(returned) : max.push(returned);
}
}
const remove = (index) => {
(max.has(index)) ? max.pop(index, true) : min.pop(index, true);
balance();
}
const next = () => {
remove(start++);
insert(++end);
}
const getMedian = () => {
if(window % 2 === 0) return (max.peak + min.peak)/2;
return (max.size > min.size) ? max.peak : min.peak;
}
for(let i = 0; i <= end; i++){
insert(i);
}
const ret = [];
while(end < array.length){
ret.push(getMedian());
next();
}
return ret;
}
Here is the full code:
class MaxHeap{
#array = [];
#size = 0;
#reference = [];
#map = new Map();
constructor(reference = []){
this.#reference = reference;
}
get size(){
return this.#size;
}
/* Debug */
get array(){
return this.#array;
}
get peak(){
return this.get(0);
}
get(index){
if(index === null || index < 0 || index >= this.#array.length) return null;
return this.#reference[this.#array[index]];
}
has(indexReference){
return this.#map.has(indexReference);
}
swap(indexA, indexB){
let temp = this.#map.get(this.#array[indexA]);
this.#map.set(this.#array[indexA], indexB);
this.#map.set(this.#array[indexB], temp);
[this.#array[indexA], this.#array[indexB]] = [this.#array[indexB], this.#array[indexA]];
}
sink(index){
let currentIndex = index;
let greterChild;
while((this.get(greterChild = this.get(2*currentIndex+1) >= this.get(2*currentIndex + 2) ? 2*currentIndex + 1 : 2*currentIndex + 2) ?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
this.swap(currentIndex, greterChild);
currentIndex = greterChild;
}
}
bubble(index){
let currentIndex = index;
let parent;
while((this.get(parent = Math.ceil((currentIndex - 2)/2)) ?? Number.MAX_SAFE_INTEGER) < this.get(currentIndex)){
this.swap(currentIndex, parent);
currentIndex = parent;
}
}
push(...char){
if(char[0].constructor === Array) char = char.flat();
for(let i = 0; i < char.length; i++){
this.#array.push(char[i]);
this.#map.set(char[i], this.#array.length - 1)
this.bubble(this.#array.length - 1);
this.#size++;
}
}
pop(index = 0, fromReference = false){
const ret = (fromReference) ? index :this.#array[index];
if(fromReference) index = this.#map.get(index);
this.swap(index, this.#array.length - 1);
this.#map.delete(ret);
this.#array.pop();
this.sink(index);
this.#size--;
return ret;
}
}
class MinHeap extends MaxHeap{
constructor(reference = []){
super(reference);
}
get size(){
return super.size;
}
get peak(){
return super.peak;
}
/* Debug */
get array(){
return super.array;
}
bubble(index){
let currentIndex = index;
let parent;
while((this.get(parent = Math.ceil((currentIndex - 2)/2)) ?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
this.swap(currentIndex, parent);
currentIndex = parent;
}
}
sink(index){
let currentIndex = index;
let lesserChild;
while((this.get(lesserChild = this.get(2*currentIndex+1) >= this.get(2*currentIndex + 2) ? 2*currentIndex + 2 : 2*currentIndex + 1) ?? Number.MAX_SAFE_INTEGER) < this.get(currentIndex)){
this.swap(currentIndex, lesserChild);
currentIndex = lesserChild;
}
}
}
const medianSlidingWindow = (array, window) => {
let start = 0;
let end = window - 1;
const min = new MinHeap(array);
const max = new MaxHeap(array);
const insert = (index) => {
if(max.size === 0){
max.push(index);
return;
}
(array[index] >= max.peak) ? min.push(index) : max.push(index);
balance();
}
const balance = () => {
if(Math.abs(max.size - min.size) >= 2){
const returned = (max.size > min.size) ? max.pop() : min.pop();
(max.size > min.size) ? min.push(returned) : max.push(returned);
}
}
const remove = (index) => {
(max.has(index)) ? max.pop(index, true) : min.pop(index, true);
balance();
}
const next = () => {
remove(start++);
insert(++end);
}
const getMedian = () => {
if(window % 2 === 0) return (max.peak + min.peak)/2;
return (max.size > min.size) ? max.peak : min.peak;
}
for(let i = 0; i <= end; i++){
insert(i);
}
const ret = [];
while(end < array.length){
ret.push(getMedian());
next();
}
return ret;
}
What went wrong:
On the 30th testcase of the problem (link: https://leetcode.com/problems/sliding-window-median/submissions/859041571/), it resolves to a wrong answer but when I pick one of the windows that resolves to a wrong answer it gives me a correct answer. I'm currently confused because two of the heaps are fairly balanced (as one heap doesn't exceed above one element) and I've tested my heap that both seem to work perfectly. It will be very helpful if somebody helps me.
Link to SO questions I've followed:
How to implement a Median-heap
There are these problems in your heap implementation:
The get function will return null when an out of range index is given, which means the while condition in your sink method could sometimes choose an non-existing child (when there is only one child). Note that a numerical comparison with null will treat that null as 0, and depending of the sign of the value you compare it with can give false or true.
For example, your code fails this test case for that reason:
nums=[1,2,3,4]
k=4
You can fix this by returning undefined instead of null. Then also make sure that the false side of the comparison operator is the one with +1 (choosing the left child), while the true side takes the other child.
The pop method, when called with true for the second argument, does not guarantee to restore the heap property. It takes care of sinking the value at the given index, but does not consider the case where this value should actually bubble up!
For example, your code fails this test case for that reason:
nums=[10,6,5,2,3,0,8,1,4,12,7,13,11,9]
k=11
Here is a simplified example where I depict a min-heap with the referenced values:
5
/ \
8 6
/ \ /
10 12 7
If the node with value 10 is to be removed, the swap action will give this min-heap (which is correct):
5
/ \
8 6
/ \ /
7 12 10
And then your code calls sink on that node with value 7. It is clear that there is nothing to sink here, but instead that 7 should bubble up and swap with 8. Your code must foresee both scenarios: sift or bubble.
If you fix those two issues in your heap implementation, it will work.
I provide here the literal changes you have to make:
In the get method, replace return null with return undefined (or omit the explicit value)
In the MaxHeap sink method, swap the comparator expression, replacing:
while((this.get(greterChild = this.get(2*currentIndex+1) >= this.get(2*currentIndex + 2) ? 2*currentIndex + 1 : 2*currentIndex + 2) ?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
with:
while((this.get(greterChild = this.get(2*currentIndex+1) <= this.get(2*currentIndex + 2) ? 2*currentIndex + 2 : 2*currentIndex + 1) ?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
In the pop method, replace:
this.sink(index);
with:
this.sink(index);
this.bubble(index);
(You can also first check which of both is needed, but it doesn't hurt to just call both methods)
I'm trying to solve Three Sum (find all triplets that add to 0 within an array avoiding duplicate cases) but currently running into a bug I can't seem to find.
When the input is [-1,0,1,2,-1,-4], it works fine.
With this input however, [-1,0,1,2,-1,-4,-2,-3,3,0,4] I'm getting this array as output:
[[-1,-1,2],[-1,0,1],[-2,0,2],[-3,0,3],[-3,1,2],[-4,0,4],[-4,1,3]].
The correct output should be
[[-4,0,4],[-4,1,3],[-3,-1,4],[-3,0,3],[-3,1,2],[-2,-1,3],[-2,0,2],[-1,-1,2],[-1,0,1]]
So for some reasons my solution is omitting the triplets [-3,-1,4] and [-2,-1,3].
var threeSum = function (nums) {
const sorted = nums.sort()
const output = []
for (let i = 0; i < sorted.length - 2; i++)
if (i === 0 || (i > 0 && sorted[i] !== sorted[i - 1])) {
let lower = i + 1
let higher = sorted.length - 1
while (lower < higher) {
const currentSum = sorted[i] + sorted[lower] + sorted[higher];
if (currentSum === 0) {
output.push([sorted[i], sorted[lower], sorted[higher]])
while (sorted[lower] === sorted[lower + 1]) lower++
while (sorted[higher] === sorted[higher - 1]) higher--
lower++
higher--
}
else if (currentSum < 0) {
lower++
} else {
higher--
}
}
}
}
return output
};
By default Javascript sorts via string comparison.
You want to sort numerically so use
nums.sort(function(a, b){return a - b});
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;
}
function getMiddle({right,left}) {
return Math.floor((left + right) / 2);
}
function RecursivelyBinarySearch(arr, search) {
let left = 0;
let right = arr.length - 1;
let middle = getMiddle({left,right});
if (right >= left) {
if (arr[middle] === search) {
return middle;
} else if (arr[middle] < search) {
return RecursivelyBinarySearch(arr.slice(middle + 1, arr.length), search);
} else {
return RecursivelyBinarySearch(arr.slice(0, middle - 1), search)
}
}
return -1;
}
let result = RecursivelyBinarySearch([1,5,7,9,10,20], 20);
console.log(result);
// RecursivelyBinarySearch([1,5,7,9,10,20], 20)
RecursivelyBinarySearch returns 0, but I want it to be 5,
is it possible to do this without passing the orignal array?
Welcome to stack overflow! It's a strange request to not pass the original array. But I assume you have a good reason for it :)
Regarding your snippet, you have an error in your Binary Search function. For the last else case you need to pass arr.slice(0, middle) instead of middle - 1.
Now for your original question, we need to pass the first index of array every time, since this information is critical to find the original index (It is lost when you do arr.slice). You can modify your function like below to achieve the desired result.
function getMiddle({right,left}) {
return Math.floor((left + right) / 2);
}
function RecursivelyBinarySearch(arr, search, first_idx) {
let left = 0;
let right = arr.length - 1;
let middle = getMiddle({left,right});
if (right >= left) {
if (arr[middle] === search) {
return middle + first_idx;
} else if (arr[middle] < search) {
return RecursivelyBinarySearch(arr.slice(middle + 1, arr.length), search, middle + first_idx + 1);
} else {
return RecursivelyBinarySearch(arr.slice(0, middle), search, first_idx)
}
}
return -1;
}
let result = RecursivelyBinarySearch([1,5,7,9,10,20], 20, 0);
console.log(result);
What is the key point in deciding where to insert an element using binary search ?
Using binary search when the element is present returns its index.
function arr() {
this.arr = [5, 6, 8];
this.insert = function(element) {
var low = 0;
var high = this.arr.length - 1;
while (low <= high) {
var mid = parseInt((low + high) / 2);
if (element == this.arr[mid]) {
return mid;
} else if (element > this.arr[mid]) {
low = mid + 1;
} else {
high = mid - 1
}
}
return -1
}
}
var vector = new arr();
var x = vector.insert(6); // return 1
alert(x)
Here I can use splice to insert element on index 1, but what if do
var x = vector.insert(7);
7 isn't present in the array but should be inserted on 2th index.
How could I determine that ?
Try with something like this:
function arr() {
this.arr = [5, 6, 8];
this.insert = function(element) {
var low = 0;
var high = this.arr.length - 1;
while (low <= high) {
var mid = parseInt((low + high) / 2);
if (element == this.arr[mid]) {
return mid;
} else if (element > this.arr[mid]) {
low = mid + 1;
} else {
high = mid - 1
}
}
return mid;
}
}
You probably want to insert the element if it's not found inside your array, using splice(). Also I made small edits to your code.
The index of insertion is determined by your mid variable.
function arr() {
this.arr = [5, 6, 8];
this.insert = function(element) {
var low = 0;
var high = this.arr.length;
while (low <= high) {
var mid = Math.floor((low + high) / 2);
if (element == this.arr[mid]) {
return mid;
} else if (element > this.arr[mid]) {
low = mid + 1;
} else {
high = mid - 1;
}
}
this.arr.splice(mid, 0, element);
return mid;
}
}
var vector = new arr();
var x = vector.insert(6);
console.log(x); // 1
var x = vector.insert(7);
console.log(x); // 2
var x = vector.insert(9);
console.log(x); // 4
console.log(vector.arr); // Array [ 5, 6, 7, 8, 9 ]
document.write('<p>Check your console :-)</p>');
Some binary search implementations return the one's complement of the insertion spot when the item isn't found. In your case, the item would be inserted at index 2 if it were found. But since it's not found, you return -3. The caller sees that the return value is less than 0, does a one's complement, and then inserts at that position.
Example:
result = find(7) // find the value in the array
if (result < 0) // if not found
{
index = ~result // complement the result
insert(7, index) // and insert (probably using splice)
}
In your code, rather than return -1, do a return ~mid
The reason for using the one's complement rather than the negative index is that if the item you're looking for is smaller than the smallest item in the array, it would have to be inserted at index 0. But if you returned -0, you'd get ... 0. So it's impossible to tell the difference between the item being found at zero and the item needing to be inserted at index 0. One's complement solves that problem because the one's complement of 0 is -1.