2487 从链表中移除节点
题目:给你一个链表的头节点 head
。移除每个右侧有一个更大数值的节点。返回修改后链表的头节点 head
。
思路:自己写的超时版本,主要思路就是不断寻找链表中的最大值,然后将最大值这个节点返回,然后再从该节点的后面的节点开始寻找最大值,直至到达最后一个节点,但是当整个链表逆序的时候,假设链表一共有N个节点,那么相当于一共要循环N次,每一次也要遍历整个链表长度,所以时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
/**
* Definition for singly-linked list.
* 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) {}
* };
*/
class Solution {
public:
//这个题目说白了就是不断找最大值,找到一个最大值之后,删除左边;然后再找右边区域的最大值
ListNode* FindMaxNode(ListNode* head){
int maxVal = -1;
ListNode *maxNode = new ListNode();
while(head != nullptr){
if(head -> val > maxVal){
maxVal = head -> val;
maxNode = head;
}
head = head -> next;
}
return maxNode;
}
ListNode* removeNodes(ListNode* head) {
ListNode *ans = head;
ListNode *h = new ListNode();
ListNode *p = h;
ListNode *temp;
do{
temp = FindMaxNode(head);
p->next = temp;
p = p->next;
head = temp->next;
}while(temp != nullptr && head != nullptr);
if(p != nullptr){
p -> next = nullptr;
}
return h->next;
}
};
官方提供了以下三种解题思路,每一种都深表震撼。
-
递归:我们发现节点对它右侧的所有节点都没有影响,因此对于某一个节点,我们可以对他的右侧的节点进行递归的移除。
递归出口:到达链表末尾,返回空指针
递归操作:将右侧节点作为函数参数传入,判断该节点是否比它右边的节点的值小,如果小,则证明该节点要进行移除,所以返回该节点的next
节点,否则返回该节点即可。class Solution { public: ListNode* removeNodes(ListNode* head) { if(head == nullptr){ return nullptr; } head -> next = removeNodes(head->next); if(head->next != nullptr && head->val < head->next->val){ return head->next; } else{ return head; } } };
-
单调栈:出现这种“右侧”、“更大”就要想到单调栈,也就是用栈来模拟递归,使人更容易理解。我们先从左往右将链表中的所有节点压入栈中。然后我们不断的从栈中弹出节点,我们判断该节点的值与新链表节点的值大小关系,如果小于就证明该节点应该舍弃,狗则就是将该节点进行保留,也就是说该节点应该插入链表的头部,也就是使用头插法来创建链表。
class Solution { public: ListNode* removeNodes(ListNode* head) { stack<ListNode *> s; //节点压栈 for(; head != nullptr; head = head -> next){ s.push(head); } //创建新链表 for(; !s.empty(); s.pop()){ //最开始的时候,head节点是nullptr //判断栈顶的节点应该是舍弃还是保留,进入if则应该进行保留 if(head == nullptr || s.top() -> val >= head->val){ s.top() -> next = head; head = s.top(); } } return head; } };
-
反转链表:不管是方法1还是方法2都是从右往左进行处理的,我们知道对于单链表来说,往往从左往右进行处理更为方便,那么我们完全可以先将整个链表进行反转,反转之后问题就转化为我们要移除链表中左侧有更大数值的节点,也就是说,我们直接从左往右遍历链表,如果节点的值比当前节点的值小,则舍弃该节点,直到遇到比当前节点值更大的节点,遍历完成之后我们再反转链表进行输出即可。而对于反转链表,也就是一个通过不断头插法创建链表的一个过程。
class Solution { public: ListNode* reverse(ListNode* head){ //创建虚拟节点 ListNode dummy; while(head != nullptr){ //创建一个新节点 ListNode* p = head; head = head -> next; p->next = dummy.next; dummy.next = p; } return dummy.next; } ListNode* removeNodes(ListNode* head) { head = reverse(head); ListNode *p = new ListNode(); for(p = head; p->next != nullptr; ){ //舍弃p->next if(p->val > p->next->val){ p->next = p->next->next; } else{ p = p->next; } } return reverse(head); } };