667. Beautiful Arrangement II
Problem Description
You need to create a permutation of numbers from 1 to n such that the absolute differences between consecutive elements have exactly k distinct values.
Given two integers n
and k
, you must construct a list containing all integers from 1 to n (each appearing exactly once). The key requirement is that when you calculate the absolute differences between each pair of consecutive elements in your list, you should get exactly k
distinct difference values.
For example, if your answer list is [a₁, a₂, a₃, ..., aₙ]
, then the differences list [|a₁ - a₂|, |a₂ - a₃|, |a₃ - a₄|, ..., |aₙ₋₁ - aₙ|]
must contain exactly k
unique values.
The solution uses a clever alternating pattern. For the first k
elements, it alternates between picking from the smallest available number (starting from 1) and the largest available number (starting from n). This creates maximum differences initially. After placing k
elements, the remaining elements are filled in sequential order (either ascending or descending based on whether k
is even or odd) to ensure no new distinct differences are introduced.
The approach works because:
- Alternating between extremes (1, n, 2, n-1, 3, n-2...) creates differences of n-1, n-2, n-3, etc.
- After
k
alternations, we've createdk
distinct differences - Filling the rest sequentially (all with difference 1) doesn't add new distinct values
Intuition
To get exactly k
distinct differences, we need to think about how to control the variety of differences we create. The key insight is that we can maximize the number of distinct differences by alternating between the smallest and largest available numbers.
Consider what happens when we place numbers from opposite ends of the range:
- Placing 1 next to n gives us a difference of
n-1
- Placing n next to 2 gives us a difference of
n-2
- Placing 2 next to n-1 gives us a difference of
n-3
- And so on...
This pattern naturally creates many distinct differences. If we alternate between picking from the left (1, 2, 3...) and right (n, n-1, n-2...) ends, we generate differences of n-1
, n-2
, n-3
, etc. Each alternation creates a new unique difference value.
But we only want exactly k
distinct differences, not more. So after making k
alternations (which gives us k
distinct differences), we need to stop creating new differences. How? By placing the remaining numbers consecutively - either all ascending or all descending. Consecutive numbers always have a difference of 1, which we've likely already created in our alternating phase.
The beauty of this approach is that after k
alternations:
- If
k
is even, we last picked from the right side, so we continue with descending numbers - If
k
is odd, we last picked from the left side, so we continue with ascending numbers
This ensures all remaining differences are 1, adding no new distinct values to our set of differences.
Learn more about Math patterns.
Solution Approach
The implementation uses a two-pointer technique with l
starting at 1 and r
starting at n
. We build the answer array in two phases:
Phase 1: Create k distinct differences
For the first k
positions (indices 0 to k-1), we alternate between picking from left and right:
- When index
i
is even: appendl
and incrementl
by 1 - When index
i
is odd: appendr
and decrementr
by 1
This creates the pattern: [1, n, 2, n-1, 3, n-2, ...]
which generates differences of n-1
, n-2
, n-3
, and so on.
Phase 2: Fill remaining elements
For positions from k
to n-1
, we need to add the remaining numbers without creating new distinct differences. The strategy depends on the parity of k
:
- If
k
is even: we last picked from the left pointer in Phase 1, so now we pick all remaining elements from the right pointer in descending order (r, r-1, r-2, ...
) - If
k
is odd: we last picked from the right pointer in Phase 1, so now we pick all remaining elements from the left pointer in ascending order (l, l+1, l+2, ...
)
This ensures all consecutive differences in Phase 2 are 1, which is already one of our distinct differences from Phase 1.
Example walkthrough with n=5, k=3:
- Phase 1 (k=3 iterations):
- i=0 (even): append 1, l becomes 2, ans =
[1]
- i=1 (odd): append 5, r becomes 4, ans =
[1, 5]
- i=2 (even): append 2, l becomes 3, ans =
[1, 5, 2]
- i=0 (even): append 1, l becomes 2, ans =
- Phase 2 (k is odd, so use left pointer):
- append 3, l becomes 4, ans =
[1, 5, 2, 3]
- append 4, ans =
[1, 5, 2, 3, 4]
- append 3, l becomes 4, ans =
- Differences:
|1-5|=4
,|5-2|=3
,|2-3|=1
,|3-4|=1
- Distinct differences: {4, 3, 1} - exactly 3 as required
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with n = 6, k = 4.
We need to create a permutation of [1, 2, 3, 4, 5, 6] such that consecutive differences have exactly 4 distinct values.
Initial Setup:
l = 1
(left pointer)r = 6
(right pointer)ans = []
(result array)
Phase 1: Create k=4 distinct differences
Iteration 1 (i=0, even):
- Pick from left: append
1
- Increment
l
to2
ans = [1]
Iteration 2 (i=1, odd):
- Pick from right: append
6
- Decrement
r
to5
ans = [1, 6]
- Difference created: |1-6| = 5
Iteration 3 (i=2, even):
- Pick from left: append
2
- Increment
l
to3
ans = [1, 6, 2]
- Difference created: |6-2| = 4
Iteration 4 (i=3, odd):
- Pick from right: append
5
- Decrement
r
to4
ans = [1, 6, 2, 5]
- Difference created: |2-5| = 3
Phase 2: Fill remaining elements without new differences
Since k=4 is even, we last picked from the left in Phase 1. Now we pick remaining elements from the right pointer in descending order:
Iteration 5:
- Append
r = 4
- Decrement
r
to3
ans = [1, 6, 2, 5, 4]
- Difference created: |5-4| = 1
Iteration 6:
- Append
r = 3
ans = [1, 6, 2, 5, 4, 3]
- Difference created: |4-3| = 1
Final Result:
- Permutation:
[1, 6, 2, 5, 4, 3]
- Consecutive differences:
[5, 4, 3, 1, 1]
- Distinct differences:
{5, 4, 3, 1}
- exactly 4 distinct values ✓
The algorithm successfully creates exactly k=4 distinct differences by:
- Alternating between extremes for the first 4 positions to create maximum variety
- Filling the rest consecutively (descending) to avoid introducing new differences
Solution Implementation
1class Solution:
2 def constructArray(self, n: int, k: int) -> List[int]:
3 """
4 Construct an array with n numbers from 1 to n that has exactly k distinct differences.
5
6 Strategy: Create k distinct differences by alternating between smallest and largest
7 unused values, then append remaining values in sequential order.
8
9 Args:
10 n: The length of the array and maximum value (array contains 1 to n)
11 k: The number of distinct absolute differences between consecutive elements
12
13 Returns:
14 List of integers from 1 to n with exactly k distinct differences
15 """
16 left, right = 1, n
17 result = []
18
19 # First k elements: alternate between left and right boundaries
20 # This creates k distinct differences
21 for i in range(k):
22 if i % 2 == 0:
23 # Even index: append from left side
24 result.append(left)
25 left += 1
26 else:
27 # Odd index: append from right side
28 result.append(right)
29 right -= 1
30
31 # Remaining elements: append in sequential order
32 # The order depends on whether k is even or odd
33 for i in range(k, n):
34 if k % 2 == 0:
35 # If k is even, last element was from right, continue from right
36 result.append(right)
37 right -= 1
38 else:
39 # If k is odd, last element was from left, continue from left
40 result.append(left)
41 left += 1
42
43 return result
44
1class Solution {
2 /**
3 * Constructs an array with n elements from 1 to n such that
4 * there are exactly k different absolute differences between consecutive elements.
5 *
6 * @param n The size of the array and maximum value (elements are from 1 to n)
7 * @param k The number of distinct absolute differences required
8 * @return An array containing a valid arrangement
9 */
10 public int[] constructArray(int n, int k) {
11 // Initialize left and right pointers for the range [1, n]
12 int left = 1;
13 int right = n;
14
15 // Create result array of size n
16 int[] result = new int[n];
17
18 // First phase: Create k different differences by alternating between
19 // smallest and largest available numbers
20 for (int i = 0; i < k; i++) {
21 if (i % 2 == 0) {
22 // Even index: take from left (smallest available)
23 result[i] = left;
24 left++;
25 } else {
26 // Odd index: take from right (largest available)
27 result[i] = right;
28 right--;
29 }
30 }
31
32 // Second phase: Fill remaining positions to maintain exactly k differences
33 // Continue with remaining numbers in the same order (ascending or descending)
34 for (int i = k; i < n; i++) {
35 if (k % 2 == 0) {
36 // If k is even, continue taking from right (descending)
37 result[i] = right;
38 right--;
39 } else {
40 // If k is odd, continue taking from left (ascending)
41 result[i] = left;
42 left++;
43 }
44 }
45
46 return result;
47 }
48}
49
1class Solution {
2public:
3 vector<int> constructArray(int n, int k) {
4 // Initialize left and right pointers to the smallest and largest values
5 int left = 1;
6 int right = n;
7
8 // Create result vector of size n
9 vector<int> result(n);
10
11 // First k elements: alternate between picking from left and right
12 // This creates k distinct differences
13 for (int i = 0; i < k; ++i) {
14 if (i % 2 == 0) {
15 // Even index: pick from left side (ascending)
16 result[i] = left;
17 left++;
18 } else {
19 // Odd index: pick from right side (descending)
20 result[i] = right;
21 right--;
22 }
23 }
24
25 // Remaining elements: continue in the same direction as the last element
26 // This ensures no new distinct differences are introduced
27 for (int i = k; i < n; ++i) {
28 if (k % 2 == 0) {
29 // If k is even, last element was from right, continue descending
30 result[i] = right;
31 right--;
32 } else {
33 // If k is odd, last element was from left, continue ascending
34 result[i] = left;
35 left++;
36 }
37 }
38
39 return result;
40 }
41};
42
1/**
2 * Constructs an array of integers from 1 to n with exactly k distinct absolute differences
3 * between consecutive elements.
4 *
5 * @param n - The upper bound of the range [1, n]
6 * @param k - The number of distinct absolute differences required
7 * @returns An array containing integers from 1 to n with k distinct differences
8 */
9function constructArray(n: number, k: number): number[] {
10 let left: number = 1;
11 let right: number = n;
12 const result: number[] = new Array(n);
13
14 // First k elements: alternate between picking from left and right
15 // This creates k distinct differences
16 for (let i = 0; i < k; i++) {
17 if (i % 2 === 0) {
18 // Even index: pick from left side
19 result[i] = left;
20 left++;
21 } else {
22 // Odd index: pick from right side
23 result[i] = right;
24 right--;
25 }
26 }
27
28 // Remaining elements: continue in the same direction as the last element
29 // This ensures no new distinct differences are introduced
30 for (let i = k; i < n; i++) {
31 if (k % 2 === 0) {
32 // If k is even, last element was from left, so continue from right
33 result[i] = right;
34 right--;
35 } else {
36 // If k is odd, last element was from right, so continue from left
37 result[i] = left;
38 left++;
39 }
40 }
41
42 return result;
43}
44
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of two sequential loops:
- The first loop runs
k
times, performing constant-time operations (appending to list and incrementing/decrementing pointers) - The second loop runs
n - k
times, also performing constant-time operations
Since both loops together iterate exactly n
times in total, and each iteration performs O(1)
operations, the overall time complexity is O(k) + O(n - k) = O(n)
.
Space Complexity: O(n)
The space complexity is dominated by the output array ans
which stores exactly n
elements. The additional variables l
, r
, and i
use constant space O(1)
. Therefore, the total space complexity is O(n)
for the output array.
Note that if we consider only the auxiliary space (excluding the output array which is required for the answer), the space complexity would be O(1)
as we only use a constant amount of extra space for the pointers and loop variables.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Handling the Second Phase Direction
A frequent mistake is not properly determining which pointer (left or right) to use when filling the remaining elements after creating k distinct differences. The logic depends on which pointer was used last in Phase 1, not just the parity of k.
Incorrect Implementation:
# Wrong: Always using left pointer for remaining elements
for i in range(k, n):
result.append(left)
left += 1
Why it fails: If k is even, the last element in Phase 1 came from the left pointer (since we start with i=0 which is even). Continuing with the left pointer would create a difference of 1, but then switching directions later could introduce unwanted distinct differences.
Correct Implementation:
for i in range(k, n):
if k % 2 == 0:
# k even means last pick was from left, so continue from right
result.append(right)
right -= 1
else:
# k odd means last pick was from right, so continue from left
result.append(left)
left += 1
2. Off-by-One Error in Loop Boundaries
Another common mistake is using incorrect loop boundaries, particularly confusing whether to iterate k times or k+1 times for the first phase.
Incorrect Implementation:
# Wrong: Including index k in the alternating phase
for i in range(k + 1): # This creates k+1 distinct differences!
if i % 2 == 0:
result.append(left)
left += 1
else:
result.append(right)
right -= 1
Why it fails: This creates k+1 positions with alternating pattern, potentially generating k+1 distinct differences instead of exactly k.
Correct Implementation:
# Iterate exactly k times to create k distinct differences
for i in range(k):
# alternating logic
3. Not Considering Edge Cases
Failing to handle edge cases like k=1 or k=n-1 can lead to incorrect results.
Problematic Assumption:
# Assuming we always need both phases
def constructArray(self, n: int, k: int) -> List[int]:
# ... Phase 1 logic ...
# Might fail if k == n-1 (no elements left for Phase 2)
Robust Solution: The provided solution naturally handles these cases:
- When k=1: Only one alternation happens, then all remaining elements are added sequentially
- When k=n-1: The entire array is built using alternation (Phase 2 loop doesn't execute)
4. Misunderstanding the Problem Requirements
Some might think they need to maximize or minimize differences, or that the differences must be consecutive integers (1, 2, 3, ..., k).
Clarification: The problem only requires exactly k distinct difference values - they don't need to be any specific values or in any specific pattern. The alternating approach naturally creates differences of approximately n-1, n-2, n-3, etc., which satisfies the requirement.
Which of the following is a min heap?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!