LeetCode 热题 100_排序链表(33_148_中等_C++)(单链表;挨个匹配插入;归并排序(非递归))

发布于:2025-02-11 ⋅ 阅读:(62) ⋅ 点赞:(0)

题目描述:

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

输入输出样例:

示例 1:
请添加图片描述

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:
请添加图片描述

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:
输入:head = []
输出:[]

提示:
链表中节点的数目在范围 [0, 5 * 104] 内
-105 <= Node.val <= 105

题解:

解题思路:

思路一(挨个匹配插入):

1、每次取未排序单链表中一个结点,与排序好的链表中的结点从头开始挨个比对,查找对应插入位置再插入。

2、具体思路如下:
① 如果为空或只有一个元素返回单链表。
② 创建一个新的头节点用于统一排序元素的插入。
③ 先将第一个元素插入排序链表。
④ 其余元素在插入时,从排序链表头部开始查找插入位置,若遍历到排序链表的末尾,则在末尾插入。

3、复杂度分析:
① 时间复杂度:O(n),其中 n 是链表的长度,最坏的情况是原列表有序,在每次插入都需与每个排序好的元素进行比较。
② 空间复杂度:O(1)。

思路二(归并排序(非递归)):

1、我们必须将链表划分为1大小的分块开始进行两两归并排序。下一次划分为2大小的分块进行两两归并,再依次从4,8,16~length,最终合并成一个单链。

2、具体思路如下:
subLength=[1,2,4,8,16~length]
① 1、链表从前往后先拆下来subLength(分块)大小的链表head1,再拆下head1后subLength大小的的链表head2。
② 将head1和head2进行归并排序形成merged链表。
③ 将 merged链接到之前归并完成的尾结点(一开始是dummyHead)。
④ 再从head2后拆下来subLength(分块)大小的链表head3,再拆下head3后subLength大小的的链表head4。
⑤ 将head3和head4进行归并排序形成merged链表。
⑥ 将 merged链接到之前归并完成的尾结点。



⑦ 完成subLength大小的归并排序后,再进行subLength*2小的归并排序,直到分块大小>=length。

:head = [4,2,1,3]
subLength=1, [4,2,1,3] => [[2,4],[1,3]]
subLength=2, [[2,4],[1,3]]=>[1,2,3,4]

3、复杂度分析
① 时间复杂度:O(nlogn),其中 n 是链表的长度。
② 空间复杂度:O(1)。

代码实现

代码实现(思路一(挨个匹配插入)):
ListNode* sortList1(ListNode* head) {
	if(head==nullptr||head->next==nullptr){
		return head;
	}
	//创建一个新的头节点(统一操作)
	ListNode *dummyHead=new ListNode(-1,head);
	//r用于保存还未排序链表的首节点
	ListNode *r=head->next;
	//将dummyHead和第一个结点与后续链表断开
	head->next=nullptr;
	//tmp代表将要移动的结点
	ListNode *tmp=r;
	//pre代表插入节点在排序链表中的前驱结点
	ListNode *pre=dummyHead;
	//当未排序链表存在结点时,挨个插入排序链表
	while(r!=nullptr){
		tmp=r;
		r=r->next;
		//查找插入位置 
		while(pre->next!=nullptr&&tmp->val > pre->next->val){
			pre=pre->next;
		}
		//插入 
		tmp->next=pre->next;
		pre->next=tmp;
		
		//下一个插入的元素比当前插入的元素大,则从当前元素位置开始查找插入位置
		//否则从头开始查找
		if(r!=nullptr&&r->val>tmp->val){
			pre=tmp;
		}else{
			pre=dummyHead;
		}
	}
	head=dummyHead->next;
	//注意结点的释放
	delete dummyHead;
	return head;
}
代码实现(思路二(归并排序(非递归))):
//两个单链表的合并排序(归并排序中两个块之间的合并)
ListNode* merge(ListNode* head1, ListNode* head2) {
	//创建一个新的头节点(统一操作)
    ListNode* dummyHead = new ListNode(0);
    //代表合并过程中合并链表的尾结点
    ListNode* tail = dummyHead;
    //合并到其中一个链表为空
    while (head1 != nullptr && head2 != nullptr) {
        if (head1->val <= head2->val) {
            tail->next = head1;
            head1 = head1->next;
        } else {
            tail->next = head2;
            head2 = head2->next;
        }
        tail = tail->next;
    }
    //链接剩余链表 
    tail->next=head1?head1:head2;
    //head1存储合并好的首结点 
    head1=dummyHead->next;
    delete dummyHead;
    return head1;
}

ListNode* sortList2(ListNode* head) {
    if (head == nullptr) {
        return head;
    }
    //计算链表的长度 
    int length = 0;
    ListNode* node = head;
    while (node != nullptr) {
        length++;
        node = node->next;
    }
    
    //创建一个新的头节点(统一操作) 
    ListNode* dummyHead = new ListNode(0, head);
    //以 subLength划分的单链表进行归并排序,subLength=[1,2,4,8~length] 
    for (int subLength = 1; subLength < length; subLength <<= 1) {
    	//每一趟将prev和curr重置从头开始,prev代表两个块合并完的尾结点用于连接
		//curr 的作用是记录块的头,查找块的尾部,并断开块用于合并 
        ListNode* prev = dummyHead, *curr = dummyHead->next;
        //遍历一次链表,处理subLength长度划分的各个单链表 
        //每次从前往后进行两块之间的归并排序,一直到整个链表 
        while (curr != nullptr) {
        	//head1 和 head2 来表示要合并的两个子链表
            ListNode* head1 = curr;
            //通过一个 for 循环来找到第一个子链表的尾部 curr
            for (int i = 1; i < subLength && curr->next != nullptr; i++) {
                curr = curr->next;
            }
            //head1 和 head2 来表示要合并的两个子链表
            ListNode* head2 = curr->next;
            //将head1和head2断开 
            curr->next = nullptr;
            
            //我们更新 curr,让它指向第二个子链表的头 
            curr = head2;
            //当结点足够时,找到了第二个子链表的尾部,并截断链表,此时curr代表head2的尾部 
            for (int i = 1; i < subLength && curr != nullptr && curr->next != nullptr; i++) {
                curr = curr->next;
            }
            //如果第二个子链表后有剩余节点,next代表其后的第一个节点,将head2断开 
            ListNode* next = nullptr;
            if (curr != nullptr) {
                next = curr->next;
                curr->next = nullptr;
            }
            //合并 head1 和 head2。合并后的结果会接到 prev->next 上
            ListNode* merged = merge(head1, head2);
            prev->next = merged;
            //使prev指向新合并的链表的最后一个节点 
            while (prev->next != nullptr) {
                prev = prev->next;
            }
            curr = next;
        }
    }
    head= dummyHead->next;
    delete dummyHead;
    return head;
}
以思路一为例进行调试
#include<iostream>
#include<vector>
using namespace std;
struct ListNode{
	int val;
	ListNode *next;
	ListNode():val(0),next(nullptr){}
	ListNode(int x):val(x),next(nullptr){} 
	ListNode(int x,ListNode *next):val(x),next(next){}
}; 

//尾插法创建单链表 
ListNode *createList(vector<int> arr){
	ListNode *head=nullptr,*tail=nullptr;
	for(const auto &val:arr){
		if(head==nullptr){
			head=tail=new ListNode(val);
		}else{
			tail->next=new ListNode(val);
			tail=tail->next;	
		}
	}
	return head;
}

//方法一挨个插入
ListNode* sortList1(ListNode* head) {
	if(head==nullptr||head->next==nullptr){
		return head;
	}
	ListNode *dummyHead=new ListNode(-1,head);
	ListNode *r=head->next;
	head->next=nullptr;
	ListNode *tmp=r;
	ListNode *pre=dummyHead;
	while(r!=nullptr){
		tmp=r;
		r=r->next;
		//查找插入位置 
		while(pre->next!=nullptr&&tmp->val>pre->next->val){
			pre=pre->next;
		}
		//插入 
		tmp->next=pre->next;
		pre->next=tmp;
		
		//在下一个插入的元素比当前的元素大,则从当前元素位置开始查找插入位置 
		if(r!=nullptr&&r->val>tmp->val){
			pre=tmp;
		}else{
			pre=dummyHead;
		}
	}
	head=dummyHead->next;
	delete dummyHead;
	return head;
}

int main(){
	vector <int> a={4,2,1,3};
	//创建单链表
	ListNode *head=createList(a);
	//对链表进行排序(方法一)
	ListNode *ans=sortList1(head);
	//输出排序后的结果
	while(ans!=nullptr){
		cout<<ans->val<<"->";
		ans=ans->next;
	}
	cout<<"null";
	return 0;
}

更改链接

LeetCode 热题 100_合并两个有序链表(27_21_简单_C++)请点击此链接
LeetCode 热题 100_排序链表(33_148)原题链接
欢迎大家和我沟通交流(✿◠‿◠)


网站公告

今日签到

点亮在社区的每一天
去签到