LintCode 98-链表排序

本人电子系,只为一学生。心喜计算机,小编以怡情。


在 O(n log n) 时间复杂度和常数级的空间复杂度下给链表排序。

样例
给出 1->3->2->null,给它排序变成 1->2->3->null.


感悟:要求 O(n log n) 时间复杂度,然后我上网查了一下别的博主的博客的前言,发现只能用并归排序来做,我又查了一下并归排序,然后写出了之后的又臭又长的代码。不过用链表来做排序,那真是超级酸爽。。。


思路:我是以1,2,4,8…….为并归的单位

public ListNode sortList(ListNode head) {  
        // write your code here
        if(head==null||head.next==null) return head;

        int len=getlength(head);//得到长度

        ListNode realhead=new ListNode(0);//人为的加一个头结点
        realhead.next=head;

        ListNode flag=realhead;//为了不每次都从头开始查找
        //我们定义一个flag,比如132null,当并归2与null时,flag指向3

        for(int i=1;i<len;i*=2)//外层控制总次数1,2,4,8,....
        {
            flag=realhead;
            ListNode pre=flag.next;
            ListNode last=flag;
            //内层是分别的2,4,8,16
            while(last!=null)
            {
            int count=i;
            pre=flag.next;
            last=pre;
            while(last!=null&&count--!=0)
            {
                last=last.next;
            }//last指向要并归的片段首部,比如12345,长度为2时的片段
            //pre指向1,last指向3

            //因为等会要切成片,会丢失关系,因此给5做个引用,使链表能够恢复
            ListNode a=flag;
            int linshi=i*2+1;
            while(a!=null&&linshi--!=0)
                a=a.next;
            //
            //切片
            slash(pre, i);
            slash(last, i);
            //after slash we need reconnect;
            ListNode temp=merge(pre, last);//整合切片
            flag.next=temp;让flag指向头部,这样联系就建立起来了
            //flag de dingwei
            count=2*i;
            while(flag!=null&&count--!=0)
            {
                flag=flag.next;
            }//移动flag,这样不用每次都从头部找东西了,此例flag指向4
            //再将联系按上
            if (flag==null) {//flag可能出现null情况
                break;
            }
            flag.next=a;//让4指向5,切片断掉的联系就连上了
            }
        }
        return realhead.next;
    }

     //将两个以null结尾的链表整合到一个并返回头部
    static ListNode merge(ListNode head,ListNode last)
    {
        if(last==null) return head;
        if(head==null) return last;
        ListNode ret=head;//fanhui xiaozhi

        ListNode temphead=new ListNode(0);
        temphead.next=head;
        ListNode pre=temphead;

        while(last!=null)
        {
            ListNode temp2=last.next;
            while(pre.next!=null&&pre.next.val<last.val)
            {
                pre=pre.next;
            }

            ListNode t=pre.next;
            pre.next=last;
            last.next=t;
            last=temp2;
        }
        return temphead.next;
    }

    //将链表以1,2,4.。。。等单位划分为小片(用于并归排序)
    //就是将最后一个元素.next=null
    static  void slash(ListNode head,int count)
    {
        while(head!=null&&--count!=0)
        {
            head=head.next;
        }
        if(head!=null)
        head.next=null;
    }

    static int getlength(ListNode head)//获得链表长度函数
    {
        int count=0;
        while(head!=null)
        {
            count++;
            head=head.next;
        }
        return count;
    }
<think>好的,我现在需要帮用户解决LintCode 452题,用C++实现删除链表中指定元素的问题。首先,我得回忆一下链表的基本操作,特别是删除节点的常见方法。用户提到的是LintCode,所以可能需要考虑题目的具体要求,比如是否需要原地修改,是否允许使用额外空间等。 根据用户提供的引用内容,比如引用[2]中的链表倒数第n个节点的解法,里面使用了双指针的方法。但这里的问题是关于删除特定元素的,可能需要遍历链表,然后逐个检查节点值是否等于目标值,如果是的话就跳过或删除。 首先,链表的删除操作通常需要处理头节点的情况,因为如果要删除的节点是头节点,那么需要将头指针移动到下一个节点。此外,在单链表中,删除一个节点需要知道其前驱节点,这样才能调整指针,跳过当前节点。所以,我需要考虑如何遍历链表,同时保持前驱节点的引用。 用户提到的引用[1]中的删除排序数组重复项使用了双指针法,这可能对链表操作也有启发,比如使用快慢指针,快指针遍历节点,慢指针指向有效节点的末尾。但链表的结构不同,需要调整指针的指向。 接下来,我需要考虑可能的解法。常见的链表删除方法有两种:一种是使用虚拟头节点,简化头节点的处理;另一种是直接处理头节点,循环删除直到头节点不为目标值,然后再处理后续节点。 虚拟头节点的方法可能更简洁,因为这样可以统一处理所有节点,无需单独处理头节点。例如,创建一个dummy节点,其next指向原链表的头节点,然后用一个current指针从dummy开始遍历。当current的下一个节点值等于目标值时,将current的next指向下下个节点,否则current移动到下一个节点。这样可以避免头节点的特殊情况。 另一个方法是先处理头节点,循环检查头节点是否需要删除。例如,当head不为空且head的val等于目标值时,将head指向head->next,直到头节点的值不等于目标值。然后再处理剩下的节点,用两个指针prev和curr,prev初始化为head,curr为head->next。然后遍历链表,当curr的值等于目标值时,prev的next指向curr的next,否则prev移动到curr。curr继续前进。 这两种方法都需要考虑链表为空的情况,以及所有节点都需要删除的情况。比如,如果整个链表的节点都是目标值,那么最终应该返回空链表。 现在,我需要参考用户提供的引用内容。引用[2]中的链表倒数第n个节点的问题,使用了计算链表长度的方法。这可能对链表操作有帮助,但删除元素的问题可能不需要计算长度,而是需要遍历每个节点。 现在,我应该写出具体的代码实现。使用虚拟头节点的方法可能更简洁,代码如下: struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; class Solution { public: ListNode* removeElements(ListNode* head, int val) { ListNode* dummy = new ListNode(0); dummy->next = head; ListNode* current = dummy; while (current->next != NULL) { if (current->next->val == val) { ListNode* temp = current->next; current->next = temp->next; delete temp; // 是否需要释放内存?根据题目要求而定,有些题目可能不需要 } else { current = current->next; } } ListNode* newHead = dummy->next; delete dummy; return newHead; } }; 这里需要注意,是否需要释放被删除节点的内存,可能根据题目要求而定。在大多数编程题中,可能不需要显式释放,但如果是实际应用,可能需要。不过题目可能只关心指针的正确调整。 另一种方法,先处理头节点: class Solution { public: ListNode* removeElements(ListNode* head, int val) { // 处理头节点 while (head != NULL && head->val == val) { ListNode* temp = head; head = head->next; delete temp; } if (head == NULL) return NULL; ListNode* prev = head; ListNode* curr = head->next; while (curr != NULL) { if (curr->val == val) { prev->next = curr->next; delete curr; curr = prev->next; } else { prev = curr; curr = curr->next; } } return head; } }; 这种方法需要处理头节点可能被多次删除的情况,比如连续的几个头节点都是目标值。例如,链表为[2,2,3],删除2,结果应为[3]。 比较两种方法,虚拟头节点的方法代码更简洁,避免了对头节点的特殊处理,但需要额外的空间创建dummy节点。而直接处理头节点的方法节省了空间,但需要更多的循环处理。 现在需要验证这两种方法的正确性。例如,测试用例: 输入:1->2->6->3->4->5->6, val = 6 输出应为:1->2->3->4->5 使用虚拟头节点的方法,dummy->next指向头节点,current从dummy开始。当current->next是6时,删除该节点,current的next指向下一个节点。直到遍历完成。 另一个测试用例:所有节点都需要删除,比如7->7->7,val=7,应该返回空。 虚拟头节点的方法会遍历,将dummy的next逐个删除,最终返回null。 而直接处理头节点的方法,首先循环删除头节点,直到head为null,然后返回。 两种方法都能处理这种情况。 现在,用户的问题是关于C++的实现,所以需要注意指针操作和内存管理。例如,在删除节点时,是否需要delete,这取决于题目是否要求释放内存。在LintCode或LeetCode中,通常不需要显式释放,因为评测系统会自动处理,但正确的指针调整是关键。 总结,正确的解法应该是使用虚拟头节点或者先处理头节点的方法,遍历链表,删除所有值为val的节点,并返回新的头节点。 最后,生成相关问题,可能需要考虑链表操作的其他问题,比如删除重复项、反转链表等,或者更复杂的情况,比如删除多个元素,或者在特定条件下的删除。</think>### LintCode 452 删除链表中的元素 C++ 实现 #### 解法思路 删除链表中指定元素的核心在于遍历链表并调整指针。需注意: 1. **头节点处理**:若头节点需要被删除,需特殊处理 2. **内存管理**:根据题目要求决定是否释放被删除节点的内存 3. **循环条件**:确保遍历过程不漏节点 #### 代码实现(虚拟头节点法) ```cpp struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) {} }; class Solution { public: ListNode* removeElements(ListNode* head, int val) { ListNode* dummy = new ListNode(0); // 虚拟头节点简化操作 dummy->next = head; ListNode* curr = dummy; while (curr->next) { if (curr->next->val == val) { // 发现目标节点 ListNode* temp = curr->next; curr->next = temp->next; // 跳过目标节点 delete temp; // 释放内存(根据题目要求可选) } else { curr = curr->next; // 继续遍历 } } ListNode* res = dummy->next; delete dummy; return res; } }; ``` #### 关键步骤解析 1. **虚拟头节点**:$dummy$ 节点作为新头节点,避免单独处理原头节点被删除的情况 2. **双指针操作**:$curr$ 指针始终指向待检查节点的前驱节点 3. **时间复杂度**:$O(n)$ 仅需一次线性遍历[^2] #### 示例验证 输入:$1 \rightarrow 2 \rightarrow 6 \rightarrow 3 \rightarrow 4 \rightarrow 5 \rightarrow 6$ 删除值:6 输出:$1 \rightarrow 2 \rightarrow 3 \rightarrow 4 \rightarrow 5$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值