力扣刷题总结 链表(1)

🔥博客主页 A_SHOWY
🎥系列专栏力扣刷题总结录 数据结构  云计算

203.移除链表元素easy虚拟头结点简便
707.链表的各种操作(六个小题目)mid基础操作,熟悉
206.翻转列表easy双指针和递归方法,核心都是双指针
24.两两交换链表中的节点        mid交换的步骤不能颠倒,同时提前设置tmp
19.删除链表中的倒数第n个数mid如何找到倒数第n个数,双指针:快指针先走n,然后一起走
面试题 链表相交easyswap()函数
142. 环形链表Ⅱ‘mid双指针方法,快慢指针

一、链表基础理论

(1)什么是链表

1.链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成

2.数据域和指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针)

3.链表的入口结点为链表的头结点也就是head

(2)链表的类型 

1.单链表:上图简单的链表

2.双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点

3.循环链表:就是链表首位连接

(3)链表的存储方式

同数组不同的是,链表的内存不是不是连续分布的,而是通过指针来链接内存的各个结点

//单链表
struct(ListNode)
{
int val;//结点上存储的元素
ListNode *next//指向下一个节点的指针
ListNode(int x) : val(x) , next(NULL){}//节点的构造函数

}

这个构造函数的作用是可以初始化节点

(1)如果通过自己构造函数初始化节点

ListNode* head = new ListNode(5);    

 (2) 如果使用默认构造函数的话,初始化时不能直接给变量赋值

ListNode* head  = new ListNode();
head -> val = 5;

(4)链表的操作 

1.删除节点时,只需要把指针指向下一个节点。再释放删除的内存空间。但是在删除头结点时,比较特殊需要head  = head -> next

特殊: 删除第n个节点时,需要从头结点用next指针查询,时间复杂度为o(n)

2.增加节点时,申请一个空间,前一个指针指到这里,新空间的指针指向下一个节点 

二、 移除链表元素

 在前面提到,是否是头结点的删除元素方式不统一,为了统一,我们设置新的方法创建虚拟头结点

(1) 203. 移除链表元素

203. 移除链表元素icon-default.png?t=N7T8https://leetcode.cn/problems/remove-linked-list-elements/1. 第一种方法是分开讨论

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;
        delete tmp;
    }
    else{
        cur = cur->next;
    }
}
return head;
}
};

 2.第二种方法是创建一个虚拟头结点一起讨论

/**
 * 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
    {
          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;
        }
};

 这里有两个个点需要注意:

第一点是创建的虚拟头结点需要delete

第二点是因为是一个不断观察是否等于val的过程,所以要用while而不是if

三、设计链表

(1)707. 链表的各种操作(基础)

707. 设计链表icon-default.png?t=N7T8https://leetcode.cn/problems/design-linked-list/有几个需要注意的点:

1.首先是每逢遍历整个链表之前先设立一个指针cur用来专门遍历,不然返回头指针会受阻

2. 在删除下标为index的结点时候,delete只是释放了原本tmp 指针指向的部分的内存,delte后并不是指针tmp的值并不是NULL,会成为随机值,所以需要

tmp = nulltpr;

防止他成为乱指向的野指针。

3. 定义结构体的时候别忘了最后的括号

4. 初始化链表前声明私有成员变量

private:
int _size;
LinkedNode* _dummyhead;

class MyLinkedList {
public:
struct LinkedNode{
//定义链表结构体
     int val;
     LinkedNode* next;//定义了一个名为 next 的指针变量
     LinkedNode(int val): val(val),next(nullptr){}
};
//初始化链表
    MyLinkedList() {
    _dummyhead = new LinkedNode(0);//定义虚拟的头结点
    _size = 0;

    }
//获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 
    int get(int index) {
if(index < 0 || index >= _size) 
{
    return -1;
}
LinkedNode* cur = _dummyhead -> next;
while(index--)
{
    cur = cur -> next;
}
return cur->val;
    }
//将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点
    void addAtHead(int val) {
    LinkedNode* newcode = new LinkedNode(val);
    newcode -> next = _dummyhead -> next;
    _dummyhead -> next = newcode;
    _size ++;
    }
//将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
    void addAtTail(int val) {
    LinkedNode* newcode = new LinkedNode(val);
    LinkedNode* cur = _dummyhead;//不能用next
    while(cur -> next != nullptr)
    {
        cur = cur -> next;
    }
    cur -> next = newcode;
    _size ++;
    }
// 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将不会插入到链表中
    void addAtIndex(int index, int val) {
    LinkedNode* newcode = new LinkedNode(val);
    if(index > _size) return;
    if(index < 0) index = 0;
    LinkedNode* cur = _dummyhead;
    while(index --)
    {
        cur = cur -> next;
    }
    newcode -> next = cur -> next;
    cur -> next = newcode;
    _size ++;
    }
//如果下标有效,则删除链表中下标为 index 的节点。
 
    void deleteAtIndex(int index) {
if(index < 0 || index >= _size) return;
LinkedNode* cur = _dummyhead;
while(index--)
{
    cur = cur -> next;
}
LinkedNode* tmp = cur -> next;
cur -> next = cur -> next -> next;
delete tmp;
tmp = nullptr;
_size --;
    }
//打印链表
void printLinkedList()
{
    LinkedNode* cur = _dummyhead;
    while(cur -> next != nullptr)
    {
        cout<<cur -> next -> val;
        cur = cur -> next;
    }
    cout<<endl;
}
private:
int _size;
LinkedNode*  _dummyhead;
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

 四、 翻转链表

 (1) 206. 翻转链表

1. 双指针法

 //方法1 双指针的方法
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
     ListNode* tmp;//很重要因为要翻转链表 cur指向pre时,无法联系后边的链表,所以提前存上
     ListNode*  cur = head;
     ListNode* pre = NULL;
     while(cur)
     {
         tmp = cur -> next;
         cur -> next = pre;
         pre = cur;
         cur = tmp;//注意这两行不能换位置
     }
     return pre;
    }
};

双指针方法有两个点需要注意:

       1.  ListNode* tmp;很重要因为要翻转链表 cur指向pre时,无法联系后边的链表,所以提前存上

       2.注意两个指针同时向前移动时操作的顺序

2.递归方法

核心思想和双指针一样,只是代码更加简约

//递归方法,巧妙,核心还是双指针
ListNode* reverse(ListNode* pre,ListNode* cur)
{
    if(cur == NULL) return pre;
    ListNode* tmp = cur -> next;
    cur -> next = pre;
   return reverse(cur,tmp);
}
    ListNode* reverseList(ListNode* head) {
return reverse(NULL,head);
    }
};

想清楚上面那张图,递归就能很快对应写出来。

五、两两交换链表中的结点

(1)24.两两交换的链表中的节点

24. 两两交换链表中的节点icon-default.png?t=N7T8https://leetcode.cn/problems/swap-nodes-in-pairs/ 

1. 本题目的交换过程一共有三个步骤,但是首先就是要防止指针变换导致的节点丢失,需要提前标注出临时指针tmp和tmp1,然后的操作就是dummyhead指向2,2指向1,1指向3.然后cur向后走两位

2.还有一个要注意的是,设置dummyhead的作用是头结点交换的时候后续操作保持一致

3.在循环的时候要考虑奇数个还是偶数个,所以在while循环的时候,要考虑两种情况

while(cur -> next != nullptr && cur -> next -> next != nullptr)至于为什么要用&& 表示为只有后一个节点和后后的节点都不为NULL时,才继续循环,如果用|| (或)会在奇数个节点时陷入死循环

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)
   {
       ListNode* tmp = cur -> next;
       ListNode* tmp1 = cur -> next -> next -> next;
       cur -> next = cur -> next -> next;//第一步
       cur -> next -> next = tmp;//第二步
       tmp -> next = tmp1;//第三步

       cur = cur -> next -> next;
   }
   return dummyhead -> next;
    }
};

六、删除链表中倒数第n个数(寻找倒数第n个数)

(1)19.删除链表中倒数第n个数

19. 删除链表的倒数第 N 个结点icon-default.png?t=N7T8https://leetcode.cn/problems/remove-nth-node-from-end-of-list/

1.这道题的关键思路是如何找到倒数第n个数 思路:双指针快慢指针,快指针先走n,快慢指针一起走,直到快指针指到NULL。

2.但是要删除第n个数,需要操作第n-1个数,才能删除第n个

3.同样需要定义虚拟头结点

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyhead = new ListNode(0);
dummyhead -> next = head;
ListNode* slow = dummyhead;
ListNode* fast = dummyhead;
n++;
while(n--)
{
    fast = fast -> next; 
}
while(fast != NULL)
{
    fast = fast -> next;
    slow = slow -> next;
}
slow -> next = slow -> next -> next;
   return dummyhead -> next;
    }
 
};

七、链表相交

面试题 02.07. 链表相交icon-default.png?t=N7T8https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/

(1)面试题  链表相交

1.找后半部分的相同的部分,并返回头指针,其主要思路是先求一下两个链表的长度,然后先找出长的那个指针,先让他走len1-len2,然后再每个节点逐个判断

2.因为输入的两个链表不知道哪个是长的,如何让cur1 指向那个长度比较长的链表是很关键的,所以用到swap函数,让cura为最长链表的头,同时len1表示长的那个

3.在确定两个链表长度以后,一定不要忘记初始化cura和curb。

/**
 * 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 len1 = 0;
        int len2 = 0;
        //求第一个链表长度
        while(cura != NULL)
        {
           len1 ++;
           cura = cura -> next;
        }
        //求第二个链表长度
        while(curb != NULL)
        {
            len2 ++;
            curb = curb -> next;
        }
    //把cura为最长链表的头,len2为其长度
   cura = headA;
   curb = headB;
  if(len2 > len1)
  {
      swap(len1 ,len2);
      swap(cura , curb);
  }
   int length = len1 - len2;
   while(length--)
   {
       cura = cura -> next;
   }
   while(cura != NULL)
   {
       if(cura == curb) {return cura;}
       cura = cura -> next;
       curb = curb -> next;
   }
   return NULL;
    }
};

八、环形链表(判断是否有环)

(1)142. 环形链表Ⅱ

142. 环形链表 IIicon-default.png?t=N7T8https://leetcode.cn/problems/linked-list-cycle-ii/1.首先第一个目标是判断是否有环,那么根据经验是快慢指针,那么为什么会使用快慢指针,因为首先快指针永远走的快,慢指针永远走的慢。当有环时,快指针通过环回到原来的点,和慢指针相遇才有可能有环。

2.第二个问题就是如何找到环的入口处,通过快指针走2,慢指针走1,设置参数可以得出来,设置两个指针 ,一个从相交点走,一个从head走,相遇的时候刚好是环的入口,如图。

3.第三个点是为什么设置slow 的时候不用设置成转了很多圈是因为,我们设置了fast是slow 的二倍,所以在slow进入后,取一个极限情况,fast刚过,那么依然能够追上在一圈之内。

 /**
 * 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还要判断fast的下一位
    {
        fast = fast-> next -> next;
        slow = slow -> next;
//判断相交的点,也就是环的起始点
        if(fast == slow)
        {
            ListNode* index1 = fast;
            ListNode* index2 = head;
            while(index1 != index2)
            {
                index1 = index1 -> next;
                index2 = index2 -> next; 
            }
            return index1;
        }
    } 
    return NULL;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A_SHOWY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值