C++前缀和算法的应用:分割数组的最多方案数 原理源码测试用例

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介: C++前缀和算法的应用:分割数组的最多方案数 原理源码测试用例

本文涉及的基础知识点

C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频

题目

给你一个下标从 0 开始且长度为 n 的整数数组 nums 。分割 数组 nums 的方案数定义为符合以下两个条件的 pivot 数目:

1 <= pivot < n

nums[0] + nums[1] + … + nums[pivot - 1] == nums[pivot] + nums[pivot + 1] + … + nums[n - 1]

同时给你一个整数 k 。你可以将 nums 中 一个 元素变为 k 或 不改变 数组。

请你返回在 至多 改变一个元素的前提下,最多 有多少种方法 分割 nums 使得上述两个条件都满足。

示例 1:

输入:nums = [2,-1,2], k = 3

输出:1

解释:一个最优的方案是将 nums[0] 改为 k 。数组变为 [3,-1,2] 。

有一种方法分割数组:

  • pivot = 2 ,我们有分割 [3,-1 | 2]:3 + -1 == 2 。
    示例 2:
    输入:nums = [0,0,0], k = 1
    输出:2
    解释:一个最优的方案是不改动数组。
    有两种方法分割数组:
  • pivot = 1 ,我们有分割 [0 | 0,0]:0 == 0 + 0 。
  • pivot = 2 ,我们有分割 [0,0 | 0]: 0 + 0 == 0 。
    示例 3:
    输入:nums = [22,4,-25,-20,-15,15,-16,7,19,-10,0,-13,-14], k = -33
    输出:4
    解释:一个最优的方案是将 nums[2] 改为 k 。数组变为 [22,4,-33,-20,-15,15,-16,7,19,-10,0,-13,-14] 。
    有四种方法分割数组。
    参数范围
    n == nums.length
    2 <= n <= 105
    -105 <= k, nums[i] <= 105

分析

原理

nums[0,p)-nums[p,m_c) 如果等于0,则符合条件。修改nums[i]

i <p 左边增加 k - nums[i]
i>=p 右边增加 k - nums[i]

大致流程

先获取初始状态(未修改)所有数的和。

枚举p,左边减右边的放到mModifyRightLeftSubRight中。

初始状态下,mModifyRightLeftSubRight[0]就是分割方案数。

枚举修改nums[i]。

如果i在左边 则初始mModifyLeftLeftSubRight[-iSub]就是方案数
如果i在右边 则初始mModifyRightLeftSubRight[iSub]就是方案数

变量解释

llTota nums的和
mModifyLeftLeftSubRight; 符合i < p,各方案nums[0,p)-nums[p,m_c)
mModifyRightLeftSubRight 符合i>=p,各方案nums[0,p)-nums[p,m_c)

时间复杂度

两次循环,时间复杂度都是O(n),故总时间复杂度是O(n)。

代码

核心代码

class Solution {
public:
int waysToPartition(vector& nums, int k) {
m_c = nums.size();
long long llTotal = std::accumulate(nums.begin(), nums.end(), 0LL);
std::unordered_map<long, int> mModifyRightLeftSubRight;
//[0,p)左半部分,[p,m_c)右半部分
long long llR = 0;//llR是nums[p,m_c)的和
for (int p = m_c - 1; p > 0; p–)
{
llR += nums[p];
const long long llLeft = llTotal - llR;
mModifyRightLeftSubRight[llLeft - llR]++;
}
int iRet = mModifyRightLeftSubRight[0];//不改变任何数,能拆分的数量
std::unordered_map<long, int> mModifyLeftLeftSubRight;
//修改nums[p]
llR = 0;
for (int i = m_c - 1; i >= 0; i–)
{
int iSub = (k - nums[i]);
const int cur = mModifyRightLeftSubRight[iSub]+ mModifyLeftLeftSubRight[-iSub];
iRet = max(iRet, cur);
llR += nums[i];
const long long llLeft = llTotal - llR;
mModifyRightLeftSubRight[llLeft - llR]–;
mModifyLeftLeftSubRight[llLeft - llR]++;
}
return iRet;
}
int m_c;
};

测试用例

template
void Assert(const vector& v1, const vector& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
assert(v1[i] == v2[i]);
}
}
template
void Assert(const T& t1, const T& t2)
{
assert(t1 == t2);
}
int main()
{
Solution slu;
vector nums;
int k;
int res;
nums = { 0,0,0 };
k = 1;
res = slu.waysToPartition(nums, k);
Assert(2, res);
nums = { 0,0,0,0 };
k =3;
res = slu.waysToPartition(nums, k);
Assert(3, res);
nums = { -2, -1, 3, 4, 5 };
k = 3;
res = slu.waysToPartition(nums, k);
Assert(0, res);
nums = { -2, -1, 3, 4, 5 };
k = 2;
res = slu.waysToPartition(nums, k);
Assert(0, res);
nums = { -2, -1, 3, 4, 5 };
k = -3;
res = slu.waysToPartition(nums, k);
Assert(0, res);
nums = { -1, -1, 3, 4, 5 };
k = -3;
res = slu.waysToPartition(nums, k);
Assert(1, res);
nums = { -1,3 };
k = 3;
res = slu.waysToPartition(nums, k);
Assert(1, res);
nums = { 5,4,3 };
k = 8;
res = slu.waysToPartition(nums, k);
Assert(0, res);
nums = { 5,4,3 };
k = 1;
res = slu.waysToPartition(nums, k);
Assert(1, res);
nums = { 5,4,3 };
k = 7;
res = slu.waysToPartition(nums, k);
Assert(1, res);
nums = { 3,5,4 };
k = 1;
res = slu.waysToPartition(nums, k);
Assert(1, res);
nums = { 3,5,4 };
k = 1;
res = slu.waysToPartition(nums, k);
Assert(1, res);
nums = { 3,5,4 };
k = 0;
res = slu.waysToPartition(nums, k);
Assert(0, res);
//CConsole::Out(res);

}

2023年4月旧代码

class Solution {
public:
int waysToPartition(vector& nums, int k) {
long long llSum = 0;
std::unordered_map<long long, int> mSumNum;//各前缀和数量
vector vSum;
for (int i = 0; i < nums.size(); i++)
{
llSum += nums[i];
vSum.emplace_back(llSum);
if (i + 1 < nums.size())
{
mSumNum[llSum]++;
}
}
int iRet = 0;
if (0 == llSum % 2)
{
iRet += mSumNum[llSum / 2];
}
const long long llNewSum = llSum + k - nums.back();
if (0 == llNewSum % 2)
{
iRet =max(iRet, mSumNum[llNewSum / 2]);
}
std::unordered_map<long long, int> mRightSumNum;
for (int i = nums.size() - 2; i >= 0; i–)
{
const long long& llCurSum = vSum[i];
mSumNum[llCurSum]–;
mRightSumNum[llCurSum]++;
const long long llNewSum = llSum + k - nums[i];
if (llNewSum & 1)
{//改变后是奇数
continue;
}
int iCurRet = mSumNum[llNewSum / 2];
    iCurRet += mRightSumNum[llNewSum / 2 - (k - nums[i])];
    iRet = max(iRet, iCurRet);
  }
  return iRet;
}

};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。

https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程

https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《闻缺陷则喜算法册》doc版

https://download.csdn.net/download/he_zhidan/88348653

鄙人想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
墨家名称的来源:有所得以墨记之。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境:

VS2022 C++17


相关文章
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
321 17
|
自然语言处理 算法 数据可视化
文本聚类效果差?5种主流算法性能测试帮你找到最佳方案
本文探讨了自然语言处理中句子嵌入的聚类技术,使用Billingsmoore数据集(925个英语句子)进行实验。通过生成句子嵌入向量并可视化分析,对比了K-Means、DBSCAN、HDBSCAN、凝聚型层次聚类和谱聚类等算法的表现。结果表明,K-Means适合已知聚类数量的场景,DBSCAN和HDBSCAN适用于未知聚类数量且存在异常值的情况,而谱聚类在句子嵌入领域表现不佳。最终建议根据数据特征和计算资源选择合适的算法以实现高质量聚类。
931 0
文本聚类效果差?5种主流算法性能测试帮你找到最佳方案
|
12月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
290 0
|
机器学习/深度学习 监控 算法
局域网行为监控软件 C# 多线程数据包捕获算法:基于 KMP 模式匹配的内容分析优化方案探索
本文探讨了一种结合KMP算法的多线程数据包捕获与分析方案,用于局域网行为监控。通过C#实现,该系统可高效检测敏感内容、管理URL访问、分析协议及审计日志。实验表明,相较于传统算法,KMP在处理大规模网络流量时效率显著提升。未来可在算法优化、多模式匹配及机器学习等领域进一步研究。
324 0
|
机器学习/深度学习 存储 算法
基于 C++ 布隆过滤器算法的局域网上网行为控制:URL 访问过滤的高效实现研究
本文探讨了一种基于布隆过滤器的局域网上网行为控制方法,旨在解决传统黑白名单机制在处理海量URL数据时存储与查询效率低的问题。通过C++实现URL访问过滤功能,实验表明该方法可将内存占用降至传统方案的八分之一,查询速度提升约40%,假阳性率可控。研究为优化企业网络管理提供了新思路,并提出结合机器学习、改进哈希函数及分布式协同等未来优化方向。
356 0
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
567 12
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
298 0
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
478 0