ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LeetCode 1006 Clumsy Factorial 笨阶乘:Go 分组模拟实现与源码解析

LeetCode 1006 Clumsy Factorial 笨阶乘:Go 分组模拟实现与源码解析 LeetCode 1006 Clumsy Factorial 笨阶乘Go 分组模拟实现与源码解析【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go导读本文以 LeetCode 1006「笨阶乘Clumsy Factorial」为核心结合 LeetCode-Go 仓库中该题的 Go 实现系统讲解在无括号表达式中如何按先乘除、后加减的运算优先级进行分组模拟。文章将逐行剖析res/tmp/flag三个状态变量的设计逻辑说明 Go 整数除法与题意地板除法的一致性并给出N1等边界条件的处理与可直接运行的测试验证方法。读完本文你将掌握一类固定运算符轮换表达式的 O(N) 模拟套路并能独立理解并复现仓库中该题解法的每一个分支。一、题目描述什么是笨阶乘通常正整数n的阶乘是所有小于或等于n的正整数的乘积例如factorial(10) 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1。而笨阶乘clumsy factorial则不同我们依然使用递减的整数序列但把乘法运算替换为一组固定轮换的操作符——乘法*、除法/、加法和减法-按此顺序循环出现。例如clumsy(10) 10 * 9 / 8 7 - 6 * 5 / 4 3 - 2 * 1这些运算依然遵循通常的算术运算顺序任何加、减步骤之前先执行所有的乘法和除法步骤并且乘除法按从左到右的顺序处理。此外题目要求的除法是地板除法floor division例如10 * 9 / 8等于11而不是11.25或12这保证结果是整数。题目要求给定一个整数N返回N的笨阶乘。官方示例示例 1输入4 输出7 解释7 4 * 3 / 2 1示例 2输入10 输出12 解释12 10 * 9 / 8 7 - 6 * 5 / 4 3 - 2 * 1约束条件1 N 10000答案保证落在 32 位有符号整数范围内-2^31 answer 2^31 - 1以上题目描述与示例完整继承自 题目文档。二、解题思路把减法看成带负号的加法由于整个表达式没有括号根据算术优先级可以确定如下处理策略先乘除、后加减每个乘除片段形如a * b / c必须先算出完整结果再参与加减每 4 个数为一组操作符按*、/、、-循环即第 1 个数与第 2 个数之间是*第 2 个数与第 3 个数之间是/第 3 个数与第 4 个数之间是第 4 个数与第 5 个数之间是-然后新一轮循环从*重新开始。观察clumsy(10)的结构可以发现整个表达式可以拆解为若干乘除组的加减拼接clumsy(10) (10 * 9 / 8) 7 - (6 * 5 / 4) 3 - (2 * 1) 11 7 - 7 3 - 2 12这里有一个非常关键的等价变换减法也可以看成是加法只是带负号的加法。即- (6 * 5 / 4)等价于加上-(6 * 5 / 4)。因此我们可以在遇到减法时把负号提前到下一个乘除组的第一个数字上让整个后续组带上负号参与累加。这正是仓库实现中tmp -1这一技巧的来源。三、Go 源码实现与逐行解析仓库中该题的完整源码位于 leetcode/1006.Clumsy-Factorial 目录下的1006. Clumsy Factorial.go文件package leetcode func clumsy(N int) int { res, count, tmp, flag : 0, 1, N, false for i : N - 1; i 0; i-- { count count % 4 switch count { case 1: tmp tmp * i case 2: tmp tmp / i case 3: res res tmp flag true tmp -1 res res i case 0: flag false tmp tmp * (i) } count } if !flag { res res tmp } return res }3.1 四个状态变量的语义变量作用res累计的最终结果只累加已经完整算完的乘除组以及加减号后面的单个数字count操作计数器从 1 开始每次循环自增通过count % 4映射到当前操作位tmp正在构建的乘除组结果可能带负号暂存尚未累加到res的片段flag标记循环结束时tmp中是否还有残留的未累加乘除组需要收尾3.2 状态机的四个分支count % 4的值对应表达式中的操作位count % 4 1→ 乘法把当前数字乘进tmptmp tmp * icount % 4 2→ 除法把当前数字除进tmptmp tmp / i乘除组构建完毕count % 4 3→ 加法先把完整的乘除组tmp累加进res再把当前数字i以正号累加进res随后将flag置为true、tmp重置为-1为下一组减法组预埋负号count % 4 0→ 减法组起点把flag置为false并将tmp此时为-1乘以当前数字使整个后续乘除组带上负号。循环结束后若flag false说明tmp中还残留着一个尚未累加的乘除组循环恰好在乘除步骤处终止需要执行res res tmp完成收尾若flag true说明最后一次处理的是操作所有片段都已累加完毕无需再处理。3.3 N 10 的逐步推演以clumsy(10)为例完整推演一次状态变化与期望输出12对照步骤icount%4对应操作tmpresflag初始———100false191*tmp 10 * 9900false282/tmp 90 / 8110false373res 11再res 7-118true460减法组起点tmp -1 * 6-618false551*tmp -6 * 5-3018false642/tmp -30 / 4-718false733res -7再res 3-114true820减法组起点tmp -1 * 2-214false911*tmp -2 * 1-214false收尾——flag falseres tmp—12—最终返回12与题目示例完全一致。可以看到第 3 步与第 7 步的分支实际上一石二鸟既累加了当前的正数又把tmp预置为-1让紧随其后的减法组整体带负号。四、关键细节flag 的语义与边界情况4.1 为什么需要 flagflag存在的意义在于区分循环结束时是否还有未收尾的乘除组。因为N不一定是 4 的倍数循环可能在任何操作位终止若最后处理的是count%4 3则tmp已被重置为-1且所有片段都已进res此时flag true无需收尾若最后处理的是*、/或减法组起点count%4 0/1/2则tmp中残留着半成品乘除组此时flag false必须在循环结束后把tmp累加进res。4.2 边界情况逐一验证N 1循环体一次都不执行i从 0 开始i 0为假。此时tmp 1、flag false收尾逻辑res tmp使结果正确返回1。这正是源码中flag初始化为false的原因——这一点非常重要详见 4.3 节。N 2只执行一次乘法tmp 2 * 1 2循环结束flag falseres 0 2 2。而clumsy(2) 2 * 1 2✓。N 3tmp 3 * 2 6tmp 6 / 1 6收尾后res 6。而clumsy(3) 3 * 2 / 1 6✓。N 4推演可得res 6、tmp -1、flag true收尾被跳过返回7。而clumsy(4) 4 * 3 / 2 1 7✓。N 5tmp依次经历5*420、20/362后res 8减法组tmp -1*1 -1收尾后res 7。而clumsy(5) 5 * 4 / 3 2 - 1 7✓。4.3 一处值得注意的实现差异对比 题目文档 中的示例代码与仓库实际源码可以发现README 示例中flag初始值为true而源码文件中初始值为false。这一差异直接影响N 1的边界结果README 示例代码flag初始为true循环不执行!flag为假直接返回res 0结果是错误的clumsy(1)应为1仓库源码flag初始为false收尾逻辑生效正确返回1。从源码结构看这是实现中对边界条件的一处修正。这也提醒读者阅读开源题解时应以仓库内最终提交的源码为准并主动补充边界测试用例。五、Go 整数除法与地板除法的一致性题目明确要求使用地板除法floor division例如10 * 9 / 8 11。Go 语言中两个int做/运算得到的是**向零截断truncation toward zero**的整数除法。这两种语义在本实现中是等价的原因分两种情况情形一正数除法。当被除数和除数都为正数时向零截断与向下取整结果一致。例如90 / 8 1111.25向下取整与向零截断均为11。表达式中所有正数乘除组都满足这一条件。情形二带负号的乘除组。代码把数学意义上的-(a * b / c)实现为(-a) * b / c。由于 Go 整数除法向零截断对任意正整数x、c恒有(-x) / c -(x / c)而正数的x / c正是地板除法结果。因此该变换与原题意严格等价。例如-(6 * 5 / 4) → 代码实现为 (-6) * 5 / 4 -30 / 4 -7 - (30 / 4) → 数学上 -(7) -7两者一致这正是tmp可以放心携带负号继续做乘除运算的原因。若使用其他语言如向负无穷取整的语言需要额外留意这一语义差异。六、测试用例与运行验证仓库为该题提供了配套单元测试位于 leetcode/1006.Clumsy-Factorial/1006. Clumsy Factorial_test.go。6.1 测试结构测试采用与仓库其他题目一致的参数-答案结构体模式type question1006 struct { para1006 ans1006 } type para1006 struct { N int } type ans1006 struct { one int } func Test_Problem1006(t *testing.T) { qs : []question1006{ {para1006{4}, ans1006{7}}, {para1006{10}, ans1006{12}}, {para1006{100}, ans1006{101}}, } // ... for _, q : range qs { _, p : q.ans1006, q.para1006 fmt.Printf(【input】:%v 【output】:%v\n, p, clumsy(p.N)) } }6.2 测试用例与期望输出输入N期望输出对应表达式474 * 3 / 2 1101210 * 9 / 8 7 - 6 * 5 / 4 3 - 2 * 1100101—其中N 100 → 101是官方示例之外补充的大数据用例用于验证大规模输入下累加结果仍正确同时印证了题目答案在 32 位整数范围内的约束。6.3 如何运行在仓库根目录模块名为github.com/halfrost/LeetCode-Gogo.mod声明go 1.19执行go test -v ./leetcode/ -run Test_Problem1006即可看到三个用例的输入输出打印。读者也可以在本机将N 1、N 2、N 3等边界用例追加到qs中验证第四节中的边界推演结论。七、复杂度分析与优化讨论7.1 时间复杂度与空间复杂度时间复杂度O(N)。循环从N-1遍历到1共执行N-1次每次只做常数次算术运算与分支判断空间复杂度O(1)。仅使用res、count、tmp、flag四个标量变量无任何与N相关的额外存储。在1 N 10000的约束下O(N) 的模拟方案在时间与空间上都非常充裕。7.2 通用思路栈模拟对于更一般的无括号、含乘除优先级的表达式求值标准做法是使用栈遇到乘除操作时弹出栈顶计算后再压回遇到加减时直接入栈减法入负值最后求和。本题之所以不需要栈是因为操作符按固定周期轮换、每组的因子数量固定为 3tmp一个变量即可扮演栈顶的角色。从源码结构看仓库选择的是针对本题结构的轻量模拟方案。7.3 周期规律的推断由于操作符以 4 为周期轮换可以推断对足够大的Nclumsy(N)的结果与N之间存在以 8 为周期的规律测试用例N 100 → 101即为典型一例。理论上可以据此推导 O(1) 的闭式公式但小规模N如N 4会与周期规律产生偏差需要额外特判。仓库实现选择了普适的 O(N) 模拟避免了闭式公式在边界处的推导与特判成本可读性与正确性更易保证。八、总结LeetCode 1006「笨阶乘」是一道考察运算优先级处理与状态机设计的经典模拟题。本文围绕 LeetCode-Go 仓库的 Go 实现完整还原了题目的定义、示例与约束逐行解析了res/tmp/flag三个变量的协同逻辑验证了 Go 整数除法与地板除法在本实现中的语义一致性并给出了边界用例与测试运行方法。理解这版实现后你可以轻松应对同类固定操作符轮换 无括号表达式求值的问题也能更自信地阅读与评判开源题解代码。相关文件题目文档README.md题目描述、中文大意、解题思路与示例代码源码实现1006. Clumsy Factorial.goclumsy函数的最终实现单元测试1006. Clumsy Factorial_test.go三个用例的表格驱动测试go.mod仓库模块定义与 Go 版本go 1.19【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表