LeetCode --- 135双周赛

题目列表

3222. 求出硬币游戏的赢家

3223. 操作后字符串的最短长度

3224. 使差值相等的最少数组改动次数

3225. 网格图操作后的最大分数

一、求出硬币游戏的赢家

简单数学题,115 只能由 一个 75 和 四个 10 组成,我们只要看能构成几115就行,如果有奇数个115则Alice赢,否则Bob赢,代码如下

class Solution {
public:
    string losingPlayer(int x, int y) {
        // 75 + 10 * 4
        return min(x,y/4)%2 ? "Alice":"Bob";
    }
};

二、操作后字符串的最短长度

简单的模拟题,说的很复杂,实际上就是看每个字符出现的次数,如果字符出现为奇数,那么能剩下一个,如果为偶数,那么会剩下两个,代码如下

// 直接模拟
class Solution {
public:
    int minimumLength(string s) {
        int n = s.size();
        int cnt[26]{};
        for(auto e:s) {
            if(++cnt[e-'a']==3) 
                n-=2, cnt[e-'a']-=2;
        }
        return n;
    }
};

// 可以用数学方式来算
class Solution {
public:
    int minimumLength(string s) {
        int cnt[26]{};
        for(auto e:s) ++cnt[e-'a'];
        int ans = 0;
        for(auto c:cnt)
            if(c > 0)
                ans += (c%2?1:2);
        return ans;
    }
};

 三、使差值相等的最少数组改动

这题估计很多人都会想去贪心,觉得 cnt [ abs(nums[i] - nums[n-i-1]) ]多的操作次数就一定少。这里要注意一点,这样做仅仅是让不操作的次数多了,但是没有考虑到有可能会导致操作两次的次数变多,从而导致整体数量变多的情况,所以不能贪心,只能暴力枚举

这里就需要考虑:枚举 X 算答案,还是直接枚举 答案 ,然后检验答案是否合法?这里我们选择枚举 X ,因为它能给我们后面的计算增加条件。

那么如何计算呢?

对于每个数对来说,面对一个X,它只能有3种情况:操作次数为0/1/2

假设,一个数对为 x,y (x <= y)

  • 操作0次,此时 X = abs(x - y)
  • 操作1次,即可以改变一个数,此时 X <= max(y,k - x) 且 X != abs(x - y)
  • 操作2次,此时,X > max(y,k - x) 且 X != abs(x - y)

所以,我们需要统计不同操作次数下的数对数量,用数组记录即可,代码如下

class Solution {
public:
    int minChanges(vector<int>& nums, int k) {
        int n = nums.size();
        // cnt0[i] 记录 X = i 时,操作次数为 0 的数对有几个
        // cnt1[i] 记录 操作次数为 1 时,X 最大范围为 i 的数对有几个
        vector<int> cnt0(k+1), cnt1(k+1);
        for(int i = 0; i < n/2; i++){
            int x = nums[i], y = nums[n-i-1];
            if(x > y) swap(x, y);
            int mx = max(y, k - x);
            cnt0[y-x]++;
            cnt1[mx]++;
        }
        int ans = INT_MAX;
        int sum2 = 0; // 记录操作次数为 2 的数对有几个
        for(int x = 0; x <= k; x++){
            // n/2 - sum2 - cnt0[x] :操作次数为 1 的数对个数
            ans = min(ans, n/2 - sum2 - cnt0[x] + 2 * sum2);
            sum2 += cnt1[x]; // 计算超出操作次数为 1 的最大范围的数有几个,即操作次数为 2 的数对个数
        }
        return ans;
    }
};

上面的代码是基于操作次数来统计数对个数,但其实我们换个角度,算每个数对,针对不同 X 时所需的操作次数(即算贡献),这题就变成了一个差分数组的题。

思路如下:

对于一个数对 x,y (x <= y)

  • 当 X 在 [0,max(y,k - x)] 中时,操作次数为 1
  • 当 X 在 [ y - x,y - x] 中时,操作次数为 0
  • 当 X 在 [max(y,k - x),k] 中时,操作次数为 2

这显然是和区间的+/-操作有关,即应该用差分数组来写,代码如下

class Solution {
public:
    int minChanges(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> diff(k+2);
        for(int i = 0; i < n/2; i++){
            int x = nums[i], y = nums[n-i-1];
            if(x > y) swap(x, y);
            int mx = max(y, k - x);
            // 对区间 [0, mx] 进行 +1 操作
            diff[0]++; diff[mx+1]--;
            // 对区间 [y - x, y - x] 进行 -1 操作
            // 因为 y-x 在 [0, mx] 中,之前进行过 +1,现在需要 -1 才能让操作次数为 0
            diff[y-x]--; diff[y-x+1]++;
            // 对区间 [mx + 1, k] 进行 +1 操作
            diff[mx+1] += 2;
            // diff[k+1] -= 2 这个可以不用,用不到
        }
        int ans = INT_MAX;
        int pre = 0;
        for(int x = 0; x <= k; x++){
            pre += diff[x];
            ans = min(ans, pre);
        }
        return ans;
    }
};

四、网格图操作后的最大分数

这种题目,我们在思考时,要先将问题拆解为规模更小的问题,也就是从特殊到一般。列数多了我们思考不来,我们就先思考一列的得分如何确定(需要哪些信息),再去考虑更多列的情况。(显然这题是用动规来做)

思路如下:我们要算出第 i 列的得分,就需要知道 第 i - 1,i,i + 1 列 的黑色格子的数量。假设我们从后往前依次对每一列的得分进行计算,在遍历时,我们要知道当前列 i,它的黑格子数量 j,以及它右边的列的黑格子数量 k,对它左边列的黑格子数量,我们进行枚举。这里要注意:枚举的不是当前列的黑格子数量,而是它左边列的黑格子数量,因为我们是往一个方向枚举的,无法先得到它左列的黑格子数量。

所以我们得到如下的递归函数

dfs(i,j,k)表示第 i 列黑格子数量为j,第i+1列黑格子数量为k时,[i,n) 列的最大得分

dfs(i,j,k) = max(dfs(i-1,h,j) + 中间列的得分)  h=[0,n]

代码如下

// 记忆化搜索
class Solution {
    using LL = long long;
public:
    long long maximumScore(vector<vector<int>>& grid) {
        int n = grid.size();
        LL pre[n+1][n+1]; memset(pre, 0, sizeof(pre));
        // 计算每一列的前缀和
        for(int j = 0; j < n; j++){
            for(int i = 0; i < n; i++){
                pre[j][i+1] = pre[j][i] + grid[i][j];
            }
        }
        LL memo[n+1][n+1][n+1];
        memset(memo,-1,sizeof(memo));
        function<LL(int, int, int)> dfs=[&](int i, int j, int k)->LL{
            if(i == 0) return (k > j ? pre[i][k] - pre[i][j] : 0);
            if(memo[i][j][k]!=-1) return memo[i][j][k];
            LL res = 0;
            for(int h = 0; h <= n; h++){
                res = max(res, dfs(i-1, h, j) + (max(h, k) > j ? pre[i][max(h, k)] - pre[i][j] : 0));
            }
            return memo[i][j][k] = res;
        };
        LL ans = 0;
        for(int i = 0; i <= n; i++){
            ans = max(ans, dfs(n-1, i, 0));
        }
        return ans;
    }
};



// 递推
class Solution {
    using LL = long long;
public:
    long long maximumScore(vector<vector<int>>& grid) {
        int n = grid.size();
        LL pre[n+1][n+1]; memset(pre, 0, sizeof(pre));
        for(int j = 0; j < n; j++){
            for(int i = 0; i < n; i++){
                pre[j][i+1] = pre[j][i] + grid[i][j];
            }
        }

        LL ans = 0;
        LL f[n][n+1][n+1];
        memset(f,0,sizeof(f));
        for(int j = 0; j <= n; j++){
            for(int k = j + 1; k <= n; k++){
                f[0][j][k] = pre[0][k] - pre[0][j];
            }
        }
        for(int i = 0; i < n - 1; i++){
            for(int j = 0; j <= n; j++){
                for(int k = 0; k <= n; k++){
                    LL res = 0;
                    for(int h = 0; h <= n; h++){
                        res = max(res, f[i][h][j] + (max(h, k) > j ? pre[i+1][max(h, k)] - pre[i+1][j] : 0));
                    }
                    f[i+1][j][k] = res;
                }
            }
        }
        for(int i = 0; i <= n; i++)
            ans = max(ans, f[n-1][i][0]);
        return ans;
    }
};

以上是O(n^4)的做法,会超时,但是对我们理解状态定义有帮助,下面在贴一个O(n^3)的做法,就是对上面代码的优化(在逻辑层面上的优化,不是用数据结构辅助完成的优化),这里就不做多解释了,因为优化思路比较复杂,这里仅仅是放个代码。

class Solution {
    using LL = long long;
public:
    long long maximumScore(vector<vector<int>>& grid) {
        int n = grid.size();
        LL pre[n+1][n+1]; memset(pre, 0, sizeof(pre));
        for(int j = 0; j < n; j++){
            for(int i = 0; i < n; i++){
                pre[j][i+1] = pre[j][i] + grid[i][j];
            }
        }

        LL memo[n][n+1][2];
        memset(memo, -1, sizeof(memo));
        // i 表示 第 i 列
        // j 表示 第 i+1 列的高度
        // k 表示 第 i+2 列的高度是否大于第 i+1 列的高度
        function<LL(int, int, bool)> dfs=[&](int i, int j, bool k)->LL{
            if(i < 0) return 0;
            auto& res = memo[i][j][k];
            if(res != -1) return res;
            res = 0;
            for(int h = 0; h <= n; h++){
                if(h == j) 
                    res = max(res, dfs(i-1, h, false));
                else if(h < j)
                    res = max(res, dfs(i-1, h, true) + pre[i][j] - pre[i][h]);
                else if(!k)
                    res = max(res, dfs(i-1, h, false) + pre[i+1][h] - pre[i+1][j]);
                else if(j == 0)
                    res = max(res, dfs(i-1, h, false));
            }
            return res;
        };

        LL ans = 0;
        for(int i = 0; i <= n; i++){
            ans = max(ans, dfs(n-2, i, false));
        }
        return ans;
    }
};
  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文件编码格式:首先,我们可以检查所编辑的文件的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor中,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor中,可以在编辑器中选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值