最大的和(双指针,前缀和)

这篇博客介绍了如何解决一个计算机算法问题,即在给定长度为n的正整数数列中,通过选择长度为k的区间改变元素状态以最大化可选状态元素的和。文中提供了两种解决方案:双指针法和前缀和法,并给出了相应的代码实现。这两种方法都能够在O(n)的时间复杂度内解决这个问题,适用于数据范围1≤k≤n≤105,1≤ai≤105的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述
给定一个长度为 n 的正整数数列 a1,a2,…,an。初始时,数列中的每个元素要么处于可选状态,要么处于不可选状态。你可以选择一个长度恰好为 k 的区间 [i,i+k−1],使得 ai∼ai+k−1 这 k 个元素的状态全部变为可选。
请问,在经过此操作后,所有处于可选状态的元素之和最大是多少。

输入格式

  • 第一行包含两个整数 n 和 k。
  • 第二行包含 n 个整数 ai。
  • 第三行包含一个长度为 n 的 01 序列,如果第 i 个数为 1,表示 ai 的初始状态为可选,如果第 i 个数为 0,表示 ai 的初始状态为不可选。

输出格式

  • 一行一个整数,表示答案。

数据范围

  • 对于 30% 的数据,1≤k≤n≤1000 (双重循环可以解决)
  • 对于 100% 的数据,1≤k≤n≤105,1≤ai≤105 (O(log) O(n)的方法解决)

示例1:
输入:
3 1
2 5 4
0 0 1
输出1:
9

示例2
输入:
4 3
10 5 4 7
0 1 1 0
输出:24

双指针做法:先求解可选状态的数字的总和sum,然后使用双指针计算滑动窗口内不可选的总和的最大值v,与sum相加求和可以得到最后的答案。
双指针滑动的规则:i 指针向后移动如果超出了窗口 k 的范围需要减去位于 i-k 位置的值,且该值是不可选的。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=100010;
int a[N],b[N];
int n,k;
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    for(int i=0;i<n;i++) scanf("%d",&b[i]);
    
    LL sum=0;
    for(int i=0;i<n;i++)
    {
        if(b[i]) sum+=a[i];
    }
    LL v=0,s=0;
    for(int i=0;i<n;i++)
    {
        if(!b[i]) s+=a[i];
        if(i>k-1 && !b[i-k]) s-=a[i-k];
        v=max(v,s);
    }
    cout<<v+sum<<endl;
    return 0;
}

前缀和解法:
需要开两个数组sum和s,sum存的是所有数据的前缀和,s 存储的是可选数据的前缀和,要想在窗口 k 的范围内未选择的数值和最大,需要使得在 k 这个窗口内sum[i] - sum[i-k] - ( s[i] - s[i-k] )的值最大。最后要求解的是所有可选数据和加上在窗口 k 中的最大值。注意从1开始遍历,防止 使用 i-1 时越界。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=100010;
int a[N],b[N];
LL sum[N],s[N];
int n,k;
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    
    
    for(int i=1;i<=n;i++)
    {
        if(b[i]) 
        {
            s[i]=s[i-1]+a[i];
        }
        else 
            s[i]=s[i-1];
        sum[i]=sum[i-1]+a[i];//所有的数据和
    }
    LL v=0;
    for(int i=k;i<=n;i++)
    {
        v=max(v,sum[i]-sum[i-k]-s[i]+s[i-k]);
    }
    cout<<v+s[n]<<endl;
    return 0;
}
### 双指针算法在 Java 中的实现详解 双指针算法是一种常见的优化技术,广泛应用于数组、链表字符串等问题中。其核心思想是通过维护两个指针来遍历数据结构,从而减少不必要的计算并提升性能。 #### 核心逻辑解释 上述代码示例展示了如何使用双指针判断一个序列是否为另一个序列的子序列[^1]。具体来说: - **初始化**:定义两个指针 `i` `j` 分别指向数组 `a` `b` 的起始位置。 - **循环条件**:当 `i < n` 且 `j < m` 时继续执行。 - **匹配操作**:如果当前元素相等 (`a[i] == b[j]`),则移动指针 `i`;无论是否匹配,都需移动指针 `j` 来尝试下一个可能的位置。 - **最终判定**:若 `i == n` 表明整个数组 `a` 已被成功匹配,则输出 `"Yes"` 否则输出 `"No"`。 此方法的时间复杂度为 \(O(n + m)\)[^2],其中 \(n\) 是数组 `a` 的长度,\(m\) 是数组 `b` 的长度。 --- #### 容器盛水最大值问题实例 下面是一个经典的双指针应用案例——求解容器能容纳的最大水量[^3]。 ```java class Solution { public int maxArea(int[] height) { int left = 0; int right = height.length - 1; int ret = 0; while (left < right) { // 计算当前两线之间的面积 int v = Math.min(height[left], height[right]) * (right - left); ret = Math.max(ret, v); // 移动较短的一侧以寻找更大的可能性 if (height[left] <= height[right]) { left++; } else { right--; } } return ret; } } ``` 该程序的核心在于每次比较两侧高度后,总是将较小的那一端向内收缩,这样可以逐步逼近最优解而无需穷举所有组合。 --- #### KMP 算法简介(扩展阅读) 虽然严格意义上不属于双指针范畴,但提到模式匹配时不得不提及的是 KMP 算法[^4]。它也是一种基于指针位移的技术,在处理字符串匹配方面表现优异。 以下是其实现概览: ```java public class KMPAlgorithm { public static void getNext(String pattern, int[] next) { int j = -1; // 初始化前缀末尾索引 next[0] = j; for (int i = 1; i < pattern.length(); ++i) { while (j >= 0 && pattern.charAt(i) != pattern.charAt(j + 1)) { j = next[j]; } if (pattern.charAt(i) == pattern.charAt(j + 1)) { j += 1; } next[i] = j; } } public static boolean kmpSearch(String text, String pattern) { int[] next = new int[pattern.length()]; getNext(pattern, next); int j = -1; // 模式串指针初始状态 for (int i = 0; i < text.length(); ++i) { while (j >= 0 && text.charAt(i) != pattern.charAt(j + 1)) { j = next[j]; } if (text.charAt(i) == pattern.charAt(j + 1)) { j += 1; } if (j == pattern.length() - 1) { return true; // 找到匹配项 } } return false; } } ``` 尽管这里涉及更多复杂的边界调整机制,但它同样依赖于指针控制完成高效搜索过程。 --- ### 性能分析总结 无论是简单的子序列检测还是更高级别的容量最大化或者字符串匹配任务,双指针均展现出卓越的优势特性如下: - 显著降低了时间开销至线性级别; - 几乎不增加额外内存负担,保持常数级空间消耗; - 能够灵活应对不同类型的输入形式以及业务需求变化。 --- 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值