questions B (backspace - burst-balloons)
This commit is contained in:
197
backend/data/questions/binary-search.yaml
Normal file
197
backend/data/questions/binary-search.yaml
Normal file
@@ -0,0 +1,197 @@
|
||||
title: Binary Search
|
||||
slug: binary-search
|
||||
difficulty: easy
|
||||
leetcode_id: 704
|
||||
leetcode_url: https://leetcode.com/problems/binary-search/
|
||||
categories:
|
||||
- arrays
|
||||
- binary-search
|
||||
patterns:
|
||||
- binary-search
|
||||
|
||||
function_signature: "def search(nums: list[int], target: int) -> int:"
|
||||
|
||||
test_cases:
|
||||
visible:
|
||||
- input: { nums: [-1, 0, 3, 5, 9, 12], target: 9 }
|
||||
expected: 4
|
||||
- input: { nums: [-1, 0, 3, 5, 9, 12], target: 2 }
|
||||
expected: -1
|
||||
hidden:
|
||||
- input: { nums: [5], target: 5 }
|
||||
expected: 0
|
||||
- input: { nums: [5], target: 1 }
|
||||
expected: -1
|
||||
- input: { nums: [1, 2, 3, 4, 5], target: 1 }
|
||||
expected: 0
|
||||
- input: { nums: [1, 2, 3, 4, 5], target: 5 }
|
||||
expected: 4
|
||||
- input: { nums: [1, 2, 3, 4, 5], target: 3 }
|
||||
expected: 2
|
||||
|
||||
description: |
|
||||
Given an array of integers `nums` which is sorted in ascending order, and an integer `target`, write a function to search `target` in `nums`.
|
||||
|
||||
If `target` exists, return its index. Otherwise, return `-1`.
|
||||
|
||||
You must write an algorithm with **O(log n)** runtime complexity.
|
||||
|
||||
constraints: |
|
||||
- `1 <= nums.length <= 10^4`
|
||||
- `-10^4 < nums[i], target < 10^4`
|
||||
- All the integers in `nums` are **unique**
|
||||
- `nums` is sorted in **ascending order**
|
||||
|
||||
examples:
|
||||
- input: "nums = [-1,0,3,5,9,12], target = 9"
|
||||
output: "4"
|
||||
explanation: "9 exists in nums at index 4."
|
||||
- input: "nums = [-1,0,3,5,9,12], target = 2"
|
||||
output: "-1"
|
||||
explanation: "2 does not exist in nums, so return -1."
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
Imagine you're looking up a word in a physical dictionary. You don't start at page 1 and read every page — you open to the middle, see if your word comes before or after, then flip to the middle of the remaining half. You repeat this until you find the word.
|
||||
|
||||
Think of it like a **guessing game**: "I'm thinking of a number between 1 and 100." The optimal strategy is always to guess the middle — each guess eliminates half the possibilities.
|
||||
|
||||
This is the essence of **binary search**: by comparing the target with the middle element, we eliminate half the search space with each comparison. Instead of O(n) comparisons with linear search, we need only O(log n) comparisons.
|
||||
|
||||
The key requirement is that the array must be **sorted** — without sorted order, we can't know which half to eliminate.
|
||||
|
||||
approach: |
|
||||
We solve this using **Iterative Binary Search**:
|
||||
|
||||
**Step 1: Initialise the search boundaries**
|
||||
|
||||
- Set `left = 0` (start of array)
|
||||
- Set `right = len(nums) - 1` (end of array)
|
||||
- Our target, if it exists, must be within `[left, right]`
|
||||
|
||||
|
||||
|
||||
**Step 2: Loop while the search space is valid**
|
||||
|
||||
- Continue while `left <= right` (search space has at least one element)
|
||||
- Calculate the middle index: `mid = left + (right - left) // 2`
|
||||
- Note: We use `left + (right - left) // 2` instead of `(left + right) // 2` to prevent integer overflow in some languages
|
||||
|
||||
|
||||
|
||||
**Step 3: Compare and narrow the search space**
|
||||
|
||||
- If `nums[mid] == target`: Found it! Return `mid`
|
||||
- If `nums[mid] < target`: Target is in the right half, set `left = mid + 1`
|
||||
- If `nums[mid] > target`: Target is in the left half, set `right = mid - 1`
|
||||
|
||||
|
||||
|
||||
**Step 4: Handle not found**
|
||||
|
||||
- If the loop exits (left > right), the target doesn't exist
|
||||
- Return `-1` to indicate not found
|
||||
|
||||
|
||||
|
||||
Each iteration eliminates half the remaining elements, giving us O(log n) time complexity.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Integer Overflow in Mid Calculation
|
||||
description: |
|
||||
Using `(left + right) / 2` can cause integer overflow in languages with fixed-size integers (like Java or C++) when `left` and `right` are both large.
|
||||
|
||||
For example, if `left = 2,000,000,000` and `right = 2,000,000,000`, their sum overflows a 32-bit integer.
|
||||
|
||||
The safe formula `left + (right - left) // 2` avoids this by never computing a sum larger than `right`.
|
||||
wrong_approach: "mid = (left + right) / 2"
|
||||
correct_approach: "mid = left + (right - left) // 2"
|
||||
|
||||
- title: Off-by-One in Loop Condition
|
||||
description: |
|
||||
Using `while left < right` instead of `while left <= right` can miss the target when it's the only element left in the search space.
|
||||
|
||||
For example, if `left == right == 3` and `nums[3]` is the target, `while left < right` exits immediately without checking!
|
||||
|
||||
The `<=` ensures we check when the search space has exactly one element.
|
||||
wrong_approach: "while left < right"
|
||||
correct_approach: "while left <= right"
|
||||
|
||||
- title: Incorrect Boundary Updates
|
||||
description: |
|
||||
After checking `mid`, it must be excluded from the next search. Using `left = mid` or `right = mid` can cause infinite loops.
|
||||
|
||||
If `nums[mid] < target`, we know `mid` isn't the answer, so `left = mid + 1` excludes it.
|
||||
If `nums[mid] > target`, we know `mid` isn't the answer, so `right = mid - 1` excludes it.
|
||||
wrong_approach: "left = mid or right = mid"
|
||||
correct_approach: "left = mid + 1 or right = mid - 1"
|
||||
|
||||
key_takeaways:
|
||||
- "**Sorted array prerequisite**: Binary search only works on sorted data — always verify this condition"
|
||||
- "**O(log n) power**: Halving the search space each iteration is incredibly efficient — 1 billion elements needs only ~30 comparisons"
|
||||
- "**Template for variations**: This basic pattern extends to finding insertion points, rotated arrays, peak elements, and more"
|
||||
- "**Boundary precision matters**: Off-by-one errors are the most common bugs — always think carefully about `<` vs `<=` and `mid ± 1`"
|
||||
|
||||
time_complexity: "O(log n). Each iteration halves the search space, so we need at most log₂(n) iterations."
|
||||
space_complexity: "O(1). Only three variables (`left`, `right`, `mid`) are used, regardless of input size."
|
||||
|
||||
solutions:
|
||||
- approach_name: Iterative Binary Search
|
||||
is_optimal: true
|
||||
code: |
|
||||
def search(nums: list[int], target: int) -> int:
|
||||
# Initialise search boundaries
|
||||
left, right = 0, len(nums) - 1
|
||||
|
||||
# Continue while search space is valid
|
||||
while left <= right:
|
||||
# Calculate middle index (overflow-safe)
|
||||
mid = left + (right - left) // 2
|
||||
|
||||
if nums[mid] == target:
|
||||
# Found the target
|
||||
return mid
|
||||
elif nums[mid] < target:
|
||||
# Target is in right half, exclude mid
|
||||
left = mid + 1
|
||||
else:
|
||||
# Target is in left half, exclude mid
|
||||
right = mid - 1
|
||||
|
||||
# Target not found
|
||||
return -1
|
||||
explanation: |
|
||||
**Time Complexity:** O(log n) — Search space halves each iteration.
|
||||
|
||||
**Space Complexity:** O(1) — Only constant extra space used.
|
||||
|
||||
Classic binary search: compare the middle element with the target and eliminate half the search space each iteration. The loop terminates when the target is found or the search space is exhausted.
|
||||
|
||||
- approach_name: Recursive Binary Search
|
||||
is_optimal: false
|
||||
code: |
|
||||
def search(nums: list[int], target: int) -> int:
|
||||
def binary_search(left: int, right: int) -> int:
|
||||
# Base case: search space exhausted
|
||||
if left > right:
|
||||
return -1
|
||||
|
||||
# Calculate middle index
|
||||
mid = left + (right - left) // 2
|
||||
|
||||
if nums[mid] == target:
|
||||
return mid
|
||||
elif nums[mid] < target:
|
||||
# Search right half
|
||||
return binary_search(mid + 1, right)
|
||||
else:
|
||||
# Search left half
|
||||
return binary_search(left, mid - 1)
|
||||
|
||||
return binary_search(0, len(nums) - 1)
|
||||
explanation: |
|
||||
**Time Complexity:** O(log n) — Same number of comparisons as iterative.
|
||||
|
||||
**Space Complexity:** O(log n) — Recursion stack depth.
|
||||
|
||||
Recursive implementation with the same logic. While elegant, it uses O(log n) stack space. The iterative version is preferred for its O(1) space complexity.
|
||||
Reference in New Issue
Block a user