图解LeetCode——775、792、891

152 阅读5分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」 

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 。

二、示例

2.1> 示例 1:

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

2.2> 示例 2:

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

提示:

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

三、解题思路

3.1> 根据前缀的最大值来判断

根据题目描述,我们可以得到如下结论:

如果是局部倒置,那么一定就是全局倒置。所以,全局倒置是包含局部倒置的。

那么我们就可以将解题视角放在非局部倒置全局倒置上。换句话说,也就是—— 非相邻数字是否满足递增。  具体操作如下图所示:

3.2> 根据偏移的差值来判断

由于题目中已经给出了如下一个关键条件:

数组nums长度为n,并且数字是由0到n-1构成的。

所以,就可以通过nums[i]-i计算出i位置的元素与有序后的位置之间的差值:

差值等于0】表示元素i所在的位置就是排序后的位置。
差值等于1】表示元素1所在的位置向前1位或向后1位。
其他情况】表示元素所在位置偏差大于1位,也就是出现了全局倒置并且非局部倒置的情况。

具体操作如下图所示:

四、代码实现

4.1> 根据前缀的最大值来判断

class Solution {
    public boolean isIdealPermutation(int[] nums) {
        int max = nums[0];
        for (int i = 2; i < nums.length; i++) {
            if (nums[i] < max) return false;
            max = Math.max(max, nums[i - 1]);
        }
        return true;
    }
}

4.2> 根据偏移的差值来判断

class Solution {
    public boolean isIdealPermutation(int[] nums) {
        for (int i = 0; i < nums.length; i++) 
            if (Math.abs(nums[i] - i) > 1return false;
        return true;
    }
}

792. 匹配子序列的单词数(难度:中等)

一、题目

给定字符串 s 和字符串数组 words, 返回  words[i] 中是s的子序列的单词个数 。 字符串的 子序列 是从原始字符串中生成的新字符串,可以从中删去一些字符(可以是none),而不改变其余字符的相对顺序。

例如, “ace” 是 “abcde” 的子序列。

二、示例

2.1> 示例 1:

【输入】 s = "abcde", words = ["a","bb","acd","ace"]
【输出】 3
【解释】 有三个是 s 的子序列的单词: "a", "acd", "ace"。

2.2> 示例 2:

【输入】 s = "dsahjpjauf", words = ["ahjpjau","ja","ahbwzgqnuk","tnmlanowax"]
【输出】 2

提示:

  • 1 <= s.length <= 5 * 10^4
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 50
  • words[i]s 都只由小写字母组成。

三、解题思路

根据题目描述,需要我们去words字符串数组中却判断,哪些是字符串s子序列,最后再将子序列的总个数返回回来。那么,对于字符串子序列,我们主要关心如下两点:

是否存在?】子序列中的某个字符是否在字符串s中存在。
顺序对吗?】子序列中字符出现的顺序是否违背了字符串s中的顺序。

那么针对这两种关注点,我们首先遍历字符串s中的每个字符,由于这些字符都是由小写字母构成,所以我们可以通过采用:字符减去‘a’来确定下标位置,并将该字符在s中出现的位置保存到ArrayList集合中。

然后,我们再分别遍历字符串数组words中的每个字符串,逐一判断每个字符出现的位置顺序是否与s相同,如果不同,则可以判断该字符串不是s的子序列。具体操作详情请见下图:

四、代码实现

class Solution {
    public int numMatchingSubseq(String s, String[] words) {
        List<Integer>[] sm = new ArrayList[26]; // index:字符  sm[index]:字符出现的位置集合
        char[] sc = s.toCharArray();
        for (int i = 0; i < sc.length; i++) {
            if (sm[sc[i]-'a'] == null) sm[sc[i]-'a'] = new ArrayList<>();
            sm[sc[i]-'a'].add(i);
        }

        int result = words.length; // 初始化result数量为所有单词,如果不满足条件,则陆续执行减1操作
        for (String word : words) { // 遍历每个单词
            int compareIndex = -1, index;
            for (int i = 0; i < word.length(); i++) { // 遍历每个字符
                if (sm[word.charAt(i)-'a'] == null || 
                        ((index = findCharIndex(compareIndex, sm[word.charAt(i)-'a'])) <= compareIndex)) {
                    result--;
                    break;
                }
                compareIndex = index;
            }
        }
        return result;
    }

    // 折半查找
    private int findCharIndex(int compareIndex, List<Integer> list) {
        int head = 0, tail = list.size() - 1, mid;
        while (head < tail) {
            mid = head + (tail - head) / 2;
            if (list.get(mid) > compareIndex) tail = mid;
            else head = ++mid;
        }
        return list.get(head);
    }
}

891. 子序列宽度之和(难度:困难)

一、题目

一个序列的 宽度 定义为该序列中最大元素和最小元素的差值。

给你一个整数数组 nums ,返回 nums 的所有非空 子序列宽度之和 。由于答案可能非常大,请返回对 10^9 + 7 取余 后的结果。

子序列 定义为从一个数组里删除一些(或者不删除)元素,但不改变剩下元素的顺序得到的数组。例如,[3,6,2,7] 就是数组 [0,3,1,6,2,2,7] 的一个子序列。

二、示例

2.1> 示例 1:

【输入】nums = [2,1,3]
【输出】6
【解释】子序列为 [1], [2], [3], [2,1], [2,3], [1,3], [2,1,3] 。相应的宽度是 0, 0, 0, 1, 1, 2, 2 。宽度之和是 6 。

2.2> 示例 2:

【输入】nums = [2]
【输出】0

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

三、解题思路

根据题目描述我们可以知道,计算宽度的时候,只是需要计算序列中【最大元素】与【最小元素】的差值。那么我们只需要关心数组nums中数字的大小而不需要关心它所在的位置了,即:子序列[2,4]与子序列[4,2]差值都是2。

那么,既然我们主要关注点是数组nums中的每个元素值大小,为了方便计算,我们首先可以对nums进行排序操作。

排序之后,我们随便找一个下标为i的元素,可以找到以下规律:

下面是以nums[i]其左侧所有元素拼装组合成的子序列示例:

那么,针对于nums[i]来说,它的宽度总和就是: (2i-2nums.length-i-1) * nums[i] ,那么我们遍历所有nums数组中的元素,逐一计算每个元素的宽度总和,那么最终结果就是本题的答案。

四、代码实现

class Solution {
    public int sumSubseqWidths(int[] nums) {
        Arrays.sort(nums); // 排序
        int mod = (int)1e9 + 7, n = nums.length;
        long result = 0;
        long[] pow = new long[n];
        pow[0] = 1;
        for (int i = 1; i < n; i++) 
            pow[i] = (pow[i - 1] << 1) % mod; // 初始化2^n的值

        for (int i = 0; i < n; i++)
            result = (result + (pow[i] - pow[n-i-1]) * nums[i] % mod) % mod; // 计算总和          
        return (int)result;
    }
}

今天的文章内容就这些了:

写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享

更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \(^o^)/ ~ 「干货分享,每天更新」