2023.11.02
LC2103 环和杆
-
解题思路:
- 遍历字符串,用一个cnt数组记录每个位置颜色出现的情况
- RGB分别对应出现情况的后三位
- 统计出现情况为7的位置个数
-
解题代码:
class Solution {
public:
int countPoints(string rings) {
vector<int> cnt(10, 0);
int n = rings.size();
for(int i = 0; i < n; i += 2)
{
int idx = rings[i+1] - '0';
if(rings[i] == 'R')
cnt[idx] |= 1 << 0;
else if(rings[i] == 'G')
cnt[idx] |= 1 << 1;
else
cnt[idx] |= 1 << 2;
}
return count(cnt.begin(), cnt.end(), 7);
}
};
CF1679D Toss a Coin to Your Graph…
-
解题思路:
-
最小化最大值 => 二分答案
-
二分枚举当前可以使用的最大值,将符合条件的顶点构成一张新的有向图,判断是否满足条件
- 如果新图中有环,可以组合成任意长度的路径,因此直接返回true
- 否则,计算新图中的最长路径长度,如果其大于等于k,返回true,否则返回false
-
如果枚举了所有情况都不存在满足条件的图,返回-1
-
-
解题代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
long long n, m, k;
cin >> n >> m >> k;
vector<int> a(n);
for(int i = 0; i < n; i++)
cin >> a[i];
vector<vector<int>> g(n);
for(int i = 0; i < m; i++)
{
int u, v;
cin >> u >> v;
u -= 1;
v -= 1;
g[u].push_back(v);
}
auto check = [&](int m) -> bool
{
vector<int> deg(n, 0);
vector<int> longestPath(n, 1);
for(int u = 0; u < n; u++)
{
for(auto &v : g[u])
{
if(a[u] <= m && a[v] <= m)//有效边
deg[v] += 1;
}
}
queue<int> q;
for(int i = 0; i < n; i++)
{
if(a[i] <= m && deg[i] == 0)
q.push(i);
}
while(!q.empty())
{
auto u = q.front();
q.pop();
for(auto &v : g[u])
{
if(a[v] > m)
continue;
longestPath[v] = max(longestPath[v], longestPath[u] + 1);
deg[v] -= 1;
if(deg[v] == 0)
q.push(v);
}
}
//判断是否有环,如果有环 返回true
for(int i = 0; i < n; i++)
{
if(deg[i] != 0)
return true;
}
//无环且最长路径 > k 返回true
int length = *max_element(longestPath.begin(), longestPath.end());
return length >= k;
};
int l = *min_element(a.begin(), a.end());
int r = *max_element(a.begin(), a.end());
while(l <= r)
{
int m = l + (r - l) / 2;
if(check(m))
r = m - 1;
else
l = m + 1;
}
int ans = l > *max_element(a.begin(), a.end()) ? -1 : l;
cout << ans << endl;
//system("pause");
return 0;
}
2023.11.03
LC117 填充每个节点的下一个右侧节点指针 II
- 题目链接
- 解题思路1:
- 传统的用队列实现的BFS
- 层序遍历,对每层节点从左到右连起来
- 时间复杂度On,空间复杂度On
- 解题代码:
class Solution {
public:
Node* connect(Node* root) {
if(!root)
return root;
vector<Node*> q;
q.push_back(root);
while(!q.empty())
{
vector<Node*> cur;
int len = q.size();
for(int i = 0; i < len; i++)
{
if(i != len-1)
q[i]->next = q[i+1];
if(q[i]->left)
cur.push_back(q[i]->left);
if(q[i]->right)
cur.push_back(q[i]->right);
}
q = move(cur);
}
return root;
}
};
-
解题思路2:
- 既然每一层都连接成一个链表了,那么知道链表头,就能访问这一层的所有节点
- 从第一层开始(第一层只有一个 root\textit{root}root 节点),每次循环:
- 遍历当前层的链表节点,通过节点的 left\textit{left}left 和 right\textit{right}right 得到下一层的节点。
- 把下一层的节点从左到右连接成一个链表。
- 拿到下一层链表的头节点,进入下一轮循环。
- 时间复杂度On,空间复杂度O1
-
解题代码:
class Solution {
public:
Node* connect(Node* root) {
Node *cur = root;
while(cur)
{
Node *dummy = new Node();
Node *p = dummy;
while(cur)
{
if(cur->left)
{
p->next = cur->left;
p = p->next;
}
if(cur->right)
{
p->next = cur->right;
p = p->next;
}
cur = cur->next;
}
cur = dummy->next;
}
return root;
}
};
-
解题思路3:
- 可以用dfs
- 用一个长度为树高的数组,记录每一层当前尾部节点
- 再遍历到该层节点时,连在当前层尾部节点后面,并更新尾部节点
- 时间复杂度On,空间复杂度Oh,h是树的高度
-
解题代码:
lass Solution {
public:
Node* connect(Node* root) {
if(!root)
return root;
vector<Node*> pre;
function<void(Node*, int)> dfs = [&](Node* node, int depth)
{
if(depth == pre.size())
pre.push_back(node);
else
{
pre[depth]->next = node;
pre[depth] = node;
}
if(node->left)
dfs(node->left, depth+1);
if(node->right)
dfs(node->right, depth+1);
};
dfs(root, 0);
return root;
}
};
CF13E Holes
-
解题思路:
- 我的一些碎碎念:
- 当修改一个结点的值时,只会影响后面怎么走,不会影响其他的事情
- 怎么感觉可以抽象成一个并查集的问题呢?power[i] 相当于有一条 (a[i] + power[i]) 指向 (a[i])的边
- 维护成图的样子,每次操作0相当于删一条边,再加一条边
- 操作1就相当于求该节点到根节点的距离
- 我的一些碎碎念:
-
解题代码: TLE13
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
vector<int> a(n);
for(int i = 0; i < n; i++)
cin >> a[i];
//建图
vector<int> g(n, -1);
for(int i = 0; i < n; i++)
{
int target = i + a[i];
if(target < n)
g[i] = target;
}
function<pair<int, int>(int i)> dfs = [&](int i) -> pair<int, int>
{
if(g[i] == -1)
return {i, 1};
auto [pos, cnt] = dfs(g[i]);
return {pos, cnt+1};
};
for(int i = 0; i < m; i++)
{
int op;
cin >> op;
if(op == 0)
{
int idx, val;
cin >> idx >> val;
idx -= 1;
int target = idx + val;
if(target < n)
g[idx] = target;
else
g[idx] = -1;
}
else
{
int idx;
cin >> idx;
idx -= 1;
auto [pos, cnt] = dfs(idx);
cout << pos+1 << " " << cnt << endl;
}
}
//system("pause");
return 0;
}
- 灵神题解:
- 分块,每块大小 sqrt(n)=316。(注:改成 500 可能更快一些)
- 把每块看成一个单独的问题,算出这一块中的每个下标 i,在跳出这个块之前,最后一次的位置 last 和跳跃次数 jump。
- 一开始可以倒着 O(n) 递推算出所有的 last 和 jump。
- 对于操作 0,用 O(sqrt(n)) 的时间更新 p 所在块的 last 和 jump
- 对于操作 1,用每一块的 last 和 jump 来快速跳跃,这样只需要 O(sqrt(n)) 就可以跳出数组。
- 需要用scanf、printf 不然会超时
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
scanf("%d%d", &n, &m);
vector<int> a(n);
for(int i = 0; i < n; i++)
scanf("%d", &a[i]);
const int block = 500;
//每个节点都保存在当前块中跳多少次,最后跳到哪里
vector<pair<int, int>> data(n);
auto f = [&](int i)
{
if(i + a[i] >= n || i / block != (i + a[i]) / block)
data[i] = {i, 0};
else
{
data[i] = data[i+a[i]];
data[i].second += 1;
}
};
for(int i = n-1; i >= 0; i--)
f(i);
for(int i = 0; i < m; i++)
{
int op, p;
scanf("%d%d", &op, &p);
p -= 1;
if(op == 0)
{
int v;
scanf("%d", &v);
a[p] = v;
for(int j = p; j >= p - p % block; j--)
f(j);
}
else
{
int jumpCnt = 0;
int lastPos = p;
for(; p < n; p = lastPos + a[lastPos])
{
jumpCnt += data[p].second + 1;
lastPos = data[p].first;
}
printf("%d %d\n", lastPos + 1, jumpCnt);
}
}
//system("pause");
return 0;
}
2023.11.04
LC421 数组中两个数的最大异或值
-
解题思路:
- 使用前缀树(Trie)
- 对每个数分解二进制
- 查询时,从高位到低位在树中查找,如果该数对应位为0,则应该尽量向当前节点为1的儿子方向查找;如果当前节点没有1儿子,则只能向0儿子方向查找
- 插入时,从高位到低位将各数位更新到树中
-
解题代码:
class Trie {
public:
Trie():children(2){}
void insert(int num);
int query(int num);
private:
vector<Trie*> children;
};
void Trie::insert(int num)
{
Trie *cur = this;
for(int i = 31; i >= 0; i--)
{
int bit = num >> i & 1;
if(!cur->children[bit])
cur->children[bit] = new Trie();
cur = cur->children[bit];
}
}
int Trie::query(int num)
{
Trie *cur = this;
int ans = 0;
for(int i = 31; i >= 0; i--)
{
int bit = num >> i & 1;
if(cur->children[1-bit])
{
ans += (1 << i);
cur = cur->children[1-bit];
}
else
cur = cur->children[bit];
}
return ans;
}
class Solution {
public:
int findMaximumXOR(vector<int>& nums) {
Trie *root = new Trie();
int n = nums.size();
int ans = 0;
root->insert(nums[0]);
for(int i = 1; i < n; i++)
{
ans = max(ans, root->query(nums[i]));
root->insert(nums[i]);
}
return ans;
}
};
2023.11.05
LC187 重复的DNA序列
-
解题思路:
- 用一个map记录字符串中各长度为10的子串的出现次数
- 将出现次数大于1次的子串加到答案中
-
解题代码:
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
int n = s.size();
unordered_map<string, int> um;
for(int i = 0; i <= n-10; i++)
{
string temp = s.substr(i, 10);
um[temp] += 1;
}
vector<string> ans;
for(auto item : um)
{
if(item.second > 1)
ans.push_back(item.first);
}
return ans;
}
};
2023.11.06
LC318 最大单词长度乘积
-
解题思路:
- 首先预处理,用一个整型数字记录各单词中出现的字母情况
- 二重循环,枚举可能出现的单词情况,如果两个单词出现情况置相与等于0,说明两个单词不含有公共字母,使用其长度乘积更新答案
-
解题代码:
class Solution {
public:
int maxProduct(vector<string>& words) {
vector<int> occurs;
for(auto word : words)
{
int mask = 0;
for(auto ch : word)
mask |= 1 << (ch - 'a');
occurs.push_back(mask);
}
int ans = 0;
int n = words.size();
for(int i = 0; i < n; i++)
{
for(int j = i + 1; j < n; j++)
{
if((occurs[i] & occurs[j]) == 0)
ans = max(ans, (int)words[i].size() * (int)words[j].size());
}
}
return ans;
}
};
CF816B Karen and Coffee
-
解题思路:
- 用差分数组维护各个推荐区间
- 对差分数组求前缀和,得到各个温度的推荐次数
- 再对推荐次数数组求前缀和,只更新推荐次数 >= k的那些温度
- 对于每一个查询,可用上一步中计算出来的前缀和计算
-
解题代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, k, q;
cin >> n >> k >> q;
const int mx = 200001;
vector<int> num(mx, 0);
for(int i = 0; i < n; i++)
{
int l, r;
cin >> l >> r;
num[l] += 1;
num[r+1] -= 1;
}
vector<int> origin(mx, 0);
for(int i = 0; i < mx; i++)
origin[i] = i == 0 ? num[i] : origin[i-1] + num[i];
vector<int> s(mx+1, 0);
for(int i = 1; i < mx+1; i++)
s[i] = s[i-1] + (origin[i-1] >= k);
for(int i = 0; i < q; i++)
{
int l, r;
cin >> l >> r;
cout << s[r+1] - s[l] << endl;
}
//system("pause");
return 0;
}
2023.11.07
LC2586 统计范围内的元音字符串数
-
解题思路:
- 对于范围[left, right]内的每个字符串,判断其是否首字母和尾字母均为元音字母,如果是则答案+1
-
解题代码:
class Solution {
public:
int vowelStrings(vector<string>& words, int left, int right) {
unordered_set<char> us = {'a', 'e', 'i', 'o', 'u'};
int ans = 0;
for(int i = left; i <= right; i++)
{
if(us.count(words[i][0]) && us.count(words[i].back()))
ans += 1;
}
return ans;
}
};
CF1106D Lunar New Year and a Wander
-
解题思路:
- 类似于广度优先搜索的思路
- 用一个小根堆维护当前可以访问的结点,用vis数组记录各结点是否被访问过
- 每次都选择访问小根堆堆顶结点,并将其相连且未被访问过的结点加到堆中
-
解题代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
vector<vector<int>> g(n);
for(int i = 0; i < m; i++)
{
int x, y;
cin >> x >> y;
x -= 1;
y -= 1;
g[x].push_back(y);
g[y].push_back(x);
}
priority_queue<int, vector<int>, greater<>> pq;
vector<bool> vis(n, false);
pq.push(0);
vis[0] = true;
while(!pq.empty())
{
auto x = pq.top();
pq.pop();
cout << x+1 << " ";
for(auto y : g[x])
{
if(!vis[y])
{
pq.push(y);
vis[y] = true;
}
}
}
cout << endl;
//system("pause");
return 0;
}
LC2262. 字符串的总引力
-
解题思路:
- 从左向右遍历整个字符串
- f[i] 表示以i开头 到 当前遍历位置这个字符串有多少不同字符
- 假设当前遍历位置为cur,其字符是ch,ch上一次出现位置为pre,那么[pre+1, cur]范围内的f[i]均需要+1
-
解题代码:
class Solution {
public:
long long appealSum(string s) {
long long ans = 0;
unordered_map<char, int> um;
int n = s.size();
long long temp = 0;
for(int i = 0; i < n; i++)
{
if(um.count(s[i]))
temp += i - um[s[i]];
else
temp += i+1;
ans += temp;
um[s[i]] = i;
}
return ans;
}
};
2023.11.08
LC2609 最长平衡子字符串
-
解题思路:
- 子字符串长度一定是偶数
- 从大到小倒序枚举子字符串偶数长度,检查是否存在满足条件的子字符串。如果存在直接返回答案
- 如果所有长度都不存在满足条件的子字符串,返回0
-
解题代码:
class Solution {
public:
int findTheLongestBalancedSubstring(string s) {
//长度是偶数的子串,且前一半全是0,后一半全是1
int n = s.size();
//倒序枚举长度
int len = n & 1 ? n-1 : n;
for(len; len > 0; len -= 2)
{
for(int i = 0; i < n; i++)
{
if(i + len > n)
break;
int l = i, r = i+len-1;
while(l <= r)
{
if(s[l] != '0' || s[r] != '1')
break;
l += 1;
r -= 1;
}
if(l > r)
return len;
}
}
return 0;
}
};
-
解题思路2:
- 记录上一段连续相同字符个数pre,以及当前连续相同字符个数cur
- 如果当前字符是1,那么上一段的字符是0,这两段可以组成一个01串,其长度为2 * min(pre, cur)
-
解题代码:
class Solution {
public:
int findTheLongestBalancedSubstring(string s) {
int pre = 0;
int cur = 0;
int ans = 0;
int n = s.size();
for(int i = 0; i < n; i++)
{
cur += 1;
if(i == n-1 || s[i] != s[i+1])
{
if(s[i] == '1')
ans = max(ans, 2 * min(pre, cur));
pre = cur;
cur = 0;
}
}
return ans;
}
};
CF1140C Playlist
-
解题思路:
- 按照b从大到小排序,枚举最小的b,把当前t和其左边的t都加到一个最小堆中,从而维护堆中最大的k个数的和
-
解题代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, k;
cin >> n >> k;
vector<int> t(n), b(n);
for(int i = 0; i < n; i++)
cin >> t[i] >> b[i];
vector<pair<int, int>> songs;
for(int i = 0; i < n; i++)
songs.push_back({t[i], b[i]});
sort(songs.begin(), songs.end(), [&](const pair<int, int>& a, const pair<int, int>& b)
{
return a.second > b.second;
});
long long ans = 0;
long long sum = 0;
priority_queue<int, vector<int>, greater<>> pq;
for(int i = 0; i < n; i++)
{
if(pq.size() < k || pq.top() < songs[i].first)
{
pq.push(songs[i].first);
sum += songs[i].first;
if(pq.size() > k)
{
sum -= pq.top();
pq.pop();
}
}
ans = max(ans, sum * songs[i].second);
}
cout << ans << endl;
//system("pause");
return 0;
}