☰
Java与Python双语言刷题实战:lintcode算法与数据结构对照解析
2026/10/6 2:55:00 网站建设 项目流程

简介:这份资源是面向算法初学者与进阶开发者的lintcode刷题实战包,围绕Java与Python双语言实现展开,适合准备毕业设计、巩固数据结构与算法基础的程序员。包内共36个文件,以md题解笔记、py与java源码为主,辅以git配置类文件,压缩包约17KB,体量轻便、结构清晰。内容覆盖打劫房屋、超级丑数、中位数、爬楼梯、最小路径和、硬币排成线、摆动排序、字符串查找、第K大元素、二分查找等经典题目,每道题均配有Java与Python两套解法及README说明,便于对照理解算法思想、时间与空间复杂度评估以及数据结构选择。已有46人学习,适合通过双语言对比练习提升编码能力,也可作为毕设中算法模块的参考实现与排错思路来源。

1. 从一份 Java+Python 双语言刷题包说起:lintcode 算法与数据结构怎么落地

很多人刷 lintcode 刷到三百题以后会卡在一个尴尬位置:单语言题解看得懂,但一换语言就写不出来,Java 的PriorityQueue和 Python 的heapq到底怎么对应,KMP 的next数组在两种语言里边界差一位就全错。这份「基于 Java 和 Python 的分析、实现 lintcode 的算法、数据结构」的压缩包,本质就是解决这个问题的:用同一道题、同一套数据结构,在两种语言里各写一遍,把差异点逼出来。它适合三类人:准备 Java 岗面试但想用 Python 快速验证思路的、蓝桥杯或数据结构 408 复习时想对照两种实现的、以及想把「数据结构与算法分析:Java 语言描述」里的伪代码真正跑起来的人。下面按「先立住原理、再动手复现、最后避坑」的顺序拆开讲。

2. 双语言刷题包的目录结构与最小可跑环境

2.1 先看清包里到底有什么,别急着解压就写

拿到一个.zip刷题包,第一件事不是打开 IDE,而是先列目录树,判断它是「按题号组织」还是「按数据结构组织」。常见做法是lintcode/下按java/和python/分两个大目录,再按array、linkedlist、tree、graph、dp分子目录。先跑一遍目录统计,心里有数再动手。

# 解压后先看结构,不要直接进 IDE unzip lintcode-algo-ds.zip -d lintcode-algo-ds cd lintcode-algo-ds # 统计两种语言各有多少文件,判断覆盖度 find . -name "*.java" | wc -l find . -name "*.py" | wc -l # 看顶层目录是按什么维度切的 ls -1

逻辑说明:find ... | wc -l用来快速判断 Java 和 Python 的题量是否对等,如果 Java 有 200 个文件而 Python 只有 30 个,说明这个包是「Java 为主、Python 为辅」,后面复现时要以 Java 为准。参数上-name "*.java"是大小写敏感匹配,Windows 上如果文件名混用.JAVA会漏统计,可以加-iname。

2.2 Java 侧最小环境:JDK 17 加一个 main 入口

Java 侧不需要 Maven 也能跑,只要 JDK 和javac。我一般用 JDK 17,因为var和record在写临时测试类时省事。关键是每个题解类要有一个main方法,否则你只能靠 JUnit,而刷题包通常不带测试框架。

// Solution.java —— 以 lintcode 经典的两数之和为例 import java.util.HashMap; import java.util.Map; public class Solution { public int[] twoSum(int[] nums, int target) { // key 存数值,value 存下标,一次遍历 O(n) Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int need = target - nums[i]; if (map.containsKey(need)) { return new int[]{map.get(need), i}; } map.put(nums[i], i); } return new int[]{-1, -1}; } public static void main(String[] args) { Solution s = new Solution(); int[] r = s.twoSum(new int[]{2, 7, 11, 15}, 9); System.out.println(r[0] + "," + r[1]); // 期望 0,1 } }

逻辑说明:用HashMap把「找另一个数」从 O(n) 降到 O(1),整体 O(n)。参数上target - nums[i]是补数,map.put放在判断之后是为了避免同一个元素用两次。编译运行javac Solution.java && java Solution,输出0,1就说明环境通了。

2.3 Python 侧最小环境:venv 加 numpy 可选

Python 侧我建议用venv隔离,别直接往系统 Python 里装。刷题本身不需要 numpy,但如果你要跑「python 构建邻接矩阵」这类图题的可视化验证,numpy 会方便很多。安装方式按官方文档走即可。

python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate python -m pip install --upgrade pip # 图题需要矩阵时再装,纯刷题可不装 python -m pip install numpy

逻辑说明:venv保证依赖不污染全局,source激活后python指向虚拟环境。参数上--upgrade pip先升级再装包,避免旧 pip 解析 wheel 失败。装完用python -c "import numpy; print(numpy.__version__)"验证。

2.4 用一道题把两种语言对齐:冒泡排序的写法差异

选冒泡排序不是因为它难,而是因为它能暴露两种语言在「交换」和「循环边界」上的习惯差异。Java 里数组是定长、交换要临时变量;Python 里可以元组解包一行交换。把两边并排写,差异一目了然。

# bubble_sort.py def bubble_sort(arr): n = len(arr) for i in range(n - 1): swapped = False # 剪枝:某一轮没交换说明已有序 for j in range(n - 1 - i): if arr[j] > arr[j + 1]: arr[j], arr[j + 1] = arr[j + 1], arr[j] swapped = True if not swapped: break return arr if __name__ == "__main__": print(bubble_sort([5, 2, 9, 1, 5, 6]))

逻辑说明:swapped是剪枝标志,最好情况 O(n),最坏 O(n²)。range(n - 1 - i)里减去i是因为每轮末尾已经排好。Java 版把arr[j], arr[j+1] = ...换成三行临时变量交换即可,其余逻辑一致。这样对照,你就知道「剪枝算法」在两种语言里是同一件事,只是语法糖不同。

3. 按数据结构分类复现:数组、链表、树、图的 Java/Python 对照

3.1 数组与双指针:从暴力枚举到剪枝的过渡

数组题是 lintcode 里占比最大的一类,也是「暴力枚举算法」和「剪枝算法」对比最明显的地方。以「三数之和」为例,暴力是 O(n³),排序加双指针能降到 O(n²),再配合去重剪枝。Java 和 Python 的差异主要在排序 API 和指针移动的写法。

// 三数之和:排序 + 双指针 + 去重剪枝 import java.util.*; public class ThreeSum { public List<List<Integer>> threeSum(int[] nums) { Arrays.sort(nums); // Java 用 Arrays.sort List<List<Integer>> res = new ArrayList<>(); for (int i = 0; i < nums.length - 2; i++) { if (nums[i] > 0) break; // 剪枝:最小数大于 0 直接结束 if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复 int l = i + 1, r = nums.length - 1; while (l < r) { int sum = nums[i] + nums[l] + nums[r]; if (sum == 0) { res.add(Arrays.asList(nums[i], nums[l], nums[r])); while (l < r && nums[l] == nums[l + 1]) l++; while (l < r && nums[r] == nums[r - 1]) r--; l++; r--; } else if (sum < 0) l++; else r--; } } return res; } }

逻辑说明:Arrays.sort是原地排序,Python 对应nums.sort()。if (nums[i] > 0) break是剪枝,因为排序后最小数为正就不可能凑出 0。去重靠「跳过与前一个相同的数」,这是最容易写错的地方,漏了会输出重复三元组。参数上l和r是左右指针,sum < 0说明需要更大,左指针右移。

3.2 链表:Java 的引用与 Python 的对象,谁更容易翻车

链表题在两种语言里都容易翻车,但翻车点不同。Java 里ListNode next是引用,改cur.next会连带影响原链;Python 里对象也是引用,但因为没有显式类型,None判断更容易漏。以「反转链表」为例,迭代写法两边几乎一样,递归写法 Python 更简洁但栈深度要注意。

# 反转链表:迭代版 class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def reverse_list(head): prev, cur = None, head while cur: nxt = cur.next # 先存下一个,否则断链 cur.next = prev # 反转指针 prev = cur cur = nxt return prev

逻辑说明:nxt = cur.next必须先存,否则cur.next = prev之后原链就找不到了,这是链表题最经典的血泪经验。Java 版把prev, cur = None, head拆成两行声明即可。参数上prev初始为None,返回prev而不是head,因为head已经变成尾节点。

3.3 树与递归:前中后序遍历在两种语言里的模板

树题的核心是递归模板,Java 和 Python 的差异主要在「辅助函数要不要单独写」。Java 里常写一个private void dfs(TreeNode node, List<Integer> res),Python 里可以直接用闭包或嵌套函数。以中序遍历为例,递归版两边逻辑一致,迭代版用栈。

// 中序遍历:迭代版,用显式栈 import java.util.*; public class Inorder { public List<Integer> inorder(TreeNode root) { List<Integer> res = new ArrayList<>(); Deque<TreeNode> stack = new ArrayDeque<>(); TreeNode cur = root; while (cur != null || !stack.isEmpty()) { while (cur != null) { // 一路向左压栈 stack.push(cur); cur = cur.left; } cur = stack.pop(); // 弹出即访问 res.add(cur.val); cur = cur.right; // 转向右子树 } return res; } }

逻辑说明:Deque比Stack更推荐,因为Stack是遗留类。while (cur != null || !stack.isEmpty())两个条件缺一不可,只判栈空会在根节点右子树时提前退出。Python 版把Deque换成list,push换append,pop一致。

3.4 图与邻接矩阵:Python 构建邻接矩阵的两种写法

图题在 lintcode 里以「岛屿数量」「克隆图」为主。用 Python 构建邻接矩阵是很多人搜的点,这里给两种写法:一种用二维列表,一种用 numpy。二维列表适合小图,numpy 适合需要矩阵运算的场景。

# 用二维列表构建邻接矩阵 def build_adj_matrix(n, edges): # n 个节点,edges 是 (u, v) 列表 matrix = [[0] * n for _ in range(n)] # 注意不能用 [[0]*n]*n for u, v in edges: matrix[u][v] = 1 matrix[v][u] = 1 # 无向图对称 return matrix # 用 numpy 构建,适合后续做矩阵乘法 import numpy as np def build_adj_numpy(n, edges): m = np.zeros((n, n), dtype=int) for u, v in edges: m[u][v] = m[v][u] = 1 return m

逻辑说明:[[0] * n for _ in range(n)]是正确写法,[[0]*n]*n会让所有行指向同一个列表,改一行全变,这是 Python 图题最常见的翻车点。numpy 版dtype=int避免浮点,m[u][v] = m[v][u] = 1是无向图对称赋值。

4. 字符串与经典算法:KMP、排序、堆在双语言里的实现差异

4.1 KMP 算法:next 数组的边界是最大黑匣子

KMP 是 lintcode 字符串题的高频考点,也是「一看就懂、一写就错」的典型。核心在next数组的构建,Java 和 Python 的差异在数组长度和下标起点。我一般用「前缀表」版本,next[i]表示[0, i]的最长相等前后缀长度。

# KMP:前缀表版本 def build_next(p): nxt = [0] * len(p) j = 0 for i in range(1, len(p)): while j > 0 and p[i] != p[j]: j = nxt[j - 1] # 回退到前一个最长前缀 if p[i] == p[j]: j += 1 nxt[i] = j return nxt def kmp_search(s, p): if not p: return 0 nxt = build_next(p) j = 0 for i in range(len(s)): while j > 0 and s[i] != p[j]: j = nxt[j - 1] if s[i] == p[j]: j += 1 if j == len(p): return i - len(p) + 1 return -1

逻辑说明:j = nxt[j - 1]是回退,不是j = nxt[j],差一位就全错,这是 KMP 最大的黑匣子。nxt[i] = j在循环末尾赋值,保证nxt[0]始终为 0。Java 版把list换成int[],逻辑完全一致。参数上p是模式串,s是主串,返回首次匹配下标。

4.2 排序算法对照:冒泡、堆排序、Java 的 Arrays.sort

排序是数据结构复习的必考项。Java 的Arrays.sort对基本类型用双轴快排,对对象用 TimSort;Python 的sorted用 TimSort。手写堆排序能帮你理解PriorityQueue和heapq的底层。

// 堆排序:Java 手写版 public class HeapSort { public void sort(int[] arr) { int n = arr.length; for (int i = n / 2 - 1; i >= 0; i--) // 建堆,从最后一个非叶节点开始 heapify(arr, n, i); for (int i = n - 1; i > 0; i--) { int t = arr[0]; arr[0] = arr[i]; arr[i] = t; // 堆顶换到末尾 heapify(arr, i, 0); } } private void heapify(int[] arr, int n, int i) { int largest = i, l = 2 * i + 1, r = 2 * i + 2; if (l < n && arr[l] > arr[largest]) largest = l; if (r < n && arr[r] > arr[largest]) largest = r; if (largest != i) { int t = arr[i]; arr[i] = arr[largest]; arr[largest] = t; heapify(arr, n, largest); } } }

逻辑说明:建堆从n/2 - 1开始,因为叶子节点天然满足堆性质。heapify里l = 2*i+1、r = 2*i+2是数组存堆的下标公式。Python 版可以用heapq但那是小顶堆,手写大顶堆要把比较反过来。参数上n是当前堆大小,每轮减一。

4.3 堆与优先队列:Java PriorityQueue 和 Python heapq 的坑

Java 的PriorityQueue默认小顶堆,Python 的heapq也是小顶堆,这点一致。但 Java 可以传Comparator自定义,Python 只能靠取负或元组。以「合并 K 个有序链表」为例,两边写法差异明显。

import heapq # 合并 K 个有序链表:Python heapq 版 def merge_k_lists(lists): heap = [] for i, node in enumerate(lists): if node: heapq.heappush(heap, (node.val, i, node)) # i 防止 val 相同比较 node dummy = ListNode(0) cur = dummy while heap: val, i, node = heapq.heappop(heap) cur.next = node cur = cur.next if node.next: heapq.heappush(heap, (node.next.val, i, node.next)) return dummy.next

逻辑说明:元组(node.val, i, node)里i是关键,因为ListNode不可比较,val 相同时会报错,加i保证唯一。Java 版用PriorityQueue<ListNode>加Comparator.comparingInt(n -> n.val)即可。参数上dummy是哨兵节点,返回dummy.next。

5. 避坑与排查:双语言刷题包里最容易翻车的 5 个点

5.1 现象:Java 编译报「找不到符号」,原因:包名与目录不一致

现象是javac报cannot find symbol或package xxx does not exist。原因通常是刷题包里的.java文件带了package lintcode.array;这类声明,但你没按目录结构放,或者编译时没加-d。解决:要么删掉package行,要么在项目根目录用javac -d out $(find . -name "*.java")统一编译,让javac按包名生成目录。

5.2 现象:Python 递归爆栈,原因:默认递归深度只有 1000

现象是RecursionError: maximum recursion depth exceeded。原因是 Python 默认递归深度约 1000,树题深度大时必炸。解决:在文件开头加import sys; sys.setrecursionlimit(100000),或者改写成迭代版。Java 默认栈更大,但深递归也会StackOverflowError,可以加-Xss参数调大线程栈。

5.3 现象:两种语言结果不一致,原因:整数溢出和浮点精度

现象是同一道题 Java 输出正确、Python 输出错误,或反过来。原因常见于 Java 的int溢出(如mid = (l + r) / 2在 l、r 很大时溢出)和 Python 的浮点除法(/返回 float)。解决:Java 用l + (r - l) / 2防溢出,Python 需要整除时用//。涉及大数时 Java 用long,Python 天然大整数。

5.4 现象:链表题改完原链断了,原因:没先存 next 指针

现象是反转或重排链表后,原链后半段丢失或死循环。原因是cur.next = prev之前没存nxt = cur.next。解决:任何改next指针的操作,第一行先存下一个节点。这是链表题最经典的血泪经验,没有之一。

5.5 现象:图题邻接矩阵改一行全变,原因:浅拷贝

现象是matrix[0][0] = 1之后matrix[1][0]也变成 1。原因是[[0]*n]*n创建的是 n 个指向同一列表的引用。解决:用列表推导[[0]*n for _ in range(n)],或者用 numpy 的np.zeros((n, n))。这个坑在「python 构建邻接矩阵」的搜索里出现频率极高。

6. 进阶技巧:用双语言对照做算法验证与面试准备

6.1 用 Python 快速验证思路,用 Java 写最终版

我的习惯是:拿到一道题先用 Python 写一版,因为语法短、调试快,能快速验证思路对不对。思路通了再翻译成 Java,翻译过程本身就是一次查漏补缺。比如 KMP 的next数组,Python 里print(nxt)一眼看出边界,Java 里要Arrays.toString(nxt)。两种语言对照,错误无处藏身。

6.2 用对拍脚本验证两种语言结果一致

对拍是竞赛和面试准备的利器。写一个随机用例生成器,分别跑 Java 和 Python,比对输出。下面是一个简化版对拍思路。

# 对拍:随机生成 100 组用例,比对 Java 和 Python 输出 for i in $(seq 1 100); do python gen.py > input.txt # 生成随机输入 java Solution < input.txt > out_java.txt python solution.py < input.txt > out_py.txt if ! diff -q out_java.txt out_py.txt > /dev/null; then echo "不一致,用例:"; cat input.txt; break fi done

逻辑说明:gen.py负责生成随机输入,两个解法读同一份input.txt,diff -q静默比对。参数上seq 1 100是 100 组,发现不一致就break并打印用例。这个脚本能帮你抓出整数溢出、边界处理等隐蔽差异。

6.3 面试前的高频题清单怎么用这个包

面试前不要从头刷,而是按「数据结构 + 算法」两个维度筛。数组看双指针和前缀和,链表看反转和环检测,树看遍历和递归,图看 BFS/DFS 和拓扑排序,字符串看 KMP 和滑动窗口。这个包的价值在于同一题两种语言都有,你可以用 Java 写面试版,用 Python 写验证版。我一般会把易错的next数组、堆的比较器、邻接矩阵的构建单独记一个笔记,面试前只看笔记。

6.4 一个具体技巧:把 Java 的 Comparator 和 Python 的 key 对齐

Java 的Comparator.comparingInt和 Python 的sorted(key=...)是同一件事,但写法差异大。以「按字符串长度再按字典序排序」为例,Java 要链式thenComparing,Python 用元组 key。

// Java:先按长度,再按字典序 list.sort(Comparator.comparingInt(String::length) .thenComparing(Comparator.naturalOrder()));
# Python:元组 key,先长度后字典序 lst.sort(key=lambda s: (len(s), s))

逻辑说明:Java 的thenComparing是链式比较器,Python 的元组 key 天然按元素顺序比较。参数上Comparator.naturalOrder()是自然序,Python 的s直接参与比较。把这两个模板记住,排序类题基本不会翻车。

我自己的习惯是每刷完一类题,就把两种语言的模板各抄一遍,抄的时候故意不看题解,抄完再对拍。这样坚持两个月,Java 和 Python 的切换就不再是障碍,lintcode 上的题也能真正变成自己的东西。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询