148. 排序链表
两个思路,一种是从顶向下的排序,另外的一种是从低向上的归并排序,归并排序的复杂度是NLogN
class Solution {public ListNode sortList(ListNode head) {if (head == null || head.next == null)return head;ListNode fast = head.next, slow = head;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 -> 41 -> 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 -> 3subLength = 1:合并 [4] 和 [2] → 2 -> 4合并 [1] 和 [3] → 1 -> 3
结果:2 -> 4 -> 1 -> 3subLength = 2:合并 [2->4] 和 [1->3] → 1 -> 2 -> 3 -> 4
结果:1 -> 2 -> 3 -> 4 ✅
🧑🏫 最后送你一句人话:
它就像打牌时理牌:先一张一张理好,再两张两张理好,再四张四张……直到全部理完。
如果你愿意,我可以给你重写成超级傻瓜版代码,每一步都带注释,甚至打印出来那种。你要吗?
427. 建立四叉树
题目比较难看明白,实际上连接了该能很快的写出来,说白了,就是一个递归的处理,先处理最左上交的元素,然后划分格子去处理,得到四个格子的数据