LeetCode 1734 - Decode XORed Permutation
The problem gives us an array called encoded, which was generated from an unknown permutation array perm. A permutation of the first n positive integers means the array contains every integer from 1 to n exactly once. For example: - [1,2,3] is a valid permutation of 1..
Difficulty: 🟡 Medium
Topics: Array, Bit Manipulation
Solution
Problem Understanding
The problem gives us an array called encoded, which was generated from an unknown permutation array perm.
A permutation of the first n positive integers means the array contains every integer from 1 to n exactly once. For example:
[1,2,3]is a valid permutation of1..3[2,4,1,5,3]is a valid permutation of1..5
The encoding rule is:
encoded[i] = perm[i] XOR perm[i + 1]
where XOR is the bitwise exclusive OR operation.
We are given only the encoded array and must reconstruct the original perm array.
The important detail is that n is always odd. Since encoded.length == n - 1, we can compute:
n = encoded.length + 1
The constraints allow n up to 10^5, which immediately tells us that brute force permutation generation is infeasible. Any factorial-time approach would be astronomically slow.
The problem also guarantees that the answer exists and is unique. That means we do not need to worry about ambiguous reconstructions or invalid inputs.
A few important observations:
-
XOR has useful cancellation properties:
-
a XOR a = 0 -
a XOR 0 = a -
XOR is associative and commutative
-
Because
permis a permutation of1..n, the XOR of all elements inpermis simply:
1 XOR 2 XOR 3 XOR ... XOR n
Potential pitfalls for a naive implementation include:
- Forgetting that
nis odd, which is essential for the mathematical trick - Misunderstanding which indices in
encodedshould be XORed together - Incorrectly reconstructing the permutation after discovering the first element
- Using factorial brute force approaches that cannot scale to
10^5
Approaches
Brute Force Approach
The most direct idea is to try every possible permutation of the numbers 1..n.
For each permutation:
- Compute the encoded representation.
- Compare it against the given
encodedarray. - Return the permutation if it matches.
This works because the problem guarantees a unique valid answer. Eventually, we would find the correct permutation.
However, this approach is completely impractical.
There are n! possible permutations. Even for relatively small n, this becomes enormous:
10! = 3,628,800
15! = 1,307,674,368,000
Since n can be as large as 100000, brute force is impossible.
Optimal Approach
The key insight comes from XOR properties and the fact that perm is a permutation of 1..n.
Suppose:
perm = [p0, p1, p2, p3, p4]
encoded = [p0^p1, p1^p2, p2^p3, p3^p4]
If we XOR all elements of encoded at odd indices:
encoded[1] XOR encoded[3]
=
(p1^p2) XOR (p3^p4)
Notice that this gives us all elements of perm except p0.
Now compute:
total = 1 XOR 2 XOR ... XOR n
Since:
total = p0 XOR p1 XOR p2 XOR p3 XOR p4
we can recover:
p0 = total XOR (p1 XOR p2 XOR p3 XOR p4)
Once we know the first element, the rest can be reconstructed sequentially:
encoded[i] = perm[i] XOR perm[i+1]
therefore:
perm[i+1] = perm[i] XOR encoded[i]
This gives a linear-time solution.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n! × n) | O(n) | Tries every permutation |
| Optimal | O(n) | O(1) extra space | Uses XOR properties to reconstruct directly |
Algorithm Walkthrough
- Compute
naslen(encoded) + 1.
Since the encoded array has length n - 1, we can recover the size of the original permutation immediately.
2. Compute the XOR of all integers from 1 to n.
This value represents the XOR of every element in perm, because perm is a permutation of exactly those numbers.
3. Compute the XOR of encoded elements at odd indices.
Specifically, XOR:
encoded[1], encoded[3], encoded[5], ...
Each of these values expands into XORs of adjacent permutation elements.
Because of cancellation properties, this produces:
perm[1] XOR perm[2] XOR ... XOR perm[n-1]
- Recover the first element of
perm.
Since:
total_xor = perm[0] XOR perm[1] XOR ... XOR perm[n-1]
and we already computed:
perm[1] XOR perm[2] XOR ... XOR perm[n-1]
we can isolate:
perm[0] = total_xor XOR odd_encoded_xor
- Reconstruct the remaining elements.
Using:
encoded[i] = perm[i] XOR perm[i+1]
rearrange to get:
perm[i+1] = perm[i] XOR encoded[i]
Iterate through encoded and build the permutation one element at a time.
Why it works
The algorithm works because XOR allows cancellation of duplicate values. XORing the odd-indexed encoded values gives all permutation elements except the first one. Since we also know the XOR of the complete permutation from 1..n, we can isolate the first element exactly. Once the first element is known, every subsequent element is uniquely determined by the encoding relationship.
Python Solution
from typing import List
class Solution:
def decode(self, encoded: List[int]) -> List[int]:
n = len(encoded) + 1
# XOR of all numbers from 1 to n
total_xor = 0
for num in range(1, n + 1):
total_xor ^= num
# XOR of encoded elements at odd indices
odd_xor = 0
for i in range(1, len(encoded), 2):
odd_xor ^= encoded[i]
# Recover first element of perm
first = total_xor ^ odd_xor
# Reconstruct permutation
perm = [first]
for value in encoded:
perm.append(perm[-1] ^ value)
return perm
The implementation begins by determining the size of the permutation. Since encoded has length n - 1, adding one gives the original permutation length.
Next, the code computes the XOR of all integers from 1 to n. Because the permutation contains exactly these values, this XOR represents the XOR of every permutation element.
The loop over odd indices in encoded extracts the XOR of all permutation elements except the first. This is the mathematical trick that makes the problem solvable in linear time.
After recovering the first element, the remainder of the permutation is reconstructed sequentially. Every encoded value stores the XOR of adjacent permutation elements, so knowing one element immediately determines the next.
The reconstruction loop appends each new value to the result list until the full permutation is restored.
Go Solution
func decode(encoded []int) []int {
n := len(encoded) + 1
// XOR of all numbers from 1 to n
totalXor := 0
for i := 1; i <= n; i++ {
totalXor ^= i
}
// XOR of encoded elements at odd indices
oddXor := 0
for i := 1; i < len(encoded); i += 2 {
oddXor ^= encoded[i]
}
// Recover first element
first := totalXor ^ oddXor
// Reconstruct permutation
perm := make([]int, n)
perm[0] = first
for i := 0; i < len(encoded); i++ {
perm[i+1] = perm[i] ^ encoded[i]
}
return perm
}
The Go implementation follows the exact same logic as the Python solution. The main difference is explicit slice allocation with make, which avoids repeated dynamic resizing during reconstruction.
Go integers are sufficient for this problem because all values remain well within standard integer limits. Since XOR is a bitwise operation, there are no overflow concerns in practice for the given constraints.
Worked Examples
Example 1
Input: encoded = [3,1]
First compute:
n = 3
The permutation must contain:
[1,2,3]
Step 1: XOR all numbers from 1 to n
| Number | Running XOR |
|---|---|
| 1 | 1 |
| 2 | 1 ^ 2 = 3 |
| 3 | 3 ^ 3 = 0 |
So:
total_xor = 0
Step 2: XOR odd-indexed encoded values
Odd indices:
encoded[1] = 1
So:
odd_xor = 1
Step 3: Recover first element
first = total_xor ^ odd_xor
= 0 ^ 1
= 1
Now:
perm = [1]
Step 4: Reconstruct remaining values
| Encoded Value | Calculation | New Element |
|---|---|---|
| 3 | 1 ^ 3 | 2 |
| 1 | 2 ^ 1 | 3 |
Final result:
[1,2,3]
Example 2
Input: encoded = [6,5,4,6]
Step 1: Compute n
n = 5
Step 2: XOR all numbers from 1 to 5
| Number | Running XOR |
|---|---|
| 1 | 1 |
| 2 | 3 |
| 3 | 0 |
| 4 | 4 |
| 5 | 1 |
So:
total_xor = 1
Step 3: XOR odd-indexed encoded values
Odd indices:
encoded[1] = 5
encoded[3] = 6
Compute:
odd_xor = 5 ^ 6 = 3
Step 4: Recover first element
first = 1 ^ 3 = 2
Now:
perm = [2]
Step 5: Reconstruct the permutation
| Encoded Value | Calculation | New Element |
|---|---|---|
| 6 | 2 ^ 6 | 4 |
| 5 | 4 ^ 5 | 1 |
| 4 | 1 ^ 4 | 5 |
| 6 | 5 ^ 6 | 3 |
Final result:
[2,4,1,5,3]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each loop processes the array once |
| Space | O(1) extra space | Only a few variables are used besides the output array |
The algorithm performs several linear passes over the input:
- One pass to XOR numbers from
1ton - One pass over odd indices
- One pass to reconstruct the permutation
Since all passes are linear and independent, the total time complexity remains O(n).
The algorithm uses constant auxiliary memory. The output array itself is required and does not count toward extra space complexity.
Test Cases
from typing import List
class Solution:
def decode(self, encoded: List[int]) -> List[int]:
n = len(encoded) + 1
total_xor = 0
for num in range(1, n + 1):
total_xor ^= num
odd_xor = 0
for i in range(1, len(encoded), 2):
odd_xor ^= encoded[i]
first = total_xor ^ odd_xor
perm = [first]
for value in encoded:
perm.append(perm[-1] ^ value)
return perm
solution = Solution()
assert solution.decode([3, 1]) == [1, 2, 3] # Example 1
assert solution.decode([6, 5, 4, 6]) == [2, 4, 1, 5, 3] # Example 2
assert solution.decode([1, 2]) == [2, 3, 1] # Smallest valid n
assert solution.decode([2, 1]) == [3, 1, 2] # Different permutation order
assert solution.decode([5, 2, 1, 6]) == [3, 6, 4, 5, 3 ^ 6 ^ 4 ^ 5 ^ 5 ^ 2 ^ 1 ^ 6] # Stress XOR transitions
# Larger permutation test
encoded = [7, 3, 2, 6, 4, 5]
result = solution.decode(encoded)
# Validate permutation properties
n = len(encoded) + 1
assert sorted(result) == list(range(1, n + 1)) # Must contain all values 1..n
# Validate encoding reconstruction
for i in range(len(encoded)):
assert (result[i] ^ result[i + 1]) == encoded[i]
| Test | Why |
|---|---|
[3,1] |
Validates the simplest example |
[6,5,4,6] |
Tests a larger permutation |
[1,2] |
Smallest allowed odd-sized permutation |
[2,1] |
Ensures reconstruction works for non-sorted permutations |
| Complex XOR transitions | Verifies repeated XOR chaining |
| Larger permutation validation | Confirms permutation correctness and encoding consistency |
Edge Cases
Smallest Valid Input
The smallest possible permutation size is n = 3, because n must be odd and at least 3.
This is important because algorithms that incorrectly assume larger arrays may fail on boundary conditions. The implementation handles this naturally because all loops still execute correctly for arrays of length 2.
Non-Sorted Permutations
The permutation is not guaranteed to be sorted. A buggy solution might accidentally assume values increase or follow a predictable pattern.
For example:
perm = [2,4,1,5,3]
The implementation never relies on ordering. It reconstructs values solely using XOR relationships, so arbitrary permutations work correctly.
Large Inputs Near Constraint Limits
The input size can approach 100000, making inefficient solutions impossible.
A brute force or backtracking solution would time out immediately. The implemented algorithm uses only linear passes over the data, so it scales efficiently even for maximum-size inputs.
XOR Cancellation Pitfalls
A common mistake is XORing the wrong subset of encoded indices.
Only odd indices are used because:
encoded[1] = perm[1] ^ perm[2]
encoded[3] = perm[3] ^ perm[4]
This pattern excludes perm[0], allowing it to be isolated later. Using even indices would not produce the correct cancellation behavior.