文章目录
1.LeetCode:1838. 最高频元素的频数
元素的 频数 是该元素在一个数组中出现的次数。
给你一个整数数组 nums 和一个整数 k 。在一步操作中,你可以选择 nums 的一个下标,并将该下标对应元素的值增加 1 。
执行最多 k 次操作后,返回数组中最高频元素的 最大可能频数 。
示例 1:
输入:nums = [1,2,4], k = 5
输出:3
示例 2:
输入:nums = [1,4,8,13], k = 5
输出:2
示例 3:
输入:nums = [3,9,6], k = 2
输出:1
提示:
1 <= nums.length <= 1e5
1 <= nums[i] <= 1e5
1 <= k <= 1e5
这里先对原数组进行排序,并求前缀和。然后我们枚举每一个元素,将他作为要被改变的数字,由于是按照升序排序,题目要求我们只能吧数组元素+1,所以i位置之后的元素在这次操作中无需考虑。接着开始滑动窗口,左边界为l(初始化为0),右边界为i具体操作下面再解释。
在进行操作前先对前缀和数组的下标进行解释,这里十分关键。首先,由于我们在求前缀和的时候,吧所有元素的下标都向右移动了一位,而为了求取前缀和方便,以下除了窗口的左边界l对应着原数组的下标,其他下标均对应着他原始下标+1.这样偏移坐标的好处是当我们枚举原数组中i-1,现在为i下标的元素的时候,要求j(原始下标)到他的范围和,只需要prev[i]-prev[j]即可,求长度也只需要i-j.(i-1-j+1)
具体操作为,枚举数组所有元素,为了求取前缀和方便我们这里的下标都右移一位,称其为 j 。那么他可以表示为nums[j-1],也可以是prev[j]-prev[j-1],下面为了方便就用nums[j-1]。现在我们吧他作为了指标,那么能被改变的数字就只能是窗口内他之前的数字,也就是[l,j-2]这个范围内的数字。那么改变之后这些数字的和就是 j - l ([ j-2-l+1]+1),减去这个范围内之前的和:prev[j]-prev[l],(之前提到过的求某部分范围和的方法)就是改变前后和的差值,也就是这次操作要改变的次数,如果大于了k就一直右移左边界,直到合法位置。最后维护ans
class Solution {
typedef long long ll;
public:
int maxFrequency(vector<int>& nums, int k) {
sort(nums.begin(),nums.end());
int n=nums.size();
ll prev[100005]={0};
for(int i=1;i<=n;++i){
prev[i]=prev[i-1]+nums[i-1];
}
int l=0;
int ans=0;
for(int j=1;j<=n;++j){
ll cnt=(ll)nums[j-1]*(j-l)-prev[j]+prev[l];
while(cnt>k){
l++;
cnt=(ll)nums[j-1]*(j-l)-prev[j]+prev[l];
}
ans=max(ans,j-l);
}
return ans;
}
};
2.LeetCode:1590. 使数组和能被 P 整除
给你一个正整数数组 nums,请你移除 最短 子数组(可以为 空),使得剩余元素的 和 能被 p 整除。 不允许 将整个数组都移除。
请你返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1 。
子数组 定义为原数组中连续的一组元素
示例 1:
输入:nums = [3,1,4,2], p = 6
输出:1
示例 2:
输入:nums = [6,3,5,2], p = 9
输出:2
示例 3:
输入:nums = [1,2,3], p = 3
输出:0
示例 4:
输入:nums = [1,2,3], p = 7
输出:-1
示例 5:
输入:nums = [1000000000,1000000000,1000000000], p = 3
输出:0
提示:
1 <= nums.length <= 1e5
1 <= nums[i] <= 1e9
1 <= p <= 1e9
这道题思路很简单,要使得全部元素和能被p整除,只需要在数组中找到一个和为余数且长度最小的子数组即可。假设这段区间为[j,i],那么公式就是:prev[n]-(prev[i]-prev[j])==kp;这个公式中有两个变量,其中一个我们可以进行枚举,这里选择i。
那么这种情况下,公式就变成了kp-prev[n]+prev[i]==prevp[j],固定i的情况下我们根据公式去寻找prev[j]即可,这里可以利用unordered_map来进行查找。
这里prev中的所有下标同样是为了求取前缀和方便进行了偏移。
class Solution {
unordered_map<int,int> map;
public:
int minSubarray(vector<int>& nums, int p) {
int n=nums.size();
int prev[100005]={0};
for(int i=1;i<=n;++i){
prev[i]=prev[i-1]+nums[i-1];
prev[i]%=p;
}
if(!prev[n]){
return 0;
}
map[0]=0;//要求不能全部删除,map[0]要置为0
int ans=0x3f3f3f;
for(int i=1;i<=n;++i){
int target=(p-prev[n]+prev[i])%p;
//都是求取余数,kp和p对结果无影响
if(map.find(target)!=map.end()){
ans=min(ans,i-map[target]);
}//找到就更新距离
map[prev[i]]=i;
}
if(ans==0x3f3f3f||ans==n){
ans=-1;
}
return ans;
}
};
3.LeetCode:1589. 所有排列中的最大和
有一个整数数组 nums ,和一个查询数组 requests ,其中 requests[i] = [starti, endi] 。第 i 个查询求 nums[starti] + nums[starti + 1] + … + nums[endi - 1] + nums[endi] 的结果 ,starti 和 endi 数组索引都是 从 0 开始 的。
你可以任意排列 nums 中的数字,请你返回所有查询结果之和的最大值。
由于答案可能会很大,请你将它对 109 + 7 取余 后返回。
示例 1:
输入:nums = [1,2,3,4,5], requests = [[1,3],[0,1]]
输出:19
示例 2:
输入:nums = [1,2,3,4,5,6], requests = [[0,1]]
输出:11
示例 3:
输入:nums = [1,2,3,4,5,10], requests = [[0,2],[1,3],[1,1]]
输出:47
提示:
n == nums.length
1 <= n <= 1e5
0 <= nums[i] <= 1e5
1 <= requests.length <= 1e5
requests[i].length == 2
0 <= starti <= endi < n
这道题就很简单了,利用差分数组求出来每个位置被查询测次数然后排序,原数组也进行排序,对应的位置相乘即可。
#define mod 1000000007
typedef long long ll;
class Solution {
public:
int maxSumRangeQuery(vector<int>& nums, vector<vector<int>>& requests) {
int n=nums.size();
int diff[n+1];
memset(diff,0,sizeof(diff));
for(auto & i:requests){
diff[i[0]]++;
diff[i[1]+1]--;
}
for(int i=1;i<n;++i){
diff[i]+=diff[i-1];
}
sort(diff,diff+n);
sort(nums.begin(),nums.end());
ll ans=0;
for(int i=0;i<n;++i){
ans+=(ll)nums[i]*diff[i];//这里要强转一下,否则会爆int
ans%=mod;
}
return ans;
}
};
4.LeetCode:1712. 将数组分成三个子数组的方案数
我们称一个分割整数数组的方案是 好的 ,当它满足:
数组被分成三个 非空 连续子数组,从左至右分别命名为 left , mid , right 。
left 中元素和小于等于 mid 中元素和,mid 中元素和小于等于 right 中元素和。
给你一个 非负 整数数组 nums ,请你返回 好的 分割 nums 方案数目。由于答案可能会很大,请你将结果对 109 + 7 取余后返回。
示例 1:
输入:nums = [1,1,1]
输出:1
示例 2:
输入:nums = [1,2,2,2,5,0]
输出:3
示例 3:
输入:nums = [3,2,1]
输出:0
提示:
3 <= nums.length <= 1e5
0 <= nums[i] <= 1e4
这道题的话,可以三分,也可以二分,这里就介绍二分的方法吧(因为三分的还没看懂)。
我们枚举中间数组 右边界i,然后对其他数组进行枚举。先看左边,我们要找到符合条件的最大的左数组右边界的值。这样是符合条件的临界值,当左移这个边界的时候肯定也会符合条件,因为左数组的和在这种情况下只能减小,中间数组的和在这种情况下只能增加。具体查找就是利用前缀和数组了十分简单。(左数组的和小于等于中间数组)
左数组的最有边界找到之后,我们寻找中间数组的最左边界。这里的范围就是左数组到左数组的最右边界。这时候找到符合条件的最左边界即可(中间数组和小于等于右数组),最后对这两个边界的范围进行累加。
注意这里并没有进行下标偏移求前缀和,直接利用原数组来求取,所以要注意下标的值。
class Solution {
const long long mod= 1e9+7;
public:
int waysToSplit(vector<int>& prev) {
int n=prev.size();
for(int i=1;i<n;++i){
prev[i]+=prev[i-1];
}
int ans=0;
for(int i=1;i<n-1;++i){
int l=0,r=i-1;
int lr=-1;
while(l<=r){
int mid=l+((r-l)>>1);
if(prev[mid]<=prev[i]-prev[mid]){
lr=mid;
l=mid+1;
}else r=mid-1;
}
if(lr==-1){
continue;
}
int rl=-1;
l=0,r=lr;
while(l<=r){
int mid=l+((r-l)>>1);
if(prev[i]-prev[mid]<=prev[n-1]-prev[i]){
rl=mid;
r=mid-1;
}else l=mid+1;
}
if(rl!=-1){
ans+=lr-rl+1;
ans%=mod;
}
}
return ans;
}
};