30

I was given a problem to solve in O(n) time complexity :

"Given a list of numbers and number x. Find if there any 2 numbers in the list that add up to x?"

And this is my solution :

public class SumMatchResult {

  public static void main(String[] args){
    int[] numberList = {6,1,8,7,4,6};
    int requiredSum = 8;
    boolean isSumPresent = checkSumPresentHash(numberList,requiredSum);
    if(isSumPresent) {
      System.out.println("Numbers exist");
    }else {
      System.out.println("Numbers donot exist");
    }
  }

  private static boolean checkSumPresentHash(int[] numberList, int requiredSum) {
    Map<Integer, Integer> m = new HashMap<Integer,Integer>();
    int count = 0;
    for(int i=0;i<numberList.length;i++){
      m.put(i, numberList[i]);
    }
    for(int i=0;i<numberList.length;i++){
      if(m.containsValue(requiredSum - numberList[i])){
        count++;
      }
    }
    if(count>1){
        return true;
    }
    return false;
  }

}

I am using HashMap.containsValue() instead of using a HashSet.contains() which surely has a complexity of O(1) because, I have to account for the scenario where my input might contain identical values. For example, in the above case, I can have a set of input values {3,6,4,4,7} to be matched for the sum 8, which should return true.

My above solution's time complexity depends on the complexity of HashMap.containsValue() method. Please shed some light on the time complexity of containsValue() method and suggest me if there is any better solution for the above problem in terms of time complexity. Thank you.

Simulant
  • 19,190
  • 8
  • 63
  • 98
Vishnu Vedula
  • 303
  • 1
  • 3
  • 5
  • Maybe you should use `Set` instead of `Map`. – johnchen902 May 26 '13 at 08:14
  • I would be curious to see an algorithm that solves this in O(n) time. I'm not sure it's possible. – JB Nizet May 26 '13 at 08:19
  • How about keeping a separate `HashMap` with the values of the first as his keys? This way you'd know right off the bat (`O(1)`) is a value exists. This is `O(2n)` space, but hey, it's worht it. – acdcjunior May 26 '13 at 08:19
  • @JBNizet There's a long time since I last saw it, but this smells like a (very) simplified version of the Knapsack problem. Dynamic programming should help here. – acdcjunior May 26 '13 at 08:22
  • 4
    @Bohemian no it is about `containsValue()` which is a fundamental difference in complexity here. – Thomas Jungblut May 26 '13 at 08:31
  • Here's the solution in O(n): instead of `m.put(i, numberList[i]);` do: `m.put(numberList[i], whatever);` and instead of `if(m.containsValue(requiredSum - numberList[i])){` do `if(m.contains(requiredSum - numberList[i])){`. (And since you are not interested in the `count`, do `return true;` where you now have `count++;` - this will save you some bits). That's it. – acdcjunior May 26 '13 at 08:32
  • @acdcjunior I cannot insert the input values as key because, I might have duplicate values in my input, as I mentioned in my question.And I am using count>1 to account for the case where, in my input, if I have only one value, which is equal to sum/2, code should return false. For example {3,6,4,7} when matched with sum 8 should return false. – Vishnu Vedula May 26 '13 at 08:45
  • Then keep a counter instead of `whatever`. If it is 2 (and the sum matches), then you could use that key alone. This would be constant time, so your overall complexity is not affected. – acdcjunior May 26 '13 at 08:46
  • @acdcjunior that will work I guess, will just take an extra check. Thanks man !!! – Vishnu Vedula May 26 '13 at 08:51
  • Or.. you can use @Oak's suggestion, check while inserting on the map (`if (numberList[i] == requiredSum/2) return true;`) and never worry about duplicate values again. – acdcjunior May 26 '13 at 08:54

4 Answers4

26

To answer the question in the title - as mentioned by others, containsValue is O(n), because without the key it doesn't know where it is and the algorithm has to go over all the values stored in the map.

To answer the question in the body of your question - on how to solve it - just consider whether you really need a general map which can count how many instances have you seen of each number. I mean, the only time you'd care if there's more than one appearance of a number is when it's x/2, right? That smells like a corner case to me. Just add a test that checks for that corner case - something like embedding if (numberList[i] == requiredSum/2) half++ during your set-construction loop, and then if (requiredSum % 2 == 0 && half == 2) return true after it (see other variation below).

Then you can just iterate over the set and for each item check whether requiredSum-item also appears in the set.

To summarize (with early exits when possible):

Set<Integer> seen = new HashSet<Integer>();
boolean halfSeen = false;
for (int num : numberList) {
    if (num == requiredSum/2 && requiredSum % 2 == 0) {
        if (halfSeen) return true;
        halfSeen = true;
    } else {
        seen.add(num);
    }
}
for (int num : seen) {
    if (seen.contains(requiredSum - num)) return true;
}
return false;
Oak
  • 26,231
  • 8
  • 93
  • 152
  • 3
    +1 Very good point about the corner case (`if (numberList[i] == requiredSum/2)`). Great overall solution!! – acdcjunior May 26 '13 at 08:55
  • @Oak : thank you very much for the solution. A small correction though, seen.add(num) should be in the else part, otherwise, even if there is only one num ==requiredSum/2,it is added to the set and the next loop returns true, which is wrong. – Vishnu Vedula May 26 '13 at 09:48
  • @VishnuVedula good point, fixed. This change also required moving the divides-by-two check to the enclosing `if`, because if `requiredSum` is 7 then we do indeed want to keep 3 in `seen`. – Oak May 26 '13 at 10:25
14

The HashMap essentially is a key-value store, which can access it's keys with a complexity of O(1). Checking a value, however, there's nothing the HashMap can do but check all values and see if they're equal to the one you're searching. Therefore, the complexity is O(n) with n being the number of elements in the HashMap.

On a different note: You are looking up primitive values (int) in a collection of its boxed type (Integer). This means each time you are invoking a method on the HashMap, Java needs to box you primitive values: http://docs.oracle.com/javase/1.5.0/docs/guide/language/autoboxing.html

rethab
  • 7,170
  • 29
  • 46
4

HashMap.containsValue complexity is O(n). But n is not exactly the map size but rather hash table size because containsValue goes thru all table elements if even map size = 0. Suppose we created an empty map with initial capacity = 1024. containsValue will have to go thru 1024 elements hash table array:

public boolean containsValue(Object value) {
    if (value == null)
        return containsNullValue();

    Entry[] tab = table;
    for (int i = 0; i < tab.length ; i++)
        for (Entry e = tab[i] ; e != null ; e = e.next)
            if (value.equals(e.value))
                return true;
    return false;
}
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
0

There is no index that is tagged to any value in HashMap, so, to find a value using containsValue(), you have to go through all the values of the HashMap.

Be mindful that the complexity O(n) here is the number of values in the HashMap and not the keys/size with which we usually denote its complexity.

Saran
  • 1,253
  • 1
  • 9
  • 10