当前位置: 首页 > news >正文

Leetcode 刷题记录 07 —— 链表

本系列为笔者的 Leetcode 刷题记录,顺序为 Hot 100 题官方顺序,根据标签命名,记录笔者总结的做题思路,附部分代码解释和疑问解答。

01 相交链表

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

int val是一个整数类型的成员变量,用于存储链表节点的数据值。

ListNode *next 是一个指向 ListNode 类型的指针,用于保存下一个节点的地址。

ListNode(int x) : val(x), next(NULL) {}是一个构造函数,用于创建一个 ListNode 对象。

其中,val(x) 使用初始化列表的语法,将参数 x 的值赋给成员变量 valnex (NULL)next 指针初始化为 NULL。这表示初始化新节点时,它暂时不指向任何其他节点。

方法一:哈希集合

时间复杂度 O(m + n),空间复杂度 O(m)

  • 建立无序集合 visited,存储链表 A 中的元素情况
  • 遍历链表 B,判断 visited.count(temp)
/*** 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) {unordered_set<ListNode *> visited;ListNode *temp;temp = headA;while(temp != nullptr){visited.insert(temp);temp = temp -> next;}temp = headB;while(temp != nullptr){if(visited.count(temp)) return temp;temp = temp -> next;}return nullptr;}
};

nullptr 是一种专门用于指针的文字常量,用于表示空指针。

NULL 通常在 C 和 C++ 中被定义为整数常量 0,这有时会导致歧义和一些不希望发生的类型转换问题。

#include <iostream>void func(int) {std::cout << "Integer version called" << std::endl;
}void func(char*) {std::cout << "Pointer version called" << std::endl;
}int main() {func(nullptr); // 这将会调用指针版本的函数,因为 nullptr 是指针类型return 0;
}

方法二:双指针

时间复杂度 O(m + n),空间复杂度 O(1)

  • 建立双指针 ListNode *pAListNode *pB ,指向头元素
  • 遍历链表 A 和链表 B,判断 pA == nullptrpB == nullptr,移动双指针,直到 pA == pB
/*** 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) {if(headA == nullptr || headB == nullptr) return nullptr;ListNode *pA = headA;ListNode *pB = headB;while(pA != pB){if(pA == nullptr) pA = headB;else pA = pA -> next;if(pB == nullptr) pB = headA;else pB = pB -> next;}return pA;}
};

02 反转链表

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

/*** 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* reverseList(ListNode* head) {}
};

方法一:双指针

时间复杂度 O(n),空间复杂度 O(1)

  • 建立双指针 *prev*curr,分别指向前一个结点和当前结点
  • 遍历链表,判断 curr != nullptr ,执行 curr -> next = prev;

在这里插入图片描述

/*** 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* reverseList(ListNode* head) {ListNode *prev = nullptr;ListNode *curr = head;while(curr != nullptr){ListNode *next = curr -> next;curr -> next = prev;prev = curr;curr = next;}return prev;}
};

方法二:递归

时间复杂度 O(n),空间复杂度 O(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* reverseList(ListNode* head) {if(!head || !head->next) return head;ListNode *newNode = reverseList(head->next); //⭐head->next->next = head;head->next = nullptr;return newNode;}
};

03 回文链表

在这里插入图片描述

在这里插入图片描述

/*** 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:bool isPalindrome(ListNode* head) {}
};

方法一:数组 + 双指针

时间复杂度 O(n),空间复杂度 O(n)

  • 建立数组 vals,存储链表中的元素情况
  • 遍历数组,判断 vals[i] != vals[j]
/*** 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:bool isPalindrome(ListNode* head) {vector<int> vals;while(head != nullptr){vals.push_back(head -> val);head = head -> next;}for(int i=0, j=(int)vals.size()-1; i<j; ++i, --j){if(vals[i] != vals[j]) return false;}return true;}
};

为什么没有判断 if(head == nullptr) return true;

可以在函数开始时加入 if (head == nullptr) return true;作为显式的情况处理以表明逻辑上的完整性,不过,代码中两段循环的跳过可以隐式的处理 head == nullptr 的情况并直接 return true

方法二:递归 + 双指针

时间复杂度 O(n),空间复杂度 O(n)

  • 建立前端指针 frontNode 和后端指针 currentNode
  • 遍历数组,判断 frontNode -> val != currentNode -> val
/*** 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 {ListNode* frontNode; //前端指针
public:bool recursivelyCheck(ListNode* currentNode){ //后端指针if(currentNode){if(!recursivelyCheck(currentNode -> next)) return false; //递归式⭐if(frontNode -> val != currentNode -> val) return false;frontNode = frontNode -> next;}return true;}bool isPalindrome(ListNode* head) {frontNode = head;return recursivelyCheck(head);}
};

recursively 递归地、reverse 逆转、palindrome / ˈpælɪndroʊm / 回文

② 在运行递归函数时,计算机需要在进入被调用函数之前跟踪它在当前函数中的位置(以及任何局部变量的值),通过运行时存放在堆栈中来实现(堆栈帧)。

③ 为什么没有判断 if(head == nullptr) return true;

headnullptr 时,recursivelyCheck 函数会立即识别到,并返回 true,这相当于隐式地处理了空链表的特殊情况。

方法三:快慢指针 + 反转链表

时间复杂度 O(n),空间复杂度 O(1)

  • 找结点:建立快慢指针 fastslow,找到链表前半部分的结束点
  • 翻链表:利用反转链表,建立 prevcurr,找到链表后半部分的结束点
  • 遍历链表,判断 p1->val != p2->val
  • 注:需要在最后将反转的部分链表反转回来
/*** 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* findNode(ListNode* head){ListNode* fast = head;ListNode* slow = head;while(fast->next != nullptr && fast->next->next != nullptr){fast = fast->next->next;slow = slow->next;}return slow;}//翻链表ListNode* reverseList(ListNode* head){ListNode* prev = nullptr;ListNode* curr = head;while(curr != nullptr){ListNode* next = curr->next;curr->next = prev;prev = curr;curr = next;}return prev;}bool isPalindrome(ListNode* head) {if(head == nullptr) return true;ListNode* firstHalfEnd = findNode(head); //找结点ListNode* secondHalfStart = reverseList(firstHalfEnd->next); //翻链表ListNode* p1 = head;ListNode* p2 = secondHalfStart;bool result = true;while(result && p2 != nullptr){if(p1->val != p2->val) result = false;p1 = p1->next;p2 = p2->next;}firstHalfEnd->next = reverseList(secondHalfStart); //神来之笔,翻回链表return result;}
};

为什么需要判断 if(head == nullptr) return true;

如果没有这个判断,接下来代码中任何对链表操作(比如访问节点值或找到中间节点)都会试图访问一个空指针,从而导致运行时错误(segmentation fault)。另外,快速返回的策略使得对特殊情况的处理更高效。

04 环形链表

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:bool hasCycle(ListNode *head) {}
};

方法一:哈希表

时间复杂度 O(n),空间复杂度 O(n)

  • 建立无序集合 visited,存储已经访问的元素
  • 遍历链表,判断 visited.count(head)
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:bool hasCycle(ListNode *head) {unordered_set<ListNode*> visited;while(head != nullptr){if(visited.count(head)) return true;visited.insert(head);head = head->next;}return false;}
};

方法二:快慢指针

「Floyd 判圈算法」(龟兔赛跑算法)

假想「乌龟」和「兔子」在链表上移动,「兔子」跑得快,「乌龟」跑得慢。当「乌龟」和「兔子」从链表上的同一个节点开始移动时,如果该链表中没有环,那么「兔子」将一直处于「乌龟」的前方;如果该链表中有环,那么「兔子」会先于「乌龟」进入环,并且一直在环内移动。等到「乌龟」进入环时,由于「兔子」的速度快,它一定会在某个时刻与乌龟相遇,即套了「乌龟」若干圈。

  • 建立快慢指针 fastslow,假装乌龟和兔子
  • 遍历链表,判断 slow != fast
  • 注:if(fast == nullptr || fast->next == nullptr) return false;
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:bool hasCycle(ListNode *head) {if(head == nullptr || head->next == nullptr) return false;ListNode* slow = head;ListNode* fast = head->next;while(slow != fast){if(fast == nullptr || fast->next == nullptr) return false;fast = fast->next->next;slow = slow->next;}return true;}
};

05 环形链表Ⅱ

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

时间复杂度 O(n),空间复杂度 O(n)

  • 建立无序集合 visited,存储已经访问的元素
  • 遍历链表,判断 visited.count(head)
/*** 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) {unordered_set<ListNode*> visited;while(head != nullptr){if(visited.count(head)) return head;visited.insert(head);head = head->next;}return nullptr;}
};

方法二:快慢指针

  • 建立快慢指针 fastslow,假装乌龟和兔子
  • 遍历链表,判断 slow != fast,成立,建立额外指针 pear,假装小熊
  • 小熊从原点出发,它和乌龟会在入环点相遇

在这里插入图片描述

/*** 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* slow = head;ListNode* fast = head;while(fast != nullptr && fast->next != nullptr){fast = fast->next->next;slow = slow->next;//改动部分if(fast == slow){ListNode* pear = head;while(pear != slow){pear = pear->next;slow = slow->next;}return pear;}}return nullptr;}
};

相关文章:

  • 小土堆pytorch--transform
  • 关于loadstartcode使用
  • 使用 Poco C++ 库构建轻量级 HTTP 服务器
  • 小微企业SaaS ERP管理系统,SpringBoot+Vue+ElementUI+UniAPP
  • Oracle中游标和集合的定义查询及取值
  • Flutter开发HarmonyOS实战-鸿蒙App商业项目
  • 极速轻量,Rust 网络开发新选择:Hyperlane 框架深度解析
  • C++学习之路,从0到精通的征途:priority_queue类的模拟实现
  • 20250506异形拼图块(圆形、三角、正方,椭圆/半圆)的中2班幼儿偏好性测试(HTML)
  • 高频面试题:设计秒杀系统,用Redis+Lua解决超卖
  • SpringBoot教学管理平台源码设计开发
  • [学习]RTKLib详解:pntpos.c与postpos.c
  • C++【继承】
  • 深入浅出数据库事务:原子性、一致性、隔离性、持久性
  • ShardingSphere:使用information_schema查询时报错:Table ‘数据库名称.tables‘ doesn‘t exist
  • 荣耀A8互动娱乐组件部署实录(终章:后台配置系统与整体架构总结)
  • 【Linux系统篇】:Linux线程同步---条件变量,信号量与CP模型实现
  • Qt学习Day0:Qt简介
  • 【C语言】(7)—指针1
  • ActiveMQ 安全机制与企业级实践(一)
  • 中邮保险斥资8.69亿元举牌东航物流,持股比例达5%
  • 江苏淮安优化村级资源配置:淮安区多个空心村拟并入邻村
  • 吴清:全力支持中央汇金公司发挥好类“平准基金”作用
  • 六大车企一季报:比亚迪近92亿净利稳居第一,多家车企营收下滑
  • 退休11年后,71岁四川厅官杨家卷被查
  • 李学明谈笔墨返乡:既耕春圃,念兹乡土