一、力扣
1、二叉树展开为链表

将给定的二叉树展开为一个单链表,链表中的节点顺序与二叉树的前序遍历顺序相同。 展开后的链表中,每个节点的右指针指向链表中的下一个节点,左指针始终为null。
实现思路:
- 如果当前节点为空,返回。
- 递归右子树。
- 递归左子树。
- 把 root.left 置为空。
- 头插法,把 root 插在 head 的前面,也就是 root.right=head。
- 现在 root 是链表的头节点,把 head 更新为 root。
时间复杂度:O(n),每个节点被访问一次。
空间复杂度:O(h),递归栈的深度取决于树的高度,最坏情况为O(n)(当树退化为链表时)。
java
class Solution {
// 维护当前链表的头节点,用于连接后续处理的子树
TreeNode head;
public void flatten(TreeNode root) {
if (root == null) return;
// 后序遍历处理右子树
flatten(root.right);
// 后序遍历处理左子树
flatten(root.left);
// 将当前节点的右指针指向已展开的左子树链表(初始为null)
root.right = head;
// 左指针置空,符合链表结构要求
root.left = null;
// 更新head为当前节点,作为新的链表头(后续父节点将连接到此)
head = root;
}
}
2、员工的直属部门

sql
select employee_id, department_id
from Employee
where primary_flag = 'Y' or employee_id in (
select employee_id
from Employee
group by employee_id
having count(employee_id) = 1
)
3、排序链表

java
class Solution {
public ListNode sortList(ListNode head) {
if(head==null||head.next==null) return head;
ListNode head2=middle(head);
head=sortList(head);
head2=sortList(head2);
return merge(head,head2);
}
public ListNode middle(ListNode head){
ListNode pre=head;
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
pre=slow;
slow=slow.next;
}
pre.next=null;
return slow;
}
public ListNode merge(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(); // 用哨兵节点简化代码逻辑
ListNode cur = dummy; // cur 指向新链表的末尾
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
cur.next = list1; // 把 list1 加到新链表中
list1 = list1.next;
} else { // 注:相等的情况加哪个节点都是可以的
cur.next = list2; // 把 list2 加到新链表中
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1 != null ? list1 : list2; // 拼接剩余链表
return dummy.next;
}
}
4、下一个排列

java
class Solution {
public void nextPermutation(int[] nums) {
int n = nums.length;
// 第一步:从右向左找到第一个小于右侧相邻数字的数 nums[i]
int i = n - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
// 如果找到了,进入第二步;否则跳过第二步,反转整个数组
if (i >= 0) {
// 第二步:从右向左找到 nums[i] 右边最小的大于 nums[i] 的数 nums[j]
int j = n - 1;
while (nums[j] <= nums[i]) {
j--;
}
// 交换 nums[i] 和 nums[j]
swap(nums, i, j);
}
// 第三步:反转 nums[i+1:](如果上面跳过第二步,此时 i = -1)
reverse(nums, i + 1, n - 1);
}
private void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
private void reverse(int[] nums, int left, int right) {
while (left < right) {
swap(nums, left++, right--);
}
}
}
5、 x 的平方根

java
class Solution {
public int mySqrt(int x) {
// 初始化二分查找的左右边界:最小可能值0,最大可能值x
int l = 0, r = x;
// 存储最终结果,初始值为-1(当x=0时能正确返回)
int ans = -1;
// 二分查找核心逻辑
while (l <= r) {
// 计算中间值,使用(l + r)/2的变形避免整数溢出
int mid = l + (r - l) / 2;
// 关键点:将mid转换为long类型进行平方计算,防止int溢出
// 如果mid² <= x,说明可能是候选解,尝试寻找更大的值
if ((long) mid * mid <= x) {
ans = mid; // 更新当前最优解
l = mid + 1; // 左边界右移,搜索更大值
} else {
r = mid - 1; // mid²过大,右边界左移
}
}
return ans;
}
}
6、比较版本号

java
class Solution {
public int compareVersion(String version1, String version2) {
String[] v1 = version1.split("\\.");
String[] v2 = version2.split("\\.");
for (int i = 0; i < v1.length || i < v2.length; ++i) {
int x = 0, y = 0;
if (i < v1.length) {
x = Integer.parseInt(v1[i]);
}
if (i < v2.length) {
y = Integer.parseInt(v2[i]);
}
if (x > y) {
return 1;
}
if (x < y) {
return -1;
}
}
return 0;
}
}
split
方法通过正则表达式分割字符串,其行为由分隔符正则 和limit参数共同决定:
- 默认用法 (无limit):尽可能分割,但丢弃末尾空字符串 (如
"a.b.c.".split(".") → ["a","b","c"]
)。 -
limit > 0
:最多分割limit-1
次,后续部分保留,末尾空字符串保留 (如"a.b.c.".split(".", 2) → ["a", "b.c."]
)。 -
limit == 0
:尽可能分割,但末尾空字符串全部丢弃(等效于默认行为)。 -
limit < 0
(如-1
):完全分割,保留所有空字符串 (如"a.b.c.".split(".", -1) → ["a","b","c",""]
)。
6.1 正则表达式总结
Java 字符串分解常用简单正则总结
在 Java 中,String.split()
结合正则表达式可快速分解字符串,以下是针对常见场景的简洁正则及用法:
1. 基础分隔符分割
场景 | 正则表达式 | Java 写法 | 示例 | 说明 |
---|---|---|---|---|
空格分割 | \\s+ |
str.split("\\s+") |
"a b c" → ["a", "b", "c"] |
匹配连续空格、制表符、换行等 |
逗号分割 | , |
str.split(",") |
"a,b,,c" → ["a","b","","c"] |
默认保留末尾空字符串 |
点号分割 | \\. |
str.split("\\.") |
"192.168.1.1" → ["192","168"...] |
点号需转义 |
3. 多分隔符混合分割
场景 | 正则表达式 | Java 写法 | 示例 | 说明 |
---|---|---|---|---|
逗号、分号、空格分割 | [;,\\s]+ |
str.split("[;,\\s]+") |
"a;b, c" → ["a","b","c"] |
匹配任意组合的分隔符 |
斜杠分割 | \\/ |
str.split("\\/") |
"2023/10/05" → ["2023","10","05"] |
斜杠需转义 |
4. 保留末尾空字符串
场景 | 正则表达式 | Java 写法 | 示例 | 说明 |
---|---|---|---|---|
强制保留所有空值 | 使用 split(regex, -1) |
str.split(",", -1) |
"a,,b,".split(",", -1) → ["a","","b",""] |
-1 保留末尾空字符串 |
关键注意事项
- 转义字符 :正则中的特殊字符(如
.
、|
、/
)需用\\
转义(Java 字符串中写成\\\\
)。 - limit 参数 :
•split(regex)
→ 默认limit=0
(丢弃末尾空字符串)。
•split(regex, -1)
→ 保留所有空字符串。
示例代码:
java
String str = "a,,b;c d";
// 分割逗号或分号,忽略空格,保留空值
String[] parts = str.split("[;,\\s]+"); // ["a", "b", "c", "d"]
// 强制保留所有空值(包括末尾)
String[] parts2 = str.split("[;,\\s]+", -1); // ["a", "", "b", "c", "d", ""]
7、合并二叉树

java
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1==null) return root2;
if(root2==null) return root1;
TreeNode res=new TreeNode(root1.val+root2.val);
res.left=mergeTrees(root1.left,root2.left);
res.right=mergeTrees(root1.right,root2.right);
return res;
}
}
8、二叉搜索树中的众数

java
class Solution {
// 存储结果的列表
List<Integer> answer = new ArrayList<Integer>();
// 当前正在统计的节点值
int base;
// 当前值出现的次数
int count;
// 最大出现次数
int maxCount;
public int[] findMode(TreeNode root) {
// 通过中序遍历处理所有节点
dfs(root);
// 将列表转换为数组返回
int[] mode = new int[answer.size()];
for (int i = 0; i < answer.size(); ++i) {
mode[i] = answer.get(i);
}
return mode;
}
public void dfs(TreeNode o) {
if (o == null) {
return;
}
// 递归遍历左子树
dfs(o.left);
// 处理当前节点值
update(o.val);
// 递归遍历右子树
dfs(o.right);
}
public void update(int x) {
// 如果当前值与统计值相同,增加计数
if (x == base) {
++count;
} else {
// 否则重置统计:当前值作为新基准,计数归1
count = 1;
base = x;
}
// 当当前计数等于最大计数时,加入结果列表(可能有多个众数)
if (count == maxCount) {
answer.add(base);
}
// 当当前计数超过最大计数时,更新最大计数并重置结果列表
else if (count > maxCount) {
maxCount = count;
answer.clear();
answer.add(base);
}
}
}
9、x升杯子,y升杯子,量出z升水,找出z的规律

- 判断是否可以使用容量为x和y的两个水壶量出恰好z升的水。
- 根据贝祖定理,当且仅当z是x和y的最大公约数的倍数,且z不超过x+y时,才可能实现。
java
class Solution {
public boolean canMeasureWater(int x, int y, int z) {
// 总容量不足时直接返回false
if (x + y < z) {
return false;
}
// 处理其中一个水壶容量为0的特殊情况:
// 1. 若z为0,则无需测量直接返回true
// 2. 若另一个水壶容量正好等于z,则可以量出
if (x == 0 || y == 0) {
return z == 0 || x + y == z;
}
// 当z是x和y的最大公约数的倍数时,可以通过倒水操作量出z
return z % gcd(x, y) == 0;
}
public int gcd(int x, int y) {
if(x==0) return y;
return gcd(y%x,x);
}
}
二、语雀-数据结构
1、数组和链表有何区别?


2、栈和队列的区别

3、什么是树?了解哪些树结构?


4、什么是前缀树,有什么作用?

5、什么是堆?什么情况下要用堆?

6、什么是B+树,和B树有什么区别?
1. B树


2. B+树

7、什么是红黑树?


8、什么是BitMap?有什么用?


9、什么是小顶堆,可以用在哪些场景?




10、海量数据查找最大的 k 个值,用什么数据结构?

三、语雀-智商题
1、村庄有个约定,生男孩就结束,生女孩就继续生,直到生出男孩为止,若干年后,这个村子男女比例是多少?
✅村庄有个约定,生男孩就结束,生女孩就继续生,直到生出男孩为止,若干年后,这个村子男女比例是多少?

2、假设你有一个乒乓球盒子,里面有 3 个白球和 2 个黑球。从盒子中抽取一个球,放回后再抽取一个球。两次抽取得到的球颜色不同的概率是多少?
✅假设你有一个乒乓球盒子,里面有 3 个白球和 2 个黑球。从盒子中抽取一个球,放回后再抽取一个球。两次抽取得到的球颜色不同的概率是多少?

3、有8个球,其中7个重量相同,另一个球比其他球重,现在只有一个天平,请问最少需要称几次一定能找到那个比其他球重的球?
✅有8个球,其中7个重量相同,另一个球比其他球重,现在只有一个天平,请问最少需要称几次一定能找到那个比其他球重的球?
一定是分成三份,称两份。

4、有两个水桶,容量分别为5升和3升,请问如何使用这两个桶得到4升的水?
✅有两个水桶,容量分别为5升和3升,请问如何使用这两个桶得到4升的水?

5、有一堆桃子,猴子第一天吃了一半加一个,第二天又吃了一半加一个,... ,到第10天时剩下一个桃子,问这原来有多少个?
✅有一堆桃子,猴子第一天吃了一半加一个,第二天又吃了一半加一个,... ,到第10天时剩下一个桃子,问这原来有多少个?

6、1000瓶药水,1瓶有毒药,最少需要几只小白鼠一定能够找出?
✅1000瓶药水,1瓶有毒药,最少需要几只小白鼠一定能够找出?

7、一个天平,7g和2g砝码各一个,将140g盐分成90g和50g,需要称多少次?
✅一个天平,7g和2g砝码各一个,将140g盐分成90g和50g,需要称多少次?
