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

一、力扣

1、排序数组

912. 排序数组

java 复制代码
class Solution {
    public int[] sortArray(int[] nums) {
        quicksort(nums,0,nums.length-1);
        return nums;
    }
    public void quicksort(int[] nums,int left,int right){
        if(left>=right) return;
        Random random=new Random();
        int part=left+random.nextInt(right-left+1);
        swap(nums,left,part);
        int i=left+1,j=right;
        int target=nums[left];
        while(true){
            while(i<=j&&nums[i]<target) i++;
            while(i<=j&&nums[j]>target) j--;
            if(i>=j) break;
            swap(nums,i,j);
            i++;
            j--;
        }
        swap(nums,left,j);
        quicksort(nums,left,j-1);
        quicksort(nums,j+1,right);
    }
    public void swap(int[] nums,int left,int right){
        int temp=nums[left];
        nums[left]=nums[right];
        nums[right]=temp;
    }
}

2、二叉树的序列化与反序列化

297. 二叉树的序列化与反序列化

java 复制代码
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        String temp=sdfs(root);
        System.out.println(temp);
        return temp;
    }
    public String sdfs(TreeNode root){
        if(root==null){
            return "None";
        }
        return root.val+","+sdfs(root.left)+","+sdfs(root.right);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] ans=data.split(",");
        ArrayDeque<String> queue=new ArrayDeque<>(Arrays.asList(ans));
        return rdfs(queue);
    }
    public TreeNode rdfs(ArrayDeque<String> queue){
        String temp=queue.poll();
        if("None".equals(temp)){
            return null;
        }
        TreeNode res=new TreeNode(Integer.parseInt(temp));
        res.left=rdfs(queue);
        res.right=rdfs(queue);
        return res;
    }
}

3、基本计算器

224. 基本计算器

java 复制代码
class Solution {
    public int calculate(String s) {
        char[] ans=s.toCharArray();
        int point=0;
        int n=ans.length;
        ArrayDeque<Integer> stack=new ArrayDeque<>();
        stack.push(1);
        int aos=1;
        int res=0;
        while(point<n){
            char temp=ans[point];
            if(temp==' '){
                point++; 
            } else if(temp=='+'){
                aos=stack.peek();
                point++;
            }else if(temp=='-'){
                aos=-stack.peek();
                point++;
            }else if(temp=='('){
                stack.push(aos);
                point++;
            }else if(temp==')'){
                stack.pop();
                point++;
            }else{
                int pre=0;
                while(point<n&&Character.isDigit(ans[point])){
                    pre=pre*10+ans[point]-'0';
                    point++;
                }
                res+=pre*aos;
            }
        }
        return res;
    }
}

4、整数拆分

343. 整数拆分

java 复制代码
class Solution {
    /**
     * 计算将正整数n拆分为至少两个正整数之和时的最大乘积
     * @param n 输入的正整数
     * @return 最大乘积
     */
    public int integerBreak(int n) {
        // dp[i]表示将整数i拆分为至少两个正整数之和时的最大乘积
        int[] dp = new int[n + 1];
        dp[2] = 1; // 初始条件:2只能拆分为1+1,乘积为1

        // 从3开始递推,因为2的情况已处理
        for (int i = 3; i <= n; i++) {
            // 尝试所有可能的拆分方式:将i拆分为j和i-j两部分
            for (int j = i - 1; j >= 1; j--) {
                // 状态转移方程:
                // 比较两种情况:
                // 1. 不继续拆分i-j,直接相乘:j * (i-j)
                // 2. 继续拆分i-j,取i-j的最优解:j * dp[i-j]
                // 取两者的较大值,并与当前dp[i]比较更新
                dp[i] = Math.max(dp[i], Math.max(dp[i - j] * j, (i - j) * j));
            }
        }
        return dp[n];
    }
}

5、不同的二叉搜索树

96. 不同的二叉搜索树

java 复制代码
class Solution {
    public int numTrees(int n) {
        //初始化 dp 数组
        int[] dp = new int[n + 1];
        //初始化0个节点和1个节点的情况
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                //对于第i个节点,需要考虑1作为根节点直到i作为根节点的情况,所以需要累加
                //一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
}

6、验证IP地址

468. 验证IP地址

java 复制代码
class Solution {
    public String validIPAddress(String queryIP) {
        if (queryIP.indexOf('.') >= 0) {
            return isIpV4(queryIP) ? "IPv4" : "Neither";
        } else {
            return isIpV6(queryIP) ? "IPv6" : "Neither";
        }
    }

    public boolean isIpV4(String queryIP) {
        //加-1是防止出现空字符串无法计数 比如192.168.1.1. 后边多了一个点,不加-1会被忽略后边的空串
        String[] split = queryIP.split("\\.", -1);
        //个数不是4个
        if (split.length != 4) {
            return false;
        }
        for (String s : split) {
            //每个长度不在 1-3之间
            if (s.length() > 3 || s.length() == 0) {
                return false;
            }
            //有前导0 并且长度不为1
            if (s.charAt(0) == '0' && s.length() != 1) {
                return false;
            }
            //计算数字
            int ans = 0;
            for (int j = 0; j < s.length(); j++) {
                char c = s.charAt(j);
                //不是数字
                if (!Character.isDigit(c)) {
                    return false;
                }
                ans = ans * 10 + (c - '0');
            }
            //数字超过255
            if (ans > 255) {
                return false;
            }
        }
        return true;
    }

    public boolean isIpV6(String queryIP) {
        String[] split = queryIP.split(":", -1);
        //数量不是8个
        if (split.length != 8) {
            return false;
        }
        for (String s : split) {
            //每个串 长度不在1-4之间
            if (s.length() > 4 || s.length() == 0) {
                return false;
            }
            for (int i = 0; i < s.length(); i++) {
                char c = s.charAt(i);
                //不是数字并且字母不在 a-f之间
                if (!Character.isDigit(c) && !(Character.toLowerCase(c) >= 'a') || !(Character.toLowerCase(c) <= 'f')) {
                    return false;
                }
            }
        }
        return true;
    }
}

7、分割等和子集

416. 分割等和子集

java 复制代码
class Solution {
    public boolean canPartition(int[] nums) {
        int sum=0;
        for(int x:nums){
            sum+=x;
        }
        int n=nums.length;
        if(sum%2==1) return false;
        int target=sum/2;
        boolean[][] dp=new boolean[n+1][target+1];
        dp[0][0]=true;
        for(int i=0;i<n;i++){
            for(int j=0;j<=target;j++){
                dp[i+1][j]=dp[i][j]||(j>=nums[i]&&dp[i][j-nums[i]]);
            }
        }
        return dp[n][sum/2];
    }
}

二、面试

1、操作系统相关问题

1. 操作系统的进程跟线程有什么区别?

进程和线程的主要区别:

  • 定义

    • 进程是操作系统分配资源的基本单位,拥有独立的内存空间
    • 线程是CPU调度的基本单位,共享所属进程的内存空间
  • 资源占用

    • 进程拥有独立的地址空间、内存、数据堆等资源
    • 线程共享进程的地址空间和资源,但有独立的栈空间和寄存器
  • 通信方式

    • 进程间通信需要特殊的IPC机制(管道、消息队列、共享内存等)
    • 线程间通信更简单,可直接访问共享变量
  • 切换开销

    • 进程切换开销大,需要切换页表、刷新TLB等
    • 线程切换开销小,只需保存和恢复少量寄存器状态
  • 安全性

    • 进程间相互独立,一个进程崩溃通常不影响其他进程
    • 线程间共享内存,一个线程崩溃可能导致整个进程崩溃

2. 为什么有了进程还要线程?

引入线程的主要原因:

  1. 资源利用效率:线程共享进程资源,创建和销毁线程的开销远小于进程
  2. 并发性能:多线程可以充分利用多核处理器,提高程序执行效率
  3. 响应速度:线程切换比进程切换快,提高系统响应速度
  4. 通信效率:线程间通信更简单高效,共享内存无需额外IPC机制
  5. 编程模型:线程模型使并发编程更直观,便于开发复杂应用

3. 线程切换时需要涉及哪些部分?进程切换相比线程切换少了哪些操作?

线程切换涉及的部分:

  • 保存和恢复线程上下文(寄存器状态、程序计数器等)
  • 切换栈指针
  • 更新线程控制块(TCB)信息

进程切换比线程切换多的操作:

  • 切换页表(地址空间切换)
  • 刷新TLB(地址转换缓冲)
  • 切换内核态上下文
  • 可能涉及更多特权级别切换
  • 可能需要刷新缓存

4. 是否存在比线程更轻量级的调度单位?

是的,存在比线程更轻量级的调度单位:

  • 协程(Coroutine):用户态的轻量级线程,由应用程序自己调度
  • 纤程(Fiber):某些系统提供的轻量级线程
  • 绿色线程(Green Thread):在用户空间实现的线程
  • 任务(Task):某些系统中的轻量级执行单元
  • Go语言中的Goroutine:Go运行时管理的轻量级线程

这些单位的共同特点是在用户空间调度,避免了内核态切换的开销,通常采用协作式调度而非抢占式调度。

5. 虚拟线程(协程)与Java线程的区别是什么?

虚拟线程(Java 19+引入)与传统Java线程的区别:

  • 实现方式

    • 传统Java线程是1:1映射到操作系统线程
    • 虚拟线程是M:N模型,多个虚拟线程映射到少量平台线程
  • 资源消耗

    • 传统线程每个约消耗1MB栈内存,数量受限于系统资源
    • 虚拟线程极其轻量,可创建数百万个,每个只占几KB内存
  • 调度机制

    • 传统线程由操作系统调度
    • 虚拟线程由JVM调度,在阻塞操作时会自动切换
  • 适用场景

    • 传统线程适合计算密集型任务
    • 虚拟线程特别适合IO密集型任务

6. 虚拟线程如何避免并发问题?与普通线程的并发控制有何不同?

虚拟线程的并发控制与普通线程基本相同:

  • 两者都需要使用相同的同步机制(synchronized、Lock、Atomic变量等)
  • 两者都面临相同的并发问题(竞态条件、死锁等)
  • 两者都可以使用相同的并发工具类(ConcurrentHashMap等)

主要区别:

  • 虚拟线程在阻塞操作时会自动让出底层平台线程,提高资源利用率
  • 使用虚拟线程时应避免长时间持有锁,否则可能影响调度效率
  • 虚拟线程更适合使用结构化并发(Structured Concurrency)模式

7. 引入虚拟线程这一层的目的是什么?

引入虚拟线程的主要目的:

  1. 提高吞吐量:支持更多并发连接,特别适合IO密集型应用
  2. 简化编程模型:使用同步代码风格编写异步效果的程序,避免回调地狱
  3. 资源利用效率:减少线程资源消耗,提高系统整体效率
  4. 适应现代应用需求:支持微服务架构下的高并发场景
  5. 提高响应性:在IO等待时自动切换,减少阻塞时间
  6. 降低开发复杂度:相比反应式编程更直观易懂

8. 若物理内存为8G,JVM最大堆内存能否设置为100G?磁盘空间是否影响JVM堆大小?

  • JVM最大堆内存理论上可以设置为100G,但实际运行会受限于可用的虚拟内存
  • 在Windows/Linux系统中,虚拟内存由物理内存+交换空间(页面文件/swap分区)组成
  • 如果交换空间足够大,可以设置超过物理内存的堆大小,但性能会严重下降
  • 磁盘空间本身不直接影响JVM堆大小,但影响可配置的交换空间大小
  • 实际应用中,不建议将JVM堆设置超过物理内存,会导致频繁页面交换,性能极差

9. LINUX系统中虚拟内存上限由什么决定?进程可用内存大小由哪些因素决定?

Linux系统中虚拟内存上限由以下因素决定:

  • 硬件架构(32位/64位)的地址空间限制
  • 内核参数配置(如vm.max_map_count)
  • 物理内存大小+交换空间大小

进程可用内存大小由以下因素决定:

  • 系统总可用内存(物理内存+交换空间)
  • 系统对单个进程的资源限制(ulimit设置)
  • 内核参数(如overcommit策略)
  • 进程自身的内存管理策略
  • 其他进程的内存占用情况

10. 三个JVM进程各占6G内存,总物理内存8G,能否同时启动?

理论上可以启动,但会有严重性能问题:

  • 总共需要18G内存,而物理内存只有8G
  • 系统会使用交换空间(swap)来弥补不足的内存
  • 大量数据会被交换到磁盘,导致频繁的页面调度
  • 系统会变得极其缓慢,可能出现严重的"内存颠簸"(thrashing)
  • 在极端情况下,如果交换空间不足,系统OOM killer可能会终止某些进程

11. 个人电脑内存(如16G/32G)是否能运行所有应用?若总和超过内存限制会怎样?

个人电脑内存无法运行"所有"应用,当应用内存总和超过限制时:

  • 系统会使用虚拟内存机制,将不常用的内存页面交换到磁盘
  • 频繁访问被交换出去的数据会导致系统响应变慢
  • 如果交换空间也耗尽,新的内存分配请求会失败
  • Windows系统可能会弹出"内存不足"警告
  • Linux系统的OOM killer会选择终止某些进程以释放内存
  • 应用可能崩溃或被强制关闭

12. 磁盘在内存申请中起到什么作用?

磁盘在内存申请中的作用:

  1. 提供交换空间:作为虚拟内存的扩展,存储暂时不用的内存页面
  2. 支持内存映射:通过mmap等机制将文件内容映射到内存地址空间
  3. 持久化存储:保存程序运行产生的数据,在需要时加载到内存
  4. 缓解内存压力:通过页面置换算法,将不常用数据暂存到磁盘
  5. 支持大型数据处理:允许处理超过物理内存大小的数据集

13. HDD磁盘为何以块(block)为单位读写?为何不以单个比特或字节为单位?

HDD磁盘以块为单位读写的原因:

  1. 物理结构限制:磁头一次定位就能读取一个扇区的数据
  2. 寻址开销优化:减少寻道和旋转延迟的影响
  3. 错误检测与纠正:块级别更容易实现ECC校验
  4. 传输效率:批量传输数据比逐位/逐字节传输效率高
  5. 控制器设计简化:简化了磁盘控制器的设计复杂度
  6. 缓存管理:便于系统进行缓存管理和预读优化

14. 内存缓存对齐的作用是什么?不对齐会导致什么问题?

内存缓存对齐的作用:

  1. 提高访问效率:CPU一次可以完整读取一个缓存行的数据
  2. 减少内存访问次数:避免一个数据跨越多个缓存行
  3. 避免伪共享:防止多线程程序中的缓存行争用
  4. 硬件要求:某些处理器架构要求特定类型数据必须对齐

不对齐可能导致的问题:

  • 性能下降:需要多次内存访问获取一个数据
  • 原子操作失效:某些架构下非对齐数据无法保证原子性
  • 硬件异常:某些处理器对非对齐访问会产生异常
  • 缓存效率降低:增加缓存未命中率
  • 伪共享问题:导致多线程程序性能下降

15. HDD的机械结构如何影响其读写方式?

HDD的机械结构对读写方式的影响:

  1. 寻道时间:磁头移动到指定磁道需要时间,影响随机访问性能
  2. 旋转延迟:等待目标扇区旋转到磁头下方的时间
  3. 顺序访问优势:连续读写可以减少寻道和旋转延迟
  4. 块读写:基于扇区的物理结构决定了以块为单位读写
  5. 读写调度算法:如电梯算法,优化磁头移动路径
  6. 预读策略:基于空间局部性原理,提前读取相邻数据
  7. 写入缓存:聚合小写入操作,减少实际磁头移动次数

2、Java相关问题

1. Java中Check Exception和Unchecked Exception的区别是什么?

Checked Exception和Unchecked Exception的区别:

Checked Exception

  • 必须在代码中显式处理(try-catch或throws声明)
  • 编译时检查,不处理会导致编译错误
  • 代表可预见、可恢复的异常情况
  • 继承自Exception但不是RuntimeException
  • 例如:IOException, SQLException, ClassNotFoundException

Unchecked Exception

  • 不强制在代码中显式处理
  • 编译时不检查,运行时才会发现
  • 通常代表程序错误或不可恢复的情况
  • 包括RuntimeException及其子类和Error及其子类
  • 例如:NullPointerException, ArrayIndexOutOfBoundsException, OutOfMemoryError

2. Interface和Abstract Class的区别是什么?何时使用接口?何时使用抽象类?

Interface和Abstract Class的区别:

特性 接口 抽象类
方法实现 只能有默认方法、静态方法和私有方法 可以有具体实现的方法
变量 只能有常量(public static final) 可以有各种类型的变量
继承 一个类可以实现多个接口 一个类只能继承一个抽象类
构造器 不能有构造器 可以有构造器
访问修饰符 方法默认为public 可以使用各种访问修饰符
设计目的 定义行为契约 提供基础实现和共享代码

何时使用接口

  • 需要定义一组行为规范而不关心实现细节
  • 需要支持多重继承
  • 不同类之间有共同的行为但没有继承关系
  • 需要实现"能力"的概念(如Comparable, Serializable)
  • 设计面向未来的扩展性

何时使用抽象类

  • 需要在相关类之间共享代码和状态
  • 需要非public的成员
  • 需要定义实例变量或构造器
  • 类之间有明确的继承关系和共同的基础功能
  • 需要控制子类必须实现的方法和可选实现的方法

3、网络/DNS相关问题

1. DNS递归解析的过程是怎样的?是否会直接访问根域名服务器?

DNS递归解析过程:

  1. 客户端查询:用户的设备向本地配置的DNS服务器发送查询请求
  2. 本地DNS服务器处理
    • 检查自身缓存,如有记录则直接返回
    • 如无缓存记录,则开始递归查询过程
  3. 递归查询过程
    • 本地DNS服务器向根域名服务器查询
    • 根域名服务器返回顶级域名(TLD)服务器地址
    • 本地DNS服务器向TLD服务器查询
    • TLD服务器返回权威域名服务器地址
    • 本地DNS服务器向权威域名服务器查询
    • 权威服务器返回最终IP地址
  4. 返回结果:本地DNS服务器将结果返回给客户端并缓存

普通用户的DNS查询通常不会直接访问根域名服务器,而是由本地DNS服务器代为查询。本地DNS服务器会缓存之前的查询结果,减少对根域名服务器的访问。

2. 根域名服务器是否会被大量请求"炸掉"?实际配置中如何处理DNS解析?

根域名服务器不会被"炸掉"的原因和实际DNS解析配置:

防止根域名服务器过载的机制

  1. 多层缓存:客户端、本地DNS服务器、ISP DNS服务器都有缓存
  2. 分布式部署:全球有13组根域名服务器,每组有多个镜像站点(共数百个节点)
  3. 任播技术(Anycast):相同IP地址在不同地理位置有多个服务器实例
  4. TTL机制:DNS记录有生存时间,减少重复查询
  5. 预取技术:某些DNS服务器会主动刷新即将过期的热门记录

实际DNS解析配置

  1. 本地DNS缓存:操作系统和浏览器维护DNS缓存
  2. 递归DNS服务器:ISP提供的DNS服务器处理大部分查询
  3. 转发器配置:许多本地DNS服务器配置为转发器模式,将查询转发给上级DNS
  4. 智能DNS:根据用户位置返回最近的服务器IP
  5. DNSSEC:提供DNS安全认证
  6. DNS负载均衡:通过轮询或权重分配流量
  7. CDN集成:与内容分发网络集成,优化访问速度

在实际应用中,绝大多数DNS查询在本地或ISP级别就能得到解答,只有极少数查询需要访问根域名服务器。

相关推荐
troy128几秒前
Codex 安全盲区:代码漏洞生成实测
windows·python·ci/cd·pycharm·django·github·fastapi
其实防守也摸鱼1 小时前
常见安全架构中 Shiro 的认证确认机制解析
运维·服务器·数据库·windows·github
m4Rk_2 小时前
【论文阅读】Agent 记忆机制(75):TiMem——用时间记忆树实现长期记忆的层级巩固
论文阅读·人工智能·学习·开源·github
今夕资源网3 小时前
一只鹈鹕骑着单车闯海岛:开源 3D 网页游戏《鹈鹕骑车》体验与部署教程 github开源
开源·github·ai智能测试题·ai智能测试
沙蒿同学5 小时前
我把架构约定编译成了会变红的测试:Wails v2 + Go + Vue3 桌面脚手架实战
前端·后端·github
落魄大学生之流水线上谋生计6 小时前
幻境相机 Mirage Camera
github
wangruofeng6 小时前
从一个 10 万星 AI Agent 项目里,能学到什么真正的软件工程
github·agent·ai编程
子林super6 小时前
复杂JSON Schema结构与输出校验
github
子林super6 小时前
结构化输出解析失败处理策略
github
怕浪猫7 小时前
拆解 Google《AI Agent Handbook》:企业级 Agent 的六层架构与产品矩阵
面试·github·agent