算法训练营Day3
链表理论基础,203.移除链表元素,707.设计链表,206.反转链表
题目
203.移除链表元素
题目链接:https://leetcode.cn/problems/remove-linked-list-elements/
文章讲解:https://programmercarl.com/0203.%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.html
思路
链表基础题
迭代:使用虚拟头节点,这样可以不用额外考虑删除头节点的情况(把头节点视作普通节点),假设有三个节点,那么删除第二个节点时需要让第一个节点的next指向第三个节点,所以指针应该指向第一个节点才能完成这个操作,所以每次判断p->next是否需要删除,如果p->next 为NULL则表示已经遍历完成(最后一个节点在p还没有指向该节点之前已经完成了判断)
刚开始犯了一个错误,再每次判断是否为val之后都将指针移动一位,没有考虑到删除节点之后仍然需要判断这个节点的值是否为val(已经是新节点)
递归:自己没有想出来,看了题解
递归函数返回的是不包含val值的链表的头节点,递归的终止条件是 head 为空,先判断除了头节点之外的值是否为val,最后判断头节点
代码
//迭代
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *dummy = new ListNode(0, head); //虚拟头节点
ListNode *p = dummy;
while(p -> next != nullptr){
if(p -> next -> val == val) p -> next = p -> next -> next;
else p = p -> next;
}
return dummy -> next; //返回头节点
}
};
//递归
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if(head == nullptr) return head;
head -> next = removeElements(head -> next, val);
return head -> val == val? head -> next : head;
}
};
707.设计链表
题目链接:https://leetcode.cn/problems/design-linked-list/
文章讲解:https://programmercarl.com/0707.%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.html
思路
链表模拟题
最容易忘记的点是size没有及时更新,size++,size–
链表初始化时需要生成虚拟头节点方便后续处理,要注意index的范围,插入和删除时,cur指针要指向插入和删除位置的前一个节点,可以从下标为0的位置来思考cur初始指向dummy还是dummy->next
代码
class MyLinkedList {
private:
int size;
ListNode* dummy;
public:
MyLinkedList() {
size = 0;
dummy = new ListNode(0, nullptr);
}
void printList() {
ListNode* p = dummy->next;
while (p != nullptr) {
cout << p->val << ' ';
p = p->next;
}
cout << endl;
}
int get(int index) {
if (index < 0 || index > size - 1)
return -1;
// 以下情况index一定在[0,size - 1]范围内
ListNode* cur = dummy->next; // 考虑到第0位不用移动
while (index--) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) { addAtIndex(0, val); }
void addAtTail(int val) { addAtIndex(size, val); }
void addAtIndex(int index, int val) {
// 考虑需要指向要插入位置的前一个节点
if (index > size)
return;
else if (index < 0)
index = 0;
ListNode* cur = dummy;
while (index--) {
cur = cur -> next;
}
ListNode* newNode = new ListNode(val, cur-> next);
cur -> next = newNode;
size ++;
}
void deleteAtIndex(int index) {
if (index < 0 || index > size - 1)
return;
ListNode* cur = dummy;
while (index --) {
cur = cur->next;
}
cur -> next = cur -> next -> next;
size --;
}
};
206.反转链表
题目链接:https://leetcode.cn/problems/reverse-linked-list/
文章讲解:https://programmercarl.com/0206.%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.html
思路
画图理解
双指针:从前往后遍历,让后一个节点指向前一个节点 ,pre从空节点开始,cur从第一个节点开始,当cur为空时遍历结束;注意让虚拟头指针不断指向新的头节点,还又一个易错点是cur=temp,不能cur=cur->next,因为next已经被修改过了
递归:函数返回的是链表反转后的头节点,迭代可以从一直压栈到栈底之后弹栈来思考
代码
//双指针
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *dummy = new ListNode(0, head);
ListNode *pre = nullptr, *cur = head;
//每次操作是让cur指向pre,所以要保证cur不为NULL
while(cur != nullptr){
ListNode *temp = cur -> next; //保存cur的下一个节点,防止丢失
cur -> next = pre;
dummy -> next = cur; //dummy指向第一个节点,不断更新
pre = cur;
cur = temp; //这里不能cur=cur->next,因为next已经修改过了
}
return dummy -> next;
}
};
//递归
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head == nullptr || head -> next == nullptr) //可能链表为空,所以有head==nullptr的情况
return head;
//栈到最后一个节点,会返回最后一个节点,然后弹栈到倒数第二个节点,让倒数第二个节点实现反转链表,以此类推
ListNode *newHead = reverseList(head -> next);
head -> next -> next = head; //实现反转链表,后一个节点指向前一个节点
head -> next = nullptr;
return newHead;
}
};