1.移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };

OJ链接 来源:力扣(LeetCode) 图片.png

我们可以先画图,画图是做题的最好办法 定义一个变量用来遍历整个数组,如果找到值val,就将这个值删除,删除之后,将链表重新链接。我们需要保存cur的前一个节点地址位置,这样可以保存删除之后新节点位置。当cur遍历完整个数组之后,结束循环。返回新节点。 图片.png

但是还有一种情况,如果我们找到第一个节点就是val,无法找到cur的前一个并链接,所以,我们将head指向cur的下一个节点,并将当前节点释放掉

struct ListNode* removeElements(struct ListNode* head, int val){
    struct ListNode* cur=head;
    struct ListNode* prev=NULL;
    while(cur)
    {
        if(cur->val==val)//找到val
        {
            if(head->val==val)//头指针是val的情况
            {
                head=cur->next;//头先指向当前指针的下一个
                free(cur);//再将节点释放掉,为什么是cur,因为定义节点的时候,cur等于head,不能释放head,它用来记录新的节点并返回
                cur=head;//继续将cur指向链表
            }
            else//头结点不是val情况
            {
                prev->next=cur->next;//先将前一个节点的next指向新节点
                free(cur);//再将cur释放
                cur=prev->next;//新节点地址给到cur,继续遍历
            } 
        }
        else//没找到val
        {
            prev=cur;//prev记录当前指针
            cur=cur->next;//cur继续遍历
        }
    }
    return head;
}

如果我们在力扣上的题出现编译错误,我们可以通过vs来调试,当然,这是一种调试方法。

int main()
{
	// 方便快速调试oj代码
	struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
	struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));
	struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));
	struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));
	n1->val = 7;
	n2->val = 7;
	n3->val = 7;
	n4->val = 7;
	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = NULL;

	struct ListNode* head = removeElements(n1, 7);
	return 0;
}

2.反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };

OJ链接 如果想反转链表,只要将当前指针指向他的前一个节点,最后返回新的头。可以用定义三个新节点,一个用来记录前一个节点(n1),一个指向当前节点(n2),一个记录下一个节点(n3)。当n2节点指向空,结束循环。

图片.png

struct ListNode* reverseList(struct ListNode* head){
	struct ListNode* n1=NULL;
	struct ListNode* n2=head;
	struct ListNode* n3=n2->next;
	
	while(n2)
	{
		n2->next=n1;//n2指向新节点/前一个结点
		n1=n2;//反转节点后,更新节点,继续反转下一个
		n2=n3;
		if(n3)//这里要判断一下,当n2是最后一个节点时,n3已经为NULL,而NULL没有next,当n2为最后一个节点,就不执行这个语句
			n3=n3->next;
	}
	head=n1;
	return head;//返回新的头结点
}

3.链表的中间结点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。 如果有两个中间结点,则返回第二个中间结点。

struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };

OJ链接 这里我们可以用一个快慢指针,快指针一次走两步,慢指针一次走一步,这样快指针的路程是慢指针的两倍。如果是奇数个节点,当快指针fast走到NULL时,慢指针就是中间节点;当节点为偶数个,fast走到最后一个,这时slow节点为中间节点的第二个,fast->next为NULL.

struct ListNode* middleNode(struct ListNode* head){
    struct ListNode* slow,*fast;
    slow=fast=head;

    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
    }
    return slow;
}

4.链表中倒数第k个结

输入一个链表,输出该链表中倒数第k个结点

struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };

图片.png 这道题我们仍旧可以用快慢指针,思路是,先让快指针走k步,也就是倒数第k个数字的路数。再让快慢指针同时走,当快指针为NULL,slow就是链表中倒数第k个数字。这道题我们并不能从最后一个数字向前遍历,找到前一个节点,这样会非常麻烦。还有一种用例测试情况,如果数组只有五个数字,但是我们输入了k为6,这时fast先走k步会越界,所以我们判断一下,fast是否为NULL.

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
	struct ListNode* slow=pListHead;
	struct ListNode* fast=pListHead;
	
	while(k--)
	{
		if(fast==NULL)
		{
			return NULL;
		}
		else
		{
			fast=fast-next;
		}
	}
	while(fast)
	{
		slow=slow->next;
		fast=fast->next;
	}
	return slow;
}

5.合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 图片.png 如果链表l1的值小于链表l2的值,l1的值赋给新的链表节点,反之将l2的节点赋给新链表的尾。并将l1的指针向后移动一个。所以我们定义新链表的头和尾,尾用来链接下一个节点。 这里还有两种情况,一种是L1链表为空,这时只需要返回l2链表,反之返回l1链表。第二种情况是,当l1链表走完,l2链表还有两个或以上个节点,则将节点直接赋给newTail。

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){

    if(l1==NULL)//链表为空返回另一个链表
        return l2;

    if(l2==NULL)
        return l1;
//定义新链表头和尾
    struct ListNode* newHead=NULL;
    struct ListNode* newTail=NULL;

    while(l1&&l2)//只要一个遍历到空,跳出循环
    {
        if(l1->val<l2->val)
        {
            if(newHead==NULL)//如果头尾空,将头和尾头赋上第一个值,这样可以链接下一个
            {
                newHead=newTail=l1;
            }
            else
            {
                newTail->next=l1;//将值给到newTail的尾
                newTail=l1;//并更新尾节点
            }
            l1=l1->next;//更新原链表的节点位置
        }
        else
        {
            if(newHead==NULL)
            {
                newHead=newTail=l2;
            }
            else
            {
                newTail->next=l2;
                newTail=l2;
            }
            l2=l2->next;
        }
    }

    if(l1)//如果l1链表没有遍历完,直接将没有遍历完的数字链接到newTail后面。
    {
        newTail->next=l1;
    }
    if(l2)
    {
        newTail->next=l2;
    }
    return newHead;
}

上一种是不带哨兵位头的方法,当然我们可以写一种带哨兵位的方法,这样我们不用管头结点是否为空,直接在后面链接即可

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
	if(l1==NULL)
		return l2;
	if(l2==NULL)
		return l1;
	
	struct ListNode* newHead,*newTail;
	newHead=newTail=(struct ListNode)malloc(sizeof(struct ListNode));
	while(l1&&l2)
	{
		if(l1->val<l2->val)
		{
			newTail-next=l1;
			newTail=l1;
			l1=l1->next;
		}
		else
		{
			newTail-next=l2;
			newTail=l2;
			l2=l2->next;
		}
	}
	if(l1)
		newTail=l2;
	if(l2)
		newTail=l1;
	struct ListNode* list=head->next;//用一个新指针记录头结点
	free(newHead);//释放malloc的哨兵位
	return list;
}

6. 链表分割

现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。 OJ链接

这里虽然没有C模式,但是可以在C++中写,C++是兼容C的。这里的struct在C++中是一个类,可以省略,但是我们用C写代码还是严格规范一下比较好

假如给了这样一个链表 图片.png 如果我们直接用头插和尾插,我们无法保证链表的顺序,所以我们可以搞两个链表,一个用来放小于x的数据,一个用来放大于x的数据,然后把他们链接起来,最后释放掉两个哨兵位 图片.png 还有一种情况, 图片.png 最后cur走向空,15作为greater的最后一个节点,我们么看有改变节点的next,15的next还是4,这样形成了一个死循环,所以,不管greaterTail最后是否会形成死循环,我们都将它的next置空

class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
        struct ListNode* lessHead,*lessTail,*greaterHead,*greaterTail;
        //开一个哨兵位的头节点
        lessHead=lessTail=(struct ListNode*)malloc(sizeof(struct ListNode));
        greaterHead=greaterTail=(struct ListNode*)malloc(sizeof(struct ListNode));
        //指针遍历
        struct ListNode* cur=pHead;
        while(cur)
        {
            if(cur->val<x)
            {
                lessTail->next=cur;
                lessTail=cur;
            }
            else{
                greaterTail->next=cur;
                greaterTail=cur;
            }
            cur=cur->next;
        }
        greaterTail->next=NULL;//将尾节点next置空
        lessTail->next=greaterHead->next;//将两个表链接起来
        struct ListNode* newHead=lessHead->next;//用一个新头结点存放头结点,以便释放哨兵位
        free(lessHead);
        free(greaterHead);
        
        return newHead;
    }
};

7. 链表的回文结

描述 对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。 给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

图片.png

这道题,可以先找到中间位置,记录头结点和中间节点,翻转中间节点的链表,然后比较两个链表的值是否相同。因为空间复杂度是O(1),不需要开辟新空间,直接在原链表中操作。这道题在牛客虽然归为难题,但是,前面的翻转跟查找中间位置,我们在前面都涉及过,就迎刃而解了

图片.png 当midHead遍历到3,firstHead遍历到1,其实这个判断条件写成midHead && midHead->next还是midHead都可以,反正只要两个数不一样,之间返回false,无下一步操作。

struct ListNode* findMidNode(struct ListNode* pHead)//找中间位置
{
    struct ListNode* slow=pHead;
    struct ListNode* fast=pHead;
    
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
    }
    return slow;
}

struct ListNode* reverseNode(struct ListNode* pHead)//翻转单链表
{
    struct ListNode* cur=pHead;//当前链表的遍历指针
    struct ListNode* next=cur->next;//记录当前链表cur的下一个
    struct ListNode* newHead=NULL;//新节点的头
    
    while(cur)
    {
        cur->next=newHead;
        newHead=cur;
        cur=next;
        if(next)//cur是最后一个节点,next已经为空,没有next->next
            next=next->next;//不能是cur->next,cur->next已经变成newHead
    }
    return newHead;
    
}

class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        // write code here
        struct ListNode* firstHead=A;
        struct ListNode* midNode=findMidNode(A);//找到中间节点
        struct ListNode* midHead=reverseNode(midNode);//将中间节点之后数据翻转
        
        //回文结构为偶数个,当midHead为NULL时,跳出循环
        //非会问结构至少为奇数个,当midHead遍历到最后一个,他的next为NULL,跳出循环
        while(midHead && midHead->next)
        {
            if(firstHead->val==midHead->val)
            {
                firstHead=firstHead->next;
                midHead=midHead->next;
            }
            else
            {
                return false;
            }
        }
        return true;
    }
};

8.相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。 题目数据 保证 整个链式结构中不存在环。 注意,函数返回结果后,链表必须 保持其原始结构 。 图片.png OJ链接 思路:先判断这个链表是否相交,一种方法是比较暴力的,第一行链表的每一个与第二行链表的每一个对比。第二种方法,找到两个链表的尾,判断是否相等,如果相等,这个链表必然相交。 要求出交点,可以让长的链表节点先走长出的部分,走到两个链表长度相等时,两个指针一起走,直到二者相等。我们在向后遍历找尾时,我们可以定义一个变量len同时求出各自链表的长度,然后用abs求出长度,abs是绝对值,不需要考虑谁大谁小。最后返回节点。

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* tailA=headA;
    struct ListNode* tailB=headB;

    int lenA=1;
    while(tailA->next)
    {
        ++lenA;
        tailA=tailA->next;
    }
    int lenB=1;
    while(tailB->next)
    {
        lenB++;
        tailB=tailB->next;
    }
    //不相交
    if(tailA != tailB)
    {
        return NULL;
    }
    int gap =abs(lenA-lenB);
    //长的先走差距步,再同时走找交点
    struct ListNode* longList=headA, *shortList=headB;
    if(lenA<lenB);
    {
        shortList=headA;
        longList=headB;
    }

    while(gap--)
    {
        longList = longList->next;
    }
    while(shortList != longList)
    {
        shortList = shortList->next;
        longList=longList->next;
    }
    return longList;
}

9.环形链表

给定一个链表,判断链表中是否有环。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 如果链表中存在环,则返回 true 。 否则,返回 false 。 OJ链接 思路:这里我们通过一个快慢指针来解决,慢指针一次走一步,快指针一次走两步,如果不带环,fast就会为NULL,带环,fast就会在环里追上slow。 图片.png 图片.png 当slow进环,假设slow与fast的距离为N,当slow走一次,fast走两次,他们的激励就会从N,变成N-1,再走变成N-2,以此类推,直到距离为1,0.这时就追上了 图片.png 图片.png

bool hasCycle(struct ListNode *head) {
    struct ListNode* fast=head;
    struct ListNode* slow=head;

    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(slow==fast)
        {
            return true;
        }
        
    }
    return false;
}

思考一下,如果让fast走3步、4步甚至更多呢(n>3),fast还能不能追上slow。 证明一下: 图片.png 当slow进环时,假设距离为N。slow走一步,fast走3步. 当N为偶数,他们的距离为N-2,N-4...4,2,0 当N为奇数,他们的距离为N-2,N-4...3,1,-1,这时他们就会错过,再来一圈,此时他们的距离为C-1(C为圆的周长),当C-1为偶数时,fast可以追上slow,如果C-1为奇数,那么fast永远追不上slow。 图片.png N为4,5,6都可以用此证明,不做过多赘述。

10.环形链表 II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。 说明:不允许修改给定的链表。 OJ链接 图片.png 思路:这道题用一个公式,证明很麻烦,但是写起来简单。 图片.png 假设头结点到入环节点的距离为L,slow和fast相遇的环内距离为x,环的长度是C。 追上的过程中:慢指针走的距离为L+X,快指针走的距离为L+C+X,实际上快指针走的不是这个距离,如果环很小,入环前的距离很长。假设环的距离只有2,那么fast入环后走了好几圈,slow还没有进环。所以快指针走的距离是L+N*C+X(N>=1),N是他们相遇之前,fast在环里走的圈数。

这时候就可以导公式了:(快指针走的路程是慢指针的2倍) 2(L+X)=L+NC+X => L=NC-X => L=(N-1)*C+C-X 图片.png 这样可以导出,L的距离就是C-X. 代码演示:

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* fast=head;
    struct ListNode* slow=head;

    while(fast && fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow)
        {
             struct ListNode* meetNode=fast;
            while(meetNode!=head)
            {
            meetNode=meetNode->next;
            head=head->next;
            }
        return meetNode;
        }
    }
    return NULL;
}

11.复制带随机指针的链表

给你一个长度为 n 的链表,每个节点包含一个额外++增加的随机指针 random++ ,该指针可以指向链表中的++任何节点或空节点++。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 ++全新节点成++,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向++复制链表中的新节点++,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。 OJ链接 图片.png 思路:

  1. 复制节点,插入到节点和下一个节点之间 图片.png
  2. 根据原节点random,处理复制节点的random。 copy节点的random就是原节点random的next 图片.png
  3. 复制节点接下来链接成一个新链表,恢复原链表连接关系 给上一个copyHead和copyTail,copyHead用来指向新头,copyTail用来链接下一个copy。

图片.png

struct Node* copyRandomList(struct Node* head) {
    struct Node* cur=head;
    //1.复制节点的值
    while(cur)
    {
        //sizeof不能写struct Node* 表示指针大小,12个字节越界了
        //struct Node表示4个字节
        struct Node* copy=(struct Node*)malloc(sizeof(struct Node));
        copy->val=cur->val;
        
        //插入copy值
        copy->next=cur->next;
        cur->next=copy;

        cur=copy->next;//向后迭代
    }


    //2.复制random
    cur=head;
    while(cur)
    {
        struct Node* copy=cur->next;
        if(cur->random==NULL)
        {
            copy->random=NULL;
        }
        else
        {
            copy->random=cur->random->next;
        }
        cur=copy->next;
    }

    //将复制的节点链接
    cur=head;
    struct Node* copyHead=NULL;
    struct Node* copyTail=NULL;
    while(cur)
    {
        struct Node* copy=cur->next;
        struct Node* next=copy->next;
        if(copyTail==NULL)
        {
            copyTail=copyHead=copy;
        }
        else
        {
            copyTail->next=copy;
            copyTail=copy;
        }
        cur->next=next;
        cur=next;
    }

    return copyHead;
}

Thanks for watching.:)