Complexity for a limited for loop iterations - java

I have a quick question about Complexity. I have this code in Java:
pairs is a HashMap that contains an Integer as a key, and it's frequency in a Collection<Integer> as a value. So :
pairs = new Hashmap<Integer number, Integer numberFrequency>()
Then I want to find the matching Pairs (a,b) that verify a + b == targetSum.
for (int i = 0; i < pairs.getCapacity(); i++) { // Complexity : O(n)
if (pairs.containsKey(targetSum - i) && targetSum - i == i) {
for (int j = 1; j < pairs.get(targetSum - i); j++) {
collection.add(new MatchingPair(targetSum - i, i));
}
}
}
I know that the complexity of the first For loop is O(n), but the second for Loop it only loops a small amount of times, which is the frequency of the number-1, do we still count it as O(n) so this whole portion of code will be O(n^2) ? If it is does someone have any alternative to just make it O(n) ?

Its O(n) if 'pairs.getCapacity()' or 'pairs.get(targetSum - i)' is a constant you know before hand. Else, two loops, one nested in the other, is generally O(n^2).

You can consider that for the wors case your complexity is O(n2)

Related

Not understanding big O notation O(∑ i=0 n−1 (∑ j=i+1 n (j−i)))=O(∑ i=0 n−1 2 (1+n−i)(n−i))=O(n^3)

Working on the following problem:
Given a string s, find the length of the longest substring without repeating characters.
I'm using this brute force solution:
public class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
if (checkRepetition(s, i, j)) {
res = Math.max(res, j - i + 1);
}
}
}
return res;
}
private boolean checkRepetition(String s, int start, int end) {
int[] chars = new int[128];
for (int i = start; i <= end; i++) {
char c = s.charAt(i);
chars[c]++;
if (chars[c] > 1) {
return false;
}
}
return true;
}
}
Tbe big O notation is as follows:
I understand that three nested iterations would result in a time complexity O(n^3).
I only see two sigma operators being used on the start of the formula, could someone enlighten me on where the third iteration comes to play in the beginning of the formula?
The first sum from i=0 to n-1 corresponds to the outer for loop of lengthOfLongestSubstring, which you can see iterates from i=0 to n-1.
The second sum from j = i+1 to n corresponds to the second for loop (you could be starting j at i+1 rather than i as there's no need to check length 0 sub-strings).
Generally, we would expect this particular double for loop structure to produce O(n^2) algorithms and a third for loop (from k=j+1 to n) to lead to O(n^3) ones. However, this general rule (k for loops iterating through all k-tuples of indices producing O(n^k) algorithms) is only the case when the work done inside the innermost for loop is constant. This is because having k for loops structured in this way produces O(n^k) total iterations, but you need to multiply the total number of iterations by the work done in each iteration to get the overall complexity.
From this idea, we can see that the reason lengthOfLongestSubstring is O(n^3) is because the work done inside of the body of the second for loop is not constant, but rather is O(n). checkRepitition(s, i, j) iterates from i to j, taking j-i time (hence the expression inside the second term of the sum). O(j-i) time is O(n) time in the worst case because i could be as low as 0, j as high as n, and of course O(n-0) = O(n) (it's not too hard to show that checkRepitions is O(n) in the average case as well).
As mentioned by a commenter, having a linear operation inside the body of your second for loop has the same practical effect in terms of complexity as having a third for loop, which would probably be easier to see as being O(n^3) (you could even imagine the function definition for checkRepitition, including its for loop, being pasted into lengthOfLongestSubstring in place to see the same result). But the basic idea is that doing O(n) work for each of the O(n^2) iterations of the 2 for loops means the total complexity is O(n)*O(n^2) = O(n^3).

Why is this O(N^3) and not O(N^4)?

public static int g(LinkedList<Integer> list) {
int t = 0;
for (int i = 0; i < list.size(); i++) {
int tgt = list.get(i);
for (int j = i + 1; j < list.size(); j++) {
if (list.get(j) == tgt)
t += list.get(j);
}
}
return t;
}
Shouldn't the if statement make the complexity O(N^4)?
The if statements has nothing to do with the time complexity - the operation of an if-statement has time complexity O(1), but the work in an if-statement can have a greater time complexity.
It's because list.get(i) is of O(n). To get the n:th element of a LinkedList you need to step n times in the list to find it. This is because a LinkedList doesn't save its indexes, only the first and last element in the list, and then its direct neighbours.
This is the time complexity for each of your functions:
public static int g(LinkedList<Integer> list) {
int t = 0;
for (int i = 0; i < list.size(); i++) { // O(n) - loop
int tgt = list.get(i); // O(n)
for (int j = i + 1; j < list.size(); j++) { // O(n) - loop
if (list.get(j) == tgt) // O(n)
t += list.get(j); // O(n)
}
}
return t;
}
Since you're only iterating over 2 loops, it will initially make the time complexity O(n^2). The 3 calls of list.get(i) will make each make a time complexity of O(n), thus resulting in 3*O(n). This is however defaulted to O(n), and making the final time complexity to O(n) * O(n) * 3*O(n) => O(n^3)
Extra
On an unrelated note: You see that when you call list.get(j) twice, in the innermost loop, you will cause the program to iterate over the list twice, even though you just got the value.
if (list.get(j) == tgt)
t += list.get(j);
Chances are that the processor or compiler will optimize this and save the value in the cache, but it's still a good idea to call list.get(j) once and store its value.
If statements are not loops.
Each get may take O(n), but the statements in its body are executed after the condition. So the if statement takes O(n)+O(n) (for the two gets), which is O(n).
That is nested inside two nested loops over a list of size n, so overall it is O(n^3).

Find sum of max integer in array with least space complexity

I am new to programming and trying to learn by exploring. I was looking for a solution to find sum of maximum time repeating integer in an array with best space complexity. Suppose we have [1, 2, 3, 3] the result should be 6 with least space complexity, say O(n).
I came up with a solution but not sure about the complexity. Need some help to understand if below mentioned code has least complexity or it could be better(definitely!). Sorry if I made any mistake and thanks in advance.
public static int maxDuplicateSumSpaceBased(int[] a)
{
int maxRepCount = 1, tempCount;
int maxRepNum = a[0];
int temp = 0;
for (int i = 0; i < (a.length - 1); i++)
{
temp = a[i];
tempCount = 0;
for (int j = 1; j < a.length; j++)
{
if (temp == a[j])
tempCount++;
}
if (tempCount > maxRepCount)
{
maxRepNum = temp;
maxRepCount = tempCount;
}
}
return maxRepNum * maxRepCount;
}
Actually the space of the input is usually not counted in the O notation so your program has a spatial complexity of O(6)=O(c)=O(1). c is a constant. In fact you always use 6 variables. If the amount of space used is dependent on the input given the situation is different but it's not your case because regardless of the length of you input you use always 6 variables.
If you want to count the input as occupied space (sometimes it's done) your space complexity would be O(6+n)=O(n) assuming that n is the length of the input.
It's impossible to do better as you can easly prove:
You can't have less memory occupied than the input (or you must memorize all the input). Since the input is the only thing that's not a constant you have that the maximum space used is the one needed to store the input that is n.
The space complexity1 of your solution is O(1). You can't get better than that.
The time complexity of your solution is O(N^2). You can improve on that in a couple of ways:
If you can modify a, then you can sort it { time O(NlogN), spaceO(1) } then find / count the most frequent value { O(N) , O(1) }. Overall complexity is { O(NlogN), O(1)}.
If you cannot modify a, then copy it { O(N) / O(N) } and then proceed as above. Overall complexity is { O(NlogN), O(N) }.
If the range of the numbers (M) is less than the number of numbers, then you can use a bucket sort. Overall complexity is { O(N), O(M) }.
You can get better time complexity overall using a HashMap. The overall complexity of that will be { O(N) on average, O(N)} ... with significantly larger constants of proportionality. (Unfortunately, the worst case time complexity will be O(NlogN) or O(N^2) depending on the hash map implementation. It occurs when all of the keys collide. That is impossible for Integer keys and HashMap, but possible for Long keys.)
1 - I am referring to space in addition to the space occupied by the input array. Obviously, the space used for the input array cannot be optimized. It is a given.
I have understand your problem.. Now there could be a solution there are n integers and all integers k [1-n]. Then to find maxrepeatnumber takes O(n) time.
public static int maxDuplicateSumSpaceBased(int[] a)
{
int maxRepCount = 1, tempCount;
int k=a.length();
for (int i = 0; i <k; i++)
{
a[a[i]%k]+=k;
}
int maxRepnumber=0,temp=a[0];
for (int j = 1; j < k; j++)
{
if (temp < a[j])
{
temp=a[j];
maxRepnumber=j;
}
}
}
return maxRepNum;
}
Then you sum all that number and it take O(n)and O(1) space.

Run time of algorithm

I am trying to figure out the run time of the following algorithm.
I argue it is O(n) because the inner loop does not depend on the outer loop.
So we could have O(n) + O(n) = O(2n) which equals O(n)
Is this correct? I'm not sure my logic is correct and I cannot figure out how to analyze is correctly.
The algorithm is finding the largest elements to the left of a list of elements.
Thanks!
public static void main(String[] args){
int[] a = {4,3,2,10,4,8,9,1};
int[] p = new int[a.length];
ArrayDeque<Integer> previousIndex = new ArrayDeque<Integer>();
for(int i = 0; i < a.length ; i++){
while (!previousIndex.isEmpty() && a[previousIndex.peek()] <= a[i]){
previousIndex.pop();
}
if (previousIndex.isEmpty()) {
p[i] = 0;
} else {
p[i] = previousIndex.peek();
}
previousIndex.push(i);
}
for(int i = 0; i < p.length ; i++){
System.out.println(p[i]);
}
}
}
This is O(N) for though you have a loop within a loop, the total number of times the inner loop will be executed can never be more than the total number of times that
previousIndex.push(i);
is called, which is a.length (or N)
To work out the order really you are looking at the worst case. You are correct that the nested loop is the cause for concern here:
for(int i = 0; i < a.length ; i++){
This is immediately order N
while (!previousIndex.isEmpty() && a[previousIndex.peek()] <= a[i]){
This could potentially also go nearly N times.
So the final order is N*N or N^2
You do have to keep in mind the usual case though. If it is likely that the while loop will in fact exit after only a couple of iterations you could get back down to O(N).
In fact, you are fine and have an O(N) algorithm - but it's harder to prove than most. This assumes, though, that .isEmpty(), .peek() etc. on the ArrayDeque are all constant-time operations. Consult the documentation to be sure.
The key is that your processing of the deque in the inner loop is destructive:
while (!previousIndex.isEmpty() && a[previousIndex.peek()] <= a[i]){
previousIndex.pop();
}
This removes an element from previousIndex each time, and can only run when there is one to remove. Therefore, the total number of times the while loop could run, across all indices, is the number of times that something is .pushed into deque. And since this only happens at one point - at the end of the first for loop - we can see that the number of items pushed is O(N).

Swap-search algorithm

I have an array A of n integers. I also have an array B of k (k < n) integers. What I need is that any integer from array A that appears in array B to be increased by 3.
If I go with the most obvious way, I get to n*k complexity.
Array A cannot (must not) be sorted.
Is there a more efficient way of achieveing this?
Is there a more efficient way of achieveing this?
Yes: put the elements of B into a HashSet. Loop over A and, if the element you're on is contained in the set, increase it by 3. This will have O(n + k) complexity.
For instance:
Set<Integer> bSet = new HashSet<>(B.length);
for (int a : B) // O(k)
bSet.add(a);
for (int i = 0; i < A.length; i++) { // O(n)
if (bSet.contains(a[i]))
a[i] += 3;
}
If your integers are in a range that you can create and array with the length of the greatest value (for instance 0 <= A[i] and B[i] <= 65535) then you can do this
boolean [] constains = new boolean[65535];
for (int i = 0; i < k; i++){
constains[B[i]] = true;
}
for (int i = 0; i < n; i++){
if (constains[A[i]]){
A[i] += 3;
}
}
Which is O(n + k)
if array B can be sorted - then solution is obvious, sort it, then you can optimize "contains" to be log2(K), so your complexity will be N*log2(k)
if you cannot sort array B - then the only thing is straight forward N*K
UPDATE
really forgot about bitmask, if you know that you have only 32 bit integers, and have enough memory - you can store huge bitmask array, were "add" and "contains" always will be O(1), but of course it is needed only for very special performance optimizations

Categories