775. Global and Local Inversions
Problem Description
You are given an integer array nums
of length n
that contains a permutation of all integers from 0
to n-1
.
The problem asks you to compare two types of inversions in this array:
Global inversions: These are pairs of indices (i, j)
where:
i
comes beforej
in the array (0 <= i < j < n
)- The value at position
i
is greater than the value at positionj
(nums[i] > nums[j]
)
Local inversions: These are adjacent pairs where the first element is greater than the second:
- Valid indices are from
0
ton-2
(0 <= i < n - 1
) - The value at position
i
is greater than its immediate neighbor (nums[i] > nums[i + 1]
)
Your task is to determine whether the number of global inversions equals the number of local inversions. Return true
if they are equal, false
otherwise.
Note that every local inversion is also a global inversion (since adjacent elements satisfy the global inversion criteria), but not every global inversion is local. The key insight is that if there exists any global inversion that is not local (i.e., elements that are inverted but not adjacent), then the counts cannot be equal.
The solution cleverly checks if any element at position i
is greater than any element at position i+2
or beyond. It maintains the maximum value seen up to position i-2
and compares it with the current element at position i
. If such a non-local inversion exists, the function returns false
; otherwise, it returns true
.
Intuition
The key observation is that every local inversion (adjacent elements out of order) is automatically a global inversion. So if the number of global inversions equals the number of local inversions, it means there are no "extra" global inversions beyond the local ones.
In other words, we need to check if there exists any global inversion that is NOT a local inversion. If such an inversion exists, the counts cannot be equal.
What would a non-local global inversion look like? It would be a pair (i, j)
where j > i + 1
and nums[i] > nums[j]
. This means elements that are at least 2 positions apart are inverted.
Since the array is a permutation of [0, n-1]
, in an ideal permutation where global inversions equal local inversions, each element can only be displaced by at most 1 position from its original sorted position. If element nums[i]
is greater than nums[i+2]
, it means the element has moved too far from where it should be.
The solution approach becomes clear: for each position i
, we need to check if any element before position i-1
is greater than nums[i]
. To do this efficiently, we keep track of the maximum value among all elements up to position i-2
. If this maximum is ever greater than nums[i]
, we've found a non-local global inversion, and we return false
.
By maintaining mx = max(nums[0], nums[1], ..., nums[i-2])
and comparing it with nums[i]
, we can detect if there's any element at least 2 positions before that is greater than the current element, which would create a global inversion that isn't local.
Learn more about Math patterns.
Solution Approach
The implementation uses a single-pass algorithm with constant space complexity to solve the problem efficiently.
Algorithm Steps:
-
Initialize a maximum tracker: Start with
mx = 0
to keep track of the maximum value seen so far (excluding the immediate previous element). -
Iterate through the array starting from index 2: We begin at index 2 because we need to maintain a gap of at least 2 positions between the elements we're comparing.
-
Update and check the maximum: For each position
i
(starting from 2):- First, update
mx
to be the maximum of itself andnums[i-2]
. This ensuresmx
contains the maximum value from positions0
toi-2
. - Then check if
mx > nums[i]
. If true, we've found a non-local global inversion (an element at least 2 positions before is greater than the current element).
- First, update
-
Return the result:
- If we find any non-local global inversion during the iteration, immediately return
false
. - If we complete the iteration without finding any, return
true
.
- If we find any non-local global inversion during the iteration, immediately return
Implementation Details:
def isIdealPermutation(self, nums: List[int]) -> bool:
mx = 0
for i in range(2, len(nums)):
if (mx := max(mx, nums[i - 2])) > nums[i]:
return False
return True
The code uses the walrus operator :=
to update mx
and check the condition in a single line. This is equivalent to:
mx = max(mx, nums[i - 2])
if mx > nums[i]:
return False
Time and Space Complexity:
- Time Complexity:
O(n)
wheren
is the length of the array, as we traverse the array once. - Space Complexity:
O(1)
as we only use a single variablemx
to track the maximum value.
This approach is optimal because it solves the problem in a single pass without needing to count all inversions explicitly.
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 solution with a concrete example to understand how it detects non-local global inversions.
Example 1: nums = [1, 0, 2]
- Step 1: Initialize
mx = 0
- Step 2: Start loop at
i = 2
- Update
mx = max(0, nums[0]) = max(0, 1) = 1
- Check if
mx > nums[2]
: Is1 > 2
? No
- Update
- Step 3: Loop ends, return
true
This array has only one inversion: (0,1)
where nums[0]=1 > nums[1]=0
, which is a local inversion. Since all local inversions are also global inversions, the counts are equal.
Example 2: nums = [2, 0, 1]
- Step 1: Initialize
mx = 0
- Step 2: Start loop at
i = 2
- Update
mx = max(0, nums[0]) = max(0, 2) = 2
- Check if
mx > nums[2]
: Is2 > 1
? Yes! - Found a non-local global inversion between positions 0 and 2
- Update
- Step 3: Return
false
This array has global inversions: (0,1)
where 2>0
, (0,2)
where 2>1
, and (1,2)
where 0<1
(not an inversion). It has only one local inversion: (0,1)
. Since (0,2)
is a global inversion but not local (elements are 2 positions apart), the counts differ.
Example 3: nums = [0, 2, 1, 3]
- Step 1: Initialize
mx = 0
- Step 2: Loop from
i = 2
toi = 3
- At
i = 2
:- Update
mx = max(0, nums[0]) = max(0, 0) = 0
- Check if
mx > nums[2]
: Is0 > 1
? No
- Update
- At
i = 3
:- Update
mx = max(0, nums[1]) = max(0, 2) = 2
- Check if
mx > nums[3]
: Is2 > 3
? No
- Update
- At
- Step 3: Loop ends, return
true
This array has one local inversion (1,2)
where 2>1
, which is also the only global inversion. The algorithm correctly identifies no non-local global inversions exist.
The key insight: By maintaining the maximum of all elements at least 2 positions before the current element, we can efficiently detect if any "far away" element creates a global inversion that isn't local.
Solution Implementation
1class Solution:
2 def isIdealPermutation(self, nums: List[int]) -> bool:
3 """
4 Check if every global inversion is also a local inversion.
5
6 A local inversion occurs when nums[i] > nums[i+1] (adjacent elements).
7 A global inversion occurs when nums[i] > nums[j] for any i < j.
8
9 Key insight: If there exists nums[i] > nums[j] where j > i+1,
10 then we have a global inversion that is NOT a local inversion.
11
12 Args:
13 nums: List of integers representing a permutation
14
15 Returns:
16 bool: True if all global inversions are local inversions, False otherwise
17 """
18 # Track the maximum value seen so far (excluding the previous element)
19 max_value = 0
20
21 # Start from index 2 to ensure we can look back at nums[i-2]
22 for i in range(2, len(nums)):
23 # Update max_value to be the maximum of all elements before index i-1
24 # This ensures we're checking elements at least 2 positions apart
25 max_value = max(max_value, nums[i - 2])
26
27 # If any element at least 2 positions back is greater than current element,
28 # we have a global inversion that is not a local inversion
29 if max_value > nums[i]:
30 return False
31
32 # All global inversions are also local inversions
33 return True
34
1class Solution {
2 public boolean isIdealPermutation(int[] nums) {
3 // Track the maximum value seen so far (excluding the previous element)
4 int maxValueBeforePrevious = 0;
5
6 // Start from index 2 to ensure we have at least 2 elements before current position
7 for (int i = 2; i < nums.length; i++) {
8 // Update max with the element that is 2 positions behind current
9 maxValueBeforePrevious = Math.max(maxValueBeforePrevious, nums[i - 2]);
10
11 // If any element at least 2 positions back is greater than current element,
12 // we have a global inversion that is not a local inversion
13 if (maxValueBeforePrevious > nums[i]) {
14 return false;
15 }
16 }
17
18 // All global inversions are local inversions
19 return true;
20 }
21}
22
1class Solution {
2public:
3 bool isIdealPermutation(vector<int>& nums) {
4 // Track the maximum value seen so far (excluding the immediate previous element)
5 int maxValueBeforeGap = 0;
6
7 // Start from index 2 to maintain at least one element gap
8 for (int i = 2; i < nums.size(); ++i) {
9 // Update the maximum value from all elements before index (i-1)
10 // This ensures we're checking non-adjacent elements
11 maxValueBeforeGap = max(maxValueBeforeGap, nums[i - 2]);
12
13 // If any non-adjacent element before current position is greater than current element,
14 // it means there exists a global inversion that is not a local inversion
15 if (maxValueBeforeGap > nums[i]) {
16 return false;
17 }
18 }
19
20 // All global inversions are also local inversions
21 return true;
22 }
23};
24
1function isIdealPermutation(nums: number[]): boolean {
2 // Track the maximum value seen so far (excluding the immediate previous element)
3 let maxValueBeforeGap: number = 0;
4
5 // Start from index 2 to maintain at least one element gap
6 for (let i = 2; i < nums.length; i++) {
7 // Update the maximum value from all elements before index (i-1)
8 // This ensures we're checking non-adjacent elements
9 maxValueBeforeGap = Math.max(maxValueBeforeGap, nums[i - 2]);
10
11 // If any non-adjacent element before current position is greater than current element,
12 // it means there exists a global inversion that is not a local inversion
13 if (maxValueBeforeGap > nums[i]) {
14 return false;
15 }
16 }
17
18 // All global inversions are also local inversions
19 return true;
20}
21
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the input array nums
. The algorithm iterates through the array once, starting from index 2 to the end. In each iteration, it performs constant time operations: finding the maximum between two values (max(mx, nums[i - 2])
) and comparing with nums[i]
. Since we traverse the array once with constant operations per iteration, the overall time complexity is linear.
Space Complexity: O(1)
. The algorithm uses only a constant amount of extra space. The variable mx
is used to track the maximum value seen so far (excluding the immediate previous element), and the loop variable i
is used for iteration. No additional data structures that scale with the input size are created, resulting in constant space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Problem Statement
Pitfall: Many developers initially try to count both global and local inversions separately and compare their counts. This leads to unnecessary complexity and O(n²) time complexity for counting global inversions.
Why it's wrong: Counting all inversions explicitly is inefficient and misses the key insight that every local inversion is also a global inversion.
Incorrect approach example:
def isIdealPermutation(self, nums: List[int]) -> bool:
# Wrong: Trying to count inversions explicitly
global_count = 0
local_count = 0
# O(n²) complexity for global inversions
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] > nums[j]:
global_count += 1
# O(n) for local inversions
for i in range(len(nums) - 1):
if nums[i] > nums[i + 1]:
local_count += 1
return global_count == local_count
Solution: Recognize that if ANY non-local global inversion exists (elements inverted but not adjacent), the counts cannot be equal. Focus on detecting non-local inversions instead of counting.
2. Off-by-One Errors in Index Management
Pitfall: Starting the loop from the wrong index or incorrectly updating the maximum value tracker.
Common mistakes:
# Wrong: Starting from index 1 instead of 2
for i in range(1, len(nums)):
mx = max(mx, nums[i - 1]) # This only maintains a gap of 1
if mx > nums[i]:
return False
# Wrong: Updating max after comparison
for i in range(2, len(nums)):
if mx > nums[i]:
return False
mx = max(mx, nums[i - 2]) # Should update before comparison
Solution: Ensure the loop starts from index 2 and update the maximum tracker before the comparison to maintain the correct gap of at least 2 positions.
3. Incorrect Maximum Value Tracking
Pitfall: Including elements that are too close to the current position in the maximum value tracker.
Wrong approach:
def isIdealPermutation(self, nums: List[int]) -> bool:
# Wrong: Including nums[i-1] in the maximum
mx = 0
for i in range(2, len(nums)):
mx = max(mx, nums[i - 1]) # This includes adjacent element!
if mx > nums[i]:
return False
return True
Why it fails: This would flag local inversions as non-local inversions since we're comparing with the immediate previous element.
Solution: Always maintain at least a 2-position gap by only including elements up to nums[i-2]
in the maximum tracker.
4. Edge Case Handling
Pitfall: Not handling arrays with length less than 3 properly.
Issue: The algorithm starts from index 2, so arrays of length 0, 1, or 2 need special consideration.
Solution: The current implementation handles this correctly by starting the loop at index 2. For arrays shorter than 3 elements:
- Length 0 or 1: No inversions possible, returns
True
- Length 2: Only local inversions possible, returns
True
The loop simply doesn't execute for these cases, which is the correct behavior.
5. Misinterpreting the Permutation Property
Pitfall: Not utilizing the fact that the array is a permutation of [0, n-1].
Observation: In an "ideal" permutation where global inversions equal local inversions, each element can be at most 1 position away from its sorted position. This means nums[i]
should be in {i-1, i, i+1}.
Alternative solution using this property:
def isIdealPermutation(self, nums: List[int]) -> bool:
for i in range(len(nums)):
if abs(nums[i] - i) > 1:
return False
return True
This approach is equally valid and perhaps more intuitive once you understand the permutation property.
Which of the following problems can be solved with backtracking (select multiple)
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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!