3431. Minimum Unlocked Indices to Sort Nums 🔒
Problem Description
You have an array nums
containing only integers 1, 2, and 3, along with a binary array locked
of the same length. Each element in locked
is either 0 or 1.
The goal is to determine if you can sort nums
in ascending order using a specific type of swap operation. You can swap two adjacent elements at positions i
and i + 1
only if:
- The difference between them equals 1:
nums[i] - nums[i + 1] == 1
- The position
i
is unlocked:locked[i] == 0
This means you can only swap adjacent elements where the left element is exactly 1 greater than the right element, and the left position must be unlocked.
Initially, some positions may be locked (locked[i] == 1
). In one operation, you can unlock any single position by changing its corresponding locked[i]
from 1 to 0.
Your task is to find the minimum number of unlock operations needed to make it possible to sort nums
using the allowed swaps. If it's impossible to sort the array regardless of how many positions you unlock, return -1.
For example, if nums = [2, 1, 3]
and locked = [1, 0, 0]
, you would need to unlock position 0 to swap 2 and 1, making the array sortable with 1 operation.
Intuition
Let's think about what movements are possible with our swap rules. We can only swap adjacent elements when the left one is exactly 1 greater than the right one. This means:
- We can swap
(2, 1)
to get(1, 2)
- We can swap
(3, 2)
to get(2, 3)
- We cannot swap
(3, 1)
since the difference is 2, not 1
This constraint creates a critical insight: the number 3 can never "jump over" the number 1. If a 3 appears before a 1 in the array, there's no sequence of valid swaps that can move the 3 past the 1 to reach sorted order. This is our impossibility condition.
Now, assuming it's possible to sort (all 3s are after all 1s), which positions actually need to be unlocked?
Consider the sorted target: all 1s first, then all 2s, then all 3s. During the sorting process:
- Any 2 that appears before the last 1 will need to swap with 1s to move rightward
- Any 3 that appears before the last 2 will need to swap with 2s to move rightward
These swaps happen at specific positions. When a 2 needs to move past 1s, it will swap at positions between where the first 2 appears (first2
) and where the last 1 appears (last1
). Similarly, when a 3 needs to move past 2s, it will swap at positions between where the first 3 appears (first3
) and where the last 2 appears (last2
).
Therefore, any position in the ranges [first2, last1)
or [first3, last2)
must be unlocked for sorting to be possible. We simply count how many of these positions are currently locked (have locked[i] == 1
) - that's our minimum number of unlock operations needed.
Solution Approach
The implementation follows directly from our intuition about which positions need to be unlocked.
First, we traverse the array once to find the critical positions:
first2
: the first occurrence of number 2 (initialized ton
)first3
: the first occurrence of number 3 (initialized ton
)last1
: the last occurrence of number 1 (initialized to-1
)last2
: the last occurrence of number 2 (initialized to-1
)
for i, x in enumerate(nums):
if x == 1:
last1 = i
elif x == 2:
first2 = min(first2, i)
last2 = i
else: # x == 3
first3 = min(first3, i)
Next, we check the impossibility condition. If any 3 appears before the last 1, sorting is impossible:
if first3 < last1: return -1
Finally, we count how many positions need to be unlocked. A position i
needs to be unlocked if:
- It's currently locked (
locked[i] == 1
), AND - It falls within the critical ranges where swaps must occur:
[first2, last1)
: where 2s need to swap with 1s[first3, last2)
: where 3s need to swap with 2s
return sum(
st and (first2 <= i < last1 or first3 <= i < last2)
for i, st in enumerate(locked)
)
The expression st and (condition)
evaluates to True
(counts as 1) only when both st
(which is locked[i]
) is 1 and the position i
is in one of the critical ranges. This gives us the exact count of positions that are currently locked but need to be unlocked for sorting.
The time complexity is O(n)
for the single pass through the array, and the space complexity is O(1)
as we only use a few variables to track positions.
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 an example with nums = [2, 3, 1, 2]
and locked = [1, 1, 0, 0]
.
Step 1: Find critical positions
- Traverse the array:
- i=0, nums[0]=2:
first2 = 0
- i=1, nums[1]=3:
first3 = 1
- i=2, nums[2]=1:
last1 = 2
- i=3, nums[3]=2:
last2 = 3
- i=0, nums[0]=2:
Step 2: Check impossibility
- Is
first3 < last1
? Is1 < 2
? Yes! - Since 3 appears at position 1 before the last 1 at position 2, it's impossible to sort.
- Return
-1
Let's try another example where sorting is possible: nums = [2, 1, 2, 3]
and locked = [1, 0, 1, 0]
.
Step 1: Find critical positions
- i=0, nums[0]=2:
first2 = 0
- i=1, nums[1]=1:
last1 = 1
- i=2, nums[2]=2:
last2 = 2
- i=3, nums[3]=3:
first3 = 3
Step 2: Check impossibility
- Is
first3 < last1
? Is3 < 1
? No, so sorting is possible.
Step 3: Identify critical ranges
- Range where 2s swap with 1s:
[first2, last1)
=[0, 1)
- Range where 3s swap with 2s:
[first3, last2)
=[3, 2)
(empty range since 3 > 2)
Step 4: Count locked positions in critical ranges
- Position 0:
locked[0] = 1
, in range[0, 1)
? Yes → needs unlocking - Position 1:
locked[1] = 0
, already unlocked - Position 2:
locked[2] = 1
, in critical ranges? No - Position 3:
locked[3] = 0
, already unlocked
Result: We need to unlock 1 position (position 0).
To verify: After unlocking position 0, we can swap positions 0 and 1 (since nums[0] - nums[1] = 2 - 1 = 1
), getting [1, 2, 2, 3]
which is sorted!
Solution Implementation
1class Solution:
2 def minUnlockedIndices(self, nums: List[int], locked: List[int]) -> int:
3 """
4 Counts the minimum number of unlocked indices based on the positions
5 of values 1, 2, and 3 in the nums array and the locked status.
6
7 Args:
8 nums: List of integers containing values 1, 2, or 3
9 locked: List of booleans indicating if each index is locked
10
11 Returns:
12 Minimum count of unlocked indices, or -1 if invalid configuration
13 """
14 n = len(nums)
15
16 # Initialize position trackers
17 first_occurrence_of_2 = n # First index where value 2 appears
18 first_occurrence_of_3 = n # First index where value 3 appears
19 last_occurrence_of_1 = -1 # Last index where value 1 appears
20 last_occurrence_of_2 = -1 # Last index where value 2 appears
21
22 # Scan through nums to find first and last occurrences
23 for index, value in enumerate(nums):
24 if value == 1:
25 # Update last occurrence of 1
26 last_occurrence_of_1 = index
27 elif value == 2:
28 # Update first and last occurrences of 2
29 first_occurrence_of_2 = min(first_occurrence_of_2, index)
30 last_occurrence_of_2 = index
31 else: # value == 3
32 # Update first occurrence of 3
33 first_occurrence_of_3 = min(first_occurrence_of_3, index)
34
35 # Check validity: first 3 must not appear before last 1
36 if first_occurrence_of_3 < last_occurrence_of_1:
37 return -1
38
39 # Count locked indices that fall within specific ranges
40 # Range 1: [first_2, last_1) or Range 2: [first_3, last_2)
41 count = 0
42 for index, is_locked in enumerate(locked):
43 if is_locked:
44 # Check if this locked index falls in either valid range
45 in_range_1 = first_occurrence_of_2 <= index < last_occurrence_of_1
46 in_range_2 = first_occurrence_of_3 <= index < last_occurrence_of_2
47 if in_range_1 or in_range_2:
48 count += 1
49
50 return count
51
1class Solution {
2 public int minUnlockedIndices(int[] nums, int[] locked) {
3 int n = nums.length;
4
5 // Track first occurrences of values 2 and 3
6 int firstIndexOfTwo = n;
7 int firstIndexOfThree = n;
8
9 // Track last occurrences of values 1 and 2
10 int lastIndexOfOne = -1;
11 int lastIndexOfTwo = -1;
12
13 // Find the positions of special values in the array
14 for (int i = 0; i < n; i++) {
15 if (nums[i] == 1) {
16 lastIndexOfOne = i;
17 } else if (nums[i] == 2) {
18 firstIndexOfTwo = Math.min(firstIndexOfTwo, i);
19 lastIndexOfTwo = i;
20 } else if (nums[i] == 3) {
21 firstIndexOfThree = Math.min(firstIndexOfThree, i);
22 }
23 }
24
25 // Check validity: no 3 should appear before the last 1
26 if (firstIndexOfThree < lastIndexOfOne) {
27 return -1;
28 }
29
30 // Count locked indices that need to be unlocked
31 int unlockedCount = 0;
32 for (int i = 0; i < n; i++) {
33 // Only consider indices that are currently locked
34 if (locked[i] == 1) {
35 // Check if index falls within the critical ranges:
36 // Range 1: between first 2 (inclusive) and last 1 (exclusive)
37 // Range 2: between first 3 (inclusive) and last 2 (exclusive)
38 boolean inFirstRange = (firstIndexOfTwo <= i && i < lastIndexOfOne);
39 boolean inSecondRange = (firstIndexOfThree <= i && i < lastIndexOfTwo);
40
41 if (inFirstRange || inSecondRange) {
42 unlockedCount++;
43 }
44 }
45 }
46
47 return unlockedCount;
48 }
49}
50
1class Solution {
2public:
3 int minUnlockedIndices(vector<int>& nums, vector<int>& locked) {
4 int n = nums.size();
5
6 // Track the first occurrence of value 2 and 3
7 int firstPosOfTwo = n;
8 int firstPosOfThree = n;
9
10 // Track the last occurrence of value 1 and 2
11 int lastPosOfOne = -1;
12 int lastPosOfTwo = -1;
13
14 // Single pass to find all required positions
15 for (int i = 0; i < n; ++i) {
16 if (nums[i] == 1) {
17 lastPosOfOne = i;
18 }
19 else if (nums[i] == 2) {
20 firstPosOfTwo = min(firstPosOfTwo, i);
21 lastPosOfTwo = i;
22 }
23 else if (nums[i] == 3) {
24 firstPosOfThree = min(firstPosOfThree, i);
25 }
26 }
27
28 // Check validity: if any 3 appears before the last 1, return -1
29 if (firstPosOfThree < lastPosOfOne) {
30 return -1;
31 }
32
33 // Count locked indices that satisfy the unlocking conditions
34 int unlockedCount = 0;
35 for (int i = 0; i < n; ++i) {
36 // Only consider locked positions (locked[i] == 1)
37 if (locked[i] == 1) {
38 // Check if index i falls within either of the two ranges:
39 // Range 1: between first 2 and last 1 (inclusive-exclusive)
40 // Range 2: between first 3 and last 2 (inclusive-exclusive)
41 bool inRangeOne = (firstPosOfTwo <= i && i < lastPosOfOne);
42 bool inRangeTwo = (firstPosOfThree <= i && i < lastPosOfTwo);
43
44 if (inRangeOne || inRangeTwo) {
45 ++unlockedCount;
46 }
47 }
48 }
49
50 return unlockedCount;
51 }
52};
53
1/**
2 * Finds the minimum number of indices that need to be unlocked based on the given conditions.
3 * @param nums - Array of numbers containing values 1, 2, or 3
4 * @param locked - Array indicating whether each index is locked (1) or unlocked (0)
5 * @returns The minimum number of indices to unlock, or -1 if invalid configuration
6 */
7function minUnlockedIndices(nums: number[], locked: number[]): number {
8 const n: number = nums.length;
9
10 // Track the first occurrence of value 2 and 3
11 let firstIndexOfTwo: number = n;
12 let firstIndexOfThree: number = n;
13
14 // Track the last occurrence of value 1 and 2
15 let lastIndexOfOne: number = -1;
16 let lastIndexOfTwo: number = -1;
17
18 // Find first and last occurrences of each value
19 for (let i: number = 0; i < n; i++) {
20 if (nums[i] === 1) {
21 lastIndexOfOne = i;
22 } else if (nums[i] === 2) {
23 firstIndexOfTwo = Math.min(firstIndexOfTwo, i);
24 lastIndexOfTwo = i;
25 } else if (nums[i] === 3) {
26 firstIndexOfThree = Math.min(firstIndexOfThree, i);
27 }
28 }
29
30 // Check if configuration is valid: no 3 should appear before any 1
31 if (firstIndexOfThree < lastIndexOfOne) {
32 return -1;
33 }
34
35 // Count locked indices that need to be unlocked based on position constraints
36 let unlockedCount: number = 0;
37 for (let i: number = 0; i < n; i++) {
38 // Check if current index is locked and falls within critical ranges
39 const isInRangeBetweenTwoAndOne: boolean = firstIndexOfTwo <= i && i < lastIndexOfOne;
40 const isInRangeBetweenThreeAndTwo: boolean = firstIndexOfThree <= i && i < lastIndexOfTwo;
41
42 if (locked[i] === 1 && (isInRangeBetweenTwoAndOne || isInRangeBetweenThreeAndTwo)) {
43 unlockedCount++;
44 }
45 }
46
47 return unlockedCount;
48}
49
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array nums
. The algorithm makes a single pass through the nums
array in the first loop to find the positions of first occurrence of 2 and 3, and last occurrences of 1 and 2. Then it makes another single pass through the locked
array (which has the same length n
) in the sum comprehension. Both operations are linear, resulting in O(n) + O(n) = O(n)
total time complexity.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space to store the variables n
, first2
, first3
, last1
, and last2
, regardless of the input size. The loop variables i
and x
also use constant space. No additional data structures that scale with input size are created.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Swap Condition Direction
Pitfall: A common mistake is misinterpreting which element can swap with which. The condition nums[i] - nums[i + 1] == 1
means we can only swap when the left element is exactly 1 greater than the right element (e.g., [2,1] can swap to become [1,2], but [1,2] cannot swap back).
Why this matters: This directional constraint means:
- 1s can only move right (by being swapped over)
- 3s can only move left (by being swapped over)
- 2s can move both directions depending on their neighbor
Solution: Always remember the swap is "bubble down" - larger values bubble down to the left when possible.
2. Incorrect Range Boundaries
Pitfall: Using inclusive ranges instead of exclusive for the upper bounds. The code uses [first2, last1)
and [first3, last2)
- note the exclusive upper bounds.
Why this happens: The position at last1
itself doesn't need to be unlocked because we're swapping element at position i
with i+1
. When a 1 is at position last1
, we need to unlock positions before it to allow 2s to swap past it.
Solution: Remember that unlocking position i
allows swapping elements at positions i
and i+1
. So to move element at position j
, you need positions [start, j)
to be unlockable.
3. Not Handling Edge Cases with No Occurrences
Pitfall: If there are no 2s or no 1s in the array, the initial values (first2 = n
, last1 = -1
) create an empty range [n, -1)
which could cause issues if not handled properly.
Why it works correctly: The range check first2 <= i < last1
naturally returns False
for all i
when first2 > last1
, so no positions are counted. This is the correct behavior since no swaps are needed.
Solution: The initialization values are carefully chosen:
first2 = n
andfirst3 = n
: Ensures empty ranges if these values don't existlast1 = -1
andlast2 = -1
: Ensures empty ranges if these values don't exist
4. Forgetting the Impossibility Check
Pitfall: Focusing only on counting locked positions without checking if sorting is even possible. If any 3 appears before the last 1, no amount of unlocking can make the array sortable.
Example: nums = [3, 2, 1]
, regardless of locked
status, this cannot be sorted because 3 needs to move right but can only swap leftward with 2s.
Solution: Always perform the impossibility check if first3 < last1: return -1
before counting unlocks. This saves unnecessary computation and correctly identifies unsolvable cases.
How many times is a tree node visited in a depth first search?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!