Нахождение вставляемого элемента в отсортированном массиве
Дан отсортированный массив целых чисел nums
и целое число target
. Напишите функцию, которая находит индекс, на который можно вставить target
, чтобы сохранить порядок массива. Вставляемый элемент может быть равен уже существующим элементам массива.
nums = [1, 3, 5, 6]
, target = 5
Выход: 2
5
уже присутствует в массиве на индексе 2
.nums = [1, 3, 5, 6]
, target = 2
Выход: 1
2
должно быть вставлено на индекс 1
, чтобы сохранить порядок.nums = [1, 3, 5, 6]
, target = 7
Выход: 4
7
должно быть вставлено на индекс 4
, чтобы сохранить порядок.nums = [1, 3, 5, 6]
, target = 0
Выход: 0
0
должно быть вставлено на индекс 0
, чтобы сохранить порядок.Я исходил из того принципа, что используя бинарный поиск я либо найду индекс существующего в массиве элемента или не буду попадать в диапазоны его частей. При этом я решил, что есть два пограничных случая, которые буду проверять в начале функции - что у нас target
находится вообще вне диапазона исходного массива, то есть он либо меньше самого маленького, либо больше самого большого.
Для подсказки себе в будущем я написал небольшой тестовый класс, в который добавил все примеры
@Test
fun makeTest() {
Assert.assertEquals(2, searchInsert(intArrayOf(1, 3, 5, 6), 5))
Assert.assertEquals(1, searchInsert(intArrayOf(1, 3, 5, 6), 2))
Assert.assertEquals(4, searchInsert(intArrayOf(1, 3, 5, 6), 7))
Assert.assertEquals(0, searchInsert(intArrayOf(1, 3, 5, 6), 0))
}
И само решени
fun searchInsert(nums: IntArray, target: Int): Int {
var start = 0
var end = nums.size - 1
if (nums[0] > target) return 0
if (nums[end] < target) return end + 1
while (start <= end) {
val midi = start + (end - start) / 2
val midva = nums[midi]
when {
midva == target -> return midi
target > nums[start] && target < midva -> end = midi - 1
target > midva && target < nums[end] -> start = midi + 1
else -> return midi + 1
}
}
return -1
}
Компилятор и дебаггеры хороши тем, что автоматически и быстро пробегают по циклу и определяют корректность теста, но очень неплохо самому это делать. Чтобы не запутаться в вычислениях, "решаем" цикл чуть ниже. И вот что получается
target=5
start=0, end=3, midi=1, midva=3 -> !if1, !if2, if3 -> start=2
start=2, end=3, midi=2, midva=5 -> if1 -> return 2
target=2
start=0, end=3, midi=1, midva=3 -> !if1, if2 -> end=0
start=0, end=0, midi=0, midva=1 -> !if1, !if2, !if3 -> return 1
И так далее.
Ваше решение использует бинарный поиск, чтобы найти индекс для вставки целевого значения target
в отсортированный массив nums
. Рассмотрим подробно, как ваше решение работает и какие есть замечания:
start
устанавливается в 0
.end
устанавливается в nums.size - 1
.target
меньше первого элемента, возвращаем 0
(вставка в начало).target
больше последнего элемента, возвращаем end + 1
(вставка в конец).midi
и значение midva
в этом индексе.when
для определения следующего шага:
midva
равен target
, возвращаем midi
.target
больше nums[start]
и меньше midva
, сужаем поиск влево (end = midi - 1
).target
больше midva
и меньше nums[end]
, сужаем поиск вправо (start = midi + 1
).target
должен быть вставлен после midi
, и возвращаем midi + 1
.-1
(хотя это условие никогда не срабатывает, если входные данные корректны).nums[0] > target
и nums[end] < target
) правильные и предотвращают ненужные итерации, но в условии else
нужно корректно обрабатывать все возможные случаи, когда target
может быть вставлен в середину.else
условии: В условии else
предполагается, что target
вставляется после текущего midi
, но это не всегда правильно, если target
равен midva
. Вам нужно пересчитать условия для else
.start == end
: Этот случай не рассматривается отдельно, хотя его тоже нужно учесть, если target
равен элементу в midi
.fun searchInsert(nums: IntArray, target: Int): Int {
var start = 0
var end = nums.size - 1
// Проверка крайних случаев
if (target <= nums[start]) return start
if (target > nums[end]) return end + 1
while (start <= end) {
val mid = start + (end - start) / 2
val midVal = nums[mid]
when {
midVal == target -> return mid
target < midVal -> end = mid - 1
else -> start = mid + 1
}
}
// Позиция, куда можно вставить target
return start
}
Проверка крайних случаев: Обрабатываются правильно. Если target
меньше или равен первому элементу, возвращается start
, и если target
больше последнего элемента, возвращается end + 1
.
Исправлен бинарный поиск:
midVal
равен target
, возвращаем mid
.target
меньше midVal
, сужаем диапазон поиска влево.target
больше midVal
, сужаем диапазон поиска вправо.Эти изменения гарантируют, что все возможные случаи корректно обрабатываются, и start
будет указывать на индекс, куда можно вставить target
, когда поиск завершен.
Возврат значения: В конце, возвращаем start
, который указывает на правильную позицию для вставки target
.