自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(136)
  • 收藏
  • 关注

原创 Feed 缓存不要缓存用户态:用页骨架和条目片段拆开共享数据

本文详解公开Feed缓存设计:通过分离公共内容(页骨架、条目片段)与用户私有状态(liked/faved),实现高效复用与精准个性化。采用Caffeine+Redis三级缓存,以idsKey为粒度做single-flight防击穿,随机TTL防雪崩。关键原则:用户态状态返回前动态计算,任意片段缺失则整页回源,禁止残页。点赞仅更新计数与受影响页面,不重建整页。当前无分布式锁与故障降级机制,存在演进空间。核心边界:共享缓存仅含公共字段,用户态必须实时重算。

2026-09-25 00:20:34 361

原创 LeetCode 438:找到字符串中所有字母异位词——定长窗口 + 欠债种类数

本文通过分析字符串异位词匹配问题,提出基于“欠债表”与“欠债种类数”的高效解法。核心思想:用频次数组记录窗口对目标串的字符“欠债”,len仅统计未凑齐的字符种类数,避免个数与种类混用。滑动窗口保持固定长度,每轮右移一字符后,若窗口长度达标且len==0,则记录起始位置;随后移除左端字符,仅当其原数量恰好为0时才更新len。该方法正确处理无关字符(如e)和重复字符(如aab),确保不漏不重。时间复杂度O(|s|+|p|),空间复杂度O(1)(忽略输入复制)。关键点:a[c]==0表示种类刚齐,而非字符是否在p

2026-09-23 21:34:43 167

原创 点赞计数为什么要先聚合,再写入固定长度 Redis String

本文阐述了点赞计数链路中Bitmap、Redis Hash与固定长度SDS三类数据的分工机制,重点分析Kafka消息传递、Redis增量聚合与SDS汇总写入的并发与可靠性边界。核心要点包括:事件需在Redis写入后提交Kafka位点以避免少计;刷写时应扣减而非删除聚合字段以防丢失并发增量;SDS读改写必须通过Lua原子操作防止覆盖;当前实现存在生产端不可靠投递、消费端无幂等、无实体分区键及计数漂移无法自动修复等四重边界。演进方向包括双写原子化、事件幂等、分区有序与周期对账,四项缺一不可。

2026-09-22 11:44:36 290

原创 点赞状态是一位,不要把雪花 ID 当成 Bitmap 下标

本文阐述了点赞行为在Redis Bitmap中的高效存储与查询机制:采用分片策略(CHUNK_SIZE=32768),将雪花ID映射为分片号和位偏移,避免单Key过大导致的内存膨胀;通过单一Key、O(1)的GETBIT/SETBIT实现状态判断与翻转;总赞数优先读取快速结构,仅在异常时锁住重建;强调分片设计平衡大Key风险与命令开销,明确O(1)适用范围,避免夸大性能。

2026-09-22 11:41:52 303

原创 LeetCode 3:无重复字符的最长子串——重复后从下一个起点重新扫描

本文讨论了寻找字符串中不含重复字符的最长子串长度的问题。最初尝试用HashMap记录字符最后出现位置,但存在逻辑缺陷:仅依赖重复字符下标差无法保证中间无重复,且可能遗漏末尾子串。修正思路是:以每个位置为起点,用HashMap统计当前扫描区间字符频次,遇重复则清空并回退起点,重新扫描。每轮更新答案,确保覆盖所有情况。时间复杂度最坏为O(n²),空间复杂度O(min(n,字符集大小))。关键点在于正确推导r = r - len + 1,并配合for循环自增实现起点切换。通过“abba”“dvdf”等案例验证正确

2026-09-20 21:39:36 210

原创 关掉标签再打开,Refresh Token 还在却被强制重新登录

页面刷新后,因前端直接使用过期AccessToken请求/auth/me导致认证失败,误将仍有效的RefreshToken一并清除,引发强制重新登录。问题根源在于:未在恢复流程中优先用RefreshToken刷新AccessToken,且定时刷新周期(60秒)大于刷新窗口(5秒),导致多数情况下无法及时续期。此外,错误处理未区分401与网络异常,一律清空会话。建议改为:读取本地令牌后,若AccessToken即将过期则先刷新,仅当刷新失败才清除状态;401时尝试刷新,网络错误则保留凭证。当前实现尚未落地此逻

2026-09-18 22:39:43 448

原创 登录验证码为什么能改密码

验证码需按场景与账号隔离存储,Key必须包含scene和identifier,防止跨场景复用。校验成功后须立即删除记录,不可仅依赖TTL。发送侧限流分间隔、日次数、失败次数三类,避免被刷或猜码。当前实现为先读再删,存在并发重放风险,建议使用Lua脚本原子化处理。验证码状态依赖Redis,不可用时应关闭发送与校验。对外返回应模糊化,防止账号枚举。当前方案可演进至原子校验、独立限流策略及更安全的错误提示。

2026-09-18 22:13:39 216

原创 LeetCode 15. 三数之和

本文分析了三数之和问题的正确解法。核心思路是排序后固定第一个数,用双指针在右侧寻找另两个数,通过i<j<k保证下标不同,利用排序实现去重。原思路依赖Set判断值存在,无法保证下标独立,易出错。正确做法在固定i时跳过重复值,找到答案后移动双指针并避免重复加入。时间复杂度O(n²),空间复杂度O(1)。关键在于明确候选范围与去重层级。

2026-09-16 21:44:41 145

原创 知识分享平台项目复习第二天:登录模块中的双令牌与刷新安全(2)

项目复习第二天聚焦认证安全,梳理了XSS、CSRF与CORS的核心区别:XSS是恶意脚本在目标网站执行,可窃取localStorage中的令牌;CSRF利用浏览器自动携带Cookie,伪造用户请求;CORS仅限制跨源响应读取,不防请求发送。当前项目将令牌存于localStorage,面临XSS风险,虽用HttpOnly降低RefreshToken泄露,但无法阻止脚本冒用权限。结合SpringSecurity链路,明确401为认证失败,403为授权不足,且permitAll()不跳过安全过滤器链。

2026-09-16 10:50:13 88

原创 LeetCode 11. 盛最多水的容器

本文分析了盛最多水容器问题的正确解法。初始错误思路仅维护左侧最高柱子,忽略了“高度较低但距离更远”的组合可能更优。正确方法采用双指针:从两端开始,面积由较短边决定,每次移动较短一侧,因保留短板无法提升高度而宽度减小,故可安全舍弃。该策略确保不遗漏最优解,时间复杂度O(n),空间复杂度O(1)。

2026-09-14 23:36:48 184

原创 知识分享平台项目复习第一天:登录模块中的双令牌与刷新安全

本文系统梳理了基于JWT的双令牌登录机制,重点解析验证码登录、RefreshToken白名单与轮换、登出空窗期等核心设计。通过Redis仅存储jti实现无状态认证,保障性能与可撤销性;利用Lua脚本原子化处理刷新流程,防止并发重复使用旧令牌;明确区分AccessTokenTTL、RefreshTokenTTL与Rotation的关系,理解滑动会话与绝对期限差异。针对登出后空窗期问题,提出黑名单方案作为即时失效补充。最终强调:登录状态生命周期管理依赖于服务端对刷新请求的正确处理,而非客户端过期触发。

2026-09-13 23:29:30 314

原创 LeetCode 49. 字母异位词分组

这道题的关键是为每组字母异位词找到统一标识。将字符排序后得到的字符串可以作为规范化 key,使不同顺序但字符组成相同的字符串进入同一个分组。本次主要问题不是分组思路,而是创建分组时判断错了对象。遇到类似问题时,可以先手动模拟每组的第一个元素,确认新的 key 能否被创建并立即保存当前数据。

2026-09-11 11:39:38 127

原创 LeetCode 128. 最长连续序列

这道题的关键不是把相邻数字都数一遍,而是先认出每一段的起点。x-1不在集合中时,从x向后走到断掉,用y - x得到段长,再和全局答案取最大。本次主要问题有两层:一开始把多段长度加在了一起;后来虽然找到了起点,但既没有保留历史最长,也没有从起点把整段扫完。遇到类似「无序集合里找连续段」的问题时,可以先问自己:当前这个数是不是这段的起点?

2026-09-11 11:39:01 353

原创 OMP 默认快捷键说明

oh-my-Pi的快捷键说明,叫你如何切换模型的角色

2026-09-09 20:19:18 117

原创 LeetCode 5. 最长回文子串

这题的优化关键是利用回文串的中心对称性,将“枚举子串并完整检查”改成“枚举中心并向两边扩展”。实现时需要同时处理奇数、偶数两种中心,并牢记扩展结束后指针已经多走一步,实际长度为。

2026-09-09 10:50:52 235

原创 LeetCode 72. 编辑距离

这题的核心不是“怎么操作”,而是“怎么定义状态”。把状态定义成两个前缀的最少编辑次数后,递推就只剩三种选择:删、插、换。记忆化搜索和二维 DP 本质一致,只是写法不同。

2026-09-08 17:40:16 204

原创 LeetCode 1143. 最长公共子序列

dp[i][j]:text1 的前 i 个字符和 text2 的前 j 个字符的最长公共子序列长度因为i和jtext1[i - 1] 和 text2[j - 1]这题的关键是把状态定义为两个字符串的前缀,而不是以某个字符结尾的字符串。当当前字符相等时,继承左上角状态并加一;当当前字符不相等时,分别舍弃两个字符串的当前字符,取两条路径中的较大值。

2026-09-07 20:13:42 149

原创 LeetCode 64. 最小路径和

dfs(i,j):从 (0,0) 到达 (i,j) 的最小路径和。这个定义的重点是:返回值已经包含当前位置grid[i][j]。所以起点不是返回0grid[0][0]这题的核心不是换解法,而是让边界返回值服务于min这个转移。dfs(i,j):到达坐标 (i,j) 的最小路径和那么越界就应该返回“大数”来表示非法路径,起点就应该返回grid[0][0]来计入起点值。用[[1,2]]这种一行网格手推一遍,很容易发现越界返回0会把非法路径选进来。

2026-09-06 21:43:26 195

原创 LeetCode 62. 不同路径

dfs(i,j):从 (0,0) 到达 (i,j) 的不同路径数。ij是坐标下标,不是行数和列数。dfs(i,j):到达坐标 (i,j) 的路径数这题的核心不是换解法,而是把“网格尺寸”和“坐标下标”区分清楚。dfs(i,j):到达坐标 (i,j) 的路径数用这种最小用例手推一遍,很容易发现入口是否偏了一格。

2026-09-04 21:57:49 254

原创 LeetCode 32. 最长有效括号

这题的关键是先把“括号是否匹配”和“有效子串是否连续”拆开。当前 ')' 只能匹配最近的、尚未匹配的 '('isValid被标记的位置连续多少个,最长有效括号子串就是多少。这份写法虽然不是最省空间的栈写法,但思路清晰:先标记所有有效位置,再统计最长连续有效区间。

2026-09-03 23:21:34 216

原创 LeetCode 416. 分割等和子集

d[j] 表示:在已经处理过的数字中,能不能凑出和 j。表示什么都不选时,可以凑出和0。d[target]这道题的关键是把“能不能分成两个和相等的子集”转成“能不能凑出sum / 2最开始用 DFS 的“选或不选”方向是对的,但需要注意递归返回值、数组边界和重复状态。以及为什么内层循环必须倒序。倒序不是为了防止下标越界,而是为了防止同一个数字在同一轮里被重复使用。

2026-09-03 23:20:51 295

原创 LeetCode 22. 括号生成

obsidian。

2026-09-03 12:05:17 187

原创 LeetCode 152. 乘积最大子数组

该文章介绍了如何找到数组中乘积最大的非空连续子数组。关键思路是同时维护以当前元素结尾的最大乘积(fmax)和最小乘积(fmin),因为负数可能使最小值变为最大值。文章指出了原始代码的问题:在更新fmin时错误使用了已更新的fmax值。正确的做法是先暂存旧的fmax值,再分别计算新的fmax和fmin。给出了正确的状态转移方程和实现代码,并强调了需要考虑当前元素单独作为子数组的情况。最后提供了多个测试用例和复杂度分析(O(n)时间,O(1)空间)。核心思想是利用动态规划处理乘积中的负数翻转问题。

2026-08-23 16:44:58 238

原创 LeetCode 300. 最长递增子序列

本文讲解 LeetCode 300「最长递增子序列」的 O(n^2) 记忆化搜索思路。核心是先固定子问题:定义 dfs(i) 表示“以 nums[i] 结尾的最长递增子序列 长度”。这样每个状态只需要向左枚举前面的下标 j,当 j < i 且 nums[j] < nums[i] 时,说明 nums[i] 可以接在 nums[j] 后面,转移为 dfs(i) = max(dfs(j)) + 1;如果没有合法前驱,则只选 nums[i] 自己,长度为 1。

2026-08-23 12:20:01 175

原创 LeetCode 322. 零钱兑换

本文围绕 LeetCode 322「零钱兑换」梳理了从暴力递归到记忆化搜索的完整思路。最初通过“选当前硬币 / 不选当前硬币”进行搜索,但由于把“无 解”和“刚好凑完”都返回 0,导致结果混淆。随后将状态明确为 dfs(i, amount),表示只使用 coins[i...] 凑出 amount 的最少硬币数,并用二维 memo[i][amount] 记录完整状态。文章重点说明了边界条件、无穷大哨兵值的选择、以及为什么选当前硬币后 i 不能后移,最后给出正确的状态转移与 复杂度分

2026-08-22 21:39:02 536

原创 LeetCode 139. 单词拆分

摘要: 题目要求判断字符串s是否能由字典wordDict中的单词拼接而成(可重复使用)。采用动态规划,定义dp[i]表示前i个字符能否被拆分。初始化dp[0]=true,遍历s时枚举切分点j,若dp[j]为真且子串s[j..i-1]在字典中,则dp[i]=true。需注意下标对齐(dp[i]对应前i字符,substring(j,i)取j到i-1)。时间复杂度O(n³),空间O(n)。关键点:状态定义统一、边界正确处理、初始化dp[0]。例如,s="leetcode",wordDict=[

2026-08-22 21:37:33 199

原创 LeetCode 279. 完全平方数

摘要:本文探讨了求解和为n的完全平方数最少数量的问题。通过递归枚举思路,将问题转化为状态转移,并引入记忆化技术优化重复计算。关键点在于状态定义(使用前i个平方数凑出j)和转移方程(允许重复使用同一平方数)。文章分析了易错点(如最优值覆盖、共享变量问题等),给出了测试用例和复杂度分析(O(n√n)时间/空间)。最终解决方案采用动态规划思想,状态转移方程为dfs(i,j)=min(dfs(i-1,j), dfs(i,j-i*i)+1)。

2026-08-21 23:14:57 188

原创 LeetCode 198. 打家劫舍

这篇文章详细讲解了如何解决房屋偷窃问题,即在不相邻偷窃条件下获取最大金额。初始思路是暴力DFS枚举所有可能,但会超时。然后引入记忆化搜索优化重复子问题,定义了状态转移方程dfs(i)=max(nums[i]+dfs(i+2),dfs(i+1))。进一步转化为自底向上的动态规划,通过从右向左填表计算,将空间复杂度优化到O(n)。最终版本使用两个变量代替数组,将空间复杂度降至O(1)。文章还列举了常见错误点,并分析了各方法的时间复杂度均为O(n),强调明确递归函数定义是解题关键。

2026-08-19 22:37:29 323

原创 LeetCode 763. 划分字母区间

摘要:该问题要求将字符串划分为最多片段,使同一字母仅出现在一个片段中。解决方法是:1) 记录每个字母的最后出现位置;2) 扫描字符串时动态维护当前片段的最远边界(rightReach),当扫描位置达到边界时切分片段。关键点在于通过比较所有已出现字母的最后位置来扩展边界,确保片段独立性。时间复杂度O(n),空间复杂度O(1)。示例输入"ababcbacadefegdehijhklij"输出[9,7,8],对应片段"ababcbaca"、"defegde&quo

2026-08-18 22:26:00 194

原创 LeetCode 45. 跳跃游戏 II

本文介绍了一种贪心算法来解决跳跃游戏II问题,旨在找到从数组起点到终点的最少跳跃次数。关键思路是维护当前跳跃范围(curEnd)和下一跳最远边界(nextEnd),遍历时不断更新nextEnd。当遍历完当前层时增加跳跃次数(ans++)并更新curEnd为nextEnd。算法通过O(n)时间复杂度的单次遍历,避免了DFS的指数级复杂度。文章详细演示了示例[2,3,1,1,4]的手动模拟过程,并提醒注意边界条件和常见错误(如混淆curEnd/nextEnd、错误增加跳跃次数等)。最终代码简洁高效,仅需维护三个

2026-08-17 23:30:00 638

原创 LeetCode 55. 跳跃游戏

这道跳跃游戏问题要求判断能否从数组起点跳到终点。解题关键在于维护当前可达的最远下标maxReach:遍历数组时,若当前位置i超过maxReach则失败;否则用i+nums[i]更新maxReach。例如[2,3,1,1,4]可达,而[3,2,1,0,4]不可达。贪心算法只需O(n)时间和O(1)空间,通过不断扩展可达范围来判断终点是否可达,而无需考虑具体跳跃路径。核心步骤是先检查当前位置是否可达,再更新maxReach。

2026-08-16 23:43:02 201

原创 LeetCode 295. 数据流的中位数——双堆维护中位数

摘要: 设计一个高效数据结构,支持动态添加整数并快速查找中位数。核心思路是使用两个堆:大顶堆l保存较小的一半数字,小顶堆r保存较大的一半。维护两个不变量:1) l的元素均≤r的元素;2) l的大小等于r或比r多1。添加元素时,通过堆的插入和转移操作保持平衡,确保中位数仅与堆顶相关:奇数时取l的堆顶,偶数时取两堆顶平均值。操作时间复杂度为O(logn)(插入)和O(1)(查询),空间复杂度O(n)。此方法避免了全局排序,仅需维护部分有序性。

2026-08-15 23:10:20 227

原创 LeetCode 347. 前 K 个高频元素——HashMap 统计频率 + 桶排序取元素

摘要:题目要求找出整数数组中出现频率前k高的元素。核心思路是先用HashMap统计频率,然后通过桶排序将元素按频率分组,最后从高频到低频收集元素。易错点包括混淆key/value、桶初始化、同频元素处理等。时间复杂度O(n),空间复杂度O(n)。关键是通过频率定位元素,而非直接排序频率值。测试用例需考虑同频和边界情况。

2026-08-15 23:09:45 198

原创 LeetCode 215. 数组中的第 K 个最大元素

本文介绍了使用快速选择算法在未排序数组中查找第k大元素的方法。关键点包括:1) 将第k大转换为升序数组中的目标下标target=nums.length-k;2) 采用随机pivot和双指针划分的partition方法,避免重复元素导致的性能退化;3) 每次partition后根据pivot位置与target的比较,仅处理相应区间。算法平均时间复杂度O(n),最坏O(n^2),空间复杂度O(1)。注意事项包括正确处理目标下标、区分pivot下标与值、以及处理重复元素的情况。

2026-08-15 00:22:51 191

原创 LeetCode 84:柱状图中的最大矩形——单调栈如何确定左右边界

摘要 柱状图最大矩形面积问题要求在给定柱状图中找到面积最大的矩形。核心思路是利用单调栈高效确定每根柱子左右第一个更矮的边界:对于每根柱子,找到其左右第一个高度小于它的柱子位置,计算以该柱子高度为矩形高度的最大面积。通过维护单调递增栈,在O(n)时间内解决问题。关键点包括:栈存储下标而非高度;遇到更矮柱子时持续弹栈;正确计算宽度为右边界-左边界-1;使用前后哨兵简化边界处理。该算法避免了暴力解法的O(n²)复杂度,通过单调性优化显著提升了效率。

2026-08-13 17:37:14 347

原创 LeetCode 739:每日温度——为什么单调栈要持续弹出

这篇文章讨论了如何利用单调栈解决每日温度问题。给定每日温度数组,要求返回一个数组,其中每个元素表示当前温度之后第一个更高温度出现的天数差。 关键思路: 使用单调递减栈保存未找到更高温度的下标 遍历温度数组时,当前温度若大于栈顶温度,则循环弹出栈顶并计算天数差 将当前下标压入栈中,等待后续更高温度 实现要点: 栈中只需保存下标而非温度值 每次处理可能连续解决多个栈顶元素(用while而非if) 答案应填写在被弹出的下标位置 所有元素最终都会入栈 复杂度:时间O(n),空间O(n)。典型用例验证了算法在递增、递

2026-08-13 17:29:45 346

原创 LeetCode 394:字符串解码——为什么遇到括号要保存现场

这篇文章讨论了如何解码包含嵌套结构的编码字符串(如3[a2[c]])。核心思路是使用栈来保存每一层的状态:遇到[时将当前字符串和重复次数压栈,并重置状态;遇到]时弹出栈顶信息进行字符串重复和拼接。文章通过实例逐步分析处理数字、字母和括号的逻辑,强调了重置状态的重要性,并提供了Java代码实现。关键点包括:处理多位数、正确保存和恢复嵌套层级状态,以及测试用例的设计。最终解法的时间复杂度与输出长度相关,空间复杂度由栈深度决定。

2026-08-13 00:20:32 392

原创 LeetCode 155:最小栈——为什么不能只用一个全局 min

本文介绍了一种支持O(1)时间复杂度的最小栈实现方法。通过使用两个栈(主栈和辅助栈)同步操作,主栈存储实际元素,辅助栈存储对应栈状态下的最小值。关键点在于每次push操作时,辅助栈压入当前值与辅助栈顶的较小值;pop操作时两个栈同步弹出,确保辅助栈顶始终反映当前栈的最小值。这种方法避免了使用全局变量导致的历史状态恢复问题,正确维护了每个栈状态的最小值。测试用例验证了重复值、最小值弹出等边界情况,所有操作的时间复杂度均为O(1),空间复杂度为O(n)。

2026-08-11 22:17:57 201

原创 LeetCode 20:有效的括号——为什么只统计数量还不够

本文介绍了判断括号字符串有效性的解法。最初使用HashMap统计括号数量的方法存在问题,无法处理闭合顺序错误的场景(如"([)]")。正确的解法是使用栈结构:遇到左括号入栈,遇到右括号时检查栈顶是否匹配。通过维护未闭合括号的顺序,确保正确嵌套。Java实现采用Deque作为栈,时间复杂度O(n),空间复杂度O(n)。关键测试用例包括基本匹配、右括号开头、顺序错误和未闭合左括号等情况。该问题典型展示了栈在处理"最近相关"问题的优势。

2026-08-11 22:17:19 258

原创 4. 寻找两个正序数组的中位数:用分割点代替合并

本文介绍了在两个有序数组中找到中位数的高效算法。关键思路是通过二分法在较短数组上确定分割点,确保左半部分元素数量等于总长度一半,并满足左半部分最大值小于等于右半部分最小值。算法时间复杂度为O(log(min(m,n))),空间复杂度O(1)。主要步骤包括:交换保证nums1是较短数组;计算half值;二分查找满足条件的分割点;处理边界情况;根据奇偶长度返回对应中位数。该算法避免了直接合并数组,通过比较四个边界值即可确定中位数。

2026-08-07 21:29:18 240

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除