203.移除链表元素
题目链接/文章讲解/视频讲解:: 代码随想录
ListNode* removeElements(ListNode* head, int val) {
ListNode* sentinel = new ListNode(-1,head);
ListNode* p1 = sentinel;
while(p1->next != nullptr) {
if (p1->next->val != val)
p1 = p1->next;
else {
p1->next = p1->next->next;
}
}
return sentinel->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 > _size-1 || index < 0)
return -1;
else {
LinkedNode* p1 = _dummyHead;
for (int i = 0; i <= index; i++){
p1 = p1->next;
}
return p1->val;
}
}
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
if (_dummyHead->next != nullptr) {
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
}
else {
_dummyHead->next = newNode;
}
_size++;
}
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* p1 = _dummyHead;
while (p1->next != nullptr)
p1 = p1->next;
p1->next = newNode;
_size++;
}
void addAtIndex(int index, int val) {
if (index > _size || index < 0)
return;
else {
LinkedNode* p1 = _dummyHead;
for (int i = 0; i < index; i++) {
p1 = p1->next;
}
LinkedNode* newNode = new LinkedNode(val);
newNode->next = p1->next;
p1->next = newNode;
_size++;
}
}
void deleteAtIndex(int index) {
if (index >= _size || index < 0)
return;
else {
LinkedNode* p1 = _dummyHead;
for (int i = 0; i < index; i++) {
p1 = p1->next;
}
if (p1->next->next != nullptr)
p1->next = p1->next->next;
else
p1->next = nullptr;
_size--;
}
}
private:
int _size;
LinkedNode* _dummyHead;
};
这题是一道较为综合的题目, 考虑了链表的各种操作,从初始化到插入和删除。我采用了虚拟头结点单向链表做法,要注意的就是注意判断空指针,不要让程序访问到空指针内,以免造成未知的错误。在遍历链表的时候应该创建一个新的结点指向虚拟头结点再进行遍历,这样就不会丢失原有的头结点。
示例代码中采用了如下代码来遍历链表,较为简洁:
while(index--) {
cur = cur ->next;
}
同时在删除链表节点时,我并没有释放内存空间,但在C++中删除节点后应该释放内存空间已保证程序的安全性。示例代码如下:
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
//delete命令指示释放了tmp指针原本所指的那部分内存,
//被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
//如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
206.反转链表
题目链接/文章讲解/视频讲解: 代码随想录
ListNode* reverseList(ListNode* head) {
ListNode* temp; // 保存cur的下一个节点
ListNode* cur = head;
ListNode* pre = NULL;
while (cur) {
temp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur->next = pre; // 翻转操作
// 更新pre 和 cur指针
pre = cur;
cur = temp;
}
return pre;
}
使用双指针方法,cur指针指向当前节点,pre指针指向当前节点的前一个节点,也就是当前节点要将next转向的那个节点,然后使用temp来记录cur该指向的下一个结点。
class Solution {
public:
ListNode* reverse(ListNode* pre,ListNode* cur){
if(cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
return reverse(cur,temp);
}
ListNode* reverseList(ListNode* head) {
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(NULL, head);
}
};
上面是示例代码的递归方法,思路和双指针方式其实是一样的,只不过将更新pre和cur指针的工作交给了递归来处理,而不是利用循环来解决。