186 lines
8.6 KiB
YAML
186 lines
8.6 KiB
YAML
title: Beautiful Arrangement II
|
|
slug: beautiful-arrangement-ii
|
|
difficulty: medium
|
|
leetcode_id: 667
|
|
leetcode_url: https://leetcode.com/problems/beautiful-arrangement-ii/
|
|
categories:
|
|
- arrays
|
|
- math
|
|
patterns:
|
|
- greedy
|
|
|
|
description: |
|
|
Given two integers `n` and `k`, construct a list `answer` that contains `n` different positive integers ranging from `1` to `n` and obeys the following requirement:
|
|
|
|
Suppose this list is `answer = [a1, a2, a3, ..., an]`, then the list `[|a1 - a2|, |a2 - a3|, |a3 - a4|, ..., |an-1 - an|]` has exactly `k` distinct integers.
|
|
|
|
Return *the list* `answer`. If there are multiple valid answers, return **any of them**.
|
|
|
|
constraints: |
|
|
- `1 <= k < n <= 10^4`
|
|
|
|
examples:
|
|
- input: "n = 3, k = 1"
|
|
output: "[1, 2, 3]"
|
|
explanation: "The array [1, 2, 3] has three different positive integers ranging from 1 to 3, and the differences [|1-2|, |2-3|] = [1, 1] has exactly 1 distinct integer: 1."
|
|
- input: "n = 3, k = 2"
|
|
output: "[1, 3, 2]"
|
|
explanation: "The array [1, 3, 2] has three different positive integers ranging from 1 to 3, and the differences [|1-3|, |3-2|] = [2, 1] has exactly 2 distinct integers: 1 and 2."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Think of this problem like arranging numbered tiles on a line, where you want to control how many *different gap sizes* appear between adjacent tiles.
|
|
|
|
The key insight is understanding what happens with a simple sorted sequence versus an alternating one. If you arrange numbers as `[1, 2, 3, ..., n]`, every adjacent difference is exactly `1` — you get only **1 distinct difference**. But if you zigzag between the smallest and largest remaining numbers, like `[1, n, 2, n-1, 3, ...]`, you generate the maximum variety of differences: `n-1, n-2, n-3, ...`
|
|
|
|
Here's the clever observation: with a sequence of `n` numbers, you can create anywhere from `1` to `n-1` distinct differences. To get exactly `k` distinct differences:
|
|
|
|
1. Use the first `k+1` numbers (`1` to `k+1`) in an alternating pattern to generate `k` distinct differences
|
|
2. Fill the rest with the remaining numbers in sorted order — these contribute only difference `1`, which we already have
|
|
|
|
This works because the alternating pattern on `k+1` elements produces differences `k, k-1, k-2, ..., 1` (exactly `k` distinct values), and appending sorted numbers only adds more `1`s.
|
|
|
|
approach: |
|
|
We solve this using a **Greedy Construction Approach**:
|
|
|
|
**Step 1: Initialise two pointers**
|
|
|
|
- `low`: Start at `1` (smallest available number)
|
|
- `high`: Start at `k + 1` (to create differences from `k` down to `1`)
|
|
|
|
|
|
|
|
**Step 2: Build the alternating prefix**
|
|
|
|
- Alternate between picking `low` and `high`
|
|
- First pick `low`, then `high`, then `low + 1`, then `high - 1`, and so on
|
|
- Continue until `low > high` — this uses exactly `k + 1` numbers
|
|
- This produces differences: `k, k-1, k-2, ..., 1` (exactly `k` distinct values)
|
|
|
|
|
|
|
|
**Step 3: Append the sorted suffix**
|
|
|
|
- Append the remaining numbers `k + 2, k + 3, ..., n` in order
|
|
- Each consecutive pair has difference `1`, which we already counted
|
|
- This doesn't introduce any new distinct differences
|
|
|
|
|
|
|
|
**Step 4: Return the result**
|
|
|
|
- Return the constructed array
|
|
|
|
common_pitfalls:
|
|
- title: Overcomplicating the Construction
|
|
description: |
|
|
Many people try complex formulas or recursive approaches when a simple two-pointer alternation works perfectly.
|
|
|
|
The key realisation is that you only need to create exactly `k` distinct differences, and the alternating pattern on the first `k + 1` numbers does exactly that. The rest is just padding with sorted numbers.
|
|
wrong_approach: "Complex mathematical formulas or backtracking"
|
|
correct_approach: "Simple alternating pattern for first k+1 elements, then sorted remainder"
|
|
|
|
- title: Off-by-One Errors
|
|
description: |
|
|
It's easy to get confused about whether to use `k` or `k + 1` elements for the alternating part.
|
|
|
|
Remember: to get `k` *distinct* differences, you need `k + 1` numbers (since `n` numbers produce `n - 1` differences). The alternating pattern on `[1, 2, ..., k+1]` produces differences `k, k-1, ..., 1`.
|
|
wrong_approach: "Using k elements instead of k + 1"
|
|
correct_approach: "Use first k + 1 numbers for alternating, rest sorted"
|
|
|
|
- title: Not Handling the Full Range
|
|
description: |
|
|
The problem requires using all integers from `1` to `n`. If you only focus on getting `k` distinct differences but forget to include all numbers, your answer will be invalid.
|
|
|
|
The two-phase approach (alternating prefix + sorted suffix) naturally uses every number exactly once.
|
|
wrong_approach: "Forgetting to include numbers k+2 through n"
|
|
correct_approach: "Append remaining numbers in sorted order after the alternating prefix"
|
|
|
|
key_takeaways:
|
|
- "**Constructive algorithms**: Sometimes the best approach is to directly build the answer rather than search for it"
|
|
- "**Boundary analysis**: Understanding that `n` elements produce `n-1` differences helps identify that `k+1` elements can produce `k` distinct differences"
|
|
- "**Greedy construction**: The alternating pattern greedily maximises variety when needed, then we switch to minimal variety"
|
|
- "**Similar problems**: This technique of mixing high-variance and low-variance sections appears in problems like *Wiggle Sort* and other array construction tasks"
|
|
|
|
time_complexity: "O(n). We iterate through the array once to construct the result."
|
|
space_complexity: "O(n). We store the result array of size `n` (or O(1) if we don't count the output)."
|
|
|
|
solutions:
|
|
- approach_name: Two Pointers (Alternating Construction)
|
|
is_optimal: true
|
|
code: |
|
|
def construct_array(n: int, k: int) -> list[int]:
|
|
result = []
|
|
low, high = 1, k + 1
|
|
|
|
# Alternate between low and high to create k distinct differences
|
|
while low <= high:
|
|
result.append(low)
|
|
low += 1
|
|
if low <= high:
|
|
result.append(high)
|
|
high -= 1
|
|
|
|
# Append remaining numbers in sorted order (only adds difference of 1)
|
|
for num in range(k + 2, n + 1):
|
|
result.append(num)
|
|
|
|
return result
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Single pass to build the array.
|
|
|
|
**Space Complexity:** O(n) — The result array of size `n`.
|
|
|
|
The alternating phase creates the sequence `[1, k+1, 2, k, 3, k-1, ...]` which has differences `[k, k-1, k-2, ..., 1]`. The sorted suffix `[k+2, k+3, ..., n]` adds only difference `1` (already present), giving exactly `k` distinct differences.
|
|
|
|
- approach_name: Direct Formula Construction
|
|
is_optimal: true
|
|
code: |
|
|
def construct_array(n: int, k: int) -> list[int]:
|
|
result = []
|
|
|
|
# Build the alternating prefix using a toggle
|
|
toggle = True
|
|
low, high = 1, k + 1
|
|
|
|
for _ in range(k + 1):
|
|
if toggle:
|
|
result.append(low)
|
|
low += 1
|
|
else:
|
|
result.append(high)
|
|
high -= 1
|
|
toggle = not toggle
|
|
|
|
# Append the sorted suffix
|
|
result.extend(range(k + 2, n + 1))
|
|
|
|
return result
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Linear construction.
|
|
|
|
**Space Complexity:** O(n) — The result array.
|
|
|
|
This is an alternative formulation using an explicit toggle flag. The logic is identical: alternate between smallest and largest within `[1, k+1]`, then append `[k+2, n]` in order. Some find the toggle pattern more readable than the nested while loop.
|
|
|
|
- approach_name: Reverse Segment Approach
|
|
is_optimal: true
|
|
code: |
|
|
def construct_array(n: int, k: int) -> list[int]:
|
|
# Start with sorted array [1, 2, 3, ..., n]
|
|
result = list(range(1, n + 1))
|
|
|
|
# Reverse the first k+1 elements in a zigzag pattern
|
|
# Each reversal introduces one new distinct difference
|
|
for i in range(1, k + 1):
|
|
# Reverse from index i to index k
|
|
result[i:k+1] = result[i:k+1][::-1]
|
|
|
|
return result
|
|
explanation: |
|
|
**Time Complexity:** O(n * k) — Multiple reversals, but can be optimised.
|
|
|
|
**Space Complexity:** O(n) — The result array.
|
|
|
|
This approach starts with `[1, 2, ..., n]` (1 distinct difference) and repeatedly reverses suffixes to introduce new differences one at a time. While conceptually interesting, it's less efficient than the direct construction. Included to show an alternative way of thinking about the problem.
|