201 lines
9.8 KiB
YAML
201 lines
9.8 KiB
YAML
title: Array With Elements Not Equal to Average of Neighbors
|
|
slug: array-with-elements-not-equal-to-average-of-neighbors
|
|
difficulty: medium
|
|
leetcode_id: 1968
|
|
leetcode_url: https://leetcode.com/problems/array-with-elements-not-equal-to-average-of-neighbors/
|
|
categories:
|
|
- arrays
|
|
- sorting
|
|
patterns:
|
|
- greedy
|
|
|
|
description: |
|
|
You are given a **0-indexed** array `nums` of **distinct** integers. You want to rearrange the elements in the array such that every element in the rearranged array is **not** equal to the **average** of its neighbors.
|
|
|
|
More formally, the rearranged array should have the property such that for every `i` in the range `1 <= i < nums.length - 1`, `(nums[i-1] + nums[i+1]) / 2` is **not** equal to `nums[i]`.
|
|
|
|
Return *any rearrangement of* `nums` *that meets the requirements*.
|
|
|
|
constraints: |
|
|
- `3 <= nums.length <= 10^5`
|
|
- `0 <= nums[i] <= 10^5`
|
|
- All elements in `nums` are **distinct**
|
|
|
|
examples:
|
|
- input: "nums = [1,2,3,4,5]"
|
|
output: "[1,2,4,5,3]"
|
|
explanation: "When i=1, nums[i] = 2, and the average of its neighbors is (1+4) / 2 = 2.5. When i=2, nums[i] = 4, and the average of its neighbors is (2+5) / 2 = 3.5. When i=3, nums[i] = 5, and the average of its neighbors is (4+3) / 2 = 3.5."
|
|
- input: "nums = [6,2,0,9,7]"
|
|
output: "[9,7,6,2,0]"
|
|
explanation: "When i=1, nums[i] = 7, and the average of its neighbors is (9+6) / 2 = 7.5. When i=2, nums[i] = 6, and the average of its neighbors is (7+2) / 2 = 4.5. When i=3, nums[i] = 2, and the average of its neighbors is (6+0) / 2 = 3."
|
|
|
|
explanation:
|
|
intuition: |
|
|
The key insight is recognising when an element equals the average of its neighbours. If `nums[i] = (nums[i-1] + nums[i+1]) / 2`, then rearranging gives us `2 * nums[i] = nums[i-1] + nums[i+1]`. This only happens when the middle element is **exactly between** its neighbours numerically.
|
|
|
|
Think of it like arranging people by height in a line: you want to avoid having anyone stand between two others where they're exactly the average height of those two. The simplest way to guarantee this? Create a **zigzag pattern** — make sure each person is either taller than both neighbours or shorter than both neighbours.
|
|
|
|
This is called a **wiggle arrangement**: `nums[0] < nums[1] > nums[2] < nums[3] > nums[4] ...` (or the opposite pattern). When every element is a local maximum or local minimum, it can never be the average of its neighbours because it's never *between* them — it's always above or below both.
|
|
|
|
The elegant solution: sort the array, then **interleave** elements from the smaller half and larger half. Placing small and large elements alternately guarantees the zigzag pattern.
|
|
|
|
approach: |
|
|
We solve this using a **Sort and Interleave** approach:
|
|
|
|
**Step 1: Sort the array**
|
|
|
|
- Sort `nums` in ascending order
|
|
- This separates the values into a "small half" and a "large half"
|
|
|
|
|
|
|
|
**Step 2: Split into two halves**
|
|
|
|
- Small half: elements from index `0` to `(n-1)/2` (ceiling division)
|
|
- Large half: elements from index `(n+1)/2` to end
|
|
- For odd-length arrays, the small half gets the extra element
|
|
|
|
|
|
|
|
**Step 3: Interleave the halves**
|
|
|
|
- Create the result by alternating: `small[0], large[0], small[1], large[1], ...`
|
|
- Place elements from the small half at even indices (0, 2, 4, ...)
|
|
- Place elements from the large half at odd indices (1, 3, 5, ...)
|
|
|
|
|
|
|
|
**Step 4: Return the result**
|
|
|
|
- The interleaved array guarantees no element equals the average of its neighbours
|
|
|
|
|
|
|
|
**Why does interleaving work?**
|
|
|
|
After sorting and splitting, every element in the large half is strictly greater than every element in the small half (since all elements are distinct). When we interleave:
|
|
- Each "small" element has "large" neighbours (both bigger)
|
|
- Each "large" element has "small" neighbours (both smaller)
|
|
|
|
An element can only be the average of its neighbours if it's between them. But in our arrangement, every element is either greater than both neighbours or less than both — never between.
|
|
|
|
common_pitfalls:
|
|
- title: Random Shuffling
|
|
description: |
|
|
A tempting approach is to randomly shuffle until you find a valid arrangement. While this might work for small inputs, it has no guarantee of termination and could take extremely long for larger arrays.
|
|
|
|
With `n = 10^5` elements and many possible arrangements, random shuffling is not a reliable algorithm.
|
|
wrong_approach: "Randomly shuffle and check validity"
|
|
correct_approach: "Use deterministic sort + interleave"
|
|
|
|
- title: Missing the Wiggle Pattern Insight
|
|
description: |
|
|
Without recognising that a wiggle pattern (local maxima/minima alternating) solves the problem, you might try complex approaches like checking each position individually or using backtracking.
|
|
|
|
The key mathematical insight: `nums[i] = avg(nums[i-1], nums[i+1])` means `nums[i]` is exactly between its neighbours. A wiggle pattern ensures every element is above or below both neighbours — never between.
|
|
wrong_approach: "Try to place elements one by one with backtracking"
|
|
correct_approach: "Ensure wiggle pattern via sort + interleave"
|
|
|
|
- title: Incorrect Half Split
|
|
description: |
|
|
When splitting the sorted array, be careful with odd-length arrays. If you split incorrectly, the interleaving won't work properly.
|
|
|
|
For `n = 5`: small half should be indices 0, 1, 2 (3 elements) and large half should be indices 3, 4 (2 elements). This ensures we have enough small elements for even positions.
|
|
wrong_approach: "Always split at n/2 without considering odd lengths"
|
|
correct_approach: "Use ceiling division for small half size"
|
|
|
|
key_takeaways:
|
|
- "**Wiggle sort pattern**: Arranging elements so each is a local max or min (alternating) prevents any element from being the average of neighbours"
|
|
- "**Sort + interleave technique**: Sorting and interleaving halves is a powerful way to create alternating high-low patterns"
|
|
- "**Mathematical insight**: `nums[i] = (a + b) / 2` only when `nums[i]` is exactly between `a` and `b` — wiggle patterns avoid this"
|
|
- "**Related problems**: Wiggle Sort, Wiggle Sort II, and Rearrange Array Elements by Sign use similar interleaving ideas"
|
|
|
|
time_complexity: "O(n log n). Dominated by the sorting step. The interleaving is O(n)."
|
|
space_complexity: "O(n). We create a new result array to store the interleaved elements."
|
|
|
|
solutions:
|
|
- approach_name: Sort and Interleave
|
|
is_optimal: true
|
|
code: |
|
|
def rearrange_array(nums: list[int]) -> list[int]:
|
|
# Sort to separate small and large values
|
|
nums.sort()
|
|
n = len(nums)
|
|
result = [0] * n
|
|
|
|
# Split into two halves
|
|
# Small half goes to even indices, large half to odd indices
|
|
small_idx = 0
|
|
large_idx = (n + 1) // 2 # Start of large half
|
|
|
|
for i in range(n):
|
|
if i % 2 == 0:
|
|
# Even index: place from small half
|
|
result[i] = nums[small_idx]
|
|
small_idx += 1
|
|
else:
|
|
# Odd index: place from large half
|
|
result[i] = nums[large_idx]
|
|
large_idx += 1
|
|
|
|
return result
|
|
explanation: |
|
|
**Time Complexity:** O(n log n) — Sorting dominates; interleaving is O(n).
|
|
|
|
**Space Complexity:** O(n) — We allocate a result array of size n.
|
|
|
|
By sorting and interleaving, we guarantee that elements at even indices (from the smaller half) are always less than their odd-index neighbours (from the larger half). This creates a wiggle pattern where no element can be the average of its neighbours.
|
|
|
|
- approach_name: In-Place Swap (Greedy)
|
|
is_optimal: false
|
|
code: |
|
|
def rearrange_array(nums: list[int]) -> list[int]:
|
|
n = len(nums)
|
|
|
|
# We want: nums[0] < nums[1] > nums[2] < nums[3] > ...
|
|
for i in range(1, n):
|
|
# At odd indices, we want a local max
|
|
if i % 2 == 1:
|
|
if nums[i] < nums[i - 1]:
|
|
nums[i], nums[i - 1] = nums[i - 1], nums[i]
|
|
# At even indices (except 0), we want a local min
|
|
else:
|
|
if nums[i] > nums[i - 1]:
|
|
nums[i], nums[i - 1] = nums[i - 1], nums[i]
|
|
|
|
return nums
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Single pass through the array.
|
|
|
|
**Space Complexity:** O(1) — Swaps are done in-place.
|
|
|
|
This greedy approach iterates once, swapping adjacent elements whenever they violate the wiggle pattern. At odd indices we want a local maximum (greater than previous), at even indices we want a local minimum (less than previous). While this is O(n) time, it modifies the input array. The sort + interleave approach is clearer and avoids mutation.
|
|
|
|
- approach_name: Sort Descending and Interleave
|
|
is_optimal: false
|
|
code: |
|
|
def rearrange_array(nums: list[int]) -> list[int]:
|
|
# Sort in descending order
|
|
nums.sort(reverse=True)
|
|
n = len(nums)
|
|
result = []
|
|
|
|
left = 0
|
|
right = n - 1
|
|
|
|
# Alternate between largest remaining and smallest remaining
|
|
while left <= right:
|
|
result.append(nums[left]) # Add large element
|
|
left += 1
|
|
if left <= right:
|
|
result.append(nums[right]) # Add small element
|
|
right -= 1
|
|
|
|
return result
|
|
explanation: |
|
|
**Time Complexity:** O(n log n) — Sorting dominates.
|
|
|
|
**Space Complexity:** O(n) — Result array storage.
|
|
|
|
This variation sorts descending and alternates picking from the front (large) and back (small). It achieves the same wiggle effect with a different interleaving pattern: large-small-large-small instead of small-large-small-large. Both are valid solutions.
|