数据结构和算法 高频面试题,2024年最新java大厂面试题百度云

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

// 快速排序是不占用额外空间的排序

public void quickSort(int[] nums, int left, int right){

// 递归需要终止条件

if(left >= right){

return;

}

int l = left;

int r = right;

// 基准值默认为左边指针的值

int benchmark = l;

// 用来交换的临时变量

int tempValue;

while(l < r){

// 一定要右边先走,否则会出错

while(l < r && nums[r] >= nums[benchmark]){

r -= 1;

}

while(l < r && nums[l] <= nums[benchmark]){

l += 1;

}

// 找到后两个指针对应的值互换

tempValue = nums[l];

nums[l] = nums[r];

nums[r] = tempValue;

}

// 循环结束时,left 肯定与 right 相同,而非 left > right

tempValue = nums[l];

nums[l] = nums[benchmark];

nums[benchmark] = tempValue;

// 开始向两边二分遍历排序,其中不包括原基准值

quickSort(nums, left, l - 1);

quickSort(nums, r + 1, right);

}

2.4 插入排序


1 简述插入排序的流程

  1. 将未排序的数组分为两部分,一部分是左边的有序数组,另一部分是右边的无序数组;

  2. 判断无序数组的首位在有序数组的位置,然后插入进去,以此类推,实现数组的排序。

2 手写插入排序算法

public int[] insertSort(int[] nums){

// 插入排序的关键是一个有序列和一个无序列

for(int i = 0 + 1; i < nums.length; i++){

int curIndex = i;

// 先将无序数组的最左边的值保存到一个临时变量中

int value = nums[i];

while(curIndex > 0 && value < nums[curIndex - 1]){

nums[curIndex] = nums[curIndex - 1];

curIndex -= 1;

}

nums[curIndex] = value;

}

return nums;

}

2.5 希尔排序


1 简述希尔排序的流程

其实就是增量版的插入排序,期间不断减小增量,使得待排序数组变得大部分都是有序的,以此类推,完成数组的排序。

代码参考:传送门

理论知识参考:传送门

2 手写希尔排序算法

// 第三次修改于 2021.9.10

public int[] shellSort(int[] nums){

int increase = nums.length;

// 通过和直接插入排序对比可以看到,直接插入排序就是 increase = 1, i = 0 的希尔排序

while(increase > 1){

// 增量就用 2 吧

increase = increase / 2;

// 每一轮增量需要进行 increase 组插入排序

for(int i = 0; i < increase; i++){

// 每一组的插入排序如下,插入排序中第一个数肯定是有序的,所以就从第二个数开始

// 下面就是插入排序的代码

for(int j = i + increase; j < nums.length; j += increase){

int curIndex = j;

int value = nums[curIndex];

while(curIndex > i && value < nums[curIndex - increase]){

nums[curIndex] = nums[curIndex - increase];

curIndex -= increase;

}

nums[curIndex] = value;

}

}

}

return nums;

}

2.6 选择排序


1 简述选择排序的流程

遍历数组一次,将其中的最大值与数组最后一位进行互换,然后遍历除最后一位的剩余元素,并将其中的最大值与数组的倒数第二位进行交换,以此类推,完成数组的排序。

2 手写选择排序算法

// 选择排序的效率甚至比冒泡还要差

public int[] selectSort(int[] nums){

// 第一个 for 循环表示的是换位的次数

int assist;

for(int i = 0; i < nums.length - 1; i++){

int maxIndex = 0;

// 第二个 for 循环就是为了找到当前无序数组的最大值

for(int j = 1; j < nums.length - i; j++){

if(nums[j] > nums[maxIndex]){

maxIndex = j;

}

}

// 找到最大值后,则将该值与无序数组最后一位交换

assist = nums[nums.length - i - 1];

nums[nums.length - i - 1] = nums[maxIndex];

nums[maxIndex] = assist;

}

return nums;

}

2.7 堆排序


1 简述堆排序的流程

堆排序实际上也是一种选择排序,它通过每次建立最大堆得到数组的最大值,并将该值的位置与数组的最后一位进行交换,以此类推,实现数组的排序。

2 堆的概念

堆是用数组实现的完全二叉树,它有最大堆和最小堆两种形式,所谓最大堆,即父节点的值比每一个子节点的值都要大,并且这个属性对堆中的每一个节点都成立。根据这一属性,那么最大堆总是将其中的最大值存放在树的根节点。

不过虽然堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index[0] 的位置,但是最小的元素则未必是最后一个元素,唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。

用数组来实现树相关的数据结构也许看起来有点古怪,但是它在时间和空间上都是很高效的。

接下来用一个如下的例子来介绍,其中该数组实现的是一个最大堆,不需要用到额外的空间。

[ 10, 7, 2, 5, 1 ]

但当我们不允许使用指针,那么我们怎么知道哪一个节点是父节点,哪一个节点是它的子节点呢?问得好!节点在数组中的位置 index 和它的父节点以及子节点的索引之间有一个映射关系。比如i 是节点的索引,那么下面的公式就给出了它的父节点和子节点在数组中的位置:

parent(i) = floor((i - 1)/2)

left(i) = 2i + 1

right(i) = 2i + 2

注意 right(i) 就是简单的 left(i) + 1,左右节点总是处于相邻的位置。同时并不是每一个最小堆都是一个有序数组!要将堆转换成有序数组,需要使用堆排序。

3 手写堆排序算法

// 堆排序,二次总结于 2021.9.10

public void heapSort(int[] nums){

// 构造大根堆

createMaxHeap(nums);

int size = nums.length;

while(size > 1){

swap(nums, 0, size - 1);

size -= 1;

adjustMaxHeap(nums, 0, size);

}

}

// 构造大根堆(从子节点向上推父节点)

// 只要根据大根堆的子节点总是小于父节点这一充分必要条件来判断即可

public void createMaxHeap(int[] nums){

for(int i = 0; i < nums.length; i++){

// 当前插入的索引

int currentNode = i;

// 父结点索引

int fatherNode = (currentNode - 1)/2;

// 如果当前插入的值大于其父结点的值,则交换值,并且将索引指向父结点

// 然后继续和上面的父结点值比较,直到不大于父结点,则退出循环

// 因为 fatherNode 的值最小也是为 0,所以不需要判断 fatherNode >= 0

while(nums[fatherNode] < nums[currentNode]){

swapPosition(nums, fatherNode, currentNode);

// 需要不断向上判断

// 将当前索引指向父索引

currentNode = fatherNode;

fatherNode = (currentNode - 1)/2;

}

}

}

// 将剩余的数构造成大根堆(从父节点向下推子节点)

public void adjustMaxHeap(int[] nums, int fatherNode, int size){

int maxNode;

int leftNode = 2 * fatherNode + 1;

int rightNode = 2 * fatherNode + 2;

// 此时,就是考虑到 rightNode 为被根节点交换的节点的问题,这里的 size 是减过 1 的

while(leftNode < size){

if(nums[leftNode] < nums[rightNode] && rightNode < size){

maxNode = rightNode;

}else{

maxNode = leftNode;

}

// 没有这个 break,尽然还报错了

if(nums[maxNode] < nums[fatherNode]){

break;

}

swap(nums, maxNode, fatherNode);

// 将索引指向孩子中较大的值的索引,因为孩子中较小的值的索引没有变换,所以其子节点都没有变换,这个时候只需要考虑较大的值的索引即可

fatherNode = maxNode;

leftNode = 2 * fatherNode + 1;

rightNode = 2 * fatherNode + 2;

}

}

public void swapPosition(int[] nums, int x, int y){

int tempValue = nums[x];

nums[x] = nums[y];

nums[y] = tempValue;

}

2.8 归并排序


1 简述归并排序的流程

即不断使用分治的思路进行排序。

参考文献:传送门

2 手写归并排序算法

// 2021.8.1 自己写出来一遍

class Solution {

public int[] sortArray(int[] nums) {

divide(nums, 0, nums.length - 1);

return nums;

}

public void divide(int[] nums, int left, int right){

if(left >= right){

return;

}

int mid = left + (right - left)/2;

// 怎么分,就怎么合

// 不断向下递归,重要的是后面归并的过程

// 下面两个是分治中分的过程

divide(nums, left, mid);

divide(nums, mid + 1, right);

// 这个则是分治中治的过程

conquer(nums, left, mid, right);

}

public void conquer(int[] nums, int left, int mid, int right){

int[] tempArray = new int[right - left + 1];

// 第一部分是[left, mid],第二部分是[mid + 1, right]

int index1 = left;

int index2 = mid + 1;

int index = 0;

// 跳出循环时肯定是两个 index 中的其中一个大于临界值,不可能两个同时大于临界值

while(index1 <= mid && index2 <= right){

// 如果这里判断条件是 < 的话,那么该排序方式就不是稳定的

if(nums[index1] <= nums[index2]){

tempArray[index++] = nums[index1++];

}else{

tempArray[index++] = nums[index2++];

}

}

// 一个循环结束,可能出现的结果是要不 index1 大于 mid 了,要不 index2 大于 right 了,不可能两者同时都大于

// 如果 index1 小于临界值,则说明 index2 是大于临界值的

if(index1 <= mid){

// 从左到右依次是:原数组、原数组的拷贝起始位置、目标数组、目标数组的起始位置、拷贝的数据的长度

System.arraycopy(nums, index1, tempArray, index, mid - index1 + 1);

}

if(index2 <= right){

System.arraycopy(nums, index2, tempArray, index, right - index2 + 1);

}

System.arraycopy(tempArray, 0, nums, left, right - left + 1);

}

}

3 其它算法

=========================================================================

1 你怎么理解深度优先遍历

深度优先遍历是优先往纵向深入挖掘,主要思想是从图中的一个未访问的顶点开始,沿一条路走到底,然后从这条通路的尽头回退到上一个节点,再从另一条路走到底,不断递归重复这个过程,直到所有节点都遍历完成;

2 你怎么理解广度优先遍历

广度优先遍历就是按层来遍历节点,在实现上,广度优先遍历需要一个队列来保持访问过的节点的顺序,以便按照这个顺序来访问这些节点的邻接节点。

3 说说动态规划的思想

  • 想到最值,想到动态规划;
  • 想到动态规划,首先想到暴力解法;
  • 想到暴力解法,就想到将所有的子结构都列出来;

总结

面试前的“练手”还是很重要的,所以开始面试之前一定要准备好啊,不然也是耽搁面试官和自己的时间。

我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。

面试题及解析总结

三年Java开发,刚从美团、京东、阿里面试归来,分享个人面经

大厂面试场景

三年Java开发,刚从美团、京东、阿里面试归来,分享个人面经

知识点总结

三年Java开发,刚从美团、京东、阿里面试归来,分享个人面经

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
的邻接节点。

3 说说动态规划的思想

  • 想到最值,想到动态规划;
  • 想到动态规划,首先想到暴力解法;
  • 想到暴力解法,就想到将所有的子结构都列出来;

总结

面试前的“练手”还是很重要的,所以开始面试之前一定要准备好啊,不然也是耽搁面试官和自己的时间。

我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。

面试题及解析总结

[外链图片转存中…(img-jJ7ph8Oq-1713338603395)]

大厂面试场景

[外链图片转存中…(img-bqH9unmR-1713338603396)]

知识点总结

[外链图片转存中…(img-ET8Y9mf1-1713338603396)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-Rl5FIYnu-1713338603396)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值