当前位置: 首页 > news >正文

LeetCode 3695. 交换元素后的最大交替和【无向图连通分量,快速选择】困难

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

给你一个整数数组nums

你希望最大化nums交替和:将偶数下标的元素相加减去奇数索引的元素获得的值。即nums[0] - nums[1] + nums[2] - nums[3]...

同时给你一个二维整数数组swaps,其中swaps[i] = [pi, qi]。对于swaps中的每对[pi, qi],你可以交换索引piqi处的元素。这些交换可以进行任意次数和任意顺序。

返回nums可能的最大交替和

示例 1:

输入:nums=[1,2,3],swaps=[[0,2],[1,2]]输出:4

解释:当nums[2, 1, 3][3, 1, 2]时,可以实现最大交替和。例如,你可以通过以下方式得到nums = [2, 1, 3]

  • 交换nums[0]nums[2]。此时nums[3, 2, 1]
  • 交换nums[1]nums[2]。此时nums[3, 1, 2]
  • 交换nums[0]nums[2]。此时nums[2, 1, 3]

示例 2:

输入:nums=[1,2,3],swaps=[[1,2]]输出:2

解释:不进行任何交换即可实现最大交替和。

示例 3:

输入:nums=[1,1000000000,1,1000000000,1,1000000000],swaps=[]输出:-2999999997

解释:由于我们不能进行任何交换,因此不进行任何交换即可实现最大交替和。

提示:

  • 2 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • 0 <= swaps.length <= 10^5
  • swaps[i] = [pi, qi]
  • 0 <= pi < qi <= nums.length - 1
  • [pi, qi] != [pj, qj]

方法 建图+DFS/并查集

用并查集将p i p_ipiq i q_iqi合并。合并后,同一集合(连通块)中的元素可以随意交换。

贪心地:

  • 交替和中取负号的元素,,我们把较小元素交换到奇数下标上。
  • 交替和中取正号的元素,位于偶数下标,我们把较大元素交换到偶数下标上。

为此,并查集需要额外维护集合中的奇数下标个数。

对于同一组,设奇数下标个数为o d d oddodd。把元素从小到大排序后,前o d d oddodd个元素取负号,其余元素取正号。

// 模板来源 https://leetcode.cn/circle/discuss/mOr1u6/classUnionFind{privatefinalint[]fa;// 代表元publicfinalint[]odd;// 集合中的奇数个数UnionFind(intn){// 一开始有 n 个集合 {0}, {1}, ..., {n-1}// 集合 i 的代表元是自己fa=newint[n];odd=newint[n];for(inti=0;i<n;i++){fa[i]=i;odd[i]=i%2;}}// 返回 x 所在集合的代表元// 同时做路径压缩,也就是把 x 所在集合中的所有元素的 fa 都改成代表元publicintfind(intx){// 如果 fa[x] == x,则表示 x 是代表元if(fa[x]!=x){fa[x]=find(fa[x]);// fa 改成代表元}returnfa[x];}// 把 from 所在集合合并到 to 所在集合中publicvoidmerge(intfrom,intto){intx=find(from);inty=find(to);if(x==y){// from 和 to 在同一个集合,不做合并return;}fa[x]=y;// 合并集合odd[y]+=odd[x];// 更新集合中的奇数个数}}classSolution{publiclongmaxAlternatingSum(int[]nums,int[][]swaps){intn=nums.length;UnionFinduf=newUnionFind(n);for(int[]p:swaps){uf.merge(p[0],p[1]);}List<Integer>[]g=newArrayList[n];Arrays.setAll(g,_->newArrayList<>());for(inti=0;i<n;i++){g[uf.find(i)].add(nums[i]);// 相同集合的元素分到同一组}longans=0;for(inti=0;i<n;i++){List<Integer>a=g[i];if(a.isEmpty()){continue;}Collections.sort(a);intodd=uf.odd[i];// 小的取负号,大的取正号for(intj=0;j<a.size();j++){intx=a.get(j);ans+=j<odd?-x:x;}}returnans;}}
// 模板来源 https://leetcode.cn/circle/discuss/mOr1u6/classUnionFind{vector<int>fa;// 代表元public:vector<int>odd;// 集合中的奇数个数UnionFind(intn):fa(n),odd(n){// 一开始有 n 个集合 {0}, {1}, ..., {n-1}// 集合 i 的代表元是自己for(inti=0;i<n;i++){fa[i]=i;odd[i]=i%2;}}// 返回 x 所在集合的代表元// 同时做路径压缩,也就是把 x 所在集合中的所有元素的 fa 都改成代表元intfind(intx){// 如果 fa[x] == x,则表示 x 是代表元if(fa[x]!=x){fa[x]=find(fa[x]);// fa 改成代表元}returnfa[x];}// 把 from 所在集合合并到 to 所在集合中// 返回是否合并成功voidmerge(intfrom,intto){intx=find(from),y=find(to);if(x==y){// from 和 to 在同一个集合,不做合并return;}fa[x]=y;// 合并集合。修改后就可以认为 from 和 to 在同一个集合了odd[y]+=odd[x];// 更新集合中的奇数个数}};classSolution{public:longlongmaxAlternatingSum(vector<int>&nums,vector<vector<int>>&swaps){intn=nums.size();UnionFinduf(n);// 假设并查集已实现for(auto&p:swaps){uf.merge(p[0],p[1]);}vector<vector<int>>g(n);for(inti=0;i<n;i++){g[uf.find(i)].push_back(nums[i]);// 相同集合的元素分到同一组}longlongans=0;for(inti=0;i<n;i++){auto&a=g[i];if(a.empty()){continue;}ranges::sort(a);// 或快速选择:ranges::nth_element(a, a.begin() + odd);intodd=uf.odd[i];// 小的取负号,大的取正号ans-=reduce(a.begin(),a.begin()+odd,0LL);ans+=reduce(a.begin()+odd,a.end(),0LL);}returnans;}};
# 模板来源 https://leetcode.cn/circle/discuss/mOr1u6/classUnionFind:def__init__(self,n:int):# 一开始有 n 个集合 {0}, {1}, ..., {n-1}# 集合 i 的代表元是自己self._fa=list(range(n))# 代表元self.odd=[i%2foriinrange(n)]# 集合中的奇数个数self.cc=n# 连通块个数# 返回 x 所在集合的代表元# 同时做路径压缩,也就是把 x 所在集合中的所有元素的 fa 都改成代表元deffind(self,x:int)->int:# 如果 fa[x] == x,则表示 x 是代表元fa=self._faiffa[x]!=x:fa[x]=self.find(fa[x])# fa 改成代表元returnfa[x]# 把 from 所在集合合并到 to 所在集合中defmerge(self,from_:int,to:int)->None:x,y=self.find(from_),self.find(to)ifx==y:# from 和 to 在同一个集合,不做合并returnself._fa[x]=y# 合并集合self.odd[y]+=self.odd[x]# 更新集合中的奇数个数classSolution:defmaxAlternatingSum(self,nums:List[int],swaps:List[List[int]])->int:uf=UnionFind(len(nums))forp,qinswaps:uf.merge(p,q)g=defaultdict(list)fori,xinenumerate(nums):g[uf.find(i)].append(x)# 相同集合的元素分到同一组ans=0fori,aing.items():a.sort()odd=uf.odd[i]# 小的取负号,大的取正号ans+=sum(a[odd:])-sum(a[:odd])returnans
// 模板来源 https://leetcode.cn/circle/discuss/mOr1u6/typeunionFindstruct{fa[]int// 代表元odd[]int// 集合中的奇数个数}funcnewUnionFind(nint)unionFind{fa:=make([]int,n)odd:=make([]int,n)// 一开始有 n 个集合 {0}, {1}, ..., {n-1}// 集合 i 的代表元是自己fori:=rangefa{fa[i]=i odd[i]=i%2}returnunionFind{fa,odd}}// 返回 x 所在集合的代表元// 同时做路径压缩,也就是把 x 所在集合中的所有元素的 fa 都改成代表元func(u unionFind)find(xint)int{// 如果 fa[x] == x,则表示 x 是代表元ifu.fa[x]!=x{u.fa[x]=u.find(u.fa[x])// fa 改成代表元}returnu.fa[x]}// 把 from 所在集合合并到 to 所在集合中func(u*unionFind)merge(from,toint){x,y:=u.find(from),u.find(to)ifx==y{// from 和 to 在同一个集合,不做合并return}u.fa[x]=y// 合并集合u.odd[y]+=u.odd[x]// 更新集合中的奇数个数}funcmaxAlternatingSum(nums[]int,swaps[][]int)(ansint64){n:=len(nums)uf:=newUnionFind(n)for_,p:=rangeswaps{uf.merge(p[0],p[1])}g:=make([][]int,n)fori,x:=rangenums{f:=uf.find(i)g[f]=append(g[f],x)// 相同集合的元素分到同一组}fori,a:=rangeg{ifa==nil{continue}slices.Sort(a)odd:=uf.odd[i]// 小的取负号,大的取正号forj,x:=rangea{ifj<odd{ans-=int64(x)}else{ans+=int64(x)}}}return}

复杂度分析:

  • 时间复杂度:O ( n log ⁡ n ) O(n\log n)O(nlogn)O ( n ) O(n)O(n),其中n nnn u m s numsnums的长度。使用快速选择算法求出前k kk小元素,可将时间复杂度优化到O ( n ) O(n)O(n)
  • 空间复杂度:O ( n ) O(n)O(n)

相似题目

[[LeetCode 1202. 交换字符串中的元素【无向图连通分量】1855]]
[[LeetCode 1722. 执行交换操作后的最小汉明距离【无向图连通分量】1892]]

专题训练

图论题单的【1.1 深度优先搜索(DFS)】
数据结构题单的【7. 并查集】

http://www.cnnetsun.cn/news/2041889.html

相关文章:

  • 推理模型为什么一开长思维就开始吞 Token:从 reasoning budget 到上下文回压的工程实战
  • Phi-3.5-mini-instruct效果展示:将3000字技术白皮书压缩为300字核心摘要真实输出
  • LosslessCut终极指南:掌握无损视频剪辑的7个高效技巧
  • 如何构建高效系统清理工具:Windows Cleaner架构深度解析
  • nli-MiniLM2-L6-H768高性能:单T4卡并发处理50+句子对的负载均衡配置
  • 在哪里下载?AI智能客服源码完整版,无加密,可二次开发
  • 如何快速解决C盘爆红问题:Windows Cleaner完整使用指南
  • ComfyUI-Manager:解放AI绘画工作流的终极管理利器
  • 别再瞎调了!DAZ Studio 4.12 Iray渲染参数保姆级避坑指南(附实战对比图)
  • Rust的PhantomData标记类型与泛型参数在编译期检查中的用途
  • 混合检索架构落地难?EF Core 10向量+全文+关系三合一查询引擎设计(附GitHub Star超1.2k的开源扩展包)
  • 一站式二次元游戏模组管理终极指南:XXMI启动器完整解决方案
  • Windows Cleaner完全掌握:三步彻底解决C盘爆红的高效免费工具终极指南
  • GESP2023年9月认证C++三级( 第三部分编程题(2、进制转换))
  • 从性能限制到性能释放:Universal-x86-Tuning-Utility 硬件调优全攻略
  • R-CNN目标检测算法精读全解
  • Phi-4-Reasoning-Vision行业落地:用多模态推理替代传统CV+LLM串联方案
  • 神经网络与数学理论的深度结合及应用实践
  • 9 款任务管理工具对比:哪类更适合企业协作场景
  • 告别环境配置焦虑:用Docker Compose一键部署RuoYi-Vue前后端开发环境(含MySQL 5.7/Redis 6.2)
  • 碧蓝航线Alas自动化脚本:解放双手的终极懒人指南
  • 边缘断网场景下Docker容器自动降级运行的7种配置组合(含离线证书续签、本地镜像签名验证等军工级实践)
  • AWS CI流水线自动化安全补丁方案解析
  • NS-USBLoader完整指南:Switch玩家的全能文件管理解决方案
  • 文科生也能轻松入局AI行业?高薪岗位唾手可得!
  • 软件数据访问对象管理中的持久化层
  • 2026降AI工具实测:论文降AIGC率首选方案指南
  • 财务BP,财务,会计,到底有什么区别?一文帮你分清财务BP,财务,会计!
  • 不只是QTextCodec:盘点Qt处理中文乱码时那些容易被忽略的‘坑’(含文件读写与UI设计器)
  • 简单几步!VoxCPM-1.5-WEBUI实现文字转语音,支持在线试听