周赛345(模拟、分类讨论、DFS求完全联通分量)

周赛345

2682. 找出转圈游戏输家

难度简单0收藏分享切换为英文接收动态反馈

n 个朋友在玩游戏。这些朋友坐成一个圈,按 顺时针方向1n 编号。从第 i 个朋友的位置开始顺时针移动 1 步会到达第 (i + 1) 个朋友的位置(1 <= i < n),而从第 n 个朋友的位置开始顺时针移动 1 步会回到第 1 个朋友的位置。

游戏规则如下:

1 个朋友接球。

  • 接着,第 1 个朋友将球传给距离他顺时针方向 k 步的朋友。
  • 然后,接球的朋友应该把球传给距离他顺时针方向 2 * k 步的朋友。
  • 接着,接球的朋友应该把球传给距离他顺时针方向 3 * k 步的朋友,以此类推。

换句话说,在第 i 轮中持有球的那位朋友需要将球传递给距离他顺时针方向 i * k 步的朋友。

当某个朋友第 2 次接到球时,游戏结束。

在整场游戏中没有接到过球的朋友是 输家

给你参与游戏的朋友数量 n 和一个整数 k ,请按升序排列返回包含所有输家编号的数组 answer 作为答案。

示例 1:

输入:n = 5, k = 2
输出:[4,5]
解释:以下为游戏进行情况:
1)第 1 个朋友接球,第 1 个朋友将球传给距离他顺时针方向 2 步的玩家 —— 第 3 个朋友。
2)第 3 个朋友将球传给距离他顺时针方向 4 步的玩家 —— 第 2 个朋友。
3)第 2 个朋友将球传给距离他顺时针方向 6 步的玩家 —— 第 3 个朋友。
4)第 3 个朋友接到两次球,游戏结束。

示例 2:

输入:n = 4, k = 4
输出:[2,3,4]
解释:以下为游戏进行情况:
1)第 1 个朋友接球,第 1 个朋友将球传给距离他顺时针方向 4 步的玩家 —— 第 1 个朋友。
2)第 1 个朋友接到两次球,游戏结束。

提示:

  • 1 <= k <= n <= 50

模拟

class Solution {
    public int[] circularGameLosers(int n, int k) {
        int[] vis = new int[n];
        vis[0] = 1;
        int cur = 0, t = 1;
        while(true){
            cur = (cur + t * k) % n;
            if(vis[cur] == 1) break;
            vis[cur] = 1;
            t += 1;
        }
        List<Integer> res = new ArrayList<>();
        for(int i = 0; i < n; i++){
            if(vis[i] == 0)
                res.add(i+1);
        }
        return res.stream().mapToInt(Integer::intValue).toArray();
    }
}

时间复杂度:O(n)

2683. 相邻值的按位异或

难度中等0

下标从 0 开始、长度为 n 的数组 derived 是由同样长度为 n 的原始 二进制数组 original 通过计算相邻值的 **按位异或(⊕)**派生而来。

特别地,对于范围 [0, n - 1] 内的每个下标 i

  • 如果 i = n - 1 ,那么 derived[i] = original[i] ⊕ original[0]
  • 否则 derived[i] = original[i] ⊕ original[i + 1]

给你一个数组 derived ,请判断是否存在一个能够派生得到 derived有效原始二进制数组 original

如果存在满足要求的原始二进制数组,返回 true ;否则,返回 false

  • 二进制数组是仅由 01 组成的数组。

示例 1:

输入:derived = [1,1,0]
输出:true
解释:能够派生得到 [1,1,0] 的有效原始二进制数组是 [0,1,0] :
derived[0] = original[0] ⊕ original[1] = 0 ⊕ 1 = 1 
derived[1] = original[1] ⊕ original[2] = 1 ⊕ 0 = 1
derived[2] = original[2] ⊕ original[0] = 0 ⊕ 0 = 0

示例 2:

输入:derived = [1,1]
输出:true
解释:能够派生得到 [1,1] 的有效原始二进制数组是 [0,1] :
derived[0] = original[0] ⊕ original[1] = 1
derived[1] = original[1] ⊕ original[0] = 1

示例 3:

输入:derived = [1,0]
输出:false
解释:不存在能够派生得到 [1,0] 的有效原始二进制数组。

提示:

  • n == derived.length
  • 1 <= n <= 105
  • derived 中的值不是 0 就是 1

方法一:分类讨论(反向思考)

思路启发:2437. 有效时间的数目,由于二进制数组是仅由 01 组成的数组。那么原数组只能有两种情况,a[0] = 0a[0] = 1,分别构造这两原数组

class Solution {
    public boolean doesValidArrayExist(int[] derived) {
        // 两种情况,a[0] = 0 , a[0] = 1 分别尝试能否构成derived数组
        int n = derived.length;
        int[] ori = new int[n];
        // 先构造a[0] = 0的情况,根据derived[i]取值判断a[i+1]的取值情况,最后根据a[n-1]和a[0]的取值是否合法判断是否为答案
        for(int i = 0; i < n; i++){
            if(i != n-1){
                if(derived[i] == 1){
                    ori[i+1] = 1 - ori[i];
                }else{
                    ori[i+1] = ori[i];
                }
            }else{
                if((derived[i] == 1 && ori[i] != ori[0]) || 
                    (derived[i] == 0 && ori[i] == ori[0]))
                    return true;
            }
        }
        ori = new int[n];
        ori[0] = 1;
        for(int i = 0; i < n; i++){
            if(i != n-1){
                if(derived[i] == 1){
                    ori[i+1] = 1 - ori[i];
                }else{
                    ori[i+1] = ori[i];
                }
            }else{
                if((derived[i] == 1 && ori[i] != ori[0]) || 
                    (derived[i] == 0 && ori[i] == ori[0]))
                    return true;
            }
        }
        return false;
    }
}

方法二:找规律、推公式

class Solution {
    /**
    找规律:推公式
    由 a ^ a = 0
       a ^ b = c ,两边同时异或 a 得到 b = c ^ a

    公式一:derived[i] = original[i] ⊕ original[i + 1]
    a[0]
    a[1] = b[0] ^ a[0]
    a[2] = b[1] ^ a[1] = b[1] ^ b[0] ^ a[0]
    a[3] = b[2] ^ b[1] ^ b[0] ^ a[0]
    公式二:如果 i = n - 1 ,那么 derived[i] = original[i] ⊕ original[0]
    a[3] ^ a[0] = b[3]

    两个公式合起来(左边异或左边,右边异或右边)
    a[0] = b[2] ^ b[1] ^ b[0] ^ a[0] ^ b[3]
    ==> 0 = b[3] ^ b[2] ^ b[1] ^ b[0]
    判断所有数异或和是否为0
     */
    public boolean doesValidArrayExist(int[] derived) {
        int i = 0;
        for(int d : derived) i ^= d;
        return i == 0;
    }
}

时间复杂度:O(n)

2684. 矩阵中移动的最大次数(求最大路径长度)

难度中等0收藏分享切换为英文接收动态反馈

给你一个下标从 0 开始、大小为 m x n 的矩阵 grid ,矩阵由若干 整数组成。

你可以从矩阵第一列中的 任一 单元格出发,按以下方式遍历 grid

  • 从单元格 (row, col) 可以移动到 (row - 1, col + 1)(row, col + 1)(row + 1, col + 1) 三个单元格中任一满足值 严格 大于当前单元格的单元格。

返回你在矩阵中能够 移动最大 次数。

示例 1:

img

输入:grid = [[2,4,3,5],[5,4,9,3],[3,4,2,11],[10,9,13,15]]
输出:3
解释:可以从单元格 (0, 0) 开始并且按下面的路径移动:
- (0, 0) -> (0, 1).
- (0, 1) -> (1, 2).
- (1, 2) -> (2, 3).
可以证明这是能够移动的最大次数。

示例 2:

输入:grid = [[3,2,4],[2,1,9],[1,1,7]]
输出:0
解释:从第一列的任一单元格开始都无法移动。

提示:

  • m == grid.length
  • n == grid[i].length
  • 2 <= m, n <= 1000
  • 4 <= m * n <= 105
  • 1 <= grid[i][j] <= 106

方法一:DFS(递归==> 记忆化搜索)

class Solution {
    int[][] dirts = {{-1, 1}, {0, 1}, {1, 1}};
    int[][] grid;
    int m, n;
    int[][] cache; 
    public int maxMoves(int[][] grid) {
        // dfs求最大路径长
        int ans = 0;
        m = grid.length; n = grid[0].length;
        cache = new int[m][n];
        for(int i = 0; i < m; i++)
            Arrays.fill(cache[i], -1);
        this.grid = grid;
        for(int i = 0; i < m; i++){
            ans = Math.max(ans, dfs(i, 0));
        }
        return ans;
    }

    public int dfs(int i, int j){
        int ans = 0;
        if(cache[i][j] >= 0) return cache[i][j];
        for(int[] d : dirts){
            int x = i + d[0], y = j + d[1];
            if(x < 0 || x >= m || y < 0 || y >= n)
                continue;
            if(grid[x][y] > grid[i][j])
                ans = Math.max(ans, dfs(x, y) + 1);
        }
        return cache[i][j] = ans;
    }
}

方法二:记忆化转DP

class Solution {
    public int maxMoves(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        // 定义f[i][j]表示从f[i][j]开始按照要求移动能走的最大次数
        int[][] f = new int[m][n];
        // 思考清楚递推顺序
        // f[i][j] 需要 f[i-1][j+1] f[i][j+1] f[i+1][j+1],因此要先枚举j再枚举i
        for(int j = n-2; j >= 0; j--){
            for(int i = 0; i < m; i++){
                for (int k = Math.max(i - 1, 0); k < Math.min(i + 2, m); k++)
                    if (grid[k][j + 1] > grid[i][j])
                        f[i][j] = Math.max(f[i][j], f[k][j + 1] + 1);
            }
        }
        int ans = 0;
        for(int i = 0; i < m; i++)
            ans = Math.max(ans, f[i][0]);
        return ans;
    }
}
/**
class Solution:
    def maxMoves(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        @cache
        def dfs(i: int, j: int) -> int:
            if j == n - 1:
                return 0
            res = 0
            for k in i - 1, i, i + 1:
                if 0 <= k < m and grid[k][j + 1] > grid[i][j]:
                    res = max(res, dfs(k, j + 1) + 1)
            return res
        return max(dfs(i, 0) for i in range(m))
 */

方法三:BFS

class Solution:
    def maxMoves(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        q = range(m) # 记录行号
        print(q) # range(0, 4)
        vis = [-1] * m
        # 从左往右遍历,每一轮向右搜索一列
        # 只要能搜索到第 k 列,那么答案至少为k,当队列长度=0时,表示走不到第 k 列了,答案为k-1
        for j in range(n-1):
            tmp = q
            q = []
            for i in tmp:
                for k in i-1, i, i+1:
                    if 0 <= k < m and vis[k] != j and grid[k][j + 1] > grid[i][j]:
                        vis[k] = j # 时间戳标记,避免重复创建 vis 数组
                        q.append(k)
            if len(q) == 0:
                return j
        return n-1

🎉相似题目:网格图 DP

https://leetcode.cn/problems/maximum-number-of-moves-in-a-grid/submissions/

2685. 统计完全连通分量的数量

难度中等2

给你一个整数 n 。现有一个包含 n 个顶点的 无向 图,顶点按从 0n - 1 编号。给你一个二维整数数组 edges 其中 edges[i] = [ai, bi] 表示顶点 aibi 之间存在一条 无向 边。

返回图中 完全连通分量 的数量。

如果在子图中任意两个顶点之间都存在路径,并且子图中没有任何一个顶点与子图外部的顶点共享边,则称其为 连通分量

如果连通分量中每对节点之间都存在一条边,则称其为 完全连通分量

示例 1:

img

输入:n = 6, edges = [[0,1],[0,2],[1,2],[3,4]]
输出:3
解释:如上图所示,可以看到此图所有分量都是完全连通分量。

示例 2:

img

输入:n = 6, edges = [[0,1],[0,2],[1,2],[3,4],[3,5]]
输出:1
解释:包含节点 0、1 和 2 的分量是完全连通分量,因为每对节点之间都存在一条边。
包含节点 3 、4 和 5 的分量不是完全连通分量,因为节点 4 和 5 之间不存在边。
因此,在图中完全连接分量的数量是 1 。

提示:

  • 1 <= n <= 50
  • 0 <= edges.length <= n * (n - 1) / 2
  • edges[i].length == 2
  • 0 <= ai, bi <= n - 1
  • ai != bi
  • 不存在重复的边

方法一:DFS

class Solution {
    List<Integer>[] g;
    boolean[] vis, tmp;
    public int countCompleteComponents(int n, int[][] edges) {
        g = new ArrayList[n];
        Arrays.setAll(g, e -> new ArrayList<>());
        for(int[] e : edges){
            g[e[0]].add(e[1]);
            g[e[1]].add(e[0]);
        }
        int ans = 0;
        vis = new boolean[n];
        tmp = new boolean[n];
        // 遍历每一个连通分量
        for(int i = 0; i < n; i++){
            if(vis[i]) continue;
            vis[i] = true;  
            // 统计每一个联通分量 点的个数 和 边的个数,边的个数应该为节点数 的 n(n-1) / 2
            // 统计连通分类个数
            tmp[i] = true;
            int num = dfsnum(i);
            int edge = dfs(i) / 2;
            if(edge == num * (num-1) / 2)
                ans += 1;
        }
        return ans;
    }
	// dfs求连通块节点个数
    public int dfsnum(int x){
        int res = 1;
        for(int y : g[x]){
            if(!tmp[y]){
                tmp[y] = true;
                res += dfsnum(y);
            }
        }
        return res;
    }
	// dfs求连通块边的个数(总个数,最后还要 / 2)
    public int dfs(int x){
        int res = g[x].size();
        for(int y : g[x]){
            if(!vis[y]){
                vis[y] = true;
                res += dfs(y);
            }
        }
        return res;
    }
}

简洁写法,在一个dfs中求边和求点

class Solution {
    List<Integer>[] g;
    boolean[] vis;
    int v, e;
    public int countCompleteComponents(int n, int[][] edges) {
        g = new ArrayList[n];
        Arrays.setAll(g, e -> new ArrayList<>());
        for(int[] e : edges){
            g[e[0]].add(e[1]);
            g[e[1]].add(e[0]);
        }
        int ans = 0;
        vis = new boolean[n];
        for(int i = 0; i < n; i++){
            if(!vis[i]){
                v = 0;
                e = 0;
                dfs(i);
                if(e == v * (v-1))
                    ans += 1;
            }
        }
        return ans;
    }

    public void dfs(int x){
        vis[x] = true;
        v++;
        e += g[x].size();
        for(int y : g[x]){
            if(!vis[y])
                dfs(y);
        }
    }
}

方法二:并查集

题解:https://leetcode.cn/u/coco-e1/

并查集维护顶点数和边数

class Solution {
    int[] parent, cnt, size;

    public int countCompleteComponents(int n, int[][] edges) {
        parent = new int[n];
        cnt = new int[n]; // 边个数
        size = new int[n]; // 节点个数
        Arrays.fill(size, 1);
        for (int i = 0; i < n; i++) parent[i] = i;
        for (int[] e : edges) union(e[0], e[1]);
        boolean[] vis = new boolean[n];
        int ans = 0;
        for (int i = 0; i < n; i++) {
            int px = find(i);
            if (!vis[px]) {
                vis[px] = true;
                int s = size[px];
                if (cnt[px] == s * (s - 1)) ans++;
            }
        }
        return ans;
    }

    int find(int x) {
        if (parent[x] == x) return x;
        return parent[x] = find(parent[x]);
    }

    void union(int x, int y) {
        int px = find(x), py = find(y);
        if (px != py) {
            parent[px] = py;
            cnt[py] += cnt[px] + 2;
            size[py] += size[px];
        } else cnt[px] += 2;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值