ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

codeforces-go 题解精读:二进制数组全部变为 1 的最小反转次数——贪心唯一性与正确性证明(LeetCode 双周赛 133 B)

codeforces-go 题解精读:二进制数组全部变为 1 的最小反转次数——贪心唯一性与正确性证明(LeetCode 双周赛 133 B) 科学计算【免费下载链接】codeforces-go算法竞赛模板库 by 灵茶山艾府 项目地址https://gitcode.com/GitHub_Trending/co/codeforces-go点击查看免费下载导读本题是 LeetCode 双周赛 133 的 B 题Minimum Operations to Make Binary Array Elements Equal to One I同时也是 codeforces-go 仓库中以题解文档 源码 测试数据三联件沉淀的一类经典贪心问题每次操作反转连续三个位置求使 01 数组全变为 1 的最小操作次数。读完本文你将掌握如何从第一个位置是否必须操作出发构造唯一操作序列、为什么贪心在本题必然正确可交换性 至多一次 唯一性三段论证、七种主流语言的等价实现以及把区间长度 3 推广到任意 k对应 CF1955E时如何使用差分数组做到与 k 无关的 O(n) 做法。题目回顾与问题建模给定一个 01 数组nums每次操作可以选择一个下标i ∈ [0, n-3]把nums[i]、nums[i1]、nums[i2]三个位置全部反转即异或 1。目标返回把nums全变成 1 的最小操作次数如果无法做到返回-1。几个关键边界直觉操作是幂等的对同一个i操作两次等价于没操作操作覆盖的位置只有i, i1, i2三个彼此之间只与相邻下标重叠当n 3时不存在任何合法下标此时只有数组已经全为 1 才可行答案为 0否则为-1。贪心决策从必须操作到唯一的操作序列原文档给出的核心思路是按位置从左到右逐位决策讨论是否需要对i 0执行操作如果nums[0] 1不需要操作问题变成剩下n-1个数的子问题如果nums[0] 0一定要操作问题同样变成剩下n-1个数的子问题。接下来对i 1重复同样的判断处理方式与上一步完全相同。依此类推一直处理到i n-3。处理完毕后还剩下nums[n-2]和nums[n-1]两个位置——这两个数必须都等于 1否则无法达成题目要求返回-1。为什么遇到 0 就必须操作是安全的关键洞察在于处理到位置i时nums[i]的最终状态已经定型。因为任何操作j只会影响j, j1, j2三个位置当j i时影响的起点已经超过i无法再回头改变nums[i]而j i正是此刻的决策点。也就是说当前位置一旦被扫描过去就再也无法被后续操作修改因此它是决定操作与否的唯一依据。模拟过程可以写作对i 0..n-3若nums[i] 0执行操作反转i1、i2两个位置即可因为nums[i]不需要再看了操作次数加 1若nums[i] 1跳过。结束后检查nums[n-2]与nums[n-1]两者都为 1 则返回累计次数否则返回-1。正确性论证为什么从左到右贪心一定是对的原文档用四步问答把正确性讲得很透彻这里完整保留并展开问为什么这样做是对的答操作顺序不影响结果可交换性先操作i再操作ji ≠ j与先操作j再操作i的结果完全相同。因为每次操作只是对三个固定位置做异或 1异或运算是可交换、可结合的最终每个位置被反转的次数只取决于操作集合与执行顺序无关。既然顺序无关我们总可以把任何最优操作序列重排成从左到右执行。同一个下标至多操作一次对同一个i操作两次等于没有操作所以最优解中每个i的操作次数要么 0 次要么 1 次多余的偶数次可以删除奇数次可以合并为 1 次。从左到右的操作方式有且仅有一种结合上述两点在从左到右扫描的过程中遇到1一定不能操作操作了反而会引入多余的翻转且违反唯一性遇到0一定要操作否则这个位置永远无法变成 1所以操作序列被唯一确定。既然操作方式是唯一的只需模拟唯一性保证了贪心决策没有分叉直接按规则模拟整个扫描过程即可得到答案。问题目要求的最少体现在哪里答对同一个i至多操作一次就可以做到最少的操作次数。由于任意可行解都能重排成从左到右、每个下标至多一次的形态而满足该形态的操作序列又是唯一的因此这个唯一序列天然就是操作次数最少的可行解。复杂度分析时间复杂度O(nk)其中 n 是nums的长度k 3 是每次操作反转的元素个数由于 k 是常数实际表现为 O(n) 的单次扫描。空间复杂度O(1)全程原地修改数组只使用常数个变量。七种语言的等价实现原文档给出了 Python3、Java、C、C、Go、JavaScript、Rust 七个版本的实现逻辑完全一致全部继承如下class Solution: def minOperations(self, nums: List[int]) - int: ans 0 for i in range(len(nums) - 2): if nums[i] 0: # 必须操作 nums[i 1] ^ 1 nums[i 2] ^ 1 ans 1 return ans if nums[-2] and nums[-1] else -1class Solution { public int minOperations(int[] nums) { int n nums.length; int ans 0; for (int i 0; i n - 2; i) { if (nums[i] 0) { // 必须操作 nums[i 1] ^ 1; nums[i 2] ^ 1; ans; } } return nums[n - 2] ! 0 nums[n - 1] ! 0 ? ans : -1; } }class Solution { public: int minOperations(vectorint nums) { int n nums.size(); int ans 0; for (int i 0; i n - 2; i) { if (nums[i] 0) { // 必须操作 nums[i 1] ^ 1; nums[i 2] ^ 1; ans; } } return nums[n - 2] nums[n - 1] ? ans : -1; } };int minOperations(int* nums, int n) { int ans 0; for (int i 0; i n - 2; i) { if (nums[i] 0) { // 必须操作 nums[i 1] ^ 1; nums[i 2] ^ 1; ans; } } return nums[n - 2] nums[n - 1] ? ans : -1; }func minOperations(nums []int) (ans int) { n : len(nums) for i, x : range nums[:n-2] { if x 0 { // 必须操作 nums[i1] ^ 1 nums[i2] ^ 1 ans } } if nums[n-2] 0 || nums[n-1] 0 { return -1 } return }var minOperations function(nums) { const n nums.length; let ans 0; for (let i 0; i n - 2; i) { if (nums[i] 0) { // 必须操作 nums[i 1] ^ 1; nums[i 2] ^ 1; ans; } } return nums[n - 2] nums[n - 1] ? ans : -1; };impl Solution { pub fn min_operations(mut nums: Veci32) - i32 { let n nums.len(); let mut ans 0; for i in 0..n - 2 { if nums[i] 0 { // 必须操作 nums[i 1] ^ 1; nums[i 2] ^ 1; ans 1; } } if nums[n - 2] ! 0 nums[n - 1] ! 0 { ans } else { -1 } } }各实现中值得注意的实现细节Python 版用range(len(nums) - 2)控制扫描区间末尾用nums[-2] and nums[-1]做短路判断Go 版的循环体可以直接读nums[i]的当前值因为每次操作后nums[i1]、nums[i2]会立刻被就地更新下一次迭代读到的nums[i1]已经是应用了之前所有操作后的真实值——这正是当前值已定型的落地体现末尾两个位置无需进入循环它们可能被i n-4, n-3的操作修改但自己永远不能作为操作起点所以扫描结束后必须单独校验。仓库中的实现与实测验证本题在仓库中不是孤立的一页题解而是文档 源码 测试数据三件套题解文档leetcode/biweekly/133/b/README.md即本文主体Go 实现leetcode/biweekly/133/b/b.go与上文 Go 版代码逐字一致测试数据leetcode/biweekly/133/b/b.txt测试用例leetcode/biweekly/133/b/b_test.go文件头注明由 copypasta/template/leetcode/generator_test.go 自动生成。b_test.go 的核心只有一条调用func Test_b(t *testing.T) { if err : testutil.RunLeetCodeFuncWithFile(t, minOperations, b.txt, 0); err ! nil { t.Fatal(err) } }b.txt 中存放的是两组输入 → 期望输出数据[0,1,1,1,0,0] 3 [0,1,1,1] -1两组数据对应的模拟过程如下输入输出决策轨迹[0,1,1,1,0,0]3依次在i0、i1、i3执行操作[0,1,1,1]-1扫描后末尾两位为1,0无法达成以第一组为例逐步验证i0时nums[0]0翻转1,2得[1,0,0,1,0,0]i1时nums[1]0翻转2,3得[1,0,1,0,0,0]i2时nums[2]1跳过i3时nums[3]0翻转4,5得[1,0,1,0,1,1]末尾nums[4]1、nums[5]1返回3。测试机制testutil.RunLeetCodeFuncWithFile实现在 leetcode/testutil/leetcode.go读取 b.txt按每fNumIn fNumOut 2行一组切分样例交给RunLeetCodeFuncWithExamplesleetcode/testutil/leetcode.go逐组调用先通过反射把[0,1,1,1,0,0]解析为[]int入参、把3解析为期望输出再执行被测函数并比对实际结果每个用例以t.Run(Case N)子测试运行还内置了基于time.Timer的 TLE 检测。运行方式在仓库根目录执行go test ./leetcode/biweekly/133/b -run Test_b -v -count1注意本文撰写环境的 shell 中未检测到 Go 工具链go: not found上述命令适用于安装了 Go 1.x 的开发环境。边界情况与无法做到的判定本题判-1的唯一条件是扫描完[0, n-3]后nums[n-2]与nums[n-1]中存在 0。深层原因有二末尾两个位置永远不可能作为操作起点没有合法的i能覆盖它们之前的完整三连只能被i n-4或i n-3的操作翻转若扫描结束后它们仍为 0则没有任何操作还能改变它们问题无解。此外还有两类平凡情形值得注意输入全为 1循环一次也不触发末尾校验通过答案为0n 3不存在任何合法操作。此时若数组已全为 1 答案为0否则为-1。仓库 Go 实现中range nums[:n-2]在n2时自然为空切片、直接以末尾两位判断逻辑自洽测试数据集中在n 4的场景。思考题延伸把 3 推广到任意 kCF1955E原文档留下一道思考题把题目中的 3 替换成k1 k n能否想出一个与 k 无关的 O(n)做法思路分两步递进固定 k 的贪心依旧成立把翻三个位置换成翻k个连续位置后前面的三段论证完全不变——操作可交换、同一个起点至多一次、从左到右唯一。扫描i 0..n-k遇到nums[i] 0就翻转[i, ik-1]最后检查末尾k-1个位置。去掉翻转本身的 O(k) 开销朴素实现每次翻转要写k个位置总代价 O(nk)。要做到与 k 无关的 O(n)需要引入差分标记 懒更新计数用变量cur记录当前位置累积被翻转的次数奇偶性用差分数组diff记录每个操作在区间右端点1位置处的撤销标记。处理位置i时先cur diff[i]若(nums[i] cur) % 2 0即当前位置当前值为 0则执行一次区间翻转cur ^ 1懒标记生效diff[ik] ^ 1在区间外撤销ans。这样每次翻转是 O(1) 的整体 O(n)。这道思考题对应的正是 Codeforces 1955ELong Inversions外层枚举每个k内层套用上述固定k的 O(n) 检查总复杂度 O(n²)。理解本题从左到右唯一性是解开那道题的关键前置知识。归类与延伸这类贪心在竞赛题单中的位置原文档将本题归类于贪心与思维题单该分类下覆盖基本贪心策略、反悔贪心、区间贪心、字典序相关、数学与思维、脑筋急转弯、构造类问题。与本题同族的知识点还包括滑动窗口与双指针定长/不定长/单序列/双序列/三指针反转区间问题的兄弟模型常用数据结构差分把区间批量操作从 O(k) 摊到 O(1)正是上一节思考题的解法基石位运算异或 1 实现 0/1 翻转是本题操作的语言实现核心。如果你需要继续在仓库中精读同族题目可以重点关注 main/ 目录下按题号组织的 CF 题解以及 leetcode/ 目录下按周赛/双周赛组织的逐题目录——每一题通常都遵循README 题解 同名 Go 实现 文本测试数据 自动生成测试的同一套沉淀模式。一句话总结本题的价值不在于贪心策略本身而在于它示范了一套可复用的正确性证明框架——先证明操作可交换顺序无关、再证明每个决策点至多一次、最后推出操作序列唯一——这套三段论几乎可以原样迁移到所有区间反转、目标为全 1/全 0的翻转类问题含 CF1955E 的 k 推广版本上。赞分享科学计算【免费下载链接】codeforces-go算法竞赛模板库 by 灵茶山艾府 项目地址https://gitcode.com/GitHub_Trending/co/codeforces-go点击查看免费下载相关推荐Sails 框架下通过自定义 HTTP 中间件配置 P3P 隐私策略头P3P 兼容旧版 IE 应用实战指南Sails 框架下通过自定义 HTTP 中间件配置 P3P 隐私策略头P3P 兼容旧版 IE 应用实战指南 导读 本文讲解如何在 SailsNode.js科学计算循环数组周期归约 中位数贪心makeSubKSumEqual 最小操作次数题解codeforces-go 仓库 LeetCode 双周赛 101 C 题深度解析循环数组周期归约 中位数贪心makeSubKSumEqual 最小操作次数题解codeforces go 仓库 LeetCode 双周赛 101 C 题科学计算codeforces-go 仓库题解实战LeetCode 双周赛 116 第二题美丽二进制串最少修改次数codeforces go 仓库题解实战LeetCode 双周赛 116 第二题美丽二进制串最少修改次数 本题解源自 leetcode/biweekly/科学计算上一篇ncclient测试框架如何编写和运行单元测试与集成测试下一篇3种方案彻底解决Ant Design父子组件数据传递难题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表