【基础算法】链表相关题目

系列综述:
💞目的:本系列是个人整理为了秋招算法的,整理期间苛求每个知识点,平衡理解简易度与深入程度。
🥰来源:材料主要源于代码随想录进行的,每个算法代码参考leetcode高赞回答和其他平台热门博客,其中也可能含有一些的个人思考。
🤭结语:如果有帮到你的地方,就点个赞关注一下呗,谢谢🎈🎄🌷!!!
🌈数据结构基础知识总结篇



😊点此到文末惊喜↩︎


一、链表理论基础

链表的定义

  1. 单链表数据结构
    • 链表由下一个节点指针组成
    • 成员初始化构造函数,如果不定义,C++也会进行默认生成
    struct LinkList {
    	int val;
    	LinkList *next;
    	LinkList(int v) : val(v), next(nullptr){}
    };
    
  2. 简化的结构体定义:在 C++ 中定义 struct 时,可以省略 struct 关键字,并直接使用结构体名称。

移除链表元素

  1. 链表基本操作
    • 统一遍历操作:添加虚拟头节点vHead,统一遍历操作
    • 内存泄漏问题:删除链表中的节点,不要忘记delete释放
  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) {}
     * };
     */
    ListNode* removeElements(ListNode* head, int val) {
        // 定义虚拟头节点
        ListNode *vHead = new ListNode(0, head);
        ListNode* cur = vHead;				// 定义工作指针cur
        while(cur->next != nullptr){
            if(cur->next->val == val){		// 为了获取操作节点的前一个节点进行删除
                ListNode* tmp = cur->next;	// key:结点删除后要释放
                cur->next = cur->next->next;
                delete tmp;
            }else{
                cur = cur->next;// 工作指针++
            }
        }
        // 删除头节点
        head = vHead->next;
        delete(vHead);// 不要忘记啦
        return head;
    }
    

实现链表相关函数

  1. 相关题目

实现 MyLinkedList 类:

  • MyLinkedList() 初始化 MyLinkedList 对象。
  • int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
  • void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
  • void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
  • void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
  • void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。
class MyLinkedList {
public:
	// 定义链表节点
    struct LinkNode{
        int val;
        LinkNode *next;
        LinkNode(int val):val(val),next(nullptr){}
    };

	// 初始化头节点
    MyLinkedList() {
        vHead = new LinkNode(0);
        size = 0;
    }
    // 根据索引值获取对应节点值
    int get(int index) {
        if(index < 0 || index > (size-1)) 
            return -1;
        // *****带头节点的索引值定位********
        LinkNode* cur = vHead->next;
        while(index--){
            cur = cur->next;
        }
        // ************************************
        return cur->val;
    }
    
    void addAtHead(int val) {
        LinkNode* node = new LinkNode(val);
        node->next = vHead->next;
        vHead->next = node;
        ++size;
    }
    
    void addAtTail(int val) {
        LinkNode* node = new LinkNode(val);
        LinkNode *cur = vHead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = node;
        ++size;
    }
    
    void addAtIndex(int index, int val) {
        if(index > size) return;
        if(index < 0) index = 0;
        LinkNode* node = new LinkNode(val);
        LinkNode* cur = vHead;
         while(index--){
            cur = cur->next;
        }
        node->next = cur->next;
        cur->next = node;
        size++;

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


    }

    private:
        LinkNode *vHead;// 首地址
        int size;// 大小
};

二、双指针

  1. 基本快慢指针
    // 初始化快慢指针
    int slow = 0;
    int fast = 0;
    // 快指针未超边界
    while(fast < nums.size()){
    	if(nums[slow] == val){
    		doing();
    		++slow;
    	}	
    	++fast;
    }
    
    

反转链表

  1. 链表的遍历
    • 遍历使用当前结点:while (cur != nullptr) { }
    • 遍历使用当前结点的前一个结点:while (cur->next != nulllptr) { }
  2. 头插法可以进行链表的反转
    • 尽量使用明确的逻辑关系,减少变量的转换
    • 先进行状态变量的状态保存,后进行逻辑处理
    ListNode* reverseList(ListNode* head) {
            if(head == nullptr)
                    return nullptr;
            // 头插法
            ListNode* vHead = new ListNode(0); // 虚拟头节点
            // 被遍历链表的工作指针
            ListNode* cur = head;
            ListNode* hind;
           // 开始遍历
            while(cur!= nullptr){ 	// key:遍历当前结点使用该条件
                hind = cur->next;	// 一定要确切的使用逻辑关系
                // 头插法
                cur->next = vHead->next;
                vHead->next = cur;
                cur = hind;   
            }
            return vHead->next;
        }
    

交换相邻链表节点

  1. letcode题目网址
    在这里插入图片描述
    ListNode* swapPairs(ListNode* head){
    	// 健壮性检查
    	if(head == nullptr)
    	    return nullptr;
    	
    	// 指向操作节点组的第一个和第二个
    	ListNode* prior_first;
    	ListNode* prior_second;-
    	// 增加虚拟头节点
    	ListNode* vHead = new ListNode(0);
    	vHead->next = head;
    	ListNode *cur = vHead;
    	while(cur->next != nullptr && cur->next->next != nullptr){
    	    prior_first = cur->next;
    	    prior_second = cur->next->next;
    	    prior_first->next = prior_second->next;
    	    prior_second->next = prior_first;
    	    cur->next = prior_second; 
    	    cur = prior_second->next;// cur指向被操作节点组的前一个
    	}
    	return vHead->next;
    }
    

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

  1. letcode题目
    在这里插入图片描述
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    	if(n < 0 || head == nullptr)
    	    return nullptr;
    	// 快慢指针拉开n个节点的距离
    	ListNode *vHead = new ListNode(0);
    	vHead->next = head;
    	ListNode *slow = vHead;
    	ListNode *fast = vHead;
    	// 让slow指向被删除节点的前一个
    	while(n--){
    	    fast = fast->next;
    	}
    	// 同步移动
    	while(fast->next != nullptr){
    	    fast = fast->next;
    	    slow = slow->next;
    	}
    	// 删除节点
      	LinkList *tmp = slow->next;
      	slow->next = slow->next->next;
      	delete tmp;
    	return vHead->next;
    }
    

链表相交

  1. letcode题目链接
    在这里插入图片描述
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *curA = headA;
        ListNode *curB = headB;
        
        int lenA = 0;
        int lenB = 0;
        // 计算A和B的长度
        while(curA != nullptr){
            lenA++;
            curA = curA->next;
        }
        while(curB != nullptr){
            lenB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        // 求两链的差距
        int dis = 0;
        if(lenA > lenB){
            dis = lenA-lenB;
            while(dis--){
                curA = curA->next;
            }
        }else{
            dis = lenB-lenA;
            while(dis--){
                curB = curB->next;
            }
        }
    
        // 同步移动判断指针
        while(curA != curB){
            if(curA == nullptr || curB == nullptr){
                return nullptr;
            }
            curA = curA->next;
            curB = curB->next;
        }
    
        return curA;
    }
    
  2. 有点东西的写法
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
            ListNode *A = headA, *B = headB;
            // 核心在于交换头节点
            while (A != B) {
                A = A != nullptr ? A->next : headB;
                B = B != nullptr ? B->next : headA;
            }
            return A;
        }
    

求环形链表的入口

  1. letcode题目链接
    • 一个节点的next是否能访问,需要判断该节点是否存在
    • 只写容易判断的逻辑,其他的交给else
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow=head;
        ListNode* fast=head;
        // 1. 快指针走两步,慢指针走一步
        while(fast!=NULL&&fast->next!=NULL){
            slow=slow->next;
            fast=fast->next->next;
            // 如果相等,让慢指针从头开始走,快慢指针一起移动,相等则为链表环形入口
            if(slow==fast){
                slow=head;
                while(slow!=fast){
                    slow=slow->next;
                    fast=fast->next;
                }
                return fast;
            }
        }
        return NULL;
     }
    

移除链表中的重复结点

LinkList *DeleteSameNode(LinkList *head) {
	LinkList *vhead = new LinkList(0);
	vhead->next = head;
	
	// 删除cur->next中剩下的值为val的结点
	auto delete_node = [](int val, LinkList *cur){
	  while (cur->next != nullptr) {
	    if (cur->next->val == val) {
	      LinkList *tmp = cur->next;
	      cur = cur->next->next;
	      delete tmp;
	    }
	    cur = cur->next;
	  }
	};
	// 遍历链表,删除重复结点
	LinkList *cur = vhead;
	while (cur->next != nullptr) {
	  delete_node(cur->next->val, cur);
	  cur = cur->next;
	}
}

删除无序链表中值重复出现的节点

  1. 采用哈希表set性质进行单一化,然后进行新建。

    • 空间复杂度:O(n)
    • 时间复杂度:O(n)
  2. 使用unordered_set<int> s;记录曾经出现过的情况,通过s.find(val) != s.end()实现对过去历史的快速查找。

    #include <set>
    void deleteDuplicates(ListNode* head) {
        if (head == nullptr) return ;
        std::unordered_set<int> s; // 用于存储出现过的节点值
        // key:处理头节点,从而不需要建立vhead,但是头节点必须不会被删除
        s.insert(head->val); // 先将头节点的值插入 set
        ListNode* cur = head;
        while (cur->next != nullptr) {
            if (s.find(cur->next->val) != s.end()) { // 如果下一个节点的值在 set 中出现过,删除下一个节点
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            } else { // 否则将下一个节点的值插入 set 中,继续遍历
                s.insert(cur->next->val);
                cur = cur->next;
            }
        }
    }
    
    
  3. 采用双重链表遍历,每次排除一个元素的其他重复结点

    • 空间复杂度:O(1)
    • 时间复杂度:O(n^2)
    void deleteDuplicates(ListNode* head) {
        if (head == nullptr) {
            return;
        }
        ListNode* cur = head;
        while (cur != nullptr) {		// key:使用了当前遍历法,cur
            ListNode* prev = cur;
            ListNode* next = cur->next;
            while (next != nullptr) {	// key:使用了前者遍历法,cur->next
                if (next->val == cur->val) { // 如果下一个节点的值与当前节点的值相同,删除下一个节点
                    prev->next = next->next;
                    delete next;
                    next = prev->next;
                } else { // 否则继续向后遍历
                    prev = next;
                    next = next->next;
                }
            }
            cur = cur->next;
        }
    }
    
    


少年,我观你骨骼清奇,颖悟绝伦,必成人中龙凤。
不如点赞·收藏·关注一波


🚩点此跳转到首行↩︎

参考博客

  1. 代码随想录
  2. letcode
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

逆羽飘扬

如果有用,请支持一下。

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

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

打赏作者

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

抵扣说明:

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

余额充值