每日一题 —— LC. 775 全局倒置与局部倒置

775. 全局倒置与局部倒置

给你一个长度为 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 。

提示

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

思路一

局部倒置的数量可以通过一次遍历求出,全局倒置的数量等同于求逆序对数量,可以用归并排序来做。

最后对比局部倒置数量全局倒置数量是否相等即可。

// C++ 300ms
typedef long long LL;
class Solution {
public:

    vector<int> temp;

    LL merge_sort(vector<int>& nums, int l, int r) {
        if (l >= r) return 0;
        int mid = l + r >> 1;
        LL ans = merge_sort(nums, l, mid) + merge_sort(nums, mid + 1, r);
        int i = l, j = mid + 1, k = 0;
        while (i <= mid && j <= r) {
            if (nums[i] <= nums[j]) temp[k++] = nums[i++];
            else {
                ans += mid - i + 1;
                temp[k++] = nums[j++];
            }
        }
        while (i <= mid) temp[k++] = nums[i++];
        while (j <= r) temp[k++] = nums[j++];
        for (i = l, j = 0; i <= r; i++, j++) nums[i] = temp[j];
        return ans;
    }
    bool isIdealPermutation(vector<int>& nums) {
        LL cnt = 0, n = nums.size();
        for (int i = 0; i < n - 1; i++) {
            if (nums[i] > nums[i + 1]) cnt++;
        }
        temp.resize(n);
        LL tot_cnt = merge_sort(nums, 0, n - 1);
        return cnt == tot_cnt;
    }
};

思路二

仔细审题,由于每个局部倒置,一定是一个全局倒置。而要局部倒置和全局倒置的数量相等,则说明所有的全局倒置就是所有的局部倒置。

那么如何能满足所有全局倒置就是局部倒置呢?

假设在某个位置i,出现了局部倒置nums[i] > nums[i + 1],我们不妨设nums[i] = A, nums[i + 1] = B,则有A > B。由于所有的局部倒置就是全局倒置。那么A只能与B形成一组倒置关系,也就是说,A不能和B之外的任何数再形成一组倒置关系;类似的,B也只能与A形成倒置关系,不能和A之外的任何数再形成倒置关系。

我们将整个数组画出来:

??????AB?????
  • 对于A,由于A > B,且A只能和B形成倒置,那么对于所有B右侧的数 A + A^+ A+,一定有 A + ≥ A A^+ \ge A A+A ,也就是B右侧的所有数都不可能再和A组成倒置关系,同样,B左侧的所有数 B − B^- B ,一定有 B − ≤ A B^- \le A BA

  • 对于B,其只能和A形成倒置,那么在A左侧的数,都一定是小于等于B的,B右侧的数都是大于等于B的。

整理一下,我们设B右侧的数为 A + A^+ A+A左侧的数为 B − B^- B,根据我们的命名就能体现出来,要使得AB能组成一对唯一的倒置关系(A不能和B以外的任何数再组成倒置,B也不能和A以外的任何数再组成倒置)。只有满足

  • A + ≥ A A^+ \ge A A+A (B右侧的所有数都大于等于A)
  • B ≥ B − B \ge B^- BB (A左侧的所有数都小于等于B)

由于本身有 A > B A > B A>B,所以A一定是大于等于A左侧的所有数的( B − B^- B),B也一定是小于等于B右侧的所有数的( A + A^+ A+),所以这两个条件我们没有列出来。

则我们遍历一次整个数组,每次判断当前的数nums[i]是否违反上述约束即可。如何判断呢?

我们先看当前的数与前一个数的大小关系。

  • nums[i] >= nums[i - 1],说明[i - 1, i]不构成逆序对,但我们还要判断i是否能和i - 1左侧的某个数构成逆序对,如何判断呢?假设[0, i - 2]的最大值为m,则只要nums[i] >= m,那么nums[i]一定是大于[0, i - 2]的所有数的,即一定不存在逆序对。
  • nums[i] < nums[i - 1],说明遇到了逆序对,则我们要判断i除了与i - 1构成逆序对,是否还能和[0, i - 2]中的某个数构成逆序对,同样的,只要nums[i] >= m,就不存在逆序对。

上面只论述了i与左侧的数是否构成逆序对,而对于i右侧的数不必考虑,因为在遍历到右侧某个数时,会往左看,是会把i考虑进去的。

当我们遍历到i时,

  • nums[i] < nums[i - 1],则我们需要[0, i - 2]的最大值m,因为我们要判断nums[i]m的大小关系;
  • nums[i] >= nums[i - 1],则我们也需要[0, i - 2]的最大值m,因为我们要判断nums[i]m的大小关系;

我们用两个变量来维护这个最大值,curMaxprevMax

我们在处理完i这个位置时,更新prevMax = curMax,并更新curMax = max(curMax, nums[i]),这样,在下次循环处理i + 1这个位置时,curMax存的就是[0, i]的最大值,prevMax存的就是[0, i - 1]的最大值。

代码如下

// C++ 100ms
class Solution {
public:
    bool isIdealPermutation(vector<int>& nums) {
        int curMax = -1, prevMax = -1;
        for (int i = 0; i < nums.size(); i++) {
            if (i - 1 >= 0) {
                if (nums[i] >= nums[i - 1] && nums[i] < curMax) return false; // 这里 curMax 也可以替换成 prevMax
                if (nums[i] < nums[i - 1] && nums[i] < prevMax) return false;
            }
            prevMax = curMax;
            curMax = max(curMax, nums[i]);
        }   
        return true;
    }
};

思路三

注意到,这道题目的数据具有特殊性。比如题目中规定了,nums的全部的数就是[0, n - 1]的一个排列。

根据上面的分析,要局部倒置等于全局倒置,则数组整体来看是要单调递增的,如果出现递减,则一定是相邻两个数。并且相邻两个数互换后,一定有nums[i] = i。则我们可以遍历整个数组,判断nums[i]是否等于i即可,若不等于,则一定能和i - 1i + 1这两个位置的数互换,换完后得到nums[i] = i;若无法使得nums[i] = i,则直接return false

若没有这个特殊的条件,则思路二才是更加通用的做法。

// C++ 100ms
class Solution {
public:
    bool isIdealPermutation(vector<int>& nums) {
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] != i) {
                // 需要和相邻的互换
                if (nums[i] == i - 1 && nums[i - 1] == i) swap(nums[i], nums[i - 1]);
                else if (nums[i] == i + 1 && nums[i + 1] == i) swap(nums[i], nums[i + 1]);
                else return false;
            }
        }   
        return true;
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值