For quite some time I found my time procastinating after getting home from work. Untill a few days ago I stumbled upon the Daily Coding Problem (DCP) and decided to give it a shot.
The code is in C#.
How it works?
The folks at DCP send you a problem that was asked at a top company everyday for you to solve. The premium membership also offers the opportunity to verify your solution.
Problem #1
This problem was recently asked by Google.
Given a list of numbers and a number k, return whether any two numbers from the list add up to k.
For example, given [10, 15, 3, 7] and k of 17, return true since 10 + 7 is 17.
Bonus: Can you do this in one pass?
My solution
using System;
using System.Collections.Generic;
using System.Linq;
namespace Problem01
{
public class Program
{
public static void Main(string[] args)
{
var numbers = Console.ReadLine()
.Split(' ')
.Select(int.Parse)
.ToList();
var k = int.Parse(Console.ReadLine());
var result = TwoNumbersEqual(numbers, k);
Console.WriteLine(result);
}
public static bool TwoNumbersEqual(List<int> numbers, int k)
{
var set = new HashSet<int>();
foreach (var number in numbers)
{
var remaining = k - number;
if (set.Contains(remaining))
{
return true;
}
set.Add(number);
}
return false;
}
}
}
Explanation
The naive solution would be to do two nested for loops and check if any of the combinations gives the k
we desire.
But because I wanted the bonus tag and after some tought, I realized that you could foreach the input array and for each item check find the difference between it and k
. After you find the difference, you can check if you have already seen this number before and the HashSet
data structure allows for constant lookups (because it is a hash table).
I will try to do the daily problem everyday, but sometimes life gets in the way :)
Solutions are posted in my github account
.
Feel free to leave a like and let me know in the comments if I should continue to post my solutions.
If you also have any better solution in mind, by all means share it, so we can learn from each other.
Top comments (58)
One pass in Python using built-in list and set types
Can I suggest you use more explicit variable names?
Although I prefer a more concise implementation, although probably not O(N)
This is should be the shortest version of your code
I've just realised that my algorithm is wrong, it would fail for something like 10 and [5]. Guess I need to put a bit more thought in my comments.
ATM I see 2 proposals for JS, here's another one taking advantage of the fact that JS arrays are sparse:
No need for fancy hash structures (because arrays in javascript are already hashes).
Yes, you could,
but if you look into lookup array, and imagen
k = -1
,you will create an array with a lot of empty
lookup.length === 16
now, for example, we have input some array with big numbers
lookup.length > max.number in inList
So… you are saying that having very big sequence of empty space is a problem.
Can you explain why is that a problem?
There no such a big problem now,
it just my opinion,
but as suggested my friend
anyway, it's better using
new Map || new Set
for better performance.jsperf.com/hashtable-vs-array
Here is my O(N) solution using Set:
You should move line:
visitedNumbers.add(number);
after the if clause (otherwise the sums like 4+4=8 will be counted)
A compare i made for the Set vs HashTable
jsperf.com/add-to-k
there was actually an article recently that touched on the subject mentioned by Serhii Sakal dev.to/_codingblocks/when-is-an-ar...
I was wondering, why not just use an object instead of an array?
let's take a simple example, where you have something like this
and look at the size of a.
even with your example, the created lookup array is bigger than the given array, so you're using more memory than needed.
Hash lookup is O(n) due to collisions. You cannot assume no collisions, so it's O(n2)
That's only partially true. If we asume:
a proper hash function (for integers their own value is a very good hash code) and
a hash set implementation that resizes once a certain percentage of buckets have been filled (which all standard collection libraries do)
...then not only is the cost of a hash collision amortized, but also the cost of re-hashing in case that the hash set runs out of space. There's a whole lot of theory behind this (look up "amortized complexity analysis"), but generally speaking, provided a proper implementation of both the set collection itself and the hash function of the objects to be stored, hash set lookups are always amortized O(1).
I think yiu might be misunderstanding how hash tables work.
The idea is that there exists a "bounded" table for hashes. Hence, hases of ints are not themselves, but themselves modulo some number. Due to that, it might be the case that the numbers in the given list are all divisible into that number, hence they will all fall into the same hash bucket, hence the hash becomes a simple linked list, hence it's O(n). Moreover, there is no hash function dealing with thia problem, unless you assume an infinite storage space LOL
No, I understand hash tables perfectly fine. Every bucket in ha hash table can be implemented as a list, that's true (however, alternatives do exist as well). That list has O(n) complexity for "contains", that's true as well. However, each hash table implementation worth its salt will perform a procedure called "rehashing". For example, lets say you have a hash table with 10 buckets. Now entries 1 through 7 are being inserted, no problem. If you have a decent hash function, you will end up with 7 buckets of size 1, and 3 empty buckets. Collissions MAY occur, but if your hash function is any good they will be extremely rare. Now, we have 70% fill ratio in our table. Entry #8 wants to be inserted. Instead of just putting it in (risking more collisions), we create a NEW list of buckets which is larger than the old one (typically twice as large), so now we have 20 buckets. We reinsert (rehash) our first 7 entries into the new 20-bucket-table, then insert entry #8. We continue inserting until we are again 70% full, then we resize to 40 buckets, and so on. No infinite storage needed. Amortized cost of contains is O(1), also for insertion O(1).
I studied all of that long enough, but you don't have to take my word for it - just check it on wikipedia or Stackoverflow.
Question: What happens if the hash of the values being inserted happens to be the same for all values? (Hint: Remember that you have to first check if the value already exists in the set before you can add it).
Is amortized cost for n inserts still O(n) in that scenario?
Or an even easier "proof": If you're doing open hashing as most implementations do, a hashset that contains values with the same hash degrades to a simple linked list. What is the amount of time to insert n values into a linked list of you have to check for duplicates first?
The amortized performance you learned for hashsets in school only works under the assumption of a reasonably well distributed hash function - in the worst case it degrades.
If all values deliver the same hash code then I'd argue that you have a pretty useless hash function... as I said, all of the argumentation I provided is based on the assumption of a proper hash function.
No, you don't have to check first if something is in a hash set before you insert it. Simply look up the bucket and the position where the element would be, then put it there if absent, or don't if it's not there. If your buckets are of size 0, 1 or 2 (as they should be) then the algorithm is O(1).
Bad hash codes will result in bad hash performance. No rocket science. If you take an object from a standard library, be it a number, a string, you name it - they are bound to have proper hash functions with good distributions. If a real-world hash set degenerates into a list (which is possible), I would question the programmer - not the theory.
The problem is that even the best hash function will always have pathological cases. In this case with integers the problem isn't the hash function of the integer, but the necessary transformation to fit into the set, which always requires some kind of modulo computation.
If hash mod bucket_size is always the same number you have the problem. If you know the used algorithm to convert the hash to the bucket number, the starting size and the growth algorithm of the set you can always pick your numbers in such a way that you'll get collisions. This has nothing to do with whether the hash algorithm of the data type is bad or good (integers have a pretty perfect naive hash algorithm after all).
"No, you don't have to check first if something is in a hash set before you insert it. Simply look up the bucket and the position where the element would be, then put it there if absent, or don't if it's not there."
Good explanation of how you check to see if the element exists - that's exactly how you'd do it. And since we've already established that for certain inputs all values will be in the same bucket, this degenerates to O(N) in the worst case.
Consequently the worst case performance here is O(n2) and not O(n). That's simply how hashsets work. Is that important? Depends on your use case. If you aren't worrying about an attacker, or the input is outside the control of any attacker you're good. If not, you've just opened yourself up to a classical DDOS attack by not paying attention to those * next to the complexity numbers.
And before you disregard this as purely theory: This issue has plagued web frameworks and their handling of POST form data amongst many other examples I can think of. You can read bugs.python.org/issue13703 for one classic example.
Hold your horses. We went from a discussion with somebody who clearly was unfamiliar with hashing techniques to malicious attack vectors as an argument to prove a generally accepted theory wrong? Good grief, that's a stretch. Sorry, this is getting ridiculous - I just wanted to clarify a misunderstanding, that's all. If we all explained things this way, we would have an army of students running about, firmly believing that the common access complexity for a hash set is O(n). I highly doubt that this is useful to anybody.
To each their own. I find "Hashsets have O(1) amortized lookup and insert performance assuming a relatively uniform hash distribution, otherwise O(n)" a perfectly reasonable explanation even if it is marginally more complex than "Hashsets always have O(1) amortized lookup and insert performance". I doubt that most students would fail to understand the first explanation.
It's also a valuable lesson to students to teach them that you have to specify whether you mean best case, average or worst case performance when analysing algorithms.
I have come up with this
Here's another solution in Haskell
In JavaScript you could do
looks good, but when you use
includes
is it O(log n) ?jsperf.com/hashtable-vs-includes
Two problems I can find.
The first is that I don't think you want to do
k-=n
, rather justk-n
. As written, this will only work with pairs with the first number in the array.Second, you need to limit the
includes
check to the remainder of the array, or you will fail in cases such asdoTheyAddTo([5], 10)
, because 5 will be considered as a valid addend of itself.To fix the latter, refactor into a
for (let i = 0, n; i < a.length; n = a[i++])
loop to get access to the index, or the much much more readablefind
, e.g...Note, using
find
instead ofreduce
orfilter
will maintain the short circuit you had.Your function does not return anything though. Even if you do return the value of the initial find it won't be a Boolean.
whoops. too much REPL, not enough real code. Should be
return typeof a.find(...) !== 'undefined'
.If you enjoy this, you may also like Project Euler
Rust solution:
Made a sized Set to overcome the reallocation and copying of data. This increases the Size requirement to O(N).
Probably the shortest
slight improvement with a short circuit (
find
vsfilter
):I just realized both of these solutions fail in the case of
twoNumbersEqual([5], 10)
. This can be fixed by passing the index toincludes
, as such...This should also theoretically be more performant.
Best way for 1 pass would be to as you iterate through the array, store the number pair that would make that index total k into an array. And then on each subsequent number check it it exists in the check array before storing its pair
This page lists the complexities of the major data structures in Python. Sets and dicts have a similar implementation. The lookup is O(1) in average case, the hash function can lead to O(n) lookups in pathological cases.
Also, amortized O(n) is possible when dicts/sets are resized when adding new elements
PS: For this specific problem, we do not need to use a set as an array will do i.e keys are integers. So if the range of numbers is limited, array index provides a good hash function.
I do not think there is an upper range of numbers and they are also not subsequent, so using an array like a hashset is not usefull in this particular situation
Yes. I was not sure of that constraint. In that case, set is better.