167. Two Sum II - Input Array Is Sorted
Problem Description
You are given a 1-indexed array of integers called numbers
that is already sorted in non-decreasing order (smallest to largest). Your task is to find two numbers in this array that add up to a specific target
value.
The key requirements are:
- The array uses 1-based indexing (the first element is at index 1, not 0)
- You need to find exactly two different elements whose sum equals the target
- Return the indices of these two numbers as
[index1, index2]
whereindex1 < index2
- Since the array is 1-indexed, you need to add 1 to the actual array positions
- You cannot use the same element twice (the two numbers must be at different positions)
- The problem guarantees exactly one solution exists
- Your solution must use only constant extra space (O(1) space complexity)
For example, if numbers = [2, 7, 11, 15]
and target = 9
, you would return [1, 2]
because numbers[1] + numbers[2] = 2 + 7 = 9
.
The solution leverages the sorted nature of the array. For each element numbers[i]
, it uses binary search to look for the complement value target - numbers[i]
in the remaining portion of the array (from position i+1
onwards). The bisect_left
function efficiently finds where the complement would be positioned in the sorted array. If the complement exists at that position, the function returns the 1-indexed positions of both numbers.
Intuition
When we need to find two numbers that sum to a target, the most straightforward approach would be to check every possible pair, but that would take O(n²) time. However, we can take advantage of the fact that the array is already sorted.
The key insight is that for any number in our array, we know exactly what its complement needs to be: complement = target - current_number
. Since the array is sorted, we can efficiently search for this complement using binary search instead of checking every element.
Here's the thought process:
- Pick a number from the array (let's call it
numbers[i]
) - Calculate what value we need to find:
x = target - numbers[i]
- Since the array is sorted, we can use binary search to quickly check if
x
exists in the remaining part of the array
Why search only in the remaining part (from i+1
onwards)? Because:
- We can't use the same element twice
- If the complement existed before position
i
, we would have already found it when we processed earlier elements - This prevents us from finding duplicate pairs like
[i, j]
and[j, i]
Binary search reduces the search time from O(n) to O(log n) for each element we check. Since we might need to check up to n elements in the worst case, the overall time complexity becomes O(n log n), which is much better than the brute force O(n²) approach.
The bisect_left
function finds the leftmost position where x
would fit in the sorted array. If the element at that position equals x
, we've found our pair. If not, x
doesn't exist in the array, and we move on to the next element.
Solution Approach
The solution implements a binary search strategy to find the two numbers that sum to the target. Let's walk through the implementation step by step:
Algorithm Overview: The approach iterates through each element in the array and uses binary search to find its complement.
Step-by-Step Implementation:
-
Iterate through the array: We loop through indices from
0
ton-2
(wheren
is the length of the array). We don't need to check the last element because it would have no elements after it to pair with. -
Calculate the complement: For each
numbers[i]
, we calculate the required complement:x = target - numbers[i]
This
x
is the value we need to find in the remaining array to achieve our target sum. -
Binary search for the complement: We use Python's
bisect_left
function to perform binary search:j = bisect_left(numbers, x, lo=i + 1)
bisect_left
returns the leftmost insertion position forx
in the sorted array- The
lo=i + 1
parameter ensures we only search in the portion of the array after indexi
- This prevents using the same element twice and avoids duplicate pairs
-
Check if complement exists: After getting the position
j
, we verify two conditions:if j < n and numbers[j] == x:
j < n
: Ensures the position is within array boundsnumbers[j] == x
: Confirms that the element at positionj
is actually our target complement
-
Return the result: If we find a valid pair, we return their 1-indexed positions:
return [i + 1, j + 1]
We add 1 to both indices because the problem uses 1-based indexing.
Time Complexity: O(n log n)
- We iterate through at most
n
elements - For each element, we perform a binary search which takes O(log n)
Space Complexity: O(1)
- We only use a constant amount of extra space for variables
i
,j
, andx
- This satisfies the problem's requirement of using only constant extra space
The beauty of this solution lies in leveraging the sorted nature of the array to transform what could be an O(n²) brute force approach into an efficient O(n log n) solution using binary search.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the binary search solution works.
Example: numbers = [1, 3, 4, 7, 11]
, target = 10
We need to find two numbers that sum to 10. The answer should be [2, 4]
because numbers[2] + numbers[4] = 3 + 7 = 10
(using 1-based indexing).
Step-by-step execution:
Iteration 1: i = 0
(examining numbers[0] = 1
)
- Calculate complement:
x = 10 - 1 = 9
- Binary search for 9 in
[3, 4, 7, 11]
(from index 1 onwards) bisect_left
returns position 4 (where 9 would be inserted)- Check:
numbers[4] = 11 ≠ 9
- No match found, continue
Iteration 2: i = 1
(examining numbers[1] = 3
)
- Calculate complement:
x = 10 - 3 = 7
- Binary search for 7 in
[4, 7, 11]
(from index 2 onwards) bisect_left
returns position 3 (where 7 is located)- Check:
numbers[3] = 7 = 7
✓ - Match found! Return
[i + 1, j + 1] = [2, 4]
Visual representation of Iteration 2:
Array: [1, 3, 4, 7, 11] Indices: 0 1 2 3 4 ↑ i = 1 numbers[i] = 3 Search for x = 7 in subarray [4, 7, 11] (indices 2, 3, 4) Binary search finds 7 at index 3 Return [1+1, 3+1] = [2, 4] (converting to 1-based indexing)
The algorithm successfully finds that elements at 1-based positions 2 and 4 (values 3 and 7) sum to our target of 10. The binary search makes this process efficient by quickly locating the complement value rather than checking every remaining element.
Solution Implementation
1from typing import List
2from bisect import bisect_left
3
4
5class Solution:
6 def twoSum(self, numbers: List[int], target: int) -> List[int]:
7 """
8 Find two numbers in a sorted array that sum to the target.
9
10 Args:
11 numbers: Sorted array of integers in ascending order
12 target: Target sum to find
13
14 Returns:
15 List containing 1-indexed positions of the two numbers
16 """
17 n = len(numbers)
18
19 # Iterate through each number as the first element
20 for i in range(n - 1):
21 # Calculate the complement needed to reach the target
22 complement = target - numbers[i]
23
24 # Use binary search to find the complement in the remaining array
25 # Search starts from i+1 to avoid using the same element twice
26 j = bisect_left(numbers, complement, lo=i + 1)
27
28 # Check if the complement exists at the found position
29 if j < n and numbers[j] == complement:
30 # Return 1-indexed positions
31 return [i + 1, j + 1]
32
1class Solution {
2 /**
3 * Finds two numbers in a sorted array that add up to the target sum.
4 * Uses binary search to find the complement of each number.
5 *
6 * @param numbers The sorted array of integers
7 * @param target The target sum to find
8 * @return An array containing the 1-indexed positions of the two numbers
9 */
10 public int[] twoSum(int[] numbers, int target) {
11 int arrayLength = numbers.length;
12
13 // Iterate through each number as the first element of the pair
14 for (int firstIndex = 0; ; firstIndex++) {
15 // Calculate the complement needed to reach the target
16 int complement = target - numbers[firstIndex];
17
18 // Binary search for the complement in the remaining array
19 int left = firstIndex + 1;
20 int right = arrayLength - 1;
21
22 // Binary search to find the leftmost occurrence of complement
23 while (left < right) {
24 int middle = (left + right) / 2;
25
26 if (numbers[middle] >= complement) {
27 // Complement might be at middle or to the left
28 right = middle;
29 } else {
30 // Complement must be to the right
31 left = middle + 1;
32 }
33 }
34
35 // Check if we found the complement
36 if (numbers[left] == complement) {
37 // Return 1-indexed positions
38 return new int[] {firstIndex + 1, left + 1};
39 }
40 }
41 }
42}
43
1class Solution {
2public:
3 vector<int> twoSum(vector<int>& numbers, int target) {
4 int n = numbers.size();
5
6 // Iterate through each element as the first number
7 for (int i = 0; i < n; ++i) {
8 // Calculate the complement needed to reach the target
9 int complement = target - numbers[i];
10
11 // Binary search for the complement in the remaining sorted array
12 // Search starts from index i+1 to avoid using the same element twice
13 int j = lower_bound(numbers.begin() + i + 1, numbers.end(), complement) - numbers.begin();
14
15 // Check if the complement exists at the found position
16 if (j < n && numbers[j] == complement) {
17 // Return 1-indexed positions as required by the problem
18 return {i + 1, j + 1};
19 }
20 }
21
22 // This line should never be reached given problem constraints
23 // (problem guarantees exactly one solution exists)
24 return {};
25 }
26};
27
1/**
2 * Finds two numbers in a sorted array that sum to the target value
3 * @param numbers - Sorted array of numbers in ascending order
4 * @param target - Target sum to find
5 * @returns Array containing 1-indexed positions of the two numbers
6 */
7function twoSum(numbers: number[], target: number): number[] {
8 const arrayLength = numbers.length;
9
10 // Iterate through each number as the first element
11 for (let firstIndex = 0; ; firstIndex++) {
12 // Calculate the complement needed to reach the target
13 const complement = target - numbers[firstIndex];
14
15 // Use binary search to find the complement in the remaining array
16 let left = firstIndex + 1;
17 let right = arrayLength - 1;
18
19 // Binary search for the complement value
20 while (left < right) {
21 // Calculate middle index using bit shift for efficiency
22 const middle = (left + right) >> 1;
23
24 // Narrow down search range based on comparison
25 if (numbers[middle] >= complement) {
26 // Complement might be at middle or to the left
27 right = middle;
28 } else {
29 // Complement must be to the right
30 left = middle + 1;
31 }
32 }
33
34 // Check if we found the complement
35 if (numbers[left] === complement) {
36 // Return 1-indexed positions as per problem requirements
37 return [firstIndex + 1, left + 1];
38 }
39 }
40}
41
Time and Space Complexity
The time complexity is O(n × log n)
, where n
is the length of the array numbers
. This is because:
- The outer loop runs
n - 1
times, iterating through each element as a potential first number - For each iteration,
bisect_left
performs a binary search on the remaining portion of the array (from indexi + 1
to the end), which takesO(log n)
time - Therefore, the overall time complexity is
O(n) × O(log n) = O(n × log n)
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space for variables i
, j
, x
, and n
, regardless of the input size. The bisect_left
function performs binary search iteratively without using additional space proportional to the input.
Common Pitfalls
1. Using the Same Element Twice
One of the most common mistakes is accidentally allowing the same array element to be used twice in the sum. This can happen if you don't properly restrict the search range.
Incorrect approach:
# WRONG: This might find the same element twice j = bisect_left(numbers, complement) # Searches entire array if j < n and numbers[j] == complement: return [i + 1, j + 1]
Why it fails: If target = 4
and numbers = [1, 2, 3]
, when i = 1
(value 2), the complement is also 2. Without restricting the search range, binary search might return index 1 again, giving us the same element twice.
Solution: Always search starting from i + 1
:
j = bisect_left(numbers, complement, lo=i + 1) # Correct
2. Forgetting to Convert to 1-Based Indexing
The problem explicitly requires 1-indexed results, but Python uses 0-based indexing. Forgetting to add 1 to the indices is a frequent error.
Incorrect approach:
# WRONG: Returns 0-based indices return [i, j]
Solution: Always add 1 to both indices:
return [i + 1, j + 1] # Correct 1-based indexing
3. Not Checking if the Found Position Contains the Target Value
bisect_left
returns where the value should be inserted, not necessarily where it exists. Failing to verify the actual value can lead to incorrect results.
Incorrect approach:
# WRONG: Assumes the complement exists without verification j = bisect_left(numbers, complement, lo=i + 1) return [i + 1, j + 1] # Might return wrong indices
Why it fails: If the complement doesn't exist, bisect_left
still returns a valid insertion position, but numbers[j]
won't equal the complement.
Solution: Always verify both bounds and value:
if j < n and numbers[j] == complement: # Correct validation return [i + 1, j + 1]
4. Using Two Pointers Incorrectly with Additional Constraints
While the two-pointer approach is valid for this problem, mixing it with binary search or implementing it incorrectly can cause issues.
Alternative correct two-pointer approach:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
left, right = 0, len(numbers) - 1
while left < right:
current_sum = numbers[left] + numbers[right]
if current_sum == target:
return [left + 1, right + 1]
elif current_sum < target:
left += 1
else:
right -= 1
This approach is actually more efficient (O(n) time) than the binary search method, but both are valid solutions.
How does merge sort divide the problem into subproblems?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!