万字详细面试被吊打的总结(SE->数据结构->MYSQL)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 万字详细面试被吊打的总结(SE->数据结构->MYSQL)

力扣138.随机链表的问题(经典——重要)

我是有思路,但是20分钟,确实我的头脑没有转那么快,没有做出来,我就想到拆分,把它依次连接

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;
 
    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
 
class Solution {
   public static Node copyRandomList(Node head) {
       if (head==null)
        return null;
        Node cur=head;
        while(cur!=null){
            Node a=new Node(cur.val);
            a.next=cur.next;
            cur.next=a;
            cur=cur.next.next;
        }
        Node newhead=head.next;
        cur=newhead;
        Node prev=head;
        while(cur!=null&&cur.next!=null){
            if(prev.random!=null){
                cur.random=prev.random.next;
            }
            prev=prev.next.next;
            cur=cur.next.next;
        }
        if(prev.random!=null){
        cur.random=prev.random.next;}
        cur=newhead;
        prev=head;
 
        while(cur!=null&&cur.next!=null){
            prev.next=prev.next.next;
            cur.next=cur.next.next;
            prev=prev.next;
            cur=cur.next;
        }
            prev.next=null;
     
        return newhead;
    }
}

面试题1:java创建对象有几种方式

1.new 对象这个就不用说了,简单
2.反射 Class对象调用newInstance()接口
 String m = "Node";
 Class clas=Class.forName(m);
 Node t=(Node) clas.newInstance();
3.使用Constructor类的newInstance方法
 Constructor<Node> constructor =Node.class.getConstructor();
        Node t2=constructor.newInstance();
        System.out.println(t2);
这两种都叫反射,事实上Class对象的newInstance在内部也是调用的Constructor这个类
4.Object类实现的clone接口
public class Node implements  Cloneable{
        int val;
        Node next;
        Node random;
 
        public Node(int val) {
            this.val = val;
            this.next = null;
            this.random = null;
        }
//需要重写这个方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
 
    public Node() {
    }
}
 public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, CloneNotSupportedException {
//       String m = "Node";
//       Class clas=Class.forName(m);
//       Node t=(Node) clas.newInstance();
        Node t1=new Node(5);
        Node t2=(Node)t1.clone();
        System.out.println(t1==t2);
 
    }
5.通过序列化来创建对象

面试题2:hashmap的底层数据结构?hashcode重写为什么必须要重写equals?

hashmap(JDK1.7的时候,是使用的拉链法,只有数组和链表

但是JDK1.8的时候,有了数组,链表,红黑树,扩容的时候resize(),红黑树拆分树的节点,小于6个退化成链表)我准备挺多的,但是hashcode这块确实没有看(只能说知道啥意思,但是具体的忘记了)

拿到一个hash值之后,利用key的hashcode重新hash计算在数据中下标。

equals是比较两个对象是否相等,

hashcode用于获取对象的哈希码。

如果equal()方法判断为相等,则他们的hashcode必须返回相同的值,这是因为使用哈希表等数据结构时,会先根据对象的哈希码确定存储位置,然后再使用 equals() 方法进行比较来确保唯一性。

1.如果重写了equals,但是没有重写hashcode()方法,可能会导致对象放入哈希表中,hashcode返回的不是相同的值,从而无法正常操作这个对象

2.当使用哈希集合的时候,由于HashCode返回的不是相同的值,哈希集合也无法判断这两个对象是否相等,从而又可能会导致set 有重复元素.

面试题3:ArrayList和LinkedList的区别

ArrayList基于数组,LinkedList基于双向链表

对于随机访问,ArrayList优于LinkedList

对于插入和删除操作,LinkedList优于ArrayList(详细:LinkedList优于ArrayList头插,因为LinkedList只需要移动指针,但是ArrayList需要搬运元素)

LinkedList中间的插入数据慢,ArrayList中间插入数据较快)LinkedList尾插慢,ArrayList尾插速度快,不需要移动位置

ArrayList的内部

 
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
 
    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;
 
    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
 
    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 
    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access
 
    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;
 
    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

DEFAULT_CAPACITY默认容量为10.

size:当前ArrayList元素个数

elementData:用于存储实际元素的数组

MAX_ARRAY_SIZE: 数组的最大容量,一般为Integer.MAX_VALUE - 8

扩容的触发条件:

1.添加元素:size>elementData的长度

2.使用带有指定初始容量的构造方法,并且该初始容量大于默认容量,也会触发扩容

新容量一般为原先容量的1.5倍

为什么说快排的空间复杂度最优情况下是O(N)*(logN),最坏情况下是O(N^2)?(知识点:堆排序,快排,归并)

第一次Partiation应该是需要对整个数组扫描一遍,做n次比较,他是递归的过程,一层一层的去遍历,那么也就是说我们需要求他的层数,总结点,是一个等比数列求和,每一层是2的等比数列,累加在一起,然后我们使用log ,来求出他的层数,最后就是N*log,但是不好的情况就是N^2,因为他这个时候,就不是一个树,更像是类似于链表,都是连起来的,那么层高就是N,所以复杂度就是O(N^2)

这里首先要全面的写一下堆和快排和归并排序

堆最好是一颗完全二叉树(用数组存储),假如不是,会造成空间浪费)

小根堆/大根堆

根节点都比左右孩子小。(小根堆特性中序遍历,全部顺序。)

建立的是一个大根堆

import java.util.Arrays;
 
public class TestHeap {
    //自己去写一个堆
    public int[] elem;
 
    public int usedSize;
 
    public TestHeap() {
        this.elem = new int[10];
    }
 
    //创建一个大根堆
    public void createHeap(int[] array) {
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
        for (int parent = (usedSize - 1 - 1) / 2; parent >= 0; parent--) {
            //向下调整,需要给的数字,当前父亲位置,结束位置
            shiftDown(parent, usedSize);
        }
    }
 
 
 
    /**
     * @param parent 每颗子树根节点
     * @param len    每个子树的结束位置
     */
    //向下调整,已知孩子下标len-1拿到,父亲节点(len-1-1)/2
    public void shiftDown(int parent, int len) {
        int child = parent * 2 + 1;
        //看他是否有左孩子
        while (child < len) {
            if(child+1<len){
            if(elem[child]<elem[child+1]){
                child=child+1;
            }
            }
            if (elem[child] > elem[parent]) {
                int tmp = elem[parent];
                elem[parent] =elem[ child];
                elem[child] = tmp;
                parent = child;
                child = parent * 2 + 1;
            }else{
                break;
            }
        }
    }
 
 
    //保证仍然原先是大根堆,还是小根堆(当插入一个元素的时候,先插入到最后一个位置),插入后,直接和根比较
    //向上调整
    public void push(int val){
        //检查是否满了
        //2.存数据
        if(isFull()){
            elem=Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize]=val;
        usedSize++;
 
        shiftUp(usedSize-1);
    }
 
 
 
    public  boolean isFull(){
        return usedSize==elem.length;
    }
 
 
 
    public void shiftUp(int child){
        int parent=(child-1)*2;
        while(child>0){
            if(elem[child]>elem[parent]){
                int tmp=elem[child];
                elem[child]=elem[parent];
                elem[parent]=tmp;
                child=parent;
                parent=(child-1)/2;
            }else{
                break;
            }
        }
    }
    //假如只能删除堆顶,还要保持他是一个大根堆的形态(那么就要头和最下面的换,然后去,向下调整)
public void  poll(){
        int tmp =elem[0];
        elem[0]=elem[usedSize-1];
        //让0下标元素和最下面的元素交换,然后再去向下调整(把size--即可)
        elem[usedSize-1]=tmp;
        usedSize--;
        shiftDown(0,usedSize);
 
}
//堆排序
    public void headSort(){
        int end=usedSize-1;
        while(end>0){
            //本身已经是一个大根堆
            int tmp=elem[0];
            elem[0]=elem[end];
            elem[end]=tmp;
            shiftDown(0,end);
            end--;
        }
    }
 
 
 
}

建立堆的时间复杂度约等于O(N),向下调整

关于PriorityQueue的问题(类似于堆,所以,他内部会有一个比较的操作):

1.PriorityQueue放置的元素必须能够比较大小,不能插入无法比较的对象,否则就会抛出ClassCastException(类型转换异常)

倘若非要添加一个自定义类型,你要实现Comparable,这个方法

2.不能插入NULL,不然会抛空指针

3.没有容量限制,内部会自动扩容

4.底层是使用了堆堆数据结构

5.默认是小堆

equal:所有的类继承自Object,所以直接覆写,但是只能比较是否相等

Comparable.compareTo:手动实现接口,对类的侵入性强,要在类的里面,一旦实现,每次用这个类,都有顺序,属于其内部顺序

Comparator.compare:实现一个比较器对象,对类的侵入性弱,可以仅仅写一个类的属性的比较,这样易扩展。

TOP-K问题(找到十个数字中,最小的5个数)

1.排序后取5个数字,就是最小的

2.堆,建立一个小根堆(建立一个堆O(N),弹一个元素要去调整->k*log(N)+O(N)

  static class Imp implements  Comparator<Integer>{
 
          @Override
          public int compare(Integer o1, Integer o2) {
               return o2-o1;
          }
     }
     public  static  int[] smallestK2(int []array,int k){
          
          int []ret=new int[k];
          if(k==0){
               return ret;
          }
          //PriorityQueue必须大于0
          PriorityQueue <Integer>maxHeap=new PriorityQueue<>(new Imp());
          for(int i=0;i<k;i++){
               maxHeap.offer(array[i]);
          }
          for(int i=k;i< array.length;i++){
               int top=maxHeap.peek();
               if(top>array[i]){
                    maxHeap.poll();
                    maxHeap.offer(array[i]);
               }
          }
         
          for(int i=0;i<k;i++){
               ret[i]=maxHeap.poll();
          }
          return ret;
     }

怎么从小到大排序:要求数组本身有序

(堆很多都是反的情况)建立大根堆。

不断调整堆,然后堆顶和堆尾换(最后一个未排序的结果end)

1.交换

2.调整(0下标)

  /**
     * @param parent 每颗子树根节点
     * @param len    每个子树的结束位置
     */
    //向下调整,已知孩子下标len-1拿到,父亲节点(len-1-1)/2
    public void shiftDown(int parent, int len) {
        int child = parent * 2 + 1;
        //看他是否有左孩子
        while (child < len) {
            if(child+1<len){
            if(elem[child]<elem[child+1]){
                child=child+1;
            }
            }
            if (elem[child] > elem[parent]) {
                int tmp = elem[parent];
                elem[parent] =elem[ child];
                elem[child] = tmp;
                parent = child;
                child = parent * 2 + 1;
            }else{
                break;
            }
        }
    }
 
 
//堆排序
    public void headSort(){
        int end=usedSize-1;
        while(end>0){
            //本身已经是一个大根堆
            int tmp=elem[0];
            elem[0]=elem[end];
            elem[end]=tmp;
            shiftDown(0,end);
            end--;
        }
    }

快速排序(N*log(N),好的情况(完全二叉树,正好均匀分割,不稳定的排序)

面试题的回答:

不断去找基准,然后去排序。

public class Sort {
 
    public void  quickSort(int []array){
        quick(array,0, array.length-1);
    }
    private static int parttion(int []array,int left,int right){
        int i=left;
        int key=array[left];
        while(left<right){
            //这里一定要有等于符号
            //测试用例:6,1,2,7,5,3,4,5,10,6    这个情况,left和right是一直不会动动
           //左边的key,要从右边开始先移动
//为什么先移动右边
//答案:为了避免平移交换过程中, pivot自己被交换掉。
            while(left<right&&array[right]>=key){
                right--;
            }
            while(left<right&&array[left]<=key){
                left++;
            }
            swap(array,left,right);
        }
        swap(array,left,i);
        return left;
    }
 
    private static void swap(int[] array, int i, int left) {
        int tmp=array[i];
        array[i]=array[left];
        array[left]=tmp;
    }
 
    private static  void quick(int[]array,int start,int end){
        if(start>=end){
            return ;
        }
        int pivot=parttion(array,start,end);
 
        quick(array,0,pivot-1);//左树
        quick(array,pivot+1,end);//右树
    }
//挖坑法
 private  int pattion1(int []array,int left,int right){
        int tmp=array[left];
        while(left<right){
            while (left<right&&array[right]>=tmp){
                right--;
            }
            array[left]=array[right];
            while(left<right&&array[left]<=tmp){
                left++;
            }
            array[right]=array[left];
        }
        array[left]=tmp;
        return left;
    }
 
//快排非递归
   public   void  quickSort1(int[]array){
        //  使用栈
        Stack<Integer> stack=new Stack<>();
        int start=0;
        int end=array.length-1;
        int pivot=parttion(array,start,end);
        //至少有两个元素才可以入栈
        //保证左边
        if(pivot>start+1){
            stack.push(start);
            stack.push(pivot-1);
        }
        //保证右边
        if(pivot<end-1){
            stack.push(pivot+1);
            stack.push(end);
        }
        while(!stack.isEmpty()){
            end= stack.pop();
            start=stack.pop();
            //弹出后,再去找基准
            pivot=parttion(array,start,end);
            if(pivot>start+1){
                stack.push(start);
                stack.push(pivot-1);
            }
            //保证右边
            if(pivot<end-1){
                stack.push(pivot+1);
                stack.push(end);
            }
        }
    }
 
 
 
}

归并排序(N*log(N),空间复杂度O(N),稳定的排序)

ublic void mergeSort(int []array){
        mergeSortFunc(array,0,array.length-1);
}
private   void  mergeSortFunc(int[]array,int left,int right){
        if(left>=right){
            return;
        }
        int mid=(left+right)/2;
    //这两个相当于遍历到二叉树的最下面了
        mergeSortFunc(array,left,mid);
        mergeSortFunc(array,mid+1,right);
        
        merge(array,left,mid,right);
}
public void merge(int[]array,int left,int mid,int right){
        int s1=left;
        int e1=mid;
        int s2=mid+1;
        int e2=right;
        int []tmpArr=new int[right-left+1];
        int k=0; //tmpArr数组的下标
    while(s1<=e1&&s2<=e2){
        if(array[s1]<=array[s2]){
            tmpArr[k++]=array[s1];
            s1++;
        }else{
            tmpArr[k++]=array[s2];
            s2++;
        }
        k++;
    }
    while(s1<=e1){
        tmpArr[k]=array[s1];
        s1++;
        k++;
    }
    while(s2<=e2){
        tmpArr[k]=array[s2];
        s2++;
        k++;
    }
    //tmpArr放入所有有序的数据
    for(int i= 0;i<k;i++){
        array[i+left]=tmpArr[i];
    }
}
//归并非递归
    public void mergerSort(int[]array){
        int gap=1;
        while (gap<array.length){
            for(int i=0;i<array.length;i=i+gap*2){
                int left=i;
                int mid=left+gap-1;
                int right=mid+gap;
                //这个是进行合并的操作
                if(mid>array.length){
                    mid=array.length-1;
                }
                if(right>array.length){
                    right=array.length-1;
                }
                merge(array,left,mid,right);
            }
            gap=gap*2;
        }
    }
 


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5天前
|
存储 SQL 关系型数据库
MySQL进阶突击系列(03) MySQL架构原理solo九魂17环连问 | 给大厂面试官的一封信
本文介绍了MySQL架构原理、存储引擎和索引的相关知识点,涵盖查询和更新SQL的执行过程、MySQL各组件的作用、存储引擎的类型及特性、索引的建立和使用原则,以及二叉树、平衡二叉树和B树的区别。通过这些内容,帮助读者深入了解MySQL的工作机制,提高数据库管理和优化能力。
|
1月前
|
SQL 关系型数据库 MySQL
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
MySQL慢查询优化、索引优化,是必知必备,大厂面试高频,本文深入详解,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验分享。
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
|
2月前
|
存储 关系型数据库 MySQL
阿里面试:为什么要索引?什么是MySQL索引?底层结构是什么?
尼恩是一位资深架构师,他在自己的读者交流群中分享了关于MySQL索引的重要知识点。索引是帮助MySQL高效获取数据的数据结构,主要作用包括显著提升查询速度、降低磁盘I/O次数、优化排序与分组操作以及提升复杂查询的性能。MySQL支持多种索引类型,如主键索引、唯一索引、普通索引、全文索引和空间数据索引。索引的底层数据结构主要是B+树,它能够有效支持范围查询和顺序遍历,同时保持高效的插入、删除和查找性能。尼恩还强调了索引的优缺点,并提供了多个面试题及其解答,帮助读者在面试中脱颖而出。相关资料可在公众号【技术自由圈】获取。
|
1月前
|
SQL 缓存 关系型数据库
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴因未能系统梳理MySQL缓存机制而在美团面试中失利。为此,尼恩对MySQL的缓存机制进行了系统化梳理,包括一级缓存(InnoDB缓存)和二级缓存(查询缓存)。同时,他还将这些知识点整理进《尼恩Java面试宝典PDF》V175版本,帮助大家提升技术水平,顺利通过面试。更多技术资料请关注公号【技术自由圈】。
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
|
1月前
|
SQL 算法 关系型数据库
面试:什么是死锁,如何避免或解决死锁;MySQL中的死锁现象,MySQL死锁如何解决
面试:什么是死锁,死锁产生的四个必要条件,如何避免或解决死锁;数据库锁,锁分类,控制事务;MySQL中的死锁现象,MySQL死锁如何解决
|
1月前
|
SQL 关系型数据库 MySQL
美团面试:Mysql如何选择最优 执行计划,为什么?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴面试美团时遇到了关于MySQL执行计划的面试题:“MySQL如何选择最优执行计划,为什么?”由于缺乏系统化的准备,小伙伴未能给出满意的答案,面试失败。为此,尼恩为大家系统化地梳理了MySQL执行计划的相关知识,帮助大家提升技术水平,展示“技术肌肉”,让面试官“爱到不能自已”。相关内容已收录进《尼恩Java面试宝典PDF》V175版本,供大家参考学习。
|
2月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
1月前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
下一篇
DataWorks