ARTICLE DETAIL

资讯详情

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

Java基础算法学习:光看不练假把式,手写才是硬道理

Java基础算法学习:光看不练假把式,手写才是硬道理 Java基础算法学习不要光看一定要敲起来不管是准备校招面试、打蓝桥杯这类竞赛还是刚入门想把Java基础打牢基础算法都是一道绕不过去的坎。我身边太多人学Java时啃完语法、看完集合框架就开始“飘”一到手写排序、二分查找就卡壳。我自己带过的新人也是一样视频看了好几遍笔记抄了一整本真让他上黑板写个快排直接愣住。问题出在哪就是没有“敲起来”。看一百遍算法讲解不如亲手敲一遍让报错打你的脸你才能真的记住。这篇内容我打算从Java基础算法到底要学哪些、怎么动手敲才能发现问题、面试和蓝桥杯怎么练、常见踩坑怎么避这几个角度把我自己走过弯路之后总结出来的方法完整写一遍。适合刚学完Java基础语法正准备进阶的人、算法基础薄弱但要面Java开发岗的人也适合在算法上刷了又忘、始终找不到状态的竞赛党。1. 为什么说基础算法是Java开发的“基本功”1.1 面试必考算法题是筛人的第一道滤网Java开发岗的面试基本上逃不过算法题这一关。不是说每个岗位都要你手撕红黑树但像反转链表、两数之和、二分查找、排序这类基础题目几乎是“入场券”级别的存在。面试官让我面候选人的时候我最怕遇到的情况就是简历里写着“熟悉Java集合”结果让人手写一个基于数组的栈连扩容逻辑都说不清楚。很多人觉得面试考算法是“卷”是“八股文”。但你换个角度想算法题考的其实是一个人的基本功边界条件处理是不是严谨、代码风格是不是干净、遇到没写过的题能不能拆解成步骤。这些能力在平时写业务代码时不会直接暴露但一到线上故障排查、接口性能调优、数据迁移脚本编写有没有算法底子写出来的代码完全是两个层次。1.2 竞赛场景蓝桥杯这类比赛只认算法功底蓝桥杯的Java组题目跟你在公司里写的Spring Boot业务代码完全是两码事。它不考框架、不考中间件考的就是字符串处理、数字计算、递归回溯、动态规划、图论这些基础算法和数据结构。我记得拿到的“蓝桥杯 数字题目”这类题核心就是进制转换、大数取模、数字拆分这里面每一步都建立在基础算法之上。打这类比赛没有捷径同一个题型你见过的次数多了考场上才能条件反射式地想出解法。我之前有个同事带学生打蓝桥杯他说了一句很实在的话一等奖不是教出来的是刷出来的。虽然有点绝对但方向是对的竞赛面前坚持刷题就是最有效的准备方式。1.3 日常开发里的算法影子哪怕你不面大厂、不打竞赛算法也会在日常开发中冒出来。举个最简单的例子你写一个接口要给前端返回树形菜单一次从数据库查出所有节点自己组装成树这个过程本质上就是用HashMap做父子关系的匹配你处理一批订单数据要按金额排序底层就是排序算法你用Redis做缓存Key的哈希分布背后是哈希函数的设计思想。框架源码里更是处处是算法。HashMap的桶位计算和扩容机制、ArrayList的扩容拷贝、ConcurrentHashMap的并发控制这些源码看懂的前提是你自己实现过类似的简单版本。所以“Java工程师不需要懂算法”这个说法真的只适用于永远停留在CRUD层面的人。2. Java基础算法必知必会一份不注水的清单2.1 排序算法先手写冒泡、快排、归并这五种排序算法是基础算法里最直观的一类也是面试手写频率最高的。先列一个我建议所有学Java的人都亲手实现过的清单冒泡排序、选择排序、插入排序、快速排序、归并排序。前三种是“慢但简单”的O(n²)排序后两种是“快但也更好出错”的O(nlogn)排序。拿冒泡排序来说绝大多数人第一次写都会写错内层循环的边界。我给个我常用的模板你照着敲一遍然后再自己默写public static void bubbleSort(int[] arr) { int n arr.length; for (int i 0; i n - 1; i) { boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped true; } } // 优化如果没有发生交换说明已经有序 if (!swapped) { break; } } }这个flag优化很关键日常开发里如果数据基本有序它能直接让算法变成近乎O(n)的复杂度。快速排序的核心则在于partition我建议所有细节都不要图省事跳过public static void quickSort(int[] arr, int left, int right) { if (left right) { return; } int base arr[left]; int i left; int j right; while (i j) { // 从右往左找比base小的 while (i j arr[j] base) { j--; } // 从左往右找比base大的 while (i j arr[i] base) { i; } if (i j) { swap(arr, i, j); } } // 把基准数放到中间的位置 swap(arr, left, i); quickSort(arr, left, i - 1); quickSort(arr, i 1, right); } private static void swap(int[] arr, int a, int b) { int temp arr[a]; arr[a] arr[b]; arr[b] temp; }记住快排是“不稳定的排序”相等的元素相对顺序会被打乱。如果业务上有稳定排序的需求就选归并排序。2.2 二分查找Java里一定要吃透的查找算法二分查找看起来只有几行但它是面试里翻车率最高的题之一。先看标准模板这种写法是我踩坑无数之后固定下来的public static int binarySearch(int[] arr, int target) { int left 0; int right arr.length - 1; while (left right) { int mid left (right - left) / 2; if (arr[mid] target) { return mid; } else if (arr[mid] target) { left mid 1; } else { right mid - 1; } } return -1; }这里两个坑很多人会踩。第一个mid写成(left right) / 2当left和right接近int最大值时会溢出正确写法是left (right - left) / 2。第二个循环条件是left right还是left right取决于搜索区间的定义我习惯用左闭右闭区间所以用写错了就会漏查边界元素或出现死循环。二分查找的进阶版本包括查找第一个等于target的下标、最后一个等于target的下标这类题目在LeetCode和面试里出现频率很高。把基础版吃透了变体就不难核心就是搞清楚最终收缩到哪个边界。2.3 字符串处理Java开发最常见的算法场景字符串这块在Java里是实实在在的高频场景无论是白板题还是实际业务都会遇到。Java给字符串提供了非常丰富的API比如Character.isLetter、isDigit、isLetterOrDigit很多人面试时写判断字符串中是否包含非字母数字字符都是自己一堆正则加循环其实一个字符数组遍历分分钟搞定public static boolean isAlphanumeric(String s) { if (s null || s.isEmpty()) { return false; } for (char c : s.toCharArray()) { if (!Character.isLetterOrDigit(c)) { return false; } } return true; }字符串算法的大类题型还有判断回文串、统计字符出现频率、反转字符串、最长公共前缀、括号匹配。处理这些题的基本功是先把String、StringBuilder、char[]之间的关系理清楚。再强调一遍字符串拼接循环里不要用一定要用StringBuilder不然长字符串拼接就是性能灾难。2.4 递归与分治建立拆解问题的思维模型递归是很多人的“心理阴影”但递归其实是Java基础算法里最有魅力的一块。很多看起来复杂的题比如汉诺塔、二叉树遍历、全排列一旦找到递归结构代码可以写得非常短。我学递归时最核心的心得就一条不要试图在脑子里跑完每一层递归你只需要保证递归三要素成立即可。递归三要素是递归出口base case、递归表达式、返回值处理。以阶乘为例public static int factorial(int n) { if (n 1) { // 出口 return 1; } return n * factorial(n - 1); // 表达式 }还有一点要提醒你能不用递归的地方别硬用。比如求斐波那契数列纯递归的复杂度是O(2^n)n到40就卡死了。用迭代或者记忆化搜索用一个数组保存中间结果直接降到O(n)。这就涉及到算法思维里很重要的“空间换时间”思想。2.5 数据结构基础算法题离不开的容器算法再厉害没有数据结构支撑就是空中楼阁。Java基础算法阶段我建议你按这个顺序把手写一遍数组的动态扩容、单链表的增删改查与反转、栈的入栈出栈、队列的入队出队、HashMap的简单实现、二叉树的层序遍历。这些数据结构在Java集合框架里都有现成实现ArrayList、LinkedList、ArrayDeque、HashMap、TreeMap但你学基础算法时必须自己实现一遍。原因很简单使用别人的轮子不需要知道轮子的辐条怎么装可一旦轮子出问题你就只能干瞪眼。我用一个表来总结我自己学数据结构时的对应关系方便你对照着敲数据结构Java集合对应类手写练习要点动态数组ArrayList扩容、缩容、随机访问单向链表LinkedList节点Node定义、反转、快慢指针栈ArrayDeque/Stack括号匹配、最小栈队列ArrayDeque/Queue循环队列、双端队列哈希表HashMaphash函数、冲突链表、扩容二叉树TreeMap/自定义前中后序、层序、深度3. 动手敲起来用正确的方式暴露问题3.1 先手写实现再调用库函数我看到很多人学算法时有一个很浪费时间的习惯一边看视频一边抄代码抄完就觉得自己会了。下次自己写还是无从下手。正确顺序应当是先关掉视频自己手写写不出来了再去看一眼思路然后关掉重新写。这个过程很痛苦但正是这份痛苦在帮你把算法内化成肌肉记忆。我在面试候选人的时候经常能看到有人熟练地背出Arrays.sort的用法但一问排序原理就沉默。这不是说你不能用库函数实际开发里你用Arrays.sort完全没问题但学习阶段你至少要能够脱离sout、脱离IDE的自动补全把核心逻辑写出来。3.2 调试三板斧打印、断点、日志敲代码一定会遇到“看起来都对运行结果不对”的情况。这时候不要慌用下面三个方法逐层排查。第一个是打印法在循环开始、循环结束、关键变量变化处加System.out.println这是最原始也最直接的方式很适合刚起步时用。第二个是断点调试用IDEA的Debug模式一步一步看变量变化一旦发现某个数不对问题基本就在那一两行附近。第三个是日志级别的排查当你的代码越来越复杂打印语句删不干净时你可以用日志框架或者干脆写一个开关private static final boolean DEBUG true; private static void log(String msg, Object... args) { if (DEBUG) { System.out.printf(msg, args); System.out.println(); } }这样调试完只需要把DEBUG改成false不用一行行删打印语句。这个小技巧是我在一个老项目里学到的实测非常省事。3.3 边界条件测试算法题隐藏的“命门”同一个算法写对核心逻辑只能拿一半分边界条件全过才叫真正写完。给你一个我常用的边界测试清单无论写什么算法题先跑一遍这些用例空数组或null数组单个元素的数组两个元素的数组已经有序的数组完全逆序的数组所有元素都一样含有大量重复元素的数组拿排序来说一个只有两个元素且逆序的数组 [2, 1]就能测出冒泡排序的内层循环范围写得对不对。二分查找则要额外测试target小于所有元素、大于所有元素、不在数组中且应该插入的位置是第一个或最后一个。我强烈建议你给自己的每个手写算法配上一个main方法专门用来跑这些边界用例。这个习惯一旦养成面试手写代码时你会很自然地先和面试官确认边界输入这一点非常加分。3.4 从“跑得对”到“跑得快”复杂度意识写完一个算法别急着庆祝问自己一句这个解法的时间复杂度和空间复杂度是多少如果数组中一亿个元素还能不能跑初学者最容易忽视的问题是代码能跑但复杂度太高。比如在循环里反复调用List.indexOf()或者把字符串相加写在一个大循环里数据量一大就超时。我自己的经验是每次写完代码都先估算一下复杂度当你的解法达到O(n²)时就停下来想想有没有更好的方案。这个习惯会直接影响你后面写业务代码。比如在数据量大的循环里查字典很多人下意识用List而正确的选择从来都是HashMap为什么因为ArrayList的contains是O(n)而HashMap的get是O(1)。这样的差别在千万级数据面前是几秒和几十毫秒的差别。4. 面试与竞赛双线实战刷题的正确姿势4.1 面试向高频基础题吃透思路优先Java开发岗面试最常考的基础算法题我根据自己面试别人的经验和被面试经验整理了这么几道必刷题反转单链表、两数之和、判断括号是否有效、合并两个有序数组、快速排序、二分查找、求最大子数组和、二叉树前中后序遍历。这些题在LeetCode上都有对应原题也都是“leecode必刷基础算法题”里的高频钉子户。刷这些题时不要只背答案。面试官看重的是你的思考过程和代码组织能力。以两数之和为例面试时你要能在短时间内说清楚三种解法暴力O(n²)的思路、用哈希表O(n)的思路、排序加双指针的思路并且分析各自的时间和空间复杂度。你要是张嘴就能说出“我直接双重循环”那基本注定了低分。我见过不少候选人题目刷了三四百道但面评很差原因是背题痕迹太重。你要记住面试考算法不是在考你记忆力而是在看你遇到问题时的解题框架。拿个新的变形题扔给你如果你只会“这题我见过”那基本就废了。4.2 蓝桥杯向Java竞赛题的数字与套路蓝桥杯Java组的题目和前端的“字节题”风格很像喜欢考察数字处理和逻辑推理。诸如进制转换、大数运算、回文数判断、公约数公倍数、日期计算、排列组合都是高频考点。Java在这类题上的优势是提供了BigInteger和BigDecimal还有极其丰富的字符串API。举个例子蓝桥杯里经常出现类似“给定一个正整数n求n的阶乘结果的后三位数字”这种题如果你用int直接算很快溢出。这时候你要么用BigInteger要么用取模运算在每一轮乘法后取后三位后者性能好太多。Java竞赛里这类“取模防溢出”的技巧非常重要你刷题时应该有意识地练习% 1000000007这类常见取模操作。另一个高频方向是图论和动态规划的入门题比如最短路径、背包问题、最长上升子序列。这些题在蓝桥杯省赛里年年出现对算法基础的要求就更高了。我的建议是先把排序、递归、DFS/BFS这些基础打扎实再碰动态规划和图论别一口吃成胖子。4.3 刷题平台怎么用LeetCode与Java题单的高效玩法现在刷算法题首选平台就是LeetCode。打开首页你能看到海量题目不加以筛选地乱刷就是最错误的做法。我的刷题策略是按标签刷先点开数组、链表、字符串、二分查找、排序这些基础标签每个标签内部按通过率从高到低刷20道左右。通过率高的题往往是经典模板题用来建立信心和理解套路都很合适。LeetCode的每道题都有讨论区和官方题解。但你要记住看题解的时机很有讲究自己实在想不出来再看看完合上页面自己重写一遍。直接抄一遍就跑效果约等于零。更合理的复盘方式是写完之后去题解区找那些点赞最高的思路看看自己有没有遗漏的优化方向再决定要不要重写。我还会用一个很笨但很有用的方法维护一份错题本记录题目链接、我的错误思路、正确的解题方向、以及这道题对应的模板。每周固定抽出一天重做错题本里的三道题。实测下来这个方法的遗忘速度远低于单纯刷题。5. 常见问题与踩坑实录我把新手期踩过的坑都写在这里5.1 Java环境配置磨刀不误砍柴工学习算法前先把Java环境配好。这里说的“配好”不是指装个JDK就完事而是要理解环境变量为什么会配错、启动失败怎么排查。多数教程会让你在系统变量里新建JAVA_HOME然后在PATH中添加%JAVA_HOME%\bin。如果你安装了多个JDKPATH里哪个排前面命令行里执行的就是哪个版本这是最常见的“版本不对”问题来源。如果你遇到“java不是内部或外部命令”的提示九成是PATH配置没有生效或拼写错误如果你执行java -version显示的版本和你想用的JDK不一致就检查PATH中JDK路径的先后顺序。还有JAVA_HOME的路径不要加末尾的反斜杠不要在值里加花括号之外的多余空格这些都是新手期极容易踩的坑。5.2 手写算法的高频错误数组越界、死循环、溢出手写算法翻车最多的是三类错误。第一类数组越界。比如遍历数组时用了for (int i 0; i arr.length; i)多出来的那一次必然越界。还有一种隐蔽情况在快排partition或者二分查找中取了arr[i-1]或arr[j1]而没有先判断边界是否合法。第二类死循环。典型场景是二分查找中指针移动条件写反或者快排里i和j移动后忘记重新判断i j。死循环在本地调试时很好发现但面试现场写白板就比较尴尬。我建议你在写完循环后立刻检查循环体内的指针是否一定朝收敛方向移动这是预防死循环最有效的一步。第三类整数溢出。mid (left right) / 2在left和right巨大时会溢出正确答案是写成left (right - left) / 2。还有一个容易忽略的场景进行数字运算类算法题时累加和可能超过int范围记得用long接收中间结果。5.3 心态崩塌自救指南刷了忘、忘了刷怎么办算法学习最挫败的时刻不是不会写而是昨天刚会的题今天又不认识了。这不是你记忆力差是正常现象。我自己的体验是一个算法至少要三次以上的重复接触才能真正内化第一次接触理解思路第二次独立实现第三次在一周后重新默写不出来再看答案。整个过程很像背单词艾宾浩斯遗忘曲线同样适用于算法。所以我强烈建议你使用“隔天复习 每周复盘”的节奏而不是疯狂地一天刷20道新题然后第二天全忘光。另一个行之有效的方法是给别人讲题。你在自己的笔记里用文字讲有条件的在社区发题解讲的过程倒逼你把逻辑漏洞暴露出来比闷头刷十道都有用。6. 相信坚持的力量我给Java学习者的实操路线6.1 按阶段推进别想一口吃成胖子Java基础算法学习我根据自己带人带项目的经验画了一条我验证过多次的路线。第一阶段是打牢Java语法顺序结构、分支、循环、类与对象、集合框架的基本使用这个阶段不必太深入算法。第二阶段是数据结构实现按照我刚才列的单子把数组、链表、栈、队列、哈希表每个手写一遍基础操作。第三阶段集中突击基础算法排序、二分、递归、字符串、双指针这是整条路线的核心阶段耗时也最久建议保持每天至少1小时的独立编码时间。第四阶段进入刷题期LeetCode按标签刷蓝桥杯真题按年份刷这个阶段可以持续到你找到工作或比完赛为止。不要把“学算法”当成一个孤立的负重任务它应当是你日常编码习惯的一部分。每写一道业务题想一想有没有更优的数据结构每看一段框架源码问一句这里用的是哪个算法思想。6.2 建立最小可持续的打卡机制常有人问我“大佬你一天刷多少道题才够”我的回答是一天能稳定完成2道并完全理解坚持三个月的效果远好于周末突击20道。因为算法学习的本质是建立起模式识别能力这种能力需要睡眠、需要间隔、需要长时间积累才能形成。我的打卡方式是每天在待办清单里留一个固定任务“手写一道基础算法题并跑通测试用例”。这个任务足够小不会让人有任何心理压力但又足够具体确保每天都在跟算法打交道。我会在GitHub上建一个仓库把每天的代码提交上去用commit记录来量化自己的坚持。回看commit记录的时候那种“我真的在变强”的实感是支撑我走下去最大的精神动力。6.3 输出是最好的内化写题解、记笔记、讲给你听最后一个建议也是我认为坚持得最久的人都在做的事情输出。你学到一个算法闭卷把它默写出来是初级输出写一篇题解把解题思路、边界测试、复杂度分析都讲清楚是高级输出能把一道题给一个完全不懂的人讲懂是最顶级的输出。我自己最开始学快速排序的时候总觉得理解了直到我在博客里想写一篇快排的文章写到partition交换那一步卡住了才发现自己根本没完全理清。那次之后我彻底理解了“做出来”和“讲清楚”之间的距离。学Java基础算法真正难的不是那些算法本身而是你有没有勇气每天打开编辑器、真实地敲一遍、坦然地面对错误。我带过的新人里进步最快的从来不是天赋最好的而是那个每天雷打不动提交两段代码的人。基础算法没有那么多捷径我相信坚持的力量也希望读到这里的你从今天起动手敲起来等着你的下一篇题解。
返回列表