数组刷题总结

文章介绍了在解决数组问题时如何运用二分查找来寻找最大值或最小值,以及如何利用前缀和与差分数组优化计算。通过四个具体的编程题目,展示了在求解最小开销、最大磁力、最低运载能力和最小子数组和问题中的应用方法。
摘要由CSDN通过智能技术生成

数组刷题总结

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;
    }
};
先求出无限数组的前缀和,然后通过二分法遍历每一种可能得步法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值