Given an integer n
, construct a sequence seq
of length 2 * n - 1
using the numbers from 1
to n
(each number used exactly twice except for 1
, which is used once). The arrangement must satisfy the following rule:
seq[i] = x
and x > 1
, then seq[i + x]
must also be x
(i.e., the two occurrences of x
are exactly x
indices apart).seq
must be filled, and each number must appear the correct number of times.
For example, for n = 3
, a valid sequence is [3, 1, 2, 3, 2]
.
At first glance, this problem seems similar to permutation or backtracking problems where we need to place numbers under certain constraints. A brute-force approach would be to generate all possible sequences and check which ones are valid, but this quickly becomes infeasible as n
increases because the number of possibilities grows factorially.
To optimize, we observe that we want the lexicographically largest result. This means we should try to place the largest numbers first, as early as possible in the sequence. We also need to respect the distance constraint for each number: for x > 1
, its two occurrences must be exactly x
indices apart.
The problem thus becomes a backtracking search, but with a greedy twist: always try to place the largest unused number at the earliest possible valid position. If we ever get stuck, we backtrack and try the next possibility.
We use a recursive backtracking algorithm with the following steps:
seq
of length 2 * n - 1
filled with zeros to represent empty slots. Also, maintain a boolean array or set to track which numbers have been used.
seq
, try to place the largest unused number that fits:
x
from n
down to 1
:x
has not been used, and for x > 1
, both seq[i]
and seq[i + x]
are empty, place x
at both positions.x = 1
, only one occurrence is needed; place it at the first available slot.x
as used and recursively proceed to the next empty slot.We use a hash set or boolean array for O(1) lookups to check if a number is already used. The recursive structure ensures all constraints are met, and the greedy order ensures the largest sequence.
Let's walk through the algorithm for n = 3
:
seq = [0, 0, 0, 0, 0]
seq = [3, 0, 0, 3, 0]
seq = [3, 0, 2, 3, 2]
seq = [3, 1, 2, 3, 2]
If at any step a placement is not possible, the algorithm backtracks and tries the next possible number or position.
Brute-force approach: Would try all permutations of the numbers, checking each for validity. The number of permutations is approximately (2n-1)!
, which is infeasible for larger n
.
Optimized approach: The backtracking algorithm still explores many possibilities, but pruning (by skipping invalid placements and always starting with the largest numbers) reduces the search space significantly. In the worst case, the time complexity remains exponential, but in practice, it is much faster due to early pruning.
The key to solving this problem efficiently is to combine backtracking with a greedy strategy: always try to place the largest number first, and only backtrack when necessary. By carefully managing the constraints and using data structures for quick lookups, we can construct the lexicographically largest valid sequence without exhaustively checking all possibilities. This approach balances correctness and efficiency, making it elegant and practical for reasonable values of n
.
class Solution:
def constructDistancedSequence(self, n: int) -> List[int]:
res = [0] * (2 * n - 1)
used = [False] * (n + 1)
def backtrack(idx):
if idx == len(res):
return True
if res[idx] != 0:
return backtrack(idx + 1)
for num in range(n, 0, -1):
if used[num]:
continue
if num == 1:
res[idx] = 1
used[1] = True
if backtrack(idx + 1):
return True
res[idx] = 0
used[1] = False
else:
j = idx + num
if j < len(res) and res[idx] == 0 and res[j] == 0:
res[idx] = res[j] = num
used[num] = True
if backtrack(idx + 1):
return True
res[idx] = res[j] = 0
used[num] = False
return False
backtrack(0)
return res
class Solution {
public:
vector<int> res;
vector<bool> used;
int n, N;
bool backtrack(int idx) {
if (idx == N) return true;
if (res[idx] != 0) return backtrack(idx + 1);
for (int num = n; num >= 1; --num) {
if (used[num]) continue;
if (num == 1) {
res[idx] = 1;
used[1] = true;
if (backtrack(idx + 1)) return true;
res[idx] = 0;
used[1] = false;
} else {
int j = idx + num;
if (j < N && res[idx] == 0 && res[j] == 0) {
res[idx] = res[j] = num;
used[num] = true;
if (backtrack(idx + 1)) return true;
res[idx] = res[j] = 0;
used[num] = false;
}
}
}
return false;
}
vector<int> constructDistancedSequence(int n_) {
n = n_;
N = 2 * n - 1;
res.assign(N, 0);
used.assign(n + 1, false);
backtrack(0);
return res;
}
};
class Solution {
int[] res;
boolean[] used;
int n, N;
public int[] constructDistancedSequence(int n) {
this.n = n;
this.N = 2 * n - 1;
res = new int[N];
used = new boolean[n + 1];
backtrack(0);
return res;
}
private boolean backtrack(int idx) {
if (idx == N) return true;
if (res[idx] != 0) return backtrack(idx + 1);
for (int num = n; num >= 1; --num) {
if (used[num]) continue;
if (num == 1) {
res[idx] = 1;
used[1] = true;
if (backtrack(idx + 1)) return true;
res[idx] = 0;
used[1] = false;
} else {
int j = idx + num;
if (j < N && res[idx] == 0 && res[j] == 0) {
res[idx] = res[j] = num;
used[num] = true;
if (backtrack(idx + 1)) return true;
res[idx] = res[j] = 0;
used[num] = false;
}
}
}
return false;
}
}
var constructDistancedSequence = function(n) {
const N = 2 * n - 1;
const res = new Array(N).fill(0);
const used = new Array(n + 1).fill(false);
function backtrack(idx) {
if (idx === N) return true;
if (res[idx] !== 0) return backtrack(idx + 1);
for (let num = n; num >= 1; --num) {
if (used[num]) continue;
if (num === 1) {
res[idx] = 1;
used[1] = true;
if (backtrack(idx + 1)) return true;
res[idx] = 0;
used[1] = false;
} else {
let j = idx + num;
if (j < N && res[idx] === 0 && res[j] === 0) {
res[idx] = res[j] = num;
used[num] = true;
if (backtrack(idx + 1)) return true;
res[idx] = res[j] = 0;
used[num] = false;
}
}
}
return false;
}
backtrack(0);
return res;
};