双周赛111(双指针模拟、LIS、状态机DP、数位DP)

文章目录

  • 双周赛111
    • [2824. 统计和小于目标的下标对数目](https://leetcode.cn/problems/count-pairs-whose-sum-is-less-than-target/)
      • 模拟O(n^2)
      • O(nlogn)双指针
    • [2825. 循环增长使字符串子序列等于另一个字符串](https://leetcode.cn/problems/make-string-a-subsequence-using-cyclic-increments/)
      • 双指针模拟
    • [2826. 将三个组排序](https://leetcode.cn/problems/sorting-three-groups/)
      • 方法一:最长非递减子序列
      • 状态机DP
    • [2827. 范围中美丽整数的数目](https://leetcode.cn/problems/number-of-beautiful-integers-in-the-range/)
      • 数位DP

双周赛111

2824. 统计和小于目标的下标对数目

简单

给你一个下标从 0 开始长度为 n 的整数数组 nums 和一个整数 target ,请你返回满足 0 <= i < j < nnums[i] + nums[j] < target 的下标对 (i, j) 的数目。

示例 1:

输入:nums = [-1,1,2,3,1], target = 2
输出:3
解释:总共有 3 个下标对满足题目描述:
- (0, 1) ,0 < 1 且 nums[0] + nums[1] = 0 < target
- (0, 2) ,0 < 2 且 nums[0] + nums[2] = 1 < target 
- (0, 4) ,0 < 4 且 nums[0] + nums[4] = 0 < target
注意 (0, 3) 不计入答案因为 nums[0] + nums[3] 不是严格小于 target 。

示例 2:

输入:nums = [-6,2,5,-2,-7,-1,3], target = -2
输出:10
解释:总共有 10 个下标对满足题目描述:
- (0, 1) ,0 < 1 且 nums[0] + nums[1] = -4 < target
- (0, 3) ,0 < 3 且 nums[0] + nums[3] = -8 < target
- (0, 4) ,0 < 4 且 nums[0] + nums[4] = -13 < target
- (0, 5) ,0 < 5 且 nums[0] + nums[5] = -7 < target
- (0, 6) ,0 < 6 且 nums[0] + nums[6] = -3 < target
- (1, 4) ,1 < 4 且 nums[1] + nums[4] = -5 < target
- (3, 4) ,3 < 4 且 nums[3] + nums[4] = -9 < target
- (3, 5) ,3 < 5 且 nums[3] + nums[5] = -3 < target
- (4, 5) ,4 < 5 且 nums[4] + nums[5] = -8 < target
- (4, 6) ,4 < 6 且 nums[4] + nums[6] = -4 < target

提示:

  • 1 <= nums.length == n <= 50
  • -50 <= nums[i], target <= 50

模拟O(n^2)

class Solution:
    def countPairs(self, nums: List[int], target: int) -> int:
        ans, n = 0, len(nums)
        for i in range(n):
            for j in range(i+1, n):
                if nums[i] + nums[j] < target:
                    ans += 1
        return ans

O(nlogn)双指针

https://leetcode.cn/problems/count-pairs-whose-sum-is-less-than-target/solutions/2396216/onlogn-pai-xu-shuang-zhi-zhen-by-endless-qk40/

class Solution {
    public int countPairs(List<Integer> nums, int target) {
        Collections.sort(nums);
        int left = 0, right = nums.size() - 1;
        int ans = 0;
        while(left < right){
            while(left < right && nums.get(left) + nums.get(right) >= target) 
                right -= 1;
            if(left < right)
                ans += right - left;
            left += 1;
        }
        return ans;
    }
}

写法二:

class Solution {
    public int countPairs(List<Integer> nums, int target) {
        Collections.sort(nums);
        int left = 0, right = nums.size() - 1;
        int ans = 0;
        while(left < right){
            if(nums.get(left) + nums.get(right) < target){
                ans += right - left;
                left += 1;
            }else{
                right -= 1;
            }
        }
        return ans;
    }
}

2825. 循环增长使字符串子序列等于另一个字符串

中等

给你一个下标从 0 开始的字符串 str1str2

一次操作中,你选择 str1 中的若干下标。对于选中的每一个下标 i ,你将 str1[i] 循环 递增,变成下一个字符。也就是说 'a' 变成 'b''b' 变成 'c' ,以此类推,'z' 变成 'a'

如果执行以上操作 至多一次 ,可以让 str2 成为 str1 的子序列,请你返回 true ,否则返回 false

**注意:**一个字符串的子序列指的是从原字符串中删除一些(可以一个字符也不删)字符后,剩下字符按照原本先后顺序组成的新字符串。

示例 1:

输入:str1 = "abc", str2 = "ad"
输出:true
解释:选择 str1 中的下标 2 。
将 str1[2] 循环递增,得到 'd' 。
因此,str1 变成 "abd" 且 str2 现在是一个子序列。所以返回 true 。

示例 2:

输入:str1 = "zc", str2 = "ad"
输出:true
解释:选择 str1 中的下标 0 和 1 。
将 str1[0] 循环递增得到 'a' 。
将 str1[1] 循环递增得到 'd' 。
因此,str1 变成 "ad" 且 str2 现在是一个子序列。所以返回 true 。

示例 3:

输入:str1 = "ab", str2 = "d"
输出:false
解释:这个例子中,没法在执行一次操作的前提下,将 str2 变为 str1 的子序列。
所以返回 false 。

提示:

  • 1 <= str1.length <= 105
  • 1 <= str2.length <= 105
  • str1str2 只包含小写英文字母。

双指针模拟

class Solution {
    public boolean canMakeSubsequence(String str1, String str2) {
        int i = 0, j = 0;
        while(i < str1.length() && j < str2.length()){
            if(str1.charAt(i) == str2.charAt(j) || (char)(((str1.charAt(i) - 'a') + 1 + 26) % 26) + 'a' == str2.charAt(j))
                j++;
            i++;
        }
        return j == str2.length();
    }
}

2826. 将三个组排序

中等

给你一个下标从 0 开始长度为 n 的整数数组 nums

0n - 1 的数字被分为编号从 13 的三个组,数字 i 属于组 nums[i] 。注意,有的组可能是 空的

你可以执行以下操作任意次:

  • 选择数字 x 并改变它的组。更正式的,你可以将 nums[x] 改为数字 13 中的任意一个。

你将按照以下过程构建一个新的数组 res

  1. 将每个组中的数字分别排序。
  2. 将组 123 中的元素 依次 连接以得到 res

如果得到的 res非递减顺序的,那么我们称数组 nums美丽数组

请你返回将 nums 变为 美丽数组 需要的最少步数。

示例 1:

输入:nums = [2,1,3,2,1]
输出:3
解释:以下三步操作是最优方案:
1. 将 nums[0] 变为 1 。
2. 将 nums[2] 变为 1 。
3. 将 nums[3] 变为 1 。
执行以上操作后,将每组中的数字排序,组 1 为 [0,1,2,3,4] ,组 2 和组 3 都为空。所以 res 等于 [0,1,2,3,4] ,它是非递减顺序的。
三步操作是最少需要的步数。

示例 2:

输入:nums = [1,3,2,1,3,3]
输出:2
解释:以下两步操作是最优方案:
1. 将 nums[1] 变为 1 。
2. 将 nums[2] 变为 1 。
执行以上操作后,将每组中的数字排序,组 1 为 [0,1,2,3] ,组 2 为空,组 3 为 [4,5] 。所以 res 等于 [0,1,2,3,4,5] ,它是非递减顺序的。
两步操作是最少需要的步数。

示例 3:

输入:nums = [2,2,2,2,3,3]
输出:0
解释:不需要执行任何操作。
组 1 为空,组 2 为 [0,1,2,3] ,组 3 为 [4,5] 。所以 res 等于 [0,1,2,3,4,5] ,它是非递减顺序的。

提示:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 3

方法一:最长非递减子序列

class Solution {
    // 最后要求构造成非递减的数组,那么找原数组最大的非递减子序列,然后答案为len(nums) - len(LIS)
    public int minimumOperations(List<Integer> nums) {
        int lislen = lengthOfLIS(nums);
        return nums.size() - lislen;
    }

    public int lengthOfLIS(List<Integer> nums) {
        // O(nlogn)
        List<Integer> g = new ArrayList<>();
        for(int x : nums){
            // 找最长非递减子序列的长度,只需要lower_bound(g, x) 改为 lower_bound(g, x + 1)
            int pos = lower_bound(g, x + 1);
            if(pos == g.size()){
                g.add(x); // >= x 的 g[i] 不存在
            }else
                g.set(pos, x);
        }
        return g.size();
    }

    // 找出第一个大于等于 x 的下标
    public int lower_bound(List<Integer> g, int x){
        int left = 0, right = g.size();
        while(left < right){
            int mid = (left + right) >> 1;
            if(g.get(mid) < x) left = mid + 1;
            else right = mid;
        }
        return right;
    }
}

状态机DP

class Solution {
    public int minimumOperations(List<Integer> nums) {
        int n = nums.size();
        // 定义f[i][j] 表示考虑nums[0] 到 nums[i], 且nums[i] 变成 j 的最小修改次数
        // 枚举第 i-1 个数改成 k,
        //      f[i+1][j] = f[i][k] + (j != nums[i])
        int[][] dp = new int[n+1][4];
        for(int i = 0; i < n; i++){
            int x = nums.get(i);
            dp[i+1][1] = (x == 1 ? 0 : 1) + dp[i][1];
            dp[i+1][2] = (x == 2 ? 0 : 1) + Math.min(dp[i][1], dp[i][2]);
            dp[i+1][3] = (x == 3 ? 0 : 1) + Math.min(dp[i][1], Math.min(dp[i][2], dp[i][3]));
        }
        int ans = nums.size();
        for(int j = 1; j < 4; j++)
            ans = Math.min(ans, dp[n][j]);
        return ans;
    }
}

2827. 范围中美丽整数的数目

困难

给你正整数 lowhighk

如果一个数满足以下两个条件,那么它是 美丽的

  • 偶数数位的数目与奇数数位的数目相同。
  • 这个整数可以被 k 整除。

请你返回范围 [low, high] 中美丽整数的数目。

示例 1:

输入:low = 10, high = 20, k = 3
输出:2
解释:给定范围中有 2 个美丽数字:[12,18]
- 12 是美丽整数,因为它有 1 个奇数数位和 1 个偶数数位,而且可以被 k = 3 整除。
- 18 是美丽整数,因为它有 1 个奇数数位和 1 个偶数数位,而且可以被 k = 3 整除。
以下是一些不是美丽整数的例子:
- 16 不是美丽整数,因为它不能被 k = 3 整除。
- 15 不是美丽整数,因为它的奇数数位和偶数数位的数目不相等。
给定范围内总共有 2 个美丽整数。

示例 2:

输入:low = 1, high = 10, k = 1
输出:1
解释:给定范围中有 1 个美丽数字:[10]
- 10 是美丽整数,因为它有 1 个奇数数位和 1 个偶数数位,而且可以被 k = 1 整除。
给定范围内总共有 1 个美丽整数。

示例 3:

输入:low = 5, high = 5, k = 2
输出:0
解释:给定范围中有 0 个美丽数字。
- 5 不是美丽整数,因为它的奇数数位和偶数数位的数目不相等。

提示:

  • 0 < low <= high <= 109
  • 0 < k <= 20

数位DP

class Solution {
    // 1. 偶数数位的数目与奇数数位的数目相同 ==> 记录数位奇偶出现次数
    //          ==> 偶数数位的数目 - 奇数数位的数目 = 0 ==> cnt
    // 2. 这个整数可以被k整除
    //      1) 假设 k=3, 10 和 40 答案相同
    //      2) 设前面数位为mask,当前枚举数位为d
    //          ==> mask' = (mask * 10 + d) % k
    private static final int diff = 15;
    int[][][] cache;
    public int numberOfBeautifulIntegers(int low, int high, int k) {
        char[] s1 = String.valueOf(low-1).toCharArray();
        int m = s1.length;
        cache = new int[m][k][2*diff+1];
        for(int i = 0; i < m; i++)
            for(int j = 0; j < k; j++)
                Arrays.fill(cache[i][j], -1);
        int lownum = f(0, 0, diff, true, false, s1, k);
        char[] s2 = String.valueOf(high).toCharArray();
        int n = s2.length;
        cache = new int[n][k][2*diff+1];
        for(int i = 0; i < n; i++)
            for(int j = 0; j < k; j++)
                Arrays.fill(cache[i][j], -1);
        int highnum = f(0, 0, diff, true, false, s2, k);
        return highnum - lownum;
    }

    public int f(int i, int mask, int cnt, boolean isLimit, boolean isNum, char[] s, int k){
        if(i == s.length){
            if(isNum && mask == 0 && cnt == diff) return 1;
            else return 0;
        }
        if(!isLimit && isNum && cache[i][mask][cnt] >= 0) return cache[i][mask][cnt];
        int res = 0;
        if(!isNum) res = f(i+1, mask, cnt, false, false, s, k);
        int up = (isLimit ? s[i] - '0' : 9);
        for(int d = isNum ? 0 : 1; d <= up; d++){
            res += f(i+1, (mask * 10 + d) % k, cnt + (d % 2 == 0 ? 1 : -1), isLimit && d == up, true, s, k);
        }
        if(!isLimit && isNum) cache[i][mask][cnt] = res;
        return res;
    }
}

简洁写法

class Solution {
    // 1. 偶数数位的数目与奇数数位的数目相同 ==> 记录数位奇偶出现次数
    //          ==> 偶数数位的数目 - 奇数数位的数目 = 0 ==> cnt
    // 2. 这个整数可以被k整除
    //      1) 假设 k=3, 10 和 40 答案相同
    //      2) 设前面数位为mask,当前枚举数位为d
    //          ==> mask' = (mask * 10 + d) % k
    int[][][] cache;
    public int numberOfBeautifulIntegers(int low, int high, int k) {
        return calc(high, k) - calc(low-1, k);
    }

    public int calc(int num, int k){
        char[] s = Integer.toString(num).toCharArray();
        int n = s.length;
        cache = new int[n][k][n*2 + 1];
        for(int i = 0; i < n; i++)
            for(int j = 0; j < k; j++)
                Arrays.fill(cache[i][j], -1);
        return f(0, 0, n, true, false, s, k);
    }

    public int f(int i, int val, int diff, boolean isLimit, boolean isNum, char[] s, int k){
        if(i == s.length){
            if(isNum && val == 0 && diff == s.length) return 1;
            else return 0;
        }
        if(!isLimit && isNum && cache[i][val][diff] >= 0) return cache[i][val][diff];
        int res = 0;
        if(!isNum) res = f(i+1, val, diff, false, false, s, k);
        int up = (isLimit ? s[i] - '0' : 9);
        for(int d = isNum ? 0 : 1; d <= up; d++){
            res += f(i+1, (val * 10 + d) % k, diff + (d % 2 == 0 ? 1 : -1), isLimit && d == up, true, s, k);
        }
        if(!isLimit && isNum) cache[i][val][diff] = res;
        return res;
    }
}

你可能感兴趣的:(算法刷题记录,leetcode)