Solving the Two Integer Sum Problem

In this post, we’re going to tackle the Two Integer Sum problem. Given an array of integers and a target sum, the task is to return the indices of two numbers whose sum equals the target. The solution needs to return the indices in increasing order. This is a common algorithm problem that can be approached in different ways, from brute force to more optimized solutions.

Problem Breakdown

Imagine you’re given a list of items, each with a different price. Your goal is to find two items whose total price matches a specific budget. This problem is essentially the same but applied to numbers instead of prices.

Approach 1: Brute Force

Our first approach will be a brute force solution, where we check every possible pair of numbers to see if they sum up to the target. While this is a simple method, it’s inefficient for large arrays.

public int[] TwoSumBruteForce(int[] aiNums, int iTarget)
{
    // Loop through each element in the array
    for (int i = 0; i < aiNums.Length; i++)
    {
        for (int j = i + 1; j < aiNums.Length; j++)
        {
            // Check if the sum of aiNums[i] and aiNums[j] equals iTarget
            if (aiNums[i] + aiNums[j] == iTarget)
            {
              // Return the indices if the condition is met
              return new int[] { i, j };
            }
        }
    }

    // Return this if no valid pair is found
    return new int[] { -1, -1 };
}

While the brute force approach is easy to implement, its time complexity is O(n²), which means it doesn’t scale well as the size of the array increases.

Approach 2: Dictionary-Based Solution

The second approach uses a dictionary to store each number and its index as we iterate through the array. For each number, we calculate its complement (i.e., the value needed to reach the target) and check if that complement exists in the dictionary. This significantly reduces the time complexity by avoiding the need to check every pair manually.

public int[] TwoSumDictionary(int[] aiNums, int iTarget)
{
    // Dictionary to store numbers and their indices
    Dictionary<int, int> indicesDictionary = new Dictionary<int, int>();

    for (int i = 0; i < aiNums.Length; i++)
    {
        // Calculate the complement of the current number
        int iComplement = iTarget - aiNums[i];

        // Check if the complement already exists in the dictionary
        if (indicesDictionary.ContainsKey(iComplement))
        {
          // Return the indices
          return new int[] { indicesDictionary[iComplement], i };
        }

        // Store the current number and its index in the dictionary
        indicesDictionary[aiNums[i]] = i;
    }

    // If no valid pair is found
    return new int[] { -1, -1 };
}

This dictionary-based approach has a time complexity of O(n), making it much more efficient than the brute force method. The space complexity is O(n), as we need to store the dictionary.

Edge Cases

  1. No valid pair exists: If there’s no pair that sums to the target, the function should return a fallback value (e.g., [-1, -1]).
  2. Duplicate elements: Consider cases where the same number appears more than once, such as [5, 5] for a target of 10.
  3. Negative numbers: Ensure that the solution works with both positive and negative values.

Comparison Table

ApproachTime ComplexitySpace ComplexityAdvantagesDisadvantages
Brute ForceO(n2)O(1)Simple to implementSlow for large arrays
DictionaryO(n)O(n)Fast, efficientRequires extra space

Testing in Visual Studio

  1. Create a new Console Application in Visual Studio.
  2. Implement one of the methods (e.g., the dictionary-based approach).
  3. Add test cases to check if the solution works as expected:
public static void Main(string[] args)
{
    int[] aiNums = new int[] { 3, 4, 5, 6 };
    int[] aiResult = TwoSumDictionary(aiNums, 7);
    
    // Expected output: [0,1]
    Console.WriteLine($"[{aiResult[0]}, {aiResult[1]}]"); 
}

4. Run the project and verify the results with different inputs.

Benchmarking the Approaches

You can benchmark both approaches to measure how they perform with larger datasets. Here’s how to use the System.Diagnostics.Stopwatch class to do that:

using System;
using System.Collections.Generic;
using System.Diagnostics;

public class Program
{
    public static void Main(string[] args)
    {
        int[] aiNums = GenerateLargeArray(1000000);
        // Arbitrary target to ensure a solution exists
        int iTarget = aiNums[999999] + aiNums[500000];
        
        // Benchmark Brute Force
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        int[] aiBruteForceResult = TwoSumBruteForce(aiNums, iTarget);
        stopwatch.Stop();
        Console.WriteLine($"Brute Force Time: {stopwatch.ElapsedTicks} ticks");

        // Benchmark Dictionary Method
        stopwatch.Reset();
        stopwatch.Start();
        int[] aiDictionaryResult = TwoSumDictionary(aiNums, iTarget);
        stopwatch.Stop();
        Console.WriteLine($"Dictionary Method Time: {stopwatch.ElapsedTicks} ticks");
    }

    public static int[] TwoSumBruteForce(int[] aiNums, int iTarget)
    {
        for (int i = 0; i < aiNums.Length; i++)
        {
            for (int j = i + 1; j < aiNums.Length; j++)
            {
                if (aiNums[i] + aiNums[j] == iTarget)
                {
                    return new int[] { i, j };
                }
            }
        }
        return new int[] { -1, -1 };
    }

    public static int[] TwoSumDictionary(int[] aiNums, int iTarget)
    {
        Dictionary<int, int> indicesDictionary = new Dictionary<int, int>();

        for (int i = 0; i < aiNums.Length; i++)
        {
            int iComplement = iTarget - aiNums[i];

            if (indicesDictionary.ContainsKey(iComplement))
            {
                return new int[] { indicesDictionary[iComplement], i };
            }

            indicesDictionary[aiNums[i]] = i;
        }
        return new int[] { -1, -1 };
    }

    // Helper method to generate a large array
    public static int[] GenerateLargeArray(int size)
    {
        int[] aiNums = new int[size];
        Random random = new Random();
        for (int i = 0; i < size; i++)
        {
            aiNums[i] = random.Next(1, 1000);
        }
        return aiNums;
    }
}

After you run the program, you should get a result like following:

Console-App-Results-Anagram

Explanation of the Benchmarking Code

  • The Stopwatch class is used to measure how long each method takes to run. You can read more about Stopwatch class here.
  • We use the GenerateLargeArray function to create an array of 1,000,000 random integers.
  • The target is set using two known indices from the array to ensure the solution exists.
  • Both the brute force and dictionary-based methods are benchmarked, and the results are displayed in ticks, which provide higher resolution than milliseconds.

Final Thoughts

When solving problems like the Two Integer Sum, it’s important to consider both simplicity and efficiency. The brute force method may be easy to implement, but the dictionary-based solution is far more efficient for larger datasets. Understanding both approaches and when to apply them is key to improving your problem-solving skills.

Challenge for You

As a final challenge, consider extending this problem. What if you had to find three numbers that sum to a target? How would you approach that? Try solving it using some of the concepts discussed here.

Similar Posts