数组刷题总结
1.1 二分答案
当题目要求最大化某最小、最小化某最大,考虑使用二分查找。遍历所有可能得key,然后送入check函数进行题目判断
1760. 袋子里最少数目的球
给你一个整数数组 nums ,其中 nums[i] 表示第 i 个袋子里球的数目。同时给你一个整数 maxOperations 。
你可以进行如下操作至多 maxOperations 次:
选择任意一个袋子,并将袋子里的球分到 2 个新的袋子中,每个袋子里都有 正整数 个球。
比方说,一个袋子里有 5 个球,你可以把它们分到两个新袋子里,分别有 1 个和 4 个球,或者分别有 2 个和 3 个球。
你的开销是单个袋子里球数目的 最大值 ,你想要 最小化 开销。
请你返回进行上述操作后的最小开销。
class Solution {
public:
bool check(int cost,vector<int>& nums, int maxOperations){
int count=0;
for(int i=0;i<nums.size();i++){
if(nums[i]%cost==0) count+= nums[i]/cost-1;
else count += nums[i]/cost;
}
if(count<=maxOperations) return true;
return false;
}
int minimumSize(vector<int>& nums, int maxOperations) {
int left =1;
int right = 1e9;
while(left<right){
int mid = left+(right-left)/2;
if(check(mid,nums,maxOperations)){
right = mid;
}
else{
left = mid+1;
}
}
return left;
}
};
在本题目中,需要最小化,拆分后剩余的最大值。所以在check函数中,由于除法得到的余数肯定小于被除数,所以直接用数组值除以遍历得到的最大值,得到需要划分的次数,并进行判断。
1552. 两球之间的磁力
在代号为 C-137 的地球上,Rick 发现如果他将两个球放在他新发明的篮子里,它们之间会形成特殊形式的磁力。Rick 有 n 个空的篮子,第 i 个篮子的位置在 position[i] ,Morty 想把 m 个球放到这些篮子里,使得任意两球间 最小磁力 最大。
已知两个球如果分别位于 x 和 y ,那么它们之间的磁力为 |x - y| 。
给你一个整数数组 position 和一个整数 m ,请你返回最大化的最小磁力。
class Solution {
public:
bool check(vector<int>& position, int m,int work){
int j =0;
int n = position.size();
int count=0;
for(int i =0;i<n;i++){
if((position[i]-position[j])>=work){
count++;
j = i;
}
}
if(count>=(m-1)) return true;
return false;
}
int maxDistance(vector<int>& position, int m) {
sort(position.begin(),position.end());
int n = position.size();
int left =1;
int right = 1e9;
while(left<right){
int mid = left+(right-left)/2;
if(check(position,m,mid)){
left = mid+1;
}
else{
right = mid;
}
}
return left-1;
}
};
在这道题中,讲题目抽象为,求最大均分长度,在check中判断均分长度最大能分成多少段
1011 D天内运船
传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。
传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。
class Solution {
public:
bool check(vector<int>& weights, int days,int mid){
int n = 0;
int sum=0;
int n1=0;
for(auto c:weights){
sum+=c;
if(mid<c) return false;
if(mid<sum){
n++;
sum=c;
}
}
if(sum<mid) n++;
return n<=days;
}
int shipWithinDays(vector<int>& weights, int days) {
int l = 1;
int r = 1e9;
while(l<r){
int mid = l+(r-l)/2;
if(check(weights,days,mid)){
r = mid;
}
else l = mid+1;
}
return l;
}
};
在这道题目中,主要难点在于check函数中,计算运载天数时,如果船满载,则需要放弃当前货物,下次再运。且船的运载能力不能小于货物大小。
410 分割数组最大值的个数最小
给定一个非负整数数组 nums 和一个整数 m ,你需要将这个数组分成 m 个非空的连续子数组。
设计一个算法使得这 m 个子数组各自和的最大值最小。
class Solution {
public:
bool check(vector<int>& nums, int k,int mid){
int sum1=0;
int n = 1;
for(auto c:nums){
sum1+=c;
if(sum1>mid){
sum1 = c;
n++;
}
}
return n<=k;
}
int splitArray(vector<int>& nums, int k) {
int maxc = 0;
int all =0;
for(auto c:nums){
maxc = max(maxc,c);
all+=c;
}
int l = maxc;
int r = all;
while(l<r){
int mid = l+(r-l)/2;
if(check(nums,k,mid)){
r = mid;
}
else l = mid+1;
}
return l;
}
};
由于这道题要求最大连续字数组和,所以先猜一个数字。在check函数中,求所有值和小于等于这个值的数组数,最后再加上一个大于这个值的数组,就是当前猜的值可以划分的数组数。如果这个数小于要求的k,说明猜的和大了,需要r = mid。
并且由于check函数中包含了等于条件,所以在r=mid中包含了正确答案。
2.1 差分
1094. 拼车
车上最初有 capacity 个空座位。车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向)
给定整数 capacity 和一个数组 trips , trip[i] = [numPassengersi, fromi, toi] 表示第 i 次旅行有 numPassengersi 乘客,接他们和放他们的位置分别是 fromi 和 toi 。这些位置是从汽车的初始位置向东的公里数。
当且仅当你可以在所有给定的行程中接送所有乘客时,返回 true,否则请返回 false。
class Solution {
public:
bool carPooling(vector<vector<int>>& trips, int capacity) {
vector<int> bus(1000,0);
for(auto c:trips){
bus[c[1]]+=c[0];
if(c[2]<1000) bus[c[2]]-=c[0];
}
for(int i=0;i<bus.size();i++){
if(i>0) bus[i]+=bus[i-1];
if(bus[i]>capacity) return false;
}
return true;
}
};
通过差分的思想,维护一个差分数组,使得多次对数组内容增删操作可以在数组遍历外部一次实现。从n*m的复杂度变为n+m
1、服务器能耗统计
服务器有三种运行状态:空载、单任务、多任务,每个时间片的能耗的分别为1、3、4;
每个任务由起始时间片和结束时间片定义运行时间:
如果一个时间片只有一个任务需要执行,则服务器处于单任务状态;
如果一个时间片有多个任务需要执行,则服务器处于多任务状态;
给定一个任务列表,请计算出从第一个任务开始,到所有任务结束,服务器的总能耗。
#include<bits/stdc++.h>
using namespace std;
int N = 1e7;
vector<int>vec(N,0);
int n;
int main(){
cin>>n;
int max=0;
for(int i=0;i<n;i++){
int a,b;
cin>>a>>b;
max = max<b? b:max;
vec[a]+=1;
if(b<N) vec[b+1]-=1;
}
for(int i=1;i<vec.size();i++){
vec[i]+=vec[i-1];
}
int ans=0;
int start=0;
for(int i=0;i<=max;i++){
int c = vec[i];
if(vec[i]!=0) start=1;
if(start==1){
if(c==0) ans+=1;
else if(c==1) ans+=3;
else if(c>1) ans+=4;
}
}
cout<<ans;
return 0;
}
在这道题中,每一个时间片段,通过差分加1.。然后再输入数据的过程中,得到工作的最大时长。
3.1 前缀和
3.1.1 连续子数组的和
1031. 两个非重叠子数组的最大和
给你一个整数数组 nums 和两个整数 firstLen 和 secondLen,请你找出并返回两个非重叠 子数组 中元素的最大和,长度分别为 firstLen 和 secondLen 。
长度为 firstLen 的子数组可以出现在长为 secondLen 的子数组之前或之后,但二者必须是不重叠的。
子数组是数组的一个 连续 部分。
class Solution {
public:
int maxSumTwoNoOverlap(vector<int>& nums, int firstLen, int secondLen) {
vector<int>pre(nums.size()+1,0);
for(int i=1;i<=nums.size();i++){
pre[i]=pre[i-1]+nums[i-1];
}
int n = nums.size();
int ans=0;
for(int i=0;i+firstLen-1<n;i++){
int fir_sum=0;
int se_sum=0;
int path=0;
int fir_end = i+firstLen-1;
fir_sum = pre[fir_end+1]-pre[i];
for(int j=0;j+secondLen-1<i;j++){
int se_end = j+secondLen-1;
se_sum = pre[se_end+1]-pre[j];
path=fir_sum+se_sum;
ans = ans<path? path:ans;
}
for(int j=fir_end+1;j+secondLen-1<n;j++){
int se_end = j+secondLen-1;
se_sum = pre[se_end+1]-pre[j];
path=fir_sum+se_sum;
ans = ans<path? path:ans;
}
}
return ans;
}
};
思路为通过前缀和,免去相加的循环部分,然后用n2的复杂度爆搜。
2106. 摘水果
在一个无限的 x 坐标轴上,有许多水果分布在其中某些位置。给你一个二维整数数组 fruits ,其中 fruits[i] = [positioni, amounti] 表示共有 amounti 个水果放置在 positioni 上。fruits 已经按 positioni 升序排列 ,每个 positioni 互不相同 。
另给你两个整数 startPos 和 k 。最初,你位于 startPos 。从任何位置,你可以选择 向左或者向右 走。在 x 轴上每移动 一个单位 ,就记作 一步 。你总共可以走 最多 k 步。你每达到一个位置,都会摘掉全部的水果,水果也将从该位置消失(不会再生)。
返回你可以摘到水果的 最大总数 。
class Solution {
public:
int maxTotalFruits(vector<vector<int>>& fruits, int startPos, int k) {
int N = 2e5;
int n = fruits.size();
if(n==1&&k!=0) return fruits[0][1];
vector<int>dist(N+1,0);
for(int i=0;i<n;i++){
vector<int> tmp = fruits[i];
int a = tmp[0];
int b = tmp[1];
dist[a] = b;
}
vector<int> presum(N+2,0);
for(int i=1;i<=N+1;i++){
presum[i]+=presum[i-1]+dist[i-1];
}
if(k==0) return dist[startPos];
int ans=0;
for(int x=k;x>=0;x--){
int y = (k - x) / 2;
int l, r;
// x + 2y = k
l = startPos - x, r = startPos + y;
if(l>=0&&r<=N) ans = max(ans, presum[r+1] - presum[l]);
// 2y + x = k
l = startPos - y, r = startPos + x;
if(l>=0&&r<=N) ans = max(ans, presum[r+1] - presum[l]);
}
return ans;
}
};
先求出无限数组的前缀和,然后通过二分法遍历每一种可能得步法。