[Medium] 969. Pancake Sorting
Given an array of integers arr, sort the array by performing a series of pancake flips.
In one pancake flip we do the following steps:
- Choose an integer
kwhere1 <= k <= arr.length. - Reverse the sub-array
arr[0...k-1](0-indexed).
For example, if arr = [3,2,1,4] and we performed a pancake flip choosing k = 3, we reverse the sub-array [3,2,1], so arr = [1,2,3,4].
Return an array of the k-values corresponding to a sequence of pancake flips that sort arr. Any valid answer that sorts the array within 10 * arr.length flips will be judged as correct.
Examples
Example 1:
Input: arr = [3,2,4,1]
Output: [4,2,4,3]
Explanation:
We perform 4 pancake flips, with k values [4,2,4,3]:
Starting state: arr = [3, 2, 4, 1]
After 1st flip (k=4): arr = [1, 4, 2, 3]
After 2nd flip (k=2): arr = [4, 1, 2, 3]
After 3rd flip (k=4): arr = [3, 2, 1, 4]
After 4th flip (k=3): arr = [1, 2, 3, 4]
Example 2:
Input: arr = [1,2,3]
Output: []
Explanation: The input is already sorted, so there is no need to flip anything.
Note that other answers, such as [3, 3], would also be accepted.
Constraints
1 <= arr.length <= 1001 <= arr[i] <= arr.length- All integers in
arrare unique (i.e.arris a permutation of the integers from1toarr.length).
Thinking Process
- Greedy Strategy: Place largest elements first, working from right to left
- First flip: Bring target element to front (if not already there)
- Second flip: Move target element to its correct position
- Greedy works when local optimal choices lead to global optimum.
- Often sort first to make the greedy choice obvious.
- Prove or sanity-check: would swapping two choices ever help?
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Sort + greedy (this problem) | O(n log n) | O(1) | Interval scheduling, assignment |
| Local greedy choice | O(n) | O(1) | Jump game, gas station |
| Greedy + heap | O(n log n) | O(n) | Merge streams, room allocation |
| Exchange argument | O(n) | O(1) | Prove greedy choice is safe |
Solution
Time Complexity: O(n²) - For each of n elements, we may need to search and flip
Space Complexity: O(1) excluding output array
The key insight is to use a greedy strategy: place the largest unsorted element in its correct position, then work on the next largest, and so on.
Solution: Greedy with Helper Functions
class Solution:
def flip(self, subarr, k):
i = 0
while i < k // 2:
tmp = subarr[i]
subarr[i] = subarr[k - i - 1]
subarr[k - i - 1] = tmp
i += 1
def find(self, arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i
return -1
def pancakeSort(self, arr):
rtn = []
for valueToSort in range(len(arr), 0, -1):
idx = self.find(arr, valueToSort)
if idx == valueToSort - 1:
continue
if idx != 0:
rtn.append(idx + 1)
self.flip(arr, idx + 1)
rtn.append(valueToSort)
self.flip(arr, valueToSort)
return rtn
Solution Explanation
Approach: Sort + greedy (this problem)
Key idea: 1. Greedy Strategy: Place largest elements first, working from right to left
How the code works:
- Greedy Strategy: Place largest elements first, working from right to left
- First flip: Bring target element to front (if not already there)
- Second flip: Move target element to its correct position
- Greedy works when local optimal choices lead to global optimum.
- Often sort first to make the greedy choice obvious.
- Prove or sanity-check: would swapping two choices ever help?
Walkthrough — input arr = [3,2,4,1], expected output [4,2,4,3]:
We perform 4 pancake flips, with k values [4,2,4,3]: Starting state: arr = [3, 2, 4, 1] After 1st flip (k=4): arr = [1, 4, 2, 3] After 2nd flip (k=2): arr = [4, 1, 2, 3] After 3rd flip (k=4): arr = [3, 2, 1, 4] After 4th flip (k=3): arr = [1, 2, 3, 4]
| Approach | Time | Space | Pros | Cons | |———-|——|——-|——|——| | Greedy with Linear Search | O(n²) | O(1) | Simple, clear | O(n) find per element | | STL max_element | O(n²) | O(1) | Cleaner code | Iterator complexity | | Position Map | O(n²) | O(n) | O(1) find | More complex |
Algorithm Breakdown
def pancakeSort(self, arr):
list[int> rtn
# Process from largest to smallest value
for(valueToSort = len(arr) valueToSort > 0 valueToSort -= 1) :
# Find current position of valueToSort
idx = find(arr, valueToSort)
# If already in correct position, skip
if(idx == valueToSort - 1) continue
# Step 1: Bring to front (if not already there)
if idx != 0:
rtn.append(idx + 1) # k is 1-indexed
flip(arr, idx + 1) # Reverse first (idx+1) elements
# Step 2: Move to correct position
rtn.append(valueToSort) # k = valueToSort
flip(arr, valueToSort) # Reverse first valueToSort elements
return rtn
Helper Functions
Flip Function:
def flip(self, subarr, k):
i = 0
while i < k / 2:
swap(subarr[i], subarr[k - i - 1])
i += 1
Reverses the first k elements by swapping elements from both ends.
Find Function:
def find(self, arr, target):
for(i = 0 i < len(arr) i += 1) :
if(arr[i] == target) return i
return -1 # Should never happen given constraints
Linear search to find the index of target value.
Complexity
| Approach | Time | Space | Pros | Cons | |———-|——|——-|——|——| | Greedy with Linear Search | O(n²) | O(1) | Simple, clear | O(n) find per element | | STL max_element | O(n²) | O(1) | Cleaner code | Iterator complexity | | Position Map | O(n²) | O(n) | O(1) find | More complex |
Implementation Details
Flip Operation
class Solution:
def pancakeSort(self, arr):
result = []
n = len(arr)
for size in range(n, 1, -1):
# Find index of maximum in unsorted portion
maxIdx = arr.index(max(arr[:size]))
if maxIdx == size - 1:
continue # Already in place
# Bring max to front
if maxIdx > 0:
result.append(maxIdx + 1)
arr[:maxIdx + 1] = reversed(arr[:maxIdx + 1])
# Move max to correct position
result.append(size)
arr[:size] = reversed(arr[:size])
return result
Why k / 2?
- We only need to swap up to the middle
- Swapping
iwithk-i-1handles both ends - When
i >= k/2, all pairs are swapped
Index Conversion
class Solution:
def pancakeSort(self, arr):
result = []
n = len(arr)
# Create position map: value -> index
pos = [0] * (n + 1)
for i in range(n):
pos[arr[i]] = i
for val in range(n, 0, -1):
idx = pos[val]
if idx == val - 1:
continue
if idx != 0:
result.append(idx + 1)
self.flip(arr, idx + 1, pos)
result.append(val)
self.flip(arr, val, pos)
return result
def flip(self, arr, k, pos):
i = 0
j = k - 1
while i < j:
arr[i], arr[j] = arr[j], arr[i]
pos[arr[i]] = i
pos[arr[j]] = j
i += 1
j -= 1
The problem uses 1-indexed k (flip first k elements), but arrays are 0-indexed.
Early Termination
def flip(self, subarr, k):
i = 0
while i < k / 2:
swap(subarr[i], subarr[k - i - 1])
i += 1
If element is already in correct position, skip both flips to optimize.
Common Mistakes
- Already sorted:
[1,2,3]→ return[] - Reverse sorted:
[3,2,1]→ requires flips - Single element:
[1]→ return[] - Element at front:
[4,1,2,3]→ skip first flip -
Element in place: Skip both flips
- Off-by-one errors: Using
idxinstead ofidx + 1for k - Wrong flip condition: Not checking if
idx != 0before first flip - Incorrect position check: Using
idx == valueToSortinstead ofidx == valueToSort - 1 - Missing continue: Not skipping when element is already in place
- Wrong loop direction: Processing smallest to largest instead of largest to smallest
Optimization Tips
- Skip Already Sorted: Check if element is in place before flipping
- Position Map: Use hash map for O(1) find instead of O(n) linear search
- Early Exit: If array becomes sorted, stop early
- STL Functions: Use
reverse()andmax_element()for cleaner code
Related Problems
- 324. Wiggle Sort II - Different sorting constraint
- 912. Sort an Array - General sorting
- 75. Sort Colors - Three-way partition
- 969. Pancake Sorting - This problem
Real-World Applications
- Network Routing: Reordering packets with limited operations
- Database Operations: Optimizing query execution order
- Game Theory: Puzzles and optimization problems
- Algorithm Design: Understanding constraint-based sorting
Pattern Recognition
This problem demonstrates the “Greedy Sorting with Constraints” pattern:
1. Identify target element (largest unsorted)
2. Bring to front (if needed)
3. Move to correct position
4. Repeat for next target
Similar problems:
- Sorting with limited operations
- Constraint-based optimization
- Greedy algorithms
Why Greedy Works
- Optimal Substructure: Placing largest element correctly doesn’t affect smaller elements
- Greedy Choice: Always placing largest unsorted element is optimal
- No Future Dependencies: Decisions don’t depend on future placements
- Constraint Satisfaction: Each flip brings us closer to sorted state
Pancake Sorting Theory
- Minimum Flips: Finding minimum flips is NP-hard
- Upper Bound: At most
2n - 3flips needed (proven) - Greedy Bound: This algorithm uses at most
2nflips - Optimal: For small arrays, can find optimal solution
This problem is a fun introduction to constraint-based sorting, demonstrating how greedy algorithms can solve seemingly complex problems with simple strategies.
Key Takeaways
- Greedy Strategy: Place largest elements first, working from right to left
- Two-Step Process:
- First flip: Bring target element to front (if not already there)
- Second flip: Move target element to its correct position
- Skip Optimization: If element is already in correct position, skip it
- Index Conversion: k is 1-indexed (flip first k elements), but arrays are 0-indexed
- Unique Values: Since all values are unique,
findalways returns a valid index
References
- LC 969: Pancake Sorting on LeetCode
- LeetCode Discuss — LC 969: Pancake Sorting
- LeetCode Editorial (may require premium)