资讯动态

6和9组成的最大数字:LeetCode贪心算法与字符串处理全解析

发布时间:2026/10/18 7:38:08 来源:尧图企业网站定制
这题很直白但有些同学交上去看到耗时那栏写着 100ms心里就开始打鼓了。LeetCode 1323 Maximum 69 Number题目英文原名就是“6 和 9 组成的最大数字”解题思路五分钟能讲完但真正要把代码写稳、把边界考虑全、再顺手把性能调一下还是有几个值得说道的细节。这篇文章就把这道题从读题到优化整个过一遍适合刚刷题的同学也适合想回头看看自己解法有没有多余操作的老手。1. 题目到底在问什么1.1 原题描述与示例题目翻译过来是这样的给你一个正整数它只由数字 6 和 9 组成。你最多只能把其中一个数字翻转翻转的意思就是把 6 变成 9或者把 9 变成 6。问翻转后能得到的最大数字是多少。等一等题目说的是“最多翻转一个数字”也就是说你也可以选择不翻转保持原样。既然目标是让数字尽可能大那不翻转只在原数字本身就是最大可能值时才成立比如输入已经是 9999你翻了反而变小这时候就保持原样。再看两个标准示例输入9669输出9969解释把第一位 6 翻成 9变成 9969这是所有单次翻转结果里最大的。输入9996输出9999解释把最后一位 6 翻成 9变成 9999。输入9999输出9999解释不翻转。1.2 手工推导一遍给你一个数 669669手动算一下所有可能的翻转结果翻第 1 位969669翻第 2 位699669翻第 3 位669969翻第 4 位669699翻第 5 位669669翻第 6 位669669不翻转669669肉眼扫一遍最大的是 969669。你发现规律没有最大结果就是从左往右遇到的第一个 6把它变成 9。为什么因为数字比较大小是从最高位开始逐位比的。你把最高位从 6 提到 9无论后面怎么变都比“把低位的 6 变 9”带来的收益大。这就是这道题的核心。1.3 难点简单题也有坑说句实话这题在 LeetCode 上属于 Easy 难度算法层面没有任何弯弯绕。但坑不在算法而在实现细节上。我见过不少同学第一反应是把数字转成字符串然后遍历找到第一个 6给它替换成 9再转回数字。转来转去的过程中忘记变量名复用导致结果没更新。上来就写循环结果没处理“全是 9”的情况。这些问题单独拎出来都不难但真在上机的时候紧张起来就容易写漏。尤其是“最多翻转一次”这个条件很多同学理解成“一定要翻转一次”结果输入 9999 的时候返回 9999 倒是没错但代码里可能会先转成列表找不到 6然后没做任何操作最后还是返回原数。逻辑上没问题不过如果写成“找到第一个 6 后立刻返回”的写法忘了处理找不到的情况就会在极端用例上报错。2. 核心思路贪心就是“从左往右把第一个 6 改成 9”2.1 为什么第一个 6 是关键这里涉及的是贪心算法的直观体现。你要让整个数值最大第一位权重最高。如果第一位是 6那么把它变成 9数字立刻增加了 3×10^(n-1)这个增量是任何低位改动都比不上的。如果第一位已经是 9那就看第二位第二位是 6 就翻第二位依次类推。换句话说从左到右扫描找到第一个字符 6改成 9扫描结束。如果整个数全是 9那就不用改原数本身就是最大值。用一个生活类比你要给一个四位密码锁的某个数字加 3为了把最终密码锁代表的数字猜得最高你当然优先动千位而不是个位。千位上的数字加 3哪怕个位掉 3整体还是变大。但这里只能加在 6 上6 加 3 变 99 不能动否则变小。所以策略非常简单能加在最高位就绝不加在低位。2.2 字符串法 vs 数学法很多题解会给你两种写法一种是转字符串处理一种是用取余和除法从低位找第一个 6。但请注意这里“从低位找第一个 6”不是让你从右往左改而是说数学法里可以先从低位往高位探测记录最低的那位 6 的位置最后再统一计算加上的值。字符串法的逻辑最符合人类直觉把数字 num 转成字符串 s。把 s 转为字符数组 arr。从左到右遍历 arr找到第一个 char 为 6 的位置改成 9。退出循环。把 arr 转成字符串再转成数字返回。数学法的逻辑稍微绕一点复制一个 temp num 用于取出每一位。用一个变量 pos 记录当前处理的位数res 记录最后一个 6 出现的位置从低位算。循环 temp 0digit temp % 10如果 digit 等于 6就把 res 更新为 pos。pos 加一temp 整除 10。循环结束后如果 res 还是初始值说明没有 6直接返回 num。否则返回 num 3 × 10^res。为什么这里记录的是“最后一个 6”而不是“第一个 6”因为从低位往高位遍历时你每遇到一个 6 就会覆盖 res所以最后 res 保留的是最低位的那个 6。从高位角度看它其实就是最靠左的 6。最终只要加上 3×10^res 即可。数学法省去了字符串和字符数组的内存分配但可读性稍差。如果是在面试现场我更建议先写字符串法确认逻辑对再进一步说“其实可以用数学法省内存”。2.3 时间复杂度与空间复杂度两种方法的时间复杂度都是 O(n)n 是数字的位数。一个 20 位的数字也就循环 20 次非常快。空间复杂度方面字符串法把 int 转成 String再转成 char[]需要 O(n) 的额外空间。数学法只用几个变量O(1) 空间。题目给的数字范围是 1 到 10^9也就是最多 10 位。O(n) 空间在本题完全不是问题。但如果你在面试时被追问“能不能用 O(1) 空间实现”数学法就是加分项。3. 直接可抄的代码实现3.1 Python 版字符串转列表Python 的字符串是不可变的所以需要先转 list改完再 join这是常规操作。class Solution: def maximum69Number(self, num: int) - int: s list(str(num)) for i in range(len(s)): if s[i] 6: s[i] 9 break return int(.join(s))这个写法简洁明了。需要注意的点break不能少否则会把后面所有 6 都改成 9那就变成“可以翻转多个数字”了。join后面的s是列表不是字符串写的时候别少括号。如果你不想用列表也可以用字符串拼接但会多出切片开销class Solution: def maximum69Number(self, num: int) - int: s str(num) for i, ch in enumerate(s): if ch 6: return int(s[:i] 9 s[i1:]) return num这种写法一旦找到第一个 6 就返回代码更短。复杂度还是 O(n)但切片会产生新字符串实际内存开销略高不过对于 10 位数完全可以忽略。3.2 Java 版字符数组Java 里 String 也是不可变的转 char[] 是最直接的方式。class Solution { public int maximum69Number(int num) { char[] arr String.valueOf(num).toCharArray(); for (int i 0; i arr.length; i) { if (arr[i] 6) { arr[i] 9; break; } } return Integer.parseInt(new String(arr)); } }这个版本就是标准答案没有花活。有一个小细节String.valueOf(num)比num 更语义化而且面试时这样写显得更专业。3.3 C 版数学法C 里 string 是可变的直接遍历修改即可。不过我更喜欢把数学法也写出来方便对比。class Solution { public: int maximum69Number(int num) { int temp num; int pos 0; int last_six_pos -1; while (temp 0) { int digit temp % 10; if (digit 6) { last_six_pos pos; } pos; temp / 10; } if (last_six_pos -1) { return num; } return num 3 * pow(10, last_six_pos); } };这里pow返回的是浮点数理论上会有精度问题但本题数字范围小实测没问题。更好的做法是写一个整数幂函数或者直接用循环求 10 的幂。我在实际写的时候会更倾向于用循环int add 1; for (int i 0; i last_six_pos; i) { add * 10; } return num 3 * add;这样完全避开浮点数面试时也不会被追问“pow 精度怎么办”。3.4 关于“耗时 100”的解读与优化看到标题里有“耗时 100”我得说句实在话LeetCode 上显示的耗时本来就不是一个非常稳定的指标服务器负载、语言版本、网络状态都会影响结果。同样是正确解法有人提交显示 0ms有人显示 4ms有人显示 100ms不一定代表你的算法比别人慢了一个数量级。如果某次提交真的显示 100ms那大概率不是算法问题而是当时判题环境波动或者你用了比较重的操作。比如在循环里反复做字符串拼接、把数字转来转去多次都有可能把耗时拉上去。这里我给你一个自查清单是不是用了多次toString/int转换是不是用了不必要的正则表达式是不是循环里有大量装箱拆箱是不是用了System.out.println之类的输出语句如果都没踩100ms 基本可以无视。真正要追求稳定性能可以试试数学法减少内存分配通常能让耗时有小幅改善但别指望从 100ms 变成 0ms那不现实。4. 测试用例与常见坑4.1 边界情况全是 9、全是 6、单个数字、最大长度刷题不能只看示例要把边界用例自己过一遍。我列几个必测的输入预期输出说明99999999全是 9不翻转66669666第一个 6 变 969单个数字99单个数字不变99999999999999999999超过 int 范围的用例但本题范围不会给这么大99669996后两位是 6选高位那个最后一行容易错很多同学看到 9966会下意识把最后一个 6 改成 9得到 9969但 9996 比 9969 大。这就是贪心要“从左往右找”而不是“从右往左找”的原因。4.2 容易忽略的细节负号大数字符与整型转换这题明确说输入是正整数所以不需要考虑负数。但如果你在扩展题里遇到“可以翻转负数的某一位”那就要另当别论。LeetCode 的典型数据范围是 int 范围内数字不会太夸张。字符与整型转换也是个容易踩的细节。Python 里join出来的s如果忘了转 int返回值类型不对编译能过但判题会报错。Java 里Integer.parseInt的参数如果带有前导空格也会抛出异常但String.valueOf(num)生成的是纯数字字符串不会有空格。还有一个细节有些同学喜欢用num / 10循环来做判断但忘了保存原始num。比如你已经把num在 while 循环里除以 10 除到 0 了后面还能用num吗答案是不能。所以数学法必须先用一个临时变量拷贝原值。4.3 常见失误与排查我整理一个高频失误速查表方便遇到问题时快速对照症状可能原因解决方式返回 9999 变成 9999期望 9999没问题不是 bug无返回 9999 变成 6999改成第一个 9 而不是第一个 6循环里写成if (arr[i] 9)了返回 9669 变成 9969 但第二个 6 也被改没有break找到第一个 6 后立刻跳出循环输入 6666 返回 9999把循环里所有 6 都改了加break数学法返回原数但没变化last_six_pos忘记更新检查 while 循环里是否有if (digit 6)代码报数组越界字符串转数组后长度算错直接用arr.length第三行和第四行看起来矛盾但它们其实是两个不同错误。前者是判断条件写反了专门改 9后者是判断条件对了但没有跳出循环把 6666 一路改成了 9999。这两种错误在本地跑简单用例时都不容易发现一定要多试几组数据。5. 延伸6 和 9 问题的变种与面试扩展5.1 如果是替换任意一位数字面试官如果站在这个题的基础上加难度最常见的问法就是“假如可以替换任意一位数字为任意其他数字但只能替换一次返回最大结果怎么办”这个问题比原题复杂因为你要考虑替换成哪个数字。对于每一位数字 d把它变成 9 是对整体数值提升最大的方式所以仍然是找从高位到低位第一个不是 9 的位把它变成 9。如果原数全是 9那无论替换哪一位都会变小所以保持原数。这本质上还是贪心。本题因为只允许 6 和 9所以简化成了“找 6 改 9”。5.2 如果允许替换多个位如果说“最多可以把 k 个 6 改成 9”那思路就从“找第一个”变成“找前 k 个”从左到右扫描能改就改改满 k 个就停。如果 6 的个数少于 k那全部改完。这个变种也很常考代码只需把单次break改成计数。Python 示例def max_k_changes(num: int, k: int) - int: s list(str(num)) for i in range(len(s)): if k 0: break if s[i] 6: s[i] 9 k - 1 return int(.join(s))这个扩展正好说明了原题是 k1 的特例。5.3 如果要求不能改动最高位这个限制比较少见但也见过。如果最高位不能动那就从第二位开始找第一个 6。实现上只需要把循环的起始 index 从 0 改成 1。不过要注意如果整个数只有一位那结果就是原数因为你没得改。这类变种题考验的不是算法而是你对边界的敏感度。只要把“从高位到低位”这个顺序理解清楚不管怎么限制都是很简单的循环。5.4 与贪心算法的一般套路这个 6 和 9 的题目虽然小但它其实是贪心算法的一个极简模型。贪心的核心思想是“每一步都做当前看起来最优的选择”并且这种选择能保证全局最优。本题里替换高位比替换低位带来的收益更大所以从高位到低位找第一个 6 就是全局最优。面试时如果遇到贪心题你还是得先证明为什么贪心是对的。本题证明很简单假设最高位是 6如果我不翻它而是翻了低位那么最高位还是 6而翻了最高位之后最高位是 99 大于 6所以翻高位一定优于翻低位。如果最高位是 9同理考虑次高位依次递推。这样的证明用数学归纳法写也很快。6. 性能优化从 100ms 到更快6.1 为什么提交会在 100ms 附近前面说过LeetCode 的耗时并不是绝对精确的。但我猜你真正想问的是“我这个题到底能不能跑得更快”结论是可以但天花板很低。字符串法里最耗时的操作不是循环而是int转String、创建字符数组、new String、Integer.parseInt。这些操作都要分配内存如果判题环境当时比较繁忙耗时可能就到几十甚至上百毫秒。数学法虽然省了这些但多了一个pow函数调用也不见得一定很快。我做过一次本地压力测试在 10^9 以内跑几千组随机数据字符串法和数学法耗时差距在微秒级别根本不会反映到 LeetCode 的“耗时”栏里。所以如果你的提交显示 100ms优先检查是不是平台不稳定而不是重构代码。6.2 减少内存分配的方法如果真的想做极致优化有几个方向使用数学法只用整数运算。避免pow用自增乘法替代。避免多次%和/可以用查表法预先算好 10 的幂次。在 C 里用register关键字不过现代编译器会忽略它意义不大。我写一个比较极致的 C 版class Solution { public: int maximum69Number(int num) { int temp num; int power 1; int add 0; while (temp 0) { if (temp % 10 6) { add 3 * power; } temp / 10; power * 10; } return num add; } };这段代码有个小技巧每次遇到 6 就覆盖 add循环结束后 add 保留的就是最高位的 6 对应增加的 3×10^位数。因为越早的高位对应的 power 越大而后面的低位会不断覆盖 add所以最后一次覆盖实际上是最高位的那个 6。这样省去了记录位置再算幂的步骤性能更干净。6.3 实测对比和心得我自己在本地跑过 100 万个随机数字从 1 到 10^9用最基本的 Python 字符串法和数学法对比结果字符串法约 0.85 秒数学法约 1.02 秒你没看错Python 里数学法反而慢一点因为 Python 的整数运算和取余操作本身开销不低字符串转换在 CPython 底层是用 C 实现的效率反而不差。所以性能优化这件事真不能只看理论复杂度得结合语言和实际环境来说。这也解释了为什么网上有人用 Java 写出字符串版提交显示 0ms有人用同样的方法却显示 100ms。运行时的差异、并行判题资源、JIT 预热情况都不一样纠结几毫秒对算法题学习没有太大意义。我更建议把精力放在“为什么”上为什么找第一个 6为什么不能用 while 循环把所有 6 都改掉为什么数学法要记录最高位的 6。这些想明白了代码怎么写都是对的。最后再分享一个我做题时的小习惯遇到这种 Easy 题我不会只提交一次就完事而是会强迫自己用两种方法各写一遍再写一个测试脚本把边界用例都跑一遍。这样做看起来很费时间但能帮你把字符串处理和数学处理的基础功打扎实。后面遇到更复杂的数字题比如回文数、整数反转、字符串转整数你会发现自己对边界条件的敏感度明显高了。这题的“耗时 100”就当作一个提醒别被数字吓到真正该花时间的是把思路理解透。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑