题目:
题目的动图:
今天这道题,我个人感觉难点不是在于算法,而是在于链表的操作上面。
所以对链表操作不太熟的朋友,建议反复回看这道题,会大大加深对链表的理解。
我一开始和题解一样,都是采用多指针去考虑这个问题。但是在指针操作上面踩了很多坑,最后看了题解才算是完全理解了。个人觉得题解的写法是多指针方法最节省空间和时间的写法,比我自己写的好多了。所以这里贴出的代码和思路都参考题解来。
也更多的讲讲是怎么得出题解思路的个人理解。
解题思路:
1.对于插入排序,我们思考的一般是以下两个关键问题:
(1)当前待排序元素
(2)插入位置
2.对于链表来说,插入操作的时间为O(1),但是因为要去遍历链表寻找位置,所以总共的时间复杂度依然是O(n^2)
3.对于链表,我们针对关键问题(1),当前待排序元素,维护一个单独的指针【curr】,对于关键问题(2),也维护一个单独的指针【prev】,去寻找插入位置的前一个结点。
4.那么我们现在拥有了【curr】指针和【prev】指针,接下来的问题还有:
(1)链表操作的统一化(头节点怎么统一操作)
(2)怎么维护已经排序完成的序列
5.那么题解对于链表操作的统一化,使用的是一个【dummyHead】的新节点(哑节点),放在头节点之前,这样,就可以保证每一次的找寻操作的遍历都可以从这个哑节点开始,少一次使用一次if判断。当然啦,【dummyHead】这个节点最主要的作用还是在于方便将待排序节点插入到头节点之前
6.至于怎么维护已经排序完成的序列,我们使用一个【lastSorted】指针,指向当前已完成排序的序列的最后一个节点。。
7.接下来就是如何找到位置。由于节点值都是升序排列的,所以只需要找到【prev->next->val <= curr->val】这个判断条件即可。那么万一curr大于所有的已排序元素呢?所以我们在循环之前还需要多加一次判断【lastSorted->val <= curr->val】,如果curr大于已排序完成的队尾元素,那么就直接让【LastSorted】指针指向curr
8.那么整体的思路就有了,接下来就是如何组织代码结构使得开销更小了,这方面我觉得题解做的很好。
贴上代码(C++附带测试)
#include<iostream>
using namespace std;
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(NULL) {}
};
void createList(ListNode* pHead){
ListNode* p = pHead;
int n = 4;
int a[n]={5,3,4,0};
for (int i = 0; i < n; ++i) {
ListNode* pNewNode = new ListNode;
pNewNode->val = a[i]; // 将新节点的值赋值为i
pNewNode->next = NULL;
p->next = pNewNode; // 上一个节点指向这个新建立的节点
p = pNewNode; // p节点指向这个新的节点
}
}
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
if (head == nullptr) {
return head;
}//如果是空链表直接返回
ListNode* dummyHead = new ListNode(0);//创建哑节点dummyHead,方便在头节点之前插入节点,使得操作统一化
dummyHead->next = head;//放置在头节点之前
ListNode* lastSorted = head;//lastSorted已排序部分的最后一个节点,初始值和head保持一致
ListNode* curr = head->next;//curr为当前需要排序的元素
while (curr != nullptr) {//只要curr不为空,即当前还有元素需要插入排序
if (lastSorted->val <= curr->val) {//如果最后一个的值小于当前值,则直接将curr插入已经排序列表
lastSorted = lastSorted->next;//让lastSorted指向curr
} else {
ListNode *prev = dummyHead;//新建一个prev指针找寻curr节点需要插入位置的前一个节点
while (prev->next->val <= curr->val) {//查找插入位置,插入位置为第一个大于curr节点值得前面
prev = prev->next;//没有找到则继续移动指针
}
lastSorted->next = curr->next;//令已排序部分的最后一个节点变成当前排序元素的下一个节点
curr->next = prev->next;//当前排序元素的下一个节点为prev指针的下一个节点(链表插入操作)
prev->next = curr;//令prev指针的下一个节点为curr
}
curr = lastSorted->next;//待排序元素等于已排序元素部分的最后一个节点的下一个
}
return dummyHead->next;//返回头指针
}
};
int main(){
ListNode* head = NULL;//创建头节点
head = new ListNode;
head->val = -1;
head->next = NULL;
createList(head);
Solution s;
ListNode* ans = s.insertionSortList(head);
while(ans!=nullptr)
{
cout<<ans->val<<endl;
ans = ans->next;
}
return 0;
}
最后:这道题真的是对操作链表能力的一次很好的考察。真的建议反复回看!