2026
2026年08月
xxx
2026年07月
3014. 输入单词需要的最少按键次数 I
20260730
题目给定一字符串 word,设 word 的长度为 n,k = n / 8(下取整)

ans =8 * (1+2+...+k) + n%8 * k+1
将出现次数多的放在前面。这样就可以少按一下。
int[] cnt =new int[26];
for(char ch: word.toCharArray()){
cnt[ch-'a']++;
}
for(int i =0;i<26;i++){
ans += cnt[25-i] * (i%8 +1);
}2685. 统计完全连通分量的数量
连通分量:如果子图中任意两个顶点之间都存在路径,并且子图中没有任何一个顶点与子图外部的顶点共享边,则称为连通分量。
完全联通分量:如果联通分量中没对节点之间都存在一条边,则称为完全两通分量。
142. 环形链表 II
Details
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。示例 2:

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。示例 3:

输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。提示:
- 链表中节点的数目范围在范围
[0, 104]内 -105 <= Node.val <= 105pos的值为-1或者链表中的一个有效索引
【图解】一张图秒懂环形链表 II,Floyd 判圈算法(Python/Java/C++/C/Go/JS)
参考灵神的题解, 这里只是做自己的理解。

设从头结点到入口的距离是 a,从入口到相遇点的距离为 b,相遇见到入口的距离为 c,如图
环长 =
慢指针移动的距离 =
快指针移动的距离 =,k 表示在环中移动的次数
那么就有等式:
=>
=> $a - c = (k-1)(b+c) $ => 这意味着什么?
slow 从相遇点出发,head 从头结点出发,走 c 步后,slow 在入口,head 到入口的距离也恰好是环长的倍数,继续走,两者必然会在入口相遇。
2026年06 月
1967. 作为子字符串出现在单词中的字符串数目TODO
Details
给你一个字符串数组 patterns 和一个字符串 word ,统计 patterns 中有多少个字符串是 word 的子字符串。返回字符串数目。
子字符串 是字符串中的一个连续字符序列。
示例 1:
输入:patterns = ["a","abc","bc","d"], word = "abc"
输出:3
解释:
- "a" 是 "abc" 的子字符串。
- "abc" 是 "abc" 的子字符串。
- "bc" 是 "abc" 的子字符串。
- "d" 不是 "abc" 的子字符串。
patterns 中有 3 个字符串作为子字符串出现在 word 中。示例 2:
输入:patterns = ["a","b","c"], word = "aaaaabbbbb"
输出:2
解释:
- "a" 是 "aaaaabbbbb" 的子字符串。
- "b" 是 "aaaaabbbbb" 的子字符串。
- "c" 不是 "aaaaabbbbb" 的字符串。
patterns 中有 2 个字符串作为子字符串出现在 word 中。示例 3:
输入:patterns = ["a","a","a"], word = "ab"
输出:3
解释:patterns 中的每个字符串都作为子字符串出现在 word "ab" 中。提示:
1 <= patterns.length <= 1001 <= patterns[i].length <= 1001 <= word.length <= 100patterns[i]和word由小写英文字母组成
题目简单,就是判断 pattern 中给出的字符串中有几个是 word 的子串,第一种暴力求解, 判断 word.contains(s),第二种是自动机,也是需要学习的内容。
两种方法:暴力 / AC 自动机(Python/Java/C++/Go)
2095. 删除链表的中间节点
Details
给你一个链表的头节点 head 。删除 链表的 中间节点 ,并返回修改后的链表的头节点 head 。
长度为 n 链表的中间节点是从头数起第 ⌊n / 2⌋ 个节点(下标从 0 开始),其中 ⌊x⌋ 表示小于或等于 x 的最大整数。
- 对于
n=1、2、3、4和5的情况,中间节点的下标分别是0、1、1、2和2。
示例 1:

输入:head = [1,3,4,7,1,2,6]
输出:[1,3,4,1,2,6]
解释:
上图表示给出的链表。节点的下标分别标注在每个节点的下方。
由于 n = 7 ,值为 7 的节点 3 是中间节点,用红色标注。
返回结果为移除节点后的新链表。示例 2:

输入:head = [1,2,3,4]
输出:[1,2,4]
解释:
上图表示给出的链表。
对于 n = 4 ,值为 3 的节点 2 是中间节点,用红色标注。示例 3:

输入:head = [2,1]
输出:[2]
解释:
上图表示给出的链表。
对于 n = 2 ,值为 1 的节点 1 是中间节点,用红色标注。
值为 2 的节点 0 是移除节点 1 后剩下的唯一一个节点。提示:
- 链表中节点的数目在范围
[1, 105]内 1 <= Node.val <= 105
主要思路:
看到链表的中间节点,就应该想到快慢指针, 就不用模拟去计算链表的长度。
让快指针先走一步,这样的话,慢指针的位置刚好是在中间节点的前一个,然后删除到慢指针后一个节点就可以了。
龟兔赛跑(Python/Java/C++/C/Go/JS/Rust)
扩展链表的遍历:
- 链表(链式存储)基本原理
- 增:
- 在头节点加:p.next = head; head = p;
- 在中间加:p.next = s.next; s.next = p;
- 在尾部加: tail.next = p; p.next = null;
- 删:
- 在头部删: return head.next;
- 在中部删:s.next = s.next.next;
- 在尾部删:s.next = null;
- 查:
- head.next != null 遍历到最后一个节点就停止了,不会处理最后一个节点,循环结束,head 指向最后一个节点
- head != null 遍历到最后一个节点之后的 null 节点, 会处理所有节点,包括最后一个节点,循环结束时,head 执行 null
2026年04 月
2033. 获取单值网格的最小操作数
Details
给你一个大小为 m x n 的二维整数网格 grid 和一个整数 x 。每一次操作,你可以对 grid 中的任一元素 加 x 或 减 x 。
单值网格 是全部元素都相等的网格。
返回使网格化为单值网格所需的 最小 操作数。如果不能,返回 -1 。
示例 1:

输入:grid = [[2,4],[6,8]], x = 2
输出:4
解释:可以执行下述操作使所有元素都等于 4 :
- 2 加 x 一次。
- 6 减 x 一次。
- 8 减 x 两次。
共计 4 次操作。示例 2:

输入:grid = [[1,5],[2,3]], x = 1
输出:5
解释:可以使所有元素都等于 3 。示例 3:

输入:grid = [[1,2],[3,4]], x = 2
输出:-1
解释:无法使所有元素相等。大概的意思就是,当全部元素为数组的中位数的时候,操作数最小,上面证明。
当x=2时,我们不能把2,5都变成一个数,因为偶数只能变成偶数,奇数只能变成奇数,一般的,对于整数k,我们有:
所以操作后,grid[i][j] mod x 是不变的,每个数取模x的结果必须都一样,才能变成同一个数,否则无解。
2026年02 月
1461. 检查一个字符串是否包含所有长度为 K 的二进制子串
Details
给你一个二进制字符串 s 和一个整数 k 。如果所有长度为 k 的二进制字符串都是 s 的子串,请返回 true ,否则请返回 false 。
示例 1:
输入:s = "00110110", k = 2
输出:true
解释:长度为 2 的二进制串包括 "00","01","10" 和 "11"。它们分别是 s 中下标为 0,1,3,2 开始的长度为 2 的子串。示例 2:
输入:s = "0110", k = 1
输出:true
解释:长度为 1 的二进制串包括 "0" 和 "1",显然它们都是 s 的子串。示例 3:
输入:s = "0110", k = 2
输出:false
解释:长度为 2 的二进制串 "00" 没有出现在 s 中。提示:
1 <= s.length <= 5 * 105s[i]不是'0'就是'1'1 <= k <= 20
这道题要求判断字符串 s 是否包含所有长度为 k 的二进制子串。下面逐步解析代码: 核心思路 长度为 k 的二进制串总共有 2^k 个(从 000...0 到 111...1)。代码用滑动窗口 + 位运算来高效地枚举 s 中所有长度为 k 的子串,检查是否覆盖了全部 2^k 种情况。 逐行解析
- 预处理 final int MASK = (1 << k) - 1;
- 1 << k 就是 2^k,例如 k=3 时为 8(二进制 1000)。
- 减 1 得到 0111,即低 k 位全为 1 的掩码。
- 作用:后面用来截取整数的低 k 位,丢弃高位多余的比特。 boolean[] has = new boolean[1 << k];
- 大小为 2^k 的布尔数组,has[x] 表示值为 x 的长度为 k 的二进制子串是否已经出现过。 int cnt = 0; // 已经收集到的不同子串数量 int x = 0; // 当前滑动窗口对应的整数值
- 滑动窗口遍历 for (int i = 0; i < s.length() && cnt < (1 << k); i++) {
- 遍历字符串,同时如果已经集齐所有 2^k 种子串就提前退出(剪枝优化)。
- 位运算更新窗口 x = (x << 1 & MASK) | (ch & 1); 这是最关键的一行,拆解如下:
| 步骤 | 操作 | 含义 |
|---|---|---|
| x << 1 | 左移一位 | 为新字符腾出最低位 |
| & MASK | 与掩码 | 只保留低 k 位,丢掉最高位(相当于窗口左边的字符滑出) |
| ch & 1 | 字符转数字 | '0' 的 ASCII 码末位是 0,'1' 的末位是 1 |
| | (ch & 1) | 或运算 | 把新字符放到最低位(窗口右边加入新字符) |
| 举例:k=3,MASK=0b111,当前 x=0b101(5),新字符 '0': |
- 0b101 << 1 = 0b1010
- & 0b111 = 0b010(最高位的 1 被丢弃)
- | 0 = 0b010(2)
- 窗口从 "101" 滑动到 "010",完全正确。
- 记录新子串 if (i >= k - 1 && !has[x]) { has[x] = true; cnt++; }
- i >= k - 1:确保窗口已经积累了 k 个字符(前 k-1 个位置窗口还没满)。
- !has[x]:如果这个子串没见过,标记并计数。
- 最终判断 return cnt == (1 << k);
- 收集到的不同子串数恰好等于 2^k,说明全部覆盖。 复杂度
- 时间:O(n),遍历一次字符串
- 空间:O(2^k),布尔数组大小 对比朴素做法 朴素做法是对每个子串调用 substring 再转整数,每次 O(k);而这里用位运算滑动窗口,每次只需 O(1) 就能从上一个窗口推导出下一个窗口的值,效率更高。

