Files
codetutor/backend/data/questions/beautiful-arrangement-ii.yaml

208 lines
9.1 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
function_signature: "def construct_array(n: int, k: int) -> list[int]:"
test_cases:
visible:
- input: { n: 3, k: 1 }
expected: [1, 2, 3]
- input: { n: 3, k: 2 }
expected: [1, 3, 2]
hidden:
- input: { n: 2, k: 1 }
expected: [1, 2]
- input: { n: 5, k: 1 }
expected: [1, 2, 3, 4, 5]
- input: { n: 5, k: 4 }
expected: [1, 5, 2, 4, 3]
- input: { n: 6, k: 3 }
expected: [1, 4, 2, 3, 5, 6]
- input: { n: 10, k: 5 }
expected: [1, 6, 2, 5, 3, 4, 7, 8, 9, 10]
- input: { n: 4, k: 3 }
expected: [1, 4, 2, 3]
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`)
&nbsp;
**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)
&nbsp;
**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
&nbsp;
**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.