Suppose you have an array of size $n \geq 6$ containing integers from $1$ to $n − 5$, inclusive, with exactly five repeated. I need to propose an algorithm that can find the repeated numbers in $O(n)$ time. I cannot, for the life of me, think of anything. I think sorting, at best, would be $O(n\log n)$? Then traversing the array would be $O(n)$, resulting in $O(n^2\log n)$. However, I'm not really sure if sorting would be necessary as I've seen some tricky stuff with linked list, queues, stacks, etc.
9 Answers
The solution in fade2black's answer is the standard one, but it uses $O(n)$ space. You can improve this to $O(1)$ space as follows:
- Let the array be $A[1],\ldots,A[n]$. For $d=1,\ldots,5$, compute $\sigma_d = \sum_{i=1}^n A[i]^d$.
- Compute $\tau_d = \sigma_d - \sum_{i=1}^{n-5} i^d$ (you can use the well-known formulas to compute the latter sum in $O(1)$). Note that $\tau_d = m_1^d + \cdots + m_5^d$, where $m_1,\ldots,m_5$ are the repeated numbers.
- Compute the polynomial $P(t) = (t-m_1)\cdots(t-m_5)$. The coefficients of this polynomial are symmetric functions of $m_1,\ldots,m_5$ which can be computed from $\tau_1,\ldots,\tau_5$ in $O(1)$.
- Find all roots of the polynomial $P(t)$ by trying all $n-5$ possibilities.
This algorithm assumes the RAM machine model, in which basic arithmetic operations on $O(\log n)$-bit words take $O(1)$ time.
Another way to formulate this solution is along the following lines:
- Calculate $x_1 = \sum_{i=1}^n A[i]$, and deduce $y_1 = m_1 + \cdots + m_5$ using the formula $y_1 = x_1 - \sum_{i=1}^{n-5} i$.
- Calculate $x_2 = \sum_{1 \leq i < j \leq} A[i] A[j]$ in $O(n)$ using the formula $$ x_2 = (A[1]) A[2] + (A[1] + A[2]) A[3] + (A[1] + A[2] + A[3]) A[4] + \cdots + (A[1] + \cdots + A[n-1]) A[n]. $$
- Deduce $y_2 = \sum_{1 \leq i < j \leq 5} m_i m_j$ using the formula $$ y_2 = x_2 - \sum_{1 \leq i < j \leq n-5} ij - \left(\sum_{i=1}^{n-5} i\right) y_1. $$
- Calculate $x_3,x_4,x_5$ and deduce $y_3,y_4,y_5$ along similar lines.
- The values of $y_1,\ldots,y_5$ are (up to sign) the coefficients of the polynomial $P(t)$ from the preceding solution.
This solution shows that if we replace 5 by $d$, then we get (I believe) a $O(d^2n)$ algorithm using $O(d^2)$ space, which performs $O(dn)$ arithmetic operations on integers of bit-length $O(d\log n)$, keeping at most $O(d)$ of these at any given time. (This requires careful analysis of the multiplications we perform, most of which involve one operand of length only $O(\log n)$.) It is conceivable that this can be improved to $O(dn)$ time and $O(d)$ space using modular arithmetic.
- 280,205
- 27
- 317
- 514
You could create an additional array $B$ of size $n$. Initially set all elements of the array to $0$. Then loop through the input array $A$ and increase $B[A[i]]$ by 1 for each $i$. After that you simply check the array $B$: loop over $A$ and if $B[A[i]] > 1$ then $A[i]$ is repeated. You solve it in $O(n)$ time at the cost of memory which is $O(n)$ and because your integers are between $1$ and $n-5$.
- 9,905
- 2
- 26
- 36
There's also a linear time and constant space algorithm based on partitioning, which may be more flexible if you're trying to apply this to variants of the problem that the mathematical approach doesn't work well on. This requires mutating the underlying array and has worse constant factors than the mathematical approach. More specifically, I believe the costs in terms of the total number of values $n$ and the number of duplicates $d$ are $\mathcal{O}(n \log d)$ and $\mathcal{O}(d)$ respectively, though proving it rigorously will take more time than I have at the moment.
Algorithm
Start with a list of pairs, where the first pair is the range over the whole array, or $[(1, n)]$ if 1-indexed.
Repeat the following steps until the list is empty:
- Take and remove any pair $(i, j)$ from the list.
- Find the minimum and maximum, $\text{min}$ and $\text{max}$, of the denoted subarray.
- If $\text{min} = \text{max}$, the subarray consists only of equal elements. Yield its elements except one and skip steps 4 to 6.
- If $\text{max} - \text{min} = j - i$, the subarray contains no duplicates. Skip steps 5 and 6.
- Partition the subarray around $\frac{\text{min}+\text{max}}{2}$, such that elements up to some index $k$ are smaller than the separator and elements above that index are not.
- Add $(i, k)$ and $(k + 1, j)$ to the list.
Cursory analysis of time complexity.
Steps 1 to 6 take $\mathcal{O}(j - i)$ time, since finding the minimum and maximum and partitioning can be done in linear time.
Every pair $(i, j)$ in the list is either the first pair, $(1, n)$, or a child of some pair for which the corresponding subarray contains a duplicate element. There are at most $d \lceil \log_2 n + 1\rceil$ such parents, since each traversal halves the range in which a duplicate can be, so there are at most $2d \lceil \log_2 n + 1\rceil$ total when including pairs over subarrays with no duplicates. At any one time, the size of the list is no more than $2d$.
Consider the work to find any one duplicate. This consists of a sequence of pairs over an exponentially decreasing range, so the total work is the sum of the geometric sequence, or $\mathcal{O}(n)$. This produces an obvious corollary that the total work for $d$ duplicates must be $\mathcal{O}(nd)$, which is linear in $n$.
To find a tighter bound, consider the worst-case scenario of maximally spread out duplicates. Intuitively, the search takes two phases, one where the full array is being traversed each time, in progressively smaller parts, and one where the parts are smaller than $\frac{n}{d}$ so only parts of the array are traversed. The first phase can only be $\log d$ deep, so has cost $\mathcal{O}(n \log d)$, and the second phase has cost $\mathcal{O}(n)$ because the total area being searched is again exponentially decreasing.
- 992
- 1
- 7
- 16
Leaving this as an answer because it needs more space than a comment gives.
You make a mistake in the OP when you suggest a method. Sorting a list and then transversing it $O(n\log n)$ time, not $O(n^2\log n)$ time. When you do two things (that take $O(f)$ and $O(g)$ respectively) sequentially then the resulting time complexity is $O(f+g)=O(\max{f,g})$ (under most circumstances).
In order to multiply the time complexities, you need to be using a for loop. If you have a loop of length $f$ and for each value in the loop you do a function that takes $O(g)$, then you'll get $O(fg)$ time.
So, in your case you sort in $O(n\log n)$ and then transverse in $O(n)$ resulting in $O(n\log n+n)=O(n\log n)$. If for each comparison of the sorting algorithm you had to do a computation that takes $O(n)$, then it would take $O(n^2\log n)$ but that's not the case here.
In case your curious about my claim that $O(f+g)=O(\max{f,g})$, it's important to note that that's not always true. But if $f\in O(g)$ or $g\in O(f)$ (which holds for a whole host of common functions), it will hold. The most common time it doesn't hold is when additional parameters get involved and you get expressions like $O(2^cn+n\log n)$.
- 777
- 4
- 19
There's an obvious in-place variant of the boolean array technique using the order of the elements as the store (where arr[x] == x for "found" elements). Unlike the partition variant that can be justified for being more general I'm unsure when you'd actually need something like this, but it is simple.
for idx from n-4 to n
while arr[arr[idx]] != arr[idx]
swap(arr[arr[idx]], arr[idx])
This just repeatedly puts arr[idx] at the location arr[idx] until you find that location already taken, at which point it must be a duplicate. Note that the total number of swaps is bounded by $n$ since each swap makes its exit condition correct.
- 82,470
- 26
- 145
- 239
- 992
- 1
- 7
- 16
Subtract the values you have from the sum $\sum_{i=1}^{n} i = \frac{(n-1) \cdot n}{2}$.
So, after $\Theta(n)$ time (assuming arithmetic is O(1), which it isn't really, but let's pretend) you have a sum $\sigma_1$ of 5 integers between 1 and n:
$x_1 + x_2 + x_3 + x_4 + x_5 = \sigma_1$
Supposedly, this is no good, right? You can't possibly figure out how to break this up into 5 distinct numbers.
Ah, but this is where it gets to be fun! Now do the same thing as before, but subtract the squares of the values from $\sum_{i=1}^{n} i^2$. Now you have:
${x_1}^2 + {x_2}^2 + {x_3}^2 + {x_4}^2 + {x_5}^2 = \sigma_2$
See where I'm going with this? Do the same for powers 3, 4 and 5 and you have yourself 5 independent equations in 5 variables. I'm pretty sure you can solve for $\vec{x}$.
Caveats: Arithmetic is not really O(1). Also, you need a bit of space to represent your sums; but not as much as you would imagine - you can do most everything modularly, as long as you have, oh, $\lceil\log(5n^6)\rceil$ bits; that should do it.
- 1,025
- 6
- 19
Map an array to 1 << A[i] and then XOR everything together. Your duplicates will be the numbers where corresponding bit is off.
- 82,470
- 26
- 145
- 239
- 101
- 2
Easiest way to solve the problem is to create array in which we will count the apperances for each number in the original array, and then traverse all number from $1$ to $n-5$ and check if the number appears more than once, the complexity for this solution in both memory and time is linear, or $O(N)$
- 1,428
- 15
- 27
DATA=[1,2,2,2,2,2]
from collections import defaultdict
collated=defaultdict(list):
for item in DATA:
collated[item].append(item)
if len(collated) == 5:
return item.
# n time