力扣周赛345

189 阅读1分钟

力扣周赛345的题解

本文是对力扣周赛345的题目的解题思路和代码的总结,希望能够对大家有所帮助。

题目一:找出转圈游戏输家

这是一道简单的模拟题,我们可以用一个布尔数组来标记接触到球的玩家,然后根据数组输出结果。时间复杂度是O(n),空间复杂度是O(n)。

代码如下:

class Solution {
    public int[] circularGameLosers(int n, int k) {
        boolean[] visit = new boolean[n]; // 标记数组
        int i = 0; // 当前玩家下标
        int j = 1; // 当前轮数
        int cnt = n; // 剩余玩家数
        while (!visit[i]) { // 如果当前玩家没有接触过球,就继续游戏
            visit[i] = true; // 标记当前玩家接触过球
            i = (i + j * k) % n; // 计算下一个玩家的下标
            j++; // 增加轮数
            cnt--; // 减少剩余玩家数
        }
        int[] ret = new int[cnt]; // 结果数组
        int k = 0; // 结果数组的下标
        for (int i = 0; i < n; i++) { // 遍历标记数组
            if (!visit[i]) ret[k++] = i + 1; // 如果没有接触过球,就是输家,加入结果数组
        }
        return ret; // 返回结果数组
    }
}

题目二:相邻值的按位异或

这是一道涉及异或运算的性质的题目,我们可以用两种方法来解决:

  • 方法一:模拟。我们可以假设原始数组的第一个元素为0,然后按照题目给出的规则递推出原始数组的其他元素,最后检查原始数组的第一个元素和最后一个元素是否相同。如果不同,说明不存在有效的原始数组。时间复杂度是O(n),空间复杂度是O(1)。

代码如下:

class Solution {
    public boolean doesValidArrayExist(int[] derived) {
        int pre = 0; // 假设原始数组的第一个元素为0
        for (int d : derived) { // 遍历派生数组
            if (d == 1) pre ^= 1; // 如果派生数组的元素为1,就异或1,相当于翻转原始数组的元素
        }
        return pre == 0; // 检查原始数组的第一个元素和最后一个元素是否相同,即pre是否为0
    }
}
  • 方法二:数学。我们可以利用异或运算的一些性质来简化计算:

    • 自反律:x ⊕ x = 0,x ⊕ 0 = x
    • 结合律:(x ⊕ y) ⊕ z = x ⊕ (y ⊕ z)
    • 交换律:x ⊕ y = y ⊕ x

根据题目给出的规则,我们有:

  • derived[i] = original[i] ⊕ original[i + 1]
  • original[i + 1] = derived[i] ⊕ original[i]
  • original[n - 1] = derived[n - 2] ⊕ derived[n - 1] ⊕ ... ⊕ derived[0] ⊕ original[0]
  • original[0] ⊕ original[n - 1] = derived[n - 1]

联立上面四个式子,我们可以得到:

  • original[0] = original[0] ⊕ derived[n - 1] ⊕ derived[n - 2] ⊕ ... ⊕ derived[0] ⊕ original[0]
  • 即 0 = derived[n - 1] ⊕ derived[n - 2] ⊕ ... ⊕ derived[0]

所以我们只需要检查派生数组中所有元素的异或和是否为0即可。时间复杂度是O(n),空间复杂度是O(1)。

代码如下:

class Solution {
    public boolean doesValidArrayExist(int[] derived) {
        int xor = 0; // 异或和
        for (int d : derived) { // 遍历派生数组
            xor ^= d; // 累计异或和
        }
        return xor == 0; // 检查异或和是否为0
    }
}

题目三:矩阵中移动的最大次数

这是一道涉及图遍历的题目,我们可以用两种方法来解决:

  • 方法一:深度优先搜索(DFS)。我们可以定义一个二维数组dp来存储每个单元格开始能够移动的最大次数,初始值为-1表示未计算过。然后我们从第一列中的任意一个单元格开始进行DFS,每次尝试向右上、右、右下三个方向移动,如果满足条件(值严格大于当前单元格),就递归计算该方向能够移动的最大次数,并取最大值加一作为当前单元格能够移动的最大次数。如果没有满足条件的方向,就返回0表示不能移动。最后我们遍历dp数组,取其中的最大值作为答案。时间复杂度是O(mn),空间复杂度是O(mn)。

代码如下:

class Solution {
    public int maximumNumberOfMovesInAGrid(int[][] grid) {
        int m = grid.length, n = grid[0].length; // 矩阵大小
        int[][] dp = new int[m][n]; // dp数组,存储每个单元格能够移动的最大次数
        for (int i = 0; i < m; i++) { // 初始化dp数组为-1表示未计算过
            Arrays.fill(dp[i], -1);
        }
        int ans = 0; // 答案变量,存储能够移动的最大次数
        for (int i = 0; i < m; i++) { // 遍历第一列中的任意一个单元格开始进行DFS
            ans = Math.max(ans, dfs(grid, dp, i, 0)); // 更新答案变量为当前单元格能够移动的最大次数和之前答案变量中较大者
        }
        return ans; // 返回答案变量
    }

    private int dfs(int[][] grid, int[][] dp, int i, int j) { // DFS函数,返回从grid[i][j]开始能够移动的最大次数
        if (dp[i][j] != -1) return dp[i][j]; // 如果已经计算过,直接返回dp值
        int m = grid.length, n = grid[0].length; // 矩阵大小
        int max = 0; // 最大次数变量,初始化为0表示不能移动
        for (int[] dir : new int[][]{{-1, 1}, {0, 1}, {1, 1}}) { // 遍历三个方向(右上、右、右下)
            int x = i + dir[0], y = j + dir[1]; // 计算新位置坐标
            if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] > grid[i][j]) { // 如果新位置在矩阵范围内且值严格大于当前位置
                max = Math.max(max, dfs(grid, dp, x, y)); // 更新最大次数变量为新位置能够移动的最大次数和之前最大次数变量中较大者
            }
        }