序言
今天有同事问,两年的Java后端开发应达到什么水平?讨论一番,趁着余热,简要记录一下,技术栈JAVA,鉴于博主水平有限,如有不周之处,欢迎各位读者拍砖
算法能力为什么要摆在第一位?
第一,这是后端开发能力的根基。作为初级软件开发人员,你的本职工作就是玩内存,使用最小的时间复杂度、空间复杂度实现系统功能。有算法能力作为内力保证,就像天龙八部里面的乔大帮主,无论什么招式,在你的手里就是发挥出与众不同的能量;
第二,这直接关联到你的薪水。务实一点,现在哪个大厂面试不要求算法呢?20分钟内手写算法题目且BUG FREE,是最基础的要求。
那如何提升算法能力呢?
无它,努力刷LeetCode!但是,刷题讲究方法:
第一,讲究由易到难,循序渐进,建议有一定做题经验后,按主题刷题,难度选Middle即可;
第二,及时总结经验,形成产出。博主挑选了部分经典算法题,总结如下:
双指针
• LRU算法:使用链表+Map实现,队头是在容量不足时候,会首先出队的数据;所以凡是初次插入或被访问,都会从队列中先移出,再放入队尾
• 三数之和:锁定第一位,后两位采用双指针(一个在头,一个在尾),去重的关键是找到相邻数,值不相同
• 四数之和:锁定第一位和第二位,后两位采用双指针(一个在头,一个在尾),去重的关键是找到相邻数,值不相同
• 盛最多水的容器:容器取决于短的边,稳住长边,短的边迭代,取面积比较
滑动窗口
• 最长无重复字符串:Map记录<字符,最新下标>,窗口的左侧是上一个出现在map里的字符下标+1,右侧是遍历字符串的I,实时计算长度
• 数组中连续数字之和为K:算出前K 个元素的前缀和dp[],left到right之和为dp[right+1] - dp[left],两层for循环遍历
排序
• 插入排序的本质:插入排序的本质是,从尾部往首部看,当前要插入的元素>=当前元素I,则找到插入位置为I+1,否则,当前元素往后搓一位
• 数组中逆序对总和:
○ 归并排序先分别megersort左、右子集,再merge子集,自底向上的过程
○ 在merge时候统计逆序对总和
• 快速排序:自顶向下的过程,partition先获取mid,然后再分别左、右两边排序(quicksort)
• 堆排序:在java里就是PriorityQueue,往构造函数内传Comparator比较器,排序的依据可在外部数据结构中,只要比较的时能找到,比如Map
• 下一个字典序排列:从后往前找相邻的顺序对,定位小数,再从后往前找,定位大数,进行交换后,排序后方数
• 搜索旋转数组:本质上也是变种的二分查找,mid后必然有一段是有序的,若不在当前范围,则找另一段
• 升序数组中查找给定值的开始、结束位置:做两次二分查找,第一次拿left,第二次拿right
• 最大的第K个数:快速排序
• 最大的K个数:优先队列
栈:能记录下遍历前的信息
• 字符串解码:使用栈,数字、字符串、左括号均存下;遇到右括号出栈字符串、数字、左括号,存入字符串;出栈到空
• 接雨水:单调栈,栈顶元素最小,当前值大于栈顶时,将栈顶元素出队,计算当前填充值,直到小于栈顶元素,入队
• 每日温度:单调栈,栈顶元素最小,当前值大于栈顶时,将栈顶元素出队,记录下当前值与出栈元素之差,直到小于栈顶元素,入队
数组
• 除自身外数组乘积:以当前下标为中心,将左、右两边之和相乘
• 跳跃游戏:维护一个最远端的值,遍历数组更新
• 合并区间:先做Arrays.sort()对数组排序,然后遍历数组,将结果加入使用链表数组,最后转换为数组
链表
• 排序链表:插入排序
• 合并两个有序链表:简单的归并排序,声明头结点dummy,next指向两个有序链表头较小的node,直到两个链表有一个是null,退出,接上非null的链表
• 合并多个排序链表:声明小顶堆,定义头结点最小的链表放堆顶,每次堆顶出队,再入队(出队链表的下一个结点),直到堆空
二进制
• 比特位计数:高位加1,与之前生成的数组组合,可以生成新序列:// 0生1,0-1生2-3,0-3生4-7,0-7生8-15,等等
BFS
• 课程表(拓扑排序):
○ 每门课程表记录自己的入度Int[],入度代表先修课程的数量,入度为0代表本门课程可修,无前置课程
○ 使用数组链表,记录课程间的关系,获取入度为0的课程,入队列
§ List<Integer>[] union = new ArrayList[numCourses];
○ 使用队列,执行入度为0课程的后续结点入度-1,并实时判断入度为0的结点入队列
• 在二维矩阵,标记下标时,(M,N)可以使用一个int值表示, X = M*矩阵列数+N
DFS
• 排列组合题:括号的生成((())):核心是只要左括号大于等于右括号,该迭代可以继续,dfs关注好剪枝条件
• 组合总和:搜索回溯,剪枝的关键是判断剩余量大于0,去重的关键是数组做排序,dfs时候记录下当前下标
• 数组全排列:排列过的元素移到数组的前方,记录下当前的下标,回溯时候再移回来
• N 皇后问题:声明数组array,第n行的array[n]列存皇后,专注于当前行,往上、往左上、往右上方向,不存在皇后,则当前存皇后,继续下一行
树
• 后序遍历,注意退栈后若右树存在且没处理过,则访问右树
• 层次遍历,注意保存当前队列长度后,再行出队;万万不可边出队,边计算长度
• 将二叉树原地换成链表:采取前序遍历,用栈存储右、左子结点,出栈时候,接到root结点后(视为根访问)
• 反转二叉树,迭代使用BFS,出队根节点,反转左、右子树,入队子树
• 检验标准二叉树,dfs返回树的深度,同时比较左、右子树相差值
• 检验搜索二叉树,使用中序遍历,看是否是递增序列,有相同值的结点不算
• 树的最大路径,dfs返回当前结点作为子树的最大值,result=Max(全局最大,当前结点作为根结点的深度)
• 前序和中序生成树:前序第一个是根,在中序找到相应位置,左侧是左儿子,右侧是右儿子
动态规划(关键是状态转移方程)
• 最长回文字符串、回文子串个数:dp[I,j] = (dp[i+1][j-1] or j-i<2 ) && s[i] ==s[j]
• 零钱凑整:A[amount] = (min) A[amount-coin[i]] +1,min的获取需要通过迭代来实现,不涉及递归
• 不同路径:dp[i][j]=dp[i-1][j]+dp[i][j-1];
• 单词拆分:dp[i]= dp[j] && check(s[j…i] )
• 乘积最大子数组:维护当前下标对应的最大值,最小值:dp[i][0]=Math.max(dp[i-1][0]*nums[i],nums[i] if(nums[i]>0)
• 打家劫舍:f是选当前结点,g是不选当前结点,选当前结点:g(L) + g(R)+ root.val ;不选当前结点:Max(g(L),f(L))+Max(g(R)+f(R))
• 最大正方形:以当前结点为正方形右下角的最大长度:dp[i][j]=Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
• 最长递增子序列:dp[i]=Math.max(dp[i],dp[j]+1); dp[i]代表当前下标,递增序列长度
• 通配符匹配:若通配符是*,dp[i][j]=dp[i][j-1]||dp[i-1][j];通配符是*或者点, dp[i][j]=dp[i-1][j-1];
• 分割两个子集和相同:0-i个元素中任选,值为target,dp[i][j]=dp[i-1][j]|| dp[i-1][j-nums[i]];
• 目标和:符号可正可负,所以整体的取值范围为[-sum,sum],dp[i][j]=dp[i-1][j-nums[i]]+dp[i-1][j+nums[i]];
问自己两个问题
JDK是什么?JAVA提供的开发工具包,特别是Java的基础类库,是前浪大佬们给你造好的轮子。
那我们后浪应该怎么做?建议“三步走”:会用轮子,理解轮子,再造轮子。如何理解这句话?举个简单的例子:
想不起来的话,贴心的博主已经帮你准备好了,开始阅读吧
问自己两个问题
分别从Java程序、JVM内存模型、处理器、CPU,分析一下吧
深入了解的话,建议阅读《深入理解Java内存模型》,程晓明版本,本书的优点是简明扼要,薄薄的一本,静下心来一天一口气读完,相信你会收益颇丰。比周志明的大部头《深入理解JAVA虚拟机》,好太多。
找不到本书,可给博主留言或发邮件,chengkevinhuang@163.com
作为Java程序提升系统性能的利器,开多线程是最普遍的方式,不过博主入门Go语言的goroutine之后,感觉原生的语言级并发还是好用太多,学有余力的读者可深入了解。
问自己两个问题
想不起来的话,贴心的博主已经帮你准备好了,开始阅读吧
你和新手最大的差距在于,会用好轮子了,博主总结了三个轮子的使用方式:SpringBoot框架下的Redis、Kafka、ES的使用。
走过路过,不要错过
面试的另一个分水岭是,数据库理解么?
问自己三个问题
贴心的博主已经帮你整理好了
咱们稍微务虚一下吧,上面都是初级开发工程师必备的知识点,如果想要成为一个资深的工程师,那你和天天解BUG的他们,区别在哪里呢?思想!设计思路,架构思维,冰冻三尺,非一日之功
博主一家之言,读者辩证看待即可
心态,这个有点形而上学了,但确实很重要!浮云难能终蔽日,风物长宜放眼量
总结 | 正解 |
---|---|
开放心态 | 面对复杂环境,勇于拥抱变化 |
严谨表达 | 思考后再表述,说话过脑子 |
提升学历 | 能力提升下限,学历提升上限 |
学习方法 | 做题要有标准答案,提出问题,在文章中寻找到答案,形成自己的思考 |
深耕领域 | 选定长期发展的业务领域与技术栈,选择比努力更重要,战略的缺失无法用战术的勤奋弥补 |