周赛356(枚举、滑动窗口、数位DP)

文章目录

  • 周赛356
    • [2798. 满足目标工作时长的员工数目](https://leetcode.cn/problems/number-of-employees-who-met-the-target/)
      • 枚举
      • python一行
    • [2799. 统计完全子数组的数目](https://leetcode.cn/problems/count-complete-subarrays-in-an-array/)
      • 暴力枚举
      • O(n)滑动窗口
    • [2800. 包含三个字符串的最短字符串](https://leetcode.cn/problems/shortest-string-that-contains-three-strings/)
      • 枚举
    • [2801. 统计范围内的步进数字数目](https://leetcode.cn/problems/count-stepping-numbers-in-range/)
      • 数位DP

周赛356

2798. 满足目标工作时长的员工数目

难度简单2

公司里共有 n 名员工,按从 0n - 1 编号。每个员工 i 已经在公司工作了 hours[i] 小时。

公司要求每位员工工作 至少 target 小时。

给你一个下标从 0 开始、长度为 n 的非负整数数组 hours 和一个非负整数 target

请你用整数表示并返回工作至少 target 小时的员工数。

示例 1:

输入:hours = [0,1,2,3,4], target = 2
输出:3
解释:公司要求每位员工工作至少 2 小时。
- 员工 0 工作 0 小时,不满足要求。
- 员工 1 工作 1 小时,不满足要求。
- 员工 2 工作 2 小时,满足要求。
- 员工 3 工作 3 小时,满足要求。
- 员工 4 工作 4 小时,满足要求。
共有 3 位满足要求的员工。

示例 2:

输入:hours = [5,1,4,2,2], target = 6
输出:0
解释:公司要求每位员工工作至少 6 小时。
共有 0 位满足要求的员工。

提示:

  • 1 <= n == hours.length <= 50
  • 0 <= hours[i], target <= 105

枚举

class Solution {
    public int numberOfEmployeesWhoMetTarget(int[] hours, int target) {
        int ans = 0;
        for(int h : hours){
            if(h >= target)
                ans += 1;
        }
        return ans;
    }
}

python一行

class Solution:
    def numberOfEmployeesWhoMetTarget(self, hours: List[int], target: int) -> int:
        return sum(h >= target for h in hours)

2799. 统计完全子数组的数目

难度中等4

给你一个由 整数组成的数组 nums

如果数组中的某个子数组满足下述条件,则称之为 完全子数组

  • 子数组中 不同 元素的数目等于整个数组不同元素的数目。

返回数组中 完全子数组 的数目。

子数组 是数组中的一个连续非空序列。

示例 1:

输入:nums = [1,3,1,2,2]
输出:4
解释:完全子数组有:[1,3,1,2]、[1,3,1,2,2]、[3,1,2] 和 [3,1,2,2] 。

示例 2:

输入:nums = [5,5,5,5]
输出:10
解释:数组仅由整数 5 组成,所以任意子数组都满足完全子数组的条件。子数组的总数为 10 。

提示:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 2000

暴力枚举

双重循环:枚举左端点,寻找右端点

class Solution {
    public int countCompleteSubarrays(int[] nums) {
        Set<Integer> set = new HashSet<>();
        for(int num : nums)
            set.add(num);
        int cnt = set.size();
        int ans = 0, n = nums.length;
        for(int left = 0; left < n; left++){
            int right = left;
            set = new HashSet<>();
            while(right < n && set.size() <= cnt){
                set.add(nums[right++]);
                if(set.size() == cnt)
                    ans += 1;
            }
        }
        return ans;
    }
}

O(n)滑动窗口

窗口 [l, r] 内子数组满足 完全子数组 时,对于任意右侧边界 r <= i < n窗口 [l, i] 内子数组亦满足 完全子数组,即对于当前窗口左侧边界 r,存在 n - r完全子数组~

class Solution {
    //  当右端点固定时,不断循环直到[left, right]不满足要求,此时合法左端点的个数就是left
    public int countCompleteSubarrays(int[] nums) {
        var set = new HashSet<Integer>();
        for (int x : nums) set.add(x);
        int m = set.size();
        var cnt = new HashMap<Integer, Integer>();
        int ans = 0, left = 0;
        for (int v : nums) { // 枚举子数组右端点 v=nums[i]
            cnt.merge(v, 1, Integer::sum);
            while (cnt.size() == m) {
                int x = nums[left++];
                if (cnt.merge(x, -1, Integer::sum) == 0)
                    cnt.remove(x);
            }
            ans += left; // 子数组左端点 < left 的都是合法的
        }
        return ans;
    }
}

两类滑窗的题目:

1、while循环结束后,不满足要求(while循环内是满足要求的) - 本题属于这种

2、while循环结束后,满足要求

2800. 包含三个字符串的最短字符串

难度中等9

给你三个字符串 abc , 你的任务是找到长度 最短 的字符串,且这三个字符串都是它的 子字符串

如果有多个这样的字符串,请你返回 字典序最小 的一个。

请你返回满足题目要求的字符串。

注意:

  • 两个长度相同的字符串 ab ,如果在第一个不相同的字符处,a 的字母在字母表中比 b 的字母 靠前 ,那么字符串 a 比字符串 b 字典序小
  • 子字符串 是一个字符串中一段连续的字符序列。

示例 1:

输入:a = "abc", b = "bca", c = "aaa"
输出:"aaabca"
解释:字符串 "aaabca" 包含所有三个字符串:a = ans[2...4] ,b = ans[3..5] ,c = ans[0..2] 。结果字符串的长度至少为 6 ,且"aaabca" 是字典序最小的一个。

示例 2:

输入:a = "ab", b = "ba", c = "aba"
输出:"aba"
解释:字符串 "aba" 包含所有三个字符串:a = ans[0..1] ,b = ans[1..2] ,c = ans[0..2] 。由于 c 的长度为 3 ,结果字符串的长度至少为 3 。"aba" 是字典序最小的一个。

提示:

  • 1 <= a.length, b.length, c.length <= 100
  • abc 只包含小写英文字母。

枚举

由于就三个字符串,只要枚举 3! 中排列就可以了

class Solution:
    def minimumString(self, a: str, b: str, c: str) -> str:

        def sadd(s1,s2):
            if s1 in s2:
                return s2
            if s2 in s1:
                return s1
            for j in range(min(len(s1),len(s2)),0,-1):
                if s1[len(s1)-j:]==s2[:j]:
                    return s1+s2[j:]
            return s1+s2
        
        return sorted([sadd(sadd(a,b),c),sadd(sadd(b,a),c),sadd(sadd(a,c),b),sadd(sadd(c,a),b),sadd(sadd(b,c),a),sadd(sadd(c,b),a)],key=lambda x:[len(x),x])[0]

2801. 统计范围内的步进数字数目

难度困难8

给你两个正整数 lowhigh ,都用字符串表示,请你统计闭区间 [low, high] 内的 步进数字 数目。

如果一个整数相邻数位之间差的绝对值都 恰好1 ,那么这个数字被称为 步进数字

请你返回一个整数,表示闭区间 [low, high] 之间步进数字的数目。

由于答案可能很大,请你将它对 109 + 7 取余 后返回。

**注意:**步进数字不能有前导 0 。

示例 1:

输入:low = "1", high = "11"
输出:10
解释:区间 [1,11] 内的步进数字为 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 和 10 。总共有 10 个步进数字。所以输出为 10 。

示例 2:

输入:low = "90", high = "101"
输出:2
解释:区间 [90,101] 内的步进数字为 98 和 101 。总共有 2 个步进数字。所以输出为 2 。

提示:

  • 1 <= int(low) <= int(high) < 10100
  • 1 <= low.length, high.length <= 100
  • lowhigh 只包含数字。
  • lowhigh 都不含前导 0 。

数位DP

mask表示前一个数位上是多少

判断条件:if(Math.abs(d - mask) == 1 || !isNum),表示 ①相邻数位之间差的绝对值都 恰好1 或者 ②还不是数字(刚刚开始从0计数)

class Solution {
    private static final int MOD = (int)1e9+7;
    int dp[][]; // i, mask 记忆化搜索不需要记忆islimit和isnum
    public int countSteppingNumbers(String low, String high) {
        int m = low.toCharArray().length;
        dp = new int[m][10];
        for(int i = 0; i < m; i++) Arrays.fill(dp[i], -1);
        char[] s1 = low.toCharArray();
        s1[s1.length-1]--;
        int lownum = f(0, 0, true, false, s1);
        int n = high.toCharArray().length;
        dp = new int[n][10];
        for(int i = 0; i < n; i++) Arrays.fill(dp[i], -1);
        int highnum = f(0, 0, true, false, high.toCharArray());
        return (highnum - lownum + MOD) % MOD;
    }
    
    // mask表示前一个数位
    public int f(int i, int mask, boolean isLimit, boolean isNum, char[] s){
        if(i == s.length) //到了递归终点
            return isNum ? 1 : 0;
        if(!isLimit && isNum && dp[i][mask] >= 0) 
            return dp[i][mask];
        int res = 0;
        if(!isNum) res = f(i + 1, mask, false, false, s) % MOD;
        // 枚举要填入的数字 d
        for(int d = isNum ? 0 : 1, up = isLimit? s[i]-'0' : 9; d <= up; d++){
            if(Math.abs(d - mask) == 1 || !isNum){ // d不在mask中【这里的判断具体看题目要求】
                res = (res + f(i+1, d, isLimit & (d == up), true, s)) % MOD;
            }
        }
        if(!isLimit && isNum) dp[i][mask] = res % MOD;
        return res % MOD;
    }
}

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