博客记录-day148-力扣+面试

一、力扣

1、旋转图像

48. 旋转图像

java 复制代码
class Solution {
    public void rotate(int[][] matrix) {
        // 设矩阵行列数为 n
        int n = matrix.length;
        // 起始点范围为 0 <= i < n / 2 , 0 <= j < (n + 1) / 2
        // 其中 '/' 为整数除法
        for (int i = 0; i < n / 2; i++) {
            for (int j = 0; j < (n + 1) / 2; j++) {
                // 暂存 A 至 tmp
                int tmp = matrix[i][j];
                // 元素旋转操作 A <- D <- C <- B <- tmp
                matrix[i][j] = matrix[n - 1 - j][i];
                matrix[n - 1 - j][i] = matrix[n - 1 - i][n - 1 - j];
                matrix[n - 1 - i][n - 1 - j] = matrix[j][n - 1 - i];
                matrix[j][n - 1 - i] = tmp;
            }
        }
    }
}

2、 用 Rand7() 实现 Rand10()

470. 用 Rand7() 实现 Rand10()

java 复制代码
class Solution extends SolBase {
    public int rand10() {
        while (true) {
            int ans = (rand7() - 1) * 7 + (rand7() - 1); // 进制转换
            if (1 <= ans && ans <= 10) return ans;
        }
    }
}

3、最大正方形

221. 最大正方形

解法一:统计[i,j]左侧连续的1个数,遍历[0,i-1]看是否为正方形全一。

java 复制代码
class Solution {
    public int maximalSquare(char[][] matrix) {
        int m=matrix.length;
        int n=matrix[0].length;
        int[][] count=new int[m][n];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(matrix[i][j]=='1'){
                    count[i][j]=1+(j>0?count[i][j-1]:0);
                }
            }
        } 
        int res=0;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(matrix[i][j]=='1'){
                    int start=count[i][j];
                    for(int k=i;k>=0;k--){
                        start=Math.min(start,count[k][j]);
                        if(start==i-k+1||(k==i&&start==1)) res=Math.max(res,start*(i-k+1));
                    }
                }
            }
        }
        return res;
    }
}

解法二:动态规划,dp[i+1][j+1]=Math.min(dp[i][j+1],Math.min(dp[i][j],dp[i+1][j]))+1;

java 复制代码
class Solution {
    public int maximalSquare(char[][] matrix) {
        int m=matrix.length,n=matrix[0].length;
        int[][] dp=new int[m+1][n+1];
        int res=0;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(matrix[i][j]=='1'){
                    dp[i+1][j+1]=Math.min(dp[i][j+1],Math.min(dp[i][j],dp[i+1][j]))+1;
                    
                }
                res=Math.max(res,dp[i+1][j+1]);
            }
        }
        return res*res;
    }
}

4、全排列

46. 全排列

java 复制代码
class Solution {
    List<List<Integer>> res;
    List<Integer> path;
    int[] nums;
    public List<List<Integer>> permute(int[] nums) {
        res=new ArrayList<>();
        path=new ArrayList<>();
        this.nums=nums;
        int[] vis=new int[nums.length];
        dfs(vis);
        return res;
    }
    public void dfs(int[] vis){
        if(path.size()==nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i=0;i<nums.length;i++){
            if(vis[i]==0){
                vis[i]=1;
                path.add(nums[i]);
                dfs(vis);
                vis[i]=0;
                path.removeLast();
            }
        }
    }
}

5、全排列 II

47. 全排列 II

解法一:使用Set来进行树层去重,因为每一次递归都会重新创Set

java 复制代码
class Solution {
    List<List<Integer>> res;
    List<Integer> path;
    int[] nums;
    public List<List<Integer>> permuteUnique(int[] nums) {
        res=new ArrayList<>();
        path=new ArrayList<>();
        this.nums=nums;
        int[] vis=new int[nums.length];
        dfs(vis);
        return res;
    }
    public void dfs(int[] vis){
        if(path.size()==nums.length){
            res.add(new ArrayList<>(path));
            return; 
        }
        Set<Integer> set=new HashSet<>();
        for(int i=0;i<nums.length;i++){
            if(vis[i]==0&&!set.contains(nums[i])){
                vis[i]=1;
                set.add(nums[i]);
                path.add(nums[i]);
                dfs(vis);
                vis[i]=0;
                path.removeLast();
            }
        }
    }
}

解法二:排序后使用used数组在树枝上去重

java 复制代码
class Solution {
    List<List<Integer>> res;
    List<Integer> path;
    int[] nums;
    int[] used;
    int n;
    public List<List<Integer>> permuteUnique(int[] nums) {
        res=new ArrayList<>();
        path=new ArrayList<>();
        Arrays.sort(nums);
        this.nums=nums;
        n=nums.length;
        used=new int[n];
        backtracking();
        return res;
    }
    public void backtracking(){
        if(path.size()==n){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i=0;i<n;i++){
            if(used[i]==1) continue;
            if(i>0&&used[i-1]==1&&nums[i]==nums[i-1]) continue;
            path.add(nums[i]);
            used[i]=1;
            backtracking();
            used[i]=0;
            path.removeLast();
        }
    }
}

6、重新安排行程

332. 重新安排行程

java 复制代码
class Solution {  
    // 使用 HashMap 存储每个出发地点及其对应的目的地的优先队列  
    Map<String, PriorityQueue<String>> map = new HashMap<String, PriorityQueue<String>>();  
    // 存储最后的行程路径  
    List<String> itinerary = new LinkedList<String>();  

    // 主方法:寻找行程  
    public List<String> findItinerary(List<List<String>> tickets) {  
        // 遍历每张机票  
        for (List<String> ticket : tickets) {  
            String src = ticket.get(0); // 获取出发地  
            String dst = ticket.get(1); // 获取目的地  
            // 如果出发地不在map中,初始化一个优先队列  
            if (!map.containsKey(src)) {  
                map.put(src, new PriorityQueue<String>());  
            }  
            // 将目的地加入对应出发地的优先队列  
            map.get(src).offer(dst);  
        }  
        // 从起点 "JFK" 开始深度优先搜索构建行程  
        dfs("JFK");  
        // 反转行程顺序,因为是在回溯过程中添加的,最终结果需要逆序  
        Collections.reverse(itinerary);  
        return itinerary; // 返回最终的行程  
    }  

    // 深度优先搜索方法  
    public void dfs(String curr) {  
        // 当当前地点在 map 中并且存在可访问的目的地时  
        while (map.containsKey(curr) && map.get(curr).size() > 0) {  
            // 从当前地点的优先队列中取出最小的目的地(字典序最小)  
            String tmp = map.get(curr).poll();  
            // 递归访问这个目的地  
            dfs(tmp);  
        }  
        // 将当前地点加入行程列表,保证最终是从目的地到出发地的逆序  
        itinerary.add(curr);  
    }  
}

7、 N 皇后

51. N 皇后

java 复制代码
class Solution {
    List<List<String>> res;
    int n;
    public List<List<String>> solveNQueens(int n) {
        char[][] ans=new char[n][n];
        this.n=n;
        for(int i=0;i<n;i++){
            Arrays.fill(ans[i],'.');
        }
        res=new ArrayList<>();
        dfs(0,ans);
        return res;
    }
    public void dfs(int point,char[][] ans){
        if(point==n){
            List<String> temp=new ArrayList<>();
            StringBuilder zos=new StringBuilder();
            for(int i=0;i<n;i++){
                zos=new StringBuilder();
                for(int j=0;j<n;j++){
                    zos.append(ans[i][j]);
                }
                temp.add(zos.toString());
            }
            res.add(temp);
            return;
        }
        for(int i=0;i<n;i++){
            if(check(ans,point,i)){
                ans[point][i]='Q';
                dfs(point+1,ans);
                ans[point][i]='.';
            }
        }
    }
    public boolean check(char[][] ans,int x,int y){
        for(int i=0;i<x;i++){
            if(ans[i][y]=='Q') return false;
        }
        for(int i=0;i<y;i++){
            if(ans[x][i]=='Q') return false;
        }
        for(int i=x-1,j=y-1;i>=0&&j>=0;i--,j--){
            if(ans[i][j]=='Q') return false;
        }
        for(int i=x-1,j=y+1;i>=0&&j<n;i--,j++){
            if(ans[i][j]=='Q') return false;
        }
        return true;
    }
}

8、解数独

37. 解数独

java 复制代码
class Solution {
    // 用于标记每行、每列和每个3x3宫内已出现的数字
    private boolean[][] line = new boolean[9][9]; // 行标记
    private boolean[][] column = new boolean[9][9]; // 列标记
    private boolean[][][] block = new boolean[3][3][9]; // 3x3宫标记
    private boolean valid = false; // 用于标记是否找到有效的解
    private List<int[]> spaces = new ArrayList<int[]>(); // 存储所有空白格的位置

    // 主函数,用于解决数独问题
    public void solveSudoku(char[][] board) {
        // 遍历整个数独板,初始化标记和空白格
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] == '.') {
                    // 如果是空白格,添加到spaces列表中
                    spaces.add(new int[]{i, j});
                } else {
                    // 如果是非空白格,更新行、列和宫的标记
                    int digit = board[i][j] - '0' - 1;
                    line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true;
                }
            }
        }

        // 使用深度优先搜索(DFS)来填充空白格
        dfs(board, 0);
    }

    // 深度优先搜索辅助函数,用于递归填充数独板
    public void dfs(char[][] board, int pos) {
        // 如果pos等于spaces的大小,说明所有空白格都已填充,标记为有效解
        if (pos == spaces.size()) {
            valid = true;
            return;
        }

        // 获取当前空白格的位置
        int[] space = spaces.get(pos);
        int i = space[0], j = space[1];
        // 尝试填充当前空白格为1到9中的每一个数字
        for (int digit = 0; digit < 9 && !valid; ++digit) {
            // 如果当前数字在行、列和宫中都没有出现过
            if (!line[i][digit] && !column[j][digit] && !block[i / 3][j / 3][digit]) {
                // 更新行、列和宫的标记
                line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true;
                // 填充当前空白格
                board[i][j] = (char) (digit + '0' + 1);
                // 递归填充下一个空白格
                dfs(board, pos + 1);
                // 如果当前填充的数字不是有效解,回溯并清除标记
                line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = false;
            }
        }
    }
}

二、面试

1、项目经验相关

  1. 第一个项目(仿掘金抽奖系统)的整体流程是怎样的?在项目中承担了哪些工作?遇到了哪些技术难点?如何解决的?
  2. 第二个项目(DB Router路由中间件)是基于什么技术实现的?有什么特点?
  3. 抽奖系统中使用REDIS预扣减和锁兜底的方案,为什么选择这种方式?有没有其他替代方案?

2、Java基础知识相关

  1. 面向对象编程的三大特性是什么?请分别解释。
  2. 封装、继承和多态的具体理解是什么?
  3. 多肽中的重载和重写有什么区别?
  4. 抽象类和普通类有什么区别?抽象类和接口有什么区别?
  5. 接口中可以包含构造函数吗?
  6. 什么是深拷贝和浅拷贝?它们的区别是什么?
  7. 泛型一般在什么时候使用?
  8. 请解释一下Java的反射机制?动态代理是如何实现反射的?
  9. 在项目开发中用到了哪些Java集合?CONCURRENT哈希map和哈希map有什么区别?
  10. 请解释一下线程池的工作原理?常用的核心参数有哪些?

1. 问题答案

  1. 面向对象编程的三大特性是什么?请分别解释。
    封装、继承、多态。
  • 封装:将数据与操作数据的方法绑定,隐藏内部实现细节,通过公共接口访问,提升安全性和可维护性。
  • 继承:子类继承父类的属性和方法,实现代码复用,支持层次化类设计。
  • 多态:同一方法在不同对象中表现不同行为,通过重写(运行时多态)和重载(编译时多态)实现,增强灵活性。

  1. 封装、继承和多态的具体理解是什么?
  • 封装:通过访问修饰符(如 private)限制直接访问,提供 getter/setter 控制数据操作,降低耦合。
  • 继承:子类复用父类代码,支持 is-a 关系,如 Dog 继承 Animal,可扩展或重写方法。
  • 多态:同一方法签名在不同上下文表现不同,如父类引用指向子类对象时,调用方法按实际类型执行。

  1. 多态中的重载和重写有什么区别?
  • 重载(Overload):同一类中方法名相同、参数列表不同(类型/数量/顺序不同),返回类型可不同,属于编译时多态。
  • 重写(Override):子类覆盖父类方法,方法名、参数列表、返回类型必须一致,访问权限不能更严格,属于运行时多态。

  1. 抽象类和普通类有什么区别?抽象类和接口有什么区别?
  • 抽象类 vs 普通类:抽象类不能实例化,可含抽象方法(无实现)和具体方法;普通类可实例化,方法全为具体实现。
  • 抽象类 vs 接口:
    • 抽象类支持构造方法、成员变量和具体方法,用于代码复用;接口仅定义方法(Java 8+ 可有默认方法)、常量,强制实现契约。
    • 类单继承抽象类,但可多实现接口。

  1. 接口中可以包含构造函数吗?
    不可以。接口不能被实例化,无需构造函数。其方法默认由实现类实现(Java 8+ 允许默认方法,但无构造函数)。

  1. 什么是深拷贝和浅拷贝?它们的区别是什么?
  • 浅拷贝:复制对象时,引用类型字段指向原对象的内存地址,修改会影响原对象。
  • 深拷贝:完全复制对象及其引用字段指向的新对象,与原对象独立。

区别:浅拷贝共享引用,深拷贝完全独立。例如,浅拷贝后的数组元素修改会影响原对象,深拷贝不会。


  1. 泛型一般在什么时候使用?

类型安全与代码复用。泛型用于集合类(如 List<T>)避免强制类型转换,或在通用算法中处理多种类型,保证编译时类型检查。例如,HashMap<K, V> 键值对类型明确,减少运行时错误。


  1. 请解释一下Java的反射机制?动态代理是如何实现反射的?
  • 反射:在运行时动态获取类信息(方法、字段、构造函数),并调用或修改其行为,如 Class.forName()method.invoke()
  • 动态代理:通过 Proxy.newProxyInstance() 生成代理对象,在运行时实现接口,利用反射拦截方法调用并增强逻辑(如日志、事务),核心依赖 InvocationHandler

  1. 在项目开发中用到了哪些Java集合?CONCURRENT哈希map和哈希map有什么区别?
  • 常用集合:ArrayList(列表)、HashMap(键值对)、HashSet(集合)、ConcurrentHashMap(线程安全映射)。
  • 区别:
    HashMap 非线程安全,多线程并发可能导致死循环或数据丢失;
    ConcurrentHashMap 通过分段锁(Java 7)或 CAS(Java 8+)实现高并发安全,性能更高。

  1. 请解释一下线程池的工作原理?常用的核心参数有哪些?
  • 工作原理:复用已创建的线程处理任务,减少频繁创建销毁的开销。流程:提交任务→核心线程执行→队列等待→创建新线程→队列满且达最大线程数→拒绝策略。
  • 核心参数:
    corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(空闲线程存活时间)、workQueue(任务队列)、threadFactory(线程工厂)、RejectedExecutionHandler(拒绝策略)。
  1. 一个新任务到线程池后的过程

当一个新任务提交到线程池时,线程池会首先尝试通过空闲的核心线程执行任务;若核心线程均忙碌且任务队列未满,则将任务放入队列等待;若队列已满,则创建新的非核心线程(不超过最大线程数限制)执行任务;若线程数已达上限且队列仍满,则触发拒绝策略(如抛出异常或丢弃任务)。任务执行完成后,线程会从队列中获取下一个任务,空闲线程在超时时间内无任务则销毁,实现资源复用与动态扩缩容。

3、并发编程相关

  1. 常用的锁有哪些?synchronize和ReentrantLock的工作原理和区别是什么?

1. 问题答案

常用的锁包括synchronized(内置锁)、ReentrantLock(可重入锁)、ReentrantReadWriteLock(读写锁)、StampedLock(乐观读锁)等。synchronized是JVM层面的锁,基于监视器(monitor)实现,自动获取和释放锁,支持可重入但仅非公平;ReentrantLock是java.util.concurrent.locks包下的API,需手动加锁/释放(通过try-finally确保),支持公平/非公平模式,提供条件变量(Condition)和可中断特性。两者的核心区别在于:1) 实现层面,前者依赖JVM,后者基于AQS(AbstractQueuedSynchronizer);2) 功能,后者支持公平锁、多条件队列及可中断加锁;3) 性能,早期版本中ReentrantLock在高竞争下更优,但Java 6后synchronized通过偏向锁、轻量级锁等优化大幅缩小差距。适用场景:简单同步优先选synchronized(简洁性),复杂需求(如公平性、精细控制)则用ReentrantLock。

4、JVM相关

  1. JVM的内存模型分为哪几部分?线程私有和线程共享的区域分别有哪些?
  2. 请解释一下JVM的垃圾回收机制?常用的垃圾回收器有哪些?垃圾回收算法有哪些?

1. 问题答案

  1. JVM的内存模型分为哪几部分?线程私有和线程共享的区域分别有哪些?

JVM内存模型主要分为以下部分:

  1. 程序计数器(Program Counter Register):线程私有,记录当前线程执行的字节码指令地址(或行号),用于线程切换后恢复执行位置。
  2. Java虚拟机栈(Java Virtual Machine Stacks):线程私有,存储方法调用的栈帧(局部变量表、操作数栈、动态链接、方法出口)。
  3. 本地方法栈(Native Method Stack):线程私有,为Native方法(非Java代码)服务。
  4. 堆(Heap):线程共享,存放对象实例和数组,是垃圾回收的主要区域。
  5. 方法区(Method Area):线程共享,存储类信息、常量、静态变量等,JDK8后由元空间(Metaspace)实现,使用本地内存。

线程私有区域:程序计数器、虚拟机栈、本地方法栈。

线程共享区域:堆、方法区。


  1. JVM的垃圾回收机制?常用垃圾回收器?垃圾回收算法?

垃圾回收机制:

JVM通过自动内存管理回收不再使用的对象(即"垃圾"),核心是判断对象可达性(通过GC Roots引用链)。当对象不可达时,会被标记为可回收,由垃圾回收器(GC)清理内存,避免内存泄漏。

常用垃圾回收器:

• Serial:单线程串行回收,适用于客户端模式(小型应用)。

• ParNew:多线程版Serial,常与CMS配合使用。

• Parallel Scavenge:关注吞吐量的多线程新生代回收器(高吞吐量=运行时间/(运行时间+GC时间))。

• CMS(Concurrent Mark-Sweep):并发标记清除,低延迟但可能产生内存碎片,适用于老年代。

• G1(Garbage-First):面向服务端应用的垃圾回收器,分代收集,可预测停顿时间,优先回收高价值区域。

• ZGC/Shenandoah:低延迟回收器(亚毫秒级停顿),支持超大堆。

垃圾回收算法:

  1. 标记-清除(Mark-Sweep):标记不可达对象后清除,效率低且产生内存碎片。
  2. 复制(Copying):将存活对象复制到空闲区域(如新生代Serial/ParNew),简单高效但浪费一半内存。
  3. 标记-整理(Mark-Compact):标记后整理存活对象至一端(如老年代CMS最终阶段),解决碎片问题。
  4. 分代收集(Generational):基于对象存活周期划分堆(新生代+老年代),新生代用复制算法,老年代用标记-清除/整理。

5、MySQL相关

  1. 一条SQL语句的执行过程是怎样的?
  2. 请解释一下MySQL的索引?分为哪几类?非聚簇索引一定会回表吗?
  3. 索引背后的数据结构是什么?为什么使用B+树?
  4. 请解释一下MySQL的事务?四大特性是什么?常见的并发问题有哪些?解决措施是什么?
  5. 事务的隔离级别有哪些?分别解决了哪些并发问题?

1. 问题答案

  1. 一条SQL语句的执行过程是怎样的?

    SQL语句的执行过程通常分为多个阶段:首先,解析器进行语法和语义分析,生成解析树;优化器根据统计信息选择最优执行计划;执行引擎调用存储引擎接口访问数据,若涉及索引则通过B+树定位数据页,读取数据到内存并处理;最后返回结果。若涉及事务,还会通过日志模块(如redo log、undo log)保证持久性和原子性。

  2. 请解释一下MySQL的索引?分为哪几类?非聚簇索引一定会回表吗?

    索引是加速数据检索的数据结构,MySQL中主要分为聚簇索引(数据与索引存储在一起,如InnoDB的主键索引)和二级索引(非聚簇索引,叶子节点存储主键值)。此外还有唯一索引、全文索引等。非聚簇索引不一定会回表:若查询字段全部包含在索引中(覆盖索引),可直接返回数据,无需回表;若包含非索引字段,则需通过主键值二次查找聚簇索引(回表)。

  3. 索引背后的数据结构是什么?为什么使用B+树?

    索引常用B+树结构。相比B树,B+树的非叶子节点仅存储键值(不存数据),可容纳更多键,减少树高,降低磁盘I/O次数;叶子节点通过链表连接,支持高效范围查询;且查询稳定性更高(所有查询到叶子节点)。B+树更适合数据库的随机查找与范围扫描场景。

  4. 请解释一下MySQL的事务?四大特性是什么?常见的并发问题有哪些?解决措施是什么?

    事务是数据库操作的逻辑单元,具备ACID特性:原子性(操作不可分割,通过undo log实现回滚);一致性(事务前后数据合法,依赖原子性+隔离性+持久性);隔离性(事务互不干扰,通过锁或MVCC实现);持久性(提交后数据永久保存,依赖redo log)。

    并发问题包括:脏读(读未提交数据)、不可重复读(同一事务内多次读取结果不一致)、幻读(同一查询条件返回不同行数)。解决措施:通过锁(悲观锁、乐观锁)、MVCC(多版本并发控制)实现不同隔离级别下的数据一致性。

  5. 事务的隔离级别有哪些?分别解决了哪些并发问题?

    • 读未提交(Read Uncommitted):最低级别,可能脏读、不可重复读、幻读。

    • 读已提交(Read Committed):仅读取已提交数据,解决脏读,但存在不可重复读和幻读。

    • 可重复读(Repeatable Read,默认InnoDB):事务内多次读取结果一致,解决脏读和不可重复读,但可能幻读(通过MVCC和间隙锁优化,实际可避免幻读)。

    • 串行化(Serializable):完全隔离,通过锁强制顺序执行,解决所有并发问题,但性能最低。

6、HTTP相关

  1. HTTP报文的结构是怎样的?
  2. 常见的HTTP状态码有哪些?
  3. 请解释一下TCP的三次握手过程?为什么需要序列号?

1. 问题回答

  1. HTTP报文的结构是怎样的?

HTTP报文分为请求报文和响应报文。请求报文包含:

• 请求行(方法、URL、HTTP版本,如 GET /index.html HTTP/1.1);

• 请求头(键值对,如 Host: example.comUser-Agent: Chrome);

• 空行(分隔头部与正文);

• 请求体(可选,如POST请求的数据)。

响应报文包含:

• 状态行(HTTP版本、状态码、原因短语,如 HTTP/1.1 200 OK);

• 响应头(如 Content-Type: text/htmlContent-Length);

• 空行;

• 响应体(实际返回内容,如HTML网页)。

  1. 常见的HTTP状态码有哪些?

• 2xx(成功):200 OK(请求成功)、201 Created(资源创建成功)。

• 3xx(重定向):301 Moved Permanently(永久跳转)、302 Found(临时跳转)。

• 4xx(客户端错误):400 Bad Request(请求格式错误)、404 Not Found(资源不存在)、403 Forbidden(无权限)。

• 5xx(服务器错误):500 Internal Server Error(服务器内部错误)、503 Service Unavailable(服务暂时不可用)。

  1. 请解释一下TCP的三次握手过程?为什么需要序列号?

三次握手过程:

  1. 客户端发送SYN包(初始序列号seq=x),请求建立连接;
  2. 服务端回复SYN+ACK包(确认号ack=x+1,自身序列号seq=y);
  3. 客户端再发ACK包(确认号ack=y+1),连接建立。

序列号的作用:

• 保证有序传输:接收方按序列号重组数据包;

• 去重:通过唯一序号识别重复数据包;

• 流量控制:通过确认机制实现可靠传输。

三次握手防止已失效的旧连接请求(如网络滞留包)被误认为新连接,确保双方同步初始序列号,建立可靠通信。

7、算法

1. 买卖股票的最佳时机 II

122. 买卖股票的最佳时机 II

java 复制代码
class Solution {
    public int maxProfit(int[] prices) {
        int n=prices.length;
        int[][] dp=new int[n][2];
        dp[0][0]=-prices[0];
        int res=0;
        for(int i=1;i<n;i++){
            dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
            dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
            res=Math.max(res,dp[i][1]);
        } 
        return res;
    }
}

2. 买卖股票的最佳时机 III

123. 买卖股票的最佳时机 III

java 复制代码
class Solution {
    public int maxProfit(int[] prices) {
        int n=prices.length;
        int[][] dp=new int[n][5];
        dp[0][1]=-prices[0];
        dp[0][3]=-prices[0];
        for(int i=1;i<n;i++){
            dp[i][0]=dp[i-1][0];
            dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
            dp[i][2]=Math.max(dp[i-1][2],dp[i-1][1]+prices[i]);
            dp[i][3]=Math.max(dp[i-1][3],dp[i-1][2]-prices[i]);
            dp[i][4]=Math.max(dp[i-1][4],dp[i-1][3]+prices[i]);
        }
        return dp[n-1][4];
    }
}

3. 买卖股票的最佳时机 IV

188. 买卖股票的最佳时机 IV

java 复制代码
class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        // dp[i][j]表示第i天处于第j个状态时的最大利润
        // 状态j的范围是0~2k,共2k+1个状态:
        // 0: 无操作(初始状态)
        // 1: 第一次买入,3: 第二次买入,..., 2k-1: 第k次买入
        // 2: 第一次卖出,4: 第二次卖出,..., 2k: 第k次卖出
        int[][] dp = new int[n][2 * k + 1];
        
        // 初始化:所有可能的买入状态在第0天的情况
        // 奇数位(1,3,...,2k-1)表示买入操作,初始化为-price[0]
        for (int i = 1; i < k * 2 + 1; i += 2) {
            dp[0][i] = -prices[0];
        }
        
        // 遍历每个交易日
        for (int i = 1; i < n; i++) {
            // 遍历所有可能的状态
            for (int j = 1; j < 2 * k + 1; j++) {
                if (j % 2 == 0) {
                    // 偶数状态:卖出状态,可以选择保持或卖出
                    // 卖出操作需从前一天的买入状态转移而来(j-1为奇数)
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i]);
                } else {
                    // 奇数状态:买入状态,可以选择保持或买入
                    // 买入操作需从前一天的卖出状态转移而来(j-1为偶数)
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]);
                }
            }
        }
        
        // 返回最后一天完成k次卖出后的最大利润
        return dp[n - 1][2 * k];
    }
}
相关推荐
西瓜本瓜@36 分钟前
在 Android 中实现通话录音
android·java·开发语言·学习·github·android-studio
码流怪侠1 小时前
🚀 2025年 GitHub 暴涨 star 热门项目 🔥【持续更新】
程序员·github
创实信息5 小时前
GitHub Copilot在产品/安全团队中的应用实践:处理Markdown、自动化报告与电子表格、使用CLI命令等
github·copilot·ai编程
七月丶6 小时前
📦 把 CLI 工具发布到 npm:gix 发布流程全解析(附 CI 自动化)
前端·后端·github
流沙krysent9 小时前
git常用指令
前端·github
运营猫小海豚9 小时前
Dootask任务管理实战——从需求到交付的闭环
前端·github
我是哪吒9 小时前
分布式微服务系统架构第118集:Future池管理容器-CompletableFuture
后端·面试·github
OpenTiny社区9 小时前
Node.js技术原理分析系列8——将Node.js内置模块外置
前端·node.js·github
Andy32210 小时前
029 期 告别复杂!三款神器让你的Docker管理轻松又高效
docker·github