贪心算法实战:中位数思想解决字符串相邻交换最小化问题
1. 问题引入一道看似简单的“难题”在信息学奥赛的练习题库里有一道编号为1223的题目名字叫“An Easy Problem”。很多初学者看到这个标题第一反应可能是放松警惕——既然都叫“简单问题”了那应该不难吧但实际情况往往相反这道题恰恰是检验你是否真正理解“贪心算法”思想精髓的经典门槛。它不像动态规划那样有复杂的递推方程也不像搜索算法那样需要庞大的状态空间它的核心挑战在于你能否从看似无序或复杂的要求中抽丝剥茧找到一个“每一步都采取当前看来最优选择”的策略并且能严格证明这个局部最优的选择最终能导向全局最优解。这道题的具体描述通常是给定一个由‘0’和‘1’组成的字符串你可以进行一种操作——交换任意两个相邻的字符。题目要求求出为了使字符串中所有的‘1’都连续即所有的‘1’都挨在一起中间没有‘0’最少需要多少次相邻交换。举个例子对于字符串“1010”最少交换次数是1交换第二个字符‘0’和第三个字符‘1’得到“1100”。问题本身描述非常简洁但解法背后的贪心思路却值得深入咀嚼。今天我们就来彻底拆解这道“简单问题”不仅给出代码更要讲清楚贪心策略是如何被发现的为什么它是正确的以及在编码实现时有哪些意想不到的坑。2. 贪心策略的发现与证明为什么中位数位置是关键面对这个问题最直接的暴力想法是模拟所有可能的‘1’的最终连续段的位置然后计算每个位置下的移动代价。假设字符串长度为n其中有m个‘1’。那么这m个‘1’在最终状态下会占据一段连续的位置假设这段连续区间的左端点是L那么‘1’的位置就是L, L1, ..., Lm-1。我们的任务就是为原始的m个‘1’它们散布在字符串的各个位置找到新家L到Lm-1并使得所有‘1’移动的总距离最短这里“移动”被定义为相邻交换的次数而相邻交换次数正好等于一个字符需要移动的格子数。这听起来有点像“仓库选址”问题。我们有m个点‘1’的原始位置需要将它们移动到m个连续的位置上使得总移动距离最小。一个经典的结论是当目标位置也是一个序列时让原始点与目标点按顺序一一配对总距离最小。换句话说我们把所有‘1’的原始位置记录在一个数组pos[]里按从左到右的顺序然后我们选择一段长度为m的连续区间[L, Lm-1]作为目标位置。最优的匹配方式就是将pos[0]移动到Lpos[1]移动到L1……pos[m-1]移动到Lm-1。这样总移动代价就是 Σ |pos[i] - (L i)|其中i从0到m-1。那么问题就转化为如何选择这个左端点L使得上面这个总和最小我们令target[i] L i则代价为 Σ |pos[i] - target[i]|。仔细观察target[i]是一个公差为1的等差数列。我们的目标是调整L使这个等差数列与pos序列的“差距”最小。这里就用到另一个经典结论要使一组数到一组连续整数等差数列的距离之和最小当连续整数的公差为1时最优解是让target序列的中位数与pos序列的中位数对齐。更具体地说如果我们把pos[i]减去i得到一个新的序列b[i] pos[i] - i。那么我们的总代价公式可以重写 总代价 Σ |pos[i] - (L i)| Σ |(pos[i] - i) - L| Σ |b[i] - L|。看问题神奇地简化了现在我们需要找一个L使得它到数组b所有元素的绝对值之和最小。这是一个经典的“选址问题”在数轴上找一点使得到一组已知点的距离之和最小。结论是选择这组点的中位数作为L可以使得绝对值之和最小。因此最优的左端点L就是序列b的中位数。贪心策略由此浮出水面记录所有‘1’的原始位置索引从0开始或从1开始需统一存入数组pos。构造新数组b其中b[i] pos[i] - i。这里的i是pos数组中的序号0-indexed。b[i]的物理意义可以理解为如果最终连续‘1’段的第一个‘1’放在位置0那么第i个原始‘1’的理想“基准位置”是多少。实际上b数组的每个元素代表了一个“偏移”需求。求出b数组的中位数记为median_b。最优的连续段左端点L就等于median_b。计算总代价ans Σ |pos[i] - (L i)|。这个策略的贪心性质体现在我们并没有动态地考虑交换的相互影响而是通过数学转化直接找到了一个全局最优的“聚集点”L。每一步选择L都基于当前信息的全局最优最小化绝对值和并且一旦L确定匹配方案第i个‘1’去第Li个位置也是确定且最优的。证明的关键在于绝对值函数求和的最小值点在中位数这一数学性质上。3. 从理论到代码实现细节与边界处理理解了算法原理代码实现就相对清晰了。但其中仍有几个细节需要仔细处理否则极易出错。3.1 索引的统⼀与中位数计算首先需要明确字符串索引。通常输入字符串长度为n索引从0到n-1比较方便。我们遍历字符串将字符为‘1’的索引i存入pos列表。假设pos列表的长度为m。接着构建b数组。这里i是pos列表中的序号0到m-1。所以b[i] pos[i] - i。然后求b数组的中位数。中位数的定义对于有序数组如果数组长度m是奇数中位数是正中间那个数如果是偶数中位数是中间两个数的任意一个吗对于“使绝对值和最小”这个问题当点数为偶数时选取中间两个数构成的闭区间内的任意一点都能达到最小值。通常为了方便我们取中间两个数的下中位数或上中位数或者直接取它们的平均值但平均值可能不是整数而我们的L必须是整数。由于L必须是整数位置而b数组中的元素都是整数所以当m为偶数时取b[m//2 - 1]或b[m//2]作为中位数都可以。为了简化我们可以直接对b数组排序后取下标为m // 2的元素作为中位数无论是奇是偶这个下标都能给出一个有效的中位数候选在偶数时是上中位数。即b.sort() median_b b[m // 2] # 整数除法 L median_b这里L就是计算出的最优左端点。3.2 代价计算与溢出风险得到L后计算总代价ans 0 for i in range(m): ans abs(pos[i] - (L i))这里需要注意pos[i]、L、i都是整数计算出的代价可能很大。题目虽然没有明确给出数据范围但假设字符串长度n最大为10^6且所有字符都是‘1’那么代价可能达到O(n^2)级别显然会超过32位整数的范围。因此在C中应使用long long在Python中整数自动支持大数但也要注意计算效率。一个容易忽略的边界情况是计算出的L可能小于0或者Lm-1可能大于等于n吗我们来分析一下。b[i] pos[i] - i。因为pos[i]是递增的且pos[i] i最紧凑的情况是前m个位置都是‘1’所以b[i] 0。实际上b数组是非递减的。中位数median_b也必然大于等于0。所以L 0。另一方面L m - 1的最大值是多少考虑最坏情况所有‘1’都在字符串末尾。假设最后一个‘1’在索引pos[m-1] n-1。那么b[m-1] (n-1) - (m-1) n - m。中位数不会超过b[m-1]所以L n - m。因此L m - 1 (n - m) m - 1 n - 1。这说明我们计算出的最优连续段一定在原字符串的索引范围内是合法的。这个数学性质保证了我们无需对L进行额外的边界检查。3.3 代码实现示例Python下面给出一个清晰且高效的Python实现包含了输入处理和核心逻辑def min_swaps_to_group_ones(s: str) - int: # 1. 收集所有1的位置索引 positions [i for i, ch in enumerate(s) if ch 1] m len(positions) if m 1: # 0个或1个1无需交换 return 0 # 2. 构建b数组b[i] positions[i] - i b [positions[i] - i for i in range(m)] # 3. 找到b数组的中位数作为最优左端点L b.sort() median_b b[m // 2] L median_b # 4. 计算总代价 total_cost 0 for i in range(m): target_pos L i # 第i个1应该去往的目标位置 total_cost abs(positions[i] - target_pos) return total_cost # 示例用法 if __name__ __main__: # 假设从标准输入读取字符串例如 101001 # input_str input().strip() input_str 101001 result min_swaps_to_group_ones(input_str) print(f最少需要相邻交换次数: {result})这段代码的时间复杂度是O(m log m)主要来自对b数组的排序。其中m是字符串中‘1’的个数。空间复杂度是O(m)。对于长度很大的字符串如果‘1’的个数非常稀疏这个算法依然高效。4. 贪心算法的思维训练与常见误区解出这道题不仅仅是学会了一个模板更重要的是训练了一种化归的思维。面对一个复杂问题我们通过定义合适的数学模型pos数组目标序列代价公式然后利用已知的数学结论中位数最小化绝对值和来解决问题。这是贪心算法中非常高级的一种应用其正确性依赖于严谨的数学证明而非直观上的“显然”。在实际做题或面试中围绕这道题常见的误区和考察点有以下几个误区一误用“最近匹配”贪心。有人可能会想到这样的策略从左到右扫描遇到一个‘1’就把它往左移动直到碰到另一个‘1’或边界。或者总是交换当前最左边‘0’和最右边‘1’之类的局部操作。这些策略都是错误的无法保证得到最小交换次数。例如字符串“1001”错误策略可能先交换中间两个字符变成“1100”代价为1但正确的最优解其实也是1交换第一个‘1’和它右边的‘0’或者交换最后一个‘1’和它左边的‘0’。虽然这个例子结果相同但对于更复杂的串如“1010001”错误策略就会得到次优解。这道题的正解要求我们必须从全局出发找到那个最优的“聚集中心”L。误区二忽略索引转换的细节。在推导b[i] pos[i] - i时这个i是pos数组的序号而不是字符串的索引。如果这里搞混比如写成pos[i] - pos[0]之类的整个计算就会出错。务必理解i在这里代表的是“第几个1”它的目的是将最终目标位置序列L, L1, ...与pos序列对齐。误区三中位数计算处理不当。如前所述当m为偶数时中位数不唯一但取b[m//2]是简单有效的选择。有些初学者可能会尝试计算(b[m//2 - 1] b[m//2]) // 2但这样得到的L可能不是整数或者即使取整了也不一定比直接取其中一个更好。实际上对于绝对值最小化问题闭区间内的任意点都是最优解所以直接取数组中的一个元素b[m//2]是最稳妥的因为它一定是整数并且是b数组中的一个实际值保证了L是整数。考察点对“相邻交换”代价的深刻理解。这道题巧妙地将“相邻交换次数”等价为“移动格子的距离之和”。这是解决很多字符串相邻交换问题的基础。例如另一道经典题“使字符串平衡的最少交换次数”交换任意两个字符不一定是相邻其代价计算方式就完全不同。理解这种等价关系是灵活运用贪心思想的关键。5. 算法扩展与变式思考掌握了“An Easy Problem”的核心解法我们可以看看它的几种变式这有助于深化对贪心策略的理解。变式一移动‘0’而非‘1’。如果题目改为每次交换相邻字符求使所有‘0’连续的最少交换次数。解法完全一样吗是的完全对称。我们可以选择移动‘0’到连续位置也可以等价地认为移动‘1’因为字符串只有‘0’和‘1’。实际上使所有‘0’连续等价于使所有‘1’连续。所以算法无需改变计算‘1’的位置即可。变式二推广到多种字符。如果字符串不止‘0’和‘1’而是有多个字符比如‘a’, ‘b’, ‘c’要求使所有‘a’连续最少需要多少次相邻交换解法依然相同。我们只关心‘a’的位置将其视为“1”其他字符视为“0”问题就规约到了原问题。这是因为我们只移动‘a’而其他字符的相对顺序在交换过程中会被打乱但这不影响我们的目标——只要‘a’连续就行。代价计算依然只依赖于‘a’的初始位置和目标位置。变式三求最终连续段的可能位置。题目可能不仅要求最小交换次数还要求输出所有可能的最优连续段起始位置L。根据之前的分析当m为奇数时中位数唯一所以L唯一。当m为偶数时最优的L可以是b[m//2 - 1]到b[m//2]之间的任意整数。但L必须是整数且要保证连续段[L, Lm-1]在字符串范围内。所以我们需要找出这个区间内所有合法的整数L。计算方法是设low b[m//2 - 1],high b[m//2]则所有满足max(0, low) L min(n-m, high)的整数L都是最优解。这里n-m是为了保证Lm-1 n-1。变式四数据范围极大时的优化。如果字符串长度n高达10^9但‘1’的个数m只有10^5我们无法存储整个字符串。这时输入可能会给出‘1’的位置列表。我们的算法本身只依赖于pos数组所以完全可以处理。时间复杂度依然是O(m log m)。如果m也非常大比如10^7排序可能成为瓶颈。这时我们可以用线性时间选择算法如快速选择来找到中位数将时间复杂度降至O(m)。但一般情况下基于比较的排序已经足够高效且代码简洁。通过这道“简单问题”我们深入实践了贪心算法中“数学建模结论应用”的高阶玩法。它提醒我们很多看似需要复杂模拟或动态规划的问题经过巧妙的转化可以变成一个有经典结论的数学问题。核心在于训练自己将问题抽象化的能力以及熟悉一些基本的优化模型如中位数最小化绝对值和。下次再遇到类似“通过相邻交换使相同元素聚集”的问题你就能一眼看穿其本质快速给出优雅而高效的解法了。

相关新闻

MySQL数据库与表操作全指南:从基础到实战

MySQL数据库与表操作全指南:从基础到实战

1. MySQL数据库基础操作全指南作为关系型数据库的经典代表,MySQL在各类应用系统中扮演着重要角色。今天我将结合多年DBA经验,详细梳理MySQL中库与表的核心操作要点,这些技能无论是开发人员还是运维工程师都需要熟练掌握。2. 数据库操作详解2.…

2026/8/8 3:29:22 阅读更多 →
修复损坏的C64 D64磁盘映像:从原理到实践,让复古游戏重获新生

修复损坏的C64 D64磁盘映像:从原理到实践,让复古游戏重获新生

在 8 位计算机的黄金时代,Commodore 64 以其强大的音画表现和庞大的软件库,成为了无数玩家的启蒙机器。其中,STG(射击游戏)类型更是涌现了大量经典作品,它们以有限的硬件资源,创造出了令人惊叹的…

2026/8/8 1:40:25 阅读更多 →
从 push 到上线 10 秒:手把手搭一条 Facebook 风格的 CI/CD 流水线

从 push 到上线 10 秒:手把手搭一条 Facebook 风格的 CI/CD 流水线

从 push 到上线 10 秒:手把手搭一条 Facebook 风格的 CI/CD 流水线本文是《研发效能实战》系列第三篇。参考极客时间《研发效能》课程第 5、6 讲(代码入库前 Facebook 如何让开发人员聚焦于开发;代码入库到产品上线的 CI/CD)&…

2026/8/8 10:31:47 阅读更多 →

最新新闻

SEIRS+高级功能:测试、追踪与隔离干预模拟全攻略

SEIRS+高级功能:测试、追踪与隔离干预模拟全攻略

SEIRS高级功能:测试、追踪与隔离干预模拟全攻略 【免费下载链接】seirsplus Models of SEIRS epidemic dynamics with extensions, including network-structured populations, testing, contact tracing, and social distancing. 项目地址: https://gitcode.com/…

2026/8/8 20:10:22 阅读更多 →
Bilidown:专业级B站视频下载工具,一键收藏你喜爱的所有内容

Bilidown:专业级B站视频下载工具,一键收藏你喜爱的所有内容

Bilidown:专业级B站视频下载工具,一键收藏你喜爱的所有内容 【免费下载链接】bilidown 哔哩哔哩视频解析下载工具,支持 8K 视频、Hi-Res 音频、杜比视界下载、批量解析,可扫码登录,常驻托盘。 项目地址: https://git…

2026/8/8 20:10:22 阅读更多 →
终极指南:如何利用自动更新的纯真IP库实现精准IP地址定位

终极指南:如何利用自动更新的纯真IP库实现精准IP地址定位

终极指南:如何利用自动更新的纯真IP库实现精准IP地址定位 【免费下载链接】qqwry.dat 自动更新的纯真ip库,每天自动更新 项目地址: https://gitcode.com/gh_mirrors/qqwr/qqwry.dat 你是否曾经在开发网络应用时,为IP地址定位的准确性而…

2026/8/8 20:10:22 阅读更多 →
LunaTranslator游戏翻译工具:3步实现视觉小说实时翻译终极指南

LunaTranslator游戏翻译工具:3步实现视觉小说实时翻译终极指南

LunaTranslator游戏翻译工具:3步实现视觉小说实时翻译终极指南 【免费下载链接】LunaTranslator 视觉小说翻译器 / Visual Novel Translator 项目地址: https://gitcode.com/GitHub_Trending/lu/LunaTranslator LunaTranslator是一款功能强大的视觉小说实时翻…

2026/8/8 20:10:22 阅读更多 →
为什么选择UFM-Refine-336?CMU团队打造的高效视觉匹配模型深度测评

为什么选择UFM-Refine-336?CMU团队打造的高效视觉匹配模型深度测评

解锁Go语言并发编程:Goroutine与Channel的终极指南 【免费下载链接】go The Go programming language 项目地址: https://gitcode.com/GitHub_Trending/go/go Go语言以其卓越的并发编程能力脱颖而出,而Goroutine和Channel正是这一能力的核心所在。…

2026/8/8 20:10:22 阅读更多 →
Asspp 终极指南:如何轻松管理多地区App Store账户和应用

Asspp 终极指南:如何轻松管理多地区App Store账户和应用

Asspp 终极指南:如何轻松管理多地区App Store账户和应用 【免费下载链接】Asspp The App Store for your multi-account eco system. 项目地址: https://gitcode.com/gh_mirrors/as/Asspp Asspp是一款专为多账户生态系统设计的App Store管理工具,…

2026/8/8 20:09:21 阅读更多 →

日新闻

AI多智能体时代来临,读懂MCP与A2A架构,抢占企业数字化新风口

AI多智能体时代来临,读懂MCP与A2A架构,抢占企业数字化新风口

当下AI应用飞速普及,无数企业下场搭建智能体系统,可落地阶段难题接踵而至:上下文无限堆积频繁爆栈、AI工具调用准确率低下、Token成本居高不下、企业数据权限混乱暗藏安全隐患……很多团队卡在架构搭建环节,空有前沿技术概念&…

2026/8/8 0:00:07 阅读更多 →
PHP二维码生成终极指南:用chillerlan/php-qrcode打造专业级二维码

PHP二维码生成终极指南:用chillerlan/php-qrcode打造专业级二维码

PHP二维码生成终极指南:用chillerlan/php-qrcode打造专业级二维码 【免费下载链接】php-qrcode A PHP QR Code generator and reader with a user-friendly API. 项目地址: https://gitcode.com/gh_mirrors/ph/php-qrcode 在当今数字时代,二维码已…

2026/8/8 0:00:08 阅读更多 →
UniApp微信小程序隐私保护组件开发:从原理到实战

UniApp微信小程序隐私保护组件开发:从原理到实战

1. 项目缘起:为什么我们需要一个隐私保护通用组件?最近在维护一个基于uniapp开发的微信小程序矩阵时,我遇到了一个非常棘手的问题。随着平台对用户隐私保护的要求越来越严格,几乎每一个新版本发布,或者在某些特定机型&…

2026/8/8 0:00:08 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/8 17:02:43 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/8 8:58:26 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/7 23:24:08 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/8 17:02:44 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/7 23:54:54 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/8 17:02:44 阅读更多 →