My solution for a certain problem is apparently slower than 95% of solutions and I wanted to make sure that I was correct on the time complexity.
It looks to me like this code is O(n). I use a couple of loops that are at most O(n) and they aren't nested so I don't believe the solution is n^2.
I use a HashMap as storage and use HashMap methods which are O(1) inside my while and for-loop for insertion and look ups respectively.
Am I correct in that this solution is O(n) or am I missing something?
public int pairSum(ListNode head) {
HashMap<Integer, Integer> nodeVals = new HashMap<Integer, Integer>();
int count = 0;
ListNode current = head;
nodeVals.put(count, current.val);
count++;
while (current.next != null) {
current = current.next;
nodeVals.put(count, current.val);
count++;
}
int maxTwinSum = 0;
for (int i = 0; i < nodeVals.size() / 2; i++) {
int currTwinSum;
currTwinSum = nodeVals.get(i) + nodeVals.get(nodeVals.size() - 1 - i);
if (currTwinSum > maxTwinSum) maxTwinSum = currTwinSum;
}
return maxTwinSum;
}
Is my Java solution O(N) or am I missing something?
Yes to both!
Your solution is O(N), AND you are missing something.
The something that you are missing is that complexity and performance are NOT the same thing. Complexity is about how some measure (e.g. time taken, space used, etc) changes depending on certain problem size variables; e.g. the size of the list N.
Put it another way ... not all O(N) solutions to a problem will have the same performance. Some are faster, some are slower.
In your case, HashMap is a relatively expensive data structure. While it it (amortized) O(1) for operations like get and put, the constants of proportionality are large compared with (say) using an ArrayList or an array to hold the same information.
So ... I expect that the solutions that are faster than yours won't be using HashMap.
The flipside is that an O(N^2) solution can be faster than an O(N) solution if you only consider values of N less than some threshold. This follows from the mathematical definition of Big O.
For instance, if you are sorting arrays of integers and the array size is small enough, a naive bubblesort will be faster than quicksort.
In short: complexity is not performance.
First off: HashMap methods are amortized O(1), which basically means that you can treat them as if they were O(1) if you use them often enough because that's what they'll be on average. But building a hashmap is still a "relatively expensive" operation (a notion that can't be expressed in bit-O notation, because that one only cares about the asymptotic worst case).
Second: you construct a complete, inefficient copy of the list in a HashMap which is probably slower than most other approaches.
The first optimization is to replace your HashMap with a simple ArrayList: you only use numeric and strictly monotonically increasing keys anyway, so a list is a perfect match.
Related
Let's say i have a nested List which contains n^2 element in it and also i have a HashMap which contains n^2 keys in it. I want to get common elements between the list and hashmap with using retainAll() function. What is the time complexity of retainAll() in this case?
List<List<Integer> list = new ArrayList<>();
Map<List<Integer>, Integer> hashmap = new HashMap<List<Integer>, Integer>();
list.retainAll(hashmap);
To get the commons,i'm using this loop but it has n^2 complexity.
List<List<Integer>> commons = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
if (hashmap.get(list.get(i)) != null) {
commons.add(list.get(i));
}
}
Also if you have an algorithm which has better time complexity to get intersection of them, i need it.
EDIT :
Actually the problem is , i have a list of integers size of n . But i divided that list into sublists and sublists count became n^2. Since i want to find out that hashmap contains every sublist as a key in it, i used n^2 in question. But it's too big according to my whole project. I'm searching complexity decreasing ways.
Well, I mean if you have n^2 items in the list then the complexity is O(n^2), although this a really weird thing to say. Usually n is the size of the collection, so the time complexity of retainAll is consider O(n).
It's impossible to have an algorithm that produces a better time complexity for this, as far as I can think of. You have to iterate the list at least once...
What you can do is switch your data structure. Read more: Data structures for fast intersection operations?
I thought I got this right, to find the mode in O(n). But when timing, it seems to take much closer to O(n*log(n)):
public int mode(List<Integer> numList) {
//Holds frequencies
int[] frequencies = new int[Collections.max(numList)+1];
for(int i=0; i<numList.size(); i++) {
frequencies[numList.get(i)]+=1;
}
//Convert to List
List<Integer> freqArray = IntStream.of(frequencies).boxed().collect(Collectors.toCollection(ArrayList::new));
int maxFreq = Collections.max(freqArray);
int mode = freqArray.indexOf(maxFreq);
return mode;
}
Where am I going wrong? Thanks
You're almost right as most of the operations take up to O(n) time, except maybe for streams. They can take more time than O(n) even when traversing through Iterable of length n. More here.
Like #Andronicus specified, you can get rid of streams and use pure Arrays not even lists. But again, with your approach your time complexity isn't O(n), it is O(m), where m is the maximum element in your array. Also like mentioned in the comments, your approach won't work for negative values. In such cases HashMap is your best bet and most of the time guarantees O(1) inserts/fetches for calculating hashes for simple types like integers.
I solved this problem from codefights:
Note: Write a solution with O(n) time complexity and O(1) additional space complexity, since this is what you would be asked to do during a real interview.
Given an array a that contains only numbers in the range from 1 to a.length, find the first duplicate number for which the second occurrence has the minimal index. In other words, if there are more than 1 duplicated numbers, return the number for which the second occurrence has a smaller index than the second occurrence of the other number does. If there are no such elements, return -1.
int firstDuplicate(int[] a) {
HashSet z = new HashSet();
for (int i: a) {
if (z.contains(i)){
return i;
}
z.add(i);
}
return -1;
}
My solution passed all of the tests. However I don't understand how my solution met the O(1) additional space complexity requirement. The size of the hashtable is directly proportional to the input so I would think it is O(n) space complexity. Did codefights incorrectly test my algorithm or am I misunderstanding something?
Your code doesn’t have O(1) auxiliary space complexity, since that hash set can grow up to size n if given an array of all different elements.
My guess is that the online testing infrastructure didn’t check memory usage or otherwise checked memory usage incorrectly. If you want to meet the space constraints, you’ll need to go back and try solving the problem a different way.
As a hint, think about reordering the array elements.
In case you are able to modify incomming array, you could fix your problem with O(n) time complexity, and do not use external memory.
public static int getFirstDuplicate(int... arr) {
for (int i = 0; i < arr.length; i++) {
int val = Math.abs(arr[i]);
if (arr[val - 1] < 0)
return val;
arr[val - 1] = -arr[val - 1];
}
return -1;
}
This is technically incorrect, for two reasons.
Firstly, depending on the values in the array, there may be overhead when the ints become Integers and added to the HashSet.
Secondly, while the additional memory is largely the overhead associated with a HashSet, that overhead is linearly proportional to the size of the set. (Note that I am not counting the elements in this, as they are already present in the array.)
Usually, these memory constraints are tested by setting a limit to the amount of memory it can use. A solution like this I would expect to fall below the said threshold.
In an interview I was asked for the following:
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
int [] array = new int [10000];
for (int i = 0; i < array.length; i++) {
// do calculations
}
for (int x = array.length-1; x >= 0; x--) {
// do calculations
}
}
}
Is it the same to iterate an array either from the end or from the start? As my understanding it would be the same since complexity is constant i.e O(1) ? Am I correct?
Also I was asked regarding ArrayList Complexity compared to other collections in java, for example, LinkedList.
Thank you.
There can be a difference due to CPU prefetch characteristics.
There is no difference between looping in either direction as per computational theory. However, depending on the kind of prefetcher that is used by the CPU on which the code runs, there will be some differences in practice.
For example, the Sandy Bridge Intel processor has a prefetcher that goes forward only for data (while instructions could be prefetched in both directions). This will help iteration from the start (as future memory locations are prefetched into the L1 cache), while iterating from the end will cause very little to no prefetching, and hence more accesses to RAM which is much slower than accessing any of the CPU caches.
There is a more detailed discussion about forward and backward prefetching at this link.
It's O(n) in both cases for an array, as there are n iterations and each step takes O(1) (assuming the calculations in the loop take O(1)). In particular, obtaining the length or size is typically an O(1) operation for arrays or ArrayList.
A typical use case for iterating from the end is removing elements in the loop (which may otherwise require more complex accounting to avoid skipping elements or iterating beyond the end).
For a linked list, the first loop would typically be O(n²), as determining the length of a linked list is typically an O(n) operation without additional caching, and it's used every time the exit condition is checked. However, java.util.LinkedList keeps explicitly track of the length, so the total is O(n) for linked lists in java.
If an element in a linked list is accessed using the index in the calculations, this will be an O(n) operation, yielding a total of O(n²).
Is it the same to iterate an array either from the end or from the start? As my understanding it would be the same since complexity is constant i.e O(1) ? Am I correct?
In theory, yes it's the same to iterate an array from the end and from the start.
The time complexity is O(10,000) which is constant so O(1) assuming that the loop body has a constant time complexity. But it's nice to mention that the constant 10,000 can be promoted to a variable, call it N, and then you can say that the time complexity is O(N).
Also I was asked regarding ArrayList Complexity compared to other collections in java, for example, LinkedList.
Here you can find comparison between ArrayList and LinkedList time complexity. The interesting methods are add, remove and get.
http://www.programcreek.com/2013/03/arraylist-vs-linkedlist-vs-vector/
Also, data in a LinkedList are not stored consuecutively. However, data in an ArrayList are stored consecutively and also an ArrayList uses less space than a LinkedList
Good luck!
I have to find some common items in two lists. I cannot sort it, order is important. Have to find how many elements from secondList occur in firstList. Now it looks like below:
int[] firstList;
int[] secondList;
int iterator=0;
for(int i:firstList){
while(i <= secondList[iterator]/* two conditions more */){
iterator++;
//some actions
}
}
Complexity of this algorithm is n x n. I try to reduce the complexity of this operation, but I don't know how compare elements in different way? Any advice?
EDIT:
Example: A=5,4,3,2,3 B=1,2,3
We look for pairs B[i],A[j]
Condition:
when
B[i] < A[j]
j++
when
B[i] >= A[j]
return B[i],A[j-1]
next iteration through the list of A to an element j-1 (mean for(int z=0;z<j-1;z++))
I'm not sure, Did I make myself clear?
Duplicated are allowed.
My approach would be - put all the elements from the first array in a HashSet and then do an iteration over the second array. This reduces the complexity to the sum of the lengths of the two arrays. It has the downside of taking additional memory, but unless you use more memory I don't think you can improve your brute force solution.
EDIT: to avoid further dispute on the matter. If you are allowed to have duplicates in the first array and you actually care how many times does an element in the second array match an array in the first one, use HashMultiSet.
Put all the items of the first list in a set
For each item of the second list, test if its in the set.
Solved in less than n x n !
Edit to please fge :)
Instead of a set, you can use a map with the item as key and the number of occurrence as value.
Then for each item of the second list, if it exists in the map, execute your action once per occurence in the first list (dictionary entries' value).
import java.util.*;
int[] firstList;
int[] secondList;
int iterator=0;
HashSet hs = new HashSet(Arrays.asList(firstList));
HashSet result = new HashSet();
while(i <= secondList.length){
if (hs.contains( secondList[iterator]))
{
result.add(secondList[iterator]);
}
iterator++;
}
result will contain required common element.
Algorithm complexity n
Just because the order is important doesn't mean that you cannot sort either list (or both). It only means you will have to copy first before you can sort anything. Of course, copying requires additional memory and sorting requires additional processing time... yet I guess all solutions that are better than O(n^2) will require additional memory and processing time (also true for the suggested HashSet solutions - adding all values to a HashSet costs additional memory and processing time).
Sorting both lists is possible in O(n * log n) time, finding common elements once the lists are sorted is possible in O(n) time. Whether it will be faster than your native O(n^2) approach depends on the size of the lists. In the end only testing different approaches can tell you which approach is fastest (and those tests should use realistic list sizes as to be expected in your final code).
The Big-O notation is no notation that tells you anything about absolute speed, it only tells you something about relative speed. E.g. if you have two algorithms to calculate a value from an input set of elements, one is O(1) and the other one is O(n), this doesn't mean that the O(1) solution is always faster. This is a big misconception of the Big-O notation! It only means that if the number of input elements doubles, the O(1) solution will still take approx. the same amount of time while the O(n) solution will take approx. twice as much time as before. So there is no doubt that by constantly increasing the number of input elements, there must be a point where the O(1) solution will become faster than the O(n) solution, yet for a very small set of elements, the O(1) solution may in fact be slower than the O(n) solution.
OK, so this solution will work if there are no duplicates in either the first or second array. As the question does not tell, we cannot be sure.
First, build a LinkedHashSet<Integer> out of the first array, and a HashSet<Integer> out of the second array.
Second, retain in the first set only elements that are in the second set.
Third, iterate over the first set and proceed:
// A LinkedHashSet retains insertion order
Set<Integer> first = LinkedHashSet<Integer>(Arrays.asList(firstArray));
// A HashSet does not but we don't care
Set<Integer> second = new HashSet<Integer>(Arrays.asList(secondArray));
// Retain in first only what is in second
first.retainAll(second);
// Iterate
for (int i: first)
doSomething();