155 lines
7.1 KiB
YAML
155 lines
7.1 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
|
|
|
|
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
|
|
|
|
|
|
|
|
**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]`
|
|
|
|
|
|
|
|
**Step 3: Return the result**
|
|
|
|
- Return the completed `ans` array
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
**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.
|