组合
要点
方法1:dfs传什么【start,depth,+常规的四个】
java
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> ans = new ArrayList<>();
int[] path = new int[k];
dfs(1,0,n,k,path,ans);
return ans;
}
public void dfs(int start, int depth, int n, int k, int[] path, List<List<Integer>> ans){
if(depth == k){
List<Integer> list = new ArrayList<>();
for (int num : path) {
list.add(num);
}
ans.add(list);
return;
}
//int temp = i+1;
for(int j = start; j <= n; j++){
path[depth] = j;
dfs(j+1,depth+1,n,k,path,ans);
}
}
}
方法2:枚举,for,下一个是dfs(j-1),从后往前, d = k - path.size()
java
class Solution {
public List<List<Integer>> combine(int n, int k) {
//枚举下一个数字选什么
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
dfs(n, k, path,ans);
return ans;
}
public void dfs(int i, int k, List<Integer> path, List<List<Integer>> ans){
int d = k - path.size();
if(d == 0){
ans.add(new ArrayList<>(path));
return;
}
for(int j = i; j >= d; j--){
path.add(j);
dfs(j-1,k,path,ans);
path.removeLast();
}
}
}
方法3:选还是不选
java
class Solution {
public List<List<Integer>> combine(int n, int k) {
//枚举下一个数字选什么
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
dfs(n, k, path,ans);
return ans;
}
public void dfs(int i, int k, List<Integer> path, List<List<Integer>> ans){
int d = k - path.size();
if(d == 0){
ans.add(new ArrayList<>(path));
return;
}
//不选i
if(i>d){
dfs(i-1, k, path,ans);
}
//选
path.add(i);
dfs(i-1,k, path, ans);
path.removeLast();
}
}
组合总和 III
要点:
方法1:枚举,剪枝,dfs(j-1, k, n-j,path,ans)
java
class Solution {
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
dfs(9,k,n,path, ans);
return ans;
}
public void dfs(int i, int k, int n, List<Integer> path, List<List<Integer>> ans){
int d = k - path.size();
if(n <0 || n > (i * 2 - d + 1) * d / 2 ){
return;
}
if(d == 0 && n ==0){
ans.add(new ArrayList<>(path));
return;
}
for(int j = i ; j >= d; j--){
path.add(j);
dfs(j-1,k,n-j,path,ans);
path.removeLast();
}
}
}
方法2:选还是不选
java
class Solution {
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
dfs(9,k,n,path, ans);
return ans;
}
public void dfs(int i, int k, int n, List<Integer> path, List<List<Integer>> ans){
int d = k - path.size();
if(n <0 || n > (i * 2 - d + 1) * d / 2 ){
return;
}
if(d == 0 && n ==0){
ans.add(new ArrayList<>(path));
return;
}
//不选
if(i > d){
dfs(i-1,k,n,path,ans);
}
//选择
path.add(i);
dfs(i-1,k,n-i,path,ans);
path.removeLast();
}
}
括号生成
要点:选还是不选,left,right,什么时候return【right == n】
java
class Solution {
public List<String> generateParenthesis(int n) {
//选或者不选
List<String> ans = new ArrayList<>();
char[] path = new char[n*2];
dfs(0,0, n,path, ans);
return ans;
}
public void dfs(int left, int right,int n,char[] path, List<String>ans){
if(right == n){
ans.add(new String(path));
return;
}
if(left < n){
path[left+right] = '(';
dfs(left+1,right, n, path, ans);
}
if(right < left){
path[left+right] =')';
dfs(left, right+1, n, path, ans);
}
}
}
随机知识
三、持久化机制(高频)
RDB 和 AOF 有什么区别?你平时用什么?
面试官为什么这么问?
这题考察你对数据安全性和性能的权衡思维。我要看你是否知道它们各自的工作原理、优缺点,以及混合持久化的好处。
希望听到怎样的回答:
- RDB :快照持久化,某个时刻把整个内存写入 .rdb 文件。
- 优点:文件紧凑,恢复快,适合冷备。
- 缺点:两次快照之间的数据可能丢失(最近几分钟),生成快照时如果 Fork 子进程会阻塞主进程(对大数据集)。
- AOF :追加写命令,记录到 .aof 文件,默认每秒 Fsync。
- 优点:数据安全性高,最多丢失 1 秒数据;文件可读可重写;能用
bgrewriteaof压缩。 - 缺点:AOF 文件通常比 RDB 大,恢复慢。
- 优点:数据安全性高,最多丢失 1 秒数据;文件可读可重写;能用
- 混合持久化(Redis 4.0+):RDB 写全量,AOF 补增量,结合两者优点。
候选人 :
好的。RDB 和 AOF 是 Redis 的两种持久化机制,它们的核心区别在于持久化的时机 和数据的完整性。我从原理、优缺点和实际选型三个角度来讲。
第一,RDB 持久化。
RDB 是快照持久化 ,它把 Redis 在某个时间点的整个内存数据拍成一个"照片",写入到一个二进制的 .rdb 文件里。
触发方式有两种:一种是自动触发,在配置里写 save 900 1 之类的规则(900 秒内至少一个 key 变化就触发);另一种是手动执行 BGSAVE 命令。BGSAVE 会 fork 一个子进程来做快照,主进程继续处理请求。fork 过程利用了操作系统的写时复制机制------fork 的瞬间内存数据被标记为只读,主进程需要修改某个数据时,系统会先把这个数据页复制一份,主进程改新副本,子进程继续读旧版本。这样快照数据是 fork 时刻的一致性快照,不额外占用大量内存。
优点:文件紧凑,恢复大容量数据非常快,适合冷备、异地灾备这些场景。把 rdb 文件传到其他服务器直接加载就行。
缺点:两次快照之间有数据丢失的风险。如果配置 5 分钟保存一次,在这 5 分钟内 Redis 宕机,最近 5 分钟的数据全部丢失。另外,fork 子进程在大数据集下耗时较长,期间 Redis 的写时复制也会消耗额外内存和 CPU,对性能有一定影响。
第二,AOF 持久化。
AOF 是追加写命令的日志 。它把 Redis 执行的每一条写命令都记录到 .aof 文件末尾,就像 MySQL 的 binlog。Redis 重启时,把 AOF 文件里的命令重新执行一遍来恢复数据。
刷盘策略有三种:always 每条命令都立即刷盘,数据最安全但性能最差;everysec 每秒刷一次,最多丢失 1 秒数据,性能和安全性最平衡;no 交给操作系统刷盘,性能最好但可能丢失较多数据。默认是 everysec。
AOF 有一个重要的优化叫 AOF 重写(bgrewriteaof)。随着写入越来越多,AOF 文件会越来越大,很多记录其实是对同一个 key 的反复修改,只有最后一次有效。Redis 在后台 fork 子进程,根据当前内存数据的最终状态,生成一份新的最精简的 AOF 文件,替换掉老的。重写过程不影响主进程写操作------重写期间的增量命令会写入一个缓冲区,重写完成后追加到新文件末尾。
优点:数据安全性更高,最多就丢 1 秒的数据;AOF 是人类可读的文本格式,极端情况下可以手动修改恢复。
缺点:AOF 文件通常比 RDB 大;恢复时需要逐条重放命令,速度比 RDB 慢。如果 AOF 文件损坏或格式错误,启动时可能修复失败。
第三,混合持久化。
Redis 4.0 开始支持混合持久化,结合了 RDB 和 AOF 的优点。在 AOF 重写时,子进程把当前内存数据以 RDB 二进制格式写入新 AOF 文件的开头,之后的增量命令以 AOF 格式追加在后面。最终文件结构是 [RDB 快照][AOF 增量命令]。
恢复时,Redis 先快速加载 RDB 部分(恢复大部分数据),再执行 AOF 尾部少量增量命令(补齐最新数据),比纯 AOF 恢复快很多。数据安全性上,everysec 依然保证最多丢失 1 秒数据。
如果 Redis 支持 4.0 以上版本,且需要兼具恢复速度和数据安全性,混合持久化是最佳选择。
第四,项目中的实际选型。
我项目里的 Redis 有双重用途:缓存层和极少数轻量状态存储。具体选择取决于数据的重要性。
对于题目缓存、排行榜这类纯缓存数据,数据库里有完整的数据源,挂了重建就是。这部分数据只开 RDB,关闭 AOF(减少写磁盘开销和文件体积)。RDB 做冷备,提供宕机后快速恢复大部分热数据的能力,几分钟的数据空缺从数据库补回来。
对于部分用户会话状态、业务进度标记等不允许丢失的关键数据,开启混合持久化 + everysec 刷盘,保证最多丢失 1 秒数据,且重启加载快。
高可用方面不能只靠持久化,主从复制加哨兵模式是默认方案。主节点故障时自动切换,持久化文件是最后的兜底。
总结一句话:RDB 重冷备和恢复速度,AOF 重数据安全性,混合持久化兼具两者优点,实际选型看数据丢失的容忍度------缓存类低风险数据用 RDB,需要高安全性的状态数据用混合持久化。线上高可用核心靠主从和哨兵,持久化文件是最后一道保险。
碎碎念:后续会更新每天学习的八股和算法 题,开始准备秋招的第26天。努力连续更新100天!以后每天就按,秋招项目【java+agent】,科研,必做项目,算法,八股,锻炼身体来总结。
总结:项目的代码起码还是要逻辑理清楚,看懂,不然一点下问题都要搞半天
1.算法要系统过一遍【灵神】15/27【早上】大概1.5h
2.秋招项目,【java】开始实际看业务,2.9/10;无
【agent】还在学,无
3.科研要跑一下,无,
4.检测项目也得总结文档,6h+,【今天深度思考了,但是效果还是不好生气啊啊啊】
5.训练项目,无
6.背八股,无
7.锻炼身体,无
反思:今天想把检测羡慕搞一下,但是还没搞好,晚上看来下浪姐直播,好生气可恶!!
早睡早起,每天继续努力,时间分布还是要均匀点哇。