2024/6/24 周末两天没去实验室,可能跟天气有关,也可能跟我不想去有关。最近实在太热,不想出门。早上来,去二楼看了一下我的栀子花,长得很好,但是花苞都没了,只剩下唯一一颗,给它浇了水,养花还是需要技术的,顺其自然吧。现在开始做题!
1、题目描述
2、逻辑分析
链表排序问题。简单粗暴的方法就是转换成列表,排序后再转换回去。以下是代码:
public ListNode sortList(ListNode head) {
List<Integer> lists = new ArrayList<Integer>();
ListNode cur = head;
while(cur != null){
lists.add(cur.val);
cur = cur.next;
}
lists.sort(null);
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
for(int list:lists){
tail.next = new ListNode(list);
tail = tail.next;
}
return dummy.next;
}
但是这种解题方法不符合要求。面向面试官编程是不允许这么做的。怎么做呢?官方给出两种:自顶向下归并排序和自底向上归并排序。我们这里就只来看看第一种自顶向下归并排序。
大致思路:
- 找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2 步,慢指针每次移动1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。
- 对两个子链表分别排序。
- 将两个排序后的子链表合并,得到完整的排序后的链表。
我先去敲一下。ok,下面是代码
3、代码演示
public ListNode sortList(ListNode head) {
// 调用重载的sortList方法,初始时tail为null,表示整个链表
return sortList(head, null);
}
// 重载的sortList方法,用于递归地执行归并排序
public ListNode sortList(ListNode head, ListNode tail){
// 如果链表为空,则直接返回
if(head == null){
return head;
}
// 如果链表中只有一个节点(head.next即为tail),则将该节点后面的next置为null,表示链表结束
// 并返回这个唯一的节点
if(head.next == tail){
head.next = null;
return head;
}
// 使用快慢指针找到链表的中间节点
ListNode slow = head, fast = head;
// fast指针每次走两步,slow指针每次走一步
while(fast != tail){
slow = slow.next;
fast = fast.next;
if(fast != tail){
fast = fast.next;
}
}
// mid是链表的中间节点
ListNode mid = slow;
// 递归地对左半部分链表进行排序
ListNode list1 = sortList(head, mid);
// 递归地对右半部分链表进行排序
ListNode list2 = sortList(mid, tail);
// 合并两个已排序的链表
ListNode sorted = merge(list1, list2);
// 返回合并后的链表
return sorted;
}
// 合并两个已排序的链表
public ListNode merge(ListNode head1, ListNode head2){
// dummyHead是一个哑节点,用于简化链表的构建过程
ListNode dummyHead = new ListNode(0);
// temp用于遍历dummyHead链表,temp1和temp2分别指向两个待合并链表的头节点
ListNode temp = dummyHead, temp1 = head1, temp2 = head2;
// 当两个链表都不为空时,比较当前节点的值,选择较小的节点接到temp后面
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 = temp.next;
}
// 如果链表1还有剩余节点,则将它们连接到结果链表的末尾
if(temp1 != null){
temp.next = temp1;
// 如果链表2还有剩余节点,则将它们连接到结果链表的末尾
}else if(temp2 != null){
temp.next = temp2;
}
// 返回合并后的链表的头节点(跳过哑节点)
return dummyHead.next;
}
思路不难,我感觉边界条件比较难啊,anyway,总算做完啦!
4、复杂度分析
- 时间复杂度:
O(nlogn)
,其中 n 是链表的长度。 - 空间复杂度:
O(logn)
,其中 n是链表的长度。空间复杂度主要取决于递归调用的栈空间。 - okok,拜拜啦!