今天开始关于链表的题目:
第一题、LeetCode 203 删除链表中的元素https://leetcode.cn/problems/remove-linked-list-elements/
链表中不能用头节点来遍历,否则头结点的指针一直在变,最后无法返回头节点,需要定义一个临时的指针来遍历整个列表。
方法一是直接删除。此方法需要判断是否是头节点,其次要注意在判断中用while而不是if,因为删除一个头节点会出现一个新的头节点。
此外,我们在删除时不是直接对cur进行操作,而是对cur的下一个节点进行操作,因为我们需要把删除节点的前后节点连起来,cur为需删除结点的话在单链表中找不到其上一个节点。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while(head != NULL && head->val == val){ //这里使用while是因为每一个头节点被删除之后,
//下一个节点就成为新的头节点,需要循环遍历。判断非空是因为操作空指针会报错、
ListNode *tmp = head;
head = head->next;
delete tmp;
}
ListNode *cur = head;
while(cur != NULL && cur->next != NULL){
if(cur->next->val == val){
ListNode *tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else{
cur = cur->next;
}
}
return head;
}
};
第二种方法是设置虚拟头节点,这样不需要判断是否为头节点。只需要最后把链表的头节点设置回去,并删除虚拟头节点。
注意不要直接return head,因为原来的head可能已经被删了。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *dummyhead = new ListNode(0);
dummyhead->next = head;
ListNode *cur = dummyhead;
while(cur->next != NULL){ //要删除的是肯定不会是虚拟头节点,所以从其下一个开始判断是否为空
if(cur->next->val == val){
ListNode *tmp = cur->next;
//dummyhead->next = cur->next->next;
//这里是cur在更新
cur->next = cur->next->next;
delete tmp;
}
else{
cur = cur->next;
}
}
//return dummyhead->next;
head = dummyhead->next;
delete dummyhead;
return head;
}
};
第二题,LeetCode707 设计链表 https://leetcode.cn/problems/design-linked-list/
设计一个链表需要注意的点比较多,C++中需要注意private里面的size和虚拟链表头。
虚拟表头的妙处:虚拟头节点让你的cur等于需要删除结点的前一个节点,使得删除的操作更加简单。
注意的点:
1、获得第n个几点的值,注意判断index时边界的问题。
2、加表头:要先设置加入节点的next,否则就找不到next了(先设置前一个结点的next的话)
3、加表尾:定义一个新节点时,默认其next是NULL。
4、在第index个节点前加:需要遍历到其前一个节点(体现了虚拟表头的妙处),并同时注意2。
5、删除节点:体现虚拟表头的妙处。
class MyLinkedList {
public:
struct LinkedNode{
int val;
LinkedNode *next;
LinkedNode(int val):val(val), next(nullptr){};
};
MyLinkedList() {
dummyhead = new LinkedNode(0);//和private相对应
_size = 0;
}
int get(int index) {
LinkedNode *cur = dummyhead->next;
if(index >= 0 && index <= (_size-1)){//注意边界条件
while(index--){
cur = cur->next;
}
return cur->val;
}
else{
return -1;
}
// LinkedNode* cur = dummyhead->next;
// if (index > (_size - 1) || index < 0) {
// return -1;
// }
// while(index--){ // 如果--index 就会陷入死循环
// cur = cur->next;
// }
// return cur->val;
}
void addAtHead(int val) {
LinkedNode *newnode = new LinkedNode(val);
//dummyhead->next = newnode;要先设置新加入节点的next,否则就找不到dummyhead的next了
newnode->next = dummyhead->next;
dummyhead->next = newnode;
_size++;
}
void addAtTail(int val) {
//需要让遍历的节点是目前链表的尾巴
LinkedNode *newnode = new LinkedNode(val);
//在定义newnode的时候,默认其next指向NULL
LinkedNode *cur = dummyhead;
while(cur->next != NULL){
cur = cur->next;
}
cur->next = newnode;
_size++;
}
void addAtIndex(int index, int val) {
// if (index >= _size || index < 0) {
// return;
// }
//注意题目要求
if(index > _size) return;
if(index < 0) index = 0;
//需要遍历到index前一个的节点,才能在两个节点之间插入一个节点
LinkedNode *newnode = new LinkedNode(val);
LinkedNode *cur = dummyhead;
while(index){
cur = cur->next;
index--;
}
//插入节点都同理,先更新需插入的next
newnode->next = cur->next;
cur->next = newnode;
_size++;
}
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkedNode *cur = dummyhead;
//这里就体现了虚拟头节点的妙处,虚拟头节点让你的cur等于需要删除结点的前一个节点,
//是的删除的操作更加简单。
while(index){
cur = cur->next;
index--;
}
LinkedNode *tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
_size--;
}
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);
*/
第三题、LeetCode206 反转链表https://leetcode.cn/problems/reverse-linked-list/
反转一个链表,用双指针法的思路是设置pre作为目前表头的前一个节点,并为NULL,cur设为表头,依次遍历下去
注意:遍历时更新各个变量的次序,以及while循环里的判断。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//双指针法
ListNode *tmp;
ListNode *cur = head;
ListNode *pre = NULL;
while(cur){
//cur在最后会指向尾节点next的null,以此来判断
tmp = cur->next; //先存cur->next,否则待会找不到
cur->next = pre;//再反转
pre = cur;//再移动pre,否则cur变了
cur = tmp;//最后移动cur
}
return pre;
}
};
递归方法: 先熟练掌握双指针法后,才能更好地理解递归法。
定义一个reverse函数,反转()前后两个参数。
可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步pre = cur;cur = temp;
class Solution {
public:
ListNode* reverse(ListNode* pre, ListNode* cur){
if(cur == NULL) return pre;
ListNode* tmp = cur->next;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
cur->next = pre;
return reverse(cur,tmp);
}
ListNode* reverseList(ListNode* head) {
return reverse(NULL,head);
// 和双指针法初始化是一样的逻辑,ListNode* cur = head;ListNode* pre = NULL;
}
};
最后调用reverse返回时注意和双指针法初始化是一样的逻辑辑ListNode* cur = head;ListNode* pre = NULL;