数组相关问题学习指南

前置芝士

最长上升子序列 (LIS)

平衡子序列的最大和

[problem description]

给你一个下标从 0 开始的整数数组 nums

nums 一个长度为 k子序列 指的是选出 k下标 \(i_0、i_1、 ... 、i_{k-1}\) ,如果这个子序列满足以下条件,我们说它是 平衡的

  • 对于范围 [1, k - 1] 内的所有 j\(nums[i_j] - nums[i_{j-1}] >= i_j - i_{j-1}\) 都成立。

nums 长度为 1子序列 是平衡的。

请你返回一个整数,表示 nums 平衡 子序列里面的 最大元素和

一个数组的 子序列 指的是从原数组中删除一些元素( 也可能一个元素也不删除 )后,剩余元素保持相对顺序得到的 非空 新数组。

[input]

输入:nums = [3,3,5,6]
输出:14
解释:这个例子中,选择子序列 [3,5,6] ,下标为 0 ,2 和 3 的元素被选中。
nums[2] - nums[0] >= 2 - 0 。
nums[3] - nums[2] >= 3 - 2 。
所以,这是一个平衡子序列,且它的和是所有平衡子序列里最大的。
包含下标 1 ,2 和 3 的子序列也是一个平衡的子序列。
最大平衡子序列和为 14 。

[datas]

  • 1 <= nums.length <= 10^5
  • -10^9<= nums[i] <= 10^9

[solved]

定义 b[i]=nums[i]−i,问题变成从 b 中选出一个非降子序列,求对应的 nums的元素和的最大值。

这可以用 权值树状数组 (或者权值线段树)来优化。树状数组用来维护前缀最大值,设下标为x=b[i],维护的值为max(f[x],f[x-1],f[x-2],...)。

# 树状数组模板(维护前缀最大值)
class BIT:
    def __init__(self,n):
        # 空间需要多开大一些
        self.tr=[-inf]*n
    def change(self,i:int,val:int)->None:
        while i<len(self.tr):
            self.tr[i]=max(self.tr[i],val)
            i+=i&-i
    def query(self,i:int)->int:
        mx=-inf
        while i>0:
            mx=max(mx,self.tr[i])
            i-=i&-i
        return mx
class Solution:
    def maxBalancedSubsequenceSum(self, nums: List[int]) -> int:
        n=len(nums)
        b=sorted(set(x-i for i,x in enumerate(nums)))
        t=BIT(len(b)+1)
        res=-inf
        for i,x in enumerate(nums):
            j=bisect_left(b,x-i)+1 # nums[i]-i 离散化后的值(从 1 开始)
            f=max(t.query(j),0)+x
            res=max(res,f)
            t.change(j,f)
        return res
    

求解两个有序数组的第 K 小乘积

先统计分负数乘积个数neg、正数乘积个数pos以及乘积为0的个数 zero,

然后分三种情况讨论:

k≤negk,我们可以二分负数答案,统计不超过二分值的乘积个数;
neg<k≤neg+zero,此时返回0;
k>neg+zero,我们可以二分正数答案,统计不超过二分值的乘积个数。

字段和问题

不限制长度——在一个数列里找两个不相交区间使得他们权值和最大

找 m个长度为 k 的不相交区间使得他们的权值和最大 (1≤n≤5000)

f[i][j]=max(f[i−k][j−1]+sum[i]−sum[i−k],f[i−1][j])

区间数目变多且不限制长度——找 m 个不相交长度不限的区间使得他们权值和最大(1≤n≤5000)

选2个不重叠的长度为k的区间

使子数组和最大

因为区间大小是固定为k的,所以显然需要前缀和处理一下处理之后我们去维护前缀中长度为k的最大值ma,枚举第二个长度为k的起点,那么答案就是max(ma+当前长度为k的序列和)复杂度为O(n).

int T,n,k;
ll a[2000100];
void solve(){
    cin>>T;
    while(T--){
        cin>>n>>k;
        for(int i=1;i<=n;i++) {cin>>a[i];a[i]+=a[i-1];}
        ll ma=-1e18,ans=-1e18;
        for(int i=k;i+k<=n;i++){
            ma=max(ma,a[i]-a[i-k]);
            ans=max(ans,ma+a[i+k]-a[i]);
        }
        cout<<ans<<endl;
    }

使子数组元素和相等(循环数组)

给你一个下标从 0 开始的整数数组 arr 和一个整数 k ,数组 arr 是一个循环数组,数组中的最后一个元素的下一个元素是数组中的第一个元素,数组中第一个元素的前一个元素是数组中的最后一个元素。

选中 arr 中任意一个元素,并使其值加上 1 或减去 1。

执行运算使每个长度为 k 的 子数组 的元素总和都相等,返回所需要的最少运算次数。

  • 1 <= k <= arr.length <= 105
  • 1 <= arr[i] <= 10^9

【解题思路分析】

1)解决 a不是循环数组的情况

考虑从 i 和 i+1开始的两个长为 k的子数组的和,如果要求这两个和相等,则有

\(a[i]+a[i+1]+⋯+a[i+k−1]=a[i+1]+a[i+2]+⋯+a[i+k]\)

\(a[i]=a[k+i]=a[2k+i]....\)

2)分组处理

按照 i mod k的结果将 a 分组,对每一组(记作 b)

我们需要解决:

让数组 b的所有元素相等的最少运算次数。

根据中位数贪心,将 b的所有元素变为 b 的中位数是最优的。

3)翡翠定理

比如 n=6,k=4,那么 a[2]循环后是 a[8],和 a[0]在同一组,而 a[1]无论怎么循环都无法和 a[0] 在同一组。((1+6n) mod 4≠0

一个循环数组如果既有周期 n,又有周期 k,则必然有周期 gcd⁡(n,k)。证明:根据 裴蜀定理,有:

a[i]=a[i+nx+ky]=a[i+gcd⁡(n,k)]
这样就转换成了不是循环数组的情况。

【java】

public long makeSubKSumEqual(int[] arr, int k) {
        int n=arr.length;
       k=gcd(n,k);
       System.out.println(k);
        long res=0;
        for(int i=0;i<k;i++){
            ArrayList<Integer> list=new ArrayList<>();
            for(int j=i;j<n;j+=k){
                     list.add(arr[j]);
            }
            Collections.sort(list);
            int mid=list.get((list.size())/2);
            for(int x:list){
                res+=Math.abs(x-mid);
            }                     
        }
        return res;
    }
    int gcd(int a,int b){
        while(b!=0){
            int tmp=b;
           b=a%b;
            a=tmp;
        }
        return a;
    }
posted @ 2023-10-17 20:14  White_Sheep  阅读(9)  评论(0编辑  收藏  举报