188 lines
8.6 KiB
YAML
188 lines
8.6 KiB
YAML
title: Search Insert Position
|
|
slug: search-insert-position
|
|
difficulty: easy
|
|
leetcode_id: 35
|
|
leetcode_url: https://leetcode.com/problems/search-insert-position/
|
|
categories:
|
|
- arrays
|
|
- binary-search
|
|
patterns:
|
|
- slug: binary-search
|
|
is_optimal: true
|
|
|
|
function_signature: "def search_insert(nums: list[int], target: int) -> int:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { nums: [1, 3, 5, 6], target: 5 }
|
|
expected: 2
|
|
- input: { nums: [1, 3, 5, 6], target: 2 }
|
|
expected: 1
|
|
- input: { nums: [1, 3, 5, 6], target: 7 }
|
|
expected: 4
|
|
hidden:
|
|
- input: { nums: [1, 3, 5, 6], target: 0 }
|
|
expected: 0
|
|
- input: { nums: [1], target: 0 }
|
|
expected: 0
|
|
- input: { nums: [1], target: 2 }
|
|
expected: 1
|
|
|
|
description: |
|
|
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.
|
|
|
|
constraints: |
|
|
- `1 <= nums.length <= 10^4`
|
|
- `-10^4 <= nums[i] <= 10^4`
|
|
- `nums` contains **distinct** values sorted in **ascending** order
|
|
- `-10^4 <= target <= 10^4`
|
|
|
|
examples:
|
|
- input: "nums = [1,3,5,6], target = 5"
|
|
output: "2"
|
|
explanation: "5 is found at index 2."
|
|
- input: "nums = [1,3,5,6], target = 2"
|
|
output: "1"
|
|
explanation: "2 is not found, but would be inserted at index 1 (between 1 and 3)."
|
|
- input: "nums = [1,3,5,6], target = 7"
|
|
output: "4"
|
|
explanation: "7 is not found and is greater than all elements, so it would be inserted at the end (index 4)."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine you have a bookshelf where books are arranged by page count from smallest to largest. You want to find where a book with a specific page count belongs — either its exact position if it's already there, or where you'd slide it in to maintain the order.
|
|
|
|
The key insight is that this is a **classic binary search problem with a twist**: instead of just returning `-1` when the target isn't found, we return the *insertion point*. This insertion point is exactly where our search "narrows down to" when the target doesn't exist.
|
|
|
|
Think of it like this: binary search works by maintaining a range `[left, right]` where the answer *could* be. Each step, we eliminate half the range. When we find the target, we return its index. When we don't find it, the `left` pointer ends up at exactly the position where the target would need to be inserted to maintain sorted order.
|
|
|
|
Why does `left` give us the insertion point? Because we're looking for the **first position** where `nums[i] >= target`. When the loop ends without finding the target, `left` points to the smallest element greater than target (or past the end if target is larger than all elements).
|
|
|
|
approach: |
|
|
We solve this using **Binary Search**:
|
|
|
|
**Step 1: Initialise pointers**
|
|
|
|
- `left`: Set to `0` (start of array)
|
|
- `right`: Set to `len(nums) - 1` (end of array)
|
|
|
|
|
|
|
|
**Step 2: Binary search loop**
|
|
|
|
- While `left <= right`:
|
|
- Calculate `mid = left + (right - left) // 2` to avoid integer overflow
|
|
- If `nums[mid] == target`: return `mid` (found the target)
|
|
- If `nums[mid] < target`: the target must be in the right half, so set `left = mid + 1`
|
|
- If `nums[mid] > target`: the target must be in the left half, so set `right = mid - 1`
|
|
|
|
|
|
|
|
**Step 3: Return insertion point**
|
|
|
|
- If we exit the loop without finding the target, return `left`
|
|
- At this point, `left` is the index where target should be inserted
|
|
|
|
|
|
|
|
The algorithm works because binary search naturally converges to the insertion point. When the target isn't found, `left` ends up at the position of the smallest element greater than target.
|
|
|
|
common_pitfalls:
|
|
- title: Using Linear Search
|
|
description: |
|
|
A naive approach is to iterate through the array and return the first index where `nums[i] >= target`:
|
|
|
|
```python
|
|
for i in range(len(nums)):
|
|
if nums[i] >= target:
|
|
return i
|
|
return len(nums)
|
|
```
|
|
|
|
While correct, this is **O(n)** time complexity. The problem explicitly requires **O(log n)**, so this approach would be considered incorrect even if it passes the test cases.
|
|
wrong_approach: "Linear scan through the array"
|
|
correct_approach: "Binary search halving the search space each step"
|
|
|
|
- title: Returning -1 When Not Found
|
|
description: |
|
|
Classic binary search returns `-1` when the element isn't found. But this problem asks for the *insertion position*, not whether the element exists.
|
|
|
|
If you return `-1` when `nums[mid] != target` after the loop, you'll fail test cases where the target needs to be inserted.
|
|
wrong_approach: "Return -1 when target not found"
|
|
correct_approach: "Return left pointer as the insertion position"
|
|
|
|
- title: Integer Overflow in Mid Calculation
|
|
description: |
|
|
Calculating `mid = (left + right) // 2` can cause integer overflow in some languages when `left` and `right` are both large.
|
|
|
|
Use `mid = left + (right - left) // 2` instead. In Python this isn't strictly necessary due to arbitrary precision integers, but it's a good habit for interviews where you might code in Java or C++.
|
|
wrong_approach: "mid = (left + right) // 2"
|
|
correct_approach: "mid = left + (right - left) // 2"
|
|
|
|
- title: Off-by-One Errors
|
|
description: |
|
|
A common mistake is using `left < right` instead of `left <= right`, or incorrectly updating pointers with `left = mid` or `right = mid` instead of `mid + 1` and `mid - 1`.
|
|
|
|
With `left <= right` and proper pointer updates, the search space shrinks by at least one element each iteration, guaranteeing termination.
|
|
wrong_approach: "while left < right with mid assignments"
|
|
correct_approach: "while left <= right with mid +/- 1 assignments"
|
|
|
|
key_takeaways:
|
|
- "**Binary search template**: This problem demonstrates the standard binary search pattern that applies to many problems — sorted array, O(log n) requirement, halving search space"
|
|
- "**Insertion point insight**: When binary search doesn't find the target, the `left` pointer naturally lands at the insertion position"
|
|
- "**Foundation for harder problems**: This exact technique is used in `bisect_left` in Python and underlies problems like finding first/last occurrence, search in rotated array, and more"
|
|
- "**Interview favourite**: This is a classic warm-up problem that tests whether you truly understand binary search beyond just finding an element"
|
|
|
|
time_complexity: "O(log n). Each iteration halves the search space, so we perform at most log<sub>2</sub>(n) comparisons."
|
|
space_complexity: "O(1). We only use two pointers (`left` and `right`) regardless of input size."
|
|
|
|
solutions:
|
|
- approach_name: Binary Search
|
|
is_optimal: true
|
|
code: |
|
|
def search_insert(nums: list[int], target: int) -> int:
|
|
left, right = 0, len(nums) - 1
|
|
|
|
while left <= right:
|
|
# Avoid potential overflow with this formula
|
|
mid = left + (right - left) // 2
|
|
|
|
if nums[mid] == target:
|
|
# Found the target, return its index
|
|
return mid
|
|
elif nums[mid] < target:
|
|
# Target is in the right half
|
|
left = mid + 1
|
|
else:
|
|
# Target is in the left half
|
|
right = mid - 1
|
|
|
|
# Target not found, left is the insertion point
|
|
return left
|
|
explanation: |
|
|
**Time Complexity:** O(log n) — Each iteration eliminates half the remaining elements.
|
|
|
|
**Space Complexity:** O(1) — Only two pointer variables used.
|
|
|
|
The binary search maintains the invariant that the answer (either the target's index or its insertion point) lies within `[left, right]`. When the target isn't found, `left` converges to the correct insertion position.
|
|
|
|
- approach_name: Linear Search
|
|
is_optimal: false
|
|
code: |
|
|
def search_insert(nums: list[int], target: int) -> int:
|
|
# Find first element >= target
|
|
for i in range(len(nums)):
|
|
if nums[i] >= target:
|
|
return i
|
|
|
|
# Target is larger than all elements
|
|
return len(nums)
|
|
explanation: |
|
|
**Time Complexity:** O(n) — May need to scan the entire array.
|
|
|
|
**Space Complexity:** O(1) — Only loop variable used.
|
|
|
|
This approach is simple and correct, but doesn't meet the O(log n) requirement. Included to illustrate why binary search is necessary. In an interview, using this solution would likely be marked incorrect despite passing test cases.
|