首先我们来了解C++中如何对链表进行定义:
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) {}
};
抛去方法不谈 我们发现链表的定义主要有两部分,第一部分为节点数值 val ,第二部分为节点指针 next
如何声明一个新的链表呢?
ListNode* cur=new ListNode(0);
利用第二个函数,当我们没有定义指针指向时,默认指向为空。
当然通过第三个函数,我们可以对指针指向进行声明。
LeetCode 203 移除链表元素
此题其实较为简单,适合入门学者练手,我们只需注意一个问题即可:
如果要删除的元素恰好在链表的头结点,该如何处理?
众所周知链表的删除是利用前一节点进行指针的断开实现删除,那么头结点该怎么删除呢?
有两种方法,一种是直接将头结点后移,并删除原节点,但这种情况需要单独分离讨论,略显复杂。
ListNode* temp=head;
head=head->next;
delete temp;
第二种办法,建立一个虚拟的头结点,使当前头结点变为正常节点。
ListNode* dummyHead =new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
后进行正常的删除即可。
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* temp=cur->next;
//指针指向下下个节点
cur->next=cur->next->next;
//清除要删除节点内存
delete temp;
}
else{
//如不需要删除,则向后移动
cur=cur->next;
}
}
//移交头结点
head = dummyHead->next;
//清空虚拟头结点内存
delete dummyHead;
return head;
}
};
接下来的这道题能很好的考察链表的基本操作。
class MyLinkedList {
public:
struct LinkedList {
int val;
LinkedList* next;
LinkedList(int val):val(val),next(nullptr){};
};
MyLinkedList() {
dummyHead = new LinkedList(0);
}
int get(int index) {
//首先判断index值是否违法
if(index < 0){
return -1;
}
//指向虚拟头结点
LinkedList* cur = dummyHead;
//注意index从0开始
for(int i = 0;i <= index;i++){
//如果遍历到末尾 说明index超限
if(cur->next == NULL){
return -1;
}
//合法则后移
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
//赋值给新节点
LinkedList* cur = new LinkedList(val);
//新节点指向头结点
cur->next = dummyHead->next;
//虚拟头结点左移
dummyHead->next = cur;
}
void addAtTail(int val) {
LinkedList* cur = dummyHead;
LinkedList* temp = new LinkedList(val);
while(cur->next != NULL){
cur = cur->next;
}
cur->next = temp;
}
void addAtIndex(int index, int val) {
if(index < 0){
index=0;
}
LinkedList* cur = dummyHead;
for(int i = 0;i < index;i++){
// 移动前先判断
if(cur->next == NULL){
return;
}
cur = cur->next;
}
LinkedList* temp = new LinkedList(val);
temp->next = cur->next;
cur->next = temp;
}
void deleteAtIndex(int index) {
if(index < 0){return;}
LinkedList* cur = dummyHead;
for(int i = 0;i < index;i++){
if(cur->next == NULL){
return;
}
cur = cur->next;
}
LinkedList* temp = cur->next;
//这里记得要再判断一次 因为最后一个元素后面并没有元素可以删
if(cur->next == NULL){
return;
}
else{ cur->next = cur->next->next;}
delete temp;
}
private:
LinkedList* dummyHead;
};
进阶之双指针:
链表逆序,看似远离简单,但如果方法不对,很容易陷入死循环。
抛开实际操作,逆序链表我们首先想到的办法就是:从第二个节点开始,让他指向前一个节点,不断循环。
那我们需要记住两个位置,一个是下一个要反指的节点,另一个是要被指向的节点。因此不难想到用两个指针保存它们的位置。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;
ListNode* slow = NULL;
ListNode* fast = cur;
ListNode* temp = new ListNode();
while(fast != NULL){
//如果fast移动至尾结点,只需反指即可,无需继续跳跃
if(fast->next == NULL){
fast->next = slow;
return fast;
}
//临时节点保存下一次fast节点要跳跃的位置
temp = fast->next;
//反指
fast->next = slow;
//移动slow指针
slow = fast;
//fast向后跳跃
fast = temp;
}
return fast;
}
};
笔者起初想法是定义两个指针: slow 、fast
slow指向头结点,fast指向头结点的下个节点。但这样在运行时报出了heap-use-after-free的错误,让笔者两眼一黑。
一个完全不理解的错误,在查询了多方资料后,发现核心是 对链表尾结点指向问题。
当尾结点没有指向时,计算机默认该链表并未建立完毕
所以笔者重新定义了 slow 和 fast 指针的初始位置,使slow指向null,fast指向头结点,这样在逆序的时候便自动完成了逆序后尾结点的指向问题。
另外注意fast移动至末尾时,无需再移动slow与跳跃fast。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* temp = new ListNode();
ListNode* middle = new ListNode();
middle->next=head;
ListNode* slow = head;
ListNode* cur = middle;
if(head == NULL || head->next == NULL){
return head;
}
ListNode* fast = head->next;
while(fast != NULL){
temp=fast->next;
fast->next = slow;
slow->next = temp;
middle->next = fast;
middle = slow;
slow = temp;
if(temp==NULL){
break;
}
fast=temp->next;
}
cur=cur->next;
return cur;
}
};
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* slow=head;
ListNode* fast=head;
ListNode* cur=head;
if(head->next == NULL){
return NULL;
}
for(int i = 0;i<n;i++){
fast = fast->next;
}
if(fast == NULL){
head = head->next;
return head;
}
while(fast->next != NULL){
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return cur;
}
};
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int i=0,j=0;
//建立临时节点用来计算链表长度
ListNode* cur=headA;
ListNode* cur1=headB;
while(cur!=NULL){
cur=cur->next;
i++;
}
while(cur1!=NULL){
cur1=cur1->next;
j++;
}
ListNode* a=headA;
ListNode* b=headB;
if(i>j){
int length=i-j;
while(length>0){
a=a->next;
length--;
}
}
else{
int length=j-i;
while(length>0){
b=b->next;
length--;
}
}
while(a!=b){
a=a->next;
b=b->next;
}
return a;
}
};
此题笔者最初犯了个错误,在遍历链表求长度时,使用原节点a,b进行遍历,导致最终ab均指向尾结点,在后面的操作涉及向后移动时便报出空指针异常,特纪录在此提醒自己,应设立临时节点指向a,b进行遍历,以免影响原节点位置。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while(fast != NULL && fast->next != NULL){
slow = slow->next;
fast = fast->next->next;
if(slow == fast){
ListNode* index1 = fast;
ListNode* temp = head;
while(temp != index1){
temp = temp->next;
index1 = index1->next;
}
return index1;
}
}
return NULL;
}
};