3

I have been doing some practice tech interview questions, and this one has been pretty easy, but I am wondering if the more efficient of my two solutions is actually so.

I now see that someone else on here has previously asked this question, but my question has the code snippets and I fell it may provide further insight for anyone else who is searching for a solution.

The question is: given an array of integers and some number k, create a function that returns true if any two numbers in the array add to k.

Here is my "slow" solution:

private static boolean canAddToKSlow(int[] nums, int k) {
    /*
    Uses double for loops to compare each value in the array to
    another value (not including the current one). If they add
    up to k, return true. Otherwise, at the end of iteration,
    return false.
    */

    for(int i = 0; i < nums.length; i++) {
      for(int j = 0; j < nums.length; j++) {
        if (j != i) {
          if (nums[i] + nums[j] == k) {
            return true;
          }
        }
      }
    }

    return false;
  }

and my "fast" solution:

private static boolean canAddToKFast(int[] nums, int k) {
    /*
    Uses a single optimized for loop to iterate through the list,
    and adds the "complement" of the current int to the arraylist.
    By complement, I mean whatever number you would have to add to
    n to get k. Then, it checks to see if that n is in the arraylist, and if so, then there must be two numbers that 
    add to k. More efficient.
    */
    ArrayList<Integer> comps = new ArrayList<>();

    for(int n: nums) {
      comps.add(k - n);
      if(comps.contains(n))
        return true;

    }

    return false;
 }

The problem that I am running into here is that ArrayList.contains() calls indexOf(), which uses for loops anyway, so it may be just as inefficient and just obfuscated. Is there any voodoo magic that makes the second solution any more efficient? If not, is there actually a way to make this more efficient or is this the best you can do? I wonder if a hash table would make any difference.

Chandler Davis
  • 362
  • 1
  • 4
  • 12
  • Possible duplicate of [Given a list of numbers and a number k, return whether any two numbers from the list add up to k](https://stackoverflow.com/questions/51300360/given-a-list-of-numbers-and-a-number-k-return-whether-any-two-numbers-from-the) – Jonny Henly Apr 02 '19 at 20:26

2 Answers2

4

The efficiency is not different in both cases, because list.contains() has a time complexity of O(n), so both have a time complexity of O(n²).

A better solution would be to use HashSet.contains() which has a time complexity of O(1).

This class offers constant time performance for the basic operations (add, remove, contains and size), assuming the hash function disperses the elements properly among the buckets. (docs)

So the time complexity of this is O(n):

private static boolean canAddToKFast2(int[] nums, int k) {
    Set<Integer> comps = new HashSet<>();
    for (int n : nums) {
        if (comps.contains(n))
            return true;
        comps.add(k - n);
    }
    return false;
}
Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56
  • Okay, so just to check my understanding, the HashSet is faster because I am not *searching* for the value, but rather the hash set creates the index based off of the value so it is found in constant time, correct? – Chandler Davis Apr 02 '19 at 20:28
  • Your code is broken - you can have an array from 1 to 10 and then set `k = 10000`, and it will still return `true`. The spirit of this *seems* right but the implementation leaves much to be desired. – Makoto Apr 02 '19 at 20:36
  • @Makoto You are right, i had an error in reasoning. I fixed it. Thanks for noticing. – Samuel Philipp Apr 02 '19 at 20:41
  • 1
    @ChandlerDavis Yes, that's correct. I added some more information to my answer. – Samuel Philipp Apr 02 '19 at 20:49
3

Here is an O(n) solution using a hash table, but it does use extra space:

    private static boolean canAddToKFast2(int[] nums, int k) {

        Map<Integer,Integer> table = new HashMap<Integer, Integer>();

        for(int val: nums) {
            if(table.containsKey(k - val)) { // O(1) lookup to see if the difference exists
                return true;
            }
            table.put(val, val); //If it doesn't exist, add the value to the table
        }
        return false; // We searched all values and didn't find a pair
    }

This is O(n) time and we can see that because we only touch each element once. If the target minus element doesn't exist in the table, then we insert it into the table. If we loop through all the items and don't find a match, return false.

What about space efficiency?

With regards to your interview, I would ask if extra space is allowed (the hash table is going to use O(n) space). If extra space is not allowed, then the most efficient answer is the code in your question (which runs in O(n²) time and does not use any additional space.

bsheps
  • 1,438
  • 1
  • 15
  • 26
  • This solution suffers from the same weakness as the previous one. You should be able to get all numbers between 2 and 19 to sum up but your answer doesn't say that *any* of them are permissible. Also, you can realistically replace `Hashtable` with `Map`. – Makoto Apr 02 '19 at 20:55
  • @Makoto with regards to the hashtable: the asker specifically asks about a hashtable so that's why I used it. What do you mean by the answers are permissible? – bsheps Apr 02 '19 at 20:59
  • My test returns "false" for those values. – Makoto Apr 02 '19 at 21:00
  • What is your array and what is your target? – bsheps Apr 02 '19 at 21:01
  • 1
    Oops - I had thought I set up a range for those values but was apparently short on it. My mistake; this answer seems appropriate. – Makoto Apr 02 '19 at 21:02
  • 3
    The term "hash table" is a generic term for a data structure (see https://en.wikipedia.org/wiki/Hash_table ). This does not mean that one should use the specific Java class called `Hashtable`. In fact, the relevant interface here is `Map`, and there basically is *never* a reason to use `Hashtable`, because usually, `HashMap` is sufficient. – Marco13 Apr 02 '19 at 21:10
  • @Marco13 Good feedback! I edited my answer to use HashMap. Seems my 4 year degree is about as relevant as Hashtable. Joking aside, thanks for making me a better dev. – bsheps Apr 02 '19 at 22:03
  • 1
    Now the extra step: The declaration should be `Map ...`, see https://stackoverflow.com/questions/383947/what-does-it-mean-to-program-to-an-interface - but for a local variable, this is *far* less critical than for e.g. the argument- or return types of (public) methods. – Marco13 Apr 02 '19 at 22:25
  • @Marco13 That's a great Q/A thread. The author of the pest example found the perfect blend of ELI5 and humor. Anyways, I edited my answer again, equipped with this new outlook on interfaces. Thanks again for the feedback and sharing! – bsheps Apr 03 '19 at 01:41