目录
数组&链表基础
数组定义:
数组是一个固定长度的存储相同数据类型的数据结构,数组中的元素被存储在一段连续的内存空间中。它是最简单的数据结构之一,大多数现代编程语言都内置数组支持。
数组在遍历时速度非常快,但是在删除和增加元素时需要平移大量的元素(删除和增加首末位置除外),复杂度为o(n)。
数组特点:
- 使用前需要申请数组长度,声明长度之后不能更改;
- 插入和删除操作需要移动大量的元素,效率比较慢;
- 只能存储一种类型的数据;
链表定义:
链表是一种物理存储单元上非联系、非顺序的存储结构。数据元素中的逻辑顺序是通过链表中的指针连接次序依次实现的,链表由一系列的节点(链表中每一个元素称为节点)组成,节点可以在运行时动态生成,节点的数据空间一般会包含一个数据域和一个指针域,该指针一般称为next,用来指向下一个节点的位置。
链表特点:
- 节点之间通过指针相连;
- 每个节点有一个前驱节点和一个后继节点;
- 首节点没有前驱节点,尾节点没有后继节点;
- n个节点离散分配;
链表图解
链表分类:
单向链表:
链表中的元素只能指向链表中的下一个元素,元素之间不能互相指向。
双向链表:
每个链表既有指向下一个元素的指针,又有指向上一个元素的指针,每个节点都有两个指针。
循环链表:
高频面试题(leetcode)
206 反转链表(常考)
206. 反转链表 - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/reverse-linked-list/
方法1:迭代算法(双指针)(cpp&python)
- 定义curr,pre指针,curr指针初始化为head,pre指针为空指针;
- 定义tmp指针,存储curr的下一节点;
- curr的next指针指向pre;
- curr和pre均向前移动一个位置;
/**
* 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*curr = head,*pre = nullptr; //定义两个指针
while(cur){
ListNode* tmp = cur->next; // 存储当前的下一个节点数据
curr->next = pre; //cur下一节点 指向pre
pre = curr; //pre指针前进一位
curr = tmp; // cur指针前进一位
}
return pre;
}
};
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
cur,pre = head,None
while cur:
# 同时赋值,省掉tmp
cur.next,pre,cur = pre,cur,cur.next
return pre
方法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* reverseList(ListNode* head) {
if (head == 0 || head->next == 0) return head;
ListNode* tmp = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return tmp;
}
};
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
# if head is None or head.next is None:return head # 这个也行
if not head or not head.next:return head
tmp = self.reverseList(head.next)
head.next.next = head
head.next = None
return tmp
妖魔化的双指针算法。
- 定义指针curr,初始化为head;
- 将head的下一个节点的next指向curr,实现局部反转;
- 局部反转完成之后,curr和head的next指针同时向前移动一个位置;
- 循环,一直到curr达到链表的最后一个节点;
/**
* 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 == nullptr) { return nullptr;}
ListNode* curr = head;
while (head->next){
ListNode* tmp = head->next->next;
head->next->next= curr;
curr = head->next;
head->next = tmp;
}
return curr;
}
};
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if head == None:return None
curr = head
while (head.next):
tmp = head.next.next
head.next.next = curr
curr = head.next
head.next = tmp
return curr
24 两两交换链表中的节点
迭代算法,创建哑节点。
- 创建哑结点,令dummyHead.next = head;tmp表示当前到达的节点,初始化为dummyHead;每次交换tmp后面的两个节点。
- tmp后面的节点分别为node1和node2,交换之前:tmp->node1->node2,交换之后:tmp->node2->node1;
- 迭代,当新的链表的头节点为dummyHead.next,停止迭代。
示意图:
/**
* 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* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* tmp = dummyHead;
while (tmp->next != nullptr && tmp->next->next != nullptr){
ListNode* node1 = tmp->next;
ListNode* node2 = tmp->next->next;
tmp->next = node2;
node1->next = node2->next;
node2->next = node1;
tmp = node1;
}
return dummyHead->next;
}
};
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummyHead = ListNode(0)
dummyHead.next = head
tmp = dummyHead
while (tmp.next and tmp.next.next):
node1 = tmp.next
node2 = tmp.next.next
tmp.next = node2
node1.next = node2.next
node2.next = node1
tmp = node1
return dummyHead.next
拓展:哑节点
哑节点是在处理与链表相关的操作时,设置在链表头之前的指向链表头的节点,用于简化与链表头相关的操作。因为LeetCode上的题的测试用例都是在头结点就已经存储数据,所以我们需要一个哑结点,放在头结点的前面。
ListNode dummy = new ListNode(0);
dummy.next = head;
//head是链表的头节点,dummy就是指向链表头部的哑节点。
具体可以参考:
(15条消息) 链表 - 哑节点_Hi-YOLO的博客-CSDN博客_哑节点
141环形链表
141. 环形链表 - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/linked-list-cycle/
方法1:快慢指针算法。
我们定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
步骤示意图:
/**
* 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* fast = head->next;
ListNode* slow = head;
while (fast != slow){
if (fast == nullptr || fast->next == nullptr){ return false;}
fast = fast->next->next;
slow = slow->next;
}
return true;
}
};
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
if(head == None or head.next == None):return False
fast = head.next
slow = head
while (fast != slow):
if (fast == None or fast.next == None):
return False
fast = fast.next.next
slow = slow.next
return True
写一个python简洁版本:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
fast = slow = head
while slow and fast and fast.next.next:
slow = slow.next
fast = fast.next.next
if slow is fast:
return True
return False
方法2:哈希表
哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。
/**
* 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*> seen;
while (head != nullptr){
if (seen.count(head)){
return true;
}
seen.insert(head);
head = head->next;
}
return false;
}
};
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
if (head == None or head.next == None):return False
seen = set()
while (head):
if head in seen:
return True
seen.add(head)
head = head.next
return False
142 环形链表2
142. 环形链表 II - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/linked-list-cycle-ii/
方法1:哈希表
/**
* 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*> visit;
if (head == nullptr || head->next == nullptr){
return nullptr;
}
while (head != nullptr){
if (visit.count(head)){
return head;
}
visit.insert(head);
head = head->next;
}
return nullptr;
}
};
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
visit = set()
if not head or not head.next:return None
while (head):
if head in visit:
return head
visit.add(head)
head = head.next
return None
注:暂时不明白
扩展:哈希表基础及其在c++和python中的基本语法。
set集合
官方文档:
内置类型 — Python 3.10.3 文档https://docs.python.org/zh-cn/3/library/stdtypes.html?highlight=set#set
集合是按照特定顺序存储唯一元素的容器。
在集合中,元素的值也标识它(值本身就是键,类型为 T),并且每个值必须是唯一的。
集合中元素的值不能在容器中修改一次(元素始终为 const),但可以从容器中插入或删除它们。 在内部,集合中的元素始终按照其内部比较对象(比较类型)指示的特定严格弱排序标准进行排序。
set 容器通常比 unordered_set 容器通过键访问单个元素要慢,但它们允许根据它们的顺序对子集进行直接迭代。
集合通常实现为二叉搜索树。
基本函数(c++):
- begin()--返回指向第一个元素的迭代器
- clear()--清除所有元素
- count()--返回某个值元素的个数//值为1或者0
- empty()--如果集合为空,返回true
- end()--返回指向最后一个元素的迭代器
- equal_range()--返回集合中与给定值相等的上下限的两个迭代器
- erase()--删除集合中的元素
- find()--返回一个指向被查找到元素的迭代器
- get_allocator()--返回集合的分配器
- insert()--在集合中插入元素
- lower_bound()--返回指向大于(或等于)某值的第一个元素的迭代器
- key_comp()--返回一个用于元素间值比较的函数
- max_size()--返回集合能容纳的元素的最大限值
- rbegin()--返回指向集合中最后一个元素的反向迭代器
- rend()--返回指向集合中第一个元素的反向迭代器
- size()--集合中元素的数目
- swap()--交换两个集合变量
- upper_bound()--返回大于某个值元素的迭代器
- value_comp()--返回一个用于比较元素间的值的函数1.
多看c++官方文档:
set - C++ Reference (cplusplus.com)
基本函数(python)
多看官方文档:
set和unordered_set区别以及适用情况
(17条消息) set和unordered_set区别以及适用情况_小乾的计算机学习之路-CSDN博客_set unordered_set
致谢
【反转链表】:双指针,递归,妖魔化的双指针 - 反转链表 - 力扣(LeetCode) (leetcode-cn.com)(15条消息) 链表 - 哑节点_Hi-YOLO的博客-CSDN博客_哑节点