周赛

Leetcode 周赛复盘

第426场周赛

100501. 仅含置位位的最小整数

Easy. 只需要不断将1向左移,直到大于该数即可,返回此时的数-1。

class Solution {
public:
int smallestNumber(int n) {
int i=1;
while(i <= n)
i <<= 1;
return i-1;
}
};

100444. 识别数组中的最大异常值

Medium.
不要慌,首先思考一下,寻找到异常值后,会出现剩下的元素之和等于其中一个元素。这样我们的和肯定是偶数。那么,只需要在累加的时候统计一下数字出现的情况,然后:

  1. 判断和是否为偶数。
  2. 一个个挑选出异常值,然后判断和的一半是否存在。存在的情况包括:(1)和的一半与异常值相同且异常值出现了不止一次。(2)和的一半与异常值不同且出现了一次。
  3. 直到最后找到最大的异常值即可。
class Solution {
public:
int getLargestOutlier(vector<int>& nums) {
int sum = 0;
vector<int> cal(2005,0);
for(auto i:nums)
{
cal[i+1000] ++;
sum += i;
}
int ans = -2005;
for(auto i:nums)
{
sum -= i;
if(sum % 2 != 0)
{
sum+=i;
continue;
}
int temp = sum / 2;
if(temp+1000 >= 0 && temp+1000 <= 2000 && cal[temp+1000])
{
if(temp == i && cal[temp+1000] > 1 || temp != i)
ans = (ans > i) ? ans : i;
}
sum += i;
}
return ans;
}
};

100475. 连接两棵树后最大目标节点数目

参考:灵神的题解,膜拜大佬orz

首先很明显,要加边的端点在第一棵树上必然是端点i。
接下来需要考虑第二棵树上应该添加到哪个节点。暴力枚举第二个节点。DFS计算距离j不超过k1的节点个数cntj。要注意新添加的边,所以是k1cntj取得最大值,记为max2。新添加的边连接到其对应的节点上。

暴力枚举第一棵树节点i,DFS计算距离i不超过k的节点个数。则有cnti+cntj为答案。

class Solution {
public:
vector<vector<int> > buildGraph(vector<vector<int> > & edges)
{
// 建立无向图邻接链表。
vector<vector<int> > g(edges.size() + 1); // 边数
for (auto &e: edges)
{
int v1 = e[0];
int v2 = e[1];
g[v1].push_back(v2);
g[v2].push_back(v1);
}
return g;
}
int dfs(int cur, int pa, int dist, vector<vector<int> >&g, int k)
{
// dfs 终点。
if(dist > k)
return 0;
// 计数:当前状态为1.
int cnt = 1;
for(int next:g[cur])
{
// 无向图:下一个节点和上一个节点不形成环。递归累加。
if(next != pa)
cnt += dfs(next, cur, dist+1, g, k);
}
return cnt;
}
vector<int> maxTargetNodes(vector<vector<int>>& edges1, vector<vector<int>>& edges2, int k) {
int max2 = 0;
if(k > 0)
{
vector<vector<int> > graph2 = buildGraph(edges2);
for(int i = 0; i < edges2.size()+1; i++)
max2 = max(max2, dfs(i, -1, 0, graph2, k-1));
}
vector<vector<int> > graph1 = buildGraph(edges1);
vector<int> ans(edges1.size() + 1);
for(int i = 0; i < ans.size(); i++)
ans[i] = dfs(i,-1,0,graph1,k) + max2;
return ans;
}
};

436 Weekly

3446.按对角线进行矩阵排序

暴力排序即可。对于每一个对角线,我们有其差为定值,并且横纵下标的差值在 [(n1),n1] 之间。

class Solution {
public:
vector<vector<int>> sortMatrix(vector<vector<int>>& grid) {
int size = grid.size();
int diff = size;
vector<int> temp;
for(diff = size - 1; diff > 0; diff--)
{
int x = 0;
int y = diff - x;
temp = vector<int>();
while(y < grid.size())
{
temp.push_back(grid[x][y]);
x++;
y++;
}
sort(temp.begin(),temp.end());
x = 0;
y = diff - x;
int i = 0;
while(y < grid.size())
{
grid[x][y] = temp[i];
x++;
y++;
i++;
}
}
for(diff = 0; diff > 1 - size; diff--)
{
int x = 0;
int y = x - diff;
temp = vector<int>();
while(y < grid.size())
{
temp.push_back(grid[y][x]);
x++;
y++;
}
sort(temp.begin(),temp.end(),std::greater<int>());
x = 0;
y = x - diff;
int i = 0;
while(y < grid.size())
{
grid[y][x] = temp[i];
x++;
y++;
i++;
}
}
return grid;
}
};

3447.将元素分配给有约束条件的组

  1. 对element不断遍历,每次从element元素开始,不断增加到group元素最大值,标记上索引最小值。
  2. 要注意当element元素大于group最大值时肯定不能整除。
  3. 遍历得到结果。

时间复杂度

得到group的最大值为 O(n). 设其为 M。
我们有element的长度为m,这样我们需要遍历element进行标注。我们每个内循环就有遍历 Mk 次。这样就有:

M(1+12+13++1m)=O(Mlogm)

这样我们的时间复杂度应为: O(max{Mlogm,n})

3448. 统计可以被最后一个数位整除的子字符串数目

好难。。。

我们将最后一个字符设置成 s[i], 这是一个0-9的数字。接下来我们有:

vali110+s[i]=vali(vali110+s[i])modk=valimodkvali1modk10+s[i]modk=valimodk

这样就可以用以 s[i1] 为结尾的子字符串的余数来代替val。我们有:

(Remi110+s[i1])modk=Remimodk

我们现在需要知道以最后一个字符结尾的子字符串有多少在对最后一个字符取模之后的值为0.

动态规划

转移方式:以 s[i] 结尾的字符串具有的子字符串即为 dp[i]。影响其转移的方式有两种:

  • 原先非整除,现在整除;
  • 原先整除,现在不整除。
  • 原先整除,现在仍然整除。

余数和对每个数取模后的数都会影响到状态方程的转移。因此我们用j代表对j取模,用k来代表余数。这样根据上面的方式,我们有:

dp[i+1][j][((k10)+s[i])modj]+=dp[i][j][k]

初始条件:仅有字符 s[i]
这样 dp[i+1][j][s[i]modj]=1.

空间优化:采用滚动数组

用temp数组来暂存中间值。这样我们有:

  • 初始化: temp[s[i]modk]=1
  • 进行转移: temp[((k10)+s[i])modj]+=dp[j][k]
  • 赋值: dp[j]=temp.
class Solution {
public:
long long countSubstrings(string s) {
// 这样动态转移方程:
// dp[i+1][j][(k * 10+s[i]) mod j] += dp[i][j][k];
// 字符0-9.
// 优化空间:dp'[j][k*10+s[i] mod j] += dp[j][k];
vector<vector<int> > dp(10, vector<int>(10,0));
vector<int> a(10,0);
long long ans = 0;
for(int i = 0; i < s.length(); i++)
{
int si = s[i] - '0';
for(int j = 1; j < 10; j++)
{
a = vector<int>(10,0);
a[si % j] = 1; // 仅当前字符为自字符串。
for(int k = 0; k < 10; k++)
{
a[((k * 10) + si) % j] += dp[j][k];
}
dp[j] = a;
}
ans += dp[si][0];
}
return ans;
}
};

437 Weekly

3457.吃披萨

我们需要寻找它的最优子状态。我们可以发现,我们可以先进行奇数天的选择,再进行偶数天的选择,这样的选择必定是最大的。

假设我们有 4n 个披萨。这样我们就需要 n 个最终的值来作为我们的总体重。考虑一个已经排好序的数组 {a1,a2,,a4n}, 我们在奇数天选择尽可能大的值,也就是 {a1,a2,,an+12}, 在偶数天选择 {an+32,an+52,}, 这样的选择将会比放弃在奇数天选择最大的要多。

因此我们直接采取贪心的策略即可。

class Solution {
public:
long long maxWeight(vector<int>& p) {
int n=p.size();
long long res=0;
int k=n/4;
int l=(k+1)/2;
int r=k-l;
sort(p.begin(),p.end(),greater<int>());
for(int i=0;i<l;++i){
res+=p[i];
}
for(int i=0;i<r;++i){
res+=p[l+2*i+1];
}
return res;
}
};

3458. 选择K个互不重叠的特殊子字符串

  1. 统计每个字符在字符串中出现的位置。
  2. 筛选出仅包含这些字符的区间间隔。也就是逐个检查每个字符的最左到最右区间,统计这其中其他字符出现的次数。如果少于整个字符串中该字符出现的次数,则说明字符在其他地方出现。因此扩张间隔直到所有字符全部仅在该区间出现为止。(这里不要遍历字符串,因为n远大于间隔集合(间隔集合最多26个),因此检查间隔集合。)
  3. 利用贪心的方法去重。对间隔的一个区间进行排序,贪心地选择更小的集合从而达到不重叠的目的。
class Solution {
public:
#define pii pair<int,int>
bool maxSubstringLength(string s, int k) {
// 1. 遍历所有的字符,生成区间。
int n = s.length();
unordered_map<char, vector<int> > positions;
for(int i = 0; i < n; i++) {
positions[s[i]].push_back(i);
}
vector<pii> intervals;
for(auto [ch, ind]: positions) {
int left = ind[0];
int right = ind[ind.size() - 1];
bool flag = true;
while(flag) {
flag = false;
// 检查子串。
for(auto [ch1,ind1]: positions) {
if(ch1 == ch) continue;
else {
int count = upper_bound(ind1.begin(), ind1.end(), right) - lower_bound(ind1.begin(),ind1.end(), left);
if(count > 0 && count < ind1.size()) {
left = min(left, ind1[0]);
right = max(right, ind1.back());
flag = true;
}
}
}
}
if(left > 0 || right < n-1) intervals.emplace_back(make_pair(right, left));
}
sort(intervals.begin(),intervals.end());
int preRight = -1, count = 0;
for(auto inter: intervals) {
if(inter.second > preRight) {
preRight = inter.first;
count++;
}
}
return count >= k;
}
};

439 Weekly

3472.至多K次操作后形成的最长回文子序列

区间DP

递归方式

一、子问题和转移状态

我们需要定义我们的最优子结构。认为经过k次操作后,我们的最长回文序列的长度为 dp(i,j,k)。这样我们就有以下三种情况:
1.不加入 s[i]。这样我们就有: dp(i,j,k)=dp(i+1,j,k)
2.不加入 s[j]。这样我们就有: dp(i,j,k)=dp(i,j1,k)
3.将 s[i]s[j] 进行变换成为回文字符串,加入到当前字符串中。设我们需要变换 l 次,这样我们就有: dp(i,j,k)=dp(i+1,j1,kl)+2。 因为两个字符都加入了回文串。

计算 l,需要进行一下计算。
1.我们可以先计算一下两个字母之间的差距。l=abs(s[i]s[j]).
2.我们注意到字符可以相邻变小,也可以变大。l=min(l,26l).

这样我们的l就是我们的操作次数。

二、递归终点

我们每次i,j会只变化一边一次或者两次,因此会出现 i=j 和 i=j+1 的情况。我们注意到单个字符是回文串,因此 dp(i,i,k)=1
另一种情况没有字符,因此为 dp(i+1,i,k)。这样我们有当i == j或i == j+1时立刻返回。

三、记忆化

递归的时候一定要记得记忆化,已经计算过的值可以剪枝,也就是立刻返回。这样我们初始化的时候可以设置一个小于0的值。

class Solution {
public:
int longestPalindromicSubsequence(string s, int k) {
int n = s.length();
// dp(i,j,k);
vector<vector<vector<int> > > dp(n, vector<vector<int> >(n, vector<int>(k+1,-1)));
auto dfs = [&](this auto && self, int i, int j, int kk) -> int {
if(i == j) {
return 1;
}
else if(i == j+1) {
return 0; // Initialzed as 0.
}
if(dp[i][j][kk] >= 0) return dp[i][j][kk];
// Option 1 and 2: discard the s[i] or s[j];
dp[i][j][kk] = max(self(i+1,j,kk), self(i, j-1, kk));
int l = min(abs(s[i] - s[j]), (26 - abs(s[i]-s[j])));
if(kk - l >= 0)
dp[i][j][kk] = max(dp[i][j][kk], self(i+1,j-1,kk-l) + 2);
return dp[i][j][kk];
};
// The start point, the final answer is the dp(0,n-1,k);
return dfs(0, n-1, k);
}
};
变成递推

我们可以通过观察的方式来进行递推。
1.注意到i需要通过i+1和i获得,因此i需要从大到小
2.注意到j需要通过j-1和j获得,因此j需要从小到大
3.注意到k需要通过k-l和k获得,因此k需要从小到大

注意到我们需要访问频繁且跨度较远的是k,因此将k放在第一维度即可。

class Solution {
public:
int longestPalindromicSubsequence(string s, int k) {
int n = s.length();
// dp(i,j,k);
vector<vector<vector<int> > > dp(k+1, vector<vector<int> >(n, vector<int>(n,0)));
// 不知道用什么变量名了实在想不到了
for(int kk = 0; kk <= k; kk++) {
for(int i = n-1; i >= 0; i--) {
dp[kk][i][i] = 1;
for(int j = i+1; j < n; j++) {
dp[kk][i][j] = max(dp[kk][i][j-1], dp[kk][i+1][j]);
int l = abs(s[i]-s[j]);
l = min(26 - l, l);
if(kk-l >= 0)
dp[kk][i][j] = max(dp[kk-l][i+1][j-1]+2, dp[kk][i][j]);
}
}
}
// The start point, the final answer is the dp(0,n-1,k);
return dp[k][0][n-1];
}
};

3473.长度至少为M的K个子数组之和

前缀和+二维DP

img

class Solution {
public:
int maxSum(vector<int>& nums, int k, int m) {
int n = nums.size();
if (n < k * m || k == 0) {
return 0;
}
vector<int> sum(n + 1, 0);
for (int i = 1; i <= n; ++i) {
sum[i] = sum[i - 1] + nums[i - 1];
}
vector<vector<int> > dp(k + 1, vector<int>(n + 1, -INT_MAX));
// dp[0][i] = 0,没有选子数组
for (int i = 0; i <= n; ++i) {
dp[0][i] = 0;
}
for (int i = 1; i <= k; ++i) {
int current_max = -INT_MAX;
for (int j = i * m; j <= n; ++j) {
current_max = max(current_max, dp[i - 1][j - m] - sum[j - m]);
if (current_max != -INT_MAX) {
dp[i][j] = max(dp[i][j - 1], current_max + sum[j]);
} else {
dp[i][j] = dp[i][j - 1];
}
}
}
return dp[k][n];
}
};
posted @   木木ちゃん  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示