C++算法学习二.链表

1.链表基础知识:

根据代码随想录学习,做的笔记和算法心得

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思),链表的入口节点称为链表的头结点也就是head。

三种:单链表,双链表,循环链表。

双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。双链表 既可以向前查询也可以向后查询。

循环链表,就是链表首尾相连,循环链表可以用来解决约瑟夫环问题。

链表中的节点在内存中不是连续分布的 ,

C/C++的定义链表节点方式

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

c++删除链表节点需要手动释放该节点。

链表的增添和删除都是O(1)操作,也不会影响到其他节点。

要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)。

2.移除链表元素(203题)

题目描述:删除链表中等于给定值 val 的所有节点。

链表节点需要去手动删除,删除下的节点

链表的一般解法1.直接在原来的链表进行删除操作2.设置一个虚拟头节点进行删除操作

删除链表元素的操作本质就是将节点的next指针直接指向下一个节点的下一个节点,(直接链表操作)其他节点根据前一个节点删除当前节点,头节点:将头节点向后移动一位即可。设置虚拟头节点可以不用上述操作。

直接链表操作法:

 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;
                delete tmp;
            }else{
                cur = cur->next;//相当于循环体
            }
        }
        return head;
    }
};

创建一个虚拟头节点就可按照统一的方式进行处理,注:虚拟头节点的下一个节点才是头节点,

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;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

 3.设计链表(707题)

题目描述:

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

这个题包含的全是链表的基本操作,很好的练习对链表的基本操作,

class MyLinkedList {
public:
struct Linknode{//定义链表节点结构体
    int val;//数据域
    Linknode* next;//指针域
    Linknode(int val):val(val),next(nullptr){}//有参构造
};
    MyLinkedList() {//初始化链表
        _dummyhead = new Linknode(0);//设置虚拟头节点
        _size = 0;
    }
    
    int get(int index) {//获取下标的值
        if(index > (_size-1)  || index < 0){//定义下标无效的情况
            return -1;
        }
        Linknode* cur = _dummyhead->next;//设置当前节点是头节点
        while(index--){//下标进行循环
            cur =cur->next;//循环体相当于++操作
        }
        return cur->val;//返回得到的值
    }
    
    void addAtHead(int val) {//在头插入值
        Linknode* newnode = new Linknode(val);//创建一个新的节点
        newnode->next = _dummyhead->next;//连接链表的左侧
        _dummyhead->next = newnode;//再将链表的右侧连接起来
        _size++;//整体大小+1
    }
    
    void addAtTail(int val) {//在尾插入值
        Linknode* newnode = new Linknode(val);//创建一个新的节点
        Linknode* cur = _dummyhead;//定义当前节点,为找到最后节点遍历做基础
        while(cur->next != nullptr){//循环条件找到最后的节点位置
            cur = cur->next;//循环体,
        }
        cur->next=newnode;//最后节点位置的下一个将插入元素放入
        _size++;
    }
    
    void addAtIndex(int index, int val) {//将一个值为val的节点加入到Index处
        if(index > _size){//下标无效
            return;
        }
        Linknode* newnode = new Linknode(val);//新建立节点
        Linknode* cur = _dummyhead;//当前节点
        while(index--){
            cur = cur->next;
        }
        newnode->next = cur->next;//找到index位置,先链接后面再链接前面
        cur->next = newnode;//
        _size++;
    }
    
    void deleteAtIndex(int index) {//删除链表Index的节点
        if(index >= _size || index < 0){//下标无效情况
            return;
        }
        Linknode* cur = _dummyhead;//当前节点
        while(index--){//
            cur = cur->next;
        }
        Linknode* tmp = cur->next;//找到当前index位置的节点
        cur->next = cur->next->next;//链接后面
        delete tmp;//删除节点
        _size--;
    }

    void printLinkedList(){//打印节点值
        Linknode* cur = _dummyhead;//当前节点
        while(cur->next != nullptr){//知道整个链表结束
            cout<<cur->next->val<<"";
            cur = cur->next;
        }
        cout<<endl;
    }
private:
    int _size;
    Linknode* _dummyhead;
};

4.反转链表(206题)

题目要求:反转一个单链表,不申请额外的内存空间。

改变链表的指针指向即可

双指针解法:定义一个快指针,一个慢指针,快指针指向头节点,慢指针指向空,使用tmp保持临时节点。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {//双指针方法
        ListNode* fast = head;//定义一个快指针指向头节点
        ListNode* tmp;//临时节点,接入快指针的位置
        ListNode* slow = nullptr;//定义一个慢指针指向空
        while(fast){//遍历整个链表
            tmp = fast->next;//首先断开后面的链表,并且用一个指针接受位置,更新下一个快指针的位置
            fast->next = slow;//链接前面的节点
            slow = fast;//更新慢指针到快指针位置
            fast = tmp;//快指针在向后移动到达临时接受的指针位置
        }
        return slow;//新的头节点也就是慢指针的位置
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

 递归解法:注:是根据两个节点去写反转的实现到整个链表

class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur){//定义两个链表节点进行操作
        if(cur == nullptr)return pre;//如果快指针为0,返回慢指针
        ListNode* tmp = cur->next;//临时指针接收快指针下一个位置
        cur->next = pre;//反转
        return reverse(cur,tmp);//递归下一个函数,相当于cur是新的pre,tmp是新的cur变相链表指针向后移动
    }
    ListNode* reverseList(ListNode* head) {
        return reverse(nullptr,head);//从头到尾反转
    }
};
  • 时间复杂度: O(n), 要递归处理链表的每个节点
  • 空间复杂度: O(n), 递归调用了 n 层栈空间

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

题目描述:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

 模拟一下反转链表即可实现,定义循环条件这两个交换的节点都不为空时才可以循环,且循环条件也有顺序,不可以随意替换

注:要断的地方需要用临时节点保存一下

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;//头节点指向2号位置
            cur->next->next = tmp;//2号的下一个指向1号位置也就是第一个临时节点
            cur->next->next->next = tmp1;//一号位置下一个指向第二次节点指向位置

            cur = cur->next->next;//走到相当于2号位置
        }
        return dummyhead->next;//返回头节点
        delete dummyhead;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

 6.删除链表的倒数第N个节点(19题)

双指针解法:删除倒数第n个节点,让快指针先走n步,快指针应该向后移动一个节点,为了慢指针可以删除操作,之后让快指针和慢指针同时移动,直到快指针为null为止,然后删除slow指向的节点即可。

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 != nullptr){//先让快指针走n个节点并且确保快指针不为空
            fast = fast->next;
        }
        fast = fast->next;//快指针再前一步,因为删除操作需要前一个节点信息,所以这为了帮助slow指向删除节点的上一个节点
        while(fast != nullptr){//快指针循环条件知道空指针为止
            fast = fast->next;//快慢指针一起移动
            slow = slow->next;
        }
        ListNode* tmp = slow->next;//定义一个临时的节点接收需要删除的节点
        slow->next = slow->next->next;//删除节点,指向需要删除节点的下个节点
        delete tmp;//释放节点内存
        return dummyhead->next;//返回头节点
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

7. 链表相交(面试题 02.07)

链表相交求的是两个链表交点节点的指针,

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;//定义两个指针分别指向头节点
        ListNode* curB = headB;
        int lenA = 0,lenB = 0;//定义每个链表的长度值
        while(curA){//计算A链表长度
            lenA++;
            curA = curA->next;
        }
        while(curB){//计算B链表长度
            lenB++;
            curB = curB->next;
        }
        curA = headA;//重新将指针指向头节点位置
        curB = headB;
        if(lenA<lenB){//让lenA是最大值的链表
            swap(curA,curB);
            swap(lenA,lenB);
        }
        int gap = lenA-lenB;//计算两个链表的差值
        while(gap--){//将A 链表移动到与b链表尾部一个位置的位置
            curA = curA->next;
        }
        while(curA){//循环条件直到A链表为空
            if(curA == curB){//找到两个链表相等的位置返回A链表
                return curA;
            }
            curA = curA->next;//否则两个链表一起向后移动
            curB = curB->next;
        }
        return NULL;
    }
};

实际上就是首先定义两个节点,并且对两个链表求长度,得到差值,然后再经过长度大的链表移动差值之后,对比两个链表的值,如果相等就返回。

  • 时间复杂度:O(n + m)(取决两个链表的长度)
  • 空间复杂度:O(1)

 8.环形链表II(142题)

题目描述:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。不允许修改给定的链表。

两个任务首先判断是否有环,找到入环的入口,判断是否有环采用的是双指针的做法,快指针每次走两个节点,慢指针每次走一个节点,为何能相遇,对于slow来说是一个节点速度慢慢的逼近,设头节点到入环节点的节点数为x,在环内相遇的地方距离入口为y,剩余的节点环内为z,slow指针走了x+y节点,fast指针走了x+n(y+z)+y,可以得到一个等式2(x+y)=x+n(y+z)+y,我们需要x值,所以可以整理可得x=(n-1)(y+z)+z,说明一个问题假设两个指针一个从头节点出发,另一个在相遇点出发,每次移动一个节点,两个指针相遇时候则就是入环地址。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow = head;//设置两个指针一个快指针一个慢指针都指向头节点
        ListNode* fast = head;
        while(fast != nullptr && fast->next != nullptr){//循环条件
            slow = slow->next;//慢指针一次走一个节点
            fast = fast->next->next;//快指针一次走两个节点
            if(slow == fast){//如果快慢指针相遇
                ListNode* index1 = fast;//记录快指针的位置
                ListNode* index2 = head;//说明一个指针从头节点出发,另一个指针从相遇位置出发,最后相遇的点就是环的入口
                while(index1 != index2){//循环条件
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2;//返回入口地址也是两个指针相遇的地址
                delete index1;
                delete index2;
            }
        }
        return nullptr;//
        delete slow;
        delete fast;
    }
};

总结:

链表基础知识,在空间不是连续存在的,删除节点需要手动释放空间,第一个节点是头指针,最后一个是空指针,记住链表节点的结构体,

移除链表元素,采用虚拟头节点方法将指针指向删除节点的下一个指针域即可,不要忘记释放节点

设计链表:首先定义链表节点的结构体,数据域,指针域,有参构造,删除节点,以及打印,加值删除值,

反转链表:双指针方法实现,快指针指向头节点,慢指针指向空指针,注意断开连接时需要找个临时节点接入断开节点的下一个节点,每次两个指针向前移动。

两两交换链表:其实就是模拟反转的过程,但是需要采用两个临时节点接入,而且注意顺序,循环条件的顺序也要注意,其他就是模拟一下链表的反转操作流程。

删除链表的倒数第N个节点:双指针解法:思想非常好,先让快指针跑n+1个节点,这里为什么是n+1节点,因为慢指针需要操作删除节点,

链表相交:一个长链表和一个短链表,计算两个链表的长度,计算差值,让长链表移动差值之后,两个链表一起遍历如果相等返回数值。

环形链表:有两个点一个是如何判断有环,还有一个是环的入口在哪,判断有环的思想是定义两个指针,快慢指针快指针每次走两个节点,慢指针每次走一个节点,如果相遇则有环,并且用指针记录下来,另一个指针从头节点开始,这两个指针每次走一个节点最后相遇就是环的入口。真是妙极了!!!!!!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值