总结
在清楚了各个大厂的面试重点之后,就能很好的提高你刷题以及面试准备的效率,接下来小编也为大家准备了最新的互联网大厂资料。
//栈2空,需要从栈1取数据
while(!stack1.empty()){
//栈1栈顶入栈栈2
//pop方法取栈顶并出栈,peek方法仅取栈顶
stack2.push(stack1.pop());
}
}
//栈2不空,直接从栈2出栈即可,所以返回栈2栈顶
return stack2.pop();
}
}
* * *
[]( )JZ6 旋转数组的最小数字
============================================================================
题目:[旋转数组的最小数字]( )
* 暴力求解,循环遍历
import java.util.ArrayList;
public class Solution {
//暴力求解,遍历数组
public int minNumberInRotateArray(int [] array) {
//第一个数值开始
int min = array[0];
//循环遍历
for(int i = 1;i<array.length;i++){
//比较最小值
if(array[i] <= min){
min = array[i];
}
}
//返回最小值
return min;
}
}
* 二分查找
import java.util.ArrayList;
public class Solution {
// 二分查找
public int minNumberInRotateArray(int[] array) {
// 数组大小为0
if (array.length == 0)
return 0;
// 定义数组左边界
int left = 0;
// 定义数组右边界
int right = array.length - 1;
// 定义基准
int target;
// 定义中间值
int mid;
// 循环查找,条件为左界小于右界
while (left < right) {
// 去最右边作为基准,随着边界改变而改变
target = array[right];
// 中值随边界变化而变
mid = (left + right) / 2;
// 最右边的值大于中间的
// 本来递增,旋转后得到的非递减
// 所以最小值一定不在后面,但是mid那个可能是最小的
if (array[mid] < target) {
right = mid;
} else if (array[mid] > target) {
// 中值大于最后的值
// 旋转得到表示中值前面的都大于最后的
// 所以中值后的值中才存在最小的
left = mid + 1;
} else {
// 最后一个值和中值相等
// 无法判断,去掉最后一个值,继续
// 因为中值和最后一个相等,去掉最后无碍
right = right - 1;
}
}
// 最后左右边界相等,即为最小值,返回哪一个边界都可
// 返回左边界198ms,返回右边界188ms
// 不懂为什么,但是返回右边界复杂度小
return array[right];
}
}
* * *
[]( )JZ7 斐波那契数列
=========================================================================
题目:[斐波那契数列]( )
public class Solution {
//斐波那契数列,典型递归调用
public int Fibonacci(int n) {
//第0项和第1项,为0和1
if(n==0 || n==1)
return n;
else
//第n项,等于第n-1项和第n-2项的和
//递归调用,自己调用自己
return Fibonacci(n-1) + Fibonacci(n-2);
}
}
* * *
[]( )JZ8 跳台阶
======================================================================
题目:[跳台阶]( )
> 同斐波那契数列,递归调用即可。
public class Solution {
public int jumpFloor(int target) {
//边界条件
if(target>=0 && target <= 2){
return target;
}
//跳到第n级台阶的跳法等于跳到第n-1级台阶的跳法加上跳到第n-2级台阶的跳法
//到了n-1级,就只有一种跳法了
//n-2级,也就剩一种跳法了
//n-2后,要是一级一级跳,就又是在n-1级里面包含了,可以不考虑
return jumpFloor(target-1) + jumpFloor(target-2);
}
}
* * *
[]( )JZ9 变态跳台阶
========================================================================
题目:[跳台阶扩展问题]( )
public class Solution {
public int jumpFloorII(int target) {
//边界条件
if(target == 0 || target == 1){
return 1;
}
//f(n) = f(n-1) + ......+f(0)
//f(n-1) = f(n-2) + ......+f(0)
//相减,所以f(n) = 2*f(n-1)
//所以是2的n-1次幂
return 2*jumpFloorII(target - 1);
}
}
* * *
[]( )JZ10 矩形覆盖
========================================================================
题目:[矩形覆盖]( )
* 递推(11ms)
public class Solution {
//递推法
public int rectCover(int target) {
//边界条件
if(target == 0 || target == 1 || target == 2){
return target;
}
int res = 0;
int first = 1;
int second = 2;
//循环向后求值
for(int i=3;i<=target;i++){
res = first + second;
first = second;
second = res;
}
return res;
}
}
* 递归(389ms)
public class Solution {
//递归法
public int rectCover(int target) {
//边界条件
if(target == 0)
return 0;
if(target == 1)
return 1;
if(target == 2)
return 2;
//递归
return rectCover(target-2) + rectCover(target-1);
}
}
* 记忆递归(备忘录法)(10ms)
1. 核心代码模式
import java.util.*;
public class Solution {
//声明数组,也可以创建数组,但不能初始化在类文件中
int[] bp = new int[200];
//构造函数中创建并且初始化
//利于类的继承
Solution(){
for(int i=0;i<bp.length;i++){
bp[i] = 0;
}
}
//记忆递归,备忘录法
public int rectCover(int target) {
//边界条件
if(target == 1 || target == 2){
//保存数据
bp[target] = target;
}
//当前值没有计算过,考虑bp[0]的值,那个0为边界,要去掉
if(bp[target] == 0 && target != 0){
//递归进行计算
//计算完毕进行保存
bp[target] = rectCover(target-2) + rectCover(target-1);
return bp[target];
}else{
//已经计算过该值,直接返回
return bp[target];
}
}
}
2. ACM模式
package com.hnucm;
import java.util.*;
public class Solution {
// 声明数组
static int[] bp;
//记忆递归,备忘录法
public static int rectCover(int target) {
//边界条件
if(target == 1 || target == 2){
//保存数据
bp[target] = target;
}
//当前值没有计算过,考虑bp[0]的值,那个0为边界,要去掉
if(bp[target] == 0 && target != 0){
//递归进行计算
//计算完毕进行保存
bp[target] = rectCover(target-2) + rectCover(target-1);
return bp[target];
}else{
//已经计算过该值,直接返回
return bp[target];
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n;
bp = new int[200];
for (int i = 0; i < bp.length; i++) {
bp[i] = 0;
}
while (sc.hasNext()) {
n = sc.nextInt();
System.out.println(rectCover(n));
}
}
}
* 动态规划(11ms)
> **动态规划有点像递归填表,和记忆递归和递推差不多,更像递推填表,参照上面的即可。**
* * *
[]( )JZ11 二进制中1的个数
============================================================================
题目:[二进制中1的个数]( )
public class Solution {
/**
* 求负数的补码的方法。 注意: 负数的补码是在其原码的基础上,符号位不变,其余位取反,然后加1
* @param a
* @author lhever 2017年4月4日 下午8:42:47
* @since v1.0
*/
//负数
public static int back(int a)
{
int s = 0;
for (int i = 0; i < 32; i++)
{
// 0x80000000 是一个首位为1,其余位数为0的整数
int t = (a & 0x80000000 >>> i) >>> (31 - i);
if(t == 1){
s++;
}
}
return s;
}
//正数
int m = 0;
public int normal(int a){
if(a%2 == 1)
m++;
if(a/2 == 0)
return m;
else
return normal(a/2);
}
public int NumberOf1(int n) {
if(n>=0){
return normal(n);
}else{
return back(n);
}
}
}
* * *
[]( )JZ12 数值的整数次方
===========================================================================
题目:[数值的整数次方]( )
* 递推(36ms)
public class Solution {
//递推
public double Power(double base, int exponent) {
double s = 1.0;
if(base == 0){
return 0;
}
else if(exponent == 0){
return 1;
}else if(exponent > 0){
for(int i=0;i<exponent;i++){
s *= base;
}
}else{
for(int i=0;i<Math.abs(exponent);i++){
s *= base;
}
s = 1.0/s;
}
return s;
}
}
* * *
[]( )JZ13 调整数组顺序使奇数位于偶数前面
===================================================================================
题目:[调整数组顺序使奇数位于偶数前面]( )
> * **先进先出,采用队列**
> * **遍历数组,加入队列**
> * **遍历数组,改变数组值**
* 队列(时间:400ms,O(n),空间:30MB,O(n))
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param array int整型一维数组
* @return int整型一维数组
*/
public int[] reOrderArray (int[] array) {
// write code here
//先进先出,采用队列
Queue<Integer> q1 = new LinkedList();
Queue<Integer> q2 = new LinkedList();
//遍历每一个数组元素
for(int i=0;i<array.length;i++){
//偶数
if(array[i] % 2 == 0){
//加入队列2
q2.offer(array[i]);
}
//奇数
else{
//加入队列1
q1.offer(array[i]);
}
}
//遍历每一个元素,更换值
for(int i=0;i<array.length;i++){
//队列1没有出完
if(!q1.isEmpty()){
//存储并出队
array[i] = q1.poll();
}
//队列1结束,队列2开始
else{
array[i] = q2.poll();
}
}
return array;
}
}
* 直接交换法(时间:O(n),空间:O(n))
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param array int整型一维数组
* @return int整型一维数组
*/
public int[] reOrderArray (int[] array) {
// write code here
//定义位置
int i = 0;
//创建新数组存储新的
int[] res = new int[array.length];
//遍历每一个,先加入奇数
//array数组中的每一个元素,定义为j
for(int j : array){
//奇数
if(j%2 != 0){
res[i] = j;
i++;
}
}
//加入偶数
for(int j : array){
//偶数
if(j%2 == 0){
res[i] = j;
i++;
}
}
return res;
}
}
* * *
[]( )JZ14 链表中倒数第k个节点
==============================================================================
题目:[链表中倒数第k个节点]( )
import java.util.*;
/*
-
public class ListNode {
-
int val;
-
ListNode next = null;
-
public ListNode(int val) {
-
this.val = val;
-
}
-
}
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @param k int整型
* @return ListNode类
*/
//求链表长度
public int length(ListNode pHead){
//定义长度
int length = 0;
//遍历链表
while(pHead != null){
//长度加一
length++;
//向后遍历
pHead = pHead.next;
}
//返回长度
return length;
}
//返回倒数第k个节点,就是返回以倒数第k个节点为头节点的剩下的链表
public ListNode FindKthToTail (ListNode pHead, int k) {
// write code here
//获得链表长度
int length = length(pHead);
//如果没有第k个节点,链表长度小于k,即k不存在
if(length < k)
return null;
//遍历去掉0 -> (length-k-1)的节点
for(int i=0;i<length-k;i++){
//将指针移到第length-k个节点
//后面还剩下k个,所以这就是倒数第k个节点
pHead = pHead.next;
}
//返回这个节点
return pHead;
}
}
* * *
[]( )JZ15 反转链表
========================================================================
题目:[反转链表]( )
**解答详情请看:[反转链表]( )**
* * *
[]( )JZ16 合并两个排序的链表
=============================================================================
题目:[合并两个排序的链表]( )
有问题
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
//递归、循环,两种解决方式
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode head = null;
ListNode cur = head;
while(list1 != null && list2 != null){
if(list1.val <= list2.val){
cur.next = list1;
list1 = list1.next;
}else{
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
if(cur.next == list1){
cur.next = list1;
}else
cur.next = list2;
return head.next;
}
}
没问题
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
// 递归方式解决
public ListNode Merge(ListNode list1, ListNode list2) {
// 定义头节点
ListNode head = null;
// 有一个链表为空的情况,边界条件,结束条件
// 返回另一个链表
if (list1 == null)
return list2;
else if (list2 == null)
return list1;
//数值小的,加入链表
if (list1.val <= list2.val) {
// 加入
head = list1;
// 递归剩下的链表,继续加入小的
head.next = Merge(list1.next, list2);
} else {
head = list2;
head.next = Merge(list1, list2.next);
}
//返回所有的加入之后的
return head;
}
}
* * *
[]( )JZ17 树的子结构
=========================================================================
题目:[树的子结构]( )
> * **首先遍历大树**
> * **找到结点值相同的结点**
> * **进行判断,以这两个点为根的两个树是否为子结构**
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
//遍历整个树,大树
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
//空树
if(root1 == null || root2 == null)
return false;
//如果当前结点值和子树根节点相同,判断以该结点为根的树是否是子结构
if(root1.val == root2.val){
//判断是否是子结构
if(judge(root1,root2)){
return true;
}
}
//当前结点值不等
//递归遍历左右孩子,有一个是,那就是,所以采用||
return HasSubtree(root1.left,root2) || HasSubtree(root1.right,root2);
}
//判断是否是子结构
public boolean judge(TreeNode node1,TreeNode node2){
//子树已经循环完毕,证明全部匹配,是子结构
if(node2 == null){
return true;
}
//整个树已经循环完毕,未成功匹配完全,不是子结构
if(node1 == null){
return false;
}
//当前结点值相等,那么只需要左右孩子为子结构,那么就为子结构
if(node1.val == node2.val){
//要两个同时为子结构,当前的才是子结构,所以是&&
return judge(node1.left,node2.left) && judge(node1.right,node2.right);
}
return false;
}
}
* * *
[]( )JZ18 二叉树的镜像
==========================================================================
题目:[二叉树的镜像]( )
**方法一:BFS(广度优先搜索)**
**方法二:DFS(深度优先搜索)**
**方法三:中序遍历**
**方法四:递归**
import java.util.*;
/*
-
public class TreeNode {
-
int val = 0;
-
TreeNode left = null;
-
TreeNode right = null;
-
public TreeNode(int val) {
-
this.val = val;
-
}
-
}
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pRoot TreeNode类
* @return TreeNode类
*/
// 镜像变换
public TreeNode Mirror(TreeNode pRoot) {
// write code here
// 递归遍历
if (pRoot == null) {
return null;
}
// 中序遍历
// 将当前结点的右子树当作根节点,将它的子树镜像变换
Mirror(pRoot.right);
// 将当前结点的左右子树整个交换
TreeNode node = pRoot.right;
pRoot.right = pRoot.left;
pRoot.left = node;
// 左子树变成了右子树,将右子树做根节点,将它的子树再变换
Mirror(pRoot.right);
// 返回根节点
return pRoot;
}
}
* * *
[]( )JZ20 包含min函数的栈
=============================================================================
题目:[包含min函数的栈]( )
> **以空间换时间,很常见的方法。**
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
if(stack2.isEmpty() || stack2.peek() >= node){
stack2.push(node);
}else{
stack2.push(stack2.peek());
}
}
public void pop() {
stack1.pop();
stack2.pop();
}
public int top() {
return stack1.peek();
}
public int min() {
return stack2.peek();
}
}
* * *
[]( )JZ21 栈的压入、弹出序列
=============================================================================
题目:[栈的压入、弹出序列]( )
![](https://img-blog.csdnimg.cn/20210616162229446.png)
> **辅助栈进行判断,后面循环,必加入数据,还需要判断栈不为空,还未理解这一点。**
import java.util.*;
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
if(pushA.length == 0 || popA.length == 0 || popA.length != pushA.length){
return false;
}
//辅助栈
Stack<Integer> stack = new Stack<Integer>();
//定义指向出栈数组的指针
int j = 0;
//入栈
for(int i=0;i<pushA.length;i++){
//入栈
stack.push(pushA[i]);
//判断当前栈顶元素是否等于出栈数组元素
while(!stack.isEmpty() && stack.peek() == popA[j]){
//栈顶元素等于出栈元素
//栈顶出栈,指针后移
stack.pop();
j++;
}
}
//判断是否出栈顺序正确
//辅助栈空,正确,不为空,错误
return stack.isEmpty();
}
}
* * *
[]( )JZ22 从上往下打印二叉树
=============================================================================
题目:[从上往下打印二叉树]( )
> * **层次遍历**
> * **广度优先搜索**
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
//先序遍历,深度优先递归,广度优先队列
//深度优先dfs不对,要广度优先搜索bfs
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
//建立链表存储结点值
ArrayList<Integer> array = new ArrayList<Integer>();
//新建队列进行广度优先搜索,层次遍历
Queue<TreeNode> queue = new LinkedList<TreeNode>();
//空树,返回空表
if(root == null){
return array;
}
//不空,使用队列进行层次遍历
//将根结点加入队列,一层一层结点加入
queue.offer(root);
//队列不为空,即树没有遍历完全,无限循环
while(!queue.isEmpty()){
//取出队列头,将头节点的值加入链表
TreeNode node = queue.poll();
array.add(node.val);
//当前结点的左孩子,右孩子都加入,即当前结点的下一层结点全部加入
if(node.left != null){
queue.offer(node.left);
}
if(node.right != null){
queue.offer(node.right);
}
}
return array;
}
}
* * *
[]( )JZ23 二叉搜索树的后序遍历序列
================================================================================
题目:[二叉搜索树的后序遍历序列]( )
public class Solution {
//划分出左右子树
public boolean divideToLeft(int[] s,int left,int right){
//左边界大于右边界,证明全部化分,正确,是二叉搜索树
//递归结束条件
if(left >= right){
return true;
}
//找到后序遍历的根
int root = s[right];
//定义左右子树边界
int i;
//从左边边界遍历到根前一个结点
for(i = left;i<right;i++){
//值大于根的值,证明是根的右子树,跳出循环
if(s[i] > root){
break;
}
}
//边界前是左子树,后是右子树
//遍历右子树,确保后面不存在左子树的值,确保为二叉搜索树
for(int j=i;j<right;j++){
if(s[j] < root){
return false;
}
}
//返回左右子树
return divideToLeft(s,left,i-1) && divideToLeft(s,i,right-1);
}
public boolean VerifySquenceOfBST(int [] sequence) {
//空树
if(sequence.length == 0){
return false;
}
//返回整个数组的树
return divideToLeft(sequence,0,sequence.length-1);
}
}
* * *
[]( )JZ24 二叉树中和为某一值的路径
================================================================================
题目:[二叉树中和为某一值的路径]( )
import java.util.ArrayList;
import java.util.Stack;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
ArrayList<Integer> path = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
if (root == null) {
return res;
}
Path(root, target);
return res;
}
public void Path(TreeNode root, int target) {
//因为FindPath中和 下面程序中都进行了判null操作,root绝对不可能为 null
path.add(root.val);
//已经到达叶子节点,并且正好加出了target
if (root.val == target && root.left == null && root.right == null) {
//将该路径加入res结果集中
res.add(new ArrayList(path));
}
//如果左子树非空,递归左子树
if (root.left != null) {
Path(root.left, target - root.val);
}
//如果右子树非空,递归右子树
if (root.right != null) {
Path(root.right, target - root.val);
}
//这个路径到达叶子结点,去掉这条路径的最后一个结点
//返回父结点,找另一条路径
path.remove(path.size() - 1);
return;
}
}
* * *
[]( )JZ25 复杂链表的复制
===========================================================================
题目:[复杂链表的复制]( )
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021051908394950.png)
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
/*
*解题思路:
*1、遍历链表,复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
*2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
*3、拆分链表,将链表拆分为原链表和复制后的链表
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
//如果链表为空,无需复制,返回空
if(pHead == null) {
return null;
}
//定义从头结点开始指向当前节点的指针
RandomListNode currentNode = pHead;
//1、复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
//遍历整个链表,进行复制
while(currentNode != null){
//定义一个新的克隆节点,值为当前节点的值
RandomListNode cloneNode = new RandomListNode(currentNode.label);
//定义一个原链表的下一个节点
RandomListNode nextNode = currentNode.next;
//将克隆节点插入到当前节点和下一个节点中间
currentNode.next = cloneNode;
cloneNode.next = nextNode;
//跳过加入的克隆节点,将当前指针指向下一个节点,向后遍历
currentNode = nextNode;
}
//重头开始遍历,以pHead为头节点的链表已经改变
currentNode = pHead;
//2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
while(currentNode != null) {
//当前节点的下一个节点即为克隆节点
//克隆节点的随机节点为空则为空,否则为当前节点的随机节点的下一个节点
currentNode.next.random = currentNode.random==null?null:currentNode.random.next;
//当前节点指针要跳过下一个克隆节点,直接跳两个节点,继续指向随机节点
currentNode = currentNode.next.next;
}
//3、拆分链表,将链表拆分为原链表和复制后的链表
//重新重头开始
currentNode = pHead;
//定义拷贝后的头节点从原链表的头节点的下一个节点开始,即第一个克隆节点开始
RandomListNode pCloneHead = pHead.next;
//遍历整个链表
while(currentNode != null) {
//取出克隆节点
RandomListNode cloneNode = currentNode.next;
//将克隆节点取出,将当前节点的指针,指向克隆节点的下一个节点,即下一个原节点
currentNode.next = cloneNode.next;
//将克隆节点的下一个节点指向克隆节点的下下个节点,即下一个克隆节点,为空,则置为空
cloneNode.next = cloneNode.next==null?null:cloneNode.next.next;
//因为当前节点的下一个节点指向下一个原节点了,所以直接下一个节点即可
currentNode = currentNode.next;
}
//就分成了两个链表
//返回克隆链表的头节点
return pCloneHead;
}
}
* * *
[]( )JZ26 二叉搜索树与双向链表
==============================================================================
题目:[二叉搜索树与双向链表]( )
> **思路:将整棵树遍历,中序遍历二叉搜索树,直接得到有序的结点。然后将这些结点的关系重新链接,形成双向链表,后只需要返回第一个结点,即是头结点。**
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
//总方法
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null){
return null;
}
ArrayList<TreeNode> list = new ArrayList<>();
//遍历
Order(pRootOfTree,list);
//返回头结点
return ConVert(list);
}
//中序遍历,保存节点
public void Order(TreeNode root,ArrayList<TreeNode> list){
//递归遍历左子树
if(root.left != null){
Order(root.left,list);
}
//加入当前节点
list.add(root);
//遍历右子树
if(root.right != null){
Order(root.right,list);
}
}
//将链表中排好序的节点,重新链接关系
//返回头结点
public TreeNode ConVert(ArrayList<TreeNode> list){
//如果只有一个数,无需链接
if(list.size() == 1){
return list.get(0);
}else{
//第一个只向后链接
list.get(0).right = list.get(1);
//最后一个只向前链接
list.get(list.size()-1).left = list.get(list.size()-2);
//中间的结点双向链接
for(int i=1;i<list.size()-1;i++){
list.get(i).left = list.get(i-1);
list.get(i).right = list.get(i+1);
}
}
//返回头结点
return list.get(0);
}
}
* * *
[]( )JZ27 字符串的排列
==========================================================================
题目:[字符串的排列]( )
> **就是全排列问题。**
> **思路:先固定一个位置的数字,然后对剩下的数字继续全排列,直到只剩下一个位置固定,证明一次全排列结束,输出这个序列。(递归)**
* 不考虑`字典序输出`和`重复的数值`,只考虑`全排列`
import java.util.ArrayList;
public class Solution {
//保存每一个排列序列
ArrayList<String> list = new ArrayList<>();
//全排列
public void Permutation(char c[],int start,int end){
//只有最后一个没固定,只有一种情况
//一次全排列结束
if(start == end){
list.add(new String(c));
}else{
for(int i = start;i<=end;i++){
//确定首位的数
swap(c,start,i);
//后面的所有数,全排列
Permutation(c,start+1,end);
//避免重复排列
swap(c,start,i);
}
}
}
//交换
public void swap(char c[],int i,int j){
char temp;
if(i != j){
temp = c[i];
c[i] = c[j];
c[j] = temp;
}
}
public ArrayList<String> Permutation(String str) {
char c[] = str.toCharArray();
Permutation(c,0,str.length()-1);
return list;
}
}
* 不考虑`字典序`,考虑`全排列`和`重复值`
import java.util.ArrayList;
public class Solution {
//保存每一个排列序列
ArrayList<String> list = new ArrayList<>();
//全排列
public void Permutation(char c[],int start,int end){
//只有最后一个没固定,只有一种情况
//一次全排列结束
if(start == end){
list.add(new String(c));
}else{
for(int i = start;i<=end;i++){
//后面存在一个相同值
//进行下一个循环
if(!isSwap(c,i,end)){
continue;
}
//无重复值
//执行下述代码
swap(c,start,i);
//后面的所有数,全排列
Permutation(c,start+1,end);
//避免重复排列
swap(c,start,i);
}
}
}
//交换
public void swap(char c[],int i,int j){
char temp;
if(i != j){
temp = c[i];
c[i] = c[j];
c[j] = temp;
}
}
//去重复
public boolean isSwap(char c[],int i,int end){
for(int j=i+1;j<=end;j++){
//i后面的值存在和i重复的
//跳过此次循环
if(c[j] == c[i])
return false;
}
return true;
}
public ArrayList<String> Permutation(String str) {
char c[] = str.toCharArray();
Permutation(c,0,str.length()-1);
return list;
}
}
* 全部考虑
import java.util.*;
public class Solution {
//保存每一个排列序列
ArrayList<String> list = new ArrayList<>();
//全排列
public void Permutation(char c[],int start,int end){
//只有最后一个没固定,只有一种情况
//一次全排列结束
if(start == end){
list.add(new String(c));
}else{
for(int i = start;i<=end;i++){
//跳出此次循环
if(!isSwap(c,i,end)){
continue;
}
swap(c,start,i);
//后面的所有数,全排列
Permutation(c,start+1,end);
//避免重复排列
swap(c,start,i);
}
}
}
//交换
public void swap(char c[],int i,int j){
char temp;
if(i != j){
temp = c[i];
c[i] = c[j];
c[j] = temp;
}
}
//去重复
public boolean isSwap(char c[],int i,int end){
for(int j=i+1;j<=end;j++){
//i后面的值存在和i重复的
//跳过此次循环
if(c[j] == c[i])
return false;
}
return true;
}
public ArrayList<String> Permutation(String str) {
if(str == ""){
return null;
}
char c[] = str.toCharArray();
Permutation(c,0,str.length()-1);
//内置函数,升序排列,字典序
Collections.sort(list);
return list;
}
}
> **字典序法**
> **思路:先给定一个排列,然后找下一个排列顺序。下一个排列顺序要刚好比这个排列大,但是它们中间不能有排列,直到找到排列的左邻没有小于右邻的值为止,即是递减排列了,证明全排结束,全部找到了。**
> **步骤:**
>
> 1. 从右至左,找到左邻小于右邻的第一个数,记录位置`i`和值`c[i]`
> 2. 从右至左,找到第一个比`c[i]`大的数,记录位置`j`和值`c[j]`
> 3. 交换这两个数字
> 4. 然后将`i`后面的值进行从小到大升序排列。
> 5. 因为交换的是第一个比`c[i]`大的值和`c[i]`,所以交换后,`i`后面的值还是从大到小排序的。所以`逆序`就可。
* * *
[]( )JZ28 数组中出现次数超过一半的数字
==================================================================================
题目:[数组中出现次数超过一半的数字]( )
* 哈希法
> **思路:新建一个哈希数组,用于在位置为值的地方存放这个值出现的次数,遍历哈希数组,找出大小比原数组的一半大的数,这个数存放的位置,就是众数的值。**
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
//只有一个元素,直接返回
if(array.length == 1){
return array[0];
}
//采用hash数组,存放出现次数
int hash[] = new int[10001];
//初始化数组
for(int i=0;i<hash.length;i++){
hash[i] = 0;
}
//遍历原数组,将值的个数存在在a中
//存放的位置为值
for(int i=0;i<array.length;i++){
hash[array[i]]++;
}
//找出a中值大于原数组一半的数,证明是众数
//返回a中的位置,即为众数的值
for(int i=0;i<hash.length;i++){
if(hash[i] > (array.length/2)){
return i;
}
}
//都不符合上述情况,返回0
return 0;
}
}
* 候选法
> **思路:将两个数对比,如果这两个数不相同,则一起去掉。所以有两种情况:一种是去掉的是一个众数和一个非众数;另一种是两个非众数。无论是哪一种,如果众数存在,最后剩下的,肯定是众数。**
* * *
[]( )JZ29 最小的K个数
==========================================================================
题目:[最小的K个数]( )
> **主要思路:先对数组进行排序,然后返回前k个数。**
* 内置函数排序(不推荐)
> **因为肯定是要考我们排序算法,直接使用内置函数了,就没有意义了。**
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
ArrayList<Integer> array = new ArrayList<>();
for(int i=0;i<input.length;i++){
array.add(input[i]);
}
Collections.sort(array);
for(int i=0;i<k;i++){
list.add(array.get(i));
}
return list;
}
}
* 快速排序(推荐)
> **思路:二分法进行分区,递归快排。**
`单向扫描快排法`
> **思路:从左到右扫描,小于基准,不动,看下一个;大于基准,将当前值和最后一个交换,最后一个值固定,右指针前移;继续对比当前值和基准,循环进行。**
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
//对数组排序
quickSort(input,0,input.length-1);
//取前k个加入list
for(int i=0;i<k;i++){
list.add(input[i]);
}
return list;
}
// 单向扫描分区
public static int partition(int s[], int start, int end) {
//确定基准
int base = s[start];
//左指针
int i = start + 1;
//右指针
int j = end;
//从左到右扫描
while (i <= j) {
//这个数小于基准,不变,看下一个
if(s[i]<=base) {
i++;
}else {
//大于基准,放到后面
swap(s,i,j);
//不在考虑放在后面的数字,固定
j--;
}
}
//无论如何,j最后指向的都是小于base的最后一个数
swap(s, start, j);
return j;
}
//快排
public static void quickSort(int[] s,int start,int end) {
//相等代表只有一个,排序无意义
if(start < end) {
int mid = partition(s,start,end);
//递归排序分区好的左右两边
quickSort(s, start, mid-1);
quickSort(s, mid+1, end);
}
}
//交换
public static void swap(int[] s,int i,int j) {
int temp = 0;
temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
`双向扫描快排法`
> **思路:定义第一个数为基准;从左到右找第一个比基准大的数字,从右到左找第一个比基准小的数字;如果左指针小于右指针,交换他们,循环往复;直到左指针大于等于右指针结束;交换基准和右指针位置,分区完成。然后递归对每一个子块快排即可。**
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
//对数组排序
quickSort(input,0,input.length-1);
//取前k个加入list
for(int i=0;i<k;i++){
list.add(input[i]);
}
return list;
}
//双向扫描分区
public static int partition(int[] s,int start,int end) {
//确定基准
int base = s[start];
//i从前开始
int i = start + 1;
//j从后开始
int j = end;
//只要i在j前
while(i<=j) {
//找到第一个比基准大的i
while(i<=j && s[i]<=base) {
i++;
}
//找到第一个比基准小的j
while(i<=j && s[j]>=base) {
j--;
}
if(i<j) {
swap(s,i,j);
}
}
swap(s,start,j);
return j;
}
//快排
public static void quickSort(int[] s,int start,int end) {
//相等代表只有一个,排序无意义
if(start < end) {
int mid = partition(s,start,end);
//递归排序分区好的左右两边
quickSort(s, start, mid-1);
quickSort(s, mid+1, end);
}
}
//交换
public static void swap(int[] s,int i,int j) {
int temp = 0;
temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
> **还有`三分快排法`,感兴趣可以继续探索。**
另外两种方法博客链接:[快速排序的三种分区方法(整理)]( )
* * *
[]( )JZ30 连续子数组的最大和
=============================================================================
题目:[连续子数组的最大和]( )
* 动态规划法(填表法)
> **思路:定义一个表dp,根据原来数组的位置去填表,dp\[i\]表示以第i个位置为结尾的子数组的最大和。所以dp\[i\]等于array\[i\]和array\[i\]+dp\[i-1\]之间的最大值。每一个dp填入后,最大和就是这张表中的最大值。**
public class Solution {
//动态规划法,填表法
public int FindGreatestSumOfSubArray(int[] array) {
//dp[i]表示以i结尾的子数组的最大和
int dp[] = new int[array.length];
//第一个值只有一个值,所以就是第一个值
//可以保证无论数是有正负,还是全负全正,都通用
dp[0] = array[0];
//初始化最大值为第一个
int max = dp[0];
//一重循环,时间复杂度O(n)
for(int i=1;i<array.length;i++){
//下一个值为当前的值加上上一个dp和当前值之间的最大值
//如果上一个dp是负数,那么当前值最大,即子数组开头换了位置一样
dp[i] = Math.max(array[i],array[i]+dp[i-1]);
//最大值,就是当前最大值和dp之比,动态变化
max = Math.max(max,dp[i]);
}
//返回子数组最大和
return max;
}
}
* 备忘录法(查表法)
> **思路:基本使用动态规划法的题目,备忘录法同样可以解决。思路几乎一样,就是一个查表,一个填表。**
* 变量判断法
> **思路:设置一个变量tmp = 0。如果tmp+array\[i\] < 0, 说明以i结尾的不作贡献,重新赋值tmp = 0,否则更新tmp = tmp + array\[i\]。最后判断tmp是否等于0, 如果等于0, 说明数组都是负数,选取一个最大值为答案**
* * *
[]( )JZ31 整数中1出现的次数(从1到n整数中1出现的次数)
============================================================================================
题目:[整数中1出现的次数(从1到n整数中1出现的次数)]( )
* 暴力解法,空间复杂度较高
> **思路:计算出一个数中1的个数,然后分别求每一个数中1的个数,求和即可。**
public class Solution {
//返回1-n这n个数中1的个数
public int NumberOf1Between1AndN_Solution(int n) {
int s = 0;
for(int i=1;i<=n;i++){
s += NumofOne(i);
}
return s;
}
//返回这个数中1的个数
public int NumofOne(int n){
int num = 0;
String s = n + "";
char c[] = s.toCharArray();
for(int i=0;i<s.length();i++){
if(c[i] == '1'){
num++;
}
}
return num;
}
}
* * *
[]( )JZ32 把数组排成最小的数
=============================================================================
题目:[把数组排成最小的数]( )
* 全排列
> **思路:把数组里的整数元素,看成一个字符串整体。进行字符串全排列,然后进行升序排序,第一个即是最小值。**
import java.util.*;
public class Solution {
//保存每一个排列序列
ArrayList<String> list = new ArrayList<>();
//得到组成的最小值
public String PrintMinNumber(int [] numbers) {
String str = "";
//输入为空,输出为空
if(numbers.length == 0){
return str;
}
//将整数数组变为字符串数组
String s[] = new String[numbers.length];
for(int i=0;i<numbers.length;i++){
s[i] = numbers[i] + "";
}
//进行全排列
Permutation(s,0,s.length-1);
//字典序
Collections.sort(list);
//最小值
str = list.get(0);
//返回最小数
return str;
}
//全排列
public void Permutation(String s[],int start,int end){
//只有最后一个没固定,只有一种情况
//一次全排列结束
if(start == end){
//把结果加入list中保存
String str = "";
for(int i=0;i<s.length;i++){
str += s[i];
}
list.add(str);
}else{
for(int i = start;i<=end;i++){
//有重复,跳出此次循环
if(isEquals(s,i,end)){
continue;
}
//固定开头的值
swap(s,start,i);
//后面的所有数,全排列
Permutation(s,start+1,end);
//避免重复排列
swap(s,start,i);
}
}
}
//是否有重复
//去重复
public boolean isEquals(String s[],int i,int end){
for(int j=i+1;j<=end;j++){
if(s[i].equals(s[j])){
return true;
}
}
return false;
}
//交换
public void swap(String s[],int i,int j){
String temp;
temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
* * *
[]( )JZ33 丑数
======================================================================
题目:[丑数]( )
* 穷举法(枚举法)
> **思路:第一个丑数是1,下一个丑数肯定是前一个丑数乘以2/3/5的最小值。即第n个丑数肯定可以由第i个丑数乘以2/3/5得到。所以使用这个规律进行枚举。因为得到最小的之后,还有两个剩下的值需要继续比,所以需要三个指针,指向不同的第i个丑数。**
import java.util.*;
public class Solution {
//控制一个数组列举丑数,然后返回这个丑数
public int GetUglyNumber_Solution(int index) {
//第0个丑数,不合理,返回0
if(index == 0){
return 0;
}
//创建数组保存丑数
//为了不浪费空间,刚好到所求的第n个丑数
int res[] = new int[index];
//第一个是1
res[0] = 1;
//定义三个表示丑数*2*3*5
int p2 = 0;
int p3 = 0;
int p5 = 0;
//循环放入丑数
for(int i=1;i<index;i++){
//下一个丑数是三个里面最小的
res[i] = Math.min(res[p2]*2,Math.min(res[p3]*3,res[p5]*5));
//三个指针向后移动
if(res[i] == res[p2]*2){
p2++;
}
if(res[i] == res[p3]*3){
p3++;
}
if(res[i] == res[p5]*5){
p5++;
}
}
//返回最后一个丑数,即是所求
return res[index-1];
}
}
* * *
[]( )JZ34 第一个只出现一次的字符
===============================================================================
题目:[第一个只出现一次的字符]( )
* 数组法
> **思路:在这个值的位置,放置它出现的次数。但是有点问题,使用map得到值增加有点问题。所以改成了ASC码和数组保存其出现的次数。**
import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
//asc码的长度多1
int map[] = new int[128];
//遍历得到每个字符出现次数
for(int i=0;i<str.length();i++){
map[str.charAt(i)]++;
}
//遍历,找到第一个出现次数只有一次的字符
for(int i=0;i<str.length();i++){
if(map[str.charAt(i)]==1){
//返回位置
return i;
}
}
//没有只出现一次的字符
return -1;
}
}
* 哈希法
> **思路:由于在这个位置保存其出现的次数有问题,而题目只需要我们判断第一个出现一次的字符的位置,所以可以把出现一次的看做无重复,把多次出现的,记作重复,采用boolean进行判断。**
import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
//创建哈希表
HashMap<Character,Boolean> map = new HashMap<>();
//遍历判断其是否重复出现
for(int i=0;i<str.length();i++){
//重复出现
if(map.get(str.charAt(i)) != null){
//设置true,表示重复出现
map.put(str.charAt(i),true);
}else{
//第一次出现
map.put(str.charAt(i),false);
}
}
//重新遍历原字符串
//找出第一个不重复出现的字符的位置
for(int i=0;i<str.length();i++){
//map中key为这个字符的,不重复
if(map.get(str.charAt(i)) == false){
//返回其在原数组中的位置
return i;
}
}
//都不符合,返回-1
return -1;
}
}
* * *
[]( )JZ35 数组中的逆序对
===========================================================================
题目:[数组中的逆序对]( )
* 暴力解法
> **思路:以当前数字固定,找出它后面比它小的数字的个数,这些数字就可以和它组成这么多逆序对。然后遍历数组,使固定的数字不同。得到的所有数之和即为逆序对总数。因为有双重循环,所以对于10^5来说,一定超时了。**
public class Solution {
//所有的逆序对
public int InversePairs(int [] array) {
int res = 0;
for(int i=0;i<array.length-1;i++){
res += NumsOfSmall(array,i);
}
return res;
}
//在后面中小于这个数的数字个数
public int NumsOfSmall(int[] array,int i){
int s = 0;
for(int j=i+1;j<array.length;j++){
if(array[i] > array[j]){
s++;
s %= 1000000007;
}
}
return s;
}
}
* 归并排序法
> **思路:先分解数组,直到不能再分,然后进行对比两个数组,排序,将小的先放入,大的后放入,大的还要计算逆序数。`只要前一个数组的当前值大于后一个数组的值,那么在前一个数组中,位于这个数后面的所有数,都可以和后一个数组的这个数组成逆序对。`最后返回逆序对总数。**
public class Solution {
//记录逆序对总数
private int res;
//归并排序
private void MergeSort(int[] array, int start, int end){
if(start>=end)return;
//分界点
int mid = (start+end)/2;
//分成两个数组
MergeSort(array, start, mid);
MergeSort(array, mid+1, end);
//进行合并,排序,并记录逆序数
MergeOne(array, start, mid, end);
}
//合并,排序,记录逆序数
private void MergeOne(int[] array, int start, int mid, int end){
//保存排序后的数组
int[] temp = new int[end-start+1];
//两个指针,指向两个数组的第一个数值
int k=0,i=start,j=mid+1;
while(i<=mid && j<= end){
//如果前面的元素小于后面的不能构成逆序对
if(array[i] <= array[j])
temp[k++] = array[i++];
else{
//如果前面的元素大于后面的
//那么在前面元素之后的元素都能和后面的元素构成逆序对
temp[k++] = array[j++];
//计算逆序对数量
res = (res + (mid-i+1))%1000000007;
}
}
//合并第一个数组
while(i<= mid)
temp[k++] = array[i++];
//合并第二个数组
while(j<=end)
temp[k++] = array[j++];
//改变数组位置,进行排序
for(int l=0; l<temp.length; l++){
array[start+l] = temp[l];
}
}
//调用方法,计算数量
public int InversePairs(int [] array) {
MergeSort(array, 0, array.length-1);
return res;
}
}
* * *
[]( )JZ36 两个链表的第一个公共结点
================================================================================
题目:[两个链表的第一个公共结点]( )
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
//双重遍历,寻找第一个相同的节点
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
//保存第二个链表
ListNode temp = pHead2;
//两个链表都不为空
if (pHead2 != null && pHead1 != null) {
//pHead1不为空,进入循环
while (pHead1 != null) {
//pHead2不为空,进入循环,双重循环
while (pHead2 != null) {
//两个相等,返回任意一个都行
if (pHead2 == pHead1) {
return pHead2;
}
//不等,第一个链表不变,第二个向右继续
pHead2 = pHead2.next;
}
//一轮过后,第二个链表指针指向了末尾
//需要重新指向开头
pHead2 = temp;
//第一个链表向右,开始下一轮
pHead1 = pHead1.next;
}
}
//没有,返回空
return null;
}
}
* * *
[]( )JZ38 二叉树的深度
==========================================================================
题目:[二叉树的深度]( )
**方法一:分治法,先求左子树,后求右子树(递归DFS)**
> 分治法简介:**求一个规模为n的问题,先求左边规模大约为n/2的问题,再求右边规模大约为n/2的问题,然后合并左边,右边的解,从而求得最终解**。具体可参考归并排序。
> 步骤:
>
> * 求 pro(left, rigth) -> int
> * 先求pro(left, (left+right)/2) -> left
> * 再求pro((left+right)/2 + 1, right) -> right
> * merge(left, right) -> result
> 求二叉树的最大深度,我们不必管函数具体是怎么实现的。
> 所以最终结果为 **max( 头结点左子树的最大深度, 头结点右子树的最大深度)+1**
> 步骤:
>
> * 求TreeDepth(TreeNode pRoot)->int
> * 先求 TreeDepth(pRoot.left) ->left
> * 再求TreeDepth(pRoot.right) ->right
> * return **Math.max(left, right) + 1**
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
//求树的深度,根结点到叶结点的最长路径
public int TreeDepth(TreeNode root) {
//树不存在,返回0
if(root == null){
return 0;
}
//找出左子树的深度
int left = TreeDepth(root.left);
//找出右子树的深度
int right = TreeDepth(root.right);
//返回左右子树大的深度加一,就是最长的路径,即整个树的深度
return Math.max(left,right) + 1;
}
}
**方法二:层次遍历(队列BFS)**
> 非递归:求最大深度,可用队列。因为要满足先进先出的特性。
>
> * 初始化:一个队列queue<TreeNode\*> q, 将root节点入队列q
> * 如果队列不空,做如下操作:
> * 弹出队列头,保存为node,将node的左右非空孩子加入队列
> * 做2,3步骤,知道队列为空
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
//层次遍历,广度优先搜索BFS
public int TreeDepth(TreeNode root) {
//空树,返回0
if(root == null){
return 0;
}
Queue<TreeNode> queue = new LinkedList();
TreeNode nlast = null;
TreeNode last = root;
int depth = 0;
queue.offer(root);
while(!queue.isEmpty()){
TreeNode cur = queue.poll();
if(cur.left != null){
queue.offer(cur.left);
nlast = cur.left;
}
if(cur.right != null){
queue.offer(cur.right);
nlast = cur.right;
}
if(cur == last){
depth++;
last = nlast;
}
}
return depth;
}
}
* * *
[]( )JZ39 平衡二叉树
# 难道这样就够了吗?不,远远不够!
提前多熟悉阿里往年的面试题肯定是对面试有很大的帮助的,但是作为技术性职业,手里有实打实的技术才是你面对面试官最有用的利器,这是从内在散发出来的自信。
备战阿里时我花的最多的时间就是在学习技术上,占了我所有学习计划中的百分之70,这是一些我学习期间觉得还是很不错的一些学习笔记
我为什么要写这篇文章呢,其实我觉得学习是不能停下脚步的,在网络上和大家一起分享,一起讨论,不单单可以遇到更多一样的人,还可以扩大自己的眼界,学习到更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。
今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!
![阿里一面就落马,恶补完这份“阿里面试宝典”后,上岸蚂蚁金服](https://img-blog.csdnimg.cn/img_convert/80acb7db86f0f49d876b1656f8a1396d.webp?x-oss-process=image/format,png)
![阿里一面就落马,恶补完这份“阿里面试宝典”后,上岸蚂蚁金服](https://img-blog.csdnimg.cn/img_convert/8e530b4829ae1020f39e28e653ab2e70.webp?x-oss-process=image/format,png)
![image.png](https://img-blog.csdnimg.cn/img_convert/753ec902f24d6a5a52fd6afb2e95efc9.webp?x-oss-process=image/format,png)
> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)收录**
**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**
lass ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
//双重遍历,寻找第一个相同的节点
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
//保存第二个链表
ListNode temp = pHead2;
//两个链表都不为空
if (pHead2 != null && pHead1 != null) {
//pHead1不为空,进入循环
while (pHead1 != null) {
//pHead2不为空,进入循环,双重循环
while (pHead2 != null) {
//两个相等,返回任意一个都行
if (pHead2 == pHead1) {
return pHead2;
}
//不等,第一个链表不变,第二个向右继续
pHead2 = pHead2.next;
}
//一轮过后,第二个链表指针指向了末尾
//需要重新指向开头
pHead2 = temp;
//第一个链表向右,开始下一轮
pHead1 = pHead1.next;
}
}
//没有,返回空
return null;
}
}
==========================================================================
题目:二叉树的深度
方法一:分治法,先求左子树,后求右子树(递归DFS)
分治法简介:求一个规模为n的问题,先求左边规模大约为n/2的问题,再求右边规模大约为n/2的问题,然后合并左边,右边的解,从而求得最终解。具体可参考归并排序。
步骤:
- 求 pro(left, rigth) -> int
- 先求pro(left, (left+right)/2) -> left
- 再求pro((left+right)/2 + 1, right) -> right
- merge(left, right) -> result
求二叉树的最大深度,我们不必管函数具体是怎么实现的。
所以最终结果为 max( 头结点左子树的最大深度, 头结点右子树的最大深度)+1
步骤:
- 求TreeDepth(TreeNode pRoot)->int
- 先求 TreeDepth(pRoot.left) ->left
- 再求TreeDepth(pRoot.right) ->right
- return Math.max(left, right) + 1
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
//求树的深度,根结点到叶结点的最长路径
public int TreeDepth(TreeNode root) {
//树不存在,返回0
if(root == null){
return 0;
}
//找出左子树的深度
int left = TreeDepth(root.left);
//找出右子树的深度
int right = TreeDepth(root.right);
//返回左右子树大的深度加一,就是最长的路径,即整个树的深度
return Math.max(left,right) + 1;
}
}
方法二:层次遍历(队列BFS)
非递归:求最大深度,可用队列。因为要满足先进先出的特性。
- 初始化:一个队列queue<TreeNode*> q, 将root节点入队列q
- 如果队列不空,做如下操作:
- 弹出队列头,保存为node,将node的左右非空孩子加入队列
- 做2,3步骤,知道队列为空
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
//层次遍历,广度优先搜索BFS
public int TreeDepth(TreeNode root) {
//空树,返回0
if(root == null){
return 0;
}
Queue<TreeNode> queue = new LinkedList();
TreeNode nlast = null;
TreeNode last = root;
int depth = 0;
queue.offer(root);
while(!queue.isEmpty()){
TreeNode cur = queue.poll();
if(cur.left != null){
queue.offer(cur.left);
nlast = cur.left;
}
if(cur.right != null){
queue.offer(cur.right);
nlast = cur.right;
}
if(cur == last){
depth++;
last = nlast;
}
}
return depth;
}
}
难道这样就够了吗?不,远远不够!
提前多熟悉阿里往年的面试题肯定是对面试有很大的帮助的,但是作为技术性职业,手里有实打实的技术才是你面对面试官最有用的利器,这是从内在散发出来的自信。
备战阿里时我花的最多的时间就是在学习技术上,占了我所有学习计划中的百分之70,这是一些我学习期间觉得还是很不错的一些学习笔记
我为什么要写这篇文章呢,其实我觉得学习是不能停下脚步的,在网络上和大家一起分享,一起讨论,不单单可以遇到更多一样的人,还可以扩大自己的眼界,学习到更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。
今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!
[外链图片转存中…(img-ayp4E2Fm-1715596303413)]
[外链图片转存中…(img-uSbdbpNy-1715596303413)]
[外链图片转存中…(img-3sGeDfaO-1715596303413)]