简体   繁体   中英

How can I make this code to find a pair with a sum more efficient?

I'm doing this test on testdome.com for fun, and it's failing the efficiency test. What better way is there? I'm not counting any values twice. It seems the only way to do this is by brute force, which is an n^2 algorithm.

Here are the directions for the problem:

Write a function that, given a list and a target sum, returns zero-based indices of any two distinct elements whose sum is equal to the target sum. If there are no such elements, the function should return null.

For example, findTwoSum(new int[] { 1, 3, 5, 7, 9 }, 12) should return any of the following tuples of indices: 1, 4 (3 + 9 = 12), 2, 3 (5 + 7 = 12), 3, 2 (7 + 5 = 12) or 4, 1 (9 + 3 = 12).

And here's my code:

public class TwoSum {
public static int[] findTwoSum(int[] list, int sum) {
    if (list == null || list.length < 2) return null;
    for (int i = 0; i < list.length - 1; i++) { //lower indexed element
        for (int j = i + 1; j < list.length; j++) { //higher indexed element
            if (list[i] + list[j] == sum) {
                return new int[]{i, j};
            }
        }
    }

    //none found  
    return null;
}


public static void main(String[] args) {
    int[] indices = findTwoSum(new int[] { 1, 3, 5, 7, 9 }, 12);
    System.out.println(indices[0] + " " + indices[1]);
}

}

EDIT: So here's my final working code. Thanks everyone!

import java.util.HashMap;
import java.util.Map;

public class TwoSum {
    public static int[] findTwoSum(int[] list, int sum) {
        if (list == null || list.length < 2) return null;
        //map values to indexes
        Map<Integer, Integer> indexMap = new HashMap<>();
        for (int i = 0; i < list.length; i++) {
            indexMap.put(list[i], i);
        }

        for (int i = 0; i < list.length; i++) {
            int needed = sum - list[i];
            if (indexMap.get(needed) != null) {
                return new int[]{i, indexMap.get(needed)};
            }
        }

        //none found
        return null;
    }

    public static void main(String[] args) {
        int[] indices = findTwoSum(new int[] { 1, 3, 5, 7, 9 }, 12);
        System.out.println(indices[0] + " " + indices[1]);
    }
}

As per Kon's suggestion, in one pass:

public static int[] findTwoSum(int[] list, int sum) {
    if (list == null || list.length < 2) return null;
    //map values to indexes
    Map<Integer, Integer> indexMap = new HashMap<>();
    for (int i = 0; i < list.length; i++) {
        int needed = sum - list[i];
        if (indexMap.get(needed) != null) {
            return new int[]{i, indexMap.get(needed)};
        }

        indexMap.put(list[i], i);
    }

    //none found
    return null;
}

Look at what you do in the inner loop, your checking if list[i] + list[j] == sum.

If you transform the equation slightly, it means given list[i] and sum (which are both constants within the inner loop), you are really asking "is there an index where the value (sum - list[i]) is stored", and thats what your inner loop solves.

Now applying the knowledge that you could solve the problem using essentially a indexOf(sum - list[i])-style method in linear time, there are data structures that can answer this kind of question in better time than O(N).

Here is linear solution (save sorting which is O(n*log(n)) ):
1) Sort your initial array a[]
2) let i be the first index of a[], and j - the last

i = 0;
j = a[].length - 1;

3) lets move from two ends:

do{
  if(a[i]+a[j] < sum)
    i++;
  else if(a[i]+a[j] > sum)
    j--;
  else { // we have found required indexes!
     put (i, j) to result set;
     i++;
  }
} while(i < j);

The final result - set of pairs (i,j) that give required sum.
You can stop after first pair and return it.

PS if you have array like {3, 3, 3, 3, 9, 9, 9, 9} this solution will not give all the combinations:)

public static Map<Integer, Integer> findTwoSum(int[] list, int sum) {
    if (list == null || list.length < 2) return null;
    Map<Integer, Integer> indexMap = new HashMap<Integer, Integer>();
    Map<Integer, Integer> arrayResult = new HashMap<Integer, Integer>();
    for (int i = 0; i < list.length; i++) {
        indexMap.put(list[i], i);
    }

    for (int i = 0; i < list.length; i++) {
        int needed = sum - list[i];
        if (indexMap.get(needed) != null) {
            arrayResult.put(i, indexMap.get(needed));
        }
    }
    return arrayResult.isEmpty()?null:arrayResult;
}

public static void main(String[] args) {
    Map<Integer, Integer> indices = findTwoSum(new int[] { 1, 3, 5, 7, 9 }, 12);
    System.out.println(indices);
}

Here's a solution written in C#. It should be easy enough to convert it over to Java lingo:

static public IEnumerable<Tuple<int, int>> FindAllTwoSumIndexes(IList<int> list, long desiredSum)
{
    var count = list?.Count;
    if (list == null || count <= 1)
        return null;

    var results = new List<Tuple<int, int>>(32);
    var indexesMap = new ConcurrentDictionary<long, List<int>>(); //0 value-to-indexes
    for (var i = 0; i < count; i++)
    {
        var thisValue = list[i];
        var needed = desiredSum - thisValue;
        if (indexesMap.TryGetValue(needed, out var indexes))
        {
            results.AddRange(indexes.Select(x => Tuple.Create(x, i)));
        }

        indexesMap.AddOrUpdate(
            key: thisValue,
            addValueFactory: x => new List<int> { i },
            updateValueFactory: (x, y) =>
            {
                y.Add(i);
                return y;
            }
        );
    }

    return results.Any() ? results.OrderBy(x => x.Item1).ThenBy(x => x.Item2).ToList() : null;

    //0 bare in mind that the same value might be found over multiple indexes   we need to take this into account
    //  also note that we use concurrentdictionary not for the sake of concurrency or anything but because we like
    //  the syntax of the addorupdate method which doesnt exist in the simple dictionary
}

Here is a Java program which find the pair of values in the array whose sum is equal to k using Hashtable or Set in the most efficient way.

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class ArraySumUsingSet {

public static void main(String args[]) {
   prettyPrint(getRandomArray(9), 11);
   prettyPrint(getRandomArray(10), 12);
}

/**
 * Given an array of integers finds two elements in the array whose sum is equal to n.
 * @param numbers
 * @param n
 */
public static void printPairsUsingSet(int[] numbers, int n){
    if(numbers.length < 2){
        return;
    }        
    Set set = new HashSet(numbers.length);

    for(int value : numbers){
        int target = n - value;

        // if target number is not in set then add
        if(!set.contains(target)){
            set.add(value);
        }else {
            System.out.printf("(%d, %d) %n", value, target);
        }
    }
}

/*
 * Utility method to find two elements in an array that sum to k.
 */
public static void prettyPrint(int[] random, int k){
    System.out.println("Random Integer array : " + Arrays.toString(random));
    System.out.println("Sum : " + k);
    System.out.println("pair of numbers from an array whose sum equals " + k);
    printPairsUsingSet(random, k);
}

/**
 * Utility method to return random array of Integers in a range of 0 to 15
 */
public static int[] getRandomArray(int length){
    int[] randoms = new int[length];
    for(int i=0; i<length; i++){
        randoms[i] = (int) (Math.random()*15);
    }
    return randoms;
}

}

Output

Random Integer array : [0, 14, 0, 4, 7, 8, 3, 5, 7]
Sum : 11
pair of numbers from an array whose sum equals 11
(7, 4)
(3, 8)
(7, 4)
Random Integer array : [10, 9, 5, 9, 0, 10, 2, 10, 1, 9]
Sum : 12
pair of numbers from an array whose sum equals 12
(2, 10)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM