163 lines
8.1 KiB
YAML
163 lines
8.1 KiB
YAML
title: Beautiful Array
|
|
slug: beautiful-array
|
|
difficulty: medium
|
|
leetcode_id: 932
|
|
leetcode_url: https://leetcode.com/problems/beautiful-array/
|
|
categories:
|
|
- arrays
|
|
- math
|
|
patterns:
|
|
- dynamic-programming
|
|
|
|
description: |
|
|
An array `nums` of length `n` is **beautiful** if:
|
|
|
|
- `nums` is a permutation of the integers in the range `[1, n]`.
|
|
- For every `0 <= i < j < n`, there is no index `k` with `i < k < j` where `2 * nums[k] == nums[i] + nums[j]`.
|
|
|
|
Given the integer `n`, return *any beautiful array* `nums` *of length* `n`. There will be at least one valid answer for the given `n`.
|
|
|
|
constraints: |
|
|
- `1 <= n <= 1000`
|
|
|
|
examples:
|
|
- input: "n = 4"
|
|
output: "[2, 1, 4, 3]"
|
|
explanation: "This is a beautiful array because no three elements at positions i < k < j satisfy 2 * nums[k] == nums[i] + nums[j]."
|
|
- input: "n = 5"
|
|
output: "[3, 1, 2, 5, 4]"
|
|
explanation: "This permutation satisfies the beautiful array property — no arithmetic mean constraint is violated."
|
|
|
|
explanation:
|
|
intuition: |
|
|
The key insight is understanding what makes an array *not* beautiful: three elements `nums[i]`, `nums[k]`, `nums[j]` where `i < k < j` and `nums[k]` is the **arithmetic mean** of `nums[i]` and `nums[j]`.
|
|
|
|
Think of it like this: if `2 * nums[k] == nums[i] + nums[j]`, then `nums[k]` lies exactly halfway between `nums[i]` and `nums[j]`. For this to happen, `nums[i] + nums[j]` must be **even**, which requires `nums[i]` and `nums[j]` to have the **same parity** (both odd or both even).
|
|
|
|
Here's the breakthrough: if we arrange all **odd numbers** on one side and all **even numbers** on the other side, then for any `i < j` where `nums[i]` and `nums[j]` have different parities, the sum `nums[i] + nums[j]` is odd. An odd sum divided by 2 cannot be an integer, so there's no valid `nums[k]` that could violate the condition!
|
|
|
|
But what about pairs with the same parity? We apply the same logic **recursively**. Within the odd section and within the even section, we recursively build beautiful sub-arrays. This divide-and-conquer approach ensures no violations exist at any level.
|
|
|
|
approach: |
|
|
We solve this using a **Divide and Conquer** approach based on parity separation:
|
|
|
|
**Step 1: Understand the transformation properties**
|
|
|
|
- If `A` is a beautiful array, then `2 * A - 1` (making all elements odd) is also beautiful
|
|
- If `A` is a beautiful array, then `2 * A` (making all elements even) is also beautiful
|
|
- Concatenating `[odd elements] + [even elements]` preserves the beautiful property across the join
|
|
|
|
|
|
|
|
**Step 2: Build recursively with memoisation**
|
|
|
|
- Base case: `n = 1` returns `[1]`
|
|
- For larger `n`, recursively build beautiful arrays for `(n + 1) // 2` elements (odd positions) and `n // 2` elements (even positions)
|
|
- Transform the first array to odd numbers: `2 * x - 1`
|
|
- Transform the second array to even numbers: `2 * x`
|
|
- Concatenate and filter to keep only values `<= n`
|
|
|
|
|
|
|
|
**Step 3: Return the result**
|
|
|
|
- The concatenation of transformed odd and even arrays gives us our beautiful array
|
|
|
|
|
|
|
|
This works because the parity separation guarantees no arithmetic mean can exist between elements from different halves, and recursion ensures the same property within each half.
|
|
|
|
common_pitfalls:
|
|
- title: Brute Force Validation
|
|
description: |
|
|
A naive approach might try to generate random permutations and validate each one by checking all triplets `(i, k, j)`.
|
|
|
|
With `n = 1000`, there are `n!` permutations and O(n^3) validation per permutation. This is astronomically slow and will never complete.
|
|
wrong_approach: "Random permutation generation with triplet validation"
|
|
correct_approach: "Constructive divide-and-conquer algorithm"
|
|
|
|
- title: Missing the Parity Insight
|
|
description: |
|
|
Without recognising that `2 * nums[k] == nums[i] + nums[j]` requires `nums[i]` and `nums[j]` to have the same parity, you might not see the path to a solution.
|
|
|
|
The sum of an odd and even number is always odd. Half of an odd number is not an integer, so it cannot equal any `nums[k]`.
|
|
wrong_approach: "Trying complex arrangements without mathematical insight"
|
|
correct_approach: "Separate by parity, then recurse"
|
|
|
|
- title: Off-by-One Errors in Recursion
|
|
description: |
|
|
When splitting `n` elements into odd and even counts, be careful:
|
|
- Odd count: `(n + 1) // 2` (ceiling division)
|
|
- Even count: `n // 2` (floor division)
|
|
|
|
After transformation, filter to keep only values `<= n` since the transformation might produce values outside the valid range.
|
|
wrong_approach: "Incorrect ceiling/floor division"
|
|
correct_approach: "Use `(n + 1) // 2` for odds, `n // 2` for evens, then filter"
|
|
|
|
key_takeaways:
|
|
- "**Parity separation**: When an arithmetic relationship requires an even sum, separating odds and evens breaks the relationship across the boundary"
|
|
- "**Constructive algorithms**: Instead of searching for valid solutions, construct them using mathematical properties"
|
|
- "**Affine transformations preserve structure**: If `A` is beautiful, then `k * A + c` is also beautiful for any constants `k`, `c`"
|
|
- "**Divide and conquer with invariants**: The beautiful property is preserved through concatenation when the halves are parity-separated"
|
|
|
|
time_complexity: "O(n log n). We build arrays of size roughly n/2 at each recursion level, with log n levels total."
|
|
space_complexity: "O(n log n). The memoisation cache stores arrays for sizes 1 through n, with total elements proportional to n log n."
|
|
|
|
solutions:
|
|
- approach_name: Divide and Conquer
|
|
is_optimal: true
|
|
code: |
|
|
def beautiful_array(n: int) -> list[int]:
|
|
# Memoisation to avoid recomputing the same sizes
|
|
memo = {1: [1]}
|
|
|
|
def build(size: int) -> list[int]:
|
|
if size in memo:
|
|
return memo[size]
|
|
|
|
# Recursively build smaller beautiful arrays
|
|
# Odd positions: transform to 2*x - 1 (odd numbers)
|
|
odds = build((size + 1) // 2)
|
|
# Even positions: transform to 2*x (even numbers)
|
|
evens = build(size // 2)
|
|
|
|
# Transform and concatenate: all odds first, then all evens
|
|
# This ensures no arithmetic mean exists across the boundary
|
|
result = [2 * x - 1 for x in odds] + [2 * x for x in evens]
|
|
|
|
# Filter to keep only values <= size
|
|
result = [x for x in result if x <= size]
|
|
|
|
memo[size] = result
|
|
return result
|
|
|
|
return build(n)
|
|
explanation: |
|
|
**Time Complexity:** O(n log n) — At each level of recursion, we process O(n) elements total, with O(log n) levels.
|
|
|
|
**Space Complexity:** O(n log n) — The memoisation cache stores arrays of decreasing sizes, summing to O(n log n) total elements.
|
|
|
|
The algorithm exploits that separating odds and evens prevents arithmetic means across the boundary. By recursively applying this within each half, we construct a beautiful array directly.
|
|
|
|
- approach_name: Iterative Construction
|
|
is_optimal: true
|
|
code: |
|
|
def beautiful_array(n: int) -> list[int]:
|
|
# Start with the simplest beautiful array
|
|
result = [1]
|
|
|
|
# Build up by applying transformations
|
|
while len(result) < n:
|
|
# Odd transformation: 2*x - 1, then even: 2*x
|
|
# Concatenate odds first, then evens
|
|
result = [2 * x - 1 for x in result] + [2 * x for x in result]
|
|
|
|
# Filter to keep only values in range [1, n]
|
|
return [x for x in result if x <= n]
|
|
explanation: |
|
|
**Time Complexity:** O(n log n) — We double the array size in each iteration, performing O(n) work per iteration across O(log n) iterations.
|
|
|
|
**Space Complexity:** O(n) — We only store the current result array.
|
|
|
|
This iterative version builds bottom-up instead of top-down. Starting from `[1]`, we repeatedly transform to create odds and evens, doubling the size until we exceed `n`, then filter. This is equivalent to the recursive approach but uses less memory.
|