2023.10.26
LC2520. 统计能整出数字的位数
-
解题思路:
- 分解数位,判断每个数位是否能整除num
-
解题代码:
class Solution {
public:
int countDigits(int num) {
int temp = num;
int ans = 0;
while(temp)
{
int bit = temp % 10;
ans += (num % bit == 0);
temp /= 10;
}
return ans;
}
};
CF750C- New Year and Rating
-
解题思路:
-
如果是参加1类,要求上一场竞赛后最大分 >= 1900
-
如果是参加2类,要求上一场竞赛后最小分 < 1900
-
初始化最小分为INT_MIN,最大分为INT_MAX
-
遍历每一场竞赛:
-
如果是1类竞赛
- 最大分小于1900,输出Impossible
- 最小分更新为max(1900, 最小分)+ c[i],最大分如果不为INT_MAX,更新为最大分 + c[i]
-
如果是2类竞赛
- 最小分如果大于等于1900,输出Impossible
- 最大分更新为min(1899,最大分)+ c[i],最小分如果不为INT_MIN,更新为最小分 + c[i]
-
-
如果最大分为INT_MAX,输出Infinity,否则输出最大分
-
-
解题代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n = 0;
cin >> n;
vector<int> c(n), d(n);
for(int i = 0; i <n; i++)
cin >> c[i] >> d[i];
//如果是参加1类,要求上一场竞赛后最大分 >= 1900
//如果是参加2类,要求上一场竞赛后最小分 < 1900
int left = INT_MIN, right = INT_MAX;
for(int i = 0; i < n; i++)
{
if(d[i] == 1)
{
if(right < 1900)
{
cout << "Impossible" << endl;
return 0;
}
left = max(1900, left) + c[i];
if(right != INT_MAX)
right += c[i];
}
else if(d[i] == 2)
{
if(left >= 1900)
{
cout << "Impossible" << endl;
return 0;
}
right = min(right, 1899) + c[i];
if(left != INT_MIN)
left += c[i];
}
}
if(right == INT_MAX)
cout << "Infinity" << endl;
else
cout << right << endl;
return 0;
}
ARC134D Concatnate Subsequences
-
解题思路:
- 先将原始数组拆成a b两个数组,其中各有n个元素
- 使用单调栈,将a处理成一个非递减数组,满足a[i+1] >= a[i]
- 令mn 等于所有满足a[i] = a[0] 的i中最小的b[i]
- 如果mn <= a[0],最小字典序序列就是{a[0],mn}
- 如果mn > a[0],b[0]一定在最后的最小字典序序列中,分两种情况讨论
- 将a中大于b[0]的元素都删掉
- 将a中大于等于b[0]的元素都删掉
- 比较上述两种情况产生的序列,较小的那个即为答案
-
解题代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n = 0;
cin >> n;
vector<int> a(n*2);
for(int i = 0; i < 2 * n; i++)
cin >> a[i];
vector<int> a1, a2;
for(int i = 0; i < n; i++)
{
while(!a1.empty() && a1.back() > a[i])
{
a1.pop_back();
a2.pop_back();
}
a1.push_back(a[i]);
a2.push_back(a[i+n]);
}
int mn = INT_MAX;
int len = a1.size();
for(int i = 0; i < len; i++)
{
if(a1[i] != a1[0])
break;
mn = min(mn, a2[i]);
}
if(mn <= a1[0])
cout << a1[0] << " " << mn << endl;
else
{
vector<int> ans1, ans2;
int idx1 = lower_bound(a1.begin(), a1.end(), a2[0]) - a1.begin();
for(int i = 0; i < idx1; i++)
ans1.push_back(a1[i]);
for(int i = 0; i < idx1; i++)
ans1.push_back(a2[i]);
int idx2 = upper_bound(a1.begin(), a1.end(), a2[0]) - a1.begin();
for(int i = 0; i < idx2; i++)
ans2.push_back(a1[i]);
for(int i = 0; i < idx2; i++)
ans2.push_back(a2[i]);
vector<int> ans = min(ans1, ans2);
for(int i = 0; i < ans.size(); i++)
cout << ans[i] << " ";
cout << endl;
}
//system("pause");
return 0;
}
2023.10.27
LC1465. 切割后面积最大的蛋糕
-
解题思路:
- 横切和纵切是两个相互独立的过程
- 分别找到横切纵切可以得到的最长边长,其乘积就是可以得到的最大面积
-
解题代码:
class Solution {
public:
int maxArea(int h, int w, vector<int>& horizontalCuts, vector<int>& verticalCuts) {
const int MOD = 1e9+7;
long long maxWidth = 0;
long long pre = 0;
sort(verticalCuts.begin(), verticalCuts.end());
for(auto verticalCut : verticalCuts)
{
maxWidth = max(maxWidth, verticalCut - pre);
pre = verticalCut;
}
maxWidth = max(maxWidth, w-pre);
sort(horizontalCuts.begin(), horizontalCuts.end());
long long maxHeight = 0;
pre = 0;
for(auto horizontalCut : horizontalCuts)
{
maxHeight = max(maxHeight, horizontalCut - pre);
pre = horizontalCut;
}
maxHeight = max(maxHeight, h - pre);
return (maxWidth * maxHeight) % MOD;
}
};
2023.10.28
2558. 从数量最多的堆中取走礼物
-
解题代码:
- 用一个优先队列维护有最多礼物数量的堆
- 每次都弹出队首元素,选走礼物,更新礼物数量,再将其入队
-
解题代码:
class Solution {
public:
long long pickGifts(vector<int>& gifts, int k) {
long long ans = 0;
priority_queue<int> pq;
for(auto gift : gifts)
{
ans += gift;
pq.push(gift);
}
while(k--)
{
int temp = pq.top();
pq.pop();
int take = sqrt(temp);
ans -= temp - take;
pq.push(take);
}
return ans;
}
};
2023.10.29
LC274 H指数
-
解题思路:
- 二分答案 + O(n)判断答案是否合法
- 二分枚举h指数mid,判断citations数组中是否满足有大于mid篇论文满足引用次数大于等于mid
-
解题代码:
class Solution {
public:
int hIndex(vector<int>& citations) {
int n = citations.size();
int l = 0, r = n;
auto check = [&](int m) -> bool{
int cnt = 0;
for(int i = 0; i < n; i++)
{
if(citations[i] >= m)
cnt += 1;
}
return cnt >= m;
};
while(l <= r)
{
int m = l + (r-l)/2;
if(check(m))
l = m + 1;
else
r = m - 1;
}
return r;
}
};
2023.10.30
LC275 H指数II
- 题目链接
- 解题思路:
- 相当于是一个二分答案
- 二分枚举h指数mid,判断citations数组中是否存在mid篇论文引用数大于等于mid
- 注意肯定满足有0篇论文被引用了至少0次
- 解题代码:
class Solution {
public:
int hIndex(vector<int>& citations) {
//对数时间复杂度
int n = citations.size();
int l = 1, r = n;
while(l <= r)
{
int mid = l + (r-l)/2;
if(citations[n-mid] >= mid)
l = mid + 1;
else
r = mid - 1;
}
return r;
}
};
CF26B Regular Bracket Sequence
-
解题思路:
-
用栈模拟
-
如果是遇到左括号,入栈,答案加1
-
如果是遇到右括号:
- 栈不为空,弹栈,答案加1
- 栈为空,答案不变
-
最后答案是答案 - 栈中元素数量
-
-
解题代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin >> s;
int ans = 0;
int cnt = 0;
for(auto ch : s)
{
if(ch == '(')
{
cnt += 1;
ans += 1;
}
else
{
if(cnt == 0)
continue;
ans += 1;
cnt -= 1;
}
}
cout << ans - cnt << endl;
return 0;
}
LC32 最长有效括号
-
解题思路:
-
与上面CF的题相比,该题是要求一个合法的最长子串
-
从左向右遍历一次,分别计数左括号与右括号的次数:
- 当左括号 = 右括号次数时,找到一个合法的子串,更新答案
- 当左括号 < 右括号次数时,将左括号右括号出现次数置0
- 当左括号 > 右括号次数时,继续向右检查
-
如果只用上述的思路,会漏掉一种情况:(((()))
-
因此可以再反向使用一次上述的思路,从右向左遍历一次
- 当左括号 = 右括号次数时,找到一个合法的子串,更新答案
- 当左括号 > 右括号次数时,将左括号右括号出现次数置0
- 当左括号 < 右括号次数时,继续向右检查
-
此时答案就是最终答案
-
-
解题代码:
class Solution {
public:
int longestValidParentheses(string s) {
//这题相当于是去找子数组
//左一次右一次
int n = s.size();
int l = 0, r = 0;
int ans = 0;
for(int i = 0; i < n; i++)
{
if(s[i] == '(')
l += 1;
else
r += 1;
if(r == l)
ans = max(ans, r * 2);
else if(r > l)
l = r = 0;
}
l = r = 0;
for(int i = n-1; i >= 0; i--)
{
if(s[i] == ')')
r += 1;
else
l += 1;
if(r == l)
ans = max(ans, r * 2);
else if(l > r)
l = r = 0;
}
return ans;
}
};
- 解题思路2:
- 可以用动态规划的思想来做
- 考虑i和i-1位置的元素:
- 如果i位置为(,最长字串必不可能以i位置结尾,dp[i] = 0
- 如果i位置为),i-1位置为(,二者互相匹配,dp[i] = dp[i-2] + 2
- 如果i位置为),i-1位置为),i位置应该去与以i-1位置为结尾的合法子串前的最后一个字符匹配:
- 如果其为(,匹配成功,dp[i] = dp[i-1] + dp[i-dp[i-1]-2] + 2
- 如果其为),匹配失败,dp[i] = 0
- 需要注意边界的判断
- 解题代码:
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.size();
//dp
vector<int> dp(n, 0);
//考虑为')'的位置 与其前一位置
//')' + '(' 两者匹配
int ans = 0;
for(int i = 1; i < n; i++)
{
if(s[i] == '(')
continue;
if(s[i-1] == '(')//i 和 i-1 匹配,加上以i-2为末尾可以组成的最长有效括号
dp[i] = i - 2 >= 0 ? dp[i-2] + 2 : 2;
else//')' ')' i 和 i-1-dp[i]匹配
{
if(dp[i-1] == 0)
continue;
int idx = i-dp[i-1];
if(idx-1 >= 0 && s[idx-1] == '(')
dp[i] = idx-2 >= 0 ? dp[i-1] + dp[idx-2] + 2 : dp[i-1] + 2;
}
ans = max(ans, dp[i]);
}
return ans;
}
};
-
解题思路3:
- 也可以用栈来做
-
解题代码:
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.size();
int ans = 0;
stack<int> st;
st.push(-1);
for(int i = 0; i < n; i++)
{
if(s[i] == '(')
{
st.push(i);
continue;
}
else
{
st.pop();
if(st.empty())
st.push(i);
else
ans = max(ans, i - st.top());
}
}
return ans;
}
};
2023.10.31
LC2003 每棵子树内缺失的最小基因值
-
解题思路:
-
如果树中没有一个节点的值为1,则树中所有节点缺少的最小基因值都是1
-
树中有节点值为1,从该节点开始向上遍历
- 将该节点及其所有子节点加入哈希表
- 查找最小基因值
- 下一个遍历该节点的父亲节点
-
在从下往上走的过程中,由于上面的节点(对应的子树)一定包含下面的节点,所以下面节点的基因值集合,一定是上面节点的基因值集合的子集,所以上面节点的 ans 值一定大于等于下面节点的 ans 值。这意味着,在计算 ans[i] 时,不需要从 1 开始枚举,而是从 ans[j]开始枚举(假设从 j往上走到了 i)。这可以将时间复杂度优化至 O(n)
-
-
解题代码:
class Solution {
public:
vector<int> smallestMissingValueSubtree(vector<int>& parents, vector<int>& nums) {
//先判断整个树中有没有为1的节点
int n = parents.size();
vector<int> ans(n, 1);
auto it = find(nums.begin(), nums.end(), 1);
if(it == nums.end())//没有
return ans;
//建图
vector<vector<int>> g(n);
for(int i = 1; i < n; i++)
g[parents[i]].push_back(i);
unordered_set<int> vis;
function<void(int)> dfs = [&](int x)
{
vis.insert(nums[x]);
for(auto y : g[x])
{
if(!vis.count(nums[y]))
dfs(y);
}
};
int mex = 2;
int node = it - nums.begin();
while(node >= 0)
{
//从为1的节点开始向上遍历
dfs(node);
while(vis.count(mex))
mex += 1;
ans[node] = mex;
node = parents[node];
}
return ans;
}
};
CF1180B Nick and Array
-
解题思路:
- 两个正数的积 会小于 将两个正数都变成负数之后得到的积
- 所以可以先将数组中的所有数都变成负数
- 此时如果负数的个数是偶数,可以直接输出答案
- 如果负数的个数是奇数,将最小的负数变成正数
-
解题代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n = 0;
cin >> n;
vector<int> a(n);
for(int i = 0; i < n; i++)
cin >> a[i];
int mn = 0;
int idx = -1;
for(int i = 0; i < n; i++)
{
if(a[i] >= 0)
a[i] = -a[i]-1;
if(a[i] < mn)
{
mn = a[i];
idx = i;
}
}
if(n & 1)
a[idx] = -a[idx]-1;
for(int i = 0; i < n; i++)
cout << a[i] << " ";
cout << endl;
//system("pause");
return 0;
}
2023.11.01
LC2127. 参加会议的最多员工数
-
解题思路:
- 基环树
- 先用拓扑排序,找到图中存在的环
- 如果环长大于2,桌子上只能坐环中的这些人
- 如果环长等于2,除去环中的人外,还分别可以坐环中人向环外方向最长路径的人
- 因此定义dfs(i, fa),用于去找从i节点开始向环外的最长路径
- 需要注意的是,环长为2的环之间可以互相拼接
-
解题代码:
class Solution {
public:
int maximumInvitations(vector<int>& favorite) {
int n = favorite.size();
vector<vector<int>> g(n);
vector<vector<int>> g_out(n);
vector<int> deg(n, 0);
for(int i = 0; i < n; i++)
{
g[i].push_back(favorite[i]);
g_out[favorite[i]].push_back(i);
deg[favorite[i]] += 1;
}
//拓扑排序
queue<int> q;
for(int i = 0; i < n; i++)
{
if(deg[i] == 0)
q.push(i);
}
while(!q.empty())
{
auto x = q.front();
q.pop();
for(auto y : g[x])
{
deg[y] -= 1;
if(deg[y] == 0)
q.push(y);
}
}
//反向dfs找节点到根节点的最长路径
function<int(int, int)> dfs = [&](int i, int fa) -> int
{
int ans = 1;
for(auto y : g_out[i])
{
if(y != fa)
ans = max(ans, dfs(y, i) + 1);
}
return ans;
};
int ans = 0;
//找基环 判断长度
int lenFor2 = 0;//长度为2的环的总长度
for(int i = 0; i < n; i++)
{
if(deg[i] == 0)
continue;
queue<int> q;
vector<int> ring;
q.push(i);
deg[i] -= 1;
ring.push_back(i);
while(!q.empty())
{
auto x = q.front();
q.pop();
for(auto y : g[x])
{
deg[y] -= 1;
if(deg[y] == 0)
{
q.push(y);
ring.push_back(y);
}
}
}
int len = ring.size();
if(len > 2)
ans = max(ans, len);
else if(len == 2)
lenFor2 += dfs(ring[0], ring[1]) + dfs(ring[1], ring[0]);
}
ans = max(ans, lenFor2);
return ans;
}
};
- 优化:在求环上节点到环外的最长路径时,可以不用dfs,而是在拓扑排序的过程中进行记录
class Solution {
public:
int maximumInvitations(vector<int>& favorite) {
int n = favorite.size();
vector<vector<int>> g(n);
vector<int> deg(n, 0);
vector<int> longestPath(n, 1);
for(int i = 0; i < n; i++)
{
g[i].push_back(favorite[i]);
deg[favorite[i]] += 1;
}
//拓扑排序
queue<pair<int, int>> q;
for(int i = 0; i < n; i++)
{
if(deg[i] == 0)
q.push({i, longestPath[i]});
}
while(!q.empty())
{
auto [x, length] = q.front();
q.pop();
for(auto y : g[x])
{
deg[y] -= 1;
longestPath[y] = max(length + 1, longestPath[y]);
if(deg[y] == 0)
q.push({y, longestPath[y]});
}
}
int ans = 0;
//找基环 判断长度
int lenFor2 = 0;//长度为2的环的总长度
for(int i = 0; i < n; i++)
{
if(deg[i] == 0)
continue;
queue<int> q;
vector<int> ring;
q.push(i);
deg[i] -= 1;
ring.push_back(i);
while(!q.empty())
{
auto x = q.front();
q.pop();
for(auto y : g[x])
{
deg[y] -= 1;
if(deg[y] == 0)
{
q.push(y);
ring.push_back(y);
}
}
}
int len = ring.size();
if(len > 2)
ans = max(ans, len);
else if(len == 2)
lenFor2 += longestPath[ring[0]] + longestPath[ring[1]];
}
ans = max(ans, lenFor2);
return ans;
}
};
CF777C Alyona and Spreadsheet
-
解题思路:
- mnLine维护对于每一行,存在满足非递减子数组的最小行
- 对于每一列数据,分别单独考虑,并更新mnLine数组
- 对于每一个task,判断 l 与 mnLine[r] 的关系
- 如果 l < mnLine[r] 输出No
- 否则 输出Yes
-
解题代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m));
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
cin >> grid[i][j];
}
vector<int> mnLine(n, INT_MAX);
for(int j = 0; j < m; j++)//检查第j列
{
int pre = -1;
for(int i = 0; i < n; i++)
{
if(pre == -1 || grid[i][j] < grid[i-1][j])
pre = i;
mnLine[i] = min(mnLine[i], pre);
}
}
int k = 0;
cin >> k;
for(int i = 0; i < k; i++)
{
int l, r;
cin >> l >> r;
l -= 1;
r -= 1;
if(l >= mnLine[r])
cout << "Yes" << endl;
else
cout << "No" << endl;
}
//system("pause");
return 0;
}