hot100【acm版】【2026.7.21打卡-java版本】

课程表

java 复制代码
package hot100;
import java.util.*;
public class lc207 {
    /*课程表
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,
其中 prerequisites[i] = [ai, bi] ,
表示如果要学习课程 ai 则 必须 先学习课程  bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。*/

    //图,List【】 出度, 入度,对于入度==0,bfs
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        //构建图
        //注意写法!!是数组
        List<Integer>[] graph = new ArrayList[numCourses];

        for(int i = 0; i < numCourses; i++){
            graph[i] = new ArrayList<>();
        }

        //出度
        //入度
        int[] degree = new int[numCourses];
        for(int[] p : prerequisites){
            int pre = p[1];
            int next = p[0];
            degree[next]++;
            graph[pre].add(next);
        }

        //bfs
        Deque<Integer> queue = new ArrayDeque<>();
        for (int i = 0; i < numCourses; i++) {
            if (degree[i] == 0) {
                queue.offer(i);   // ✅ 入队的是课程编号
            }
        }
        int count = 0;
        while(!queue.isEmpty()){
            int temp = queue.poll();

            count++;
            for(int chudu : graph[temp]){
                degree[chudu]--;
                if(degree[chudu] == 0){
                    queue.offer(chudu);
                }
            }
        }

        return count == numCourses;
    }

    public static void main(String[] args) {
        lc207 solution = new lc207();

        // 测试用例 1: 可以完成,无环
        int numCourses1 = 2;
        int[][] pre1 = {{1, 0}};  // 先修 0 -> 1
        System.out.println("测试1: " + solution.canFinish(numCourses1, pre1));  // true

        // 测试用例 2: 有环,无法完成
        int numCourses2 = 2;
        int[][] pre2 = {{1, 0}, {0, 1}};  // 0<->1 环
        System.out.println("测试2: " + solution.canFinish(numCourses2, pre2));  // false

        // 测试用例 3: 多门课程,无环
        int numCourses3 = 4;
        int[][] pre3 = {{1, 0}, {2, 1}, {3, 2}};  // 0->1->2->3
        System.out.println("测试3: " + solution.canFinish(numCourses3, pre3));  // true

        // 测试用例 4: 没有先修课程
        int numCourses4 = 3;
        int[][] pre4 = {};
        System.out.println("测试4: " + solution.canFinish(numCourses4, pre4));  // true
    }
}

208. 实现 Trie (前缀树)

java 复制代码
package hot100;

public class Trie {
    /*208. 实现 Trie (前缀树)
Trie() 初始化前缀树对象。
void insert(String word) 向前缀树中插入字符串 word 。
boolean search(String word) 如果字符串 word 在前缀树中,
返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix)
如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;
否则,返回 false 。*/



    //26叉树


    //26叉树
    public class Node{
        //主要是看儿子,儿子是那个应该就是信息
        Node[] son = new Node[26];
        boolean end = false;
    }
    //唯一的树
    private final Node root = new Node();

    public Trie() {


    }

    public void insert(String word) {
        //toCharArray()
        Node cur = root;
        for(char c : word.toCharArray()){
            int index = c - 'a';
            if( cur.son[index] == null){
                cur.son[index] = new Node();
            }
            //接下去要知道index才能准确找到儿子分支
            cur = cur.son[index];

        }

        cur.end = true;//代表结束
    }

    public boolean search(String word) {
        return find(word)== 1 ? true : false;

    }

    public boolean startsWith(String prefix) {
        return find(prefix) != 0 ? true : false;
    }

    public int find(String word){
        Node cur = root;
        for(char c : word.toCharArray()){

            int index = c - 'a';

            if(cur.son[index] == null){
                return 0;
            }else{
                cur = cur.son[index];
            }
        }

        return cur.end ? 1 : 2;
    }



    // ========== main 测试方法 ==========
    public static void main(String[] args) {
        Trie trie = new Trie();

        // 插入单词
        trie.insert("apple");
        trie.insert("app");
        trie.insert("appl");
        trie.insert("banana");

        // 测试 search
        System.out.println(trie.search("apple"));   // true
        System.out.println(trie.search("app"));     // true
        System.out.println(trie.search("appl"));    // true
        System.out.println(trie.search("appli"));   // false
        System.out.println(trie.search("banana"));  // true
        System.out.println(trie.search("banan"));   // false

        // 测试 startsWith
        System.out.println(trie.startsWith("app"));   // true
        System.out.println(trie.startsWith("appl"));  // true
        System.out.println(trie.startsWith("ban"));   // true
        System.out.println(trie.startsWith("bana"));  // true
        System.out.println(trie.startsWith("cat"));   // false
    }
}

全排列

java 复制代码
package hot100;
import  java.util.*;
public class lc46 {
    /*全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。
你可以 按任意顺序 返回答案。*/
    List<List<Integer>> anss = new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {

        dfs(nums, new ArrayList<>(),new boolean[nums.length]);
        return anss;

    }

    public void dfs(int[] nums, List<Integer> ans, boolean[] used) {
        if (ans.size() == nums.length) {
            anss.add(new ArrayList<>(ans));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (!used[i]) {
                used[i] = true;
                ans.add(nums[i]);
                dfs(nums, ans, used);
                ans.remove(ans.size() - 1);
                used[i] = false;
            }
        }
    }

    public static void main(String[] args) {
        int[] nums = new int[]{1,2,3};
        lc46 solution = new lc46();
        System.out.println(solution.permute(nums));
    }
}

子集

java 复制代码
package hot100;
import  java.util.*;
public class lc78 {
    /* 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。*/

    List<List<Integer>> anss = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        dfs(nums, new ArrayList<>(), 0);
        return anss;

    }

    public void dfs(int[] nums, List<Integer> ans, int index){
        //空集也算
        if(index >= 0 && index <= nums.length){
            anss.add(new ArrayList<>(ans));
        }

        if(index > nums.length){
            return;
        }

        for(int i = index; i < nums.length; i++){
            ans.add(nums[i]);
            //这里是i!!!
            dfs(nums, ans, i+1);
            ans.remove(ans.size() -1);
        }
    }

    public static void main(String[] args) {
        int[] nums = new int[]{1,2,3};
        lc78 solution = new lc78();
        System.out.println(solution.subsets(nums));
    }
}

电话号码的字母组合

java 复制代码
package hot100;
import  java.util.*;
public class lc17 {
    /*17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母*/

    private static final String[] MAPPING = {
            "",     // 0
            "",     // 1
            "abc",  // 2
            "def",  // 3
            "ghi",  // 4
            "jkl",  // 5
            "mno",  // 6
            "pqrs", // 7
            "tuv",  // 8
            "wxyz"  // 9
    };
    List<String> anss = new ArrayList<>();
    public List<String> letterCombinations(String digits) {
        //全排列的变形

        dfs(digits, new char[digits.length()], 0);
        return anss;

    }

    public void dfs(String digits,char[] path, int index){

        if(index == digits.length()){
            //不能anss.add(path.toString);要加一个新的!!!
            anss.add(new String(path));
            return;
        }
        //是减0
        String temp = MAPPING[digits.charAt(index) - '0'];
        for(int i = 0; i < temp.length(); i++){
            path[index] = temp.charAt(i);
            dfs(digits, path,index+1);
        }
    }

    public static void main(String[] args) {
        String s = "23";
        lc17 solution = new lc17();
        System.out.println(solution.letterCombinations(s));
    }
}

随机知识

操作系统是后端开发的基石,面试中侧重考察你对资源管理的理解,尤其是进程线程、内存管理和I/O模型。这些知识能帮你更好地解释JVM原理和多线程问题。


一、操作系统核心概念速览

1. 进程与线程
  • 进程:资源分配的最小单位,有独立的内存地址空间。

  • 线程:CPU调度的最小单位,同一进程的线程共享进程资源(堆、全局变量、文件句柄),有独立的栈和寄存器。

  • 协程:用户态的轻量级线程,由程序自己调度,切换无需陷入内核。

2. 内存管理
  • 虚拟内存:为每个进程提供连续、独立的地址空间,通过页表映射到物理内存。

  • 分页:将虚拟地址划分为固定大小的页,物理内存划分为帧,以页为单位管理,解决外部碎片。

  • 分段:按逻辑单元划分(代码段、数据段),有逻辑意义但可能产生外部碎片。

  • 页表与TLB:TLB是页表的缓存,加速地址转换。

3. 文件系统
  • inode:存储文件元信息(大小、权限、数据块指针),文件名是给用户看的别名。

  • 硬链接/软链接:硬链接指向同一个inode,删除原文件不影响;软链接(符号链接)存目标路径,类似快捷方式。

4. I/O管理
  • 设备无关性:用户程序通过系统调用使用统一接口访问设备。

  • I/O模型:阻塞I/O、非阻塞I/O、I/O多路复用、信号驱动I/O、异步I/O。


二、高频面试题 + 答案(15题精选)

🧵 进程线程篇

1. 进程和线程的区别?

  • 资源:进程拥有独立的内存和资源,线程共享进程资源,仅独享栈和寄存器。

  • 开销:进程创建/销毁、切换开销大;线程开销小。

  • 通信:进程间需IPC(管道、队列),线程直接读写共享内存(但需同步)。

  • 健壮性:一个进程崩溃不影响其他进程;一个线程崩溃可能导致整个进程崩溃。

  • Java映射:JVM是单进程多线程,Java线程与OS内核线程一一对应(HotSpot)。

2. 进程间通信(IPC)有哪些方式?

  • 管道/FIFO:半双工,用于父子进程或无亲缘关系进程(命名管道)。

  • 消息队列:消息的链表,异步,适合非大量数据。

  • 共享内存:最快,直接映射同一块物理内存,需信号量同步。

  • 信号量/信号:信号量用于同步互斥;信号是短消息,通知事件。

  • Socket:可用于不同主机进程通信。

  • Java关联 :NIO的FileChannel.map()就是利用内存映射文件(共享内存)。

3. 线程同步的方式有哪些?

  • 互斥锁synchronizedLock,保证同一时间只有一个线程访问临界区。

  • 信号量:控制同时访问资源的线程数量。

  • 条件变量Condition,实现等待-唤醒机制。

  • 原子操作AtomicInteger,无锁但只能保证单个操作的原子性。

  • Java中 :还有CountDownLatchCyclicBarrierSemaphore等工具类。

🎯 死锁篇

4. 死锁产生的四个必要条件?如何预防?

  • 互斥:资源独占。

  • 持有并等待:持有资源的同时等待新资源。

  • 不可剥夺:已获得资源不能被强行抢走。

  • 循环等待:形成资源的环形等待链。

  • 预防:破坏任意一个条件。例如一次性申请所有资源(破坏"持有并等待")、资源排序强制按序申请(破坏"循环等待")。

5. 银行家算法是什么?

  • 一种死锁避免算法,核心思想:判断系统分配资源后是否仍处于安全状态。若存在一个安全序列,则分配;否则拒绝分配,进程必须等待。适用于资源静态分配的系统中。
🧠 内存管理篇

6. 什么是虚拟内存?有什么好处?

  • 将物理内存和进程的逻辑地址空间分离,让进程感觉自己拥有连续的、独占的大内存。

  • 好处:隔离进程、可运行比物理内存大的程序(借助磁盘)、简化内存管理、共享内存和写时拷贝。

7. 分页和分段的区别?

  • 分页:页大小固定,按物理角度分割,消除外部碎片,有内部碎片,地址一维。

  • 分段:段大小可变,按逻辑单元(如函数、数组)分割,有外部碎片,地址二维(段号+偏移)。

  • 现代OS:通常段页式,Linux主要用分页,用平坦模式。

8. 页面置换算法有哪些?

  • OPT(最佳置换):换出未来最远使用的页,无法实现,仅作比较标准。

  • FIFO:先进先出,有Belady异常。

  • LRU(最近最久未使用):根据历史访问记录,换出最久没访问的页。实现可用链表或栈,近似实现用Clock算法。

  • Clock(时钟算法):循环检查引用位,如果为0则换出,否则清0并向后扫描,是NRU的改进。

9. 什么是写时复制(Copy-on-Write)?

  • 多个进程共享同一块物理内存,只要没有写操作,就一直共享;当某个进程尝试写时,才复制一份私有副本。典型应用:fork()后父子进程共享内存,exec时才复制;Linux内存管理、Redis的BGSAVE也用到了COW。
📂 文件系统

10. 硬链接和软链接的区别?

  • 硬链接:共享同一个inode,文件只要有硬链接存在就不会被删除。不能跨文件系统,不能链接目录。

  • 软链接:存放目标路径,删除原文件则失效,可以跨文件系统,可以链接目录。

⚡ I/O与高性能

11. 什么是I/O多路复用?select/poll/epoll的区别?

  • 核心思想:单个线程同时监听多个文件描述符,一旦有事件就通知处理,避免阻塞在每个描述符上。

  • select:遍历所有FD,有最大文件描述符限制(一般1024),轮询O(n)。

  • poll:链表存储,无最大限制,但仍需轮询O(n)。

  • epoll :使用红黑树+事件驱动,当FD就绪时通过回调主动通知,O(1),支持ET/LT模式。ET边缘触发 :只有状态变化时才通知,需一次读完数据;LT水平触发:未读完会不断通知。

12. 什么是零拷贝?Java中如何实现?

  • 传统数据发送需要4次拷贝(用户态↔内核态)和4次上下文切换。零拷贝减少CPU拷贝次数。

  • sendfile + DMA scatter/gather:数据只拷贝两次到Socket缓冲区,内核态完成传输。

  • mmap:将文件映射到内存,减少内核到用户态的拷贝,适合小文件。

  • Java实现FileChannel.transferTo() 底层就是调用sendfileMappedByteBuffer底层是mmap。RocketMQ、Kafka都大量使用零拷贝提高吞吐。

🔧 调度与其他

13. 进程调度算法有哪些?

  • FCFS:先来先服务,非抢占,对短作业不友好。

  • SJF:最短作业优先,平均等待时间最小,但长作业可能饥饿。

  • 优先级调度:按优先级,抢占或非抢占。

  • 时间片轮转:公平,每个进程固定时间片,响应快,适合交互。

  • 多级反馈队列:多个队列不同优先级和时间片,新进程进最高优先队列,用不完时间片降级,兼顾短作业和交互。

14. 用户态和内核态的区别?为什么要区分?

  • 用户态受限,不能执行特权指令(如I/O、内存管理);内核态拥有所有权限。

  • 区分目的:保护系统安全,防止用户程序破坏系统。系统调用是用户态进入内核态的唯一入口。

  • 切换方式:系统调用、中断、异常。切换涉及栈切换和保存寄存器,有一定开销。

15. Linux中常见的锁或同步机制?

  • 互斥锁(mutex):内核态互斥锁,睡眠等待。

  • 自旋锁(spinlock):忙等待,不休眠,适合临界区极小的情况。

  • 读写锁(rwlock):多读单写,提高并发。

  • RCU(Read-Copy Update):读者无锁,写者先拷贝再更新,适合读多写少。

  • 信号量(semaphore):计数的睡眠锁,控制访问数量。

碎碎念:后续会更新每天学习的八股和算法 题,开始准备秋招的第72天。努力连续更新100天!以后每天就按,秋招项目【java +agent】,科研,必做项目,算法,八股,锻炼身体来总结。

总结:所有努力都是有用的,现在刷题都很得心应手还,虽然思路还是可能会忘记。

1.hot100 【acm】 57/100 ,2到3h,快速把hot100过一遍【9/20】

2.秋招项目,【java 项目】,继续

【agent 项目 】,继续

3.科研。确定方向就搞就可以了

4.实习;

6.背八股,无

7.锻炼身体,无

要点:坚持

相关推荐
lxw18449125141 小时前
PHP后端(CI框架方向)面试题库,分为基础必问、MySQL、Redis、工程运维、高阶加分、场景实操六大模块,适配该JD全部考点,附带标准答案
ci/cd·面试·php
IT_Octopus1 小时前
一次由 RestTemplate 引发的 Connection reset:被 XML 截胡的请求体
xml·java·spring boot
云烟成雨TD1 小时前
Micrometer 系列【2】可观测性:指标(Metrics)
java·云原生·micrometer
2501_914245931 小时前
Java应用性能调优与生产监控部署:2026年实战手册
java·开发语言
吴声子夜歌1 小时前
MongoDB 4.x——微服务入门
java·mongodb·微服务
李剑一1 小时前
AI编程已经如此强大,我们还需要再学习基础嘛?或者说初级程序员还有可能存在吗?
面试
好好沉淀1 小时前
主键选择(自增 vs UUID vs 雪花算法)
java·算法
时光不染。回忆不淡1 小时前
Python基础语法教程
开发语言·python
爱喝水的鱼丶1 小时前
SAP-ABAP:一次关于“交货日期”的增强需求:从复杂增强到标准功能的回归之旅
运维·开发语言·性能优化·sap·abap·经验交流