比赛地址:第 310 场周赛
由于本人较懒,格式原因可能不太会注意,原题地址就不单独贴了。直接进上方超链看对应题号就可以。且出于能力原因我一眼第四题都不会看,是个每周稳两道冲三道的菜鸡(笑),即只更新前三题的思路和反思。对应序号括号后面的是对应力扣题号,可以直接搜到。
出现次数问题,一眼丁真鉴定为hash,由于有序hash特性,将出现过的偶数作为key放入map后会自动排序,value为对应偶数出现次数,且用迭代器从前到后遍历map的过程中也是先看key大小(因为map的主key有序性)再在循环内比较value,大value留key,一样value不变,此时不变的value就是更小的,因为迭代器对应较小。思路就是这样。
class Solution {
public:
int mostFrequentEven(vector<int>& nums) {
map<int,int>mmp;
int result = -1;
int hmt = 0;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] % 2 == 0){
if (mmp.find(nums[i]) == mmp.end()){
mmp.emplace(nums[i],1);
} else{
mmp[nums[i]]++;
}
}
}
for (auto it : mmp) {
if (hmt < it.second){
result = it.first;
hmt = it.second;
}
}
return result;
}
};
class Solution {
public:
int mostFrequentEven(vector<int>& nums) {
map<int, int> mp;
for (auto &x : nums) { /* 统计元素出现次数 */
mp[x]++;
}
int ans = -1;
int mx = 0;
for (auto &[val, cnt] : mp) {
if (mp[val] > 0 && val % 2 == 0 && cnt > mx) { /* 统计次数最多的偶数 */
ans = val;
mx = cnt;
}
}
return ans;
}
};
//作者:liu-xiang-3
class Solution {
public:
int mostFrequentEven(vector<int>& nums) {
int s=nums.size();
int count[100000]={0};
int flag=0;
int num_max=0;
int maxn=0;
for(int i=0;i<s;i++){
if(nums[i]%2==0) {
flag = 1;
count[nums[i]]++;
if (count[nums[i]] > num_max) {
num_max = count[nums[i]];
maxn = nums[i];
}
else if(count[nums[i]] == num_max){
maxn=min(maxn,nums[i]);
}
}
}
if(flag){
return maxn;
}
else{
return -1;
}
}
};
//作者:Arc_zml
此次第一题一共提供了三版代码,效率排名为312,方法二在初始化map的时候将所有数字全放到map,这就造成了空间利用率不够高后续循环调用的冗余度过大,但代码确实是更短,可效率确实我那样更高些。方法三是双百,原作者在代码中自己模拟了map,且在每一次循环中都维护着num_max,这就导致了单次遍历就可以完成任务,且并没有使用任何顺序容器。在我建立容器时为了保持有序性编译器做了自己的排序,肯定是消耗内存的,且初始化遍历一次,对map对象进行内部遍历又一次,确实效率没一次过的效率高。“模拟map + 维护单值”的做法确实在这种题很吃香。
滑动窗口+hash,单窗口内不能存在重复字母,存在则更新窗口大小为0、划分数量+1、clear map。一次遍历得到答案,但并没有双百。至于正向反向不影响答案,因为不存在重复字母的序列是一样的不分正反。
class Solution {
public:
int partitionString(string s) {
map<char,int> mmp;
int howmany = 1;
for (int i = 0; i < s.size(); ++i) {
if (mmp.find(s[i]) == mmp.end()){
mmp.emplace(s[i],0);
} else{
mmp.clear();
mmp.emplace(s[i],0);
howmany++;
}
}
return howmany;
}
};
class Solution {
public:
int partitionString(string s) {
int i = 0;
int ans = 0;
while (i < s.size()) {
int j = i;
int mask = 0;
/* 判断是否有重复字母出现 */
while (j < s.size() && (mask & (1 << (s[j] - 'a'))) == 0) {
mask |= 1 << (s[j++] - 'a');
}
ans++;
i = j;
}
return ans;
}
};
//作者:liu-xiang-3
滑动窗口+hash固然很好,但从优化来说用unordered_map更好,因为不需要排序特性。位运算更加快速,从bit角度模拟了map,省去编译器的优化过程。这道题的位运算有两个注意点:
(mask & (1 << (s[j] - 'a'))) == 0
,这种情况便是不会出现进位的情况(对应bit位不重复)简单例子可为 “abcbcb”,对应bit为 111 110 110 。
发现自己对位运算和贪心很不熟练,加以count,加把劲骑士,下次一定学。
说来惭愧,这道题本应该用线段树来做,之前做题没有完全搞懂,只能用自己的土方法了。
intervals[j][0] > intervals[i-1][1]
即前一个的后面小于后一个的前面这种情况,就停止当前遍历并递归到下一次循环中。注意这里每一个的遍历次数最多为 size - i 因此时间复杂度还不是很爆炸。(判断当前组还有哪些合法成员并修改对应bool数组为true)class Solution {
public:
//3_将区间分为最少组数
int erfen_3(vector<vector<int>>& intervals, int findit, int i, int right){
//1,3,5 找4
int mid = (i + right)/2;
if (i >= right){
return mid;
}
if (intervals[mid][0] > findit){
right = mid -1;
} else{
i = mid + 1;
}
return erfen_3(intervals,findit,i, right);
}
void digui_3(vector<vector<int>>& intervals, vector<bool>& isUsed , int i){
int aim = erfen_3(intervals,intervals[i-1][1],i,intervals.size()-1);
if (intervals[aim][0] < intervals[i-1][1]){
aim++;
}
for (int j = aim; j < intervals.size(); ++j) {
if (intervals[j][0] > intervals[i-1][1] && isUsed[j] == false){
isUsed[j] = true;
digui_3(intervals,isUsed,j+1);
break;
}
}
}
int minGroups(vector<vector<int>>& intervals) {
int result = 0;
sort(intervals.begin(),intervals.end());
vector<bool>isUsed(intervals.size(),false);
for (int i = 0; i < intervals.size(); ++i) {
if (isUsed[i] == false){
result++;
isUsed[i] = true;
//递归
digui_3(intervals,isUsed,i+1);
}
}
return result;
}
};
class Solution {
public:
const int N = 1e6 + 2;
int minGroups(vector<vector<int>>& intervals) {
int n = intervals.size();
vector<int> diff(N);
for(auto it : intervals){
int l = it[0];
int r = it[1];
diff[l]++;
diff[r+1]--;
}
int now = diff[0];
int ans = now;
for (int i = 1; i < N; ++i) {
now += diff[i];
ans = max(now,ans);
}
return ans;
}
};
10^6
[1,2]\[3,4]\[2,5]
diff->{1,1,1,0,0,0} - {0,0,1,0,1,1} = {1,1,0,0,-1,-1}
可见 [1,2]和[3,4]可以合并,因此会出现前面累加状态 -1 的情况。class Solution {
public:
int minGroups(vector<vector<int>>& intervals) {
sort(intervals.begin(),intervals.end());
priority_queue<int, vector<int>, greater<int>>pq;
for(auto &vec : intervals){
if(pq.size()!=0 && pq.top() < vec[0]){
pq.pop();
}
pq.push(vec[1]);
}
return pq.size();
}
};
[[5,10],[6,8],[1,5],[2,3],[1,10]]
明显前三个入堆后,堆内数据变成了 {5,8,10}
,但在 [2,3] 想入堆时,原本可以合并的 [5,10] 现在只剩下了一个 10 ,无法保证可以合并。因此为了符合小顶堆堆顶最小的这种特性,需要对原数值对数组进行排序,保证不需要将当前数值对前插合并这种情况。排序后一是保证了左边界一定非递减,右边界在左边界相同时非递减。这样在遍历到一个数值对判断在堆中是否合并时就可以直接用左边界和其堆顶比,而不会出现堆内存在某值对应左边界比当前遍历到的数值对右边界还大的情况(前插合并)。待更新