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,拜拜啦!