203.移除链表元素
题目链接/文章讲解/视频讲解:: 代码随想录
解题思路
很容易想到,只需要找到删掉的节点的前一个节点的next指向删掉节点的next,即可,但这里会有问题,如果删掉的是头结点又该处理呢?
1.对原始链表进行操作
class Solution {
public:
ListNode* removeElements(ListNode* head, int val){
while(head!=nullptr && head->val==val) //判断头节点,首先不能为空指针
{
ListNode* tmp;
tmp = head; //定义一个tmp节点用于清理内存
head = head ->next;
delete tmp;
}
ListNode* cur = head; //定义一个cur,记录判断节点的上一个节点
while(cur!=nullptr&&cur->next!=nullptr) //本身不能为空指针,同时他的下一个也不能为空指针
{
ListNode* tmp;
if(cur->next->val ==val){
tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else{
cur = cur->next;
}
}
return head;
}
};
这里我比较常犯的错误是,没有判断需要操作的指针是不是空指针,对原始链表直接进行操作并不能统一操作,需要分成两部分
2.虚拟头结点
class Solution {
public:
ListNode* removeElements(ListNode* head, int val){
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head; //定义一个虚拟头结点
ListNode* cur = dummyHead; //从虚拟头结点的下一个进行判断
while(cur->next!=nullptr)
{
if(cur->next->val == val)
{
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp; //使用c++需要手动清理内存
}
else{
cur= cur->next;
}
}
head = dummyHead->next; //虚拟头结点的下一个才是真的头结点,赋值后清理内存
delete dummyHead;
return head;
}
};
总结
使用虚拟头结点可以统一对链表的操作,但同时不要忘记判断操作的指针是否为空指针,最后使用c++的话需要手动清理内存
707.设计链表
题目链接/文章讲解/视频讲解:代码随想录
解题思路
对链表的基础操作,增删改操作,熟记即可,使用虚拟头结点会有许多的方便之处
class MyLinkedList {
public:
struct LinkedNode
{
int val;
LinkedNode* next;
};
MyLinkedList() {
dummyHead = new LinkedNode();
dummyHead->val = 0;
dummyHead->next = nullptr;
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* head = new LinkedNode();
head->val = val;
head->next = dummyHead->next;
dummyHead->next = head;
size++;
}
void addAtTail(int val) {
LinkedNode* tail = new LinkedNode();
tail->val = val;
tail->next =nullptr;
LinkedNode* cur = dummyHead;
while(cur->next!=nullptr)
{
cur= cur->next;
}
cur->next = tail;
size++;
}
void addAtIndex(int index, int val) {
if(index<0 || index >size)
return ;
LinkedNode* newNode = new LinkedNode();
newNode->val = val;
newNode->next =nullptr;
LinkedNode* cur = dummyHead;
while(index--)
{
cur=cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
size++;
}
void deleteAtIndex(int index) {
if(index<0 || index >size-1)
return ;
LinkedNode* cur = dummyHead;
LinkedNode* tmp;
while(index--)
{
cur = cur->next;
}
tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
size--;
}
private:
int size;
LinkedNode* dummyHead;
};
总结
这里比较妙的是在用index的函数中,利用index来直接记录要循环移动的次数,当cur为虚拟头结点和头结点时,分别是index前一个节点和index节点,这里比较讨巧,其次是对于c++的构造函数等不太熟悉,熟记便可
/**
* 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) {}
* };
*/
206.反转链表
题目链接/文章讲解/视频讲解:代码随想录
解题思路
1.双指针法
用卡哥的一张图来形象化表示一下,当我们翻转指针时,如何继续cur要去的地方呢?利用一个temp作临时指针存储一下即可
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = nullptr; //定义前指针
ListNode* cur = head; //定义现在的指针
ListNode* temp; //定义好一个临时指针存储变量
while(cur!=nullptr) //当cur指向null的时候就已经翻转完成了
{
temp = cur->next; //先存放cur的下一个指针
cur->next = pre; //翻转链表
pre = cur; //将链表前移
cur =temp;
}
delete temp; //删除临时指针
return pre;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
2.递归法
递归法其实就是根据这个双指针法提取出来的,代码更为简洁
class Solution {
public:
ListNode* reverse(ListNode* pre, ListNode* cur) {
if (cur == nullptr)
return pre; //到最后一个就返回头指针
ListNode* temp = cur->next; //记录的临时指针
cur->next = pre;
return reverse(cur, temp); //递归操作
}
ListNode* reverseList(ListNode* head) { return reverse(nullptr, head); }
};
时间复杂度:O(n)
空间复杂度:O(n) 用了n个堆栈空间
总结
这题比较关键的一个点就是要想到利用双指针进行一个操作,从前往后改变指针的方向,并利用临时指针记录下一个结点的位置
收获
今天的题目还是比较好理解和简单的,压力不大。明天继续努力