Deriving Time Complexity from an algorithm [duplicate] - java

This question already has answers here:
Big O, how do you calculate/approximate it?
(24 answers)
Closed 5 years ago.
I am trying to learn about time complexity of an algorithm. My professor has pushed it beyond Big O and wants us to be able to derive an algorithm to a mathematical function. I am having a hard time conceptualizing how this is done and was looking for help. In my class notes, a selection sort algorithm was provided (as shown in the code below). The notes asked the following question: "Derive a function f(n) that corresponds to the total number of times that minIndex or any position of nums is modified in the worst case. Now the notes tell me the answer is f(n)= 1/2n^2 + 5/2n + 3. I was wondering if anyone could explain how this occurs.
My professor told us to count the operations in the inner loop and work our way out. so I believe that at worst case in the inner loop the if statement always executes, this would mean we run the loop n-i-1 times, I get these values by taking n (the boundary that the for loop has to be less than and subtracting it by the starting condition (i+1). Then I look at the outer loop and I see it goes from i until it reaches n-1, so it could be written as (n-1)-i or just like the inner loop, n-i-1. Looking further there are three modifications in the outer loop so we get (n-i-1)+3 ( could I write it as (n-i+2)?
The number of modification at the worst case for the inner loop:
n-i-1
The number of modifications at the worst case for the outer loop:
(n-i-1)+3
now I would like to know how do you go from counting the two different modifications done and becoming f(n)= 1/2n^2 + 5/2n + 3.
public static void selectionSort(int[] nums) {
int n = nums.length;
int minIndex;
for(int i = 0; i < n-1; i++) {
//find the index of themin number.
minIndex = i;
for(int j = i+1; j < n; j++) {
if(nums[j] < nums[minIndex]) {
minIndex = j;
}
int temp = nums[i];
nums[i] = nums[minIndex];
nums[minIndex] = temp;
}
}
}

How many times does outer loop run?
n - 1 times.
How many times does inner loop run, for each iteration of outer loop?
From n - 1 times down to 1 time, as outer loop progresses, so on average:
((n - 1) + 1) / 2 = n / 2 times.
So, how many times does inner loop run in total?
(n - 1) * (n / 2) = n^2 / 2 - n / 2 times.
How many times is minIndex modified?
Once per outer loop + once per inner loop:
(n - 1) + (n^2 / 2 - n / 2) = n^2 / 2 + n / 2 - 1 times.
How many times is a position of nums modified?
Twice per inner loop:
2 * (n^2 / 2 - n / 2) = n^2 - n times.
What is total number of modifications?
(n^2 / 2 + n / 2 - 1) + (n^2 - n) = (3*n^2 - n) / 2 - 1 times.
Or 1½n² - ½n - 1
That's not the same answer as you said your notes has, so let's prove it.
First, we add debug printing, i.e. print any modification including a modification number.
public static void selectionSort(int[] nums) {
int mod = 0;
int n = nums.length;
int minIndex;
for(int i = 0; i < n-1; i++) {
//find the index of themin number.
minIndex = i; System.out.printf("%2d: minIndex = %d%n", ++mod, i);
for(int j = i+1; j < n; j++) {
if(nums[j] < nums[minIndex]) {
minIndex = j; System.out.printf("%2d: minIndex = %d%n", ++mod, j);
}
int temp = nums[i];
nums[i] = nums[minIndex]; System.out.printf("%2d: nums[%d] = %d%n", ++mod, i, nums[minIndex]);
nums[minIndex] = temp; System.out.printf("%2d: nums[%d] = %d%n", ++mod, minIndex, temp);
}
}
}
Worst case is sorting an array of descending numbers, so lets try 3 numbers:
int[] nums = { 3, 2, 1 };
selectionSort(nums);
System.out.println(Arrays.toString(nums));
We'd expect (3*n^2 - n) / 2 - 1 = (3*3^2 - 3) / 2 - 1 = 24 / 2 - 1 = 11 modifications.
1: minIndex = 0
2: minIndex = 1
3: nums[0] = 2
4: nums[1] = 3
5: minIndex = 2
6: nums[0] = 1
7: nums[2] = 2
8: minIndex = 1
9: minIndex = 2
10: nums[1] = 2
11: nums[2] = 3
[1, 2, 3]
Yup, 11 modifications.
Lets try 9:
int[] nums = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
(3*n^2 - n) / 2 - 1 = (3*9^2 - 9) / 2 - 1 = 234 / 2 - 1 = 116 modifications.
1: minIndex = 0
2: minIndex = 1
3: nums[0] = 8
4: nums[1] = 9
5: minIndex = 2
6: nums[0] = 7
. . .
111: nums[6] = 7
112: nums[8] = 8
113: minIndex = 7
114: minIndex = 8
115: nums[7] = 8
116: nums[8] = 9
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Yup, 116 modification.
Formula verified by empiric evidence:
f(n) = (3*n^2 - n) / 2 - 1

Related

Time Complexity for Modulus in Nested Loops

sum=0;
for(i=1; i<n;i++)
for( j = 1; j < i * i; j++ )
if( j % i == 0 )
for( k = 0; k < j; k++ )
sum++;
I know that the outer most loop executes n time, the next loop n^2 times, but how do I factor the j%i with the loop? The correct answer is O(N^4), but I am not sure how that is reached.
Since we know that the k loop only executes when j is divisible by i (this is where the modulo comes in), we can figure out how many times k loops at every i loop:
i = 2, j:1..4: loops 2 times (j=2).
i = 3, j:1..9: loops 3 + 6 times (j=3,6).
i = 4, j:1..16: loops 4 + 8 + 12 times (j=4,8,12).
...
i = n, j:1..n*n: loops n + 2n + ... (n - 1) * n
Or Sum(k * n, k = 1, n - 1) = n^2 * (n - 1) / 2 times.
I realize that this should stop at i = n - 1, but this is enough to show the growth.
This means that our complexity is that of
2 + (3 + 6) + (4 + 8 + 12) + ... + (n^2 * (n - 1) / 2)
=Sum(
i^2 * (i - 1) / 2,
i = 2, n - 1
)
=Sum(
(i^3 - i^2) / 2,
i = 2, n - 1
)
Since the i^3 term dominates and the division by 2 doesn't matter, this has the same time complexity as
Sum(i^3, i=2, n - 1)
Which is O(n^4).

How to format this output?

I have created a method, which is supposed to return a growing number list when entered any number.
For example:
input: 5
expected output: [1, 2 2, 3 3 3, 4 4 4 4, 5 5 5 5 5]
current output : [1 , 2 2 , 3 3 3 , 4 4 4 4 , 5 5 5 5 5 ]
input: 2
expected output: [1, 2 2]
current output : [1 , 2 2 ]
How do I delete the white spaces?
This is my code:
ArrayList < String > maxarray = new ArrayList < String > ();
int i, j;
String num = "";
for (i = 1; i <= max; i++) {
num = "";
for (j = 1; j <= i; j++) {
num = num + i + " ";
}
//System.out.print(i);
maxarray.add(num);
}
return maxarray;
I have tried: num= num.replace(" ","");
num = num.replace(" ", "");
but, they don't seem work.
and if I try to convert it into a string, I get the following output: 1 2 2 3 3 3
Help me, please
Easy and short-sighted solution: just trim the string before adding it to the ArrayList:
maxarray.add(num.trim());
With this change the output is:
[1, 2 2, 3 3 3, 4 4 4 4, 5 5 5 5 5]
trim() removes trailing spaces (and leading, had there been any), but not the spaces between the numbers.
Produce correct output from the outset: However, rather than producing not-quite-right output and then correcting it it’s probably less confusing in the end to produce correct output from the outset. That is, without the space after the last number. This is also what the answer by Berto99 does. My preferred way of doing this is: take one iteration out of the inner loop and refrain from adding the space there.
for (i = 1; i <= max; i++) {
num = "";
for (j = 1; j < i; j++) { // stop one number before i
num = num + i + " ";
}
num = num + i; // add no space here
maxarray.add(num);
}
Output still is:
[1, 2 2, 3 3 3, 4 4 4 4, 5 5 5 5 5]
I find this way easier to read than the version with the extra if statement in the answer by Berto99.
For the sake of precision: maxarray is not an int array, it as an ArrayList of strings. And what we needed to trim was not the ArrayList but each string in it.
PS Stream version: For readers who know and like streams here’s a stream version. If you don’t want to learn about streams yet, just ignore.
List<String> maxarray = IntStream.rangeClosed(1, max)
.mapToObj(i -> IntStream.rangeClosed(1, i)
.mapToObj(j -> String.valueOf(i))
.collect(Collectors.joining(" ")))
.collect(Collectors.toList());
One of the convenient things is that Collectors.joining(" ") puts spaces between the numbers without putting any space before the first or after the last, which solves the problem asked about.
As you can see from here:
ArrayList < String > maxarray = new ArrayList < String > ();
int i, j;
String num = "";
for (i = 1; i <= max; i++) {
num = "";
for (j = 1; j <= i; j++) {
num = num + i + " ";
^^^^-- here
}
//System.out.print(i);
maxarray.add(num);
}
return maxarray;
you add a space after every number, so you only need to check if you are not in the last number, in order not to add the space in that case:
ArrayList < String > maxarray = new ArrayList < String > ();
int i, j;
String num = "";
for (i = 1; i <= max; i++) {
num = "";
for (j = 1; j <= i; j++) {
num = num + i;
if(j < i) // <--- check if you are not in the last iteration
num = num + " ";
}
//System.out.print(i);
maxarray.add(num);
}
return maxarray;

Get the consecutive numbers whose sum matches with given number

I was going through a simple program that takes a number and finds the number of occurrences of consecutive numbers that matches with given number.
For example:
if input is 15, then the consecutive numbers that sum upto 15 are:
1,2,3,4,5
4,5,6
7,8
So the answer is 3 as we have 3 possibilities here.
When I was looking for a solution I found out below answer:
static long process(long input) {
long count = 0;
for (long j = 2; j < input/ 2; j++) {
long temp = (j * (j + 1)) / 2;
if (temp > input) {
break;
}
if ((input- temp) % j == 0) {
count++;
}
}
return count;
}
I am not able to understand how this solves the requirement because this program is using some formula which I am not able to understand properly, below are my doubts:
The for loop starts from 2, what is the reason for this?
long temp = (j * (j + 1)) / 2; What does this logic indicates? How is this helpful to solving the problem?
if ((num - temp) % j == 0) Also what does this indicate?
Please help me in understanding this solution.
I will try to explain this as simple as possible.
If input is 15, then the consecutive numbers that sum upto 15 are:
{1,2,3,4,5} -> 5 numbers
{4,5,6} -> 3 numbers
{7,8} -> 2 numbers
At worst case, this must be less than the Sum of 1st n natural numbers = (n*(n+1) /2.
So for a number 15, there can never be a combination of 6 consecutive numbers summing up to 15 as the sum of 1st 6 numbers =21 which is greater than 15.
Calculate temp: This is (j*(j+1))/2.
Take an example. Let input = 15. Let j =2.
temp = 2*3/2 = 3; #Meaning 1+2 =3
For a 2-number pair, let the 2 terms be 'a+1' and 'a+2'.(Because we know that the numbers are consecutive.)
Now, according to the question, the sum must add up to the number.
This means 2a+3 =15;
And if (15-3) is divisible by 2, 'a' can be found. a=6 -> a+1=7 and a+2=8
Similarly, let a+1 ,a+2 and a+3
a + 1 + a + 2 + a + 3 = 15
3a + 6 = 15
(15-6) must be divisible by 3.
Finally, for 5 consecutive numbers a+1,a+2,a+3,a+4,a+5 , we have
5a + 15 = 15;
(15-15) must be divisible by 5.
So, the count will be changed for j =2,3 and 5 when the input is 15
If the loop were to start from 1, then we would be counting 1 number set too -> {15} which is not needed
To summarize:
1) The for loop starts from 2, what is the reason for this?
We are not worried about 1-number set here.
2) long temp = (j * (j + 1)) / 2; What does this logic indicates? How is this helpful to solving the problem?
This is because of the sum of 1st n natural numbers property as I have
explained the above by taking a+1 and a+2 as 2 consecutive
numbers.
3) if ((num - temp) % j == 0) Also what does this indicate?
This indicates the logic that the input subtracted from the sum of 1st
j natural numbers must be divisible by j.
We need to find all as and ns, that for given b the following is true:
a + (a + 1) + (a + 2) + ... (a + (n - 1)) = b
The left side is an arithmetic progression and can be written as:
(a + (n - 1) / 2) * n = b (*)
To find the limit value of n, we know, that a > 0, so:
(1 + (n - 1) / 2) * n = n(n + 1) / 2 <= b
n(n + 1) <= 2b
n^2 + n + 1/4 <= 2b + 1/4
(n + 1/2)^2 <= 2b + 1/4
n <= sqrt(2b + 1/4) - 1/2
Now we can rewrite (*) to get formula for a:
a = b / n - (n - 1) / 2
Example for b = 15 and n = 3:
15 / 3 - (3 - 1) / 2 = 4 => 4 + 5 + 6 = 15
And now the code:
double b = 15;
for (double n = 2; n <= Math.ceil(Math.sqrt(2 * b + .25) - .5); n++) {
double candidate = b / n - (n - 1) / 2;
if (candidate == (int) candidate) {
System.out.println("" + candidate + IntStream.range(1, (int) n).mapToObj(i -> " + " + (candidate + i)).reduce((s1, s2) -> s1 + s2).get() + " = " + b);
}
}
The result is:
7.0 + 8.0 = 15.0
4.0 + 5.0 + 6.0 = 15.0
1.0 + 2.0 + 3.0 + 4.0 + 5.0 = 15.0
We are looking for consecutive numbers that sum up to the given number.
It's quite obvious that there could be at most one series with a given length, so basically we are looking for those values witch could be the length of such a series.
variable 'j' is the tested length. It starts from 2 because the series must be at least 2 long.
variable 'temp' is the sum of a arithmetic progression from 1 to 'j'.
If there is a proper series then let X the first element. In this case 'input' = j*(X-1) + temp.
(So if temp> input then we finished)
At the last line it checks if there is an integer solution of the equation. If there is, then increase the counter, because there is a series with j element which is a solution.
Actually the solution is wrong, because it won't find solution if input = 3. (It will terminate immediately.) the cycle should be:
for(long j=2;;j++)
The other condition terminates the cycle faster anyway.
NB: loop is starting from 2 because=> (1*(1+1))/2 == 1, which doesn't make sense, i.e, it doesn't effect on the progress;
let, k = 21;
so loop will iterate upto (k/2) => 10 times;
temp = (j*(j+1))/2 => which is, 3 when j =2, 6 when j = 3, and so on (it calculates sum of N natural numbers)
temp > k => will break the loop because, we don't need to iterate the loop when we got 'sum' which is more than 'K'
((k-temp)%j) == 0 => it is basically true when the input subtracted from the sum of first j natural numbers are be divisible by j, if so then increment the count to get total numbers of such equation!
public static long process(long input) {
long count = 0, rest_of_sum;
for (long length = 2; length < input / 2; length++) {
long partial_sum = (length * (length + 1)) / 2;
if (partial_sum > input) {
break;
}
rest_of_sum = input - partial_sum
if (rest_of_sum % length == 0)
count++;
}
return count;
}
input - given input number here it is 15
length - consecutive numbers length this is at-least 2 at max input/2
partial_sum = sum of numbers from 1 to length (which is a*(a+1)/2 for 1 to a numbers) assume this is a partial sequence
rest_of_sum = indicates the balance left in input
if rest of sum is multiple of length meaning is that we can add (rest_of_sum/length) to our partial sequence
lets call (rest_of_sum/length) as k
this only means we can build a sequence here that sums up to our input number
starting with (k+1) , (k+2), ... (k+length)
this can validated now
(k+1) + (k+2) + ... (k+length)
we can reduce this as k+k+k+.. length times + (1+2+3..length)
can be reduced as => k* length + partial_sum
can be reduced as => input (since we verified this now)
So idea here is to increment count every-time we find a length which satisfies this case here
If you put this tweak in it may fix code. I have not extensively tested it. It's an odd one but it puts the code through an extra iteration to fix the early miscalculations. Even 1/20000 would work! Had this been done with floats that got rounded down and 1 added to them I think that would have worked too:
for (long j = 2; j < input+ (1/2); j++) {
In essence you need to only know one formula:
The sum of the numbers m..n (or m to n) (and where n>m in code)
This is ((n-m+1)*(n+m))/2
As I have commented already the code in the original question was bugged.
See here.
Trying feeding it 3. That has 1 occurrence of the consecutive numbers 1,2. It yields 0.
Or 5. That has 2,3 - should yield 1 too - gives 0.
Or 6. This has 1,2,3 - should yield 1 too - gives 0.
In your original code, temp or (j * (j + 1)) / 2 represented the sum of the numbers 1 to j.
1 2 3 4 5
5 4 3 2 1
=======
6 6 6 6 6 => (5 x 6) /2 => 30/2 => 15
As I have shown in the code below - use System.out.println(); to spew out debugging info.
If you want to perfect it make sure m and n's upper limits are half i, and i+1 respectively, rounding down if odd. e.g: (i=15 -> m=7 & n=8)
The code:
class Playground {
private static class CountRes {
String ranges;
long count;
CountRes(String ranges, long count) {
this.ranges = ranges;
this.count = count;
}
String getRanges() {
return this.ranges;
}
long getCount() {
return this.count;
}
}
static long sumMtoN(long m, long n) {
return ((n-m+1)* (n+m))/2;
}
static Playground.CountRes countConsecutiveSums(long i, boolean d) {
long count = 0;
StringBuilder res = new StringBuilder("[");
for (long m = 1; m< 10; m++) {
for (long n = m+1; n<=10; n++) {
long r = Playground.sumMtoN(m,n);
if (d) {
System.out.println(String.format("%d..%d %d",m,n, r));
}
if (i == r) {
count++;
StringBuilder s = new StringBuilder(String.format("[%d..%d], ",m,n));
res.append(s);
}
}
}
if (res.length() > 2) {
res = new StringBuilder(res.substring(0,res.length()-2));
}
res.append("]");
return new CountRes(res.toString(), count);
}
public static void main(String[ ] args) {
Playground.CountRes o = countConsecutiveSums(3, true);
for (long i=3; i<=15; i++) {
o = Playground.countConsecutiveSums(i,false);
System.out.println(String.format("i: %d Count: %d Instances: %s", i, o.getCount(), o.getRanges()));
}
}
}
You can try running it here
The output:
1..2 3
1..3 6
1..4 10
1..5 15
1..6 21
1..7 28
1..8 36
1..9 45
1..10 55
2..3 5
2..4 9
2..5 14
2..6 20
2..7 27
2..8 35
2..9 44
2..10 54
3..4 7
3..5 12
3..6 18
3..7 25
3..8 33
3..9 42
3..10 52
4..5 9
4..6 15
4..7 22
4..8 30
4..9 39
4..10 49
5..6 11
5..7 18
5..8 26
5..9 35
5..10 45
6..7 13
6..8 21
6..9 30
6..10 40
7..8 15
7..9 24
7..10 34
8..9 17
8..10 27
9..10 19
i: 3 Count: 1 Instances: [[1..2]]
i: 4 Count: 0 Instances: []
i: 5 Count: 1 Instances: [[2..3]]
i: 6 Count: 1 Instances: [[1..3]]
i: 7 Count: 1 Instances: [[3..4]]
i: 8 Count: 0 Instances: []
i: 9 Count: 2 Instances: [[2..4], [4..5]]
i: 10 Count: 1 Instances: [[1..4]]
i: 11 Count: 1 Instances: [[5..6]]
i: 12 Count: 1 Instances: [[3..5]]
i: 13 Count: 1 Instances: [[6..7]]
i: 14 Count: 1 Instances: [[2..5]]
i: 15 Count: 3 Instances: [[1..5], [4..6], [7..8]]

ArrayList Removing first element

This is the given question:
Given a non-negative number represented as an array of digits,
add 1 to the number ( increment the number represented by the digits ).
The digits are stored such that the most significant digit is at the head of the list.
Example:
If the vector has [1, 2, 3]
the returned vector should be [1, 2, 4]
as 123 + 1 = 124.
This is my code:
public class Solution {
public ArrayList<Integer> plusOne(ArrayList<Integer> A) {
int carry = 1;
int length = A.size();
ArrayList result = new ArrayList();
for( int i = length - 1; i >=0; i-- ){
int val = A.get(i) + carry;
result.add(0,val % 10);
carry = val / 10;
}
if (carry == 1){
result.add(0,1);
}
for (int j = 0; j < result.size(); j++){
if(result.get(j).equals(0))
result.remove(j);
else
break;
}
return result;
}
}
However, in the test case:
A : [ 0, 6, 0, 6, 4, 8, 8, 1 ]
it says my function returns
6 6 4 8 8 2
while the correct answer is
6 0 6 4 8 8 2
I have no idea what is wrong with my code.
Thanks!
if(result.get(j).equals(0))
result.remove(j);
else
break;
This will fail if every other index contains a 0. Here's what happens:
0 6 0 6 4 8 8 2
^ (j = 0)
The 0 will be removed, and j is incremented by one.
6 0 6 4 8 8 2
^ (j = 1)
Then this 0 is removed as well, skipping the first 6 in your array. To fix this, change the snippet to:
if(result.get(j).equals(0))
result.remove(j--);
else
break;
This compensates for when an index is removed so that j will not skip the number immediately after any removed 0s.
Check out a similar question at Looping through and arraylist and removing elements at specified index
simpler to do just
while (!result.isEmpty() && result.get(0).equals(0)) {
result.remove(0);
}
This will keep removing the left most 0 until there is no more left most zero to be deleted.
Your last for loop is removing 0 from your result ArrayList<Integer>. After removing that loop, you will get perfect output
public static ArrayList<Integer> plusOne(ArrayList<Integer> A) {
int carry = 1;
int length = A.size();
ArrayList result = new ArrayList();
for (int i = length - 1; i >= 0; i--) {
int val = A.get(i) + carry; //2 8
result.add(0, val % 10); // 2 8
carry = val / 10;
}
if (carry == 1) {
result.add(0, 1);
}
// for (int j = 0; j < result.size(); j++) {
// if (result.get(j).equals(0))
// result.remove(j);
// else
// break;
// }
for (boolean isZero = true; isZero; ) {
isZero = result.get(0).equals(0);
if(isZero)
result.remove(0);
}
return result;
}

O(n) algorithm to find the odd-number-out in array of consecutive integers from 1 to n(not odd numbers)

I'm having a great deal of trouble trying to figure this question out, and the root of that trouble is creating an algorithm of O(n) complexity. Here's the question I'm struggling with:
An Array A of length n contains integers from the range [0, .., n - 1]. However, it only contains n - 1 distinct numbers. So one of the numbers is missing and another number is duplicated. Write a Java method that takes A as an input argument and returns the missing number; the method should run in O(n).
For example, when A = [0, 2, 1, 2, 4], oddOneOut() should return 3; when A = [3, 0, 0, 4, 2, 1], oddOneOut() should return 5.
Obviously this is an easy problem to solve with an O(n2) algorithm, (and most likely O(n), I'm just not seeing it!). I've attempted to solve it using all manner of methods, but to no avail. I'm attempting to solve it in Java, but if you're more comfortable solving it Python, that would be fine as well.
Thank you in advance...
Suppose the number missing is x and the duplicate is y. If you add all numbers, the sum will be:
(n - 1) * n / 2 - x + y
From the above, you can find (x - y).....(1)
Similarly, sum the squares of the numbers. The sum will then be:
(n - 1) * n * (2 * n - 1) / 6 - x2 + y2
From the above you get (x2 - y2)....(2)
(2) / (1) gives (x + y).....(3)
(1) + (3) gives 2 * x and you can thereby find x and y.
Note that in this solution there is O(1) extra storage and is O(n) time complexity. The other solutions above are unnecessarily O(n) extra storage.
Code in mixed C/C++ for some more clarity:
#include <stdio.h>
int findDup(int *arr, int n, int& dup, int& missing)
{
int sum = 0;
int squares = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
squares += arr[i] * arr[i];
}
sum = (n - 1) * n / 2 - sum; // x - y
squares = (n - 1) * n * (2 * (n - 1) + 1) / 6 - squares; // x^2 - y^2
if (sum == 0) {
// no duplicates
missing = dup = 0;
return -1;
}
missing = (squares / sum + sum) / 2; // ((x^2 - y^2) / (x - y) + (x - y)) / 2 = ((x + y) + (x - y)) / 2 = x
dup = missing - sum; // x - (x - y) = y
return 0;
}
int main(int argc, char *argv[])
{
int dup = 0;
int missing = 0;
int a[] = {0, 2, 1, 2, 4};
findDup(a, sizeof(a) / sizeof(int), dup, missing);
printf("dup = [%d], missing = [%d]\n", dup, missing);
int b[] = {3, 0, 0, 4, 2, 1};
findDup(b, sizeof(b) / sizeof(int), dup, missing);
printf("dup = [%d], missing = [%d]\n", dup, missing);
return 0;
}
Output:
dup = [2], missing = [3]
dup = [0], missing = [5]
Some python code:
def finddup(lst):
sum = 0
sumsq = 0
missing = 0
dup = 0
for item in lst:
sum = sum + item
sumsq = sumsq + item * item
n = len(a)
sum = (n - 1) * n / 2 - sum
sumsq = (n - 1) * n * (2 * (n - 1) + 1) / 6 - sumsq
if sum == 0:
return [-1, missing, dup]
missing = ((sumsq / sum) + sum) / 2
dup = missing - sum
return [0, missing, dup]
found, missing, dup = finddup([0, 2, 1, 2, 4])
if found != -1:
print "dup = " + str(dup) + " missing = " + str(missing)
print finddup([3, 0, 0, 4, 2, 1])
Outputs:
dup = 2 missing = 3
[-1, 0, 0]
Iterate over the array twice: That is still O(n). Create a temporary array of booleans (or a Java BitSet) to hold which numbers you got. Second time you do the loop, check if there is a hole in the array of booleans.
Use a hash set and take a single pass to detect which number is the duplicate. During the same iteration, track the cumulative sum of all the numbers.
Now calculate the expected total if all the numbers were distinct: n * (n - 1) / 2. Subtract the total you found. You will be left with the "missing" number minus the duplicate. Add the duplicate back to get your answer.
Since hash table access is constant time and we're using a single pass, this is O(n). (Note that a single pass isn't strictly necessary: Martijn is correct in noting that a fixed number of passes is still linear complexity.)
This might be of interest, although I'm not certain under what conditions (if any) it performs best. The idea is that we're going to move each element into its correct place in the array (0 to index 0, etc), until it becomes clear what is missing and what is extra.
def findmissing(data):
upto = 0
gap = -1
while upto < len(data):
#print data, gap
if data[upto] == upto:
upto += 1
continue
idx = data[upto]
if idx is None:
upto += 1
continue
data[upto], data[idx] = data[idx], data[upto]
if data[upto] == data[idx]:
print 'found dupe, it is', data[upto]
data[upto] = None
gap = upto
upto += 1
elif data[upto] is None:
gap = upto
return gap
if __name__ == '__main__':
data = range(1000)
import random
missing = random.choice(data)
print missing
data[missing] = data[0]
data[0] = random.choice(data[1:])
random.shuffle(data)
print 'gap is', findmissing(data)
It's O(n) because every step either increments upto or moves a value into its "correct" place in the array, and each of those things can only happen n times.

Categories