题单来自灵神,这里针对我的刷题过程做一个记录,本系列的目的是记录心得+做题记录的统计
1456. 定长子串中元音的最大数目 1263
题意:找到字符串 s 中长为 k 的串中包含元音最多的数量。 可能踩坑:进出区间判断下标范围。
踩坑:数组存在负数,初始化INT_MIN。
INT_MIN
`INT_MIN` 是 C/C++ 中 `int` 类型的最小值,定义在 `值: 通常是 -2147483648(即 -2^31,32位系统)
用法:
#include <climits>
int maxVal = INT_MIN; // 初始化为最小值,用于求最大值
for (int x : nums) {
maxVal = max(maxVal, x);
}
常见场景:
- 求最大值时初始化变量
- 判断溢出边界
注意事项:
- INT_MIN 取反会溢出:-INT_MIN 是未定义行为
- 如果用 LeetCode / 力扣,<climits> 通常已经可以直接用,不需要额外 include
相关常量:
┌───────────┬──────────────────────┐
│ 常量 │ 值 │
├───────────┼──────────────────────┤
│ INT_MIN │ -2147483648 │
├───────────┼──────────────────────┤
│ INT_MAX │ 2147483647 │
├───────────┼──────────────────────┤
│ LLONG_MIN │ -9223372036854775808 │
├───────────┼──────────────────────┤
│ LLONG_MAX │ 9223372036854775807 │
└───────────┴──────────────────────┘
点击展开代码
class Solution {
public:
bool isVol(char c) {
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}
int maxVowels(string s, int k) {
int n = s.size();
int res = 0, cnt = 0;
for (int i = 0; i < n; i++) {
if (isVol(s[i])) {
cnt++;
}
if (i - k + 1 >= 0) {
res = max(res, cnt);
}
if (i - k + 1 >= 0 && isVol(s[i - k + 1])) {
cnt--;
}
}
return res;
}
};
643. 子数组最大平均数 I
题意:给一个数组和一个整数k,求平均数最大的长为k的连续子数组的最大平均数。
点击展开代码
class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
double sum = 0, res = INT_MIN;
int n = nums.size();
for (int i = 0; i < n; i++) {
sum += nums[i];
if (i - k + 1 >= 0) {
res = max(res, sum);
}
if (i - k + 1 >= 0) {
sum -= nums[i - k + 1];
}
}
return res / k;
}
};
1343. 大小为 K 且平均值大于等于阈值的子数组数目 1317
题意:给一个整数数组arr,两个整数k和threshold。返回长度为k且平均值大于等于threshhold的子数组数目。
class Solution {
public:
int numOfSubarrays(vector<int>& arr, int k, int threshold) {
int n = arr.size();
int res = 0;
long long sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
if (i - k + 1 >= 0) {
if (sum >= threshold * k)
res++;
}
if (i - k + 1 >= 0)
sum -= arr[i - k + 1];
}
return res;
}
};
2090. 半径为 k 的子数组平均值 1358
题意:给一个数组nums,n个整数。一个整数k。定义半径为k的子数组平均值:以i 为中心且半径为k的子数组所有元素的平均值。如果不足k个元素,均值为-1. 构建一个长为n的数组avg,其中avg[i]表示下标 i 为中心的子数组的半径为k的子数组均值。均值采用整除截断。
注意整数溢出问题
code
class Solution {
public:
vector<int> getAverages(vector<int>& nums, int k) {
int n = nums.size();
vector<int> avg(n, -1);
long long sum = 0;
int l;
// assume center i . current r . i + k = r , i - k = l => i = r - k ,
// l = r - k - k
for (int r = 0; r < n; r++) {
sum += nums[r];
l = r - k - k;
if (l >= 0)
avg[r - k] = sum / (r - l + 1);
if (l >= 0)
sum -= nums[l];
}
return avg;
}
};
2379. 得到 K 个黑块的最少涂色次数 1360
题意:给一个长为n的字符串blocks,blocks[i]要么是W要么是B,表示颜色。给一个k,表示想要连续黑色的数目,每次操作可以把一个白色涂成黑色,求最小操作次数。
code
class Solution {
public:
int minimumRecolors(string blocks, int k) {
int n = blocks.size();
int res = n, cb = 0;
// same as k area => max B or min W
for (int i = 0; i < n; i++) {
if (blocks[i] == 'B')
cb++;
if (i - k + 1 >= 0)
res = min(res, k - cb);
if (i - k + 1 >= 0 && blocks[i - k + 1] == 'B')
cb--;
}
return res;
}
};
2841. 几乎唯一子数组的最大和 1546
题意:给一个整数数组nums,两个整数m和k。返回nums中长度为k的几乎唯一子数组的最大和,不存在返回0.
几乎唯一子数组:如果nums一个子数组至少有m个互不相同的元素。
code
class Solution {
public:
long long maxSum(vector<int>& nums, int m, int k) {
int n = nums.size();
unordered_map<int, int> mp;
long long res = 0, sum = 0;
int diffcnt = 0;
for (int i = 0; i < n; i++) {
sum += nums[i];
if (!mp[nums[i]]) { // push
diffcnt++;
}
mp[nums[i]]++;
if (diffcnt >= m) {
res = max(res, sum);
}
if (i - k + 1 >= 0) {
mp[nums[i - k + 1]]--;
sum -= nums[i - k + 1];
if (mp[nums[i - k + 1]] == 0)
diffcnt--;
}
}
return res;
}
};
2461. 长度为 K 子数组中的最大和 1553
题意: 给一个整数数组,一个k,找出:1. 子数组长度为k 2. 所有元素各不相同。 返回满足条件的最大子数组和。不存在返回0.
code
class Solution {
public:
long long maximumSubarraySum(vector<int>& nums, int k) {
int n = nums.size();
long long res = 0, sum = 0;
int uniq = 0;
unordered_map<int, int> mp;
for (int i = 0; i < n; i++) {
sum += nums[i];
mp[nums[i]]++;
if (mp[nums[i]] == 1)
uniq++;
if (i - k + 1 >= 0) {
if (uniq == k)
res = max(res, sum);
mp[nums[i - k + 1]]--;
if (mp[nums[i - k + 1]] == 0)
uniq--;
sum -= nums[i - k + 1];
}
}
return res;
}
};
1423. 可获得的最大点数 1574
题意:一个数组carPoints,每次可以从开头/末尾拿1张牌,最终必须正好拿k张。
返回可以获得的最大点数.
反向考虑 n-k 的min 值。这里如果按照之前模版要特判 n==k, 因此采用新模版做法。
code
class Solution {
public:
int maxScore(vector<int>& cardPoints, int k) {
int n = cardPoints.size();
int m = n - k;
int s = reduce(cardPoints.begin(), cardPoints.begin() + m);
int min_s = s;
for (int i = m; i < n; i++) {
s += cardPoints[i] - cardPoints[i - m];
min_s = min(s, min_s);
}
return reduce(cardPoints.begin(), cardPoints.end()) - min_s;
}
};
1052. 爱生气的书店老板 1418
题意:书店开了n分钟,每分钟有一些顾客进入。给一个长度为n的数组 customers, 其中customer[i]表示第i分钟开始时进入人数,所有这些顾客在第i分钟解释后离开。 在某些分钟内,老板会生气,如果在第i分钟生气,那么 grumpy[i]=1 否则 grumpy[i]=0. 生气时顾客会不满意,老板可以控制连续 minites分钟不生气,但只能用一次,求最多多少顾客能满意。
分析:等价于求 grumpy 的满足条件的子数组,子数组中 1 的个数小于 3, 要求子数组对应的customer和最大。
开始想复杂了, 写成了能控制连续 minutes 不生气。
code
class Solution {
public:
int maxSatisfied(vector<int>& customers, vector<int>& grumpy, int minutes) {
int n = customers.size();
int res = 0, sum = 0;
for (int i = 0; i < n; i++) {
sum += (grumpy[i] == 1 ? 0 : customers[i]);
}
int savedCustomers = 0;
for (int i = 0; i < n; i++) {
if (grumpy[i] == 1) {
savedCustomers += customers[i];
}
res = max(res, sum + savedCustomers);
if (i - minutes + 1 >= 0 && grumpy[i - minutes + 1])
savedCustomers -= customers[i - minutes + 1];
}
return res;
}
};
3679. 使库存平衡的最少丢弃次数 1639
题意:给两个整数 w 和 m, 以及一个整数数组 arrivals, 其中arrivals[i] 表示第 i 天到达的物品类型。遵循以下规则:
- 对于每个到达的物品可以保留或丢弃, 物品只能在到达当天被丢弃。
- 对于每一天i,考虑天数范围为 [max(1, i-w+1), i] (也就是直到第i天为止最近w天)
- 对于任何这样的窗口,保留的物品中每种最多只能出现 m 次。
- 如果在第i 天保留该到达物品会导致其类型在该窗口中出现次数超过m次,那么必须丢弃。
- 返回满足每个 w 天窗口中每种类型最多出现 m 次,最少要丢弃物品的数量。
题意好绕。维护一个长度为w的窗口,其中重复的不超过m次. 这题怎么做转换呢?读题漏了个点,一个物品只有当天知道是否要丢。那么当一个物品进入后,如果超过了m,那么要么直接丢弃这个,要么时间回溯到上一个出现的日期,把上一个丢弃。 这里可以贪心认为晚丢会更好,因为这样会让窗口中的个数比早丢更少,后面丢弃的物品数更少。 (窗口移动过程中如果保留了更晚到的,那么这个x在窗口中停留的时间更久了)
code
class Solution {
public:
int minArrivalsToDiscard(vector<int>& arrivals, int w, int m) {
int n = arrivals.size();
int res = 0;
unordered_map<int, int> cnt;
for (int i = 0; i < n; i++) {
if (cnt[arrivals[i]] == m) {
arrivals[i] = -1;
res++;
} else {
cnt[arrivals[i]]++;
}
if (i - w + 1 >= 0)
cnt[arrivals[i - w + 1]]--;
}
return res;
}
};
3439. 重新安排会议得到最多空余时间 I 1729
题意:一个整数 evenTime 表示一个活动时长,开始于t = 0 , 结束于 t = eventTime.
给两个长度为 n 的整数数组 startTime 和 endTime, 表示n 个时间没有重叠的回忆, 其中第i个会议时间为[start[i], end[i]]
你可以重新安排最多 k 个会议, 规则是将会议时间平移,且会保留原来的时长,目标是移动后会议最大化相邻会议之间的最长连续空余时间。
移动后会议之间相对顺序保持不变。互不重叠。 返回重新安排可以得到的最大空余时间。
注意最多安排k个会议。那么就是说有 n-k 个 的时间窗口是不动的。如果没有k的上限,那么显然就是总时长-会议时长。
问题可以转化成 k 次操作,每次操作可以合并两个空闲时间段, k 次可以合并连续k+1 个空闲时间段。求最大长度。
注意是选k+1个而不是k个。
code
class Solution {
public:
int maxFreeTime(int eventTime, int k, vector<int>& startTime,
vector<int>& endTime) {
int n = startTime.size();
int res = 0;
// first startTime[0]
vector<int> free_slot(n + 1);
free_slot[0] = startTime[0];
for (int i = 1; i < n; i++)
free_slot[i] = startTime[i] - endTime[i - 1];
free_slot[n] = eventTime - endTime[n - 1];
for(int i =0 ;i<=n;i++) cout<<free_slot[i]<<" "; cout<<endl;
int sum = 0;
for (int i = 0; i <= n; i++) {
sum += free_slot[i];
res = max(sum, res);
if (i - k >= 0) {
sum -= free_slot[i - k];
}
}
return res;
}
};
3694. 删除子字符串后不同的终点 1739
题意:一个由自负 U D L R 组成的字符串,在无限的二维笛卡尔网格上移动
还有一个正整数 K , 必须选择移除恰好一个长为k 的连续子字符串s ,然后从0,0 还是执行剩余移动。
返回可到达的不同坐标的数量
分析:同样的模版题,区间内的操作计算一次位置集合即可。
code
class Solution {
public:
int distinctPoints(string s, int k) {
int res = 0;
map<pair<int, int>, int> mp;
int x = 0, y = 0;
for (int i = 0; i < s.size(); i++) {
if (s[i] == 'U')
y++;
else if (s[i] == 'D')
y--;
else if (s[i] == 'L')
x--;
else
x++;
if (i - k + 1 >= 0 && !mp[{x, y}]) {
res++;
mp[{x, y}]++;
}
if (i - k + 1 >= 0) {
if (s[i - k + 1] == 'U')
y--;
else if (s[i - k + 1] == 'D')
y++;
else if (s[i - k + 1] == 'L')
x++;
else if (s[i - k + 1] == 'R')
x--;
}
}
return max(res, 1);
}
};
2134. 最少交换次数来组合所有的 1 II 1748
题意:交换定位选中一个数组中两个互不相同的位置并交换两者的值。 环形数组是一个数组,认为第一个元素和最后一个元素相邻。给一个二进制环形数组nums,返回在任意位置将数组中所有1聚集在一起的最少交换次数。
分析: 假设 0 有k个,最终肯定是有一个长度为k 窗口,里面都是0 , 那么就是找到 长为 k 的窗口中 0 最多的个数。 k减去 num_0就得到要移走的1 的个数。
踩坑: 对 0 的情况进行了特判。
code
class Solution {
public:
int minSwaps(vector<int>& nums) {
int n = nums.size();
int res = 0, zero = 0;
for (int i = 0; i < n; i++) {
if (nums[i] == 0)
zero++;
}
if (zero == 0)
return 0;
int cnt_1 = 0, min_1 = INT_MAX;
for (int i = 0; i < n + zero; i++) {
if (nums[i % n] == 1)
cnt_1++;
int l = (i - zero + 1) % n;
if (l >= 0) {
min_1 = min(min_1, cnt_1);
if (nums[l] == 1)
cnt_1--;
}
}
return min_1;
}
};
1652. 拆炸弹 做到 O(n)
题意:有一个 长为 n 的循环数组 code 和一个密钥 k。
需要替换掉每一个数字,所有数字会同时替换。如果k > 0,将第 i 和数字和接下来 k 个数字之和替换。
如果k < 0 , 将第i个数字用之间的 k 个数字之和替换。 如果k =0, 将第i 个数字用0替换。
由于code 是循环的,返回解密后结果。
O(n) 解法。
code
class Solution {
public:
vector<int> decrypt(vector<int>& code, int k) {
int n = code.size();
vector<int> res(n, 0);
if (k == 0)
return res;
int sum = 0;
if (k > 0) {
for (int i = 0; i < n + k; i++) {
sum += code[i % n];
if (i - k >= 0) {
sum -= code[i - k];
res[i - k] = sum;
}
}
} else {
k *= -1;
for (int i = 0; i < n + k; i++) {
if (i - k >= 0) {
res[i % n] = sum;
sum -= code[i - k];
}
sum += code[i % n];
}
}
return res;
}
};
1297. 子串的最大出现次数 1748
题意:给一个字符串s,返回满足以下条件且出现次数 最大的 任意子串的 出现次数。
- 子串中不同字母数目必须小于等于 maxLetters
- 子串长度必须大于等于 minSize 且小于等于 maxSize
维护一个窗口 l,R 其中 窗口大小在 min-max之间。.. 脑筋急转弯,长度为minSize 的重复的肯定比更长的重复的更多。
因此只需要考虑长度为 minSize的子串即可。
code
class Solution {
public:
int maxFreq(string s, int maxLetters, int minSize, int maxSize) {
int res = 0, n = s.size();
unordered_map<char, int> cnt;
int l = 0, diffcnt = 0;
map<string, int> scnt;
for (int i = 0; i < n; i++) {
cnt[s[i]]++;
if (cnt[s[i]] == 1)
diffcnt++;
if (i - minSize + 1 >= 0) {
if (diffcnt <= maxLetters) {
scnt[s.substr(i - minSize + 1, minSize)]++;
res = max(res, scnt[s.substr(i - minSize + 1, minSize)]);
}
cnt[s[i - minSize + 1]]--;
if (cnt[s[i - minSize + 1]] == 0)
diffcnt--;
}
}
return res;
}
};
3652. 按策略买卖股票的最佳时机
题意:给两个整数数组 prices和strategy ,其中prices[i] 表示第i天股票价格,strategy[i]表示第i天的策略,其中-1表示买入,0表示持有,1表示卖出。 同时给一个偶数k, 可以对strategy有最多一次修改,包括:
选择 strategy恰好k 个连续元素。
将前 k/2 个元素 设为0。
将后 k/2 个元素设为1
利润定义:所有天数 strategy[i]*prices[i] (ps 这叫啥利润。。)
返回可能的最大利润
code
class Solution {
public:
long long maxProfit(vector<int>& prices, vector<int>& strategy, int k) {
int n = prices.size();
long long res = 0, sum = 0, maxx = 0;
for (int i = 0; i < n; i++)
res += prices[i] * strategy[i];
for (int i = 0; i < k / 2; i++) {
sum += (-strategy[i]) * prices[i];
}
for (int i = k / 2; i < k; i++) {
sum += (1 - strategy[i]) * prices[i];
}
maxx = max(maxx, sum);
for (int i = k; i < n; i++) {
sum += (1 - strategy[i]) * prices[i];
sum -= (-strategy[i - k]) * prices[i - k];
int middle = i - k / 2;
sum -= prices[middle];
maxx = max(maxx, sum);
}
return res + maxx;
}
};
567. 字符串的排列
题意:给两个字符串s1 和s2 ,判断s2 是否包含s1 的序列,其中序列表示字符串的任意重排。 本身该题可以不用滑动窗口,但是用滑动窗口复杂度从O(26n)优化至 O(n),其中26为字符取值范围。
code
class Solution {
public:
bool checkInclusion(string s1, string s2) {
vector<int> cnt(26,0);
int diff = 0;
int n1 = s1.size(), n2 = s2.size();
if (n1 > n2)
return false;
for (int i = 0; i < n1; i++) {
if (cnt[s1[i] - 'a'] == 0)
diff++;
cnt[s1[i] - 'a']++;
}
for (int i = 0; i < n2; i++) {
cnt[s2[i] - 'a']--;
if (cnt[s2[i] - 'a'] == 0)
diff--;
if (i < s1.size() - 1) {
continue;
}
if (diff == 0)
return true;
if (cnt[s2[i - n1 + 1] - 'a'] == 0) {
diff++;
}
cnt[s2[i - n1 + 1] - 'a']++;
}
return false;
}
};
20260530 今天写了6题
438. 找到字符串中所有字母异位词
code
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> res;
int l1 = s.size(), l2 = p.size();
if (l1 < l2)
return res;
vector<int> cnt(26, 0);
int diff = 0;
for (int i = 0; i < l2; i++) {
if (cnt[p[i] - 'a'] == 0)
diff++;
cnt[p[i] - 'a']++;
}
for (int i = 0; i < l1; i++) {
cnt[s[i] - 'a']--;
if (cnt[s[i] - 'a'] == 0)
diff--;
if (i < l2 - 1)
continue;
if (diff == 0){
res.push_back(i - l2 + 1);
}
if (cnt[s[i - l2 + 1] - 'a'] == 0) {
diff++;
}
cnt[s[i - l2 + 1] - 'a']++;
}
return res;
}
};
30. 串联所有单词的子串
题意:给一个字符串s 和一个字符串数组 words, words中所有字符串长度相同。
定义s 的串联子串 是指一个包含word始终所有字符串以任意顺序排列连接起来的子串。
返回所有串联子串在s中的开始索引。可以以任意顺序返回答案。
分析:注意到words 的长度在 5000 , s.length在 1e4以内。 直接想到的暴力解,观察到words中所有字符串长度相同 。如果存在匹配的子串,那么 区间长度 k 为sum words ,首先要满足字符个数是一致的, 但是问题在于怎么保证每三个的顺序是一样的。 可以想到用map 记录hash值?由于长度是固定不变的,但是窗口在移动时,所有hash值都会变。这样算法好像是O(n2)的。
其实 s 也可以按照固定长度进行单词划分,只是需要确定开始的下标i, 其中 i in [0,n-1] .剩余的就是套路,只是把单个字符变成了 单词。
先看了下题解灵神的实现,只能说题解写的很清晰 妙啊。 下面自己按照思路进行实现。
code
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> res;
int wordLen = words[0].size();
int totalLen = words.size() * wordLen;
unordered_map<string, int> targetCnt;
for (int i = 0; i < words.size(); i++) {
targetCnt[words[i]]++;
}
// 枚举开始的左端点
for (int start = 0; start < wordLen; start++) {
unordered_map<string, int> cnt;
int diff_word = 0;
// 枚举单词右端点,滑窗
for (int right = start + wordLen; right <= s.size();
right += wordLen) {
// enter
string word = s.substr(right - wordLen, wordLen);
if (targetCnt[word] == cnt[word])
diff_word++;
cnt[word]++;
// skip
if (right - totalLen < 0)
continue;
// update
if (diff_word == 0)
res.push_back(right - totalLen);
// out
word = s.substr(right - totalLen, wordLen);
cnt[word]--;
if (cnt[word] == targetCnt[word])
diff_word--;
}
}
return res;
}
};
1888. 使二进制字符串字符交替的最少反转次数 2006
题意: 给一个二进制字符串s, 可以任意顺序执行以下两种操作任意次。
- 删除字符串s的第一个字符并将它添加到字符串结尾
- 选择s中任意一个字符并将该字符反转,也就是如果值为0变成1,1变0.
返回使s 变成交替字符串的前提下,类型2的最少操作次数。
分析:操作1 等价循环数组。
比如 1110 1110 也就是从 0,2n-1 的区间中找一个长度为n 的区间,要求区间内必须是交替的,但是要枚举一下开始时是0/1。 维护区间长度为n的最小交换次数即可。
开始我的思路是枚举开头是0 还是1 , 然后每种情况下维护窗口n,入时和current 判断是否要改,出时根据记录值统计是否修改过, 有点麻烦。
题解思路更巧妙一些,要么改成0101...,int(t[i])=imod2。 要么 1010... 也就是int(t[i])=imod2
把满足的视为 . 不满足的视为 x那么就是 X.XX.XX.XX
这个证明在周赛时候推出来还是挺绕的。。
code
class Solution {
public:
int minFlips(string s) {
int n = s.size();
int res = n;
int cnt = 0;
for (int i = 0; i < 2 * n - 1; i++) {
if (s[i % n] % 2 != i % 2)
cnt++;
if (i - n + 1 < 0)
continue;
res = min({res, cnt, n - cnt});
if (s[i - n + 1] % 2 != (i - n + 1) % 2)
cnt--;
}
return res;
}
};
2156 先搁置, 等取模部分知识补完后补这题。
2156. 查找给定哈希值的子串 2063
题意:给定整数p和m,一个长度为k 且下标从0开始的字符串s的哈希值
hash(s, p, m) = (val(s[0]) * p0 + val(s[1]) * p1 + ... + val(s[k-1]) * pk-1) mod m
其中 val(s[i]) 表示 s[i] 在字母表中的下标,从 val('a') = 1 到 val('z') = 26 。
给你一个字符串 s 和整数 power,modulo,k 和 hashValue 。请你返回 s 中 第一个 长度为 k 的 子串 sub ,满足 hash(sub, power, modulo) == hashValue 。
测试数据保证一定 存在 至少一个这样的子串。
子串 定义为一个字符串中连续非空字符组成的序列。
分析:p^k % m 应该是个循环的? 哦哦应该是不用,维护一个左端点%m 的 p 即可,每次都少一个 p%m
2953 也暂时搁置,貌似是分组循环部分的题
浙公网安备 33010602011771号