0

See this code I have written to solve this hackerrank challenge.

https://www.hackerrank.com/challenges/frequency-queries/problem?h_l=interview&playlist_slugs%5B%5D=interview-preparation-kit&playlist_slugs%5B%5D=dictionaries-hashmaps

Essentially, at this point, I KNOW that my code is algorithmically fast enough to be acceptable for this challenge, because what I have done is EXACTLY what is discussed in the editorial section. However, the code terminates due to timeout. Therefore, I'm wondering whether theres some glaring inefficiencies in the data structures I used or if it could be a memory problem or something. Also, it is worth noting that the editorial is written in c++ and I have written my code in java

I'll describe what's going on. Queries is the list of queries to perform. countMap is a HashMap that will store how many times a given number appears in the array. freqMap is a HashMap that stores how many instances there are of a number appearing x (key) times in the array. So for example, if I add the number 5 to the array, I will add 1 to the value at key 5 in countMap. Then, if the value at key 5 in countMap is now 6, meaning that 5 occurs 6 times in the array, I will add 1 to the value at key 6 in the freqMap, since a number occurs 6 times in the array. Then, if a query happens to check if there are any numbers that appear exactly 6 times in the array, I can check directly in freqMap, and if the value at key 6 is >0, I add the number 1 to the result ArrayList. Else, I add a 0 to the result ArrayList.

static List<Integer> freqQuery(List<List<Integer>> queries) {

    HashMap<Integer,Integer> countMap = new HashMap();
    HashMap<Integer,Integer> freqMap = new HashMap();
    List<Integer> res = new ArrayList();
    for(int i = 0; i < queries.size(); i++){
        List<Integer> query = queries.get(i);
        Integer qType = query.get(0);
        Integer qVal = query.get(1);
        if(qType == 1){
            Integer countVal = countMap.get(qVal);
            if(countVal == null){
                countVal = new Integer(0);
            }
            countVal += 1;
            countMap.put(qVal,countVal);
            Integer freqVal = freqMap.get(countVal);
            if(freqVal == null){
                freqVal = new Integer(0);
            }
            freqVal += 1;
            freqMap.put(countVal,freqVal);
            if ((countVal - 1) > 0) {
                Integer prevFreqVal = freqMap.get(countVal - 1);
                prevFreqVal -= 1;
                freqMap.put((countVal -1),prevFreqVal);
            }
        }else if(qType == 2){
            Integer countVal = countMap.get(qVal);
            if(countVal != null && countVal != 0){
                countVal -= 1;
                countMap.put(qVal,countVal);
                Integer freqVal = freqMap.get(countVal);
                if(freqVal == null){
                    freqVal = new Integer(0);
                }
                freqVal += 1;
                freqMap.put(countVal,freqVal);
                Integer prevFreqVal = freqMap.get(countVal + 1);
                prevFreqVal -= 1;
                freqMap.put((countVal + 1),prevFreqVal);
            }
        }else if(qType == 3){
            Integer freqVal = freqMap.get(qVal);
            if(freqVal == null || freqVal < 1){
                res.add(0);
            }else{
                res.add(1);
            }
        }
    }
    return res;

}

All the results are correct, but I'm getting a timeout error, indicating that my code is not fast enough. I'm lost as to why this is.

HashMap get and put operations are O(1), iterating through the queries once is obviously O(q). Adding to the end of arraylist seems to be O(n), where n is length of arraylist.

Davis Owen
  • 47
  • 7
  • 1
    Linked list inserts are O(1) – robinsax Dec 21 '18 at 23:22
  • 1
    actually you're using get on a list which, if the list is a linked list, could have bad performance, switch to iterator (or use a for (List list : queries) ```List query = queries.get(i);``` (as discussed in https://stackoverflow.com/questions/15192581/complexity-of-calling-get-on-a-linkedlist-in-a-for-loop-using-o-notation) – Roy Shahaf Dec 21 '18 at 23:30

2 Answers2

0

Well stupid me didn't check the discussion section and apparently the boilerplate code for reading in the queries was too slow, mostly due to what's already been discussed to be using get() from a List is apparently O(n) complexity. On top of this, for add() for the result, one should use a LinkedList as opposed to an ArrayList for O(1) complexity.

The changes I made were to actually change from a List<List<Integer>> to a List<int[]>, thereby accessing the query and value in constant time. I could also get even faster by simply using a 2-d array if the input lengths are already known.

Also, while reading in the input, I kept count of how many queries there were that required me to actually add a value to my result List, say n, and passed that to my method. With that knowledge, I could instantiate an array int[n] and then add to the result array in constant time as well. Much faster

Davis Owen
  • 47
  • 7
  • ArrayList.add performs in amortized constant time, not O(N), linked list is almost always the wrong tool to use. To learn more about amortized constant time see for example https://stackoverflow.com/q/200384/318758 – Joni Dec 22 '18 at 00:23
  • @Joni Ok, good to know that ArrayList is probably fine, but why wouldn't linkedlist also work here? I really just need to continually append to the list and then print all the elements at the end. Isn't that perfect with a linkedlist, as long as the pointer is maintained at the tail of the list? – Davis Owen Dec 22 '18 at 00:44
  • Linked list also works here, but its memory layout makes iteration slow because every time you follow a link you risk an expensive cache miss. This makes them "bad" including for the tasks for which they are supposed to be"good". I couldn't find a Java benchmark but here's Stroustrup the creator of c++ comparing vector and list: https://youtu.be/YQs6IC-vgmo – Joni Dec 22 '18 at 08:59
0

A bit cleaner and closer to what you were doing:

static List<Integer> freqQuery(List<List<Integer>> queries) {
    Map<Integer, Integer> countMap = new HashMap<>();
    Map<Integer, Integer> freqMap = new HashMap<>();
    List<Integer> res = new LinkedList<>();
    for (List<Integer> query : queries) {
        Integer qType = query.get(0);
        Integer qVal = query.get(1);
        switch (qType) {
        case 1:
            Integer countVal = countMap.merge(qVal, 1, Integer::sum);
            freqMap.merge(countVal, 1, Integer::sum);
            freqMap.computeIfPresent(countVal - 1, (key, value) -> value - 1);
        case 2:
            countMap.computeIfPresent(qVal, (key, value) -> {
                freqMap.merge(value, -1, Integer::sum);
                freqMap.merge(value - 1, 1, Integer::sum);
                return value - 1;
            });
        case 3:
            Integer freqVal = freqMap.get(qVal);
            res.add(freqVal == null || freqVal < 1 ? 0 : 1);
        }
    }
    return res;
}
Roy Shahaf
  • 484
  • 5
  • 10