1-学习的文章和视频链接
1.1 203移除链表元素-代码随想录
1.2 203移除链表元素-卡哥B站视频讲解
1.3 707设计链表-代码随想录
1.4 707设计链表-卡哥B站视频讲解
1.5 206反转链表-代码随想录
1.6 206反转链表-卡哥B站视频讲解
2-题目
203.移除链表元素
<1> 使用原链表
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 删除头节点
// 如果链表中,从头结点开始每个节点的值都等于val,
// 删除头节点即可,这也是使用while不使用if的原因
// 链表不能为空 且 头节点的值等于val
while(head!=NULL && head->val==val){
ListNode *tmp = head; // 临时节点等于头节点,用来存储等于val的节点
head = head->next; // 移动:头节点等于当前头节点的下一个节点
delete tmp; // 删除临时节点
}
// 删除非头节点
ListNode *cur = head; // 现节点等于头节点,用来移动
// 链表不能为空 且 现节点的下一节点不能指向空
while(cur!=NULL && cur->next!=NULL){
if(cur->next->val==val){ // 现节点的下一节点的值等于val
// 临时节点等于现节点,用于存储等于val的现节点的下一节点
ListNode *tmp = cur->next;
cur->next = cur->next->next; // 现节点的下一节点等于现节点的下下节点
delete tmp;
}
else // 现节点的下一节点的值与val不等
cur = cur->next; // 现节点等于下一节点
}
return head; // 返回链表
}
};
<2> 使用虚拟头节点
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 定义一个虚拟头节点,就不用考虑头节点和非头节点的状况了
ListNode *dummyNode = new ListNode(0);
dummyNode->next = head; // 虚拟头节点指向原头节点,表示新链表
ListNode *cur = dummyNode; // 定义现节点,等于虚拟节点,用来滑动
// 现节点的下一节点不为空
while(cur->next!=NULL){
if(cur->next->val==val){ // 当现节点的下一节点的值等于val
// 定义一个临时节点等于现节点的下一节点,存储相等值的节点
ListNode *tmp = cur->next;
cur->next = cur->next->next; // 连接到下下节点
delete tmp; // 删除临时节点
}
else{ // 值不相等,跳过
cur = cur->next; // 滑动到下一节点
}
}
head = dummyNode->next; // 返回新链表
delete dummyNode; // 删除虚拟节点
return head; // 返回链表
}
};
- 思路:分为使用原链表和虚拟头节点两种方法,主要是记得定义临时节点和现节点,临时节点保存值相等的节点,现节点用来滑动,指向值相等节点的上一个节点。
- 实现难点:使用原链表时,删除头节点的循环容易写成条件;现节点容易指错,应该指向变动的上一个节点;别忘记在 循环条件中写上链表非空且下一节点非空。
- 收获:删除元素也不容易,临时节点和现节点的定义很重要。
707.设计链表
class MyLinkedList {
public:
// 定义单链表结构体
struct LinkedNode{
int val; // 节点的值
LinkedNode* next; // 节点指针
LinkedNode(int val): val(val), next(nullptr){}
};
// 初始化单链表
MyLinkedList() {
_dummyHead = new LinkedNode(0); // 初始化虚拟节点,不是真头节点
_size = 0; // 初始化链表长度
}
// 获取第index个节点的值
int get(int index) {
// 判断索引合法与否
if(index>=_size || index<0){
return -1;
}
// 用要取值的节点作为现节点即可,因为无需考虑指针变换操作
LinkedNode* cur = _dummyHead->next; // 滑动节点
while(index--){ // 先判断index,再减
cur = cur->next; // 一个节点一个节点移动
}
return cur->val; // 返回现节点的值
}
// 插入新节点为头节点
void addAtHead(int val) {
LinkedNode* newHead = new LinkedNode(val); // 定义新节点
// 新节点指向虚拟头节点的下一节点
newHead->next = _dummyHead->next;
// 虚拟头节点指向新节点,新节点成为头节点
_dummyHead->next = newHead;
_size++; // 记得插入后长度加1
}
// 插入新节点为尾节点
void addAtTail(int val) {
LinkedNode* newTail = new LinkedNode(val); // 定义新节点
LinkedNode* cur = _dummyHead; // 定义现节点
while(cur->next!=nullptr){ // 循环当现节点的下一节点不为空
cur = cur->next; // 滑动现节点至下一个节点
}
cur->next = newTail; // 循环到现节点为最后一个节点,插入新节点
_size++; // 链表长度加1
}
// 在第index个节点前添加值为val的新节点
void addAtIndex(int index, int val) {
if(index>_size){
return;
}
if(index<0){
index=0;
}
LinkedNode* newNode = new LinkedNode(val); // 定义新节点
LinkedNode* cur = _dummyHead; // 定义现节点
// 遍历现节点至index
while(index--){
cur = cur->next;
}
newNode->next = cur->next; // 新节点指向现节点的下一节点
cur->next = newNode; // 现节点指向新节点
_size++; // 链表长度加1
}
// 删除第index个节点
void deleteAtIndex(int index) {
if(index>=_size || index<0){ // index合法性检查
return;
}
LinkedNode* cur = _dummyHead; // 定义现节点
// 滑动节点至index
while(index--){
cur = cur->next;
}
LinkedNode* tmp = new LinkedNode(0); // 定义临时节点
tmp = cur->next; // 临时节点存储现节点的下一节点
// 断开现节点和下一节点,而和下下节点连接
cur->next = cur->next->next;
delete tmp; // 删除临时节点
_size--; // 链表长度减1
}
// 打印链表
void printLinkedNode(){
LinkedNode* cur = _dummyHead; // 定义现节点
while(cur->next!=nullptr){ // 当现节点的下一节点不为空
cout << cur->next->val << " "; // 输出现节点的下一节点和空格
cur = cur->next; // 滑动到下一个
}
cout << endl;
}
private:
int _size; // 定义链表长度
LinkedNode* _dummyHead; // 定义虚拟头节点
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
- 思路:首先要定义链表的结构,有其中值和指向下一个节点的指针,两部分组成。获取第index节点的值,每个节点判断时候为index,然后输出。插入新节点在头位置时,新节点指针先指向原头节点,然后把虚拟头节点指向新节点。插入新节点为尾节点时,滑动不是index判断,而是现节点的下一节点的指针是否指向空。在第index个节点前插入新节点时,判断index的合法性,滑动每个现节点直到找到第index节点,新节点指向下一节点,现节点指向新节点。在删除第index个节点时,首先也要判断index的合法性,找到第index个节点,要定义临时节点去储存要删除的节点,下节点指向下下节点,最后要删除临时节点。
- 实现难点:定义链表长度和虚拟头节点会暴露在初始化;添加或删除节点时,最后一定要变更链表长度;记得现节点指向的是哪个节点。
- 收获:熟练使用节点的遍历;清楚知道了滑动的现节点应该指向前节点还是本身;熟悉了结构体定义;删除或添加节点的时候,链表长度记得变更。
206.反转链表
<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) {
ListNode* tmp; // 定义临时节点
ListNode* pre = nullptr; // 定义一个前节点,以便节点反转指向这个前节点
ListNode* cur = head; // 定义现节点,从头节开始依次反转
while(cur){ // cur直到指向空,退出循
tmp = cur->next; // 临时节点保存现节点的原下一节点,因为反转后会丢失连接
cur->next = pre; // 现节点指向定义的前节点
pre = cur; // 前节点移动到现节点位置(向原链表方向移动一位)
cur = tmp; // 现节点移动到临时节点位置(向原链表方向移动一位)
}
delete tmp; // 删除临时节点
return pre; // 打印反向链表,注意现节点已经指向空,不能作为头节点表示
}
};
<2> 递归法(双指针思路)
class Solution {
public:
ListNode* reverse(ListNode* cur, ListNode* pre){
if(cur==nullptr) return pre; // 如果现节点指向空,则返回新链表, 新头节点pre
ListNode* tmp = cur->next; // 定义临时节点,保存现节点的下一节点
cur->next = pre; // 反向操作,现节点指向前节点
// 递归,前节点先移动到现节点,现节点再移动到临时节点
// pre = cur;
// cur = tmp;
return reverse(tmp, cur);
}
ListNode* reverseList(ListNode* head) {
// 递归,初始化,现节点指向头节点,前节点指向空
// cur = head;
// pre = nullptr;
return reverse(head, nullptr);
}
};
<3> 递归法(从后往前翻转)
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 边界条件判断,当链表未空或链表中只有1个节点时,返回的都是链表
if(head==nullptr || head->next==nullptr) return head;
ListNode* last = reverseList(head->next); // 递归,从第二个节点开始依次反转
head->next->next = head; // 反转指向
head->next = nullptr; // 原头指为空
return last; // 返回原链表的尾节点
}
};
- 思路:双指针法直接定义一前一后2个节点,还要再定义一个临时节点保存后一节点反向时,下一节点丢失的位置,不断移动前后节点,实现反向。用双指针思想的递归方法中,判断cur和pre的位置。从后往前翻转有点难理解,当就是依次链表反转。
- 实现难点:双指针法中pre和cur节点谁先移动,是有顺序的;双指针思想的递归方法中,cur和pre初始化的值,递归过程中的值,顺序不要搞错;
- 收获:递归方法很快但是双指针方法更清晰。