LeetCode周赛题解: 第 139 场

LeetCode周赛题解: 第 139 场

序言:

本周题目列表如下:

  • 5076 字符串的最大公因子(简单)
  • 5077 按列翻转得到最大值等行数(中等)
  • 5078 负二进制数相加(中等)
  • 5075 元素和为目标值的子矩阵数量(困难)

1. 5076 字符串的最大公因子(简单)

描述:

对于字符串 S 和 T,只有在 S = T + … + T(T 与自身连接 1 次或多次)时,我们才认定 “T 能除尽 S”。

返回字符串 X,要求满足 X 能除尽 str1 且 X 能除尽 str2。

示例 1:

输入:str1 = “ABCABC”, str2 = “ABC”
输出:“ABC”

示例 2:

输入:str1 = “ABABAB”, str2 = “ABAB”
输出:“AB”

示例 3:

输入:str1 = “LEET”, str2 = “CODE”
输出:""

提示:

1 <= str1.length <= 1000
1 <= str2.length <= 1000
str1[i] 和 str2[i] 为大写英文字母

这题其实没什么好说,测例很弱,暴力就完事了,直接贴代码。

AC 代码:

class Solution {
public:
    bool can(const string& s, const string& p) {
        int len1 = s.size(), len2 = p.size();
        if (len1 % len2 != 0) return false;
        int i = 0;
        while(i + len2 <= len1) {
            if (s.substr(i, len2) != p) return false;
            i += len2;
        }
        return true;
    }
    string gcdOfStrings(const string& str1, const string& str2) {
        string tmp = str1.size() < str2.size() ? str1 : str2;
        for (int i = tmp.size(); i > 0; --i) {
            string sub = tmp.substr(0, i);
            if (can(str1, sub) && can(str2, sub)) return sub;
        }
        return "";
    }
};

2. 5077 按列翻转得到最大值等行数(中等)

描述:

给定由若干 0 和 1 组成的矩阵 matrix,从中选出任意数量的列并翻转其上的 每个 单元格。翻转后,单元格的值从 0 变成 1,或者从1 变为 0 。

返回经过一些翻转后,行上所有值都相等的最大行数。

示例1:

输入:[[0,1],[1,1]]
输出:1
解释:不进行翻转,有 1 行所有值都相等。

示例2:

输入:[[0,1],[1,0]]
输出:2
解释:翻转第一列的值之后,这两行都由相等的值组成。

示例3:

输入:[[0,0,0],[0,0,1],[1,1,0]]
输出:2
解释:翻转前两列的值之后,后两行由相等的值组成。

提示:

11 <= matrix.length <= 300
1 <= matrix[i].length <= 300
所有matrix[i].length 都相等
matrix[i][j] 为 0 或 1

这题如果真的一列一列去翻,肯定是超时。因为列长上限300,必须想到曲线救国的路子。如果只考虑其中两行,比如
r 1 = [ a 1 , a 2 , . . . , a n ] r 2 = [ b 1 , b 2 , . . . , b n ] r_1=[a_1, a_2,...,a_n] \\ r_2=[b_1, b_2,...,b_n] r1=[a1,a2,...,an]r2=[b1,b2,...,bn]
对于, r 1 r_1 r1 我们一定有两种唯一的翻牌法(虽然不知道具体是怎么翻的),分别将其翻成全1或者全0。假定我们现在已经知道翻牌策略,而翻牌策略是对列连带的。即,如果我们要翻 a 1 a_1 a1,则 b 1 b_1 b1必也被翻转。
那么对于策略 c c c,如果能够将 r 1 r_1 r1 成功翻转成全1或者全0,那么所有满足
r i [ j ] = r 1 [ j ] 或 r i [ j ] = 1 − r 1 [ j ] r_i[j]= r_1[j] 或\\ r_i[j] = 1 - r_1[j] ri[j]=r1[j]ri[j]=1r1[j]
的其他行 r i r_i ri, 它们也可以被策略 c c c 成功翻转。于是, 本题就成了一道分类打表题了, map解之即可。
AC 代码:

class Solution {
public:
    vector<int> rev(const vector<int>& a) {
        vector<int> ans(a.size(), 0);
        for (int i = 0; i < a.size(); ++i) ans[i] = 1 - a[i];
        return ans;
    }
    int maxEqualRowsAfterFlips(vector<vector<int>>& matrix) {
        map<vector<int>, int> m;
        for (auto v : matrix) {
            if (m.count(v)) ++m[v];
            else {
                vector<int> tmp = rev(v);
                if (m.count(tmp)) ++m[tmp];
                else m[v] = 1;
            }
        }
        int ans = -1;
        for (auto [k, v] : m) ans = max(ans, v);
        return ans;
    }
};

3. 5078 负二进制数相加(中等)

描述:

给出基数为 -2 的两个数 arr1 和 arr2,返回两数相加的结果。

数字以 数组形式 给出:数组由若干 0 和 1 组成,按最高有效位到最低有效位的顺序排列。
例如,arr = [1,1,0,1] 表示数字(-2)^3 + (-2)^2 + (-2)^0 = -3。
数组形式 的数字也同样不含前导零:以 arr 为例,这意味着要么 arr == [0],要么 arr[0] == 1。

返回相同表示形式的 arr1 和 arr2 相加的结果。两数的表示形式为:不含前导零、由若干 0 和 1 组成的数组。

示例 1:

输入:arr1 = [1,1,1,1,1], arr2 = [1,0,1]
输出:[1,0,0,0,0]
解释:arr1 表示11,arr2 表示 5,输出表示 16 。

提示:

1 <= arr1.length <= 1000
1 <= arr2.length <= 1000
arr1 和 arr2 都不含前导零
arr1[i] 为 0 或 1 arr2[i] 为 0 或 1

这道题可以说是非常阴皮的,陷阱极多。当时提交时候错了两次,看了排名靠前的大佬们也普遍有错误提交,下面具体分析:
首先排除转十进制的方法,输入的长度不可能允许这么做。那么就要考虑从2进制加法进行拓展了。
一个关键的点,-2进制的偶数位(从低位开始计算,0, 2, 4…)与2进制一致,而奇数位则多了一个负号。换句话说,相邻两位的进位上限刚好互为相反数。
对于普通的正 t t t 进制的加法,如果第 i i i 位的结果为 c u r cur cur,则 c u r / t cur / t cur/t 的部分是进位;而对于负 t t t 进制的加法, 如果第 i i i 位的结果为 c u r cur cur,则可以联想到,它的进位应该是 − c u r / t -cur / t cur/t
此外,由于进位是负数,因此第 i i i 位的结果也可能是负数,此时就需要向上一位借位。
最后,-2进制可能还会有一些让人意想不到的结果发生,比如[1, 1] + [1] = [0, 0]。
这就会出现多余的前导0,所以最后还需要对结果进行前导0的检查。

AC 代码:

class Solution {
public:
    vector<int> addNegabinary(vector<int>& arr1, vector<int>& arr2) {
        vector<int> ans;
        reverse(arr1.begin(), arr1.end());
        reverse(arr2.begin(), arr2.end());
        int i = 0, len1 = arr1.size(), len2 = arr2.size();
        int carry = 0;
        while(i < len1 || i < len2 || carry != 0) {
            int a = i < len1 ? arr1[i] : 0;
            int b = i < len2 ? arr2[i] : 0;
            int cur = carry + a + b;
            if (cur >= 2) {
                carry = -1 * (cur / 2);
                ans.push_back(cur % 2);
            } else if (cur == -1) {
                carry = 1;
                ans.push_back(1);
            } else {
                carry = 0;
                ans.push_back(cur);
            }
            ++i;
        }
        for (int i = ans.size() - 1; i >= 1 && ans[i] == 0; --i) ans.pop_back();
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

4. 5075 元素和为目标值的子矩阵数量(困难)

描述:

给出矩阵 matrix 和目标值 target,返回元素总和等于目标值的非空子矩阵的数量。

子矩阵 x1, y1, x2, y2 是满足 x1 <= x <= x2 且 y1 <= y <= y2 的所有单元
matrix[x][y] 的集合。

如果 (x1, y1, x2, y2) 和 (x1’, y1’, x2’, y2’) 两个子矩阵中部分坐标不同(如:x1 !=
x1’),那么这两个子矩阵也不同。

示例 1:

输入:matrix = [[0,1,0],[1,1,1],[0,1,0]], target = 0
输出:4
解释:四个只含 0 的 1x1 子矩阵。

示例 2:

输入:matrix = [[1,-1],[-1,1]], target = 0
输出:5
解释:两个 1x2 子矩阵,加上两个 2x1子矩阵,再加上一个 2x2 子矩阵。

提示:

1 <= matrix.length <= 300
1 <= matrix[0].length <= 300
-1000 <= matrix[i] <= 1000
-10^8 <= target <= 10^8

典型的以空换时的题目。衡量一个子矩阵需要 4 个参数,因此暴力法的复杂度至少是 O ( n 4 ) O(n^4) O(n4)
之前跟室友交流过一维数组中寻找元素和为目标值的子数组数量,这个题目是一维的拓展。可惜在常规时间内没有想出合适的降维方法,后来参考大佬的代码才领悟,还是实力受限啊…
先讨论一下一维数组的解法:
对于一维数组 a a a,其任何一个子数组 a [ i : j ] a[i:j] a[i:j]可以看成 a [ 0 : j ] a[0:j] a[0:j] a [ 0 : i ] a[0:i] a[0:i]的补集,因此一次遍历map打表即可求解。
但是对于二维情况,既有行又有列时则变得非常棘手。正确的思路应该采用从 twoSum 推广到 threeSum 的方式。外部用两层遍历唯一确定当前考虑的行起始下标 i i i与行终止下标 j j j,在此行区间内,按列求和,将其压缩一行,即是一个一维数组,然后利用一维方式解之。

AC 代码(未在常规时间内):

class Solution {
public:
    int numSubmatrixSumTarget(vector<vector<int>>& matrix, int target) {
        int rows = matrix.size(), cols = matrix[0].size();
        int ans = 0;
        for (int i = 0; i < rows; ++i) {
            vector<int> sumCols(cols, 0); // 从 i 行开始到 j 行结束,每列的叠加和
            for (int j = i; j < rows; ++j) {
                for (int k = 0; k < cols; ++k) sumCols[k] += matrix[j][k];
                unordered_map<int, int> m;
                m[0] = 1; // 空值填充
                
                // 以下是一维常规解法
                int sum = 0;
                for (int k = 0; k < cols; ++k) {
                    sum += sumCols[k];
                    if (m.count(sum - target)) ans += m[sum - target];
                    ++m[sum];
                }
            }
        }
        return ans;
    }
};

总结:
本周题相较于上周难度增加。其中第4题AC的仅十几个,笔者AC了3题获得了29名,而上周笔者全部AC也仅58名。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值