LC-775.全局倒置与局部倒置

题目描述

leetcode 中等题

给你一个长度为 n 的整数数组 nums ,表示由范围 [0, n - 1] 内所有整数组成的一个排列。

全局倒置 的数目等于满足下述条件不同下标对 (i, j) 的数目:

  • 0 <= i < j < n
  • nums[i] > nums[j]

局部倒置 的数目等于满足下述条件的下标 i 的数目:

  • 0 <= i < n - 1
  • nums[i] > nums[i + 1]

当数组 nums 中 全局倒置 的数量等于 局部倒置 的数量时,返回 true ;否则,返回 false 。

示例1:

1
2
3
输入:nums = [1,0,2]
输出:true
解释:有 1 个全局倒置,和 1 个局部倒置。

示例2:

1
2
3
输入:nums = [1,2,0]
输出:false
解释:有 2 个全局倒置,和 1 个局部倒置。

提示:

1
2
3
4
5
n == nums.length
1 <= n <= 10^5
0 <= nums[i] < n
nums 中的所有整数 互不相同
nums 是范围 [0, n - 1] 内所有数字组成的一个排列

遍历

这道题最容易且方便的做法是,逆序遍历且维护一个 $[i + 2, n]$ 的最小值,如果当前元素大于该最小值返回 $false$ ,如果顺利遍历结束返回 $true$ 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public boolean isIdealPermutation(int[] nums) {
int n = nums.length;
int min = nums[n - 1];
for(int i = n - 3; i >= 0; i--){
if(nums[i] > min){
return false;
}
// 判断后再更新 min,相当于维护的是 [i + 2, n] 的最小值
min = Math.min(min, nums[i + 1]);
}
return true;
}
}

树状数组

对于求 逆序对 更通用的做法是使用 树状数组

由于树状数组的索引位置 $0$ 一般不会存储元素,因为 $lowbit(0) = 0$,会导致 $query$ 和 $update$ 的循环条件都要进行特判,然而题目数据范围为 $0 <= nums[i] < n$,会取到 $0$。

一种方法是构造一个原数组元素与排名的映射(排名从 $1$ 开始),使得原数组离散化,这样既不会取到 $0$ 的索引,也能压缩数字区间(虽然本题数据为排列,所以起到没有压缩作用)。

构建完映射之后只需要通过树状数组得到逆序对的总数量,再与“局部倒置”的数量比较即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {
public boolean isIdealPermutation(int[] nums) {
int n = nums.length;
// 元素map排名
int[] sort = new int[n];
System.arraycopy(nums, 0, sort, 0, n);
Arrays.sort(sort);
Map<Integer, Integer> rank = new HashMap<>();
for(int i = 1; i < n + 1; i++){
rank.put(sort[i - 1], i);
}
FenwickTree t = new FenwickTree(rank.keySet().size() + 1);
int local = 0, global = 0;
for(int i = 0; i < n; i++){
int r = rank.get(nums[i]);
t.update(r, 1);
global += t.query(rank.keySet().size()) - t.query(r);
if(i != n - 1 && nums[i] > nums[i + 1]){
local += 1;
}
}
return local == global;
}

static class FenwickTree{
int[] c;

FenwickTree(int size){
c = new int[size];
}

void update(int index, int updateValue){
while(index < c.length){
c[index] += updateValue;
index += lowbit(index);
}
}

int query(int index){
int ans = 0;
while(index > 0){
ans += c[index];
index -= lowbit(index);
}
return ans;
}

private int lowbit(int x){
return x & -x;
}
}
}

另一种避免取到 $0$ 的方法是每次加入树状数组时把元素手动加上 $1$ 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public boolean isIdealPermutation(int[] nums) {
int n = nums.length;
FenwickTree t = new FenwickTree(nums.length + 1);
int local = 0, global = 0;
for(int i = 0; i < n; i++){
int item = nums[i] + 1;
global += t.query(n) - t.query(item);
t.update(item, 1);
if(i != n - 1 && nums[i] > nums[i + 1]){
local += 1;
}
}
return local == global;
}
// FenwickTree 代码同上
}

LC-775.全局倒置与局部倒置
https://wecgwm.github.io/2022/11/16/LC-775-全局倒置与局部倒置/
作者
yichen
发布于
2022年11月16日
许可协议