分治算法刷题总结
148. 排序链表
两个思路,一种是从顶向下的排序,另外的一种是从低向上的归并排序,归并排序的复杂度是NLogN
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null)
return head;
ListNode fast = head.next, slow = head; // 这里,slow最终指向的是前半段的中间,始终如此,但是如果fast=head的话,会有问题,问题是,slow可能是重点,也可能是后半段的起点
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode tmp = slow.next;
slow.next = null;
ListNode left = sortList(head);
ListNode right = sortList(tmp);
ListNode h = new ListNode(0);
ListNode res = h;
while (left != null && right != null) {
if (left.val < right.val) {
h.next = left;
left = left.next;
} else {
h.next = right;
right = right.next;
}
h = h.next;
}
h.next = left != null ? left : right;
return res.next;
}
}
作者:Krahets
链接:https://leetcode.cn/problems/sort-list/solutions/13728/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
不傻!这段比刚才那段简单多了,它用的是——
“递归版归并排序”,思路跟“二分法”一模一样。
🧠 一句话先背住:
把链表从中间劈成两半,左边排好序,右边排好序,再合并两个有序链表。
🌰 举个例子:
链表还是:
4 -> 2 -> 1 -> 3
🧩 第一步:找中间节点(快慢指针)
ListNode fast = head.next, slow = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
👉 slow 最后会停在中间节点的前一个!
上图:
4 -> 2 -> 1 -> 3
↑ ↑
head slow
↑ ↑
fast
最后 slow 停在 2,fast 停在 null,于是:
slow.next = 1 -> 3
🧩 第二步:从中间“掰断”
ListNode tmp = slow.next;
slow.next = null;
👉 现在链表被分成两半:
- 左边:4 -> 2
- 右边:1 -> 3
🧩 第三步:递归排序两边
ListNode left = sortList(head); // 把左边排好
ListNode right = sortList(tmp); // 把右边排好
👉 就像老师把全班分成两半,分别找两个班长把各半边排好序。
🧩 第四步:合并两个有序链表(merge)
ListNode h = new ListNode(0);
ListNode res = h;
while (left != null && right != null) {
if (left.val < right.val) {
h.next = left;
left = left.next;
} else {
h.next = right;
right = right.next;
}
h = h.next;
}
h.next = left != null ? left : right;
👉 就是谁小谁先上,最后把剩下的直接接上。
🧩 第五步:返回排好序的头
return res.next;
✅ 总结一句话(给你背下来):
这段代码就是先把链表从中间劈开,左右分别排好序,再合并,跟“分糖果”一样:一人一半,先各自理好,再合起来。
🎁 文字版动画:
原始:4 -> 2 -> 1 -> 3
劈开:
左:4 -> 2
右:1 -> 3
递归左:
再劈:4 和 2
合并:2 -> 4
递归右:
再劈:1 和 3
合并:1 -> 3
最后合并:
2 -> 4
1 -> 3
→ 1 -> 2 -> 3 -> 4 ✅
🧑🏫 送你一句人话:
它就像把一堆乱牌从中间撕开,左右两半分别理好,再合起来,一直撕到只剩一张牌为止。
🎯 小口诀(背下来):
找中间,劈两半,
左右排好再合并。
如果你想,我可以给你画成流程图,或者打印每一步的链表长什么样。要不要?
🎯 一句话先回答:
奇数也不怕,慢指针会停在“前半部分的最后一个节点”,后半部分多一个节点而已。
🧠 举个例子给你看:
假设链表是:
1 -> 2 -> 3 -> 4 -> 5
(5 个节点,奇数)
🔍 快慢指针怎么走:
java复制
ListNode fast = head.next; // 快指针先走一步
ListNode slow = head; // 慢指针从头开始
表格复制
| 回合 | fast 指向 | slow 指向 | 说明 |
|---|---|---|---|
| 初始 | 2 | 1 | 快指针先走一步 |
| 第1步 | 4 | 2 | 各走一步 |
| 第2步 | null | 3 | 快指针再走两步,停住 |
✂️ 此时:
slow停在 3tmp = slow.next就是 4 -> 5slow.next = null把链表掰断:
复制
前半:1 -> 2 -> 3
后半:4 -> 5
✅ 结论:
表格复制
| 节点总数 | 前半部分 | 后半部分 |
|---|---|---|
| 偶数 | 一半 | 一半 |
| 奇数 | 少一个 | 多一个 |
🧑🏫 送你一句人话:
慢指针就像“剪刀手”, 总是站在前半部分的尾巴上 ,后半部分哪怕多一个节点,也能稳稳接住。
🎯 小口诀(背下来):
复制
奇偶无所谓,慢针剪中间,
前半少一个,后半全接住。
如果你想,我可以给你 画成动画流程图 ,或者 打印每一步指针的位置 。要不要?
class Solution {
public ListNode sortList(ListNode head) {
if (head == null) {
return head;
}
int length = 0;
ListNode node = head;
while (node != null) {
length++;
node = node.next;
}
ListNode dummyHead = new ListNode(0, head);
for (int subLength = 1; subLength < length; subLength <<= 1) {
ListNode prev = dummyHead, curr = dummyHead.next;
while (curr != null) {
ListNode head1 = curr;
for (int i = 1; i < subLength && curr.next != null; i++) {
curr = curr.next;
}
ListNode head2 = curr.next;
curr.next = null;
curr = head2;
for (int i = 1; i < subLength && curr != null && curr.next != null; i++) {
curr = curr.next;
}
ListNode next = null;
if (curr != null) {
next = curr.next;
curr.next = null;
}
ListNode merged = merge(head1, head2);
prev.next = merged;
while (prev.next != null) {
prev = prev.next;
}
curr = next;
}
}
return dummyHead.next;
}
public ListNode merge(ListNode head1, ListNode head2) {
ListNode dummyHead = new ListNode(0);
ListNode temp = dummyHead, temp1 = head1, temp2 = head2;
while (temp1 != null && temp2 != null) {
if (temp1.val <= temp2.val) {
temp.next = temp1;
temp1 = temp1.next;
} else {
temp.next = temp2;
temp2 = temp2.next;
}
temp = temp.next;
}
if (temp1 != null) {
temp.next = temp1;
} else if (temp2 != null) {
temp.next = temp2;
}
return dummyHead.next;
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/sort-list/solutions/492301/pai-xu-lian-biao-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这短短挨骂好难理解, 请帮助我理解,我是个傻瓜
你一点都不傻,这段代码确实信息密度很高,而且用了一些“技巧”让人第一眼很难看懂。
我来用最通俗的方式给你讲清楚它到底在干嘛,一句一句拆给你看,就像讲故事一样。
🧠 先记住一句话:
这段代码是在用“归并排序”的思想,给链表排序,但没用递归,而是从下到上,一步一步合并。
🌰 举个例子:
假设链表是:
4 -> 2 -> 1 -> 3
我们要把它排成:
1 -> 2 -> 3 -> 4
🧩 第一步:先数长度(就是数数)
int length = 0;
ListNode node = head;
while (node != null) {
length++;
node = node.next;
}
👉 就是从头走到尾,看看有几个节点。
比如 4->2->1->3,长度就是 4。
🧩 第二步:准备“假头”
ListNode dummyHead = new ListNode(0, head);
👉 这是链表的“万能技巧”:
弄个假的头节点,这样不管怎么变,真头都不会丢。
🧩 第三步:开始“从小到大”合并
for (int subLength = 1; subLength < length; subLength <<= 1)
👉 这句话是核心!
subLength一开始是 1,每次翻倍(<<=1就是乘以 2)- 意思是:先两两合并,再四四合并,再八八合并……直到整个链表有序
🧩 第四步:每次“切两段”出来合并
ListNode head1 = curr;
for (int i = 1; i < subLength && curr.next != null; i++) {
curr = curr.next;
}
ListNode head2 = curr.next;
curr.next = null;
👉 这就是把链表切成两段,每段长度最多是 subLength。比如第一次 subLength = 1,那就是:
- head1 是 4
- head2 是 2
然后把它俩合并成一段有序的小链表。
🧩 第五步:合并两段(merge函数)
ListNode merged = merge(head1, head2);
👉 这个 merge 函数就是你熟悉的合并两个有序链表,谁小谁先上。
🧩 第六步:把合并好的接回去
prev.next = merged;
while (prev.next != null) {
prev = prev.next;
}
👉 把合并好的那段接回主链表,然后 prev 走到最后,准备下一次合并。
🧩 第七步:继续下一轮
curr = next;
👉 刚才切完两段后,可能后面还有,就继续切继续合并。
✅ 总结一句话(给你背下来):
这段代码就是把链表先切成小段,小段排好序,再慢慢合并成大段,直到整个链表有序。
只不过它没用递归,而是从下到上,一步一步来,所以看起来有点绕。
🎁 我给你画个图(文字版):
初始:4 -> 2 -> 1 -> 3
subLength = 1:
合并 [4] 和 [2] → 2 -> 4
合并 [1] 和 [3] → 1 -> 3
结果:2 -> 4 -> 1 -> 3
subLength = 2:
合并 [2->4] 和 [1->3] → 1 -> 2 -> 3 -> 4
结果:1 -> 2 -> 3 -> 4 ✅
🧑🏫 最后送你一句人话:
它就像打牌时理牌:先一张一张理好,再两张两张理好,再四张四张……直到全部理完。
如果你愿意,我可以给你重写成超级傻瓜版代码,每一步都带注释,甚至打印出来那种。你要吗?
问题定位:找中点的逻辑错误
在归并排序中,需要将链表 从中间节点的前一个位置断开 ,但你的代码中:
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
这段逻辑会让 slow 最终指向 链表的后半段起点 (而非前半段的终点),导致:
- 分割链表时,
slow.next = null会让前半段包含中点,后半段从slow.next开始(实际是空); - 递归时会反复处理同一个子链表,触发 无限递归 ,最终抛出
StackOverflowError。
反例验证:
假设链表是 1->2:
fast初始为1,fast.next是2,进入循环;slow走到2,fast走到2.next(null),循环结束;tmp = slow.next(null),slow.next = null后,前半段是1->2,后半段是null;- 递归
sortList(head)时又会重复上述过程,无限递归。
427. 建立四叉树
题目比较难看明白,实际上连接了该能很快的写出来,说白了,就是一个递归的处理,先处理最左上交的元素,然后划分格子去处理,得到四个格子的数据

浙公网安备 33010602011771号