大家好,我是梁唐。
今天是国庆假期,即使过节LeetCode周赛系列也依然更新。
上周的周赛是LeetCode周赛第313场,由airwallex赞助。之前老梁和这家公司打过交道,他们很喜欢代码能力强的同学。是一家总部位于澳洲的外企,感兴趣的同学可以投递一下相关岗位。
对于这一场比赛大家的反应都是第四题比较难,并且在比赛的时候通过的人数还非常多。还有同学扒出了最后一题的极端case只有一个,加上特判就能通过。
我想了一下,似乎这题想要卡时间就只有这一种极端的case,毕竟题目给的字符串长度有限。如果真是这样,那这题是属于有点为了难而难了。
公因子的数目
给你两个正整数 a
和 b
,返回 a
和 b
的 公 因子的数目。
如果 x
可以同时整除 a
和 b
,则认为 x
是 a
和 b
的一个 公因子
题解
求两个数公因子的数量,其实就是求两个数最大公约数的因子的数量。
我们可以使用gcd
函数直接求出两个数的最大公约数,接着枚举一下它的因子数量即可。注意完全平方根只能算一个因子,比如6
和36/6
是同一个因子,不要计算两次。
class Solution {
public:
int commonFactors(int a, int b) {
int c = gcd(a, b);
int ret = 0;
for (int i = 1; i * i <= c; i++) {
if (c % i == 0) {
ret += i == c / i ? 1 : 2;
}
}
return ret;
}
};
复制代码
沙漏的最大总和
给你一个大小为 m x n
的整数矩阵 grid
。
按以下形式将矩阵的一部分定义为一个 沙漏 :
返回沙漏中元素的 最大 总和。
**注意:**沙漏无法旋转且必须整个包含在矩阵中。
题解
数据范围不大,枚举送分题。
class Solution {
public:
int maxSum(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
auto get_sum = [&](int x, int y) -> int {
int s = 0;
for (int i = x-2; i <= x; i++) {
for (int j = y-2; j <= y; j++) {
s += grid[i][j];
}
}
s -= (grid[x-1][y-2] + grid[x-1][y]);
return s;
};
int ret = 0;
for (int i = 2; i < n; i++) {
for (int j = 2; j < m; j++) {
ret = max(ret, get_sum(i, j));
}
}
return ret;
}
};
复制代码
最小 XOR
给你两个正整数 num1
和 num2
,找出满足下述条件的整数 x
:
x
的置位数和num2
相同,且x XOR num1
的值 最小
注意 XOR
是按位异或运算。
返回整数 x
。题目保证,对于生成的测试用例, x
是 唯一确定 的。
整数的 置位数 是其二进制表示中 1
的数目。
题解
比较容易想到我们可以算出num2
的置位数,然后枚举所有可能组成的整数,再找到和num1
异或之后最小的一个,但很遗憾,这样做会超时。极端情况:一共有32个二进制位,如果num2
的置位数是16,那么情况总数是,会超时。
所以我们不能枚举,需要结合异或操作的特性来构造。异或操作的原理为当两个二进制相同时结果为0,不同时结果为1。我们要使异或之后的值尽可能小,就需要让尽可能多的二进制置为0。
如果num2
的置位数小于num1
,那么我们不能覆盖所有num1
等于1的二进制位。我们可以贪心,尽可能覆盖值比较大的二进制位。如果num2
的置位数大于num1
,我们可以将所有num1
的二进制位都置为0,但1的个数还有剩余,我们一样使用贪心的思路,尽可能放在低位。
class Solution {
public:
int minimizeXor(int num1, int num2) {
// num2的置位数
int n2 = 0;
for (int i = 0; i < 31; i++) {
if ((1 << i) & num2) n2++;
}
// 记录num1哪些位置为1
vector<int> bits(32, 0);
// num1的置位数
int n1 = 0;
for (int i = 0; i < 31; i++) {
if ((1 << i) & num1) {
bits[i] = 1;
n1++;
}
}
int ret = num1;
if (n2 > n1) {
// 能将n1全部置为0,多余的摆放在低位
n2 -= n1;
for (int i = 0; i < 31; i++) {
if (n2 == 0) break;
if (bits[i] == 0) {
ret += (1 << i);
n2--;
}
}
}else {
// 不能将n1全部置为0,尽可能空出低位
n1 -= n2;
for (int i = 0; i < 31; i++) {
if (n1 == 0) break;
if (bits[i] > 0) {
ret -= (1 << i);
n1--;
}
}
}
return ret;
}
};
复制代码
对字母串可执行的最大删除数
给你一个仅由小写英文字母组成的字符串 s
。在一步操作中,你可以:
- 删除 整个字符串
s
,或者 - 对于满足
1 <= i <= s.length / 2
的任意i
,如果s
中的 前i
个字母和接下来的i
个字母 相等 ,删除 前i
个字母。
例如,如果 s = "ababc"
,那么在一步操作中,你可以删除 s
的前两个字母得到 "abc"
,因为 s
的前两个字母和接下来的两个字母都等于 "ab"
。
返回删除 s
所需的最大操作数。
题解
直接枚举会超时,因为字符串比较是否相等是一个的操作。在极端情况下(所有字符均相等时)我们会进行次比较,显然会超时。
我的想法是尽可能减少比较字符串的次数,由于我打算采用搜索的算法来解,那么也就是尽可能减少搜索的次数。由于我们删除字符串是从头部开始的,所以采用距离末尾的长度来表示中间的子状态。dp[i]
表示的是最后长度为i
的子串最多删除的次数。
如果找不到可以删除的字母,那么dp[i]=1
,否则dp[i] = max(dp[j]) + 1
。这里的j
是指删除若干字母之后剩余的长度。整个过程都是通过递归实现的,显然在递归时某些状态会重复出现,所以我们可以使用记忆化搜索的方法,将这些重复出现的状态缓存起来。
class Solution:
def deleteString(self, s: str) -> int:
# 记忆化搜索
dt = {}
dt[0] = 0
def dfs(s):
l = len(s)
if l in dt:
return dt[l]
ret = 0
flag = False
# 枚举所有可以删除的可能
for i in range(1, l // 2 + 1):
if s[:i] == s[i: 2*i]:
# 递归
ret = max(ret, dfs(s[i:]))
flag = True
r = ret + 1 if flag else 1
# 更新缓存
dt[l] = r
return r
return dfs(s)
复制代码
赛后看了一下大佬的题解,发现除了记忆化搜索之外,还可以通过对字符串求hash的方法来加速字符串比较的速度。对字符串hash之后,只需要判断某一段字符对应的hash值是否相等就能等价判断字符串是否相等,这样就把的字符串比较降低到了。
因为我hash算法用得很少,不是非常熟,所以搬运一下TsReaper大佬的代码仅供参考。
class Solution {
const int MOD = 998244353;
const int BASE = 131;
public:
int deleteString(string s) {
// 计算前缀哈希值
int n = s.size();
vector<long long> P(n + 1);
P[0] = 1;
for (int i = 1; i <= n; i++) P[i] = P[i - 1] * BASE % MOD;
vector<long long> H(n + 1);
for (int i = 1; i <= n; i++) H[i] = (H[i - 1] * BASE + s[i - 1]) % MOD;
int ans = 1;
vector<int> f(n + 1, (int) -1e9);
f[0] = 0;
for (int i = 1; i <= n; i++) for (int j = 0; j < i; j++) {
int len = i - j;
if (i + len > n) continue;
// 计算两个子串的哈希值
long long h1 = (H[i] - H[i - len] * P[len] % MOD + MOD) % MOD;
long long h2 = (H[i + len] - H[i] * P[len] % MOD + MOD) % MOD;
if (h1 == h2) f[i] = max(f[i], f[j] + 1);
ans = max(ans, f[i] + (i == n ? 0 : 1));
}
return ans;
}
};
复制代码