SORTING › CYCLIC SORT
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →Use each array position as a node and nums[i] as the next pointer from that node. Starting at index 0, repeatedly moving to nums[current] creates a linked-list-like walk. Every value is between 1 and n, so after the first move the walk stays among valid indices; it can never run out of bounds.
There are n + 1 positions but only n possible next values. The duplicate value gives two different positions the same outgoing destination, which causes two paths to merge. Once paths merge, the walk eventually repeats a node and forms a cycle. The first repeated node is not necessarily the duplicate itself, but the entrance to that cycle is exactly the duplicate value.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n) | Each phase follows the functional graph for at most a linear number of steps before meeting. The first phase stays within the cycle, and the second advances toward its entrance, so no phase can revisit an unbounded number of distinct positions. |
| Space | O(1) extra | Only slow and fast are stored; the input array is not modified, and the returned integer is output rather than working memory. The bound remains constant whether the cycle is short, long, or nearly all n values. |
#include <vector>
using namespace std;
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = nums[0];
int fast = nums[0];
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
slow = nums[0];
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
};You can also binary search the value range from 1 to n rather than walking pointers. For a midpoint mid, count how many array values are less than or equal to mid. If that count is greater than mid, the duplicate must be in 1 to mid by the pigeonhole principle; otherwise it must be in mid + 1 to n. This is a different arrangement, not an optimisation: it keeps constant space but takes O(n log n) time.
#include <vector>
using namespace std;
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int left = 1;
int right = static_cast<int>(nums.size()) - 1;
while (left < right) {
int mid = left + (right - left) / 2;
int count = 0;
for (int value : nums) {
if (value <= mid) {
++count;
}
}
if (count > mid) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
};The count compares the number of entries that belong to the lower value range with the number of distinct values that range can contain. A count above mid means that range contains a repetition, so discarding it would discard the answer. The scan does not alter nums, and the search range halves after each scan, giving O(n log n) time and O(1) extra space.