第 167 场双周赛 / 第 471 场周赛
第 167 场双周赛
Q1. 相等子字符串分数
给你一个由小写英文字母组成的字符串 s。
一个字符串的 得分 是其字符在字母表中的位置之和,其中 'a' = 1,'b' = 2,...,'z' = 26。
请你判断是否存在一个下标 i,使得该字符串可以被拆分成两个 非空子字符串 s[0..i] 和 s[(i + 1)..(n - 1)],且它们的得分 相等 。
如果存在这样的拆分,则返回 true,否则返回 false。
一个 子字符串 是字符串中 非空 的连续字符序列。
示例 1:
输入: s = "adcb"
输出: true
解释:
在下标 i = 1 处拆分:
左子字符串 = s[0..1] = "ad",得分 = 1 + 4 = 5
右子字符串 = s[2..3] = "cb",得分 = 3 + 2 = 5
两个子字符串的得分相等,因此输出为 true。示例 2:
输入: s = "bace"
输出: false
解释:
没有拆分能产生相等的得分,因此输出为 false。
提示:
2 <= s.length <= 100
s 由小写英文字母组成。
解题思路:枚举分割点, 判断左半边和右半边是否相等
class Solution {
public:bool scoreBalance(string s) {int n=s.length();for(int i=0;i<n;i++){int sum_1=0,sum_2=0;for(int j=0;j<i+1;j++){sum_1+=(s[j]-'a'+1);}for(int k=i+1;k<n;k++){sum_2+=(s[k]-'a'+1);}if(sum_1==sum_2){return true;}}return false;}
};
Q2. 最长斐波那契子数组
给你一个由 正 整数组成的数组 nums。
斐波那契 数组是一个连续序列,其中第三项及其后的每一项都等于这一项前面两项之和。返回 nums 中最长 斐波那契 子数组的长度。
注意: 长度为 1 或 2 的子数组总是 斐波那契 的。
子数组 是数组中 非空 的连续元素序列。
示例 1:
输入: nums = [1,1,1,1,2,3,5,1]
输出: 5
解释:
最长的斐波那契子数组是 nums[2..6] = [1, 1, 2, 3, 5]。
[1, 1, 2, 3, 5] 是斐波那契的,因为 1 + 1 = 2, 1 + 2 = 3, 且 2 + 3 = 5。
示例 2:
输入: nums = [5,2,7,9,16]
输出: 5
解释:
最长的斐波那契子数组是 nums[0..4] = [5, 2, 7, 9, 16]。
[5, 2, 7, 9, 16] 是斐波那契的,因为 5 + 2 = 7 ,2 + 7 = 9 且 7 + 9 = 16。
示例 3:
输入: nums = [1000000000,1000000000,1000000000]
输出: 2
解释:
最长的斐波那契子数组是 nums[1..2] = [1000000000, 1000000000]。
[1000000000, 1000000000] 是斐波那契的,因为它的长度为 2。
提示:
3 <= nums.length <= 10^5
1 <= nums[i] <= 10^9
解题思路:根据斐波那契数列的递推公式 an=an-1+an-2 (n>=2)
class Solution {
public:int longestSubarray(vector<int>& nums) {int n=nums.size(),cur_cnt=2,cnt=2;for(int i=2;i<n;i++){if(nums[i]-nums[i-1]==nums[i-2]){cur_cnt++; cnt=max(cur_cnt,cnt);}else{cur_cnt=2;}}return cnt;}
};
Q3. 设计考试分数记录器
Alice 经常参加考试,并希望跟踪她的分数以及计算特定时间段内的总分数。
请实现 ExamTracker 类:ExamTracker(): 初始化 ExamTracker 对象。
void record(int time, int score): Alice 在时间 time 参加了一次新考试,获得了分数 score。
long long totalScore(int startTime, int endTime): 返回一个整数,表示 Alice 在 startTime 和 endTime(两者都包含)之间参加的所有考试的 总 分数。如果在指定时间间隔内 Alice 没有参加任何考试,则返回 0。
保证函数调用是按时间顺序进行的。即,对 record() 的调用将按照 严格递增 的 time 进行。
Alice 永远不会查询需要未来信息的总分数。也就是说,如果最近一次 record() 调用中的 time = t,那么 totalScore() 总是满足 startTime <= endTime <= t 。
示例 1:
输入:
["ExamTracker", "record", "totalScore", "record", "totalScore", "totalScore", "totalScore", "totalScore"]
[[], [1, 98], [1, 1], [5, 99], [1, 3], [1, 5], [3, 4], [2, 5]]输出:
[null, null, 98, null, 98, 197, 0, 99]解释
ExamTracker examTracker = new ExamTracker();
examTracker.record(1, 98); // Alice 在时间 1 参加了一次新考试,获得了 98 分。
examTracker.totalScore(1, 1); // 在时间 1 和时间 1 之间,Alice 参加了 1 次考试,时间为 1,得分为 98。总分是 98。
examTracker.record(5, 99); // Alice 在时间 5 参加了一次新考试,获得了 99 分。
examTracker.totalScore(1, 3); // 在时间 1 和时间 3 之间,Alice 参加了 1 次考试,时间为 1,得分为 98。总分是 98。
examTracker.totalScore(1, 5); // 在时间 1 和时间 5 之间,Alice 参加了 2 次考试,时间分别为 1 和 5,得分分别为 98 和 99。总分是 98 + 99 = 197。
examTracker.totalScore(3, 4); // 在时间 3 和时间 4 之间,Alice 没有参加任何考试。因此,答案是 0。
examTracker.totalScore(2, 5); // 在时间 2 和时间 5 之间,Alice 参加了 1 次考试,时间为 5,得分为 99。总分是 99。
提示:
1 <= time <= 10^9
1 <= score <= 10^9
1 <= startTime <= endTime <= t,其中 t 是最近一次调用 record() 时的 time 值。
对 record() 的调用将以 严格递增 的 time 进行。
在 ExamTracker() 之后,第一个函数调用总是 record()。
对 record() 和 totalScore() 的总调用次数最多为 10^5 次。
解题思路:
【读完题就能想到前缀和+二分】,区别于普通数组的前缀和, 实现上要注意一点
using ll =long long;
class ExamTracker {
public:vector<ll> a,pre_sum={0};ExamTracker() {}void record(int time, int score) {a.push_back(time);pre_sum.push_back(pre_sum.back()+score);}long long totalScore(int startTime, int endTime) {int left=lower_bound(a.begin(),a.end(),startTime)-a.begin();int right=upper_bound(a.begin(),a.end(),endTime)-a.begin();return pre_sum[right]-pre_sum[left];}
};
/*** Your ExamTracker object will be instantiated and called as such:* ExamTracker* obj = new ExamTracker();* obj->record(time,score);* long long param_2 = obj->totalScore(startTime,endTime);*/
Q4. 最大划分因子
给你一个二维整数数组 points,其中 points[i] = [xi, yi] 表示笛卡尔平面上第 i 个点的坐标。
两个点 points[i] = [xi, yi] 和 points[j] = [xj, yj] 之间的 曼哈顿距离 是 |xi - xj| + |yi - yj|。将这 n 个点分成 恰好两个非空 的组。一个划分的 划分因子 是位于同一组内的所有无序点对之间 最小 的曼哈顿距离。
返回所有有效划分中 最大 可能的 划分因子 。
注意: 大小为 1 的组不存在任何组内点对。当 n = 2 时(两个组大小都为 1),没有组内点对,划分因子为 0。
示例 1:
输入: points = [[0,0],[0,2],[2,0],[2,2]]
输出: 4
解释:
我们将点分成两组: {[0, 0], [2, 2]} 和 {[0, 2], [2, 0]}。
在第一组中,唯一的点对之间的曼哈顿距离是 |0 - 2| + |0 - 2| = 4。
在第二组中,唯一的点对之间的曼哈顿距离也是 |0 - 2| + |2 - 0| = 4。
此划分的划分因子是 min(4, 4) = 4,这是最大值。
示例 2:
输入: points = [[0,0],[0,1],[10,0]]
输出: 11
解释:
我们将点分成两组: {[0, 1], [10, 0]} 和 {[0, 0]}。
在第一组中,唯一的点对之间的曼哈顿距离是 |0 - 10| + |1 - 0| = 11。
第二组是单元素组,因此不存在任何点对。
此划分的划分因子是 11,这是最大值。
提示:
2 <= points.length <= 500
points[i] = [xi, yi]
-10^8 <= xi, yi <= 10^8
解题思路:
最大最小划分因子,貌似要用二分,如果答案是5,那么我们就不能将曼哈顿距离<5 的两个点形成的集合划分到同一个划分中,剩下的就类似于二分图的交替染色了【1,-1】
class Solution {
public:int n;vector<vector<int>> points;bool solve(int low){vector<int> a(n);auto dfs=[&](this auto&&dfs,int x,int c)->bool{a[x]=c;auto& p=points[x];for(int k=0;k<n;k++){auto& q=points[k];if(k==x || abs(p[0]-q[0])+abs(p[1]-q[1])>=low){continue;}if(a[k]==c || a[k]==0 && !dfs(k,-c)) return false;}return true;};for(int i=0;i<n;i++){if(a[i]==0 && !dfs(i,1)){return false;}}return true;}int maxPartitionFactor(vector<vector<int>>& points) {if(points.size()==2) return 0;this->n=points.size();this->points=points;int max_dis=0;for(int i=0;i<points.size();i++){for(int j=i+1;j<points.size();j++){max_dis=max(max_dis,abs(points[i][0]-points[j][0])+abs(points[i][1]-points[j][1]));}}int left=0,right=max_dis+1;while(left+1<right){int mid=left+(right-left)/2;if(solve(mid)) left=mid;else right=mid;}return left;}
};
第 471 场周赛
Q1. 出现次数能被 K 整除的元素总和
给你一个整数数组 nums 和一个整数 k。
请返回一个整数,表示 nums 中所有其 出现次数 能被 k 整除的元素的总和;如果没有这样的元素,则返回 0 。
注意: 若某个元素在数组中的总出现次数能被 k 整除,则它在求和中会被计算 恰好 与其出现次数相同的次数。
元素 x 的 出现次数 指它在数组中出现的次数。
示例 1:
输入: nums = [1,2,2,3,3,3,3,4], k = 2
输出: 16
解释:
数字 1 出现 1 次(奇数次)。
数字 2 出现 2 次(偶数次)。
数字 3 出现 4 次(偶数次)。
数字 4 出现 1 次(奇数次)。
因此总和为 2 + 2 + 3 + 3 + 3 + 3 = 16。示例 2:
输入: nums = [1,2,3,4,5], k = 2
输出: 0
解释:
没有元素出现偶数次,因此总和为 0。
示例 3:
输入: nums = [4,4,4,1,2,3], k = 3
输出: 12
解释:
数字 1 出现 1 次。
数字 2 出现 1 次。
数字 3 出现 1 次。
数字 4 出现 3 次。
因此总和为 4 + 4 + 4 = 12。
提示:
1 <= nums.length <= 100
1 <= nums[i] <= 100
1 <= k <= 100
解题思路:
统计nums数组中各个元素出现的次数 能 被 k 整除的,答案加上【元素 * 出现次数】的总和
class Solution {
public:int sumDivisibleByK(vector<int>& nums, int k) {unordered_map<int,int> mp;int sum=0;for(auto& x: nums){mp[x]++;}for(auto& x: mp){if(x.second % k == 0) sum+=x.first*x.second;}return sum;}
};
Q2. 最长的平衡子串 I
给你一个由小写英文字母组成的字符串 s。
如果一个 子串 中所有 不同 字符出现的次数都 相同 ,则称该子串为 平衡 子串。请返回 s 的 最长平衡子串 的 长度 。
子串 是字符串中连续的、非空 的字符序列。
示例 1:
输入: s = "abbac"
输出: 4
解释:
最长的平衡子串是 "abba",因为不同字符 'a' 和 'b' 都恰好出现了 2 次。
示例 2:
输入: s = "zzabccy"
输出: 4
解释:
最长的平衡子串是 "zabc",因为不同字符 'z'、'a'、'b' 和 'c' 都恰好出现了 1 次。
示例 3:
输入: s = "aba"
输出: 2
解释:
最长的平衡子串之一是 "ab",因为不同字符 'a' 和 'b' 都恰好出现了 1 次。另一个最长的平衡子串是 "ba"。
提示:
1 <= s.length <= 1000
s 仅由小写英文字母组成。
解题思路:1 <= s.length <= 1000, 看数据范围 n^2 能过,直接统计字符出现的次数,看子串是否平衡
class Solution {
public:int longestBalanced(string s) {int n = s.length();int max_len = 0;for (int i = 0; i < n; i++) {vector<int> freq(26, 0);int min_freq = INT_MAX;int max_freq = 0;for (int j = i; j < n; j++) {int idx = s[j] - 'a';freq[idx]++;min_freq = INT_MAX;max_freq = 0;for (int k = 0; k < 26; k++) {if (freq[k] > 0) {min_freq = min( min_freq, freq[k]);max_freq = max(max_freq, freq[k]);}}if (min_freq == max_freq) {max_len = max(max_len, j - i + 1);}}}return max_len;}
};
Q3. 最长的平衡子串 II
给你一个只包含字符 'a'、'b' 和 'c' 的字符串 s。
如果一个 子串 中所有 不同 字符出现的次数都 相同,则称该子串为 平衡 子串。请返回 s 的 最长平衡子串 的 长度 。
子串 是字符串中连续的、非空 的字符序列。
示例 1:
输入: s = "abbac"
输出: 4
解释:
最长的平衡子串是 "abba",因为不同字符 'a' 和 'b' 都恰好出现了 2 次。
示例 2:
输入: s = "aabcc"
输出: 3
解释:
最长的平衡子串是 "abc",因为不同字符 'a'、'b' 和 'c' 都恰好出现了 1 次。
示例 3:
输入: s = "aba"
输出: 2
解释:
最长的平衡子串之一是 "ab",因为不同字符 'a' 和 'b' 都恰好出现了 1 次。另一个最长的平衡子串是 "ba"。
提示:
1 <= s.length <= 10^5
s 仅包含字符 'a'、'b' 和 'c'。
解题思路:
对比第二题,只有数据范围不同, 但是只有三种字符
分成如下三类问题,依次解答:
子串只包含一种字母。
子串只包含两种字母。用分组循环分组,每组只包含两种字母。
对于每一组,计算含有相同数量的两种字母的最长子串
特殊的子串包含三种字母
设 a 在这个组的个数前缀和数组为 Sa,b 在这个组的个数前缀和数组为 Sb,c 在这个组的个数前缀和数组为 Sc。
子串 [l,r) 中的字母 a,b,c 的出现次数相等,可以拆分为如下两个约束:
子串 [l,r) 中的字母 a 和 b 的出现次数相等。
子串 [l,r) 中的字母 b 和 c 的出现次数相等。
只要满足这两个约束,由等号的传递性可知,子串 [l,r) 中的字母 a 和 c 的出现次数相等,即三个字母的出现次数都相等。两个约束即如下两个等式
Sa[r] - Sb[r] = Sa[l] - Sb[l]
Sb[r] - Sc[r] = Sb[l] - Sc[l]
定义数组 a[i] = (Sa[i]-Sb[i],Sb[i]-Sc[i])
问题变成:
计算数组 a 中的一对相等元素的最远距离。
using ll=long long;
class Solution {
public:int longestBalanced(string s) {int n = s.size();int ans = 0;// 一种字母for (int i = 0; i < n;) {int start = i;for (i++; i < n && s[i] == s[i - 1]; i++);ans = max(ans, i - start);}// 两种字母two_char(s, 'a', 'b', ans);two_char(s, 'a', 'c', ans);two_char(s, 'b', 'c', ans);// 三种字母unordered_map<ll, int> pos = {{1LL * n << 32 | n, -1}};int cnt[3]{};for (int i = 0; i < n; i++) {cnt[s[i] - 'a']++;ll p = 1LL * (cnt[0] - cnt[1] + n) << 32 | (cnt[1] - cnt[2] + n);if (pos.contains(p)) {ans = max(ans, i - pos[p]);} else {pos[p] = i;}}return ans;}private:void two_char(const string& s, char x, char y, int& ans) {int n = s.size();for (int i = 0; i < n; i++) {unordered_map<int, int> pos = {{0, i - 1}}; int d = 0; for (; i < n && (s[i] == x || s[i] == y); i++) {d += s[i] == x ? 1 : -1;if (pos.contains(d)) {ans = max(ans, i - pos[d]);} else {pos[d] = i;}}}}
};
Q4. 完全平方数的祖先个数总和
给你一个整数 n,以及一棵以节点 0 为根、包含 n 个节点(编号从 0 到 n - 1)的无向树。该树由一个长度为 n - 1 的二维数组 edges 表示,其中 edges[i] = [ui, vi] 表示在节点 ui 与节点 vi 之间有一条无向边。
同时给你一个整数数组 nums,其中 nums[i] 是分配给节点 i 的正整数。定义值 ti 为:节点 i 的 祖先 节点中,满足乘积 nums[i] * nums[ancestor] 为 完全平方数 的祖先个数。
请返回所有节点 i(范围为 [1, n - 1])的 ti 之和。
说明:
在有根树中,节点 i 的祖先是指从节点 i 到根节点 0 的路径上、不包括 i 本身的所有节点。
完全平方数是可以表示为某个整数与其自身乘积的数,例如 1、4、9、16。
解题思路:
[8,2,4,4]
求这个数组中有多少个数对的,它的乘积是一个完全平方数
x*y=k^2
将k^2分解成两个质因数的次方, 由于k^2的指数是偶数,因此x,y 被分解成某个质因数的次方时, 它们的指数是, 同奇/同偶的,然后我们对指数%2,得到的值,就是相等的
x的平方剩余和 = y的平方剩余和
eg : 12=2^2*3 -> 平方剩余部分 = 3
本题就转换成祖先节点和后代节点,平方剩余和是否相等
1 2 3 4 5 6 7 8 9 10
平方剩余和 =1 【1,4,9...】
平方剩余和 =2 【2,8....】8=2^2*2
计算的是没有被标记过的数字, 从1,2,3....
i*j^2<=mx, j<=sqrt(mx/i)
using ll=long long;
const int N = 1e5+10;
vector<int> arr(N);
class Solution {
public:void solve(){for(int i=1;i<N;i++){if(arr[i]==0){for(int j=1;i*j*j<N;j++){arr[i*j*j]=i;}}}}long long sumOfAncestors(int n, vector<vector<int>>& edges, vector<int>& nums) {solve();vector<vector<int>> g(n);for(auto& x: edges){int a=x[0],b=x[1];g[a].push_back(b);g[b].push_back(a);}unordered_map<int,int> cnt;ll ans=0;auto dfs=[&](this auto&&dfs, int x,int fa)->void{int t=arr[nums[x]];ans+=cnt[t];cnt[t]++;for(int y:g[x]){if(y!=fa){dfs(y,x);}}cnt[t]--; // 恢复初始状态};dfs(0,-1);return ans;}
};
感谢大家的点赞和关注,你们的支持是我创作的动力!