1144. Decrease Elements To Make Array Zigzag
Problem Description
You are given an array nums
of integers. In one move, you can choose any element and decrease it by 1.
A zigzag array is an array that satisfies one of these two patterns:
-
Even-indexed elements are peaks: Every element at an even index is greater than its adjacent elements. This creates a pattern like:
A[0] > A[1] < A[2] > A[3] < A[4] > ...
-
Odd-indexed elements are peaks: Every element at an odd index is greater than its adjacent elements. This creates a pattern like:
A[0] < A[1] > A[2] < A[3] > A[4] < ...
Your task is to find the minimum number of moves needed to transform the given array into a zigzag array.
For example, if nums = [1, 2, 3]
:
- To make even-indexed elements peaks: We need
nums[0] > nums[1]
andnums[2] > nums[1]
. The array[1, 0, 3]
works, requiring 2 moves (decreasingnums[1]
by 2). - To make odd-indexed elements peaks: We need
nums[1] > nums[0]
andnums[1] > nums[2]
. The array[1, 2, 1]
works, requiring 2 moves (decreasingnums[2]
by 2). - The minimum is 2 moves.
The solution considers both possible zigzag patterns and calculates the cost for each. For each pattern, it iterates through positions that should be "valleys" (smaller than neighbors) and calculates how much to decrease them to satisfy the zigzag property. The answer is the minimum cost between the two patterns.
Intuition
The key insight is that we can only decrease elements, never increase them. This constraint actually simplifies our problem significantly.
Since we want to create a zigzag pattern, we have exactly two choices:
- Make elements at even indices the "peaks" (larger than neighbors)
- Make elements at odd indices the "peaks" (larger than neighbors)
Here's the crucial observation: if we decide which positions should be peaks, then the other positions must be "valleys" (smaller than neighbors). Since we can only decrease values, the peaks should remain unchanged - we achieve the zigzag pattern by lowering the valleys.
For any valley position, we need to ensure it's smaller than both its neighbors. If the current value at a valley position is nums[j]
, and its neighbors are nums[j-1]
and nums[j+1]
, then we need:
nums[j] < nums[j-1]
, which means we need to decrease by at leastnums[j] - nums[j-1] + 1
ifnums[j] >= nums[j-1]
nums[j] < nums[j+1]
, which means we need to decrease by at leastnums[j] - nums[j+1] + 1
ifnums[j] >= nums[j+1]
We take the maximum of these requirements to satisfy both conditions simultaneously.
The greedy approach works because:
- We never need to decrease peak positions (doing so would only make things harder)
- For each valley, we decrease it by the minimum amount necessary to be smaller than its neighbors
- Each position's adjustment is independent - lowering one valley doesn't affect the requirements for other valleys
By calculating the total cost for both patterns (even peaks vs odd peaks) and taking the minimum, we find the optimal solution.
Learn more about Greedy patterns.
Solution Approach
The implementation uses enumeration and greedy strategy to find the minimum moves needed.
We maintain an array ans = [0, 0]
where:
ans[0]
stores the total moves needed when even-indexed elements are valleysans[1]
stores the total moves needed when odd-indexed elements are valleys
The outer loop for i in range(2)
handles both patterns:
- When
i = 0
: We process even indices (0, 2, 4, ...) as valleys - When
i = 1
: We process odd indices (1, 3, 5, ...) as valleys
For each pattern, we iterate through the valley positions using for j in range(i, n, 2)
. This clever iteration starts at index i
and jumps by 2 each time, visiting all positions that should be valleys.
At each valley position j
, we calculate the required decrease amount d
:
-
Left neighbor check: If
j > 0
, we neednums[j] < nums[j-1]
. If currentlynums[j] >= nums[j-1]
, we must decrease by at leastnums[j] - nums[j-1] + 1
. We updated = max(d, nums[j] - nums[j-1] + 1)
. -
Right neighbor check: If
j < n - 1
, we neednums[j] < nums[j+1]
. If currentlynums[j] >= nums[j+1]
, we must decrease by at leastnums[j] - nums[j+1] + 1
. We updated = max(d, nums[j] - nums[j+1] + 1)
.
The max
operation ensures we decrease enough to be smaller than both neighbors. If d
remains 0, it means the current element is already smaller than its neighbors, requiring no moves.
We accumulate the total moves for each pattern: ans[i] += d
.
Finally, we return min(ans)
to get the minimum moves between the two possible zigzag patterns.
The time complexity is O(n)
as we visit each element at most twice, and space complexity is O(1)
using only constant extra space.
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 example nums = [2, 7, 10, 9, 8, 9]
to illustrate the solution approach.
Pattern 1: Even indices as valleys (positions 0, 2, 4 should be smaller than neighbors)
-
Position 0 (value = 2):
- No left neighbor
- Right neighbor at position 1 has value 7
- Need:
nums[0] < nums[1]
β2 < 7
β Already satisfied - Decrease needed: 0
-
Position 2 (value = 10):
- Left neighbor at position 1 has value 7
- Right neighbor at position 3 has value 9
- Need:
nums[2] < nums[1]
β10 < 7
β Must decrease by10 - 7 + 1 = 4
- Need:
nums[2] < nums[3]
β10 < 9
β Must decrease by10 - 9 + 1 = 2
- Decrease needed:
max(4, 2) = 4
-
Position 4 (value = 8):
- Left neighbor at position 3 has value 9
- Right neighbor at position 5 has value 9
- Need:
nums[4] < nums[3]
β8 < 9
β Already satisfied - Need:
nums[4] < nums[5]
β8 < 9
β Already satisfied - Decrease needed: 0
Total moves for Pattern 1: 0 + 4 + 0 = 4
Result: [2, 7, 6, 9, 8, 9]
(peak-valley-peak-valley-peak pattern)
Pattern 2: Odd indices as valleys (positions 1, 3, 5 should be smaller than neighbors)
-
Position 1 (value = 7):
- Left neighbor at position 0 has value 2
- Right neighbor at position 2 has value 10
- Need:
nums[1] < nums[0]
β7 < 2
β Must decrease by7 - 2 + 1 = 6
- Need:
nums[1] < nums[2]
β7 < 10
β Already satisfied - Decrease needed:
max(6, 0) = 6
-
Position 3 (value = 9):
- Left neighbor at position 2 has value 10
- Right neighbor at position 4 has value 8
- Need:
nums[3] < nums[2]
β9 < 10
β Already satisfied - Need:
nums[3] < nums[4]
β9 < 8
β Must decrease by9 - 8 + 1 = 2
- Decrease needed:
max(0, 2) = 2
-
Position 5 (value = 9):
- Left neighbor at position 4 has value 8
- No right neighbor
- Need:
nums[5] < nums[4]
β9 < 8
β Must decrease by9 - 8 + 1 = 2
- Decrease needed: 2
Total moves for Pattern 2: 6 + 2 + 2 = 10
Result: [2, 1, 10, 7, 8, 7]
(valley-peak-valley-peak-valley pattern)
Final Answer: min(4, 10) = 4
moves
The optimal solution transforms the array to [2, 7, 6, 9, 8, 9]
using 4 moves by making even indices valleys.
Solution Implementation
1class Solution:
2 def movesToMakeZigzag(self, nums: List[int]) -> int:
3 """
4 Find minimum moves to make array zigzag pattern.
5 A zigzag array satisfies either:
6 - nums[0] > nums[1] < nums[2] > nums[3] < ... (even indices are peaks)
7 - nums[0] < nums[1] > nums[2] < nums[3] > ... (odd indices are peaks)
8
9 Args:
10 nums: List of integers to be modified
11
12 Returns:
13 Minimum number of moves (decrements) needed
14 """
15 # Store moves needed for each pattern:
16 # moves_needed[0]: moves to make even indices peaks
17 # moves_needed[1]: moves to make odd indices peaks
18 moves_needed = [0, 0]
19 n = len(nums)
20
21 # Try both patterns
22 for pattern_type in range(2):
23 # Process indices that should be peaks in current pattern
24 # pattern_type=0: process even indices (0, 2, 4, ...)
25 # pattern_type=1: process odd indices (1, 3, 5, ...)
26 for peak_index in range(pattern_type, n, 2):
27 # Calculate decrease needed for neighbors to be smaller than current peak
28 decrease_amount = 0
29
30 # Check left neighbor if it exists
31 if peak_index > 0:
32 # If left neighbor >= current, need to decrease current
33 # by (neighbor_value - current_value + 1) to make current > neighbor
34 decrease_amount = max(decrease_amount, nums[peak_index] - nums[peak_index - 1] + 1)
35
36 # Check right neighbor if it exists
37 if peak_index < n - 1:
38 # If right neighbor >= current, need to decrease current
39 # by (neighbor_value - current_value + 1) to make current > neighbor
40 decrease_amount = max(decrease_amount, nums[peak_index] - nums[peak_index + 1] + 1)
41
42 # Add total decrease needed for this index
43 moves_needed[pattern_type] += decrease_amount
44
45 # Return minimum moves between the two patterns
46 return min(moves_needed)
47
1class Solution {
2 public int movesToMakeZigzag(int[] nums) {
3 // Store the number of moves needed for each strategy:
4 // movesNeeded[0]: moves to make even-indexed elements smaller (peaks at odd indices)
5 // movesNeeded[1]: moves to make odd-indexed elements smaller (peaks at even indices)
6 int[] movesNeeded = new int[2];
7 int arrayLength = nums.length;
8
9 // Try both strategies: starting from index 0 (even) and index 1 (odd)
10 for (int startIndex = 0; startIndex < 2; startIndex++) {
11 // Process elements at positions: startIndex, startIndex+2, startIndex+4, ...
12 // These are the elements we want to make smaller than their neighbors
13 for (int currentIndex = startIndex; currentIndex < arrayLength; currentIndex += 2) {
14 int decrementNeeded = 0;
15
16 // Check left neighbor: ensure current element is smaller than left neighbor
17 if (currentIndex > 0) {
18 // Calculate how much to decrease current element to be smaller than left neighbor
19 decrementNeeded = Math.max(decrementNeeded, nums[currentIndex] - nums[currentIndex - 1] + 1);
20 }
21
22 // Check right neighbor: ensure current element is smaller than right neighbor
23 if (currentIndex < arrayLength - 1) {
24 // Calculate how much to decrease current element to be smaller than right neighbor
25 decrementNeeded = Math.max(decrementNeeded, nums[currentIndex] - nums[currentIndex + 1] + 1);
26 }
27
28 // Accumulate the total moves needed for this strategy
29 movesNeeded[startIndex] += decrementNeeded;
30 }
31 }
32
33 // Return the minimum moves between the two strategies
34 return Math.min(movesNeeded[0], movesNeeded[1]);
35 }
36}
37
1class Solution {
2public:
3 int movesToMakeZigzag(vector<int>& nums) {
4 // Store the number of moves needed for two scenarios:
5 // moves[0]: make even-indexed elements smaller than their neighbors
6 // moves[1]: make odd-indexed elements smaller than their neighbors
7 vector<int> moves(2);
8 int n = nums.size();
9
10 // Try both scenarios: starting with even indices (i=0) and odd indices (i=1)
11 for (int startIndex = 0; startIndex < 2; ++startIndex) {
12 // Process elements at positions startIndex, startIndex+2, startIndex+4, ...
13 for (int currentPos = startIndex; currentPos < n; currentPos += 2) {
14 int requiredDecrease = 0;
15
16 // Check if current element needs to be decreased to be smaller than left neighbor
17 if (currentPos > 0) {
18 requiredDecrease = max(requiredDecrease, nums[currentPos] - nums[currentPos - 1] + 1);
19 }
20
21 // Check if current element needs to be decreased to be smaller than right neighbor
22 if (currentPos < n - 1) {
23 requiredDecrease = max(requiredDecrease, nums[currentPos] - nums[currentPos + 1] + 1);
24 }
25
26 // Add the required decrease for this position to the total moves
27 moves[startIndex] += requiredDecrease;
28 }
29 }
30
31 // Return the minimum number of moves between the two scenarios
32 return min(moves[0], moves[1]);
33 }
34};
35
1/**
2 * Calculates minimum moves to make array zigzag pattern
3 * A zigzag array satisfies either:
4 * - nums[0] > nums[1] < nums[2] > nums[3] < ... (even indices are peaks)
5 * - nums[0] < nums[1] > nums[2] < nums[3] > ... (odd indices are peaks)
6 *
7 * @param nums - Input array of numbers
8 * @returns Minimum number of moves needed to make zigzag pattern
9 */
10function movesToMakeZigzag(nums: number[]): number {
11 // Store the total moves needed for each strategy:
12 // strategyMoves[0]: make even indices as peaks
13 // strategyMoves[1]: make odd indices as peaks
14 const strategyMoves: number[] = Array(2).fill(0);
15 const arrayLength: number = nums.length;
16
17 // Try both strategies: starting with even indices (i=0) and odd indices (i=1)
18 for (let strategyIndex = 0; strategyIndex < 2; ++strategyIndex) {
19 // Process positions based on current strategy (even or odd indices)
20 for (let currentPosition = strategyIndex; currentPosition < arrayLength; currentPosition += 2) {
21 let decrementNeeded: number = 0;
22
23 // Check if we need to decrease left neighbor
24 if (currentPosition > 0) {
25 // Calculate how much to decrease left neighbor to make it smaller than current
26 decrementNeeded = Math.max(decrementNeeded, nums[currentPosition] - nums[currentPosition - 1] + 1);
27 }
28
29 // Check if we need to decrease right neighbor
30 if (currentPosition < arrayLength - 1) {
31 // Calculate how much to decrease right neighbor to make it smaller than current
32 decrementNeeded = Math.max(decrementNeeded, nums[currentPosition] - nums[currentPosition + 1] + 1);
33 }
34
35 // Accumulate the moves needed for this strategy
36 strategyMoves[strategyIndex] += decrementNeeded;
37 }
38 }
39
40 // Return the minimum moves between the two strategies
41 return Math.min(...strategyMoves);
42}
43
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array nums
. The algorithm uses two nested loops where the outer loop runs exactly 2 iterations (for i
in range(2)), and the inner loop iterates through the array with step size 2. For each value of i
, the inner loop visits approximately n/2
elements. Therefore, the total number of operations is 2 * (n/2) = n
, resulting in linear time complexity.
The space complexity is O(1)
. The algorithm only uses a fixed amount of extra space: the list ans
with exactly 2 elements, and a few integer variables (n
, i
, j
, d
). The space usage does not grow with the input size, making it constant space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding Which Elements to Modify
A critical pitfall is misunderstanding which elements should be decreased. The code appears to decrease elements at "peak" positions, but it's actually decreasing elements at "valley" positions.
The Bug: The variable naming and comments are misleading. When pattern_type = 0
, we're actually processing even indices as valleys (not peaks), meaning we want even indices to be smaller than their neighbors.
Incorrect Understanding:
# Wrong interpretation: "Making even indices peaks"
for peak_index in range(pattern_type, n, 2): # Misleading name!
decrease_amount = 0
# This logic actually ensures current element becomes SMALLER than neighbors
Correct Understanding:
# Correct: Making even indices valleys (odd indices become peaks)
for valley_index in range(pattern_type, n, 2):
decrease_needed = 0
# Ensure valley is smaller than both neighbors
if valley_index > 0:
# Need: nums[valley] < nums[valley-1]
if nums[valley_index] >= nums[valley_index - 1]:
decrease_needed = max(decrease_needed, nums[valley_index] - nums[valley_index - 1] + 1)
2. Incorrect Decrease Calculation
Another pitfall is calculating the wrong decrease amount. The code calculates how much to decrease the current element to be smaller than neighbors, not how much to decrease the neighbors.
Example to Illustrate:
- Array:
[2, 5, 3]
- To make index 1 a valley: We need
nums[1] < nums[0]
andnums[1] < nums[2]
- Current:
nums[1] = 5
,nums[0] = 2
,nums[2] = 3
- Decrease needed:
max(5 - 2 + 1, 5 - 3 + 1) = max(4, 3) = 4
- Result: Decrease
nums[1]
by 4 to get[2, 1, 3]
3. Pattern Confusion
The two patterns being tested are:
- Pattern 0: Even indices are valleys β Odd indices are peaks β
[small, LARGE, small, LARGE, ...]
- Pattern 1: Odd indices are valleys β Even indices are peaks β
[LARGE, small, LARGE, small, ...]
Corrected Solution with Clear Naming:
class Solution:
def movesToMakeZigzag(self, nums: List[int]) -> int:
"""
Find minimum moves to make array zigzag pattern.
Two possible patterns:
1. Even valleys: nums[0] < nums[1] > nums[2] < nums[3] > ...
2. Odd valleys: nums[0] > nums[1] < nums[2] > nums[3] < ...
"""
moves = [0, 0] # [moves for even valleys, moves for odd valleys]
n = len(nums)
for pattern in range(2):
# pattern=0: make even indices valleys (odd indices peaks)
# pattern=1: make odd indices valleys (even indices peaks)
for valley_idx in range(pattern, n, 2):
decrease = 0
# Check if valley needs to be smaller than left neighbor
if valley_idx > 0 and nums[valley_idx] >= nums[valley_idx - 1]:
decrease = max(decrease, nums[valley_idx] - nums[valley_idx - 1] + 1)
# Check if valley needs to be smaller than right neighbor
if valley_idx < n - 1 and nums[valley_idx] >= nums[valley_idx + 1]:
decrease = max(decrease, nums[valley_idx] - nums[valley_idx + 1] + 1)
moves[pattern] += decrease
return min(moves)
The key insight is that we're always decreasing elements at valley positions to ensure they're strictly smaller than their neighbors, not making peaks higher.
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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!