代码随想录:链表

203.移除链表元素

题目链接/文章讲解/视频讲解::代码随想录

重点是添加头结点,避免分情况讨论首元素是不是需要删除的情况
注意不能直接用dummy遍历按,dummy记住头结点位置,cur遍历节点,需要记住几个位置就有几个指针
cur->next是要删的节点就做删除操作,不是就往下遍历

ListNode* curNode = dummyNode;
            while(curNode->next)
            {
                if(curNode->next->val == val)
                {
                    ListNode* tmpNode = curNode->next;
                    curNode->next = curNode->next->next;
                    delete tmpNode;
                }
                else
                {
                    curNode = curNode->next;
                }
            }

注意返回值返回dummy->next,需要释放资源
head = dummyNode->next;
           delete dummyNode;
           return head
 

707.设计链表

题目链接/文章讲解/视频讲解:代码随想录

注意:链表长度length要跟着变化!!!
1. 要先定义node的struct,链表记住头结点head和长度

struct MyLinkedListNode{
        int val;
        MyLinkedListNode* next;
        MyLinkedListNode(int val)
        {
            this->val = val;
            next = nullptr;
        }
    };
    MyLinkedListNode* head; // 头结点
    int length; // 长度

    2. 链表构造函数,是构造了一个虚拟头结点dummyNode,长度还是0
   

 MyLinkedList() {
        length = 0;
        head = new MyLinkedListNode(0); // 这里定义的是虚拟头结点,所以长度还是0
    }

    3. 遍历列表时tmp_node指向头结点下一节点,就是需要操作的位置,返回时不用tmp_node->next

int get(int index) {
        if(index < 0 || index + 1> length) return -1;
        MyLinkedListNode* tmp_node = head->next;
        while( index--)
        {
            tmp_node = tmp_node->next;
        }
        return tmp_node->val;
    }

4. 添加节点

void addAtHead(int val) {
        MyLinkedListNode* tmp_node = new MyLinkedListNode(val);
        if(!tmp_node) return;
        tmp_node->next = head->next;
        head->next = tmp_node;
        length++;    
    }

5. 尾部添加元素:注意cur_node是要操作节点的前一个位置cur_node = head 而不是head->
跳出while循环时cur->next = nullptr,就是要操作的节点cur->next = tmp_node,而不是cur->next->next

void addAtTail(int val) {
        MyLinkedListNode* tmp_node = new MyLinkedListNode(val);
        if(!tmp_node) return;
        MyLinkedListNode* cur_node = head;
        while(cur_node&&cur_node->next)
        {
            cur_node = cur_node->next;
        }
        cur_node->next = tmp_node;
        length++;
    }

6. 插入节点:
while中是--i和i--的问题
后置递减 (index--):先使用 index 的当前值,然后再将其减少1。在减少值之前使用当前值时用index--。
前置递减 (--index):先将 index 减少1,然后再使用新的值。需要立即使用减少后的值时时用--index。

void addAtIndex(int index, int val) {
        if(index < 0) index = 0;
        if(index > length) return;
        if(index == length ) 
        {
            addAtTail(val);
            return;
        }
        if(index == 0 ) 
        {
            addAtHead(val);
            return;
        }
        MyLinkedListNode* tmp_node = new MyLinkedListNode(val);
        if(!tmp_node) return;
        MyLinkedListNode* cur_node = head; // cur_node指向要操作节点的上一个
        while(index--) // 注意指针移动次数比插入位置小1
        {
            cur_node = cur_node->next;
        }
        tmp_node->next = cur_node->next;
        cur_node->next = tmp_node;
        length++;
    }
        tmp_node->next = cur_node->next;
        cur_node->next = tmp_node;
        length++;
    }

7.  删除元素

void deleteAtIndex(int index) {
        if(index < 0) return;
        if(index >= length) return;
        MyLinkedListNode* cur_node = head; // cur_node指向要操作节点的上一个
        while(index-- && cur_node)
        {
            cur_node = cur_node->next;
        }
        MyLinkedListNode* tmp_node = cur_node->next;
        cur_node->next = cur_node->next->next;
        delete tmp_node;
        tmp_node = nullptr;
        length--;
    }

206.反转链表

题目链接/文章讲解/视频讲解:代码随想录

双指针法:时间复杂度O(N) 空间复杂度O(1)
pre指向操作值前一个 cur指向操作值 tmp记录下一个,cur执行反转操作
!!!注意pre从nullptr开始,cur从head开始
循环体内:判断条件cur是否为空
tmp先指向cur->next
cur->next指向pre实现翻转
per指向cur向前进一步
cur指向tmp向前进一步
跳出循环时:pre指向了最后节点 ,cur是空,返回pre

错误写法:pre从head开始,cur从head->next开始
cur->next = pre;会产生闭环链表
想用pre->next = pos; cur->next = pre; 也会有误因为pre和pos之间产生了多余的连接,当指针向后移动的时候多余的解环操作导致pre会丢失前面的数据

ListNode* reverseList(ListNode* head) {
        if(!head) return nullptr;
        ListNode* pre =head;
        ListNode* cur =head->next;
        ListNode* pos =nullptr;
        while(cur)
        {
            pos = cur->next;
            cur->next = pre;
            pre = cur;
            cur = pos;
        }       
        return pre;
    }

正确写法:pre从nullptr开始,cur从head开始
pre指向空,当pre移动到列表中的时候,pre指向的点指向之前且没有多余的连接

ListNode* reverseList(ListNode* head) {
        if(!head) return nullptr;
        ListNode* pre =nullptr;
        ListNode* cur =head;
        ListNode* pos =nullptr;
        while(cur)
        {
            pos = cur->next;
            cur->next = pre;
            pre = cur;
            cur = pos;
        }
        return pre;
    }

递归法:时间复杂度O(N) 空间复杂度O(N)
!!!重点: 类中要保留一个前指针
递归函数:对传进来的链表,每次处理头部元素,把头部的指针掉头,再把后面的链表丢进递归函数
返回值:转换完的头结点 参数表:需要处理的链表部分
终止条件:空指针,说明最后一个元素已经处理完了, 返回空指针前一个节点,所以在类中需要保存前指针
进行头部反转处理
剩余部分进行递归
最后返回当前头元素就是最后一个节点

ListNode* preNode;
    ListNode* MyReverse(ListNode* head)
    {
        if(!head) return preNode;
        ListNode* pos = head->next;
        pos->next = head;
        return MyReverse( head->next);
    }
    ListNode* reverseList(ListNode* head) {
       preNode = new ListNode;
       preNode->next = head;
       preNode = MyReverse(head);
       return preNode;
    }

简化:

ListNode* MyReverse(ListNode* cur,ListNode* pre)
    {
        if(!cur) return pre;
        ListNode* pos = cur->next;
        pos->next = cur;
        return MyReverse( cur->next, cur);
    }
    ListNode* reverseList(ListNode* head) {
       return MyReverse(head, nullptr);
    }

24. 两两交换链表中的节点

题目链接/文章讲解/视频讲解: 代码随想录

  ListNode* swapPairs(ListNode* head) {

        if(!head) return nullptr;

        // 1. 加dummynode
        ListNode* dummynode = new ListNode(0);
        dummynode->next = head;

        // 2. 设置cur指针,cur指向交换的相两邻元素之前的元素
        ListNode* cur = dummynode;
        
        // 3. 循环条件:两个交换的元素cur->next和cur->next->next不为空
        while(cur && cur->next && cur->next->next)
        {
            // 4. first指向交换的第一个元素, second指向交换的第二个元素
            ListNode* first =  cur->next;
            ListNode* second =  cur->next->next;

            // 5. second的下一个位置,否则后面移动指针的时候找不到位置
            ListNode* next_pos = second->next;

            // 6. 交换顺序:看图解
            cur->next = second;
            second->next = first;
            first->next = next_pos;
        
           // 7. 移动: cur向后移动两步
            cur = cur->next->next;
        }

        // 8. 释放dummynode
        ListNode* res = dummynode->next;
        delete dummynode
        return res;
    }

19.删除链表的倒数第N个节点

题目链接/文章讲解/视频讲解:代码随想录

思路:
因为链表单向,要删倒数第n节点,且只遍历一次,考虑双指针
!!!采用快指针比慢指针多走n + 1步,结束时快指针指向空元素,慢指针指向删除元素前一个方便删除元素
为什么不采用快指针比慢指针多走n 步,结束时快指针指向最后一个元素,边界处理时对最后一个元素的处理可能会出错
!!!增删改查时必须给链表加dummynode, fast 和slow从dummynode开始操作,以免边界处理时对第一个元素的处理出错
!!!返回 `dummynode->next` 而不是 `head` 是因为:
1. **处理头节点**:当需要删除的节点是头节点时,直接操作 `head` 会导致链表头指针丢失。哑节点(`dummynode`)提供了一个始终指向链表起点的额外层,使得即使头节点被删除,也能通过 `dummynode->next` 访问剩余链表。
2. **统一处理**:无论删除哪个节点,使用哑节点都可以通过 `dummynode->next` 统一访问修改后的链表,无需区分删除的是头节点还是其他节点

ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(!head) return nullptr;
        if(n == 1&& !head->next) return nullptr;
        ListNode* dummynode = new ListNode;
        dummynode->next = head;
        ListNode* fast = dummynode;
        ListNode* slow = dummynode;
        n++;
        while(n--)
        {
          fast = fast->next;
        }
        while( fast)
        {
            fast = fast->next;
            slow = slow->next;
        }
        slow->next =  slow->next->next;
        return dummynode->next;
    }

面试题 02.07. 链表相交

题目链接/文章讲解:代码随想录

 思路:
先分别遍历链表A 和 链表B 记录链表长度
快指针指向长的链表,先走完长度差
慢指针和快指针同步走,边走边比较是否是同一个指针
重点:
比较的是指针本身而不是指针指向的值,如下例:

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {

        if(!headA || !headB) return nullptr;



        int lengthA = 0;

        int lengthB =0;

        ListNode* tmp_A = headA;

        while(tmp_A)

        {

            tmp_A= tmp_A->next;

            lengthA++;

        }

        ListNode* tmp_B = headB;

        while(tmp_B)

        {

            tmp_B= tmp_B->next;

            lengthB++;

        }



        ListNode* tmp_fast = lengthA >= lengthB? headA:headB;

        ListNode* tmp_slow = lengthA < lengthB? headA:headB;

        int distance = abs(lengthA - lengthB);

        while(distance--)

            tmp_fast = tmp_fast->next;

  

        while(tmp_fast)

        {

            if(tmp_fast == tmp_slow) return tmp_fast;

            tmp_fast=tmp_fast->next;

            tmp_slow = tmp_slow->next;

        }

        return nullptr;

    }

142.环形链表II

题目链接/文章讲解/视频讲解:代码随想录

思路:
快慢指针遍历元素,快指针每次走两步,慢指针每次走一步
两指针一定会相遇,快慢指针相对步长是1,一旦入环,可以等效于慢指针不动,快指针一步步往前走直到相遇
慢指针x+y ,快指针x+y+n(y+z)
2*(x+y) = x+y+n(y+z) n>=1,  取n=1,得x = z
快指针和慢指针相遇说明有环,这时再从头出发一个指针out_ptr, 从快慢指针相遇处出发一个指针in_ptr,两指针同步走,直到在入环口出相遇

 ListNode *detectCycle(ListNode *head) {
        // 成环至少有两个节点
        if(!head) return nullptr;
        if(!head->next) return nullptr;

        // 定义快慢指针
        ListNode * slow = head; 
        ListNode * quick = head;

        while(quick && quick->next )
        {
            // 快指针一次走两步,慢指针一次走一步
            quick = quick->next->next;
            slow = slow->next;
            if(quick == slow)  // 快慢指针相遇:有环
            {
                // 从头出发一个指针out_ptr
                ListNode * out_ptr = head;
                // 从相遇处出发一个指针in_ptr
                ListNode * in_ptr = quick;
                while(out_ptr != in_ptr)
                {
                    out_ptr=out_ptr->next;
                    in_ptr=in_ptr->next;
                }

                // 返回相遇处指针
                return out_ptr;
            }
        }
      
      return nullptr;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值