1099. Two Sum Less Than K 🔒
Problem Description
You are given an array of integers nums
and an integer k
. Your task is to find the maximum possible sum of two different elements from the array such that their sum is less than k
.
The requirements are:
- You need to pick two elements at different indices
i
andj
wherei < j
- The sum
nums[i] + nums[j]
must be less thank
- Among all valid pairs, you want the maximum possible sum
If no such pair exists that satisfies these conditions, return -1
.
For example:
- If
nums = [34, 23, 1, 24, 75, 33, 54, 8]
andk = 60
, the maximum sum less than 60 would be58
(formed by24 + 34
) - If no two elements can sum to less than
k
, the answer would be-1
The solution uses sorting combined with binary search. After sorting the array, for each element nums[i]
, it uses binary search to find the largest possible nums[j]
such that nums[i] + nums[j] < k
. The binary search specifically looks for the position where k - nums[i]
would be inserted, then takes the element just before that position to ensure the sum stays below k
.
Intuition
The key insight is that once we sort the array, we can efficiently find the best pairing for each element. Think about it this way: for any element nums[i]
, we want to find the largest possible nums[j]
that we can pair it with while keeping their sum below k
.
Why sorting helps? In a sorted array, if we're looking for a partner for nums[i]
, we know that the maximum allowed value for its partner would be something less than k - nums[i]
. Since the array is sorted, we can use binary search to quickly locate the largest element that is still less than k - nums[i]
.
The beauty of this approach is that bisect_left(nums, k - x, lo=i + 1)
finds the insertion point for k - x
in the sorted array starting from index i + 1
. By subtracting 1 from this position, we get the index of the largest element that is smaller than k - x
. This element, when added to x
, gives us the largest sum that is still less than k
.
For each element, we're essentially asking: "What's the biggest number I can add to this element without exceeding k
?" The sorted order allows us to answer this question in O(log n)
time using binary search, rather than checking every possible pair which would take O(n)
time.
By iterating through each element and finding its best partner using this method, we ensure we don't miss the maximum possible sum. We keep track of the maximum sum found so far, and if no valid pair exists, we return -1
.
Learn more about Two Pointers, Binary Search and Sorting patterns.
Solution Approach
The solution follows a sorting + binary search strategy to efficiently find the maximum sum less than k
.
Step 1: Sort the array
nums.sort()
We first sort the array in ascending order. This enables us to use binary search and ensures that for any element, all potential partners greater than it are to its right.
Step 2: Initialize the answer
ans = -1
We start with -1
as our answer, which will be returned if no valid pair exists.
Step 3: Iterate through each element
for i, x in enumerate(nums):
We enumerate through each element x
at index i
in the sorted array. This element will be the first element of our potential pair.
Step 4: Binary search for the best partner
j = bisect_left(nums, k - x, lo=i + 1) - 1
For each element x
, we need to find the largest element that, when added to x
, gives a sum less than k
.
- We search for
k - x
starting from indexi + 1
(to ensurei < j
) bisect_left
returns the leftmost insertion position fork - x
- By subtracting 1, we get the index of the largest element less than
k - x
- This element at index
j
is the best partner forx
Step 5: Update the maximum sum
if i < j:
ans = max(ans, x + nums[j])
We verify that we found a valid index j
(ensuring i < j
), then update our answer with the maximum between the current answer and the new sum x + nums[j]
.
Step 6: Return the result
return ans
After checking all possible first elements, we return the maximum sum found, or -1
if no valid pair exists.
Time Complexity: O(n log n)
where n
is the length of the array - O(n log n)
for sorting plus O(n log n)
for n
binary searches.
Space Complexity: O(1)
if we don't count the space used by sorting (in-place sorting), otherwise O(n)
for the sorting algorithm.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Input: nums = [10, 20, 30]
, k = 31
Step 1: Sort the array
The array is already sorted: [10, 20, 30]
Step 2: Initialize answer
ans = -1
Step 3-5: Iterate and search for best partners
Iteration 1: i = 0
, x = 10
- We need to find the largest number that when added to 10 gives sum < 31
- Search for
k - x = 31 - 10 = 21
starting from indexi + 1 = 1
bisect_left([20, 30], 21, lo=0)
returns index 1 (where 21 would be inserted)j = 1 - 1 = 0
(relative to the search range), which maps to actual index 1 in the full array- Check:
i = 0 < j = 1
✓ - Sum:
10 + nums[1] = 10 + 20 = 30
(which is < 31) ✓ - Update:
ans = max(-1, 30) = 30
Iteration 2: i = 1
, x = 20
- Search for
k - x = 31 - 20 = 11
starting from indexi + 1 = 2
bisect_left([30], 11, lo=0)
returns index 0 (11 would go before 30)j = 0 - 1 = -1
(relative), which maps to actual index 1 in the full array- Check:
i = 1 < j = 1
✗ (not valid) - No update to answer
Iteration 3: i = 2
, x = 30
- Search for
k - x = 31 - 30 = 1
starting from indexi + 1 = 3
- Since we're searching beyond the array bounds, no valid partner exists
- No update to answer
Step 6: Return result
Return ans = 30
The maximum sum less than 31 is 30 (formed by 10 + 20).
Solution Implementation
1from typing import List
2from bisect import bisect_left
3
4
5class Solution:
6 def twoSumLessThanK(self, nums: List[int], k: int) -> int:
7 # Sort the array to enable binary search
8 nums.sort()
9
10 # Initialize the maximum sum to -1 (default return value if no valid pair exists)
11 max_sum = -1
12
13 # Iterate through each number as the first element of the pair
14 for i, first_num in enumerate(nums):
15 # Find the largest possible second number such that first_num + second_num < k
16 # We search for k - first_num and take the position just before it
17 target = k - first_num
18
19 # Use binary search to find the insertion point for target
20 # Search starts from i + 1 to avoid using the same element twice
21 insert_pos = bisect_left(nums, target, lo=i + 1)
22
23 # The actual index of the largest valid second number is insert_pos - 1
24 j = insert_pos - 1
25
26 # Check if we found a valid second element
27 if i < j:
28 # Update the maximum sum if current pair has a larger sum
29 current_sum = first_num + nums[j]
30 max_sum = max(max_sum, current_sum)
31
32 return max_sum
33
1class Solution {
2 /**
3 * Find the maximum sum of two elements in the array that is less than k.
4 * @param nums the input array
5 * @param k the upper bound (exclusive)
6 * @return the maximum sum less than k, or -1 if no such sum exists
7 */
8 public int twoSumLessThanK(int[] nums, int k) {
9 // Sort the array to enable binary search
10 Arrays.sort(nums);
11
12 int maxSum = -1;
13 int length = nums.length;
14
15 // Iterate through each element as the first number
16 for (int i = 0; i < length; ++i) {
17 // Find the largest number that when added to nums[i] is less than k
18 // We need nums[j] < k - nums[i], so we search for k - nums[i]
19 int targetValue = k - nums[i];
20
21 // Binary search starting from i + 1 to avoid using the same element twice
22 int insertionPoint = binarySearchLeftmost(nums, targetValue, i + 1, length);
23
24 // The largest valid index is insertionPoint - 1
25 int j = insertionPoint - 1;
26
27 // Check if we found a valid pair
28 if (i < j) {
29 int currentSum = nums[i] + nums[j];
30 maxSum = Math.max(maxSum, currentSum);
31 }
32 }
33
34 return maxSum;
35 }
36
37 /**
38 * Binary search to find the leftmost position where x should be inserted.
39 * Returns the index of the first element >= x, or right boundary if all elements < x.
40 * @param nums the sorted array to search in
41 * @param x the target value to search for
42 * @param left the left boundary (inclusive)
43 * @param right the right boundary (exclusive)
44 * @return the insertion position for x
45 */
46 private int binarySearchLeftmost(int[] nums, int x, int left, int right) {
47 while (left < right) {
48 // Calculate middle index using bit shift for efficiency
49 int mid = (left + right) >> 1;
50
51 if (nums[mid] >= x) {
52 // If mid element is >= x, search in left half
53 right = mid;
54 } else {
55 // If mid element is < x, search in right half
56 left = mid + 1;
57 }
58 }
59
60 return left;
61 }
62}
63
1class Solution {
2public:
3 int twoSumLessThanK(vector<int>& nums, int k) {
4 // Sort the array to enable binary search
5 sort(nums.begin(), nums.end());
6
7 // Initialize result as -1 (no valid pair found) and get array size
8 int maxSum = -1;
9 int arraySize = nums.size();
10
11 // Iterate through each element as the first number in the pair
12 for (int i = 0; i < arraySize; ++i) {
13 // Find the largest index j where nums[j] < k - nums[i]
14 // This ensures nums[i] + nums[j] < k
15 int targetValue = k - nums[i];
16 int j = lower_bound(nums.begin() + i + 1, nums.end(), targetValue) - nums.begin() - 1;
17
18 // Check if we found a valid pair (j must be greater than i)
19 if (i < j) {
20 // Update the maximum sum if current pair sum is larger
21 int currentSum = nums[i] + nums[j];
22 maxSum = max(maxSum, currentSum);
23 }
24 }
25
26 return maxSum;
27 }
28};
29
1/**
2 * Finds the maximum sum of two different elements in the array that is less than k.
3 * Uses two-pointer technique on a sorted array for optimal efficiency.
4 *
5 * @param nums - Array of integers to search through
6 * @param k - Upper bound (exclusive) for the sum
7 * @returns Maximum sum less than k, or -1 if no valid pair exists
8 */
9function twoSumLessThanK(nums: number[], k: number): number {
10 // Sort the array in ascending order to enable two-pointer approach
11 nums.sort((a: number, b: number) => a - b);
12
13 // Initialize result as -1 (default when no valid pair is found)
14 let maxSum: number = -1;
15
16 // Use two pointers: left starts at beginning, right starts at end
17 let leftPointer: number = 0;
18 let rightPointer: number = nums.length - 1;
19
20 // Continue while pointers haven't crossed
21 while (leftPointer < rightPointer) {
22 // Calculate sum of elements at current pointer positions
23 const currentSum: number = nums[leftPointer] + nums[rightPointer];
24
25 if (currentSum < k) {
26 // Valid sum found - update maximum if needed
27 maxSum = Math.max(maxSum, currentSum);
28 // Move left pointer right to potentially find larger sum
29 leftPointer++;
30 } else {
31 // Sum is too large or equal to k
32 // Move right pointer left to reduce sum
33 rightPointer--;
34 }
35 }
36
37 return maxSum;
38}
39
Time and Space Complexity
Time Complexity: O(n × log n)
The time complexity consists of two main operations:
- Sorting the array:
nums.sort()
takesO(n × log n)
time - Iterating through the array with binary search: The for loop runs
n
times, and each iteration performs abisect_left
operation which takesO(log n)
time, resulting inO(n × log n)
time
Since both operations are O(n × log n)
, the overall time complexity is O(n × log n)
, where n
is the length of the array nums
.
Space Complexity: O(log n)
The space complexity comes from:
- The sorting algorithm: Python's Timsort (used in
sort()
) requiresO(log n)
space for its recursion stack - The binary search (
bisect_left
): UsesO(log n)
space for its recursion stack (orO(1)
if implemented iteratively, but Python's implementation uses recursion) - A few constant space variables (
ans
,i
,x
,j
):O(1)
space
The dominant factor is the O(log n)
space from sorting, making the overall space complexity O(log n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Binary Search
One of the most common mistakes is misunderstanding what bisect_left
returns and incorrectly calculating the partner index.
The Pitfall:
# Incorrect: forgetting to subtract 1
j = bisect_left(nums, k - x, lo=i + 1) # Wrong!
if i < j:
ans = max(ans, x + nums[j]) # This might access an invalid element
bisect_left
returns the insertion position where k - x
would go to maintain sorted order. If k - x
doesn't exist in the array, this position points to the first element greater than or equal to k - x
. We need the element just before this position.
The Solution:
# Correct: subtract 1 to get the largest element less than k - x
j = bisect_left(nums, k - x, lo=i + 1) - 1
if i < j:
ans = max(ans, x + nums[j])
2. Array Index Out of Bounds
When bisect_left
returns i + 1
(meaning no element >= k - x
exists after index i
), subtracting 1 gives us j = i
, which violates our i < j
requirement.
The Pitfall:
# Potentially problematic if not checking i < j
j = bisect_left(nums, k - x, lo=i + 1) - 1
ans = max(ans, x + nums[j]) # Could access nums[i] when j = i
The Solution:
Always verify i < j
before using the indices:
j = bisect_left(nums, k - x, lo=i + 1) - 1
if i < j: # This check prevents using the same element twice
ans = max(ans, x + nums[j])
3. Using the Same Element Twice
Without proper bounds in binary search, we might accidentally pair an element with itself.
The Pitfall:
# Wrong: starting binary search from index i instead of i + 1 j = bisect_left(nums, k - x, lo=i) - 1 # Could return j = i
The Solution:
Always start the binary search from i + 1
:
# Correct: ensures j > i j = bisect_left(nums, k - x, lo=i + 1) - 1
4. Forgetting to Handle Edge Cases
Not considering when no valid pair exists or when the array has fewer than 2 elements.
The Pitfall:
def twoSumLessThanK(self, nums: List[int], k: int) -> int:
nums.sort()
# Missing check for array length
for i, x in enumerate(nums):
# ... rest of the code
The Solution: Either add an explicit check or rely on the loop naturally handling it:
def twoSumLessThanK(self, nums: List[int], k: int) -> int:
if len(nums) < 2:
return -1
nums.sort()
ans = -1
# ... rest of the code
Or let the algorithm handle it naturally (the current solution does this - if there's only one element, the loop runs once but i < j
will never be true).
Which data structure is used in a depth first search?
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
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!