Files
codetutor/backend/data/questions/build-array-from-permutation.yaml

177 lines
7.7 KiB
YAML

title: Build Array from Permutation
slug: build-array-from-permutation
difficulty: easy
leetcode_id: 1920
leetcode_url: https://leetcode.com/problems/build-array-from-permutation/
categories:
- arrays
patterns:
- matrix-traversal
function_signature: "def build_array(nums: list[int]) -> list[int]:"
test_cases:
visible:
- input: { nums: [0, 2, 1, 5, 3, 4] }
expected: [0, 1, 2, 4, 5, 3]
- input: { nums: [5, 0, 1, 2, 3, 4] }
expected: [4, 5, 0, 1, 2, 3]
hidden:
- input: { nums: [0] }
expected: [0]
- input: { nums: [1, 0] }
expected: [0, 1]
- input: { nums: [0, 1, 2, 3, 4] }
expected: [0, 1, 2, 3, 4]
- input: { nums: [4, 3, 2, 1, 0] }
expected: [0, 1, 2, 3, 4]
- input: { nums: [2, 0, 1] }
expected: [1, 2, 0]
- input: { nums: [3, 0, 2, 1] }
expected: [1, 3, 2, 0]
description: |
Given a **zero-based permutation** `nums` (**0-indexed**), build an array `ans` of the **same length** where `ans[i] = nums[nums[i]]` for each `0 <= i < nums.length` and return it.
A **zero-based permutation** `nums` is an array of **distinct** integers from `0` to `nums.length - 1` (**inclusive**).
constraints: |
- `1 <= nums.length <= 1000`
- `0 <= nums[i] < nums.length`
- The elements in `nums` are **distinct**
examples:
- input: "nums = [0,2,1,5,3,4]"
output: "[0,1,2,4,5,3]"
explanation: "ans[i] = nums[nums[i]]. For i=0: nums[nums[0]] = nums[0] = 0. For i=1: nums[nums[1]] = nums[2] = 1. And so on."
- input: "nums = [5,0,1,2,3,4]"
output: "[4,5,0,1,2,3]"
explanation: "For i=0: nums[nums[0]] = nums[5] = 4. For i=1: nums[nums[1]] = nums[0] = 5. And so on."
explanation:
intuition: |
Think of this problem as following a chain of pointers. Each element in the array tells you "go look at this index," and then you report what you find there.
Imagine the array as a treasure map where each location contains coordinates to another location. For each position `i`, you first look at `nums[i]` to get a new location, then you go to that location and record what you find. The result is your answer for position `i`.
The key insight is that since `nums` is a permutation of `0` to `n-1`, every index is valid and every value appears exactly once. This guarantees that `nums[nums[i]]` will never go out of bounds — every value in the array is a valid index.
For the follow-up challenge of O(1) space, we can encode two values in each position using the mathematical property that for any value `a` and `b` where both are less than `n`, we can store `a + n * b` and later extract `a` as `value % n` and `b` as `value // n`.
approach: |
We solve this using **direct simulation**:
**Step 1: Create a result array**
- Initialise an empty array `ans` of the same length as `nums`
- We'll fill each position with the required value
&nbsp;
**Step 2: Apply the transformation**
- For each index `i` from `0` to `n-1`:
- Look up `nums[i]` to get the intermediate index
- Look up `nums[nums[i]]` to get the final value
- Store this value in `ans[i]`
&nbsp;
**Step 3: Return the result**
- Return the completed `ans` array
&nbsp;
For the **O(1) space** solution, we use a clever encoding trick:
**Step 1: Encode both values in each position**
- For each index `i`, we want to store both the original `nums[i]` and the new `nums[nums[i]]`
- Use the formula: `nums[i] = nums[i] + n * (nums[nums[i]] % n)`
- The `% n` is crucial because some positions may already have been encoded
&nbsp;
**Step 2: Decode to get final values**
- For each index `i`, extract the encoded value using integer division: `nums[i] = nums[i] // n`
common_pitfalls:
- title: Modifying Array While Reading
description: |
In the O(1) space approach, if you simply set `nums[i] = nums[nums[i]]`, you corrupt the array for later indices that need to read the original values.
For example, with `nums = [0, 2, 1]`, if you set `nums[0] = nums[nums[0]] = nums[0] = 0`, then when computing `nums[1]`, you need `nums[nums[1]] = nums[2] = 1`, which is still correct. But consider `nums = [1, 0]`: setting `nums[0] = nums[1] = 0` means when computing `nums[1]`, you need `nums[nums[1]] = nums[0]`, but `nums[0]` is now `0` instead of `1`.
wrong_approach: "Overwriting values directly in the input array"
correct_approach: "Either use a separate result array or encode both values together"
- title: Forgetting Modulo When Encoding
description: |
When using the encoding trick, the value at `nums[i]` might already be encoded (contains both old and new values). Reading `nums[nums[i]]` directly would give the wrong result.
Always use `nums[nums[i] % n]` to extract the original value, since `original_value = encoded_value % n`.
wrong_approach: "nums[i] += n * nums[nums[i]]"
correct_approach: "nums[i] += n * (nums[nums[i]] % n)"
- title: Integer Overflow Concerns
description: |
In some languages, `nums[i] + n * encoded_value` could overflow if `n` is large. With the constraint `n <= 1000`, the maximum encoded value is `999 + 1000 * 999 = 999,999`, which fits comfortably in a 32-bit integer.
In Python, integers have arbitrary precision, so this isn't a concern, but be mindful in languages like C++ or Java.
key_takeaways:
- "**Index chaining**: When array values represent indices, you can follow chains with `arr[arr[i]]`"
- "**Encoding two values**: The formula `a + n * b` stores two values `< n` in one integer, extractable via `% n` and `// n`"
- "**Permutation properties**: A permutation of `0` to `n-1` guarantees all indices are valid and all values are distinct"
- "**In-place modification**: When modifying an array in-place, ensure you can still recover original values when needed"
time_complexity: "O(n). We iterate through the array once (or twice for the O(1) space solution)."
space_complexity: "O(n) for the straightforward solution using a result array. O(1) for the encoding approach that modifies the input in-place."
solutions:
- approach_name: Direct Simulation
is_optimal: true
code: |
def buildArray(nums: list[int]) -> list[int]:
n = len(nums)
# Create result array with the same length
ans = [0] * n
# For each index, follow the chain: i -> nums[i] -> nums[nums[i]]
for i in range(n):
ans[i] = nums[nums[i]]
return ans
explanation: |
**Time Complexity:** O(n) — Single pass through the array.
**Space Complexity:** O(n) — We create a new array of size n.
This is the most straightforward approach: create a new array and fill each position by following the index chain. Clean, readable, and efficient.
- approach_name: In-Place with Encoding
is_optimal: false
code: |
def buildArray(nums: list[int]) -> list[int]:
n = len(nums)
# First pass: encode both old and new values
# nums[i] = old_value + n * new_value
for i in range(n):
# Use % n to get original value (in case already encoded)
new_value = nums[nums[i] % n] % n
nums[i] = nums[i] + n * new_value
# Second pass: decode to get only the new values
for i in range(n):
nums[i] = nums[i] // n
return nums
explanation: |
**Time Complexity:** O(n) — Two passes through the array.
**Space Complexity:** O(1) — Only modifies the input array in-place.
This clever approach stores two values in each position using the encoding `old + n * new`. The first pass encodes, the second pass decodes. While it achieves O(1) space, it's harder to understand and modifies the input, which may not always be desirable.