最长回文子序列
要点:两个for维护区间?
java
class Solution {
public int longestPalindromeSubseq(String s) {
char[] c =s.toCharArray();
int n = s.length();
int[][] f = new int[n][n];
for(int i = n-1; i>=0; i--){
f[i][i] =1;
for(int j = i+1; j < n; j++){
f[i][j] = c[i] == c[j] ? f[i+1][j-1] +2 : Math.max(f[i+1][j], f[i][j-1]);
}
}
return f[0][n-1];
}
}
多边形三角剖分的最低得分
要点:困难
java
class Solution {
public int minScoreTriangulation(int[] v) {
int n = v.length;
int[][] f = new int[n][n];
for (int i = n - 3; i >= 0; i--) {
for (int j = i + 2; j < n; j++) {
f[i][j] = Integer.MAX_VALUE;
for (int k = i + 1; k < j; k++) {
f[i][j] = Math.min(f[i][j], f[i][k] + f[k][j] + v[i] * v[j] * v[k]);
}
}
}
return f[0][n - 1];
}
}
二叉树的直径
要点:left,right
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//dfs
int ans =1;
public int diameterOfBinaryTree(TreeNode root) {
//int ans = 1;
depth(root);
return ans - 1;
}
public int depth(TreeNode root){
if(root == null){
return 0;
}
int left = depth(root.left);
int right =depth(root.right);
ans = Math.max(ans, left+right+1);
return Math.max(left, right) + 1;
}
}
二叉树中的最大路径和
要点:加上root。val
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int ans = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return ans;
}
public int dfs(TreeNode root){
if(root == null){
return 0;
}
int left = dfs(root.left);
int right = dfs(root.right);
ans = Math.max(left+right+root.val , ans);
return Math.max(root.val+Math.max(left, right), 0);
}
}
相邻字符不同的最长路径
要点:多叉树
java
class Solution {
private List<Integer>[] g;
private char[] s;
private int ans;
public int longestPath(int[] parent, String s) {
this.s = s.toCharArray();
int n = parent.length;
g = new ArrayList[n];
Arrays.setAll(g, e -> new ArrayList<>());
for (int i = 1; i < n; i++) {
g[parent[i]].add(i);
}
dfs(0);
return ans + 1;
}
private int dfs(int x) {
int maxLen = 0;
for (int y : g[x]) {
int len = dfs(y) + 1;
if (s[y] != s[x]) {
ans = Math.max(ans, maxLen + len);
maxLen = Math.max(maxLen, len);
}
}
return maxLen;
}
}
随机知识
核心题**:你觉得怎么保证消息不丢?RabbitMQ 为此提供了哪些机制?
面试官为什么这么问?
消息丢失是生产事故。我问这个是要你从生产端、Broker 端、消费端三个环节完整阐述,每个环节都说到关键机制。能说全说明你至少有排查过问题的意识。
希望听到怎样的回答:
- 生产端(Producer → Broker) :开启 Publisher Confirm 机制。生产者发消息后等待 Broker 返回确认,没收到就重发。
- Broker 端 :
- 队列持久化(
durable = true)。 - 消息持久化(
deliveryMode = 2)。 - 注意:持久化也不是绝对不丢,毕竟刚写完缓存还没刷盘可能宕机。更可靠可用镜像队列(Mirror Queue)。
- 队列持久化(
- 消费端(Broker → Consumer) :
- 关闭自动 ACK,采用手动确认 (
basicAck)。 - 处理成功才确认,失败则
basicNack或basicReject,消息重回队列或进入死信。
- 关闭自动 ACK,采用手动确认 (
- 总结:至少要提到Confirm + 持久化 + 手动 ACK 三者组合,才能做到消息可靠传递。
候选人 :
好的,这是一个生产环境中非常关键的问题。消息丢失可能发生在三个环节:生产端到 Broker 、Broker 自身 、Broker 到消费端。要保证消息不丢,就必须在每个环节都做好防护,形成闭环。
第一,生产端:防止发送时消息丢失。
Producer 把消息发送给 Broker 的过程中,可能因为网络抖动或 Broker 宕机导致消息没有送达。RabbitMQ 提供了 Publisher Confirm(发布确认) 机制来解决这个问题。
开启 Confirm 模式后,生产者每发一条消息,Broker 收到并持久化后,会返回一个 ACK 告诉生产者"我收到了"。如果消息投递失败(比如找不到指定的交换机),Broker 会返回 NACK,或者在一定时间内没有任何回应。
生产端代码需要处理这两种情况:收到 ACK 就继续发下一条;收到 NACK 或超时,就重发这条消息。Confirm 是异步的,可以批量确认,性能比事务机制好得多,是生产环境的标配。
第二,Broker 端:防止消息在存储时丢失。
消息到了 Broker 之后,如果 Broker 突然宕机重启,内存中的消息会全部丢失。所以 RabbitMQ 提供了持久化 能力,但持久化不是单一开关,需要同时满足两个条件:
- 队列持久化 :声明队列时设置
durable = true。这样即使 Broker 重启,队列本身不会被删除,积压的消息还在。 - 消息持久化 :发送消息时设置
deliveryMode = 2,消息属性标记为持久化。这样消息会被写入磁盘,而不是只存在内存里。
但注意,持久化也不是绝对安全。 RabbitMQ 并不是每收到一条消息就立刻刷盘,而是间隔一段时间批量写入,如果在刷盘间隔内 Broker 宕机,最近几条消息仍然可能丢失。所以有了更进一步的方案------镜像队列。
镜像队列把队列数据同步到集群中的多个节点上。消息写入主节点后,同步复制到从节点。主节点宕机,从节点自动接管,消息不会丢。代价是性能下降和网络开销增加。现在更推荐的是 Quorum Queue(仲裁队列),基于 Raft 协议保证多数节点一致,比老式镜像队列更可靠。
第三,消费端:防止处理时消息丢失。
Broker 把消息投递给消费者,如果消费者刚收到消息还没处理就挂了,消息也会丢。所以 RabbitMQ 在消费端提供了手动 ACK 机制。
默认是自动 ACK:消息投递给消费者,Broker 立刻删掉消息。消费者宕机导致消息没处理完,这条消息就永丢了。
生产上必须关掉自动 ACK,开启手动确认 :消费者处理完业务逻辑后,调用 basicAck 告诉 Broker 可以安全删除消息。如果业务处理失败,调 basicNack 或 basicReject,让消息重新入队或进入死信队列排队后续处理。
手动 ACK 是整个可靠性链的最后一道防线,处理成功才确认,失败就重试或补偿。
第四,用具体场景串联。
用户注册为例:注册成功后触发发短信。注册成功要保证短信至少投递一次。用的是 Confirm + 持久化 + 手动 ACK 的组合。生产者先发消息到 Broker,用 Publisher Confirm 确认消息已送达;消息和队列都持久化防止重启丢失。
消费端关掉自动 ACK,手动确认。如果消费端拿到消息后调短信服务失败,可以有两种处理方式:一是返回 NACK 把消息重新入队等待重试,需要做好幂等处理;二是超过重试上限后将消息转存到死信队列,后续人工或定时任务补偿处理,避免无限制重试对系统的冲击。
同时为了防止消息积压,可以配合死信队列加上监控告警,当积压超过阈值时能立刻发现并人工介入。
总结一句话:保证消息不丢,需要在生产端用 Publisher Confirm、Broker 端用持久化和镜像/仲裁队列、消费端用手动 ACK,三者组合形成可靠传输闭环。另外还有两个重要补充:一是声明 Exchange 和 Queue 时开启持久化,并通过管理界面确认 D 标识已经生效,因为切换配置后若不重建队列,旧的队列属性不会自动变化,需要物理删除再重新声明;二是即使消息不会丢失,也可能会重复,服务链路必须实现幂等,结合业务唯一键或消费记录去重表来保证数据的最终一致性。
碎碎念:后续会更新每天学习的八股和算法 题,开始准备秋招的第31天。努力连续更新100天!以后每天就按,秋招项目【java+agent】,科研,必做项目,算法,八股,锻炼身体来总结。
总结:得多动脑子
1.算法要系统过一遍【灵神】23/27【早上】1h
2.秋招项目,【java】开始实际看业务,1/6;无
【agent】还在学,整理cc,无,
3.科研要跑一下,【下午】3h+【晚上】3h
4.检测项目也得总结文档,无1h
6.背八股,无
7.锻炼身体,1h
反思:得用脑子多想,不能老依靠ai