简介:这份压缩包提供基于 Java 与 Python 双语实现的 lintcode 算法与数据结构题解,面向毕业设计准备者、算法初学者及求职备考的开发者,用于通过实际编码吃透经典题目与核心思想。资源按日期与题号分目录组织,每道题均配有思路说明、双语言实现两套代码,并给出算法思想、时间空间复杂度评估和数据结构选型建议;题目涵盖二分查找、字符串查找、第K大元素、中位数、爬楼梯、打劫房屋、最小路径和、超级丑数、摆动排序、硬币排成线等经典题型。全套共36个文件,包括12份Markdown题解文档、11个Python源码、11个Java源码及Git工程配置文件,压缩包仅17KB,轻量清晰,便于逐个专题查阅。已有46人学习该资源;对需要系统提升算法能力、准备毕业设计算法模块或面试刷题的读者而言,这是一份可双语对照、能直接运行验证的实用参考资料。
1. 从 LeetCode 转向 LintCode 的工程师,为什么需要一份 Java 和 Python 双实现代码包
很多从业者刷算法题只认 LeetCode,等到真正做 LintCode 上的算法笔试,或者准备 Java 面试题里的手撕环节,才发现两个平台的脾气完全不同。LintCode 的题目在输入方式、评测数据和边界条件上都有自己的习惯,网上能找到的答案往往只有一种语言,思路看懂了,换到自己的技术栈里又跑不通。这份基于 Java 和 Python 的分析、实现 LintCode 算法与数据结构的代码包,价值不在于多一份答案,而在于用 Python 快速验证思路、用 Java 压性能和内存边界的完整闭环。它适合三类人:准备 Java 岗面试的候选人、用 Python 入门但想补 Java 基础的数据结构与算法学习者,以及需要一套可复现工程结构来整理刷题代码的从业者。
2. 双语言实现的核心逻辑:同一道算法题在 Java 和 Python 里为什么“长”得不一样
拿到双实现代码包,先别急着跑,想清楚一件事:为什么同一道题要用两种语言写?我的理解是,Java 和 Python 在算法题上的分工几乎是天然的。Python 语法薄、迭代快,适合在草稿阶段验证动态规划的状态转移、回溯的剪枝条件;Java 类型严格、运行稳定,适合验证大数据量下的耗时和内存边界。说得直白一点,Python 帮你确认“想对了没有”,Java 帮你确认“跑不跑得过”。这也是我日常刷题的工作流:先在 Python 里把递归或状态转移写通,再翻译成 Java 提交。
2.1 归并排序为例:Python 先写递归验证思路,Java 再写迭代版压性能
以归并排序算法为例。Python 的归并排序用递归写非常清爽,核心只有 merge 两个有序数组的逻辑。但有个隐藏成本:nums[:mid]这种切片操作会创建新列表,底层虽然是 C 的 memcpy,速度快,但内存占用直接翻倍。Java 如果也照着递归思路写,可以用索引l和r标记区间,只在真正 merge 时才new数组,内存更可控。这在 LintCode 上是一道隐形门槛——有些题目明确限制了额外空间复杂度,Python 切片写法很容易被卡。
def merge_sort(nums): if len(nums) <= 1: return nums mid = len(nums) // 2 left = merge_sort(nums[:mid]) right = merge_sort(nums[mid:]) return merge(left, right) def merge(left, right): i = j = 0 res = [] while i < len(left) and j < len(right): if left[i] <= right[j]: res.append(left[i]) i += 1 else: res.append(right[j]) j += 1 res.extend(left[i:]) res.extend(right[j:]) return respublic int[] mergeSort(int[] nums, int l, int r) { if (r - l <= 1) { return Arrays.copyOfRange(nums, l, r); } int mid = l + (r - l) / 2; int[] left = mergeSort(nums, l, mid); int[] right = mergeSort(nums, mid, r); return merge(left, right); } private int[] merge(int[] left, int[] right) { int[] res = new int[left.length + right.length]; int i = 0, j = 0, k = 0; while (i < left.length && j < right.length) { if (left[i] <= right[j]) { res[k++] = left[i++]; } else { res[k++] = right[j++]; } } while (i < left.length) res[k++] = left[i++]; while (j < right.length) res[k++] = right[j++]; return res; }两个版本的逻辑完全一致,但内存行为不同。Python 的切片在每次递归里都产生新列表,总内存峰值大约是原数组的两倍;Java 版通过l和r索引避免了对原始数组的反复拷贝,只有 merge 结果那一段需要分配。LintCode 上如果遇到空间限制严格的大数组题,Java 版明显更稳妥。参数上的一个小细节:Java 的mid我习惯写l + (r - l) / 2而不是(l + r) / 2,防整型溢出,虽然算法题里很少触发,但这是好习惯。
另外提一句归并排序的扩展价值:它是理解逆序对、外排序的基础,也是 Java 里Collections.sort对对象排序的底层算法。LintCode 上不少中等题看起来和归并无直接关系,实际上考的是你能不能在 merge 过程中顺便统计点别的信息。
2.2 数据结构的双语言映射:ArrayList 对应 list,PriorityQueue 对应 heapq,并查集两边都手写
双实现对初学者最大的障碍不是语法,而是数据结构的选择。很多人在 Java 里用惯了HashMap、PriorityQueue,到 Python 里不知道对应什么;反过来,有人在 Python 里用惯字典推导式,回到 Java 觉得处处要声明类型。下面这个映射表是我整理代码包时反复对照的基准。
| 场景 | Java 常用结构 | Python 对应结构 | 注意点 |
|---|---|---|---|
| 动态数组 | ArrayList / int[] | list | Python list 的 pop(0) 是 O(n),Java ArrayList remove(0) 同理 |
| 键值存储 | HashMap / TreeMap | dict | Java TreeMap 默认按键排序,Python 需要 sortedcontainers(非内置) |
| 优先队列 | PriorityQueue | heapq | Java 默认小顶堆,heapq 也是小顶堆,API 风格不同 |
| 栈 | ArrayDeque / Stack | list(append/pop) | Python list 自带栈能力,Java ArrayDeque 优先于 Stack |
| 队列 | ArrayDeque / LinkedList | collections.deque | LinkedList 同时实现了 List 和 Deque,慎用 |
| 集合去重 | HashSet | set | 遍历中修改集合,两边都会抛异常,注意 |
| 并查集 | 手写 class | 手写 list + 递归函数 | 两者都没有内置,必须自己实现路径压缩 |
这中间最容易被忽略的是并查集。LintCode 的连通性问题、岛屿类问题几乎都能用并查集解,但 Java 和 Python 都得手写。Java 版我通常写成一个内部类,包含find和union两个方法,路径压缩放在 find 里;Python 版用两个 list 一个存 parent 一个存 rank,递归实现 find。两边代码量差不多,但 Python 的递归 find 要注意前面说的递归深度限制。
Java 的PriorityQueue和 Python 的heapq有一个隐藏差异:Java 的PriorityQueue是线程安全的实现要注意加锁,但算法题里用new PriorityQueue<>()默认是非同步的;Python 的heapq是纯函数式的heappush/heappop,操作的是普通 list。写惯了 Python 再写 Java 时,容易忘了PriorityQueue构造时可以传入 Comparator,默认是小顶堆,求第 K 大要用(a, b) -> b - a反转。
更多的时候,数据结构的选择决定了一道题你能不能从 O(n^2) 降到 O(n log n)。比如 LintCode 上的合并区间类问题,Java 版用 ArrayList 排序后合并,Python 版也是同样的排序加双指针,逻辑上没有语言差异,但排序的稳定性、比较器的写法会在某些特定输入下暴露不同,这个坑我在第 4 章和第 5 章单独展开。
2.3 时间复杂度的语言差异:KMP 的 next 数组在两种语言里的实现边界
KMP 算法是字符串题里的高频考点,也是 Java 面试八股文里经常被追问细节的算法。next 数组的计算逻辑在 Java 和 Python 里几乎可以逐行对应,但边界条件的表现方式完全不同,这是双实现对照时最有价值的部分。
# Python 负索引在 KMP 里会静默出错 while j > 0 and s[i] != s[j]: j = next_[j - 1] # 当 j 为 0 时,j-1 是 -1,不会报错// Java 越界会直接抛异常,问题暴露得早 while (j > 0 && s.charAt(i) != s.charAt(j)) { j = next[j - 1]; // 当 j 为 0 时,ArrayIndexOutOfBoundsException }这个对比很典型:Python 的列表索引为负时表示从末尾取值,next_[-1]拿到的是最后一个元素,程序不报错,但计算出的 next 数组完全是错的;Java 的数组索引为负会直接抛异常,虽然刷题体验上麻烦一点,但至少你能立刻知道边界有问题。双实现的其中一个价值就在这里:同一个算法思想,两种语言暴露错误的方式不同,对照着看能加深对边界条件的理解。
再看时间复杂度的语言差异。KMP 本身是 O(n) 的,在 Python 和 Java 里都是线性;但 Python 的字符串默认是不可变对象,s[i]的随机访问是 O(1),而 Java 的charAt也是 O(1),这一点没有差异。真正的差异在常数因子:Python 的解释器开销让 KMP 在十万级字符串上的耗时明显高于 Java,但 LintCode 通常不卡这个量级。反过来,如果一个字符串算法题你用了s.find或者正则,Python 内置的 C 实现极快,Java 的正则则慢得让人怀疑人生。所以算法题里我会优先考虑:Python 能用内置方法解决的,就用内置;Java 则尽量手写逻辑,避免正则和反射。
3. 让这份代码包在本地跑起来:环境、目录与最小单测流程
很多人在 LintCode 上能 AC,但把代码搬到本地就傻眼,因为在线评测平台帮你把编译、测试、内存监控全包了。这份代码包能不能真正用起来,第一步是先把它跑成绿色。下面这套流程是我组织代码包时的标准做法,Java 和 Python 各占一边,互不干扰。
3.1 环境准备:JDK 与 Python 解释器的版本选择
不要一上来就装最新版本。LintCode 的老题很多是按 Java 8 的语法设计的,JDK 8 或 JDK 11 足够稳定;JDK 17 在模块化之后,跑一些老代码要额外加--add-opens参数,反而折腾。Python 选 3.8 或 3.9 就够用,3.10 以上对类型注解更严格,但不影响解题。Java 环境变量配置是老生常谈:Windows 下JAVA_HOME要配到 JDK 根目录而不是 bin 目录,PATH里加%JAVA_HOME%\bin;macOS 和 Linux 下我建议用sdkman或直接系统包管理装 OpenJDK。Python 安装相对简单,macOS 和 Linux 下用pyenv管理版本最省心,Windows 下安装时勾选 Add to PATH 即可,这样后面pytest和pip不用额外配置。
装完之后在终端验证一下:java -version和python --version都能正常输出,环境就算通了。如果你打算用 VSCode,Python 插件和 Java 扩展包各装一个,不要用同一套配置去跑两种语言,快捷键和调试器的行为差别很大,容易来回踩坑。
3.2 代码包的标准目录结构:按题号分模块
我见过很多刷题项目的目录结构是单文件堆积,一个文件夹里几百个 .java 或 .py 文件,找一道题全靠搜文件名。这份代码包采用按题号分模块的结构,Java 走 Maven 标准布局,Python 单独放一个目录,互不污染。
lintcode-java-python/ ├── java/ │ ├── pom.xml │ └── src/ │ ├── main/java/com/lintcode/ │ │ ├── solution/ # 按题号放的 Solution 类 │ │ │ ├── Solution001.java │ │ │ ├── Solution002.java │ │ │ └── Solution448.java │ │ └── util/ # 公共工具类(并查集、树节点、链表节点) │ │ ├── UnionFind.java │ │ └── TreeNode.java │ └── test/java/com/lintcode/ │ ├── Solution001Test.java │ ├── Solution002Test.java │ └── Solution448Test.java └── python/ ├── solution001.py ├── solution002.py ├── solution448.py ├── test_solution001.py └── util/ ├── union_find.py └── tree_node.py目录的命名规则是题号 + 方法名,例如Solution001.java对应用twoSum的题,solution001.py对应同一道题。这样做的理由:LintCode 的题号是稳定的,不会因为改版而重排,按题号组织以后找题、对照、回看都非常直接。Java 的util目录放公共结构,比如UnionFind.java和TreeNode.java,避免每道链表题都复制一遍节点定义。Python 的util目录也放对应版本,命名统一为小写下划线。
这个结构还有一个隐性的好处:Java 的包名com.lintcode.solution是 Maven 编译的最小要求,pom.xml里只需要配一个 JUnit 依赖就能跑测试。Python 侧不用建包,直接用 pytest 自动发现test_开头的文件,省去__init__.py的麻烦。
3.3 用 JUnit 和 pytest 把 LintCode 样例跑成绿色
LintCode 的题目是方法调用,不是标准输入输出,所以在本地测的时候,核心是把样例构造成方法参数直接调用。JUnit 和 pytest 都能胜任这个工作,下面给出一组最小的测试模板。
import org.junit.Test; import static org.junit.Assert.*; public class Solution001Test { @Test public void testTwoSum() { Solution001 s = new Solution001(); int[] nums = {2, 7, 11, 15}; int[] result = s.twoSum(nums, 9); assertArrayEquals(new int[]{0, 1}, result); } }from solution001 import two_sum def test_two_sum(): nums = [2, 7, 11, 15] assert two_sum(nums, 9) == [0, 1]跑测试的命令分别是mvn -q test和pytest -v test_solution001.py。逻辑上,Java 版用assertArrayEquals比较返回的数组,Python 版用assert比较 list。参数说明一个细节:Java 的assertArrayEquals比较的是数组内容而非引用,Python 的==对 list 也是比较内容,这在算法题验证里是安全的。但如果你的方法返回的是自定义对象(比如树的根节点),Java 要重写 equals,Python 要比较对象属性,不能直接==。
开发时的建议:每写一道题的实现,立刻补一个测试类或测试函数。LintCode 的样例通常覆盖主路径,你自己再加两个边界样例(空输入、极端大小)。这样后面改代码不会把已经 AC 的题弄回破功。
4. 双语言互转的五个必调参数:栈深度、递归限制与边界配置
从 Python 的思路翻译到 Java 代码,再反过来从 Java 的实现理解 Python 的写法,中间隔着五个最容易出问题的参数配置。这五个地方我在整理代码包时反复调过,列在这里作为一份速查清单。
4.1 JVM 栈深度与 Python 递归限制:-Xss 和 setrecursionlimit 怎么配合
深搜(DFS)在树和图上非常常见,而递归深度是两种语言都绕不开的坎。Java 默认线程栈在 1MB 左右,递归深度到几千层就可能抛StackOverflowError;Python 默认递归限制是 1000,超过就报RecursionError。LintCode 上很多树的题递归深度取决于树高,二叉树最坏情况下是一条链,深度可能上万。
# Java 启动时调整线程栈大小,深搜类题目常见的做法是 2m java -Xss2m -Xmx512m -cp target/classes com.lintcode.SolutionRunnerimport sys # Python 递归深搜前放宽限制,常见的做法是 10000 sys.setrecursionlimit(10000)这两个参数不是调了就万事大吉。-Xss调大意味着每个线程占用的栈内存变大,多线程场景下内存总量会涨;setrecursionlimit只是让 Python 不报错,递归深度过万依然会慢到怀疑人生,甚至触发段错误。我的建议是:先估算递归深度,如果超过两个数量级,就改成显式栈的迭代写法,用ArrayDeque或 Python 的list模拟栈。递归更接近思路,但迭代才是能扛住极限数据的写法。
4.2 输入输出模型差异:LintCode 是方法调用,不是标准输入输出
这是新手最容易卡住的地方。LeetCode 也是方法调用,但 LintCode 的模板里类名、方法签名和 LeetCode 不完全一致,很多人把 LeetCode 上的代码直接粘过来,发现编译不过。LintCode 的题目模板通常定义了一个Solution类,你只需要实现指定的方法。本地调试时,要自己写一个带main的入口来构造输入。
public class SolutionRunner { public static void main(String[] args) { Solution001 s = new Solution001(); int[] nums = {2, 7, 11, 15}; int[] res = s.twoSum(nums, 9); System.out.println(res[0] + "," + res[1]); } }逻辑说明:main方法只服务于本地调试,提交到 LintCode 时不需要把这个类带过去。在线平台会自动实例化Solution并调用目标方法,不会走main。所以代码包里的 Java 实现通常只保留Solution类,SolutionRunner是本地工程里额外加的调试入口,测试代码里也不依赖它产生的输出。
Python 也是一样,if __name__ == "__main__":里的代码只用于本地演示,LintCode 在线运行时不会执行它。写测试时直接 import 方法并调用即可,不要试图模拟平台读 stdin。
4.3 大数边界:Java 的 long 溢出与 Python 的任意精度
LintCode 里不少题涉及阶乘、组合数、大数乘法。Python 的 int 是不限精度的,随便算;Java 的 long 是 64 位有符号数,超过Long.MAX_VALUE就溢出成负数,而且不报错。这个差异会让同一道题在 Python 里 AC、在 Java 里答案错得莫名其妙。
// 计算组合数 C(n, k),n 超过 20 时 long 可能溢出 long a = 1; for (int i = 0; i < k; i++) { a = a * (n - i) / (i + 1); }参数说明:a * (n - i)在乘法的中间过程就可能溢出,即使最终结果在 long 范围内。改进做法是先除后乘,或者用BigInteger兜底。BigInteger的性能大约比 long 慢一个数量级,所以只在确认结果会超出 long 范围时才用。判断标准简单:最终答案是否超过 9.2e18。如果超过,Python 不用管,Java 直接用BigInteger或者用字符串处理。这也是双实现的价值——Python 先跑出正确结果,帮你确认答案的量级,再决定 Java 侧用 long 还是BigInteger。
4.4 排序与比较器:稳定排序和 Comparator 的坑
LintCode 的排序类题目比 LeetCode 更频繁地出现自定义排序。Java 的Arrays.sort对基本类型数组用的是双轴快速排序,不稳定;对对象数组用的是 TimSort,稳定。Python 的sorted是稳定排序。这个差异在某些“按多个字段排序”的题目里会直接导致答案不一致。
// 反例:o1 - o2 在极端值下会溢出 Arrays.sort(nums, (a, b) -> a - b); // 正解:用 Integer.compare Arrays.sort(nums, (a, b) -> Integer.compare(b, a));参数说明:a - b在a为正、b为负时可能溢出,得到错误的比较结果,而Integer.compare内部做了正确的比较逻辑,不会溢出。另一个坑是Comparator返回值的正负语义:返回负数表示第一个参数排前面。如果你想降序,用Integer.compare(b, a)而不是return -Integer.compare(a, b),后者在某些 JDK 版本的归并排序里可能触发性能退化。
Python 侧的对应写法是sorted(nums, key=lambda x: x, reverse=True)或sorted(nums, key=lambda x: -x)。当需要多字段排序时,Python 的 key 函数返回元组,sorted自动按元组顺序比较;Java 则要写Comparator.comparing(A::getField).thenComparing(B::getField)。稳定排序的要求在哪边都一样,只是写法完全不同。
4.5 null 与 None:Java 判空和 Python 短路求值的语义差
链表和树相关的题,判空是每道题的第一步。Java 的null和 Python 的None语义接近,但两种语言的空值传播逻辑不一样,写代码时会遇到一个微妙差异。
// Java 必须显式判断 node != null,顺序不能反 while (node != null && node.next != null) { node = node.next; }# Python 的 and 短路求值,None 为 falsy,顺序反了也不会报错 while node and node.next: node = node.next逻辑说明:Java 的&&虽然也是短路,但如果你写while (node.next != null && node != null),在node为 null 时,node.next已经触发了NullPointerException。Python 的and同样短路,但node为None时会被当作 falsy,node.next根本不会执行,所以while node and node.next是安全的。这个差异导致了一个常见现象:Python 代码里写反了顺序不报错,Java 里写反了直接崩溃。我建议双实现里统一在 Java 侧养成判空对象在前的习惯,在 Python 侧也保持同样的书写顺序,避免来回切换时的思维惯性。
另一个差异是字典键不存在时的行为。Java 的HashMap.get返回null,Python 的dict[key]抛KeyError,dict.get(key)返回None。这个差异在统计频率、建图时经常触发。我的习惯是 Java 用getOrDefault,Python 用collections.Counter或dict.get(key, 0),两份代码的意图保持一致。
5. 双语言刷 LintCode 避坑指南:六个常见的翻车现场
双实现最值钱的部分是踩坑记录。下面六个问题,是我在本地刷题和整理代码包时反复遇到的,每一条都按“现象、原因、解决”的流程写清楚。
5.1 Java 的 Integer 缓存:== 比较为什么时对时错
现象:比较两个Integer对象,值都是 100 时==返回 true,值都是 200 时返回 false。
原因:Integer在 -128 到 127 之间有缓存池,==比较的是对象引用,缓存范围内的自动装箱返回同一个对象,范围外的每次装箱都创建新对象。
解决:包装类型之间的比较一律用equals(),或者把变量声明成基本类型int。LintCode 的ArrayList<Integer>里取出的元素就是Integer对象,直接在==上比较是经典翻车点。我习惯在代码库里把“所有包装类型比较都走 equals”写在 util 类的注释里,避免以后自己犯浑。
5.2 Python 的二维数组初始化:[[0]*n]*m 的复制陷阱
现象:初始化一个 m 行 n 列的矩阵,给某个位置赋值后,整列的值都变了。
原因:[[0] * n] * m中的* m复制的是同一个 list 对象的引用,m 个行指向同一个底层列表。
解决:用列表推导式[[0] * n for _ in range(m)]。这个坑在动态规划类的二维 DP 题里特别常见,初始化 dp 表时一出错,整个状态转移全乱。我的排查习惯是:写完初始化先用一个样例打印 dp 表,确认每个位置的独立性,再写状态转移逻辑。
5.3 深搜同时报错:Python RecursionError,Java StackOverflowError
现象:同一道图的深搜题,在 Python 里报RecursionError,在 Java 里报StackOverflowError,两边都过不了。
原因:递归深度本质上受限于线程栈大小和解释器的递归保护,超过阈值就崩,跟算法对不对没关系。
解决:先估深度。树的递归深度等于树高,最坏情况下一条链可能有上万个节点。深度超过预期的两到三倍,就改成显式栈。Java 用ArrayDeque模拟,Python 用list的append和pop()模拟,循环代替递归。这个改造不需要换思路,只要把函数调用改成压栈和出栈,注意压栈顺序和递归顺序相反。
5.4 优先队列比较器写反:小顶堆变大顶堆
现象:求第 K 大的题,结果总是差一位,输出的是第 K+1 大。
原因:Java 的PriorityQueue默认是小顶堆,自定义Comparator时把返回值的正负写反,堆顶就从最小值变成了最大值,弹出去的自然就不是想要的元素。
解决:写比较器之前先明确“堆顶是谁”。求第 K 大时,维护一个大小为 K 的小顶堆,堆顶就是第 K 大,比较器应该是(a, b) -> Integer.compare(a, b),不要额外写反向。Python 的heapq默认也是小顶堆,需要大顶堆时,常见做法是存负数(-x),而不是改比较器。
5.5 字符串切片与子串边界:Python 负索引和 Java 的 substring
现象:字符串处理题里,Python 的s[-1]能拿到最后一个字符,Java 的s.charAt(-1)直接越界。
原因:两种语言的索引语义不同。Python 支持负索引,Java 不支持,越界就抛异常。
解决:双实现时统一用非负索引的写法,Python 写 s[i]、Java 写 s.charAt(i),宁愿多写一个len(s) - 1也不要依赖负索引的便利。另一个相关的坑是substring的区间:Java 的s.substring(1)是从位置 1 到末尾,Python 的s[1:]也是从位置 1 到末尾,这两者一致,但 Java 的s.substring(0, 1)是左闭右开,包含 0 不包含 1,Python 的s[0:1]也是左闭右开,这一点倒是对齐了。真正容易出错的地方是计算回文子串、字符串哈希时的索引偏移,建议在纸上画一遍区间再写代码。
5.6 数据结构排序算法:Arrays.sort 与 sorted 的稳定性差异
现象:多关键字排序的题,相同键值的两个元素在 Java 和 Python 里的相对顺序不一致,提交结果不稳定。
原因:Java 的Arrays.sort对基本类型数组用的是双轴快速排序,不稳定;Python 的sorted归并排序变体,稳定。当你的题面没有明确要求稳定,但评测数据恰好依赖原始顺序时,Java 版的答案可能和 Python 版对不上。
解决:先确认题面是否要求稳定排序。有要求时,Java 用Collections.sort(底层 TimSort,稳定)或者把基本类型装箱成对象再排序;Python 用sorted就行。没有明确要求时,也不要在排序后的结果里依赖原始顺序,正确的做法是给元素加一个下标字段作为次关键字。
6. 进阶技巧:用对拍脚本和基准测试做双实现的交叉验证
单测只能覆盖你想到的用例,真正想确认两种实现都对,得用对拍。我的习惯是写一个随机数据生成器,把同一组输入分别喂给 Java 和 Python 的实现,比对输出。只要随机个数够多,绝大部分边界问题都能暴露出来。
import random import subprocess random.seed(42) # 固定种子,方便复现 for _ in range(100): n = random.randint(1, 1000) nums = [random.randint(-10000, 10000) for _ in range(n)] input_data = f"{n}\n" + " ".join(map(str, nums)) java_out = subprocess.run( ["java", "-cp", "target/classes", "Main"], input=input_data, capture_output=True, text=True ).stdout.strip() python_out = subprocess.run( ["python", "main.py"], input=input_data, capture_output=True, text=True ).stdout.strip() if java_out != python_out: print("mismatch for input:", input_data) print("java:", java_out) print("python:", python_out) break else: print("all ok")这里的关键是 Java 侧写一个读 stdin 的Main类,代码包里的util目录中我已经放了一个适配器模板,把题目方法包装成标准输入输出。对拍跑出来的任何不一致,先用random.seed(42)固定种子复现,再缩小输入规模做二分定位。我自己被 KMP 的负索引坑过一次之后,字符串类的题一律先过一遍对拍再提交。
基准测试也值得做,但对比要公平。Java 有 JIT 预热,Python 有解释器启动开销,直接比单次耗时没有意义。常见做法是 Java 侧循环 1000 次取平均,Python 侧循环 100 次取平均,用System.nanoTime和time.perf_counter计时。注意,LintCode 上最终能不能 AC,取决于 OJ 的时限和机器配置,本地的绝对耗时只做参考,看量级不看数值。我现在每道题写完,都会跑一次对拍加一次粗略基准,确认两边逻辑一致再整理进代码包。这套流程帮我解决了大量边界和溢出问题,希望也能帮你省下这些时间。
本文还有配套的精品资源,点击获取