每日温度
要点:去掉没用的
方法1:从左到右
class Solution { public int[] dailyTemperatures(int[] temperatures) { int n = temperatures.length; Deque<Integer> stack = new ArrayDeque<>(); int[] ans = new int[n]; for(int i = n-1; i >=0; i--){ int t = temperatures[i]; while(!stack.isEmpty() && t >= temperatures[stack.peek()]){ stack.pop(); } if(!stack.isEmpty()) ans[i] = stack.peek() - i; stack.push(i); } return ans; } }方法2:从左到右
class Solution { public int[] dailyTemperatures(int[] temperatures) { int n = temperatures.length; Deque<Integer> stack = new ArrayDeque<>(); int[] ans = new int[n]; for(int i = 0 ; i <n; i++){ int t = temperatures[i]; while(!stack.isEmpty() && t > temperatures[stack.peek()]){ int j = stack.pop(); ans[j] = i - j; } stack.push(i); } return ans; } }接雨水
要点:单调栈
class Solution { public int trap(int[] height) { //单调栈 int ans = 0; Deque<Integer> stack = new ArrayDeque<>(); for(int i = 0; i < height.length; i++){ int h = height[i]; while(!stack.isEmpty() && h >= height[stack.peek()]){ int boottmH = height[stack.pop()]; if(stack.isEmpty()){ break; } int left =stack.peek(); int gao = Math.min(height[left], h) - boottmH; ans+= gao*(i - left -1); } stack.push(i); } return ans; } }滑动窗口最大值
要点:队列,维护一个窗口
class Solution { public int[] maxSlidingWindow(int[] nums, int k) { //单调栈但是要出列所以用队列 int n = nums.length; Deque<Integer> queue = new ArrayDeque<>(); int[] ans = new int[n-k+1]; for(int i = 0; i < n; i++){ //入栈 while(!queue.isEmpty() && nums[i] >= nums[queue.getLast()]){ queue.removeLast(); } queue.addLast(i); //出栈 if(queue.getFirst() < i - k +1){ queue.removeFirst(); } if(i >= k-1){ ans[i-k+1] = nums[queue.getFirst()]; } } return ans; } }随机知识
一、TCP 三次握手与四次挥手(必问)
核心题:TCP 为什么是三次握手而不是两次或四次?四次挥手为什么比握手多一次?
面试官为什么这么问?
这是网络协议的第一道门槛。我要看你能不能说清楚每次握手的报文和状态变化,以及背后“为什么这样设计”的思考,而不是背步骤。能讲清“防止旧连接请求造成混乱”的学生,才算真理解。
希望听到怎样的回答:
- 三次握手:
- 客户端发送 SYN=1, seq=x →SYN_SENT
- 服务端回复 SYN=1, ACK=1, seq=y, ack=x+1 →SYN_RCVD
- 客户端发送 ACK=1, seq=x+1, ack=y+1 → 双方进入ESTABLISHED
- 为什么不是两次?防止已失效的连接请求突然传到服务端,如果只两次握手,服务端收到旧的 SYN 就建连,白白浪费资源。第三次握手让客户端确认后才建连。
- 四次挥手:
- 主动方发送 FIN → FIN_WAIT_1
- 被动方回复 ACK → CLOSE_WAIT(主动方进入 FIN_WAIT_2)
- 被动方发送 FIN → LAST_ACK
- 主动方回复 ACK → TIME_WAIT,等待 2MSL 后关闭
- 比握手多一次是因为 TCP 是全双工的,一方关闭只是不再发数据,但还能收。所以需要双方各发一次 FIN 和一次 ACK。
候选人:
好的,这个问题考察的是对 TCP 连接管理和全双工通信的深层理解。我从三次握手的流程、为什么不是两次或四次,再到四次挥手多一次的原因,逐一说明。
第一,三次握手的过程。
三次握手本质上是双方确认各自的发送和接收能力都正常,并同步初始序列号。
- 第一次握手:客户端发送一个 SYN 报文,其中
SYN=1,序列号seq=x(随机生成的初始序列号)。客户端进入SYN_SENT状态。 - 第二次握手:服务端收到后,回复一个 SYN+ACK 报文,其中
SYN=1,ACK=1,确认号ack=x+1,同时生成自己的初始序列号seq=y。服务端进入SYN_RCVD状态。 - 第三次握手:客户端收到后,回复一个 ACK 报文,
ACK=1,确认号ack=y+1,序列号seq=x+1。客户端进入ESTABLISHED状态,服务端收到这个 ACK 后也进入ESTABLISHED状态,连接建立完成。
从这个过程可以看出:第一次握手让服务端知道客户端的发送能力和服务端的接收能力正常;第二次握手让客户端知道自己的发送和接收能力、服务端的发送和接收能力都正常;但此时服务端还不知道自己的发送能力和客户端的接收能力是否正常,所以需要第三次握手来确认。
第二,为什么不是两次握手?
两次握手意味着:客户端发 SYN,服务端回 SYN+ACK 后就直接进入 ESTABLISHED 状态,开始分配资源。这听上去能工作,但有一个致命缺陷:无法防止旧连接请求造成的混乱。
考虑这样一个场景:客户端向服务端发了一个 SYN 报文,但因为网络拥堵,这个报文在某处滞留了很久。客户端迟迟没收到确认,以为第一次握手丢了,于是重发一个新的 SYN,和新服务器三次握手建立连接、传输数据然后关闭了。
这时候,那个滞留在网络中的旧 SYN 终于到达了服务端。服务端并不知道这是一个早已过期的连接请求,如果只有两次握手,服务端收到这个 SYN 就直接回复 SYN+ACK 并进入 ESTABLISHED、分配连接资源。而客户端此时根本不知道这个连接,也不可能回复数据,服务端就一直空等,浪费资源。
三次握手就解决了这个问题:服务端收到那个旧 SYN 后,回复 SYN+ACK 进入 SYN_RCVD 状态,等待客户端的第三次 ACK。客户端发现这个连接不是自己发起的,直接回复 RST 拒绝连接。服务端收到 RST 就知道这是无效连接,不分配资源。这样,只有客户端真正主动发起的连接才会进入 ESTABLISHED 状态,不会因为旧报文浪费服务端资源。
第三,为什么不是四次握手?
从理论上,四次握手也可以建立连接——双方各发一次 SYN,各收一次 ACK。但实际上,服务端可以把对客户端 SYN 的确认(ACK)和它自己的 SYN 合并成一个报文(SYN+ACK)发出,把四次变成三次。这样既保证了可靠性,又减少了通信次数,是效率和可靠性的最优点。
第四,四次挥手的过程。
挥手比握手多一次,根本原因是TCP 是全双工的,两个方向的数据传输需要分别关闭。
第一次挥手:主动关闭方(假设是客户端)调用 close,发送 FIN 报文,
FIN=1,seq=u。客户端进入FIN_WAIT_1状态。第二次挥手:被动方(服务端)收到 FIN 后,回复 ACK 报文,
ACK=1,确认号ack=u+1。服务端进入CLOSE_WAIT状态,客户端收到 ACK 后进入FIN_WAIT_2状态。至此,客户端 → 服务端方向的数据传输已经关闭,客户端不能再发数据了。但服务端可能还有数据要发给客户端,所以服务端 → 客户端这个方向还开着。
第三次挥手:等服务端把剩余数据都发完了,它也调用 close,发送 FIN 报文,
FIN=1,seq=v。服务端进入LAST_ACK状态。第四次挥手:客户端收到服务端的 FIN 后,回复 ACK 报文,
ACK=1,确认号ack=v+1。客户端进入TIME_WAIT状态,等待 2MSL(最大报文生存时间的两倍)后进入 CLOSED。服务端收到 ACK 后直接关闭。
第五,为什么挥手比握手多一次?
握手阶段,双方都是在建立连接,可以合并控制报文。但挥手时,主动方说“我不说了”,被动方可能还有话没说完。被动方收到 FIN 后,先回一个 ACK 告诉对方“我知道你不说了”,但它自己可能还要继续发送剩余数据。等它把话说完了,才能发自己的 FIN 请求关闭另一个方向。
所以,被动方的 ACK 和 FIN 不能合并成一次发送,必须分两次——这就导致挥手必然比握手多一次。
第六,TIME_WAIT 为什么需要等待 2MSL?
两个原因:
- 确保最后一个 ACK 能到达服务端。如果客户端发的第四次挥手的 ACK 丢了,服务端收不到确认就会重发 FIN。客户端在 2MSL 内如果收到了服务端的重发 FIN,就知道 ACK 丢了,再重发一次 ACK。如果客户端直接关掉,丢 ACK 后服务端就无法正常关闭。
- 让旧连接的所有报文在网络中消散。MSL 是一个报文在网络中的最大存活时间,等待 2MSL 可以确保本次连接中产生的所有报文都从网络中消失,不会干扰到下一次同四元组的新连接。
总结一句话:三次握手是为了防止旧连接请求导致的服务端资源浪费,兼顾效率和可靠性;四次挥手比握手多一次,是因为全双工通信需要两个方向分别关闭,被动方的 ACK 和 FIN 无法合并,必须分开发送;TIME_WAIT 等待 2MSL 是为了保证最后一个 ACK 可靠送达,并清理网络中残留的旧报文。
碎碎念:后续会更新每天学习的八股和算法 题,开始准备秋招的第33天。努力连续更新100天!以后每天就按,秋招项目【java+agent】,科研,必做项目,算法,八股,锻炼身体来总结。
总结:得多动脑子
1.算法要系统过一遍【灵神】27/27【晚上】1h
2.秋招项目,【java】开始实际看业务,1/6;无
【agent】还在学,整理cc,无,
3.科研要跑一下,【下午】3h
4.检测项目也得总结文档,【早上】3h
6.背八股,无
7.锻炼身体,无
晚上回来玩手机了
反思:需要做一个大总结,合理规划时间