题外话:自己在 okr
中立了个 flag
,希望在 7-8
双月逐渐熟练掌握 python, go
,所以在之后的周赛题解中,我会逐渐引入 python, go
的代码,有兴趣的小伙伴可以一起学习
本次周赛着重讲解一下第三题,前面两题一笔带过,最后一题涉及到 (01 trie) 树,后续单独出一篇文章讲解
本次周赛知识点:字符串,数学,上取整,动态规划,高性能,前缀后缀
可以输入的最大单词数
给定一个字符串 (w) 表示一串单词,给定一个字符串 (b) 表示破坏的字母,对于 (w) 中的所有单词,如果包含 (b) 中的字母,那么该单词不可用,计算可用的单词数
题解
用一个数组存放 w
中的单词,用桶存放 b
中的字符,循环判断即可
// cpp
class Solution {
public:
int canBeTypedWords(string t, string b) {
vector<string> w;
string temp = "";
int bucket[27] = {0};
for (int i = 0; i < b.size(); ++i) bucket[b[i] - 'a']++;
for (int i = 0; i <= t.size(); ++i) {
if (!(t[i] >= 'a' && t[i] <= 'z')) w.push_back(temp), temp = "";
else temp += t[i];
}
int ans = 0;
for (auto i: w) {
int f = 0;
for (int j = 0; j < i.size(); ++j) if (bucket[i[j] - 'a']) {f = 1; break;}
if (!f) ans++;
}
return ans;
}
};
# py
class Solution:
def canBeTypedWords(self, t: str, b: str) -> int:
st = set(b)
ans = 0
for word in t.split():
flag = 0
for ch in word:
if ch in st:
flag = 1
break
if not flag:
ans += 1
return ans
// go
func canBeTypedWords(t string, b string) (ans int) {
for _, word := range strings.Split(t, " ") {
if !strings.ContainsAny(word, b) {
ans++
}
}
return ans
}
新增的最少台阶数
给一个严格递增的数组 (r),表示台阶的高度,给一个正整数 (dist) 表示爬一次最高能上的台阶高度,初始时站在高度 (0) 位置,计算最少还需要放置几个台阶,使得可以爬到最后一个台阶
例如 r = [1, 3, 5, 10], dist = 2
,需要放置 2
个台阶,高度分别为 7, 9
(方案不唯一)
例如 r = [3, 6, 8, 10], dist = 3
,不需要放置台阶
例如 r = [3, 4, 6, 7], dist = 2
,需要放置 1
个台阶,高度为 1
题解
对于任意两个台阶,高度分别为 (i, j),设高度差为 (h),则可以放 (lceilfrac{h}{dist} ceil - 1) 个台阶
因此我们计算出差分数组 (d),依次做判断即可
在实现 (lceilfrac{a}{b}
ceil - 1) 时,使用 (a - 1) / b
即可
// cpp
class Solution {
public:
int addRungs(vector<int>& r, int dist) {
int n = r.size();
vector<int> d(n);
d[0] = r[0];
for (int i = 1; i < n; ++i) {
d[i] = r[i] - r[i - 1];
}
int ans = 0;
for (int i = 0; i < n; ++i) {
if (d[i] % dist == 0) ans += d[i] / dist - 1;
else ans += d[i] / dist;
}
return ans;
}
};
# py
class Solution:
def addRungs(self, r: list[int], dist: int) -> int:
pre = ans = 0
for i in r:
h = i - pre
ans += (h - 1) // dist
pre = i
return ans
// go
func addRungs(r []int, dist int) (ans int) {
pre := 0
for _, h := range r {
d := h - pre
ans += (d - 1) / dist
pre = h
}
return ans
}
扣分后的最大得分
给定一个 (m imes n) 的矩阵 (p),每一个格子有一个权值 (p[i][j])
现在从第一行到最后一行,每一行选一个格子,收益为权值和
但是,对于第 (i) 行和第 (i - 1) 行,如果分别选了 (j, k) 列,那么收益减去 (mid j - kmid)
数据保证
(1leq m, n, m imes nleq 10^5)
(0leq p[i][j]leq 10^5)
题解
考虑二维动态规划,定义 (dp[i][j]) 表示走到 ((i, j)) 位置的收益最大值,则状态转移方程为
其中 (0leq k < n)
但是我们需要枚举 (i, j, k),复杂度为 (O(nmk)),不能接受,因此考虑优化决策,对于状态转移方程,我们发现实际上要计算的是
对于当前决策,我们只需要分别计算出 (jgeq k) 部分的最大值和 (j < k) 部分的最大值即可
时间复杂度 (O(nm log n)) 算法
我们用数组 (h_1, h_2) 分别维护两个值,具体来说
然后用两棵红黑树维护二元组 ((j, h_1[j]), (j, h_2[j])),这样便可以在 (O(log n)) 时间计算出两段决策的最大值,在 c++
中,我选用 set
来实现,需要重载运算符
随着决策 (j) 指针右移,第一棵红黑树中要删去节点 ((j, h_1[j])),第二颗红黑树中要插入节点 ((j, h_2[j])),时间复杂度均为 (O(log n))
因此总的时间复杂度为 (O(nm log n))
// cpp
typedef long long LL;
struct node{
int idx;
LL val;
node() {}
node(int _idx, LL _val):
idx(_idx), val(_val) {}
};
bool operator< (const node &x, const node &y) {
if (x.val == y.val) return x.idx < y.idx;
return x.val > y.val;
}
class Solution {
public:
long long maxPoints(vector<vector<int>>& a) {
int m = a.size();
int n = a[0].size();
vector<vector<LL>> dp(m, vector<LL>(n));
for (int j = 0; j < n; ++j) {
dp[0][j] = a[0][j];
}
for (int i = 1; i < m; ++i) {
vector<LL> h1(n), h2(n);
set<node> st1, st2;
for (int j = 0; j < n; ++j) {
h1[j] = dp[i - 1][j] - j;
h2[j] = dp[i - 1][j] + j;
st1.insert(node(j, h1[j]));
}
for (int j = 0; j < n; ++j) {
if (st1.size()) {
auto x = st1.begin();
dp[i][j] = max(dp[i][j], x->val + a[i][j] + j);
}
if (st2.size()) {
auto y = st2.begin();
dp[i][j] = max(dp[i][j], y->val + a[i][j] - j);
}
st1.erase(node(j, h1[j]));
st2.insert(node(j, h2[j]));
/*
for (int k = 0; k < n; ++k) {
dp[i][j] = max(dp[i][j], dp[i - 1][k] + a[i][j] - abs(j - k));
}
*/
}
}
LL ans = 0;
for (int j = 0; j < n; ++j) ans = max(ans, dp[m - 1][j]);
return ans;
}
};
时间复杂度 (O(nm)) 算法
用 (pre[j]) 维护 (maxleft{dp[i - 1][k] + kmid 0leq kleq j ight})
用 (suf[j]) 维护 (maxleft{dp[i - 1][k] - kmid jleq kleq n - 1 ight})
则状态转移方程更新为
同时使用滚动数组把 (dp) 数组的第一维状态优化掉
总的时间复杂度为 (O(nm)),空间复杂度为 (O(n))
注意此题爆 int
,不开 long long
见祖宗
// cpp
typedef long long LL;
class Solution {
public:
long long maxPoints(vector<vector<int>>& a) {
int m = a.size(), n = a[0].size();
vector<LL> dp(n);
vector<LL> pre(n), suf(n);
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (!i) dp[j] = a[i][j];
else {
LL x = max(pre[j] - j, suf[j] + j) + a[i][j];
dp[j] = max(dp[j], x);
}
}
pre[0] = dp[0] + 0;
suf[n - 1] = dp[n - 1] - (n - 1);
for (int j = 1; j < n; ++j)
pre[j] = max(pre[j - 1], dp[j] + j);
for (int j = n - 2; j >= 0; --j)
suf[j] = max(suf[j + 1], dp[j] - j);
}
LL ans = *max_element(dp.begin(), dp.end());
return ans;
}
};
使用前后缀代替红黑树,代码更好写了,运行效率也更快