第 221 场周赛

130 阅读4分钟

image-20201227150353707

5637. 判断字符串的两半是否相似

题目

给你一个偶数长度的字符串 s 。将其拆分成长度相同的两半,前一半为 a ,后一半为 b

两个字符串 相似 的前提是它们都含有相同数目的元音('a''e''i''o''u''A''E''I''O''U')。注意,s 可能同时含有大写和小写字母。

如果 ab 相似,返回 true ;否则,返回 false

示例 1:

输入:s = "book"
输出:true
解释:a = "bo" 且 b = "ok" 。a 中有 1 个元音,b 也有 1 个元音。所以,a 和 b 相似。

示例 2:

输入:s = "textbook"
输出:false
解释:a = "text" 且 b = "book" 。a 中有 1 个元音,b 中有 2 个元音。因此,ab 不相似。
注意,元音 o 在 b 中出现两次,记为 2 个。

示例 3:

输入:s = "MerryChristmas"
输出:false

示例 4:

输入:s = "AbCdEfGh"
输出:true

提示:

  • 2 <= s.length <= 1000
  • s.length 是偶数
  • s大写和小写 字母组成

代码

第一题一般都是水题

class Solution {
    public boolean halvesAreAlike(String s) {
        int snum = 0;
        int tnum = 0;
        int len = s.length();
        Set<Character> set = new HashSet<>(){{
            add('a');
            add('e');
            add('i');
            add('o');
            add('u');
            add('A');
            add('E');
            add('I');
            add('O');
            add('U');
        }};
        
        String ss = s.substring(0, len / 2);
        String st = s.substring(len / 2, len);

        for (int i = 0; i <= len / 2 - 1; i++) {
            if (set.contains(ss.charAt(i))) snum++;
            if (set.contains(st.charAt(i))) tnum++;
        }
        
        return snum == tnum;
    }
}

5638. 吃苹果的最大数目

题目

有一棵特殊的苹果树,一连 n 天,每天都可以长出若干个苹果。在第 i 天,树上会长出 apples[i] 个苹果,这些苹果将会在 days[i] 天后(也就是说,第 i + days[i] 天时)腐烂,变得无法食用。也可能有那么几天,树上不会长出新的苹果,此时用 apples[i] == 0days[i] == 0 表示。

你打算每天 最多 吃一个苹果来保证营养均衡。注意,你可以在这 n 天之后继续吃苹果。

给你两个长度为 n 的整数数组 daysapples ,返回你可以吃掉的苹果的最大数目*。*

示例 1:

输入:apples = [1,2,3,5,2], days = [3,2,1,4,2]
输出:7
解释:你可以吃掉 7 个苹果:
- 第一天,你吃掉第一天长出来的苹果。
- 第二天,你吃掉一个第二天长出来的苹果。
- 第三天,你吃掉一个第二天长出来的苹果。过了这一天,第三天长出来的苹果就已经腐烂了。
- 第四天到第七天,你吃的都是第四天长出来的苹果。

示例 2:

输入:apples = [3,0,0,0,0,2], days = [3,0,0,0,0,2]
输出:5
解释:你可以吃掉 5 个苹果:
- 第一天到第三天,你吃的都是第一天长出来的苹果。
- 第四天和第五天不吃苹果。
- 第六天和第七天,你吃的都是第六天长出来的苹果。

提示:

  • apples.length == n
  • days.length == n
  • 1 <= n <= 2 * 104
  • 0 <= apples[i], days[i] <= 2 * 104
  • 只有在 apples[i] = 0 时,days[i] = 0 才成立

代码

5638. 吃苹果的最大数目

虽然我比赛时用的方法能过测试用例,但是明显是错误的,因为如果[3,7]区间只有一个苹果,这个时候就不满足条件。这里我默认为区间里的苹果是够的

输入 [1,2,3,1,2] [3,2,1,4,2] 输出 7 预期结果 6

class Solution {
    public int eatenApples(int[] apples, int[] days) {
        int[][] intervals = new int[apples.length][2];
        if (apples.length == 1) {
            return apples[0] >= days[0] ? days[0] : apples[0];
        }
        for (int i = 0; i < apples.length; i++) {
            for (int j = 0; j < 2; j++) {
                if (j == 0) intervals[i][j] = i;
                if (j == 1) intervals[i][j] = i + days[i];
            }
        }
        int[][] res = merge(intervals);
        int ret = 0;
        for (int i = 0; i < res.length; i++) {
            ret = ret + (res[i][1] - res[i][0]);
        }
        return ret;
    }
    
     public int[][] merge(int[][] intervals) {
        if (intervals.length == 0) {
            return new int[0][2];
        }
        Arrays.sort(intervals, new Comparator<int[]>() {
            public int compare(int[] interval1, int[] interval2) {
                return interval1[0] - interval2[0];
            }
        });
        List<int[]> merged = new ArrayList<int[]>();
        for (int i = 0; i < intervals.length; ++i) {
            int L = intervals[i][0], R = intervals[i][1];
            if (merged.size() == 0 || merged.get(merged.size() - 1)[1] < L) {
                merged.add(new int[]{L, R});
            } else {
                merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], R);
            }
        }
        return merged.toArray(new int[merged.size()][]);
    }
}

正确思路:优先队列,优先吃最早过期的

class Solution {
    public int eatenApples(int[] apples, int[] days) {
        // 优先队列 队首为最快过期的 int[0] apple number int[1] 过期时间 按过期时间升序
        PriorityQueue<int[]> priorityQueue = new PriorityQueue<>((o1, o2) -> o1[1] - o2[1]);
        int ret = 0;
        // 遍历apples数组和最后储存的apple
        for (int i = 0; i < apples.length || priorityQueue.size() > 0; i++) {
            // 1. 移除队首过期的苹果
            while(!priorityQueue.isEmpty()) {
                int[] temp = priorityQueue.peek();
                if (temp[1] <= i) priorityQueue.poll();
                else break;
            }
            // 2. 添加新的苹果  i < apples.length 用来限制当遍历完之后 还有储存的水果
            if (i < apples.length && apples[i] > 0) {
                priorityQueue.add(new int[]{apples[i], days[i] + i});
            }
            // 3. 优先吃掉最快过期的
            int[] eat = priorityQueue.peek();
            if (eat != null && eat[0] > 0) {
                ret++;
                eat[0] -= 1;
                if (eat[0] == 0) priorityQueue.poll();
            }
        }
        return ret;
    }
}
class Solution {
    public int eatenApples(int[] apples, int[] days) {
        //数组的第一位是过期日期。按这个排序
        PriorityQueue<int[]> priorityQueue = new PriorityQueue<>(Comparator.comparingInt(o -> o[0]));
        int length = apples.length;
        int eat = 0;

        for (int i = 0; i < length; i++) {
            int day = i + days[i];//苹果过期日期
            priorityQueue.add(new int[]{day, apples[i]});

            while (!priorityQueue.isEmpty()) {
                int[] poll = priorityQueue.poll();
                if (poll[0] > i) {//苹果是否过期
                    eat++;//吃苹果
                    poll[1]--;
                    if (poll[0] != i + 1 && poll[1] > 0) {//苹果没了
                        priorityQueue.add(poll);
                    }
                    break;
                }
            }
        }

        //还有苹果
        int day = length;
        while (!priorityQueue.isEmpty()) {
            int[] poll = priorityQueue.poll();
            if (poll[0] > day) {
                eat++;
                poll[1]--;
                if (poll[0] != day + 1 && poll[1] > 0) {//苹果没了
                    priorityQueue.add(poll);
                }
                day++;
            }
        }
        return eat;
    }
}

5210. 球会落何处

题目

用一个大小为 m x n 的二维网格 grid 表示一个箱子。你有 n 颗球。箱子的顶部和底部都是开着的。

箱子中的每个单元格都有一个对角线挡板,跨过单元格的两个角,可以将球导向左侧或者右侧。

  • 将球导向右侧的挡板跨过左上角和右下角,在网格中用 1 表示。
  • 将球导向左侧的挡板跨过右上角和左下角,在网格中用 -1 表示。

在箱子每一列的顶端各放一颗球。每颗球都可能卡在箱子里或从底部掉出来。如果球恰好卡在两块挡板之间的 "V" 形图案,或者被一块挡导向到箱子的任意一侧边上,就会卡住。

返回一个大小为 n 的数组 answer ,其中 answer[i] 是球放在顶部的第 i 列后从底部掉出来的那一列对应的下标,如果球卡在盒子里,则返回 -1

示例 1:

img

输入:grid = [[1,1,1,-1,-1],[1,1,1,-1,-1],[-1,-1,-1,1,1],[1,1,1,1,-1],[-1,-1,-1,-1,-1]]
输出:[1,-1,-1,-1,-1]
解释:示例如图:
b0 球开始放在第 0 列上,最终从箱子底部第 1 列掉出。
b1 球开始放在第 1 列上,会卡在第 23 列和第 1 行之间的 "V" 形里。
b2 球开始放在第 2 列上,会卡在第 23 列和第 0 行之间的 "V" 形里。
b3 球开始放在第 3 列上,会卡在第 23 列和第 0 行之间的 "V" 形里。
b4 球开始放在第 4 列上,会卡在第 23 列和第 1 行之间的 "V" 形里。

示例 2:

输入:grid = [[-1]]
输出:[-1]
解释:球被卡在箱子左侧边上。

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 100
  • grid[i][j]1-1

代码

卡着时间过了这道题,但是代码写太多,思路也不是很简便,直接用模拟来写的

class Solution {
    public int[] findBall(int[][] grid) {
        int[] ret = new int[grid[0].length];
        if (grid[0].length == 0) return new int[]{-1};
        if (grid[0].length == 1) return new int[]{-1};

        for (int i = 0; i < grid[0].length; i++) {
            ret[i] = i;
        }
        
        for (int row = 0; row < grid.length; row++) {
            // 如果相加为0 并且-1在右边 证明需要置-1;
            for (int col = 1; col < grid[0].length; col++) {
                if ((grid[row][col] + grid[row][col - 1]) == 0 && grid[row][col] == -1) {
                    if (retIndex(col, ret) != -1) ret[retIndex(col, ret)]= -1;
                    if (retIndex(col - 1, ret) != -1) ret[retIndex(col - 1, ret)] = -1;
                }
            }
            
            int[] temp = new int[ret.length];
            for (int i = 0; i < ret.length; i++) {
                temp[i] = ret[i];
            }
            
            // 移动每个球的位置
            for (int col = 0; col < grid[0].length; col++) {
                if(grid[row][col] == 1) {
                    if (retIndex(col, temp) != -1 && ret[retIndex(col, temp)] != -1) {
                        ret[retIndex(col, temp)]++;
                        if (ret[retIndex(col, temp)] == grid[0].length) ret[retIndex(col, temp)] = -1;
                    } 
                } else {
                    if (retIndex(col, temp) != -1 && ret[retIndex(col, temp)] != -1) {
                        ret[retIndex(col, temp)]--;
                    } 
                }
            }
        }
        return ret;
    }
    
    public int retIndex(int n, int[] num) {
        for (int i = 0; i < num.length; i++) {
            if (num[i] == n) return i;
        }
        return -1;
    }
}

正确的思路:递归(DFS)、动态规划(BFS)

class Solution {
    public int[] findBall(int[][] grid) {
        int row = grid.length;
        int col = grid[0].length;
        int[] ans = new int[col];
        for (int i = 0; i < col; i++) {
            ans[i] = out(grid, row, col, i, 0);
        }
        return ans;
    }

    private int out(int[][] grid, int row, int col, int x, int y) {
        //到达底部
        if (y == row) {
            return x;
        }

        //卡在边缘
        if (x == 0 && grid[y][x] == -1) {
            return -1;
        }
        if (x == col - 1 && grid[y][x] == 1) {
            return -1;
        }

        //卡在中间
        if (grid[y][x] == 1 && grid[y][x + 1] == -1) {
            return -1;
        }
        if (grid[y][x] == -1 && grid[y][x - 1] == 1) {
            return -1;
        }

        //到达下一层
        return out(grid, row, col, x + grid[y][x], y + 1);
    }

}
class Solution {
    public int[] findBall(int[][] grid) {
        int row = grid.length;
        int col = grid[0].length;
        int[] ans = new int[col];

        // 默认位置
        for (int i = 0; i < col; i++) {
            ans[i] = i;
        }

        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                if (ans[j] == -1) {//忽略卡住的球
                    continue;
                }
                if (grid[i][ans[j]] == 1 && ans[j] + 1 < col && grid[i][ans[j] + 1] == 1) {
                    //右移
                    ans[j]++;
                } else if (grid[i][ans[j]] == -1 && ans[j] - 1 >= 0 && grid[i][ans[j] - 1] == -1) {
                    //左移
                    ans[j]--;
                } else {
                    //卡住
                    ans[j] = -1;
                }
            }
        }
        return ans;
    }
}

5640. 与数组中元素的最大异或值

题目

给你一个由非负整数组成的数组 nums 。另有一个查询数组 queries ,其中 queries[i] = [xi, mi]

i 个查询的答案是 xi 和任何 nums 数组中不超过 mi 的元素按位异或(XOR)得到的最大值。换句话说,答案是 max(nums[j] XOR xi) ,其中所有 j 均满足 nums[j] <= mi 。如果 nums 中的所有元素都大于 mi,最终答案就是 -1

返回一个整数数组 answer 作为查询的答案,其中 answer.length == queries.lengthanswer[i] 是第 i 个查询的答案。

示例 1:

输入:nums = [0,1,2,3,4], queries = [[3,1],[1,3],[5,6]]
输出:[3,3,7]
解释:
1) 0 和 1 是仅有的两个不超过 1 的整数。0 XOR 3 = 31 XOR 3 = 2 。二者中的更大值是 3 。
2) 1 XOR 2 = 3.
3) 5 XOR 2 = 7.

示例 2:

输入:nums = [5,2,4,6,6,3], queries = [[12,4],[8,1],[6,3]]
输出:[15,-1,5]

提示:

  • 1 <= nums.length, queries.length <= 105
  • queries[i].length == 2
  • 0 <= nums[j], xi, mi <= 109

代码

没做出来,看评论说这道题也需要用之前的离线思想

Trie字典树+离线思想

nums从小到大排序, queries 从小到大按照mi排序 遍历queries,在nums[i]中所有<=m的数字,可以加入当前字典树中(insert函数) 然后用trie树来查找当前最大异或的数字(query函数),trie树存储的是二进制数字

class Solution {
    int[][] son;
    int idx;
    public int[] maximizeXor(int[] nums, int[][] queries) {
        Arrays.sort(nums);
        int n = nums.length;
        son = new int[n * 31][2];
        // 添加index
        Pair[] pair = new Pair[queries.length];
        for(int i = 0; i < queries.length; i++) {
            pair[i] = new Pair(queries[i][0], queries[i][1], i);
        }
        //把queries按照mi从小到大排序
        Arrays.sort(pair, (o1, o2) ->(o1.m - o2.m));
        int[] ans = new int[queries.length];
        int pos = 0;
        for(Pair q : pair) {
            //所有<=m的数字,可以加入当前字典树中
            while(pos < n && nums[pos] <= q.m) {
                insert(nums[pos]);
                pos++;
            }
            //写答案
            if(idx == 0) {
                ans[q.index] = -1;
            }else {
                int t = query(q.x);
                ans[q.index] = q.x ^ t;
            }
        }
        return ans;
    }
    //返回此时trie树中的数字里,与a异或最大的数字
    private int query(int a){
        int p = 0, res = 0;
        for(int i = 30; i >= 0; i--){
            int u = a >> i & 1;//最高位,次高位,...
            if(son[p][u ^ 1] != 0){//如果可以走,u是1,就往0走,u是0,就往1走
                res = res * 2 + u ^ 1;
                p = son[p][u ^ 1];
            }else{
                res = res *2 + u;
                p = son[p][u];
            }
        }
        return res;
    }
    //在trie树中插入数字a
    private void insert(int a){
        int p = 0;
        for(int i = 30; i >= 0; i--){
            int u = a >> i & 1;
            if(son[p][u] == 0){
                son[p][u] = ++idx;
            }
            p = son[p][u];
        }
    }
}
class Pair{
    int x;
    int m;
    int index;
    public Pair(int x, int m, int index) {
        this.x = x;
        this.m = m;
        this.index = index;
    }
}