代码随想录|day3

链表

使用C++
建议顺序读入·。`
链表基础
重点在于:

    1. 虚拟头结点在什么情况下添加, carl哥说,在对链表增删时需要添加虚拟头结点。对链表进行查找或者没有改动链表元素时,就不需要;其原因在于,虚拟头结点的添加,在于在循环中便于对第一个节点操作。 而无需特别处理。
    1. 首先在于对问题的抽象,抽象成怎么能够在一次次迭代中,去处理问题。这种应该有点分治的味道,抽象成一个个相同的子问题。比如下面的翻转链表的题。虽然其采用双指针,但是这种指针的操作,怎么能够在一次次循环迭代中处理。

移除链表元素
这道题题意为在链表中删除给定的元素,主要是设计两个指针,分别是cur和prev, 每次迭代更新prev为cur,好让cur等于被移除的元素时,可以去删除这个元素。由于删除的元素可能是第一个元素所以使用虚拟头节点
核心代码

 while(cur != nullptr)
        {
            if(cur->val == val ) 
            {
                ListNode * tmp = cur ; 
                pre ->next = cur->next ;
                cur = pre;    // 因为要和 !=val的情况保持一致性(为了下面的pre= cur ; cur= cur->next ; 保证执行)所以这么设置 
                delete(tmp ) ; 
            }
            pre = cur ;
            cur= cur->next ; 
        }

完全代码

ListNode* removeElements(ListNode* head, int val) {
        ListNode* dumy = new ListNode() ;
        dumy->next = head ; 
        ListNode* pre = dumy, 
       *cur = dumy->next ; 
        if(cur== nullptr)
        {
            return nullptr ; 
        }
        while(cur != nullptr)
        {
            if(cur->val == val ) 
            {
                ListNode * tmp = cur ; 
                pre ->next = cur->next ;
                cur = pre;    // 因为要和 !=val的情况保持一致性(为了下面的pre= cur ; cur= cur->next ; 保证执行)所以这么设置 
                delete(tmp ) ; 
            }
            pre = cur ;
            cur= cur->next ; 
        }
        return dumy->next ; 
    }

设计链表
这个题的难点在于对于类的设计把握,链表类的对象构造时要生成虚拟头结点,类成员有指向头结点的指针head,还有链表的大小。 每次对链表的添加和删除,获取都从head开始操作。另外是找节点是第几个因此画图是必须的,仔细也是必须的。
核心代码

class MyLinkedList {
public:

    int _size ; 
    ListNode* head ;  
    MyLinkedList() {
        head = new ListNode() ;  // 因为要对链表进行增删,所以需要虚拟头结点
        _size = 0  ;
    }

完全代码

class MyLinkedList {
public:

    int _size ; 
    ListNode* head ;  
    MyLinkedList() {
        head = new ListNode() ; 
        _size = 0  ;
    }
    
    int get(int index) {
        if(_size-1< index)
        return -1 ; 
        ListNode *tmp = head ->next;  
        while(index--)
        {
            tmp = tmp->next ; 
        } 
        return tmp->val ; 
    }
    
    void addAtHead(int val) 
    {
        ListNode * tmp = new ListNode(val);
        tmp->next = head->next ;
        head->next = tmp ; 
        _size++ ;
    }
    
    void addAtTail(int val) {
        ListNode* tmp = new ListNode(val) ; 
        ListNode * cur = head ;
        if(head->next == nullptr)
        {
            head->next = tmp ; 
            _size++ ; 
            return ; 
        }
        while(cur->next != nullptr)
        {
            cur = cur->next ; 
        }
        cur->next = tmp ; 
        _size++ ;
    }
    
    void addAtIndex(int index, int val)
    {
        ListNode * tmp = head ;
        if(index >_size)
        {
            return ; 
        }
        // index = index+1 ;
        while(index--)
        {
            tmp= tmp->next;
        }
        ListNode *t = new ListNode(val) ;
        t->next = tmp->next ;
        tmp->next =t ;  
        _size++ ;
    }
    // void print()
    // {
    //     ListNode * tmp = head  ;
    //     tmp = tmp->next;
    //     while(tmp!=nullptr)
    //     {
    //         cout<<tmp->val>>" ";
    //         tmp = tmp->next; 
    //     }
    // }
    void deleteAtIndex(int index) 
    {
        if(index>=_size) // 如果index
        {
            return ; 
        }
        ListNode * tmp = head ;
        while(index--)
        {
            tmp= tmp->next ;
        }
        ListNode *de = tmp->next ; 
        tmp->next = tmp->next->next;
        delete(de) ; 
        _size-- ; 
        return ; 
    }
};

反转链表
题意是把整个链表反转,需要在O(1)的空间复杂度,扣扣头,发现只有更改指针指向,
原本以为使用双指针就可,但发现虽然一次迭代可以更改指针方向,
fast->next = slow ; 但是如果要迭代,抽象成一个个相同子问题,那么就需要第三个指针指向下一次要迭代处理的起始元素。当然看了卡哥的题解, 也可以是双指针,我这里的第三个指针,是用一个临时节点替代的,本质上还是三指针,不过感觉我这里要清楚点哈哈哈。 但是代码行数都差不多
另外还需要处理少于三个元素的链表
核心代码

 while(fast!= nullptr)
  {
        mid-> next = slow ; //核心语句
        slow = mid ;// 为下一次迭代继续, 重新设置slow
        mid = fast ;//为下一次迭代继续,重新设置mid
        fast = fast->next ;  //为下一次迭代继续,重新设置fast
 }

卡哥代码

    ListNode* reverseList(ListNode* head) {
        ListNode* temp; // 保存cur的下一个节点
        ListNode* cur = head;
        ListNode* pre = NULL;
        while(cur) {
            temp = cur->next;  // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next = pre; // 翻转操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre;
    }

完全代码

    ListNode* reverseList(ListNode* head) {
        //使用三指针 slow , mid , fast ; 因为二指针不能在fast->next = slow  之后更新
         ListNode* slow =nullptr, *mid = nullptr , * fast = nullptr ; 
         if(head)
         {
            slow = head ; 
            if(slow ->next != nullptr)
            {
                mid = slow->next ;
                if(mid->next != nullptr)
                {
                fast = mid->next ; 
                
                }
            }
         }
        if(slow== nullptr)
        {
            return nullptr ; 
        }
        else if(slow!= nullptr && mid == nullptr)
        {
            return slow ;
        }
        else if(slow != nullptr&& mid!= nullptr && fast == nullptr)
        {
            mid->next = slow ; 
            slow->next = nullptr ; 
            return mid ; 
        }
        else if(slow != nullptr && mid != nullptr && fast != nullptr)
        {
            slow ->next = nullptr ; 
            while(fast!= nullptr)
            {
                mid-> next = slow ;
                slow = mid ;
                mid = fast ;
                fast = fast->next ; 
            }
            mid->next = slow ; 
            return mid ; 
        }
        return nullptr ; 
    }

两两交换链表中的节点
这道题卡了半天,做不出来,最后看题解看了一眼,发现有一个cur用于在下一次迭代 连接上一次迭代的头结点。 少想了一步。
题意在于把相邻的两个节点交换。也是使用三指针。当然也可以用卡哥的临时指针替换第三个指针
核心代码

    while(slow != nullptr && mid != nullptr && fast != nullptr) //三指针的判断当然必须以有三个元素为条件
    {
        mid->next =slow ;
        cur->next =mid ;
        slow->next = fast ;
        cur =cur->next->next ; 
        slow = fast ;
        fast = nullptr , mid = nullptr ;  // 因为还不确定下次迭代有没有节点,先将fast,mid全部设为nullptr  
        if(slow->next)  
        {
            mid = slow->next; 
            if(mid->next)
            {
                fast = mid->next ; 
            }
        }
    }

[删除链表倒数第N个节点 ]

题意如题;这回只要想一想,我遍历下链表,找到倒数第N个节点,自然而然是双指针了,一个快指针指向了尾元素的下一个元素的时候,慢指针指向的是要删除的第N个节点的前面节点。这时要画图,要达到的结果的图
核心代码

  while(fast!=nullptr)
        {
            step++ ; 
            
            if(step>n+1) // 因为slow要在被删除节点的前一个节点所以特别注意条件
            {
                slow = slow->next  ; 
            }
            fast=fast->next ;
        }

完全代码

    ListNode* removeNthFromEnd(ListNode* head, int n) {
        // 使用快慢指针, 快指针到达nullptr时, 慢指针刚好到达倒数第n个节点
        ListNode * dummy = new ListNode() ;
        dummy->next = head ; 
        ListNode * fast =dummy , *slow = dummy ; 
        int step = 0 ; 
        if(dummy->next == nullptr)
        {
            return nullptr ; 
        }
        while(fast!=nullptr)
        {
            step++ ; 
            
            if(step>n+1) // 因为slow要在被删除节点的前一个节点所以特别注意条件
            {
                slow = slow->next  ; 
            }
            fast=fast->next ;
        }
        cout<<slow->val <<endl ; 
        ListNode * tmp = slow->next; 
        slow->next = slow->next->next ;
        delete(tmp) ; 
        return dummy->next ;  
    }

链表相交
这个题的题意是给两个链表,找出并返回两个单链表相交的起始节点。
此时不要想用什么方法解决,想将问题抽象,是否可以抽象成数学模型;
在这里插入图片描述
先从怎么找到第一个相交的结点先退一步,判断两个节点是否相同可以用指向两个节点的指针是否相等。 我们有一个工具迭代,还有一个是遍历, 我们能从迭代获取什么吗,迭代是抽象成相同的小问题 好像可以用,那遍历呢,哦,扣破脑袋,也想不到竟然要遍历整个链表获取链表的节点数目呀,再去相减得到的差,较长的链表又有指针从头遍历,遍历的次数是这个差,遍历完成后,较短的链表有一个指向头结点的指针。两个指针慢慢比较 所以要花至少一半的时间去看清题意,从抽象数学模型的角度去看问题。 再用工具去解决。
所以核心代码

  while(pA != nullptr)
        {
            numlistA++ ;
            pA = pA->next ; 
        }
        // cout<<"A"<<numlistA ; 
        while(pB != nullptr)
        {
            numlistB++ ;
            pB = pB->next ; 
        }
            
        if(numlistA > numlistB)
        {   
            int dif = 0 ;  
            dif = numlistA - numlistB ; 
            ListNode * tmpA  = headA ;  
            while(dif--)
            {   
               tmpA = tmpA->next ; 
            }
            ListNode * tmpB = headB ;//较短链表有个指针指向头结点 
            while(tmpA != tmpB && tmpA!= nullptr && tmpB != nullptr)  // 比较,相同的就是头结点。 
            {
                tmpA = tmpA->next ;
                tmpB = tmpB->next ; 
            }
            if(tmpA == tmpB)
            {
                return tmpA ; 
            }

        }
        else
        {
            int dif = 0 ;  
            dif = numlistB - numlistA ; 
            ListNode * tmpB  = headB ;  
            while(dif--)
            {   
               tmpB = tmpB->next ; 
            }
            ListNode * tmpA = headA ;
            while(tmpA != tmpB && tmpA!= nullptr && tmpB != nullptr) 
            {
                tmpA = tmpA->next ;
                tmpB = tmpB->next ; 
            }
            if(tmpA == tmpB)
            {
                return tmpA ; 
            }

        }

[环形链表](https://leetcode.cn/problems/linked-list-cycle-ii/description/)
又是一道非常难的数学题:
记住:快慢指针 , 快指针 一次走两步,慢指针一次走一步,相遇第一次时,一个指针指向头,一个指针指向相遇节点, 然后每次两指针都走一步,直到遇到。

    //  难点计算相遇时,两指针分别走的距离的关系 ; 关系计算时应该要分为起点到链表入口节点的距离。
        // 记忆, : 指针相遇时点到入的距离等于起点到环入口的距离 ; slow 一次走一步,fast一次走两步
        ListNode * slow =head , *fast = head ;
        while(fast!=nullptr && fast->next != nullptr)
        {
            slow = slow->next ;
            fast = fast->next->next;
            if(slow==fast)
            {
                // 如果slow和fast相遇,相遇节点到环入口的距离等于链表起点到环入口的距离。
                ListNode*tmp1 = slow ;
                ListNode*tmp2 = head ;
                while(tmp1!=tmp2)
                {
                    tmp1=tmp1->next;
                    tmp2 = tmp2->next ;
                }
                return tmp1 ; 
            }
        }
        return nullptr ; 

哈哈找到一个直观的题解了
在遍历过程中用哈希表去记录每个节点,之后第一个发现遍历过一次的结点,就是入口。以后还是靠简单数学过活吧,这种高深的东西。
核心代码

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode *> visited;
        while (head != nullptr) {
            if (visited.count(head)) {
                return head;
            }
            visited.insert(head);
            head = head->next;
        }
        return nullptr;
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/linked-list-cycle-ii/solutions/441131/huan-xing-lian-biao-ii-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值