学习目标:
- 链表理论基础
- 203.移除链表元素
- 707.设计链表
- 206.反转链表
学习内容:
链表理论基础
链表的类型
-
单链表
-
双链表
-
循环链表:循环链表可以用来解决约瑟夫环问题。
约瑟夫环(Josephus problem)是一个经典的数学和计算机科学问题,最早出现在犹太历史学家弗拉维奥·约瑟夫斯(Flavius Josephus)的著作《犹太战记》中。
问题的描述如下:N个人围成一圈,从第一个人开始报数,报到第M个数字的人出列,然后从出列的下一个人开始重新报数,重复这个过程,直到剩下最后一个人。问题的目标通常是找到最后剩下的那个人的位置。
这个问题可以用递归或数学的方法求解。解决约瑟夫环问题的一种通用的递归形式如下:
设 f(n) 表示 n 个人中最后剩下的人的位置,如果我们知道了 f(n-1),则可以计算 f(n)。
f ( n ) = ( f ( n − 1 ) + m ) % n f(n) = (f(n-1) + m) \% n f(n)=(f(n−1)+m)%n
其中,% 表示取余数操作。初始条件是 f(1) = 0,因为在只有一个人时,这个人就是最后剩下的人。
约瑟夫环问题在计算机科学和数学领域经常被用作算法和递归的练习,同时也涉及到循环链表等数据结构的应用。
链表的定义
链表的操作
删除节点
删除后的节点让然留存再内存里,知识不在这个链表里,所以在C++中最好手动释放这个节点,从而释放内存。
203.移除链表元素
/*
* @lc app=leetcode.cn id=203 lang=cpp
*
* [203] 移除链表元素
*/
// @lc code=start
/**
* 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 *dummyHead = new ListNode(0); // 设置一个虚拟头结点
dummyHead->next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
ListNode *cur = dummyHead;
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 = dummyHead->next; //虚拟的头节点最好也要回收。
delete dummyHead; //
return dummyHead->next;
}
};
// @lc code=end
错误以及注意事项
- 如果不适用dummyhead的话,对于头节点的检查,直接将head指针后移即可,但是别忘记了这个也要删除。
- 链表中
.
和->
的使用场景:->
用于通过指针访问成员,而.
用于通过对象访问成员。
//如果有一个链表节点的指针 ListNode* nodePtr,你可以使用 -> 来访问该节点的成员 val 和 next
int value = nodePtr->val; // 访问节点的val成员
ListNode* nextNode = nodePtr->next; // 访问节点的next成员
//如果你有一个节点的对象而不是指针,那么你应该使用 . 来访问成员:
ListNode node(42);
int value = node.val; // 访问节点的val成员
ListNode* nextNode = node.next; // 访问节点的next成员
707.设计链表
class MyLinkedList
{
public:
// 定义链表节点结构体
struct LinkedNode
{
int val;
LinkedNode *next;
LinkedNode(int val) : val(val), next(nullptr) {}
};
MyLinkedList() {
_dummyHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
_size = 0;
}
int get(int index)
{
if (index < 0 || index > (_size - 1))
{
return -1;
}
LinkedNode *cur = _dummyHead->next;
while (index--)
{
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val)
{
LinkedNode *newHead = new LinkedNode(val);
newHead->next = _dummyHead->next;
_dummyHead->next = newHead;
_size++;
}
void addAtTail(int val)
{
LinkedNode *newTail = new LinkedNode(val);
LinkedNode *cur = _dummyHead;
while (cur->next != nullptr)
{
cur = cur->next;
}
cur->next = newTail;
newTail->next = nullptr;
_size++;
}
// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果index大于链表的长度,则返回空
// 如果index小于0,则在头部插入节点
void addAtIndex(int index, int val)
{
if (index > _size)
return;
if (index < 0)
index = 0;
LinkedNode *newNode = new LinkedNode(val);
LinkedNode *cur = _dummyHead;
while (index--)
{
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
void deleteAtIndex(int index)
{
if (index >= _size || index < 0)
{
return;
}
LinkedNode *cur = _dummyHead;
while (index--)
{
cur = cur->next;
}
LinkedNode *tmp = cur->next;
cur->next = cur->next->next;
// delete命令指示释放了tmp指针原本所指的那部分内存,
// 被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
// 如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
// 如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
delete tmp;
tmp = nullptr;
_size--;
}
private:
int _size;
LinkedNode* _dummyHead;
};
错误以及注意事项
- 这个很不熟,我还是对定义结构体,public/private非常不熟练。
206.反转链表
学习时间:
2023.12.10 19:00-20:14
还有一题没做,明天再找时间补一补吧,今天有点惆怅。