2.链表.基础知识

基础知识

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

  • 单链表:指针域只能指向节点的下一个节点;
  • 双链表:节点有两个指针域,一个指向下一个节点,一个指向上一个节点;
  • 循环链表:就是链表首尾相连,可以用来解决约瑟夫环问题。
    链表的存储方式
    不同于数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
    链表的定义
// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

之所以要定义构造函数而不是使用默认构造函数的话,在初始化的时候就能直接给变量赋值!

链表操作

  • 删除节点
    在这里插入图片描述

  • 添加节点
    在这里插入图片描述

与数组的关系

  • 数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
  • 链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,=频繁增删,较少查询的场景。

题目

1.移除节点

题目链接
移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点
其实可以设置一个虚拟头结点,然后指向原本链表的头节点,这样原链表的所有节点就都可以按照统一的方式进行移除了。

/**
 * 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* analoghead = new ListNode();
        analoghead->next = head;
        ListNode* cur = analoghead;
        while(cur->next != nullptr){
            if(cur->next->val==val){
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            }
            else{
                cur = cur->next;
            }
        }
        head = analoghead->next;
        delete analoghead;
        return head;
    }
};

2.设计链表

题目链接
在一个列表类中实现以下功能:

  • 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 LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr){}
    };

    MyLinkedList() {
        _size = 0;
        head = new LinkedNode(0);
    }
    
    int get(int index) {
        if(index > _size-1 || index < 0) return -1;
        LinkedNode* cur = head->next;
        while(index--){
            cur = cur->next;
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        LinkedNode* newhead = new LinkedNode(val);
        newhead->next = head->next;
        head->next = newhead;
        _size++;
    }
    
    void addAtTail(int val) {
        int nums = _size;
        LinkedNode* newtail = new LinkedNode(val);
        LinkedNode* cur = head;
        while(nums--){
            cur = cur->next;
        }
        cur->next = newtail;
        _size++;
    }
    
    void addAtIndex(int index, int val) {
        if(index>_size) return;
        if(index<0) index = 0;
        LinkedNode* newtail = new LinkedNode(val);
        LinkedNode* cur = head;
        while(index--){
            cur = cur->next;
        }
        newtail->next = cur->next;
        cur->next = newtail;
        _size++;
    }
    
    void deleteAtIndex(int index) {
        if(index > _size-1 || index < 0) return;
        LinkedNode* cur = head;
        while(index--){
            cur = cur->next;
        }
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        _size--;
    }
private:
    int _size;
    LinkedNode* head;
};
};

3.反转链表

题目链接
给定一个链表,反转其排列顺序(反转链表节点的指针域)。
使用虚拟头结点:从链表起点开始,以k-1为cur结点;交换k(k=0,1,2,,,)的next指向,因此需要额外设置节点存储变量保存原本k节点的next,用于下一次遍历(cur起点是k)

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* tmp = new ListNode();
        ListNode* cur = head;
        ListNode* pre = nullptr;
        while(cur){
            tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
};

可以看出在反转链表时,至少需要3个变量分别存储pre,cur,tmp代表前一个节点,当前节点,下一个节点。
时间复杂度: O(n);空间复杂度: O(1)

class Solution {
public:
    ListNode* reverse(ListNode* pre, ListNode* cur){
        if(cur==nullptr) return pre;
        ListNode* tmp = cur->next;
        cur->next = pre;
        return reverse(cur, tmp);
    }

    ListNode* reverseList(ListNode* head) {
        return reverse(nullptr, head);
    }
};

该题也有递归的解法,递归结束标志是cur==nullptr时,在每层递归逻辑中将cur指向pre,初始化条件时cur=headpre=nullptr
时间复杂度: O(n), 要递归处理链表的每个节点;空间复杂度: O(n), 递归调用了 n 层栈空间;


4.两两交换链表节点

题目链接
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表(不能只是修改节点的值,而是要进行实际的节点位置交换)。
使用虚拟头结点:从链表起点开始,以k-1为cur结点;交换k,k+1的位置,同时修改k,k+1的next指向,因此需要额外设置节点存储变量保存原本两个节点的next-tmp0,tmp1,用于下一次遍历(起点是k+1)
在这里插入图片描述

在这里插入图片描述
使用模拟头部节点,在步骤一前,需要存储节点1指针(cur的next指针);在步骤二前,需要存储节点3的指针(节点2的next指针);步骤三后,将cur绑定为节点1,进入下一步遍历。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* analoghead = new ListNode();
        analoghead->next = head;
        ListNode* cur = analoghead;
        while(cur->next!=nullptr && cur->next->next!=nullptr){
            ListNode* tmp1 = cur->next;
            ListNode* tmp2 = cur->next->next;
            tmp1->next = tmp2->next;
            tmp2->next = tmp1;
            cur->next = tmp2;
            cur = tmp1;
        }
        return analoghead->next;
    }
};

5.删除链表倒数第N个结点

题目链接
使用虚拟结点-使用双指针(快指针,慢指针之间间隔设置为与N相关的量):L-p,R-p同时从虚拟结点开始,R-p先移动N+1位置,然后两指针同时右移,直到R-p指向nullptr,此时L-p所指向的结点为N-1,即可对其next指针域进行修改;
删除倒数第N个结点,需要在倒数第N+1个结点进行操作;因此快指针与慢指针之间因相差N+1个结点。

在这里插入图片描述

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* analoghead = new ListNode();
        analoghead->next = head;
        ListNode* right_point = analoghead;
        ListNode* left_point = analoghead;
        while(n-- && right_point!=nullptr){
            right_point = right_point->next;
        }

        while(right_point->next){ // 使用analoghead遍历结束标志
            right_point = right_point->next;
            left_point = left_point->next;
        }
        ListNode* tmp = left_point->next;
        left_point->next = left_point->next->next;
        delete tmp;
        return analoghead->next;
    }
};

时间复杂度: O(n);空间复杂度: O(1)


6.链表相交

题目链接
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
链表有交点意味了两链表的后部分的结点存在相同的指针,而链表有长短之分,因此可以将链表右端对齐,从较短链表开头位置设置指针逐步进行指针域比较。
在这里插入图片描述

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int size1 = 0;
        int size2 = 0;
        ListNode* cur = headA;
        while(cur){
            size1++;
            cur = cur->next;
        }
        cur = headB;
        while(cur){
            size2++;
            cur = cur->next;
        }

        ListNode* pointa = new ListNode();
        pointa->next = headA;
        ListNode* pointb = new ListNode();
        pointb->next = headB;

        if(size1>size2){
            int num = size1-size2;
            while(num--) pointa = pointa->next;

        }
        else{
            int num = size2-size1;
            while(num--) pointb = pointb->next;
        }

        while(pointa->next!=nullptr){
            if(pointa->next == pointb->next) return pointa->next;
            pointa = pointa->next;
            pointb = pointb->next;
        }
        return nullptr;
    }
};

7.环形列表

题目链接
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。为了表示给定链表中的环,使用整数pos来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果pos 是-1,则在该链表中没有环。
使用双指针:slow,fast分别以1,2的速度从头结点出发,直至结点相遇。
在这里插入图片描述
2 ( x + y ) = x + y + n ( y + z ) x = ( n − 1 ) ( y + z ) + z x = z \begin{align} 2(x+y) = x+y+n(y+z) \\ x = (n-1)(y+z)+z \\ x=z \end{align} 2(x+y)=x+y+n(y+z)x=(n1)(y+z)+zx=z
根据slow与fast相遇节点位置,可以推导存在以下数学关系:x=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 = head;
                ListNode* index2 = slow;
                while(index1!=index2){
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index1;
            }
        }
        return nullptr;
    }
};

时间复杂度: O ( n ) O(n) O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n;空间复杂度 O ( 1 ) O(1) O(1)


总结

链表涉及的知识&问题主要有:

  • 设计链表
  • 移除链表元素
  • 反转链表
  • 交换链表中两节点的数值
  • 删除链表倒数第N个节点
  • 判断两链表相交节点的位置
  • 环形链表
  • 27
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值