力扣2487 从链表中移除节点

文章介绍了在给定链表中移除右侧数值更大的节点的三种解决方案:递归、单调栈和反转链表。递归方法利用递归特性,单调栈模拟递归操作简化理解,反转链表则先反转再遍历以简化问题。
摘要由CSDN通过智能技术生成

2487 从链表中移除节点

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;
    }
};

官方提供了以下三种解题思路,每一种都深表震撼。

  1. 递归:我们发现节点对它右侧的所有节点都没有影响,因此对于某一个节点,我们可以对他的右侧的节点进行递归的移除。
    递归出口:到达链表末尾,返回空指针
    递归操作:将右侧节点作为函数参数传入,判断该节点是否比它右边的节点的值小,如果小,则证明该节点要进行移除,所以返回该节点的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;
            }
        }
    };
    
  2. 单调栈:出现这种“右侧”、“更大”就要想到单调栈,也就是用栈来模拟递归,使人更容易理解。我们先从左往右将链表中的所有节点压入栈中。然后我们不断的从栈中弹出节点,我们判断该节点的值与新链表节点的值大小关系,如果小于就证明该节点应该舍弃,狗则就是将该节点进行保留,也就是说该节点应该插入链表的头部,也就是使用头插法来创建链表。

    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;
        }
    };
    
  3. 反转链表:不管是方法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);
        }
    };
    
  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值