目录
- 移除链表元素
- 反转链表
- 链表的中间节点
- 倒数第k个节点
- 反转链表(初阶)
- 快慢指针法(进阶)
 
- 合并两个有序链表
- 链表分割
- 链表的回文结构
移除链表元素

思路:由题目可知我们需要在给定的一个链表中移除值为val的节点,这里需要注意的情况就是全是val的链表移除后为空链表和传过来的链表是空链表的情况,如果直接对不是val的节点进行连接,返回头结点会无法处理头结点也是val的情况,这里不妨我们用两个指针开始都设置NULL,用一个指针指向头结点开始判断,必须是从头结点开始遍历不等于val的节点才能连接,不然就不能连接。这时候就处理了两种情况
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    ListNode* NewHead = NULL;   //一开始就为空处理全是相同元素的情况和传过来是空链表的情况
    ListNode* NewTail = NULL;
    ListNode* pcur = head;
    while(pcur)
    {
        if(pcur->val != val)
        {
            if(NewHead == NULL)
            {
                NewHead = NewTail = pcur;
            }
            else
            {
                NewTail->next = pcur;
                NewTail = NewTail->next;
            }
           
        }
         pcur = pcur->next;
    }
    if(NewTail != NULL) //防止堆空链表和移除后是空链表的空指针解引用
    {
        NewTail->next = NULL;
    }
    return NewHead;
}
注意最后一个节点连接的时候,万一他的下一个节点还是val值,必须把他的next指针设置NULL;
反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

 思路:由题目可知,我们需要把给定的链表所以节点反转,并返回新的头结点。这里要注意链表的最后一个节点是一个NULL,所以我们反转的链表的尾节点也是一个NULL。反转无非就是改变next指针的指向即以下
- 定义三个指针n1,n2,n3.n1指向NULL(新尾节点),n2指向原头结点,n3指向第二个节点
- 把n2头节点的next指针指向n1,然后n1移到n2,n2移到n3,以此类推改变节点的next指针方向
- 直到n2指向NULL,循环结束,返回n1
注意:为什么定义三个指针:定义两个指针不行吗?不行,因为我们要先改变节点next指针的方向,但是如果直接改变了这个节点的next指针我们如何找到下一个节点呢?所以定义三个指针
n1:记录指针指向将要反转的结点反转后要指向的位置。
n2:记录指针指向将要反转的结点。
n3:记录指针指向将要反转的结点的下一个结点。

注意,这时这3个指针统一后移时,n3指针的后移将失败,因为n3后移前指向的是NULL,我们不能执行以下这句代码:
n3 = n3->next;
所以我们后移n3指针前需判断其是否为空。
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    if(head == NULL)
    {
        return NULL;
    }
    ListNode* n1 = NULL;
    ListNode* n2 = head;
    ListNode* n3 = head->next;
    while(n2)
    {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if(n3)
        {
            n3 = n3->next;
        }
        
    }
    return n1;
}
链表的中间节点
给你单链表的头结点 head ,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。(给定的链表是非空链表)
思路:快慢指针法,定义两个指针,两个都指向头节点,慢指针每次走一步,快指针每次走两步,最终返回的中间节点就是慢指针
注意:当fast指向空或者fast指针的next指针指向的节点为空的时候,就停止遍历返回慢指针

 
 偶数个有两个中间节点(如上面3,4),通常我们取的是第二个中间节点,也是为什么fast快指针也从头结点开始遍历。这样不用单独处理slow指向第一个中间节点的情况
 typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
    ListNode* slow = head;
    ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}
倒数第k个节点
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
反转链表(初阶)

 思路:倒数第二个节点,是不是就是反转链表后的第二个节点?所以我们结合前面的反转链表的三指针法,然后堆反转链表变量k次就可以找的倒数的第k个节点
typedef struct ListNode ListNode;
int kthToLast(struct ListNode* head, int k){
    ListNode* n1 = NULL;
    ListNode* n2 = head;
    ListNode* n3 = n2->next;
    while(n2)
    {
        n2->next = n1;
        n1 =n2;
        n2 = n3;
        if(n3)
        {
            n3 = n3->next;
        }
    }
    while(--k)
    {
        n1 = n1->next;
    }
    return n1->val;
}
快慢指针法(进阶)
如果改一下题目,不能改变原链表的结构并且k不一定有效(有可能大于总结点数)该如何做呢?这样反转链表就不适用了吧。这时候就又可以用我们的快慢指针了
思路:这里的"快慢指针"不是一个指针走得快,一个指针走的慢。这里我们是为了找倒数第k个节点。我们每个链表的最后一个节点都是NULL,那么从倒数第k个节点开始走k步是不是就是null呢?所以我们不妨先让快指针fast走k步,然后慢指针和快指针再同时向后移动,直到fast快指针指向null时slow慢指针就是倒数第k个节点
注意:如果fast在走k步时就提前遇到NULL,那么说明k大于总节点数。这时不存在倒数第k个节点。

 
 
合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

 
思路:创建一个新链表,对两个链表进行比较,那个链表的节点数据小,就插入到新链表中,直到其中两个链表其中一个为空停止(不存在同时为空的情况,数据一致也会有一个数据还没插入)。然后判断两个链表谁还没有为空,将不为空的链表尾插到新链表中。
注意:
- 两个链表为空直接返回NULL
- 其中一个链表为空返回不为空的链表
  
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;       //这样既可以处理其中一个为空,又可以处理同时为空的情况
    }
    ListNode* NewHead = NULL, *NewTail = NULL;
    ListNode* l1 = list1, *l2 = list2;
    while(l1 && l2)
    {
        if(l1 -> val < l2->val)
        {
            if(NewHead == NULL)
            {
                NewHead = NewTail = l1;
            }
            else
            {
                NewTail->next = l1;
                NewTail = NewTail->next;
            }
            l1 = l1->next;
        }
        else
        {
            if(NewHead == NULL)
            {
                NewHead = NewTail = l2;
            }
            else
            {
                NewTail->next = l2;
                NewTail = NewTail->next;
            }
             l2 = l2->next;
        }
    }
    if(l1)
    {
        NewTail->next = l1;
    }
    else
    {
        NewTail->next = l2;
    }
    return NewHead;
}
上面的链表结构我们是用一个空链表来存储,会导致插入头结点的时候总是会判断是不是NULL,导致代码冗余,这里我们不再使用空链表,使用带头的链表来存储,这样就直接可以在带头链表的下一个节点的位置插入。(为什么不在这个带头节点直接插入,这是动态开辟的空间,后面需要释放)


 typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;       //这样既可以处理其中一个为空,又可以处理同时为空的情况
    }
    ListNode* NewHead,*NewTail;
    NewHead = NewTail =(ListNode*)malloc(sizeof(ListNode));
    ListNode* l1 = list1, *l2 = list2;
    while(l1 && l2)
    {
        if(l1 -> val < l2->val)
        {
            NewTail->next = l1;
            NewTail = NewTail->next;
            l1 = l1->next;
        }
        else
        {
            NewTail->next = l2;
            NewTail = NewTail->next;
            l2 = l2->next;
        }
    }
    if(l1)
    {
        NewTail->next = l1;
    }
    else
    {
        NewTail->next = l2;
    }
    ListNode* ret = NewHead->next;
    free(NewHead);
    NewHead = NULL;
    return ret;
}
链表分割
描述
 现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
思路:由题目可知,我们需要把小于x的节点排在其他指点之前,不能改变原来的数据顺序
 不改变数据顺序:
 
所以这里不是让我们排升序
我们不妨创建两个链表,一个链表存储小于x的数据,一个链表存吃啥大于等于x的数据,然后把两个链表的首尾相连即可
1.把小于x的结点尾插到less链表,把大于x的结点尾插到greater链表
 
 2.将less链表与greater链表链接起来。
 
 注意:
 1.链接后的链表的最后一个结点的指针域需要置空,否则可能造成链表成环。
 2.返回的头指针应是lessHead->next,而不是lessHead。因为这是头节点
这是带头版本的做法
#include <cstddef>
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
        //创建两个非空链表,小链表(存储小于定值x)和大链表(存储大于等于定值x)
        ListNode* LessNewhead,*LessNewtail;
        ListNode* GreaterNewhead,*GreaterNewtail;
        LessNewhead=LessNewtail=(ListNode*)malloc(sizeof(ListNode));
        GreaterNewhead=GreaterNewtail=(ListNode*)malloc(sizeof(ListNode));
        if(LessNewhead==NULL && GreaterNewhead==NULL)
        {
            perror("malloc failed");
            exit(1);
        }
    ListNode* pcur=pHead;
    while(pcur)
    {
        if(pcur->val<x)
        {
            LessNewtail->next=pcur;
            LessNewtail=LessNewtail->next;
        }
        else
        {
            GreaterNewtail->next=pcur;
            GreaterNewtail=GreaterNewtail->next;
        }
        pcur=pcur->next;
    }
    //将小链表的尾连接到大链表的头
    LessNewtail->next=GreaterNewhead->next; //这里nGext是因为大链表的头还是指向第一个无效空间
    GreaterNewtail->next=NULL;      //一定要把大链表的下一个节点设置NULL,因为大链表村上的是大于等于x的节点,若其中一个节点的next指针指向小链表节点,则会死循环形成环,如 5 1 3 6 2,其中小链表:1 2,大链表:5 3 6,因为6的下一个节点是2,下一次循环又会指向小链表的节点2,导致 2 5 3 6,2 5 3 6....死循环
    ListNode* ret=LessNewhead->next;
    free(LessNewhead);
    free(GreaterNewhead);
    LessNewhead=NULL;
    GreaterNewhead=NULL;
    return ret;
    }
但是不带头版本又多了一个判断
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
#include <cmath>
#include <functional>
#include <sys/ucontext.h>
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
       ListNode* LessHead = NULL, *LessTail = NULL;
       ListNode* GreaterHead = NULL, *GreaterTail = NULL;
       ListNode* pcur = pHead;
        while (pcur) {
            if(pcur->val < x)
            {
                if(LessHead == NULL)
                {
                    LessHead = LessTail = pcur;
                }
                else 
                {
                    LessTail->next = pcur;
                    LessTail = LessTail->next;
                }
            }
            else 
            {
                if(GreaterHead == NULL)
                {
                    GreaterHead = GreaterTail =pcur;
                }
                else 
                {
                   GreaterTail->next = pcur;
                   GreaterTail = GreaterTail->next;
                }
            }
            pcur = pcur->next;
        }
         if (GreaterTail) {
            GreaterTail->next = nullptr;
        }
        if (LessTail) {
            LessTail->next = GreaterHead;
            return LessHead;
        } else {
            return GreaterHead;
        }
    }
  
};

 为什么要判断连接的链表为空呢,因为题目也没说给的数据全部比x大或者全部比x小呀
 例如:4 4 4 4 ,x=3
 如果直接对LessTail解引用此时Less这个链表为空,就是对空指针解引用
 
 有的人就要问了,那为啥带头的不判断,带头本身就有一个头结点不存在链表为null的情况,也就不会对null解引用
 
这里还是推荐带头的写法,会清除对头结点插入判断的代码冗余
链表的回文结构

 我们需要找到传入链表的中间结点,并将中间结点及其后面结点进行反转,然后再将原链表的前半部分与反转后的后半部分进行比较,若相同,则该链表是回文结构,否则,不是回文结构。
 1.找中间节点并返回
 
 2.对中间节点及以后得节点进行反转
 
 3.比较链表的前半部分与后半部分的结点值,若相同则是回文结构,否则,不是回文结构。
 
 注意:就算传入的链表是结点数为奇数的回文结构,该思路也可以成功判断。
 例如,以下链表反转其后半部分后,我们看似链表应该是这样的。
 
 但反转后的链表并不是这样的,而应该是下面这样:
因为我们反转的是中间结点及其后面的结点,并没有对前面的结点进行任何操作,所以结点5所指向的结点应该还是结点7。
 
于是该链表的比较过程应该是这样的:1等于1,3等于3,5等于5,7等于7,然后RHead指针指向NULL。所以判断该链表是回文结构。
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
#include <list>
class PalindromeList {
public:
    ListNode* FIndMiddle(ListNode* phead)
    {
        ListNode* slow = phead;
        ListNode* fast = phead;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
    ListNode* ReverseList(ListNode* phead)
    {
        ListNode* n1 = NULL;
        ListNode* n2 = phead;
        ListNode* n3 = n2->next;
        while(n2)
        {
            n2->next = n1;
            n1 = n2;
            n2 = n3;
            if(n3)
            {
                n3 = n3->next;
            }
        }
        return n1;
    }
    bool chkPalindrome(ListNode* A) {
        // write code here
        //找到中间节点
        ListNode* mid = FIndMiddle(A);
        //将中间节点之后反转
        ListNode* right = ReverseList(mid);
        ListNode* pcur = A;
        while(right)
        {
            if(right->val != pcur->val)
            {
                return false;
            }
            right = right->next;
            pcur = pcur->next;
        }
        return true;
    }
};



















