Leetcode 第 413 场周赛题解

Leetcode 第 413 场周赛题解

题目1:3274. 检查棋盘方格颜色是否相同

思路

找规律。

根据题目中的图片,如果 coordinate[0] 和 coordinate[1] 的 ASCII 值的奇偶性相同,那么格子是黑格,否则是白格。

进一步地,由于奇数+奇数=偶数,偶数+偶数=偶数,所以如果 (coordinate[0]+coordinate[1]) mod 2 是偶数,那么格子是黑格;否则奇数+偶数=奇数,格子是白格。

如果 (coordinate1[0]+coordinate1[1]) mod 2=(coordinate2[0]+coordinate2[1]) mod 2,那么两个格子颜色相同,否则不同。

代码

/*
 * @lc app=leetcode.cn id=3274 lang=cpp
 *
 * [3274] 检查棋盘方格颜色是否相同
 */

// @lc code=start
class Solution
{
public:
    bool checkTwoChessboards(string coordinate1, string coordinate2)
    {
        return (coordinate1[0] + coordinate1[1]) % 2 == (coordinate2[0] + coordinate2[1]) % 2;
    }
};
// @lc code=end

复杂度分析

时间复杂度:O(1)。

空间复杂度:O(1)。

题目2:3275. 第 K 近障碍物查询

思路

维护前 k 小元素,可以用最大堆。

遍历数组 queries,计算点 (x,y) 到原点的曼哈顿距离 d=∣x∣+∣y∣。

把 d 入堆,如果堆大小超过 k,就弹出堆顶(最大的元素)。

当堆的大小等于 k 时,堆顶就是第 k 小的距离。

代码

/*
 * @lc app=leetcode.cn id=3275 lang=cpp
 *
 * [3275] 第 K 近障碍物查询
 */

// @lc code=start
class Solution
{
public:
    vector<int> resultsArray(vector<vector<int>> &queries, int k)
    {
        int n = queries.size();
        vector<int> ans(n, -1);
        priority_queue<int> pq; // 大根堆
        for (int i = 0; i < n; i++)
        {
            int dis = abs(queries[i][0]) + abs(queries[i][1]);
            pq.push(dis);
            if (pq.size() > k)
                pq.pop();
            if (pq.size() == k)
            {
                // 堆顶就是当前第 k 小的距离
                ans[i] = pq.top();
            }
        }
        return ans;
    }
};
// @lc code=end

复杂度分析

时间复杂度:O(mlogk),其中 m 是数组 queries 的长度。单次插入的时间复杂度是O(logk)。

空间复杂度:O(k)。返回值不计入。

题目3:3276. 选择矩阵中单元格的最大得分

思路

用回溯会超时:

/*
 * @lc app=leetcode.cn id=3276 lang=cpp
 *
 * [3276] 选择矩阵中单元格的最大得分
 */

// @lc code=start
class Solution
{
private:
    int m, n;
    int ans = 0;

public:
    int maxScore(vector<vector<int>> &grid)
    {
        m = grid.size(), n = m ? grid[0].size() : 0;
        unordered_set<int> s;
        backtrace(0, 0, s, grid);
        return ans;
    }
    // 辅函数 - 回溯
    void backtrace(int level, int score, unordered_set<int> s, vector<vector<int>> &grid)
    {
        if (level == m)
        {
            ans = max(ans, score);
            return;
        }
        for (int j = 0; j < n; j++)
        {
            if (s.count(grid[level][j]) == 0)
            {
                s.insert(grid[level][j]);
                score += grid[level][j];
                backtrace(level + 1, score, s, grid);
                score -= grid[level][j];
                s.erase(grid[level][j]);
                backtrace(level + 1, score, s, grid);
            }
        }
    }
};
// @lc code=end

在这里插入图片描述

用状压 DP。

定义状态为 dp[i][j],表示在 [1,i] 中选数字,所选数字所处的行号不能在集合 j 中,每行至多一个数且没有相同元素时,所选元素之和的最大值。

讨论元素 i 选或不选:

  • 不选 i,问题变成在 [1,i−1] 中选数字,所选数字所处的行号不能在集合 j 中,每行至多一个数且没有相同元素时,所选元素之和的最大值,即 dp[i-1][j]。
  • 选 i,枚举选第 k 行的 i(提前预处理 i 所处的行号),问题变成在 [1,i−1] 中选数字(注意 i 只能选一次),所选数字所处的行号不能在集合 j∪{k} 中,每行至多一个数且没有相同元素时,所选元素之和的最大值,即 dp[i-1][j∪{k}]。

两种情况取最大值。

代码

// 状压 DP

class Solution
{
public:
    int maxScore(vector<vector<int>> &grid)
    {
        int m = grid.size();
        unordered_map<int, int> pos;
        for (int i = 0; i < m; i++)
        {
            for (int &x : grid[i])
            {
                // 保存所有包含 x 的行号(压缩成二进制数)
                pos[x] |= 1 << i;
            }
        }

        vector<int> all_nums;
        for (auto &[x, _] : pos)
        {
            all_nums.push_back(x);
        }

        int u = 1 << m;
        vector<vector<int>> dp(all_nums.size() + 1, vector<int>(u));
        for (int i = 0; i < all_nums.size(); i++)
        {
            int x = all_nums[i];
            for (int j = 0; j < u; j++)
            {
                dp[i + 1][j] = dp[i][j]; // 不选 x
                for (int t = pos[x], lb; t; t ^= lb)
                {
                    lb = t & -t;       // lb = 1<<k,其中 k 是行号
                    if ((j & lb) == 0) // 没选过第 k 行的数
                    {
                        dp[i + 1][j] = max(dp[i + 1][j], dp[i][j | lb] + x); // 选第 k 行的 x
                    }
                }
            }
        }
        return dp.back()[0];
    }
};

复杂度分析

时间复杂度:O(m * n * 2m),其中 m 和 n 分别为二维矩阵 grid 的行数和列数。

空间复杂度:O(m * n + 2m),其中 m 和 n 分别为二维矩阵 grid 的行数和列数。

题目4:3277. 查询子数组最大异或值

思路

考虑数组的异或值(最后剩下的元素)是由哪些元素异或得到的。

例如数组为 [a,b,c],那么操作一次后变成 [a⊕b, b⊕c],再操作一次,得到 a⊕b⊕b⊕c。其中 b 异或了 2 次。

为方便描述,下面把 a⊕b 简记为 ab,把 a⊕b⊕b⊕c 简记为 ab2c。

又例如数组为 [a,b,c,d],那么操作一次后变成 [ab,bc,cd],再操作一次,变成 [ab2c,bc2d],再操作一次,得到 ab3c3d。

可以发现,ab3c3d 相当于数组 [a,b,c] 的异或值,再异或 [b,c,d] 的异或值。

第一个区间 DP:

定义 dp[i][j] 表示下标从 i 到 j 的子数组的「数组的异或值」,根据上面的讨论,有 dp[i][j]=dp[i][j−1]⊕dp[i+1][j]。

初始值:dp[i][i]=nums[i]。

第二个区间 DP:

为了回答询问,我们需要计算下标从 i 到 j 的子数组中的所有子数组的 f 值的最大值,将其记作 mx[i][j]。

分类讨论:

  • 取 dp[i][j] 作为最大值。
  • 最大值对应的子数组,右端点不是 j,那么问题变成下标从 i 到 j−1 的子数组中的所有子数组的 dp 值的最大值,即 mx[i][j−1]。
  • 最大值对应的子数组,左端点不是 i,那么问题变成下标从 i+1 到 j 的子数组中的所有子数组的 dp 值的最大值,即 mx[i+1][j]。

三者取最大值,得 mx[i][j]=max(dp[i][j],mx[i][j−1],mx[i+1][j])。

初始值:mx[i][i]=nums[i]。

回答询问时直接查询 mx 数组。

代码

/*
 * @lc app=leetcode.cn id=3277 lang=cpp
 *
 * [3277] 查询子数组最大异或值
 */

// @lc code=start
class Solution
{
public:
    vector<int> maximumSubarrayXor(vector<int> &nums, vector<vector<int>> &queries)
    {
        int n = nums.size();
        vector<vector<int>> dp(n, vector<int>(n));
        vector<vector<int>> mx(n, vector<int>(n));
        // 初始化
        for (int i = 0; i < n; i++)
        {
            dp[i][i] = nums[i];
            mx[i][i] = nums[i];
        }
        // 状态转移
        for (int i = n - 1; i >= 0; i--)
            for (int j = i + 1; j < n; j++)
            {
                dp[i][j] = dp[i][j - 1] ^ dp[i + 1][j];
                mx[i][j] = max(dp[i][j], max(mx[i + 1][j], mx[i][j - 1]));
            }

        vector<int> ans;
        for (vector<int> &q : queries)
            ans.push_back(mx[q[0]][q[1]]);

        return ans;
    }
};
// @lc code=end

复杂度分析

时间复杂度:O(n2+q),其中 n 是数组 nums 的长度,q 是数组 queries 的长度。

空间复杂度:O(n2),其中 n 是数组 nums 的长度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值