• 每日一题 —— 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;
        }
    };
    
    • 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

    思路二

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

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

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

    我们将整个数组画出来:

    ??????AB?????
    
    • 1
    • 对于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;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    思路三

    注意到,这道题目的数据具有特殊性。比如题目中规定了,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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    CTF之序列化__toString
    Human-level control through deep reinforcement learning
    Commonjs与ES Module
    ESP8266_01S+刷入AT固件+保姆级教学+USB验证AT指令
    Android网络安全配置:允许明文HTTP通信的正确姿势20240418
    C语言 动态内存管理
    springBoot_swagger、异步任务、邮件发送、定时任务、集成redis、分布式(Dubbo、Zookeeper)
    针对我国——国产数据库进行分析
    2022 年 JavaScript 开发工具的生态,别再用过时的框架了
    转载 - 洞察问题本质,解决工作难题
  • 原文地址:https://blog.csdn.net/vcj1009784814/article/details/127882202