1838. 最高频元素的频数
题目:
元素的 频数 是该元素在一个数组中出现的次数。
给你一个整数数组 nums 和一个整数 k 。在一步操作中,你可以选择 nums 的一个下标,并将该下标对应元素的值增加 1 。执行最多 k 次操作后,返回数组中最高频元素的 最大可能频数 。
思路:
我发现这种花里胡哨难以确定选择规律的题目要么就是动态规划,要么就是基于排序后的一些操作。在这里我们想到,频数对应的元素最终还是来自数组中原有的元素,所以他问的是最大可能频数,这里的可能也是可以的意思。因为1 1 1 1 这样一个数组,让你操作4次,其实最大的频数是4,元素是2.但是我们可以选择忽略这样的操作。
有了这样的想法就轻松了,我们先将数组排序,然后用双指针遍历数组
代码:
class Solution {
public:
int maxFrequency(vector<int>& nums, int k) {
sort(nums.begin(), nums.end());
int n = nums.size();
long long total = 0;
int l = 0, res = 1;
for (int r = 1; r < n; ++r) {
total += (long long)(nums[r] - nums[r - 1]) * (r - l); //r-l
while (total > k) {
total -= nums[r] - nums[l]; //记录的就是以r为目标最大频数的结果会是多少
++l; //看下图像就是这个把第一个指针的操作给他删去了 total<=k就行,差的步骤我给其他随便一个小喽喽消化
}
res = max(res, r - l + 1); //当r=0的时候本身res=1了
}
return res;
}
};
————————————————————————————————————————
1877. 数组中最大数对和的最小值
题目: 在长度为偶数得数组中可以进行分组形成数组对,找出并返回最小的 最大数组对
思路: 排序+贪心原则,最小得最大数组对一定是最小元素和最大元素组合,因为最大元素迟早要处理,如果这时候他其实不是最大的,那就缩进。
class Solution {
public:
int minPairSum(vector<int>& nums) {
int res=0;
sort(nums.begin(),nums.end());
int n=nums.size();
for(int i=0; i<n/2;i++){
res=max(res,nums[i]+nums[n-1-i]);
}
return res;
}
};
class Solution {
public:
int minPairSum(vector<int>& nums) {
int res=0;
sort(nums.begin(),nums.end());
int n=nums.size();
for(int i=0,j=n-1;i<j;i++,j--){
// if(nums[i]+nums[j]>res)
// res=nums[i]+nums[j];
res=max(res,nums[i]+nums[j]);
}
return res;
}
};
下面引入滑动窗口的题目
—————————————————————————————————————————————
76.最小覆盖子串–典型滑动窗口~~,详情看Labuladong
题目:
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
思路:
用滑动窗口,右边界找到1个可行解,记录长度,然后收缩左边界,不断记录最小长度,直到不能收缩。此时继续扩大右边界,寻找可行解。同上面操作~
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
// 记录最小覆盖子串的起始索引及长度
int start = 0, len = INT_MAX;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
if (need.count(c)) {
window[c]++;
if (window[c] == need[c])
valid++;
}
// 判断左侧窗口是否要收缩
while (valid == need.size()) {
// 在这里更新最小覆盖子串
if (right - left < len) {
start = left;
len = right - left;
}
// d 是将移出窗口的字符
char d = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
if (need.count(d)) {
if (window[d] == need[d]) //这一步不能少,因为后续继续搜索比如ABCA又遇到A了windows还是会++/不过value不会加了!巧妙之处是正好
valid--;
window[d]--;
}
}
}
// 返回最小覆盖子串
return len == INT_MAX ?
"" : s.substr(start, len);
}
};
567. 字符串的排列
题目: 给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。换句话说,第一个字符串的排列之一是第二个字符串的 子串 。
思路:
这种题目,是明显的滑动窗口算法,相当给你一个S和一个T,请问你S中是否存在一个子串,包含T中所有字符且不包含其他字符?
对于这道题的解法代码,基本上和最小覆盖子串一模一样,只需要改变两个地方:
1、本题移动left缩小窗口的时机是窗口大小大于t.size()时,因为排列嘛,显然长度应该是一样的。
2、当发现valid == need.size()时,就说明窗口中就是一个合法的排列,所以立即返回true。
至于如何处理窗口的扩大和缩小,和最小覆盖子串完全相同。
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char, int> need, window;
for (char c : s1) need[c]++;
int left = 0, right = 0;
int valid = 0;
while (right < s2.size()) {
char c = s2[right];
right++;
// 进行窗口内数据的一系列更新
if (need.count(c)) {
window[c]++;
if (window[c] == need[c])
valid++;
}
// 判断左侧窗口是否要收缩
while (right - left == s1.size()) {//labuladong是>=我这边是==
// 在这里判断是否找到了合法的子串
if (valid == need.size())
return true;
char d = s2[left];
left++;
// 进行窗口内数据的一系列更新
if (need.count(d)) {
if (window[d] == need[d])
valid--;
window[d]--;
}
}
}
// 未找到符合条件的子串
return false;
}
};
—————————————————————————————————————————————
438. 找到字符串中所有字母异位词
题目: 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。异位词 指字母相同,但排列不同的字符串。注意到排列相同也是异位词了
思路: 跟寻找字符串的排列一样,只是找到一个合法异位词(排列)之后将起始索引加入res即可。
代码:
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
unordered_map<char, int> need, window;
for (char c : p) need[c]++;
int left = 0, right = 0;
int valid = 0;
vector<int>res;
while (right < s.size()) {
char c = s[right];
right++;
// 进行窗口内数据的一系列更新
if (need.count(c)) {
window[c]++;
if (window[c] == need[c])
valid++;
}
// 判断左侧窗口是否要收缩
while (right - left == p.size()) {
// 在这里判断是否找到了合法的子串
if (valid == need.size())
res.push_back(left);
char d = s[left];
left++;
// 进行窗口内数据的一系列更新
if (need.count(d)) {
if (window[d] == need[d])
valid--;
window[d]--;
}
}
}
return res;
}
};
3.无重复字符的最长子串
题目: 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
思路: 这个题终于有了点新意,不是一套框架就出答案,不过反而更简单了,稍微改一改框架就行了:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> window;
int left = 0, right = 0;
int res = 0; // 记录结果
while (right < s.size()) {
char c = s[right];
right++;
// 进行窗口内数据的一系列更新
window[c]++;
// 判断左侧窗口是否要收缩
while (window[c] > 1) {
char d = s[left];
left++;
// 进行窗口内数据的一系列更新
window[d]--;
}
// 在这里更新答案
res = max(res, right - left);
}
return res;
}
};
综上:
滑动窗口的基础模板如下
string minWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
// 记录最小覆盖子串的起始索引及长度
int start = 0, len = INT_MAX;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
if (need.count(c)) {
window[c]++;
if (window[c] == need[c])
valid++;
}
// 判断左侧窗口是否要收缩
while (valid == need.size()) {
// 在这里更新最小覆盖子串
if (right - left < len) {
start = left;
len = right - left;
}
// d 是将移出窗口的字符
char d = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
if (need.count(d)) {
if (window[d] == need[d])
valid--;
window[d]--;
}
}
}
// 返回最小覆盖子串
return len == INT_MAX ?
"" : s.substr(start, len);
}