[Leetcode Weekly Contest]309

链接:LeetCode

[Leetcode]2399. 检查相同字母间的距离

给你一个下标从 0 开始的字符串 s ,该字符串仅由小写英文字母组成,s 中的每个字母都 恰好 出现 两次 。另给你一个下标从 0 开始、长度为 26 的的整数数组 distance 。
字母表中的每个字母按从 0 到 25 依次编号(即,'a' -> 0, 'b' -> 1, 'c' -> 2, ... , 'z' -> 25)。
在一个 匀整 字符串中,第 i 个字母的两次出现之间的字母数量是 distance[i] 。如果第 i 个字母没有在 s 中出现,那么 distance[i] 可以 忽略 。
如果 s 是一个 匀整 字符串,返回 true ;否则,返回 false 。

遍历即可。

class Solution {
    public boolean checkDistances(String s, int[] distance) {
        HashMap<Character, Integer> hash = new HashMap<>();
        HashSet<Character> set = new HashSet<>();
        for(int i=0;i<s.length();++i) {
            Character ch = s.charAt(i);
            set.add(ch);
            hash.put(ch, i-hash.getOrDefault(ch, 0));
        }
        for(int i=0;i<26;++i) {
            Character ch = (char)('a'+i);
            if(!set.contains(ch)) continue;
            if(hash.getOrDefault(ch, 0)-1 != distance[i]) return false;
        }
        return true;
    }
}

[Leetcode]2400. 恰好移动 k 步到达某一位置的方法数目

给你两个 正 整数 startPos 和 endPos 。最初,你站在 无限 数轴上位置 startPos 处。在一步移动中,你可以向左或者向右移动一个位置。
给你一个正整数 k ,返回从 startPos 出发、恰好 移动 k 步并到达 endPos 的 不同 方法数目。由于答案可能会很大,返回对 109 + 7 取余 的结果。
如果所执行移动的顺序不完全相同,则认为两种方法不同。
注意:数轴包含负整数。

组合数
称 startPos 指向 endPos 的方向为正方向,startPos 远离 endPos 的方向为负方向。设从 startPos 出发,往正方向走了 a 步,往负方向走了(k−a) 步后到达 endPos,根据组合数的定义可知答案为 \(C_k^a\) (k 步里选 a 步走正方向)。
以用 \(C_i^j = C_{i - 1}^j + C_{i - 1}^{j - 1}\)的递推式 \(\mathcal{O}(k^2)\)求组合数。

注意求组合数不能直接用 \(C_i^j\) 算,一旦溢出或者取余再做除法结果就会出错

class Solution {
    int MOD = (int)1E9+7;
    HashMap<Pair<Integer, Integer>, Integer> hash = new HashMap<>();
    public int numberOfWays(int startPos, int endPos, int k) {
        int diff = Math.abs(startPos-endPos);
        if(((k+diff) & 1) != 0 || k < diff) return 0;
        int oneDirection = (k+diff)/2;
        return computeC(k, oneDirection);
    }

    public int computeC(int n, int m) {
        if(n == m) return 1;
        if(m == 1) return n;
        if(n<m) return 0;
        Pair<Integer,Integer> key = new Pair<>(n,m);
        if(hash.containsKey(key)) return hash.get(key);
        int res = (computeC(n-1,m)% MOD + computeC(n-1,m-1)% MOD)% MOD;
        hash.put(key, res);
        return res;
    }
}

[Leetcode]2401. 最长优雅子数组

给你一个由 正 整数组成的数组 nums 。
如果 nums 的子数组中位于 不同 位置的每对元素按位 与(AND)运算的结果等于 0 ,则称该子数组为 优雅 子数组。
返回 最长 的优雅子数组的长度。
子数组 是数组中的一个 连续 部分。
注意:长度为 1 的子数组始终视作优雅子数组。

子数组 是数组中的一个 连续 部分:联想到滑动窗口。
记 mask 表示 优雅 子数组中的元素之和,初始时 mask=0。
对于当前元素 num,

  • \(mask \ \&\ num = 0\),则可将 num 添加到 优雅 子数组中:\(mask = mask + num\) .
  • 否则,需要将当前窗口(即当前的优雅子数组)中最左侧的元素 nums[left] 依次删除: \(mask = mask - nums[left]\) ,
    直至满足要求。

遍历元素过程中,记录下最大窗口长度即可。

因为异或刚好可以把 & = 1(失败)的元素去掉,所以可以再用双指针简化

class Solution {
    public int longestNiceSubarray(int[] nums) {
        int left = 0,  mask = 0, res = 0;
        for(int right=0;right<nums.length;right++) {
            while((mask & nums[right]) > 0) {
                mask ^= nums[left];
                left ++;
            }
            mask ^= nums[right];
            res = Math.max(res, right-left+1);
        }
        return res;
    }
}

[Leetcode] 6170. 会议室 III

给你一个整数 n ,共有编号从 0 到 n - 1 的 n 个会议室。
给你一个二维整数数组 meetings ,其中 meetings[i] = [starti, endi] 表示一场会议将会在 半闭 时间区间 [starti, endi) 举办。所有 starti 的值 互不相同 。
会议将会按以下方式分配给会议室:

  1. 每场会议都会在未占用且编号 最小 的会议室举办。
  2. 如果没有可用的会议室,会议将会延期,直到存在空闲的会议室。延期会议的持续时间和原会议持续时间 相同 。
  3. 当会议室处于未占用状态时,将会优先提供给原 开始 时间更早的会议。

返回举办最多次会议的房间 编号 。如果存在多个房间满足此条件,则返回编号 最小 的房间。
半闭区间 [a, b) 是 a 和 b 之间的区间,包括 a 但 不包括 b 。

双堆模拟.整个过程需要维护两件事:一是当前在进行的会议的结束时间和占用的会议室号,二是当前还有哪些会议室可用。更早结束的会议可更早将会议室腾出来,因此可以用优先队列维护会议的结束时间;而优先采用下标更小的会议室,也可以用一个优先队列(或者 TreeSet)保存所有可用的会议室下标。
维护过程:由于会议优先分配给开始时间早的,可以先按开始时间排序,顺序遍历会议。当前会议分配前,首先检查优先队列中有没有结束时间早于当前会议开始时间的会议,如果有,直接弹出这些会议即可。分配时会遇到两种情况:一是有空会议室,直接从另一个堆中拿出一个空会议室即可;二是没有空会议室,从优先队列中拿出一个最早结束的会议室分配给它,不过这种情况下当前会议的结束时间要设置为 上一个会议的结束时间 + 当前会议的持续时间,因为需要考虑等待的时间。

class Solution {
    private class Pair<K, V> {
        public K first;
        public V second;
        public Pair(K first, V second) {
            this.first = first;
            this.second = second;
        }
    }

    public int mostBooked(int n, int[][] meetings) {
        // 按开始时间排序
        Arrays.sort(meetings, (x, y) -> x[0] - y[0]);
        int[] count = new int[n];
        // TreeSet 记录此时所有可用的会议室(也可以用 PriorityQueue)
        TreeSet<Integer> meetingRoom = new TreeSet<>();
        for (int i = 0; i < n; i++)
            meetingRoom.add(i);
        // 优先队列记录 {结束时间,占用的会议室},优先按照结束时间排序
        PriorityQueue<Pair<Long, Integer>> heap = new PriorityQueue<>((x, y) -> Long.compare(x.first, y.first) == 0 ? x.second - y.second : Long.compare(x.first, y.first));
        // 顺序遍历会议室,先遍历到的肯定是开始时间早的
        for (int[] meeting : meetings) {
            // 如果有结束时间早于当前会议开始时间的会议,都先弹出优先队列(不会对后面的会议造成影响)
            while (!heap.isEmpty() && meeting[0] >= heap.peek().first)
                meetingRoom.add(heap.poll().second);
            // 如果 TreeSet 中获取不到空的会议室,就弹出最早结束的会议把会议室腾出来,但是结束时间要设置为"弹出会议的结束时间 + 当前会议的持续时间",因为要等待会议结束
            if (meetingRoom.isEmpty()) {
                Pair<Long, Integer> cur = heap.poll();
                count[cur.second]++;
                heap.offer(new Pair<>(cur.first + (long) meeting[1] - meeting[0], cur.second));
            }
            // 有空会议室直接分配即可
            else {
                int get = meetingRoom.first();
                heap.offer(new Pair<>((long) meeting[1], get));
                count[get]++;
                meetingRoom.remove(get);
            }
        }
        // 最后计算最大的会议室下标
        int max = 0;
        for (int i = 1; i < n; i++)
            if (count[max] < count[i])
                max = i;
        return max;
    }
}

参考:
LeetCode

posted @ 2022-09-14 19:28  Jamest  阅读(61)  评论(0编辑  收藏  举报