LeetCode —— 775. 全局倒置与局部倒置

83 阅读2分钟

题目来源

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

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

示例2

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

提示

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

题目解析

方法一:维护后缀最小值

一个局部倒置一定是一个全局倒置,因此要判断数组中局部倒置的数量是否与全局倒置的数量相同,只需要检查有没有非局部倒置就可以了。这里的非局部倒置指的是 nums[i] > nums[j] ,其中 i < j - 1

朴素的判断方法需要两层循环,设 nnums 的长度,那么该方法的时间复杂度为O(n2)O(n^2),无法通过。

进一步的,对于每一个 nums[i] 判断是否存在一个 j(j > i + 1) 使得 nums[i] > nums[j] 即可。这和检查 nums[i] > min(nums[i+2],...,nums[n-1]) ,倒序遍历 [0,n-3] 中的每个 i ,如有一个 i 使得 nums[i] > minSuffix 成立,返回 false ,若结束遍历都没有返回 false ,则返回 true 。

代码

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var isIdealPermutation = function(nums) {
    let length = nums.length;minSuffix = nums[length-1]
    for(let i = length - 3;i >= 0;i--){
        if(nums[i]>minSuffix){
            return false
        }
        minSuffix = Math.min(nums[i+1],minSuffix)
    }
    return true
};

如图

image.png

代码(朴素的判断方式)

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var isIdealPermutation = function(nums) {
    let globalSum = 0,localSum = 0
    for(let i = 0;i<nums.length-1;i++){
        nums[i] > nums[i+1] ? localSum++ : ''
        for(let j = i+1;j<nums.length;j++){
            nums[i] > nums[j] ? globalSum++ : ''
        }
    }
    return globalSum === localSum
};

该方法逻辑可行,但其时间复杂度为O(n2)O(n^2),无法通过。

方法二:维护前缀最大值

同理,只需正序遍历 [2,n-1] 中的每个 i ,如有一个 i 使得 nums[i] < maxPrefix 成立,返回 false ,若结束遍历都没有返回 false ,则返回 true 。

代码

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var isIdealPermutation = function(nums) {
    let length = nums.length;maxPrefix = nums[0]
    for(let i = 2;length >= 3 && i < length;i++){
        if(nums[i] < maxPrefix){
            return false
        }
        maxPrefix = Math.max(nums[i-1],maxPrefix)
    }
    return true
};

image.png

方法三:归纳证明

nums 是一个由 0 ~ n-1 组成的排列,设不存在非局部倒置的排列为 [理想排列] 。由于非局部倒置表示存在一个 j > i + 1 使得 nums[i] > nums[j] 成立。根据题目最开始的限制条件 —— 给你一个长度为 n 的整数数组 nums ,表示由范围 [0, n - 1] 内所有整数组成的一个排列。 最理想排列即 [0,1,2,3,4,5,...,n-1] ,当其中任意一项与不相邻的数字交换,就会出现属于全局倒置而不属于局部倒置的情况。

举例:

  1. [0,1,2]只有当 0 和 1 或者 1 和 2 位置交换时,返回结果依旧是为true,当 0 与 不相邻的 2 交换时,结果便是 false 。
  2. [0,1,2,3,4,5,6,7,8,9]这时你会发现当 0 和 1 、 3 和 4 、 6 和 7 交换时,结果依旧是 true ,发生交换的数都没有相邻。

image.png

image.png

所以当数字与前一位(后一位)置换后,且前前一位(后后一位)没有交换情况时,该数列就是理想数列。因此不难归纳出 [理想数列] 的充分必要条件为对于每个元素 nums[i] 都满足 nums[i] - i <= 1

代码

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var isIdealPermutation = function(nums) {
    let length = nums.length
    for(let i = 0;i < length;i++){
        if(Math.abs(nums[i] - i) > 1){
            return false
        }
    }
    return true
};

如图

image.png

参考

执行用时和内存消耗仅供参考,大家可以多提交几次。如有更好的想法,欢迎大家提出。