Бинарный поиск можно назвать одним из самых простых и простых для понимания алгоритмов среди всех алгоритмов, но на самом деле это также один из экзаменационных вопросов с самым высоким процентом сдачи.Собеседования свежих выпускников различных крупных компаний , такие оценки не редкость: говорить о проектах Когда я пришел поболтать ну я попросил его написать бинарный поиск но не смог написать. Я не буду это комментировать.Что касается бинарного поиска, то я думаю, что это не так просто, как все думают.Уместнее всего описать его как "идея очень проста, а детали - черт". Если вы не верите мне, давайте посмотрим на это шаг за шагом.
Сложность: граничное определение
Код бинарного поиска примерно такой:
let binarySearch = (arr, target) => {
let begin = 0;
let end = ???;
while(???) {
int mid = begin + (end -begin) / 2;
if(arr[mid] == target) {}
else if(arr[mid] > target) {}
else if(arr[mid] < target) {}
}
return ???;
}
Видно, что проблема уже очевидна: когда много людей не подготовлены, базовый фреймворк можно написать, но граничные условия были неясны, что приводит к эмоциональному напряжению, растерянности и в итоге ничего.
Теперь давайте разберем различные граничные условия,
let search = (arr, target) => {
let begin = 0;
let end = arr.length-1; //写成这样,相当于搜索区间为[begin, end],这是一个闭区间
while(begin <= end) {//重点: 因为闭区间,所以到了begin等于end时,其实区间内还有一个值要判断,
//因此只有begin>end的时候才能停止
let mid = (begin + end) >>> 1;//位运算,无符号右移一位,同Math.floor((begin+end)/2)
if(arr[mid] == target) {
return mid;
}
else if(arr[mid] > target) {
end = mid - 1;//因为是闭区间,搜索范围变为[left, mid - 1]
}
else if(arr[mid] < target) {
begin = mid + 1; //搜索范围变成[mid + 1, end]
}
}
return -1;
}
На самом деле, мы также можем сделать это:
let search = (arr, target) => {
let begin = 0;
let end = arr.length; //写成这样,相当于搜索区间为[begin, end),这是一个前闭后开的区间
while(begin < end) {//重点:
//因为前闭后开的区间,所以到了begin等于end时,其实区间内已经没有值了,直接停止
let mid = (begin + end) >>> 1;
if(arr[mid] == target) {
return mid;
}
else if(arr[mid] > target) {
end = mid;//因为是闭区间,搜索范围变为[left, mid - 1]
}
else if(arr[mid] < target) {
begin = mid + 1; //搜索范围变成[mid + 1, end]
}
}
return -1;
}
Я думаю, что комментарии были объяснены достаточно ясно, и я надеюсь, что каждый сможет понять принцип интервальной оценки, вместо того, чтобы слепо запоминать код.
Кстати, согласно вышеизложенной идее, мы можем написать это и рекурсивно:
let search = (nums, target) => {
let helpSearch = (nums, begin, end, target) => {
if(begin > end) return -1;
let mid = (begin + end) >>> 1;
if(nums[mid] == target) return mid;
else if(nums[mid] > target)
return helpSearch(nums, begin, mid - 1, target);
else
return helpSearch(nums, mid+1, end, target);
}
//闭区间形式
return helpSearch(nums, 0, nums.length - 1, target);
}
let search = (nums, target) => {
let helpSearch = (nums, begin, end, target) => {
if(begin >= end) return -1;
let mid = (begin + end) >>> 1;
if(nums[mid] == target) return mid;
else if(nums[mid] > target)
return helpSearch(nums, begin, mid, target);
else
return helpSearch(nums, mid+1, end, target);
}
//前闭后开区间形式
return helpSearch(nums, 0, nums.length, target);
}
Трудности 2: целевое значение повторяется
Просто посмотрите на реальный вопрос:
Временная сложность уровня O(log n), грубо говоря, представляет собой бинарный поиск, то есть нам нужно использовать бинарный поиск, чтобы найти крайнюю левую позицию и крайнюю правую позицию целевого значения, на самом деле есть еще какие-то сложности.
Давайте разберем его шаг за шагом.
Найдите положение левой границы
let left = 0;
let mid;
let right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
right = mid; //重点
}
}
//分析:目前的nums[left]一定是大于等于target的值
if (left == nums.length) return -1;
else return nums[left] == target ? left : -1;
найти правильное положение границы
let left = 0;
let right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
left = mid + 1; //注意
}
}
//分析: 目前的nums[left]左边的部分都是大于等于target的部分,因此我们取nums[left - 1]
if (left == 0) return -1;
else return nums[left - 1] == target ? left - 1: -1;
Конечно, здесь я использую фронт-закрытый и задний-открытый интервалы, вы также можете использовать закрытый интервал напрямую.
окончательный код показать
var searchRange = function(nums, target) {
let left = 0;
let mid;
let right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
right = mid;
}
}
let leftIndex = -1, rightIndex = -1;
if (left == nums.length) return [-1, -1];
else leftIndex = nums[left] == target ? left : -1;
left = 0; right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
left = mid + 1;
}
}
if (left == 0) return [-1, -1];
else rightIndex = nums[left - 1] == target ? left - 1: -1;
return [leftIndex, rightIndex];
};
В общем, бинарный поиск действительно нетрудно думать, но необходимо очень четко понимать смысл интервала, представляемого граничной переменной, чтобы мышление не было беспорядочным.