链表-算法总结

目录

移除链表元素

设计链表

翻转链表

 两两交换链表中的节点

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

链表相交

环形链表


对链表中常用的,虚拟头节点,增删改查,翻转,删除倒数节点,环形链表进行了介绍


移除链表元素

用于单链表中删除指定元素,常用的方法包括:1虚拟节点操作,2原节点操作。

方法:

我们先以2原节点操作举例:

思路在于,我们需要做两次判断,第一种为被删除元素为头节点位置,第二种为非头节点位置。

头节点位置处理的思路是:

如果该头节点为目标值,那么我们让头节点的下一个节点为节点,

head = head->nex

之和我们在返回head头节点时,其实返回的是最开始的第二个节点,那么对于第一个节点,我们还需要释放内存。所以我们定义一个中间变量 tmp 保存 head 最开始的指针,当完成节点移动后,删除 tmp也就是删除了最开始的head。

        while(head != NULL && head->val == val){
            //需要删除头节点
            ListNode* tmp = head;
            head = head->next;//头节点在第二个节点位置了
            delete tmp;//释放空间最开始的头节点
        }

其他节点位置处理的思路是:

由于当前被删除的节点必不可能为头节点,因此删除的思路是,如果该节点需要被删除,那么就让该节点的上一个节点指向被删除节点的下一个节点。同时也需要定义中间变量进行释放内存。因此整个代码如下:

/**
 * 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* removeElements(ListNode* head, int val) {
        while(head != NULL && head->val == val){
            //需要删除头节点
            ListNode* tmp = head;
            head = head->next;//头节点在第二个节点位置了
            delete tmp;//释放空间最开始的头节点
        }
        //删除非头节点
        ListNode* cur = head;
        while(cur != NULL && cur->next != NULL){
            //数组还在边界内
            if(cur->next->val == val){
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;//让cur指向删除的位置指向被删除位置的下一个
                delete tmp;//释放空间被删除的节点
            }
            else{
                cur = cur->next;
            }
        }
        return head;
    }
};

我们以1虚拟节点操作举例:

这里的思路是:先建立一个虚拟节点,该节点指向第一个节点,那么在后续判断中就不需要再判断头节点了,直接按照删除非头节点的方式。

       ListNode* dummyHead = new ListNode(0);//设置虚拟节点
       dummyHead->next = head;
       ListNode* cur = dummyHead;

然后判断后续非头节点是否需要删除,因此代码如下:

/**
 * 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* removeElements(ListNode* head, int val) {
       ListNode* dummyHead = new ListNode(0);//设置虚拟节点
       dummyHead->next = head;
       ListNode* cur = dummyHead;
       while(cur->next != NULL){
           //在有效区间内
           if(cur->next->val == val){
               //需要被删除
               ListNode* tmp = cur->next;
               cur->next = cur->next->next;
               delete tmp;
           }
           else{
               cur=cur->next;
           }
       }
       head = dummyHead->next;//由虚拟节点指向头节点
       delete dummyHead;//释放虚拟节点内存
       return head;
    }
};

相关题目:

203. 移除链表元素 - 力扣(LeetCode)https://leetcode.cn/problems/remove-linked-list-elements/

设计链表

对单链表进行头插,尾插,任意插,任意删,获取任意节点的值。我们将分别介绍这几个部分

方法:
头插:

定义一个虚拟节点指向头节点,定义一个新节点。同时,新节点指向头节点,虚拟节点指向新节点

    void addAtHead(int val) {
        LinkNode *newNode = new LinkNode(val);
        newNode->next = _dummyHead->next;//把新节点指向头节点
        _dummyHead->next = newNode;//把虚拟节点指向新节点
        _size++;//节点数量增加
    }

尾插:

定义一个指针指向虚拟节点,定义一个新节点被最后位置指向

    void addAtTail(int val) {
        LinkNode* newNode = new LinkNode(val);
        LinkNode* cur = _dummyHead;
        //这里cur不一样可以试着代值进去
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newNode;
        _size++;
    }

任意插:

    void addAtIndex(int index, int val) {
        if(index > _size || index < 0){
            return ;
        }
        LinkNode* newNode = new LinkNode(val);
        LinkNode* cur = _dummyHead;
        while(index--){
            cur = cur->next;
        }
        newNode->next = cur->next;;
        cur->next = newNode;
        _size++;
    }
    

任意删:

    void deleteAtIndex(int index) {
        if(index >= _size || index < 0){
            return ;
        }
        LinkNode* cur =_dummyHead;
        while(index--){
            cur = cur->next;
        }
        LinkNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        _size--;
    }

相关题目:

707. 设计链表 - 力扣(LeetCode)https://leetcode.cn/problems/design-linked-list/

翻转链表

原理:

对于单链表进行元素翻转时,我们利用双指针方法,分别指向前后节点,交换节点指向顺序实现整个链表的翻转

方法:

包括:1双指针法,2递归法实现,两者思路一样,需要注意的是,在翻转之后移动指针时,先移动后指针再移动前指针

我们现在以双指针举例:

定义一个cur指向前节点赋值为head,定义一个后节点赋值为NULL。因为翻转后,之前的头节点需要指向NULL

        ListNode* cur = head;//后节点
        ListNode* pre = NULL;//前节点

当前节点指向NULL,也就是之前的尾节点时结束循环,同时利用一个中间变量保存cur的下一个节点,因为我们需要把cur的下一个节点赋值给pre,没有中间变量将导致找不到下一个节点了。当完成一次翻转后,后节点,前节点依次前移

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* cur = head;//后节点
        ListNode* pre = NULL;//前节点
        while(cur){
            //当cur指向空结束
            ListNode* temp = cur->next;//保留cur之前的下一个节点
            cur->next = pre;//指针翻转;
            //指针同时前移,先移pre,再cur
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

我们现在以递归举例:

由双指针思路一样,我们递归函数为reverse(),其中传入为reverse(head,NULL),也就是之前的(cur,pre),然后返回pre

 return reverse(head,NULL);

当cur=NULL时结束,返回pre

        if(cur == NULL){
            //结束
            return pre;
        }

定义中间变量和翻转节点,这里和双指针一样

        ListNode* temp = cur->next;
        cur->next = pre;//翻转指针

现在前移节点,按照双指针,先移动pre,再移动cur,因此传入的reverse按照这个样子,总代码如下:

class Solution {
public:
    ListNode* reverse(ListNode* cur,ListNode* pre){
        if(cur == NULL){
            //结束
            return pre;
        }
        ListNode* temp = cur->next;
        cur->next = pre;//翻转指针
        //移动指针
        return reverse(temp, cur);
    }
    ListNode* reverseList(ListNode* head) {
        return reverse(head,NULL);//cur,pre的顺序传入,返回pre

    }
};

相关题目:
206. 反转链表 - 力扣(LeetCode)https://leetcode.cn/problems/reverse-linked-list/

 两两交换链表中的节点

链表-算法总结_第1张图片

创建虚拟头节点指向头节点,定义一个指针指向虚拟头节点,以及保存 1, 3节点的指针 

        ListNode* dummyHead = new ListNode(0);//虚拟头节点
        dummyHead->next = head;
        ListNode* cur = dummyHead;
            //1.保存 1,3节点
            ListNode* temp1 = cur->next;
            ListNode* temp2 = cur->next->next->next;

判断链表遍历结束,一定要先判断cur->next。否则出现空指针报错

 while(cur->next != nullptr && cur->next->next != nullptr){

 //1.交换cur 和 2

 cur->next = cur->next->next;

 //2.交换1,2

  cur->next->next = temp1;

//3.交换1,3

 temp1->next = temp2;

//cur移动两个,总代码如下:

/**
 * 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* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0);//虚拟头节点
        dummyHead->next = head;
        ListNode* cur = dummyHead;
        while(cur->next != nullptr && cur->next->next != nullptr){
            //1.保存 1,3节点
            ListNode* temp1 = cur->next;
            ListNode* temp2 = cur->next->next->next;
            //1.交换cur 和 2
            cur->next = cur->next->next;
            //2.交换1,2
            cur->next->next = temp1;
            //3.交换1,3
             temp1->next = temp2;
            //cur移动两个
            cur = cur->next->next;
        }
        return dummyHead->next;
    }
};

相关题目:
24. 两两交换链表中的节点 - 力扣(LeetCode)https://leetcode.cn/problems/swap-nodes-in-pairs/

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

 方法:

要删除链表中的倒数第n个节点,我们使用:双指针方法。

定义一个虚拟头节点指向head,两个快慢指针指向虚拟节点

        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;

先让快指针移动n+1个位置,再让快慢指针一起移动,当快指针到了NULL时,此时慢指针在被删除元素的前一个位置。

        while(n-- && fast != NULL){
            fast = fast->next;
            //先让fast移动n
        }
        //fast移动再+1,这样slow在被删除元素前一个
         fast = fast->next;
        //fast slow一起移动,直到fast到NULL
        while(fast != NULL){
            fast = fast->next;
            slow = slow->next;
        }

现在进行元素删除,让slow的next指向被删除元素的下一个,同时释放内存,总代码如下

/**
 * 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* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;
        while(n-- && fast != NULL){
            fast = fast->next;
            //先让fast移动n
        }
        //fast移动再+1,这样slow在被删除元素前一个
         fast = fast->next;
        //fast slow一起移动,直到fast到NULL
        while(fast != NULL){
            fast = fast->next;
            slow = slow->next;
        }
        ListNode* temp = slow->next;
        slow->next = slow->next->next;
        delete temp;
        return dummyHead->next;
    }
   
};

相关题目:
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)icon-default.png?t=M85Bhttps://leetcode.cn/problems/remove-nth-node-from-end-of-list/

链表相交

方法:
求两个链表相交的位置,如果没有就返回空,我们利用一个巧妙办法,先让比较长的链表的头指针移动两个链表的差距长度,然后以长链表开始,遍历两个链表的节点是否一样。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0;
        //求A,B链表长度
        while(curA != NULL){
            lenA++;
            curA = curA->next;
        }
        while(curB != NULL){
            lenB++;
            curB = curB->next;
        }
        //让curA,curB回到头节点
        curA = headA;
        curB = headB;
        //找到最长链表,设置为A
        if(lenB > lenA){
            swap(lenA,lenB);
            swap(curA,curB);
        }
        //求差距长度
        int gap = lenA - lenB;
        while(gap--){
            curA = curA->next;
            //curA移动gap个长度
        }
        //遍历A,B是否有相同的
        while(curA != NULL){
            if(curA == curB){
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};

相关题目:

面试题 02.07. 链表相交 - 力扣(LeetCode)icon-default.png?t=M85Bhttps://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/

环形链表

 方法:

利用双指针实现,题目满足当快慢指针相遇后,同时移动z个距离必定相遇。

根据这个要求:我们做如下工作:

让快慢指针一起移动,并且满足在边界范围内

 while(fast != NULL && fast->next != NULL){
            fast = fast->next->next;
            slow = slow->next;
}

当移动过程中两者相遇,则让两者继续移动,两者必定会碰到

 if(fast == slow){
                ListNode* index1 = head;
                ListNode* index2 = fast;
                while(index1 != index2){
                    //直到两者在移动z个距离碰到
                    index1 = index1->next;
                    index2 = index2->next;
                }

完整代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL){
            fast = fast->next->next;
            slow = slow->next;
            //一直移动快慢指针,直到相遇
            if(fast == slow){
                ListNode* index1 = head;
                ListNode* index2 = fast;
                while(index1 != index2){
                    //直到两者在移动z个距离碰到
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index1;
            }
        }
        return NULL;

    }
};

相关题目:

142. 环形链表 II - 力扣(LeetCode)icon-default.png?t=M85Bhttps://leetcode.cn/problems/linked-list-cycle-ii/

你可能感兴趣的:(算法总结,链表,数据结构)