5月1日:LeetCode第 291 场周赛
2259. 移除指定数字得到的最大结果
要注意不能将找出来的前半部分和后半部分转换成整数去比较,数字太长了!
class Solution {
public:
bool compare(string s1, string s2) {
if (s1.size() > s2.size())
return true;
else if (s1.size() < s2.size())
return false;
else {
for (int i = 0; i < s1.size(); i++) {
if (s1[i] < s2[i])
return false;
else if (s1[i] > s2[i])
return true;
}
}
return true;
}
string removeDigit(string number, char digit) {
string res = "", str = "";
for (int i = 0; i < number.size(); i++) {
if (number[i] == digit) {
string s1 = "", s2 = "";
if (i != 0)
s1 = number.substr(0, i);
if (i != number.size() - 1)
s2 = number.substr(i + 1, number.size() - i - 1);
str = s1 + s2;
if (compare(str, res))
res = str;
}
}
return res;
}
};
2260. 必须拿起的最小连续卡牌数
class Solution {
public:
unordered_map<int, int> mp;
int minimumCardPickup(vector<int>& cards) {
int res = 1e9;
for (int i = 0; i < cards.size(); i ++ )
{
if (mp.count(cards[i]))
res = min(i - mp[cards[i]] + 1, res);
mp[cards[i]] = i;
}
if (res == 1e9) return -1;
return res;
}
};
2261. 含最多 K 个可整除元素的子数组——待优化
居然可以直接暴力。。。
class Solution {
public:
int countDistinct(vector<int>& nums, int k, int p) {
set<vector<int>> hashset;
int n = nums.size();
for (int i = 0; i < n; i ++ )
{
vector<int> tmp;
int count = 0;
for (int j = i; j < n; j ++ )
{
tmp.push_back(nums[j]);
if (nums[j] % p == 0) count ++ ;
if (coutn <= k) hashset.insert(tmp);
else break;
}
}
return hashset.size();
}
};
2262. 字符串的总引力
注意:本题是求的子串而不是子序列!
集合
f[i]
:表示以s[i]
结尾的子串的引力总引力
目标
求最大值
集合划分
添加s[i]
对原来子串的影响
- 若
s[i]
在之前没有出现过的话,那么对于每一个以s[i-1]
结尾的子串在其后面加上s[i]
后其引力值都会加1
,因为由i-1
个以s[i-1]
结尾的子串再加上s[i]
单独构成的子串,所以f[i] = f[i-1] + i - 1 + 1 = f[i-1] + i
; - 若s[i]在之前出现过,出现位置为j,那么以
s[0]~s[j]
结尾的子串的引力值不会改变,但是以s[j + 1]~s[i-1]
结尾的子串总共i - 1 - j - 1 + 1 = i - j - 1
个都会加上1
,再加上s[i]
单独构成的子串,f[i] = f[i-1] + i - j - 1 + 1 = f[i-1] + i - j
- 综上,状态表示即为
在这里插入代码片f[i] = f[i-1] + i - j
,第一种状态就是j = 0
的情况
这里需要额外使用一个数组来存储每个字符的最新出现的位置。
class Solution {
public:
long long appealSum(string s) {
int n = s.size();
s = '0' + s; // 使s下标从1开始
vector<long long> f(n + 1); // 默认初始化为0
vector<int> pos(27); // 存储每个字符最近出现的位置
for (int i = 1; i <= n; i ++ )
{
f[i] = f[i - 1] + i - pos[s[i] - 'a'];
pos[s[i] - 'a'] = i;
}
long long ans = 0;
for (auto c : f) // 方案求和
ans += c;
return ans;
}
};
滚动数组优化版本,省去了一部分f
空间开销(求ans
的过程可以放进for
里面,不用单独求)
class Solution {
public:
long long appealSum(string s) {
int n = s.size();
s = '0' + s; // 使s下标从1开始
int f[2] = {0, 0};
vector<int> pos(27); // 存储每个字符最近出现的位置
long long ans = 0;
for (int i = 1; i <= n; i ++ )
{
f[i & 1] = f[(i - 1) & 1] + i - pos[s[i] - 'a'];
ans += f[i & 1]; // 存储每个方案的值
pos[s[i] - 'a'] = i;
}
return ans;
}
};
5月7日:ACwing 第 50 场周赛
4416. 缺少的数
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n;
cin >> n;
vector<int> a(n + 2);
for (int i = 1; i <= n; i ++ )
{
int x;
cin >> x;
a[x] = 1;
}
for (auto i = 1; i < n + 2; i ++ )
if (a[i] == 0)
{
printf("%d\n", i);
break;
}
return 0;
}
4417. 选区间
#include <iostream>
#include <algorithm>
using namespace std;
const int INF = 1e9;
int main()
{
int n, m;
scanf("%d", &n);
int a = INF, b = -INF;
while (n -- )
{
int l, r;
scanf("%d%d", &l, &r);
a = min(a, r);
b = max(b, l);
}
scanf("%d", &m);
int res = 0;
while (m -- )
{
int l, r;
scanf("%d%d", &l, &r);
if (b > r) res = max(res, b - r);
if (a < l) res = max(res, l - a);
}
printf("%d\n", res);
return 0;
}
4418. 选元素
集合
f[i, j]
:从前i
个数中选择j
个数,且选择了第i
个数的所有方案的集合,其集合的和的最大值
集合划分
按照第j-1
个数的选法来分类,由于要保证题目条件原序列中的每一个长度为 k 的连续子序列都至少包含一个被选中的元素
。所以第j-1
个数的下标范围应该为i-k, i-k+1, ... , i - 1
。
假设第j-1
个数选择的是u
且u ∈ [i-k, i-1]
,因为总共选择了j
个数,所以应该在第[1, i-k]
之间选择j-1
个数,且第j-1
个数必须选择u
。简单描述为:从前第1
到第u
个数中选择j-1
个数,并且第u
个数被选择,第j
个数选择第i
个数的所有方案中的最大值,因为所有方案都含有i
,要使方案求和后值最大,就必须使从前u
个数中选择j-1
个数的和最大,即f[u, j - 1] + vi
。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 210;
int n, k, m;
LL f[N][N];
int main()
{
cin >> n >> k >> m;
memset(f, -0x3f, sizeof f);
f[0][0] = 0;
for (int i = 1; i <= n; i ++ )
{
int v;
scanf("%d", &v);
for (int j = 1; j <= m; j ++ ) // 因为最后一个数必须需选择,所以至少有一个数
for (int u = max(i - k, 0); u < i; u ++ )
f[i][j] = max(f[i][j], f[u][j - 1] + v);
}
LL res = -1;
for (int i = n - k + 1; i <= n; i ++ )
res = max(res, f[i][m]);
printf("%lld\n", res);
return 0;
}
5月8日:LeetCode 第292场周赛
2264. 字符串中最大的 3 位相同数字
class Solution {
public:
string largestGoodInteger(string num) {
string res = "";
if (num.size() == 3 && num[0] == num[1] && num[1] == num[2])
return num;
for (int i = 0; i <= num.size() - 3; i ++ )
{
string str = "";
if (num[i] == num[i + 1] && num[i + 2] == num[i + 1])
str = num.substr(i, 3);
if (str != "" && atoi(str.c_str()) >= atoi(res.c_str()))
res = str;
}
return res;
}
};
2265. 统计值等于子树平均值的节点数
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int res = 0;
int averageOfSubtree(TreeNode* root) {
dfs(root);
return res;
}
// 返回以root为根节点的子树(包含root本身)值的和与总节点个数
vector<int> dfs(TreeNode * root)
{
if (root->left == nullptr && root->right == nullptr)
{
res ++ ;
return vector<int>{root->val, 1};
}
// root左右子树为空的情况:若为空值的和为0,总个数也为0
vector<int> l = {0, 0}, r = {0, 0};
if (root->left != nullptr) l = dfs(root->left);
if (root->right != nullptr) r = dfs(root->right);
int sum = l[0] + r[0] + root->val;
int cnt = l[1] + r[1] + 1;
res += sum / cnt == root->val ? 1 : 0;
return vector<int>{sum, cnt};
}
};
2266. 统计打字方案数
class Solution {
const int MOD = 1e9+7;
public:
int countTexts(string pressedKeys) {
int n = pressedKeys.size();
// f[i]、g[i]:表示将连续相同的数字分成不同长度的方案,就相当于楼梯爬一层、两层、三层
vector<long long> f(n + 1), g(n + 1);
f[0] = g[0] = 1;
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= 3 && j <= i; j ++ ) f[i] = (f[i] + f[i - j]) % MOD;
for (int j = 1; j <= 4 && j <= i; j ++ ) g[i] = (g[i] + g[i - j]) % MOD;
}
int cnt = 0;
char last = 0;
long long ans = 1;
for (char c : pressedKeys)
{
if (c != last)
{
if (last == '7' || last == '9') ans = ans * g[cnt] % MOD;
else ans = ans * f[cnt] % MOD;
cnt = 0;
last = c;
}
cnt ++ ;
}
if (last == '7' || last == '9') ans = ans * g[cnt] % MOD;
else ans = ans * f[cnt] % MOD;
return ans;
}
};
2267. 检查是否有合法括号字符串路径
// f[i][j][k]:表示能否存在以格子(i,j)结尾,且求和值为k的括号序列
class Solution {
public:
bool hasValidPath(vector<vector<char>>& grid) {
if (grid[0][0] == ')') return false;
int n = grid.size(), m = grid[0].size();
vector<vector<vector<bool>>> f;
for (int i = 0; i < n; i ++ )
{
f.push_back(vector<vector<bool>> ());
for (int j = 0; j < m; j ++ )
f.back().push_back(vector<bool>(n + m)); // k 最大值 为 n+m-1
}
// 求和过程中值非负,且最后求和值为0为合法
f[0][0][1] = true; // 第一个必为'('
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
if (i || j) // 排除第一个点(0, 0)
{
int t = (grid[i][j] == '(' ? 1 : -1);
for (int k = 0; k < m + n; k ++ )
{
int kk = k - t; // 表示之前一步的求和
if (kk < 0 || kk >= m + n) continue;
// 保证i与j索引合法->只要有其中一条转移路径成立即可,因此用||
if (i) f[i][j][k] = f[i][j][k] || f[i - 1][j][kk];
if (j) f[i][j][k] = f[i][j][k] || f[i][j - 1][kk];
}
}
return f[n - 1][m - 1][0];
}
};
5月14日:ACwing 第51场周赛
4419. 上车
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
int n;
cin >> n;
int res = 0;
for (int i = 0; i < n; i ++ )
{
int x, y;
cin >> x >> y;
if (y - x >= 2)
res ++ ;
}
cout << res << endl;
return 0;
}
4420. 连通分量(求连通块!)
这个题我比赛的时候使用朴素的BFS提交后TLE了!
求连通块的方法:
- dfs
- bfs
- 并查集
比赛时的代码(后面再来优化):
#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1010, M = N * N;
int n, m;
char g[N][N];
string res[N];
PII q[M];
bool st[N][N];
int bfs(int sx, int sy) {
memset(st, false, sizeof st);
memset(q, 0, sizeof q);
int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};
int hh = 0, tt = 0;
q[0] = {sx, sy};
st[sx][sy] = true;
int cnt = 1;
while (hh <= tt) {
PII t = q[hh++];
for (int i = 0; i < 4; i++) {
int a = t.x + dx[i], b = t.y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (g[a][b] == '*') continue;
if (st[a][b]) continue;
q[++tt] = {a, b};
cnt++;
st[a][b] = true;
}
}
return cnt;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)
scanf("%s", g[i]);
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
{
if (g[i][j] == '*')
{
int n = bfs(i, j) % 10;
res[i] += to_string(n);
}
else res[i].push_back(g[i][j]);
}
for (int i = 0; i < n; i++) {
for (auto c : res[i])
cout << c;
cout << endl;
}
return 0;
}
AC代码:并查集求连通块
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010, M = N * N;
int n, m;
int p[M], s[M]; // 并查集以及每个并查集的大小
char g[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
// 将每一个坐标对应到一个唯一整数
int get(int x, int y) {
return x * m + y;
}
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) scanf("%s", g[i]);
for (int i = 0; i < n * m; i++) p[i] = i, s[i] = 1; // 初始化并查集
// 将所有连通块合并:在每一个连通块中任意找一个格子作为根结点,用它来表示该连通块
for (int i = 0; i < n; i++) // 枚举每一个元素
for (int j = 0; j < m; j++)
if (g[i][j] == '.')
for (int k = 0; k < 4; k++) { // 枚举每一个方向
int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == '.') {
int a = get(i, j), b = get(x, y);
a = find(a), b = find(b);
if (a != b) { // 合并并查集
s[b] += s[a];
p[a] = b;
}
}
}
// 枚举每一个格子
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++)
if (g[i][j] == '.') printf(".");
else { // 如果是障碍物
int fathers[4], cnt = 0; // 枚举该点周围的代表元素,存储father中,cnt用于计数
for (int k = 0; k < 4; k++) {
int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == '.') {
int a = get(x, y);
fathers[cnt++] = find(a);
}
}
// 对当前元素周围的连通块判重,有的连通块可能是连在一起的
int sum = 1;
if (cnt) { // 如果至少有2个连通块
sort(fathers, fathers + cnt);
// unique是对相邻元素进行去重,将重复元素放到数组末尾,返回值是去重之后的尾地址
// 去重之后的尾地址 - 首地址 = 数组中不重复的元素个数
cnt = unique(fathers, fathers + cnt) - fathers;
for (int k = 0; k < cnt; k++) // 将所有不同的连通块大小求和
sum += s[fathers[k]];
}
printf("%d", sum % 10);
}
puts("");
}
return 0;
}
4421. 信号(贪心)
首先考虑第一个信号发射器。这个信号发射器一定要覆盖掉第一个房间,否则,就不合法。
对于第一个发射器,假设存在如下图两个信号发射器(都能覆盖到第一个房间),对于最左边的发射器的所有选择方案就可以分为两类:一类是最左边的发射器选择上边一个,一类是最左边的发射器选择下边一个。
显然选择下边一个发射器更好。因为如果选择一个上边的发射器,那么选择选择一个下边的发射器会达到同样的覆盖效果,并且所需要的发射器数量可能更少(因为覆盖范围更大)。因此每选择一个上边的方案都可以在下边找到一个可能更好的方案,所以最优解应该尽可能选择下边的方案。所以第一个发射器选择下边一个。
对于后面的发射器同理,都应该选择尽可能靠右的路由器(下面的一种方案)。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010;
int n, r;
int q[N], cnt; // q存储所有发射器的房间编号
int main()
{
cin >> n >> r;
for (int i = 1; i <= n; i ++ )
{
int a;
cin >> a;
if (a) q[cnt ++ ] = i;
}
int res = 0, last = 0; // last表示上一个覆盖的房间
for (int i = 0; i < cnt; i ++ )
{
if (last >= n) break; // 房间覆盖完了
if (q[i] - r + 1 > last + 1) // 如果当前房间的信号不能覆盖到上一个房间边界
{
res = -1;
break;
}
// 寻找能覆盖掉当前房间的最远的发射器
int j = i;
while (j + 1 < cnt && q[j + 1] - r + 1 <= last + 1)
j ++ ;
last = q[j] + r - 1;
res ++ ;
i = j;
}
if (last < n) res = -1;
cout << res << endl;
return 0;
}