第 254 场力扣周赛(KMP、贪心、快速幂、二分+多源bfs、并查集 + 时光倒流)

本文介绍了LeetCode周赛中的三道算法题,分别是计算子字符串出现次数、构造特定数组和求最小非零乘积。解析了使用Java实现的多种解法,包括暴力搜索、字符串API和KMP算法。此外,还探讨了一道涉及路径规划的题目,提出二分查找结合BFS或并查集的解决方案。
摘要由CSDN通过智能技术生成

第 254 场力扣周赛

稀里糊涂双眼双眼惺忪的做了三道,错了4次。。。还是600来名

5843. 作为子字符串出现在单词中的字符串数目

题目描述

给你一个字符串数组 patterns 和一个字符串 word ,统计 patterns 中有多少个字符串是 word 的子字符串。返回字符串数目。

子字符串 是字符串中的一个连续字符序列。

示例 1:

输入:patterns = [“a”,“abc”,“bc”,“d”], word = “abc”
输出:3
解释:

  • “a” 是 “abc” 的子字符串。
  • “abc” 是 “abc” 的子字符串。
  • “bc” 是 “abc” 的子字符串。
  • “d” 不是 “abc” 的子字符串。
    patterns 中有 3 个字符串作为子字符串出现在 word 中。

示例 2:

输入:patterns = [“a”,“b”,“c”], word = “aaaaabbbbb”
输出:2
解释:

  • “a” 是 “aaaaabbbbb” 的子字符串。
  • “b” 是 “aaaaabbbbb” 的子字符串。
  • “c” 不是 “aaaaabbbbb” 的字符串。
    patterns 中有 2 个字符串作为子字符串出现在 word 中。

示例 3:

输入:patterns = [“a”,“a”,“a”], word = “ab”
输出:3
解释:patterns 中的每个字符串都作为子字符串出现在 word “ab” 中。

提示:

1 <= patterns.length <= 100
1 <= patterns[i].length <= 100
1 <= word.length <= 100
patterns[i] 和 word 由小写英文字母组成

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-strings-that-appear-as-substrings-in-word
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

子串问题,忘了java中有哪个API可以用了,然后第一道写了十分钟,麻了

class Solution {
    public int numOfStrings(String[] patterns, String word) {
        int res = 0;
        int l = patterns.length;
        int lw = word.length();
        for(int i = 0; i < l; i++){
            String s = patterns[i];
            int k = 0;
            boolean flag = false;
            for(int j = 0; j < lw; j++){
                char c = word.charAt(j);
                int temp = j;
                while(k < s.length() && j < lw && word.charAt(j) == s.charAt(k)){
                    j++;
                    k++;
                    if(k == s.length()){
                        flag = true;
                        break;
                    }
                }
                if(flag)
                    break;
                k = 0;
                j = temp;
            }
            if(flag)
                res++;
        }
        return res;
    }
}

调contians方法,五行…

class Solution {
    public int numOfStrings(String[] patterns, String word) {
        int res = 0;
        for(String s : patterns){
            if(word.contains(s))
                res++;
        }
        return res;
    }   
}

或者indexOf方法

class Solution {
    public int numOfStrings(String[] patterns, String word) {
        int res = 0;
        for(String s : patterns){
            if(word.indexOf(s) != -1)
                res++;
        }
        return res;
    }   
}

KMP再练一手:

class Solution {
    public int numOfStrings(String[] patterns, String word) {
        int res = 0;
        for(String s : patterns){
            int[] next = getNext(s);
            if(kmp(word, s, next))
                res++;
        }
        return res;
    }

    //首先,关键点,构建next的数组,是表示前后缀部分是否有相同
    public int[] getNext(String s){
        int l = s.length();
        int[] next = new int[l];
        int j = 0;
        for(int i = 1; i < l; i++){
            while(j > 0 && s.charAt(i) != s.charAt(j)){
                j = next[j - 1];
            }

            if(s.charAt(i) == s.charAt(j))
                j++;
            next[i] = j;
        }
        return next;
    }   

    public boolean kmp(String s, String t, int[] next){
        int ls = s.length();
        int lt = t.length();
        int j = 0;
        for(int i = 0; i < ls; i++){
            while(j > 0 && s.charAt(i) != t.charAt(j)){
                j = next[j - 1];
            }
            if(s.charAt(i) == t.charAt(j))
                j++;
            if(j == lt)
                return true;
        }
        return false;
    }
}

5832. 构造元素不等于两相邻元素平均值的数组

题目描述

给你一个 下标从 0 开始 的数组 nums ,数组由若干 互不相同的 整数组成。你打算重新排列数组中的元素以满足:重排后,数组中的每个元素都 不等于 其两侧相邻元素的 平均值 。

更公式化的说法是,重新排列的数组应当满足这一属性:对于范围 1 <= i < nums.length - 1 中的每个 i ,(nums[i-1] + nums[i+1]) / 2 不等于 nums[i] 均成立 。

返回满足题意的任一重排结果。

示例 1:

输入:nums = [1,2,3,4,5]
输出:[1,2,4,5,3]
解释:
i=1, nums[i] = 2, 两相邻元素平均值为 (1+4) / 2 = 2.5
i=2, nums[i] = 4, 两相邻元素平均值为 (2+5) / 2 = 3.5
i=3, nums[i] = 5, 两相邻元素平均值为 (4+3) / 2 = 3.5

示例 2:

输入:nums = [6,2,0,9,7]
输出:[9,7,6,2,0]
解释:
i=1, nums[i] = 7, 两相邻元素平均值为 (9+6) / 2 = 7.5
i=2, nums[i] = 6, 两相邻元素平均值为 (7+2) / 2 = 4.5
i=3, nums[i] = 2, 两相邻元素平均值为 (6+0) / 2 = 3

提示:

3 <= nums.length <= 10^5
0 <= nums[i] <= 10^5

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/array-with-elements-not-equal-to-average-of-neighbors
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

当时一层循环过不了,两层循环过了,都不知道为啥就过了,实际不太懂怎么做

class Solution {
    public int[] rearrangeArray(int[] nums) {
        //就是不是一个等差数列呗
        int l = nums.length;
        Arrays.sort(nums);
        for(int i = 1; i < l - 1; i++){
            if(nums[i + 1] - nums[i] == nums[i] - nums[i - 1])
                swap(nums, i - 1, i);

        }
        for(int i = 1; i < l - 1; i++){
            if(nums[i + 1] - nums[i] == nums[i] - nums[i - 1])
                swap(nums, i - 1, i);

        }
        return nums;
    }
    
    public void swap(int[] nums, int x, int y){
        int temp = nums[x];
        nums[x] = nums[y];
        nums[y] = temp;
    }
}

然后出来看了一下大佬们的思想:
排序,然后将数组分成小数、大数两部分,然后小、大、小、大穿插着放
其实报错的示例就是这么安排的,但是我当时没仔细看错了给定正确答案,我还以为是随便排的
又学到了一个小技巧

class Solution {
    public int[] rearrangeArray(int[] nums) {
        Arrays.sort(nums);
        int l = nums.length;
        int[] res = new int[l];
        int idx = 0;
        for(int i = 0; i < l; i += 2){
            res[i] = nums[idx++]; 
        }
        for(int i = 1; i < l; i += 2){
            res[i] = nums[idx++];
        }
        return res;
    }
}

或者直接交换相邻位置

class Solution {
    public int[] rearrangeArray(int[] nums) {
        //直接交换相邻位,道理还是那个
        //交换以后,会形成大小大小大...这种形式
        Arrays.sort(nums);
        int l = nums.length;
        for(int i = 0; i < l - 1; i += 2){
            int temp = nums[i];
            nums[i] = nums[i + 1];
            nums[i + 1] = temp;
        }
        return nums;
    }
}

5844. 数组元素的最小非零乘积

题目描述

给你一个正整数 p 。你有一个下标从 1 开始的数组 nums ,这个数组包含范围 [1, 2p - 1] 内所有整数的二进制形式(两端都 包含)。你可以进行以下操作 任意 次:

从 nums 中选择两个元素 x 和 y 。
选择 x 中的一位与 y 对应位置的位交换。对应位置指的是两个整数 相同位置 的二进制位。
比方说,如果 x = 1101 且 y = 0011 ,交换右边数起第 2 位后,我们得到 x = 1111 和 y = 0001 。

请你算出进行以上操作 任意次 以后,nums 能得到的 最小非零 乘积。将乘积对 109 + 7 取余 后返回。

注意:答案应为取余 之前 的最小值。

示例 1:

输入:p = 1
输出:1
解释:nums = [1] 。
只有一个元素,所以乘积为该元素。

示例 2:

输入:p = 2
输出:6
解释:nums = [01, 10, 11] 。
所有交换要么使乘积变为 0 ,要么乘积与初始乘积相同。
所以,数组乘积 1 * 2 * 3 = 6 已经是最小值。

示例 3:

输入:p = 3
输出:1512
解释:nums = [001, 010, 011, 100, 101, 110, 111]

  • 第一次操作中,我们交换第二个和第五个元素最左边的数位。
    - 结果数组为 [001, 110, 011, 100, 001, 110, 111] 。
  • 第二次操作中,我们交换第三个和第四个元素中间的数位。
    - 结果数组为 [001, 110, 001, 110, 001, 110, 111] 。
    数组乘积 1 * 6 * 1 * 6 * 1 * 6 * 7 = 1512 是最小乘积。

提示:

1 <= p <= 60

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-non-zero-product-of-the-array-elements
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

看到最小乘积,当时想的是正方形面积最大,而两条边差距越大的话,面积越小
所以要想整体乘积越小,就得数之间的差距越大
而看到示例中的第三个也印证了我的想法
然后再找规律,给定p,那么共有2^p-1个数,最大数也是2^p-1
而剩下的数,都可以通过交换,变成1和2^p-2(用base代替)
所以总共就有(2^p-2) / 2(用x代替)个base
也就是base的x次方
因为数很大,刚开始我想用double算的,结果60弄进去,还是溢出了
最后只能快速幂,然后每次取余了

对了,乘法取余:(a×b) mod c=(a mod c * b mod c) mod c 记住

class Solution {
    public static final int MOD = (int)1.0e9 + 7;
    public int minNonZeroProduct(int p) {
        //使数尽可能在两边
        //最小是
        //前后4位不能动
        if(p == 1)
            return 1;
        if(p == 2)
            return 6;
        long t = (long)Math.pow(2, p);
        long x = (t - 3) / 2;
        long base = t - 2;
        long ret = mypow(base, x + 1);
        //System.out.println(t);
        ret = ((ret % MOD) * ((t - 1) % MOD)) % MOD;
        return (int)ret;
    }
    
    public long mypow(long base, long x){
        long res = 1;
        base = base % MOD;
        int t = 1;
        while(x != 0){
            if((x & 1) == 1){
                res = (res * (base % MOD)) % MOD;
            }
            x >>= 1;
            base = ((base % MOD) * (base % MOD)) % MOD;
        }
        return res;
    }
}

5845. 你能穿过矩阵的最后一天

题目描述

给你一个下标从 1 开始的二进制矩阵,其中 0 表示陆地,1 表示水域。同时给你 row 和 col 分别表示矩阵中行和列的数目。

一开始在第 0 天,整个 矩阵都是 陆地 。但每一天都会有一块新陆地被 水 淹没变成水域。给你一个下标从 1 开始的二维数组 cells ,其中 cells[i] = [ri, ci] 表示在第 i 天,第 ri 行 ci 列(下标都是从 1 开始)的陆地会变成 水域 (也就是 0 变成 1 )。

你想知道从矩阵最 上面 一行走到最 下面 一行,且只经过陆地格子的 最后一天 是哪一天。你可以从最上面一行的 任意 格子出发,到达最下面一行的 任意 格子。你只能沿着 四个 基本方向移动(也就是上下左右)。

请返回只经过陆地格子能从最 上面 一行走到最 下面 一行的 最后一天 。

示例 1:
在这里插入图片描述

输入:row = 2, col = 2, cells = [[1,1],[2,1],[1,2],[2,2]]
输出:2
解释:上图描述了矩阵从第 0 天开始是如何变化的。
可以从最上面一行到最下面一行的最后一天是第 2 天。

示例 2:
在这里插入图片描述

输入:row = 2, col = 2, cells = [[1,1],[1,2],[2,1],[2,2]]
输出:1
解释:上图描述了矩阵从第 0 天开始是如何变化的。
可以从最上面一行到最下面一行的最后一天是第 1 天。

示例 3:
在这里插入图片描述

输入:row = 3, col = 3, cells = [[1,2],[2,1],[3,3],[2,2],[1,1],[1,3],[2,3],[3,2],[3,1]]
输出:3
解释:上图描述了矩阵从第 0 天开始是如何变化的。
可以从最上面一行到最下面一行的最后一天是第 3 天。

提示:

2 <= row, col <= 2 * 10^4
4 <= row * col <= 2 * 10^4
cells.length == row * col
1 <= ri <= row
1 <= ci <= col
cells 中的所有格子坐标都是 唯一 的。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/last-day-where-you-can-still-cross
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

这道题我看范围,实在没想到用dfs能过,当时觉得dfs要走的情况很多,一直在想怎么把水域连成一条线啥的。。。
现在想想,也就是把所有方格里的点都遍历一次,复杂度也就是10的4次,在加个二分,肯定超时不了。。。失策了
这个题挺简单的

思路就是二分找能通过的天数,然后查看一天是否能通过,用bfs或者dfs

第一种:二分加多源bfs

class Solution {
    int[][] dir = {{0,1},{0,-1},{1,0},{-1,0}};
    public int latestDayToCross(int row, int col, int[][] cells) {
        int left = 0;
        int right = row * col;
        while(left < right){
            int mid = (right - left + 1) / 2 + left;
            //构建图
            int[][] graph = new int[row][col];
            //将水域赋值为1,是将mid这一天之前的水域
            for(int i = 0; i < mid; i++){
                //水域下标从1开始的
                int r = cells[i][0] - 1;
                int c = cells[i][1] - 1;
                graph[r][c] = 1;
            }

            boolean[][] used = new boolean[row][col];
            Queue<int[]> queue = new LinkedList<>();
            //将第一行的所有陆地加入队列中
            for(int i = 0; i < col; i++){
                if(graph[0][i] == 0)
                    queue.offer(new int[]{0, i});
                    used[0][i] = true;
            }

            //bfs找通路
            boolean found = false;
            while(!queue.isEmpty()){
                int[] point = queue.poll();
                for(int[] d : dir){
                    int nx = point[0] + d[0];
                    int ny = point[1] + d[1];
                    if(nx >= 0 && nx < row && ny >= 0 && ny < col){
                        //如果遍历过了,返回
                        if(used[nx][ny])
                            continue;
                        //如果是水,跳过
                        if(graph[nx][ny] == 1)
                            continue;
                        //如果到达了最后一行,返回
                        if(nx == row - 1){
                            found = true;
                            break;
                        }
                        queue.offer(new int[]{nx, ny});
                        used[nx][ny] = true;   
                    }               
                }
                if(found)
                    break;
            }
            //如果找到了通路,那么找右边
            if(found)
                left = mid;
            else
                right = mid - 1;
            //System.out.println(left);
        }
        //输出的left是能走通的最后一天的下一天的下标,正好就是结果
        return left;
    }
}

并查集+时光倒流
复习一下并查集
思路:从后往前,将每一块新增的陆地放在并查集中,将和一个陆地相邻的陆地union起来,
因为第一行和最后一行走到就行,所以两个标志来表示第一行和最后一行是否到达过
如果第一行和最后一行连通了,那么就返回当前下标

class Solution {
    //并查集写一下
    int[][] dir = {{0,1},{0,-1},{1,0},{-1,0}};
    public int latestDayToCross(int row, int col, int[][] cells) {
        UnionFind uf = new UnionFind(row * col + 2);
        int S = row * col;      //表示第一行
        int T = S + 1;          //表示最后一行


        //表示当前点是否被加入了,只有被加入的点才能连通
        boolean[][] used = new boolean[row][col];
        for(int i = row * col - 1; i >= 0; i--){
            int x = cells[i][0] - 1;
            int y = cells[i][1] - 1;
            //这个点被加入了
            used[x][y] = true;
            int idx = x * col + y;
            //遍历四个方向
            for(int[] d : dir){
                int nx = x + d[0];
                int ny = y + d[1];
                //如果这个点在范围内,并且被加入过,那么将这两个点连通
                if(nx >= 0 && nx < row && ny >= 0 && ny < col && used[nx][ny]){
                    //System.out.println(nx);
                    uf.union(idx, nx * col + ny);
                }
            }
            //如果是第一行或者最后一行,将其连通
            if(x == 0)
                uf.union(idx, S);
            if(x == row - 1)
                uf.union(idx, T);
            //如果第一行和最后一行连通了
            if(uf.query(S, T)){
                return i;
            }
        }
        return -1;
    }
}

class UnionFind{
    int[] parents;
    int n;
    int count;  //连通分量的数目
    int[] rank; //秩,表示和当前这个点连接的高度,相当于树的高度
    
    //构造器
    public UnionFind(int n){
        this.n = n;
        parents = new int[n];
        rank = new int[n];
        count = n;
        for(int i = 0; i < n; i++){
            parents[i] = i;
            rank[i] = 1;
        }
    }

    public void union(int x, int y) {
        int px = find(x);
        int py = find(y);
        if(px == py)
            return;
        //如果px连接的点少,那么就把px挂在py上
        if(rank[px] < rank[py]){
            parents[px] = py;
        }
        else if(rank[px] > rank[py]){
            parents[py] = px;
        }
        //如果秩相同,那么因为px挂在了py上,所以py的秩+1;
        if(rank[px] == rank[py]){
            parents[px] = py;
            rank[py]++;
        }
        count--;
    }

    public int find(int x){
        if(x != parents[x]){
            parents[x] = find(parents[x]);
        }
        return parents[x];
    }
    //查询x和y是否连通
    public boolean query(int x, int y){
        return find(x) == find(y);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值