面试复习(刷题+基础+项目)

233 阅读16分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

面试复习。加油。


一、算法刷题(刷几道中等的 mid)

1.最长递增子序列问题M300

题目描述

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

🌸「示例:」

示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1

解答

定义dp数组:dp[i] 表示以 nums[i] 这个数结尾的最⻓递增⼦序列的⻓度。

class Solution {
    public int lengthOfLIS(int[] nums) {
        // 定义dp数组:dp[i] 表示以 nums[i] 这个数结尾的最⻓递增⼦序列的⻓度。
        int[] dp = new int[nums.length];
        Arrays.fill(dp, 1);

        for (int i = 0; i < nums.length; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]){
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }

        Arrays.sort(dp);
        return dp[dp.length - 1];
    }
}

2.最大子数组和问题 E53

题目描述

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

🌸「示例:」

示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:
输入:nums = [1]
输出:1

示例 3:
输入:nums = [5,4,-1,7,8]
输出:23

解答

class Solution {
    public int maxSubArray(int[] nums) {
        //定义dp数组:dp[i]表示以nums[i]结尾的最大和连续子数组
        int[] dp = new int[nums.length];
        Arrays.fill(dp, Integer.MIN_VALUE);

        dp[0] = nums[0];
        for(int i = 1; i < nums.length; i++){
            dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
        }

        Arrays.sort(dp);
        return dp[dp.length - 1];
    }
}

3.最长公共子序列问题M1143

题目描述

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

🌸「示例:」

示例 1:
输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace" ,它的长度为 3 。

示例 2:
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。

示例 3:
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0

解答

class Solution {
    int[][] dp;
    
    public int longestCommonSubsequence(String text1, String text2) {
        dp = new int[text1.length()][text2.length()];
        for(int[] ints : dp){
            Arrays.fill(ints, -1);
        }

        return dp(text1, 0, text2, 0);
    }

    int dp(String text1, int i, String text2, int j){
        if(i == text1.length() || j == text2.length()){
            return 0;
        }


        if(dp[i][j] != -1){
            return dp[i][j];
        }

        if(text1.charAt(i) == text2.charAt(j)){
            return 1 + dp(text1, i + 1, text2, j + 1);
        } else {
            dp[i][j] =  max(
                dp(text1, i + 1, text2, j),
                dp(text1, i, text2, j + 1),
                dp(text1, i + 1, text2, j + 1)
            );
            return dp[i][j];
        }
    }

    int max(int a, int b, int c){
        return Math.max(a, Math.max(b, c));
    }
}

4.和为k的子数组M560

题目描述

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数

🌸「示例:」

示例 1:
输入: nums = [1,1,1], k = 2
输出: 2

示例 2:
输入: nums = [1,2,3], k = 3
输出: 2

解答

解法1

class Solution {
    public int subarraySum(int[] nums, int k) {
        int count = 0;

        for (int i = 0; i < nums.length; i++) {
            int sum = 0;
            
            for (int j = i; j < nums.length; j++) {
                sum += nums[j];
                if (sum == k) {
                    count++;
                    continue;
                }
            }
        }
        return count;
    }
}

5.赛马

64 匹马,8 个赛道,找出前 4,采用什么策略 & 最少要几次?(字节老喜欢问这个了,不过这次问的老细了,尤其是第10和第11次是怎么选的,为啥要多跑那一次什么的)

image.png

第一步:淘汰一半,64 进 32(共 8 场比赛)

第二步:每组第一名进行比赛,32 进 10(共 1 场比赛)

第三步:可能可以结束 (共 1 场比赛)

第四步:一定结束 (共 1 场比赛)

6.最小路径和M64

题目描述

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明: 每次只能向下或者向右移动一步。

image.png

解答

image.png

// 典型的动态规划问题

//时间复杂度 O(M×N) : 遍历整个 grid 矩阵元素。
//空间复杂度 O(1)O(1) : 直接修改原矩阵,不使用额外空间。
class Solution {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] dp = new int[m][n];
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 && j == 0){
                    continue;
                } else if (i == 0){
                    grid[i][j] = grid[i][j - 1] + grid[i][j];
                } else if (j == 0){
                    grid[i][j] = grid[i - 1][j] + grid[i][j];
                } else {
                    grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];
                }
            }
        }
        return grid[m - 1][n - 1];
    }

7.整数反转M7

题目描述

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

如果反转后整数超过 32 位的有符号整数的范围 [−231,  231 − 1] ,就返回 0。

假设环境不允许存储 64 位整数(有符号或无符号)。

示例 1:
输入:x = 123
输出:321

示例 2:
输入:x = -123
输出:-321

示例 3:
输入:x = 120
输出:21

示例 4:
输入:x = 0
输出:0
 
提示:
-231 <= x <= 231 - 1

解答

class Solution {
    public int reverse(int x) {
        int rev = 0;
        while(x != 0){
            if (rev < Integer.MIN_VALUE / 10 || rev > Integer.MAX_VALUE / 10){
                return 0;
            }

            // 弹出 x 的末尾数字 digit
            int digit = x % 10;
            x /= 10;

            // 将数字 digit 推入 rev 末尾
            rev = rev * 10 + digit;
        }
        return rev;
    }
}

8.无重复字符的最长子串M3

题目描述

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1

示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

解答

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if (s.length() == 0) return 0;

        HashMap<Character, Integer> map = new HashMap<>();
        int max = 0;
        int left = 0;//滑动窗口左指针

        for (int i = 0; i < s.length(); i++) {
            //1、首先,判断当前字符是否包含在map中,
            //如果不包含,将该字符添加到map(字符,字符在数组下标)

            //2、如果当前字符 ch 包含在 map中,此时有2类情况:
            if (map.containsKey(s.charAt(i))) {
                left = Math.max(left, map.get(s.charAt(i)) + 1);
            }

            map.put(s.charAt(i), i);
            max = Math.max(max, i - left  +1);
        }

        return max;
    }
}

9.两数相加M2

题目描述

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

image.png

解答

class Solution2 {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        // 新链表头指针,用来指向头结点,返回结果。
        ListNode pre = new ListNode(0);
        // 可移动的一个指针
        ListNode cur = pre;
        // 进位数
        int carry = 0;

        while (l1 != null || l2 != null) {
            int x = l1 != null ? l1.val : 0;
            int y = l2 != null ? l2.val : 0;

            int sum = x + y + carry;

            // 计算进位数
            carry = sum / 10;
            // 实际存入链表的数
            sum = sum % 10;
            cur.next = new ListNode(sum);

            cur = cur.next;

            if (l1 != null){
                l1 = l1.next;
            }
            if (l2 != null){
                l2 = l2.next;
            }
        }

        if (carry == 1){
            cur.next = new ListNode(1);
        }

        return pre.next;
    }
}

class ListNode {
    int val;
    ListNode next;
    ListNode() {}
    ListNode(int val) { this.val = val; }
    ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

10.三数之和M15

题目描述

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

示例 2:
输入:nums = []
输出:[]

示例 3:
输入:nums = [0]
输出:[]

解答



二、基础(对照简历复习复习)

Java基础

熟悉 Java 基础知识,如面向对象、并发编程、异常处理、集合类、Java8新特性等

面向对象

为什么重写equals()就一定要重写hashCode()方法?

blog.csdn.net/xl_1803/art…

image.png

image.png

JVM

类加载机制 image.png

双亲委派机制 image.png

image.png

并发编程

多线程

线程相关的基本方法有 wait(等待)、notify(唤醒)、notifyAllsleep(睡眠)、join(加入)、yield(让步)等,这些方法控制线程的运行,并影响线程的状态变化

image.png

VSReentrantLocksynchronized
显式或隐式通过lock方法加锁,unlock方法解锁。隐式获取和释放锁
属于API级别的JVM级别的
是否公平可以指定fair参数来决定非公平锁
出现锁竞争竞争失败时可以阻塞等待,也可以通过trylock方法直接返回退出竞争失败时只能阻塞等待
等待机制Conditionwait/notify等待机制
底层实现同步非阻塞,采用乐观并发策略同步阻塞,采用悲观并发策略

image.png

image.png

线程池

(1)基础

// ThreadPoolExecutor 的构造方法
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
   ........................
}
序号参数说明
1corePoolSize核心线程数量
2maximumPoolSize最大线程数量
3keepAliveTime当前线程数大于corePoolSize时,空闲线程的存活时间
4unitkeepAliveTime的时间单位
5workQueue任务队列,被提交但尚未被执行的任务存放的地方
6threadFactory线程工厂,用于创建线程
7handler任务拒绝策略

(2)工作流程?

  • 如果正在运行的线程数量少于核心线程数,线程池就会立刻创建线程并执行该线程任务。否则,该任务放入阻塞队列中。
  • (关于阻塞队列)阻塞队列已满 且 正在运行的线程数量少于 最大线程数时,线程池创建非核心线程并立刻执行该线程任务。否则,拒绝执行该任务并抛出异常。
  • (线程任务执行完毕后)该任务被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行。
  • (关于空闲时间)线程处于空闲状态的时间超过 空闲线程存活时间时,正在运行的线程数量超过 核心线程数时,停止该空闲线程。所以,线程池中的所有线程任务都执行完之后,线程池会收缩到 核心线程数大小。

IMG_9605.heic

(3)拒绝策略?

  • 直接抛出异常。
  • 移除最早的一个线程任务。
  • 丢弃当前的线程任务而不作任何处理。
  • 自定义拒绝策略。

(4)5种常用的线程池

  • 可缓存
  • 固定大小
  • 可任务调度
  • 单个线程
  • 足够大小

异常处理

Q1:return 和 finally的执行顺序 ?

答: return 先执行,将方法停下后执行 finally ,最后再执行 return 将值返回。

public class Return_Finally {

    public static void main(String[] args) {
        System.out.println(A.a());
    }

}
class A {
    public static int a() {
        int i = 1;
        try{
            // return 先执行,将方法停下后执行 finally
            // 最后再执行 return 将值返回
            return i;
        }finally {
            System.out.println("f1==>>"+i);
            ++i;
            System.out.println("f2==>>"+i);
        }
    }
}

结果:

image.png

Q2:列举几个受检异常?

答: 除了Exception中的RuntimeException及RuntimeException的子类以外,其他的Exception类及其子类(例如:IOExceptionClassNotFoundException)都属于可查异常。

这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

集合类

Q1:HashMap 和 TreeMap ?

(1)对hash算法的理解?

把任意长度的输入,通过hash算法变成固定长度的输出。存在hash冲突问题。 hash冲突理论上不可避免,只能尽量避免。比如抽屉原理(10个苹果9个抽屉,一定有一个抽屉里的数量大于1)。好的hash算法应该考虑的点:效率高计算hash值,不可逆推出原文,分布均匀。

(2)HashMap中存储数据的结构?

数组 + 链表 + 红黑树。每个数据单元是一个Node结点,有hash、key、value、next字段(形成链表使用)。默认初始长度16,懒加载机制,第一次put时创建。负载因子默认0.75,扩容阈值16乘以0.75=12。链表转化为红黑树?2个条件,链表长度达到8,数组长度达到64(否则会扩容一次)。hasu字段值是key的hashcode值吗?不是,二次加工得到的,加大散列程度。为什么引入红黑树?解决哈希冲突导致链化严重的问题,影响查询效率。特殊的二叉排序树。什么情况下会扩容?达到扩容阈值的时候,位移运算16、32、64。

(3)TreeMap对集合中的元素根据键排序,默认按照key升序

排序场景?

(1)list接口

ArrayList(基于 Object 数组)

LinkedList(基于双向链表)(插入和删除快、查询慢)

linkedlist_node.png

(2)set接口(不可重复)

Set接口是Collection的子接口,set接口没有提供额外的方法。

HashSet(底层基于 HashMap 实现、无序)

LinkedHashSet(继承HashSet,底层使用 LinkedHashMap存储元素)

(3)map接口

HashMap(数组+链表+红黑树、线程不安全)(使用频率最高)⭐️

image.png

Java8新特性

设计模式

1 单例模式

常见写法有懒汉模式(线程安全)、饿汉模式、静态内部类、双重校验锁。

(1)懒汉模式(线程安全)
public class LazySingleton {
    // 私有的静态(属于类)对象,能够很好地保证单例对象的唯一性。
    private static LazySingleton instance;

    private LazySingleton() {
    }

    // 加锁的静态方法获取该对象,线程安全。
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}
(2)饿汉模式

类加载器完成后该类的实例便已经存在于JVM中了。

实现如下:

public class HungrySingleton {
    private static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance(){
        return instance;
    }
}
(3)静态内部类

静态内部类在JVM中是唯一的。

public class Singleton {

    // 静态内部类:对象实例的定义和初始化 放在内部类中进行
    private static class SingletonHolder {
        private static final Singleton  INSTANCE = new Singleton();
    }

    private Singleton(){}

    public static final Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}
(4)双重校验锁(懒汉模式优化)
public class Lock2Singleton {
    // 对象锁:保障初始化时对象的唯一性
    private volatile static Lock2Singleton singleton;

    public Lock2Singleton() {}
    
    public static Lock2Singleton getInstance() {
        if (singleton == null) {
            // synchronized方法锁:保障操作的唯一性
            synchronized (Lock2Singleton.class) {
                if (singleton == null){
                    singleton = new Lock2Singleton();
                }
            }
        }
        
        return singleton;
    }
}

Linux

熟悉 Linux 的基本使用

计算机基础

熟悉计算机网络的 OSI 和 TCP/IP 分层模型以及 HTTP 等基本网络通信协议

数据库

熟练掌握 Mysql 数据库的使用以及常见优化手段(比如索引、SQL优化、读写分离&分库分表)、熟悉Redis基本使用,了解Oracle数据库使用

事务

事务问题

幻读?

sql操作多个事务竞争存在三个问题:脏读(T1读取到T2事务未提交的数据,T2回滚则T1读取到最终不一定存在的数据)、幻读、不可重复读(2个事务同时执行,不同时刻读取同一行数据可能结果不同)。

  • 数据库的事务的概念与特性(ACID, 然后说了下隔离级别)
  • 为什么要有数据库事务?

image.png

隔离级别:为了解决多个并行事务竞争,导致数据安全问题的一种规范。

image.png

索引

  • MySQL 的索引有什么优缺点?

优点:

  • 高效查找(最主要原因)
  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
  • 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

缺点:

  • 占用空间。降低表的更新速度。
  • 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
  • 建索引对写性能有什么影响?

INSERT、UPDATE 和 DELETE 需要更多时间,因为不仅要更新数据,而且还要更新索引。

  • 索引为什么要用 B+ 树?(树更矮,层数低)
  • 相对于二叉树,层级更少,搜索效率高;
  • 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
  • 相对Hash索引,B+tree支持范围匹配及排序操作
  • 如果用其他结构,B+ 或者 红黑树 的所有节点已经全部存内存里,那性能上还存在差距吗?
  • 了解 MySQL 的 explain 命令吗?

获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序。

EXPLAIN 执行计划各字段含义:(重点字段是type、possible_key、key、key_len)

  • 自增 id 和唯一 id,在性能上有差距吗?

uuid有16个字节,比int(4 byte)和bigint(8 byte)占用更多存储空间

其实在innodb存储引擎下,自增长的id做主键性能已经达到了最佳。不论是存储和读取速度都是最快的,而且占的存储空间也是最小。

为了存储和查询性能应该使用自增长id做主键。

为了全局的唯一性,应该用uuid做索引关联其他表或做外键。

www.jb51.net/article/236…

其它(优化等)

执行SQL响应比较慢,你有哪些排查思路?

image.png

  • 慢查询日志定位sql,分析sql执行计划。优化sql语句走索引或者增加索引。

  • 表切分。水平切分(千万级别)、垂直切分(按列切分)

  • 读写分离

  • 查询频繁的热点数据放到redis缓存中

框架

熟练掌握 Spring、Spring MVC、SpringBoot、MyBatis、Spring Security等主流开发框架

Spring

(1)Spring中的Bean是线程安全的吗? 有状态的单例bean才会存在线程安全问题。

image.png

(2)用到的设计模式?

image.png

image.png

分布式

  • 熟悉 SpringCloud 常见组件的使用和部分原理

  • 熟悉分布式下的常见理论 CAP、BASE

image.png

  • 熟悉分布式事务(2PC、3PC、TCC)、配置中心(Nacos)、分布式 id (UUID、Snowflake)的使用及原理

  • 了解RPC框架(Dubbo)

  • MQ(流量削峰、应用解耦、异步处理)

MQ

(1)什么是MQ? 分布式应用之间实现异步通信的方式。 三部分组成:

  • 生产者(生产消息,消息的发起)
  • 消息服务端(消息队列,存储消息)
  • 消费者(消费消息,处理业务逻辑)

(2)应用场景

  • 流量削峰(保证高可用。大流量缓冲,如订单系统流量高峰期)
  • 应用解耦(订单、物流、库存、支付)(不同系统使用不同框架或编程语言,提高系统的灵活性)
  • 异步处理(用户登陆发送验证码、支付成功的通知)(实时性要求不高)

(3)如何选择? Kafka:数据量大,吞吐量比较高。 RocketMQ:可靠性要求高。

工具

前端

三、项目