2612. Minimum Reverse Operations
Problem Description
In this problem, you are given an integer n
which stands for the length of an array arr
that is initialized with all zeros, except for a single position p
which is set to 1
. There is also an array called banned
that contains indexes in the array arr
which must always remain zero - these are the positions that the 1
cannot move to. You have the ability to perform reverse operations on any subarray of size k
, which flips the order of elements in that subarray. The challenge is to calculate the minimum number of such operations required to move the 1
from its initial position to every other position in the array that is not banned. If it's not possible to move the 1
to a specific position due to the restrictions imposed by the banned positions, the output for that position should be -1
. The result should be returned as an array ans
, where ans[i]
represents the minimum operations to move the 1
to position i
.
Flowchart Walkthrough
Let's determine the suitable algorithm to handle LeetCode 2612, Minimum Reverse Operations, using the decision process described in the flowchart:
-
Is it a graph?
- Yes: Although not explicitly stated as a graph problem, its structure, involving connections and reversals between states, can be modeled as a graph.
-
Is it a tree?
- No: The problem likely has multiple potential states and paths connecting them, unlike a tree's single parent-child path structure.
-
Is the problem related to directed acyclic graphs (DAGs)?
- No: The problem is about finding a minimum number of operations (or transitions), not managing dependencies or ordering.
-
Is the problem related to shortest paths?
- Yes: The goal to minimize the number of operations or reversals to convert one sequence into another suggests a shortest path search in state-space, where each state represents a string's configuration.
-
Is the graph weighted?
- No: Since each operation (or reversal) can be considered as one step or unit cost, it implies an unweighted scenario.
Conclusion: According to the flowchart, Breadth-First Search (BFS) is recommended for tackling unweighted shortest path problems. Thus, BFS is ideal for implementing a solution to LeetCode 2612 by treating each state as a node and each transition as an edge in an unweighted graph.
Intuition
To approach the solution for this problem, we need to realize that when an index's element gets its position reversed because of a subarray flip, it follows an arithmetic progression - specifically, every reverse operation changes its position by 2. Also, because an index only moves within a certain range around it when reversed, only indices with the same parity (i.e., both even or both odd) can be reached from a given index since we are incrementing or decrementing by 2.
Considering this, any index can transition to any other index of the same parity within the range [i - k + 1, i + k - 1]
. There are boundaries to these transitions, which are based on the edge of the array or the size of the flipping subarray. On the edges of the array, the reachable indices are limited. If we look at the left boundary, an element can at most be moved from index i
to k - i - 1
. Similarly, for the right boundary, an element can be moved to 2 * n - k - i - 1
.
With these constraints in mind, we use two ordered sets to track which even and odd indices can still be targeted by these flip operations, excluding the banned positions and the original position of the 1
. Then, by using Breadth-First Search (BFS), we sequentially flip subarrays to bring the 1
closer to the target positions. Each time we move 1
to a new position, we check all the indices it can now flip to within the defined boundaries, update their minimum operation count, add them to the queue for further processing, and remove them from the corresponding ordered set to avoid revisiting them.
As the BFS proceeds, it finds the shortest path (minimum flips) required to bring the 1
to each index by flipping through the array while avoiding banned positions. The BFS ensures that once we reach an index, we have done so with the minimal number of operations (since BFS always finds the shortest path first), and removing processed indices ensures we don't process the same index multiple times.
Learn more about Breadth-First Search patterns.
Solution Approach
The solution uses a combination of Breadth-First Search (BFS) algorithm and Ordered Sets (specifically a SortedSet from the sortedcontainers
package in Python). The algorithm iteratively explores the minimum steps required to get the 1
to each valid position in the array, while avoiding the banned indices.
Here's an overview of how the implementation works:
-
First, initialize the
ans
array to-1
, indicating that initially, we assume it's impossible to reach any position other than the starting positionp
. Setans[p]
to0
, as we're already at positionp
with no operations. -
Create two Ordered Sets (one for even indices and another for odd indices) to leverage their properties of maintaining elements in a sorted order and to allow for efficient insertions, deletions, and lookups. This is essential for our problem to quickly determine the next index to consider for a flip operation.
-
Remove any banned indices and the starting index
p
from the corresponding Ordered Sets, ensuring they are not accidentally traversed during the process. -
Add an extra index (
n
, the length of the array) to both Ordered Sets so we can correctly handle subarray selections near the edge of the array without running out of bounds. -
Initialize a queue with the starting index
p
where BFS will start its exploration. -
The BFS loop continues until there are no more indices to explore. In each iteration:
-
Pop the front index
i
from the queue. -
Calculate the minimum (
mi
) and maximum (mx
) indices that can be reached by a flip operation from the current indexi
within the boundaries of the array and the sizek
of the subarray. -
Retrieve the relevant SortedSet (
s
) for the parity of the indices we are processing, whether even or odd. -
Determine the range of valid indices that could be flipped to from
i
, which are the indices with the same parity, withinmi
andmx
. -
Utilize the
bisect_left
method available in the SortedSet to find the smallest indexj
that is greater than or equal tomi
. This gives us the starting index to explore from within the valid range. -
Iterate over the SortedSet to flip each valid index within the range, updating
ans[j]
to beans[i] + 1
each time (since reaching indexj
takes one more operation than it took to reach indexi
). -
For each flipped index
j
, add it to the BFS queue for further exploration, and remove it from the SortedSet to avoid duplication of work.
-
Once BFS completes, every non-banned index in the ans
array should contain the minimum number of flip operations required to bring the 1
there from position p
, or -1
if it is impossible due to the banned restrictions.
This algorithm ensures correctness due to the nature of BFS always finding the shortest path first and the intelligent handling of index parity which is inherent to the problem's operations. Ordered Sets make the implementation efficient by keeping indices in order and providing fast operations to manipulate them.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution approach using a small example:
Suppose n = 5
, which means our array arr
looks like: [0, 0, 0, 0, 0]
(indices are [0, 1, 2, 3, 4]
).
Let's assume the 1
starts at position p = 2
, thus arr
becomes: [0, 0, 1, 0, 0]
.
The banned
array contains the index 3
, so arr[3]
should always remain 0
.
We are allowed to reverse subarrays of size k = 3
.
With this setup, our goal is to move 1
to all other positions except 3
, while trying to minimize the number of operations (subarray reversals).
-
Initialize the
ans
array to [-1
,-1
,0
,-1
,-1
], indicating impossible positions with-1
except where the1
initially is. -
Define two SortedSets: one for even indices
{0, 4}
, and one for odd indices{1}
(we exclude 3 because it's banned and 2 because it's the starting position) and addn
to both (which becomes{0, 4, 5}
and{1, 5}
). -
Start BFS with the initial position
p = 2
in the queue. -
For the first iteration, dequeue
p = 2
:mi
(the minimum index we can reach from 2) is2 - 3 + 1 = 0
, andmx
(the maximum) is2 + 3 - 1 = 4
.- Since
p
is even, we use the even SortedSet{0, 4, 5}
, and find the valid range in the set is{0, 4}
. - We flip
0
and4
, updatingans
to:[1, -1, 0, -1, 1]
(it takes 1 operation to reach these positions) and enqueue them for BFS while removing from the set:{5}
.
-
On the next BFS iterations, if we dequeue
0
:mi
from 0 is0
(since we can't go lower) andmx
is2
.- The odd SortedSet is
{1, 5}
, and the valid range is{1}
. - We flip
1
, updatingans
to:[1, 2, 0, -1, 1]
(it takes 2 operations to reach position1
from position0
) and enqueue1
for BFS while removing it from the set:{5}
.
-
We continue this process until all reachable positions are evaluated or the queue is empty.
At the end of the BFS, the ans
array will reflect the minimum operations required to move the 1
to each position, or -1
where it is not possible.
For our example, the final ans
array would be [1, 2, 0, -1, 1]
, showing that it's impossible to get to position 3
due to it being banned, and taking into account the minimum number of subarray reversal operations required to reach the other positions.
Solution Implementation
1from sortedcontainers import SortedSet
2from typing import List
3from collections import deque
4
5class Solution:
6 def min_reverse_operations(self, n: int, p: int, banned: List[int], k: int) -> List[int]:
7 # Initialize a list to hold the answer, with -1 for all indices.
8 # The position `p` is set to 0 as it requires 0 operations to reach from itself.
9 ans = [-1] * n
10 ans[p] = 0
11
12 # Create two SortedSet instances, one for even indices and one for odd indices.
13 even_odd_sets = [SortedSet() for _ in range(2)]
14
15 # Populate even_odd_sets with all possible indices, segregated by even and odd.
16 for i in range(n):
17 even_odd_sets[i % 2].add(i)
18
19 # Remove the initial position `p` and the banned positions from the respective set.
20 even_odd_sets[p % 2].discard(p)
21 for banned_position in banned:
22 even_odd_sets[banned_position % 2].discard(banned_position)
23
24 # Add a sentinel value (`n`) to both sets to avoid index-out-of-range during set operations.
25 even_odd_sets[0].add(n)
26 even_odd_sets[1].add(n)
27
28 # Create a queue and begin BFS with the initial position `p`.
29 queue = deque([p])
30 while queue:
31 current_pos = queue.popleft()
32
33 # Define the minimum and maximum indices we can move to from the current position.
34 min_index = max(current_pos - k + 1, k - current_pos - 1)
35 max_index = min(current_pos + k - 1, n * 2 - k - current_pos - 1)
36
37 # Select the set that contains the same parity indices as `min_index`.
38 current_set = even_odd_sets[min_index % 2]
39
40 # Find the leftmost index in the set that is greater than or equal to `min_index`.
41 next_index = current_set.bisect_left(min_index)
42
43 # Continue this process for all indices within the defined range (mi, mx].
44 while current_set[next_index] <= max_index:
45 # Append this index to the queue and update the answer for it.
46 queue.append(current_set[next_index])
47 ans[current_set[next_index]] = ans[current_pos] + 1
48
49 # Remove the current index from the set as we have found the minimum operations for it.
50 current_set.discard(current_set[next_index])
51
52 # Re-find the leftmost index in the set that is greater than or equal to `min_index`.
53 next_index = current_set.bisect_left(min_index)
54
55 # Return the completed answer list.
56 return ans
57
1class Solution {
2
3 public int[] minReverseOperations(int n, int start, int[] banned, int k) {
4 int[] result = new int[n]; // To store the minimum reverse operations
5
6 // Create two TreeSets to separate indices based on even and odd parity
7 TreeSet<Integer>[] treeSets = new TreeSet[]{new TreeSet<>(), new TreeSet<>()};
8
9 // Initialize the result array and populate the TreeSets with index positions
10 for (int i = 0; i < n; ++i) {
11 treeSets[i % 2].add(i);
12 result[i] = i == start ? 0 : -1; // Initialize with 0 for start position, -1 for others
13 }
14 // Remove the start position from its respective TreeSet as it is already counted.
15 treeSets[start % 2].remove(start);
16 // Remove the banned positions from their respective TreeSet.
17 for (int pos : banned) {
18 treeSets[pos % 2].remove(pos);
19 }
20 // Add 'n' to both TreeSets as a sentinel value
21 treeSets[0].add(n);
22 treeSets[1].add(n);
23
24 // Use a deque to perform a BFS-like algorithm
25 Deque<Integer> queue = new ArrayDeque<>();
26 queue.offer(start);
27 while (!queue.isEmpty()) {
28 int currentIndex = queue.poll();
29 // Calculate the minimum and maximum index we can move to during this step
30 int minIndex = Math.max(currentIndex - k + 1, k - currentIndex - 1);
31 int maxIndex = Math.min(currentIndex + k - 1, n * 2 - k - currentIndex - 1);
32 // Select the TreeSet with the same parity as the minIndex
33 var currentSet = treeSets[minIndex % 2];
34
35 // Iterate through the valid indices within the TreeSet
36 for (int nextIndex = currentSet.ceiling(minIndex); nextIndex <= maxIndex; nextIndex = currentSet.ceiling(minIndex)) {
37 // Add next index to queue and update result with the number of moves taken
38 queue.offer(nextIndex);
39 result[nextIndex] = result[currentIndex] + 1;
40 // Remove the index from the TreeSet as it's processed
41 currentSet.remove(nextIndex);
42 }
43 }
44 return result; // Return the array with the minimum reverse operations
45 }
46}
47
1#include <vector>
2#include <set>
3#include <queue>
4using namespace std;
5
6class Solution {
7public:
8 vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
9 vector<int> answer(n, -1); // Initialize the answer vector with -1
10 answer[p] = 0; // The start position requires 0 operations to reach
11 set<int> tiles[2]; // Create sets to hold positions for even and odd indices
12
13 // Populate the sets with all possible positions
14 for (int i = 0; i < n; ++i) {
15 tiles[i % 2].insert(i);
16 }
17
18 // Remove the start position from the appropriate set
19 tiles[p % 2].erase(p);
20
21 // Remove banned positions from the appropriate sets
22 for (int bannedPosition : banned) {
23 tiles[bannedPosition % 2].erase(bannedPosition);
24 }
25
26 // Insert a sentinel value of n to mark the end of each set
27 tiles[0].insert(n);
28 tiles[1].insert(n);
29
30 // Start the BFS with the start position
31 queue<int> positionsQueue{{p}};
32
33 while (!positionsQueue.empty()) {
34 // Take the front position from the queue
35 int currentPosition = positionsQueue.front();
36 positionsQueue.pop();
37
38 // Calculate minimum and maximum reachable indices from currentPosition
39 int minIndex = max(currentPosition - k + 1, k - currentPosition - 1);
40 int maxIndex = min(currentPosition + k - 1, n * 2 - k - currentPosition - 1);
41
42 // Select the set which corresponds to the parity of the starting index
43 auto& currentSet = tiles[minIndex % 2];
44
45 // Find the lower bound of positions we can jump to from the currentPosition
46 auto it = currentSet.lower_bound(minIndex);
47
48 // Iterate over all reachable positions and update their operation counts
49 while (*it <= maxIndex) {
50 int jumpPosition = *it;
51 answer[jumpPosition] = answer[currentPosition] + 1; // One more operation than currentPosition
52 positionsQueue.push(jumpPosition); // Add this position to the queue for further processing
53 it = currentSet.erase(it); // Remove the position from the set and move iterator to next element
54 }
55 }
56
57 // Return the final answer vector where each index has the minimum number of reverse operations needed
58 return answer;
59 }
60};
61
1type CompareFn<T> = (lhs: T, rhs: T) => number;
2
3let treeSize: number;
4let tree: RBTree<number>;
5let compareFn: CompareFn<number>;
6
7function initializeTreeSet(
8 collection: number[] | CompareFn<number> = [],
9 compare: CompareFn<number> = (l, r) => (l < r ? -1 : l > r ? 1 : 0),
10) {
11 if (typeof collection === 'function') {
12 compare = collection;
13 collection = [];
14 }
15 treeSize = 0;
16 compareFn = compare;
17 tree = new RBTree(compare);
18 for (const val of collection) {
19 treeSetAdd(val);
20 }
21}
22
23function treeSetSize(): number {
24 return treeSize;
25}
26
27function treeSetHas(val: number): boolean {
28 return !!treeFind(val);
29}
30
31// Add a new element to the tree set
32function treeSetAdd(val: number): boolean {
33 const successful = treeInsert(val);
34 treeSize += successful ? 1 : 0;
35 return successful;
36}
37
38// Remove an element from the tree set
39function treeSetDelete(val: number): boolean {
40 const successful = treeDeleteAll(val);
41 treeSize -= successful ? 1 : 0;
42 return successful;
43}
44
45// Find the lowest element greater than or equal to a given value
46function treeSetCeil(val: number): number | undefined {
47 let node = tree.root;
48 let higher: RBTreeNode<number> | null = null;
49 while (node) {
50 if (compareFn(node.data, val) >= 0) {
51 higher = node;
52 node = node.left;
53 } else {
54 node = node.right;
55 }
56 }
57 return higher?.data;
58}
59
60// Find the highest element lower than or equal to a given value
61function treeSetFloor(val: number): number | undefined {
62 let node = tree.root;
63 let lower: RBTreeNode<number> | null = null;
64 while (node) {
65 if (compareFn(val, node.data) >= 0) {
66 lower = node;
67 node = node.right;
68 } else {
69 node = node.left;
70 }
71 }
72 return lower?.data;
73}
74
75// Rest of the TreeSet functions...
76
Time and Space Complexity
The time complexity of the function mainly consists of these parts:
- Initial setup of the
ans
list, which takesO(n)
time as it creates a list of-1
of sizen
. - Creation of the two
SortedSet
instances, each insertion operation in aSortedSet
takesO(log n)
time and overall forn
elements (includingbanned
ones and the addedn
) it totals toO(m log m)
wherem
is the number of elements added to the sets. - The removal of elements from the
SortedSet
instances. There can be up ton
removal operations, and each removal operation takesO(log n)
time. Thus, the total for this part is alsoO(n log n)
. - The while loop which includes a deque popping operation which is
O(1)
per element. For theSortedSet
range queries and removals, the worst-case scenario would involve visiting each element once at a cost ofO(log n)
per access/removal operation, which sums up toO(n log n)
.
Given these parts, it is important to note the complexity O(m log m)
of inserting elements into the SortedSet
, which is dominated by the O(n log n)
operations since we're considering the input size as n
.
The space complexity of the function consists of:
- The
ans
list which takesO(n)
space. - The two
SortedSet
instances which can contain up ton
items each (initially all indexes of the array and then some are removed), thus takingO(n)
space. - The
deque
which in the worst-case can containn
elements, again taking upO(n)
space.
Putting this together, we get that the time complexity of the function is O(n \log n)
and the space complexity is O(n)
, which agrees with the provided reference answer.
Learn more about how to find time and space complexity quickly using problem constraints.
What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.
Recommended Readings
https algomonster s3 us east 2 amazonaws com cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!