How to Solve the Two Sum Problem in C++
The Two Sum problem is a common interview question that tests your understanding of algorithms and data structures. The goal is simple: given an array of integers and a target value, find two numbers that add up to the target and return their indices.
In this post, we will walk through how to solve the Two Sum problem in C++, starting from a brute force solution and then moving to a more optimized approach using a hash map (unordered map). We will also cover the time and space complexities of each approach.
Problem Statement
Given an array of integers nums
and an integer target
, return the indices of the two numbers such that they add up to the target
.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Example:
Input: nums = [2, 7, 11, 15], target = 9
Output: [0, 1]
Explanation: Because nums[0] + nums[1] = 2 + 7 = 9.
Approach 1: Brute Force Solution
The brute-force approach checks all possible pairs in the array to see if their sum equals the target. This method is straightforward but inefficient for large arrays.
Algorithm:
- Use two nested loops.
- For each element
nums[i]
, iterate through the array with another elementnums[j]
wherej > i
. - Check if
nums[i] + nums[j]
equals the target. - If the sum matches the target, return their indices.
Code:
#include <iostream>
#include <vector>
std::vector<int> twoSum(const std::vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); i++) {
for (int j = i + 1; j < nums.size(); j++) {
if (nums[i] + nums[j] == target) {
return {i, j}; // Return the indices as a vector
}
}
}
return {}; // Return an empty vector if no solution is found
}
int main() {
std::vector<int> nums = {2, 7, 11, 15};
int target = 9;
std::vector<int> result = twoSum(nums, target);
if (!result.empty()) {
std::cout << "Indices: [" << result[0] << ", " << result[1] << "]\n";
} else {
std::cout << "No solution found\n";
}
return 0;
}
Explanation:
- We use two nested loops to find all pairs of numbers.
- If the sum of two numbers equals the target, their indices are returned.
Time Complexity:
- O(n²): We check each pair of elements, resulting in quadratic time complexity.
- Space Complexity: O(1) because no additional space is used apart from the input.
Approach 2: Optimized Solution Using Hash Map (Unordered Map)
The brute-force approach can be optimized using a hash map (unordered map in C++). The idea is to use the hash map to store each element's complement (target - nums[i]
) as we iterate through the array. If we find a number that matches the complement already in the map, we have found the solution.
Algorithm:
- Create an unordered map to store the value of the number and its index.
- Loop through the array:
- Calculate the complement (
target - nums[i]
). - Check if the complement is in the map.
- If it is, return the current index and the stored index from the map.
- If not, add the current number and its index to the map.
- Calculate the complement (
Code:
#include <iostream>
#include <unordered_map>
#include <vector>
std::vector<int> twoSum(const std::vector<int>& nums, int target) {
std::unordered_map<int, int> numMap; // Create a hash map to store numbers and their indices
for (int i = 0; i < nums.size(); i++) {
int complement = target - nums[i]; // Calculate the complement
if (numMap.find(complement) != numMap.end()) { // If complement is found in the map
return {numMap[complement], i}; // Return the indices
}
numMap[nums[i]] = i; // Add the number and its index to the map
}
return {}; // Return an empty vector if no solution is found
}
int main() {
std::vector<int> nums = {2, 7, 11, 15};
int target = 9;
std::vector<int> result = twoSum(nums, target);
if (!result.empty()) {
std::cout << "Indices: [" << result[0] << ", " << result[1] << "]\n";
} else {
std::cout << "No solution found\n";
}
return 0;
}
Explanation:
- We loop through the array, calculating the complement for each number.
- We use an unordered map (
numMap
) to store the indices of numbers we’ve seen so far. - If the complement is already in the map, we return the current index and the index of the complement.
Time Complexity:
- O(n): We loop through the array once, and each lookup in the map takes constant time.
- Space Complexity: O(n) because we store up to
n
elements in the map.
Edge Cases
While the problem guarantees exactly one solution, you should still account for various edge cases during implementation:
-
Negative and Positive Numbers:
The array may contain both negative and positive numbers. Ensure that the solution works for both.std::vector<int> nums = {-3, 4, 3, 90}; int target = 0; std::vector<int> result = twoSum(nums, target); std::cout << result[0] << ", " << result[1] << std::endl; // Output: 0, 2
-
Duplicate Values:
The array may contain duplicate values, and the solution should still return the correct indices.std::vector<int> nums = {3, 3}; int target = 6; std::vector<int> result = twoSum(nums, target); std::cout << result[0] << ", " << result[1] << std::endl; // Output: 0, 1
-
Array with Only Two Elements:
Since the problem guarantees at least two elements, the array can have exactly two elements, and the solution should still work.std::vector<int> nums = {1, 4}; int target = 5; std::vector<int> result = twoSum(nums, target); std::cout << result[0] << ", " << result[1] << std::endl; // Output: 0, 1
-
Zeros in the Array:
If the array contains zeros, the solution should correctly handle the target involving zero.std::vector<int> nums = {0, 4, 3, 0}; int target = 0; std::vector<int> result = twoSum(nums, target); std::cout << result[0] << ", " << result[1] << std::endl; // Output: 0, 3
-
Large Inputs:
If the array contains a large number of elements, the hash map approach ensures efficiency even with significant data size.std::vector<int> nums(100000); for (int i = 0; i < 100000; i++) nums[i] = i; int target = 199999; std::vector<int> result = twoSum(nums, target); std::cout << result[0] << ", " << result[1] << std::endl; // Output: 99998, 99999
Testing the Solution
It’s always important to test the solution with different scenarios:
int main() {
// Test case 1: Normal case
std::vector<int> result1 = twoSum({2, 7, 11, 15}, 9);
std::cout << "Result: [" << result1[0] << ", " << result1[1] << "]\n"; // Output: [0, 1]
// Test case 2: No valid pair
std::vector<int> result2 = twoSum({1, 2, 3}, 10);
std::cout << "Result: " << (result2.empty() ? "No solution" : "Solution found") << "\n"; // Output: No solution
// Test case 3: Same element twice
std::vector<int> result3 = twoSum({3, 3}, 6);
std::cout << "Result: [" << result3[0] << ", " << result3[1] << "]\n"; // Output: [0, 1]
// Test case 4: Large array
std::vector<int> result4 = twoSum({1, 5, 3, 7, 11, 8}, 15);
std::cout << "Result: [" << result4[0] << ", " << result4[1] << "]\n"; // Output: [1, 4]
}
Conclusion
In this post, we’ve discussed two approaches to solving the Two Sum problem in C++. The brute-force approach is easy to understand but inefficient for large arrays. The optimized approach, using a hash map, improves the time complexity to O(n) by using constant-time lookups. Understanding these approaches helps build a foundation in both problem-solving and data structures like hash maps.