ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Java与Python双语言刷题实战:lintcode算法与数据结构对照解析

Java与Python双语言刷题实战:lintcode算法与数据结构对照解析 简介这份资源是面向算法初学者与进阶开发者的lintcode刷题实战包围绕Java与Python双语言实现展开适合准备毕业设计、巩固数据结构与算法基础的程序员。包内共36个文件以md题解笔记、py与java源码为主辅以git配置类文件压缩包约17KB体量轻便、结构清晰。内容覆盖打劫房屋、超级丑数、中位数、爬楼梯、最小路径和、硬币排成线、摆动排序、字符串查找、第K大元素、二分查找等经典题目每道题均配有Java与Python两套解法及README说明便于对照理解算法思想、时间与空间复杂度评估以及数据结构选择。已有46人学习适合通过双语言对比练习提升编码能力也可作为毕设中算法模块的参考实现与排错思路来源。1. 从一份 JavaPython 双语言刷题包说起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) MapInteger, 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[j1] ...换成三行临时变量交换即可其余逻辑一致。这样对照你就知道「剪枝算法」在两种语言里是同一件事只是语法糖不同。3. 按数据结构分类复现数组、链表、树、图的 Java/Python 对照3.1 数组与双指针从暴力枚举到剪枝的过渡数组题是 lintcode 里占比最大的一类也是「暴力枚举算法」和「剪枝算法」对比最明显的地方。以「三数之和」为例暴力是 O(n³)排序加双指针能降到 O(n²)再配合去重剪枝。Java 和 Python 的差异主要在排序 API 和指针移动的写法。// 三数之和排序 双指针 去重剪枝 import java.util.*; public class ThreeSum { public ListListInteger threeSum(int[] nums) { Arrays.sort(nums); // Java 用 Arrays.sort ListListInteger 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, val0, nextNone): 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, ListInteger res)Python 里可以直接用闭包或嵌套函数。以中序遍历为例递归版两边逻辑一致迭代版用栈。// 中序遍历迭代版用显式栈 import java.util.*; public class Inorder { public ListInteger inorder(TreeNode root) { ListInteger res new ArrayList(); DequeTreeNode 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换成listpush换appendpop一致。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), dtypeint) 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 版dtypeint避免浮点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对基本类型用双轴快排对对象用 TimSortPython 的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*i1、r 2*i2是数组存堆的下标公式。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 版用PriorityQueueListNode加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 用longPython 天然大整数。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.txtdiff -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 要链式thenComparingPython 用元组 key。// Java先按长度再按字典序 list.sort(Comparator.comparingInt(String::length) .thenComparing(Comparator.naturalOrder()));# Python元组 key先长度后字典序 lst.sort(keylambda s: (len(s), s))逻辑说明Java 的thenComparing是链式比较器Python 的元组 key 天然按元素顺序比较。参数上Comparator.naturalOrder()是自然序Python 的s直接参与比较。把这两个模板记住排序类题基本不会翻车。我自己的习惯是每刷完一类题就把两种语言的模板各抄一遍抄的时候故意不看题解抄完再对拍。这样坚持两个月Java 和 Python 的切换就不再是障碍lintcode 上的题也能真正变成自己的东西。希望帮到你。本文还有配套的精品资源点击获取
返回列表