算法通关村第一关——链表经典问题 (白银)

单链表结点的定义

//单链表结点结构
struct ListNode {
	int val;
	struct ListNode* next;
	ListNode() {}
	ListNode(int n, struct ListNode* next)
	{
		val = n;
		this->next = next;
	}

};

1.两个链表的第一个的公共子结点(剑指offer52)

leetcode力扣icon-default.png?t=N7T8https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/description/nowcoder牛客icon-default.png?t=N7T8https://www.nowcoder.com/exam/oj/ta?page=1&tpId=13&type=13

题目:给出两个链表,找到它们的第一个公共结点,如下图

即要找到node(c1)。

1.1使用哈希或集合

将第一个链表的所有结点存入HashMap里,然后一遍遍历第二个链表一边在检查Hash里是否有该结点,若有,则返回该结点。使用集合的思路跟哈希一样。

1.2使用栈

创建两个栈,分别将两个链表全部入栈,接着两个栈开始检测栈顶元素,相等则出栈,循环此操作,最后出栈的那个元素就是两链表的第一个的公共结点。此方法需要两个O(n)的空间。

1.3双指针法

1.3.1拼接两个链表

如下图,node(4)是我们要找的公共结点

将A,B拼接后,我们发现,此时两次拼接后的倒数第二个结点node(4)就是第一个公共结点,此时,我们找到这个node(4)就可以了。

此时最简单的方法就是两个拼接链表分别一起遍历,当两个结点相同时,返回这个结点就行了。

优化方法:

我们可以不建立新的两个拼接链表AB,BA,直接同时在A,B上进行遍历。

当A遍历至最后的NULL时,让其继续在B的头结点进行遍历。(即p1==NULL时就让p1=head2)

当B遍历至最后的NULL时,让其继续在A的头结点进行遍历。(即p2==NULL时就让p2=head2)

这样最后,如果A,B有公共结点,必定会同时遍历到第一个公共结点。

但如果A,B没有公共结点,则到NULL时又会重复遍历下去,死循环,所以在循环内部必须加个判断,只有(p1!=p2)时,才有(p1==NULL时就让p1=head2)p2==NULL时就让p2=head2)

所以如果A,B没有公共结点,最后也必定会同时遍历至NULL,所以(p1==p2)就可以作为循环遍历结束的条件。

如下图

//两个链表的第一个公共子结点 (双指针 拼接法)
struct ListNode* getIntersectionNode(struct ListNode* head1, struct ListNode* head2) {
	if (head1 == NULL || head2 == NULL)
		return NULL; 
	struct ListNode* p1 = head1;
	struct ListNode* p2 = head2;

	while (p1!=p2) {
		p1 = p1->next;
		p2 = p2->next;

        //若无交集则会陷入死循环,需判断
		if (p1 != p2) {

			if (p1 == NULL) {
				p1 = head2;
			}
			if (p2 == NULL) {
				p2 = head1;
			}
		}

	}

	return p1;
}

1.3.2做差双指针法

还可以先遍历两个单链表,求出它们的长度 l1 和 l2,它们的差为| l1-l2 |,第二次遍历时,让长的链表先走| l1-l2 |,然后在同步一起遍历,若有公共结点,(p1==p2)时就是第一个公共结点了。若没有,(p1==p2)时就是NULL;

2.判断单链表是否为回文序列

leetcode力扣icon-default.png?t=N7T8https://leetcode.cn/problems/palindrome-linked-list/description/

例:1->2->3->3->2->1    则return true

方法有很多

1.将链表全部元素入栈,然后一边出栈一边从头遍历单链表一边比较,只要有一个不相等就return false。

2.优化1,先进行一次单链表的遍历,得到总长度,然后将一半的链表入栈,接着一边出栈一边接着遍历一边比较,只要有一个不相等就return false。

3.在求总长度的第一次遍历时,一边遍历一边入栈,接着一边出栈,一边从头遍历一边比较,只比较一半的元素就停止。

4.反转链表法。将原链表的所有元素逆序保存在一个newList中,接着两个链表都从头开始一边遍历一边比较。

5.优化4,先遍历求总长度,然后反转一半的元素至newList,接着从newList的头和原链表的一半开始一边遍历一边比较。

6.双指针法,使用fast和slow指针,fast一次走两步,slow一次走一步。fast走至尾部时,slow在中间,接着就可以从头开始逆序一半的元素。

这里给出最简单的反转链表法

//判断单链表是否为回文序列  反转链表法
bool isPalindrome(ListNode* head) {
	ListNode* newList = NULL;
	ListNode* temp = head;
    //每次将原链表的值复制后插入新链表的头结点
	while (temp != NULL) {
		ListNode* newNode = new ListNode(temp->val, NULL);
		newList=insertNode(newList, newNode, 1);
		temp = temp->next;
	}
	temp = head;
	ListNode* temp2 = newList;
    //开始同时遍历比较
	while (temp != NULL)
	{
		if (temp->val != temp2->val)
		{
			return false;
		}
		temp = temp->next;
		temp2 = temp2->next;
	}

		destroyList(newList);
		return true;
}

3.合并有序单链表

leetcode力扣icon-default.png?t=N7T8https://leetcode.cn/problems/merge-two-sorted-lists/description/

3.1合并两个有序单链表

其实很简单,就规规矩矩新建一个链表,然后从两个链表遍历比较,每次将小的接上就行了

直接上代码

//合并两个有序单链表
struct ListNode* mergeTwoList(struct ListNode* list1, struct ListNode* list2) {
	ListNode* mergedList = new ListNode(-1, NULL);
	ListNode* temp = mergedList;

	//两个链表都没遍历至NULL
	while (list1 != NULL && list2 != NULL) {

		if (list1->val <= list2->val) {
			temp->next = list1;
			list1 = list1->next;
			temp = temp->next;
		}
		else {
			temp->next = list2;
			list2 = list2->next;
			temp = temp->next;
		}
	}

	//有一个链表遍历至NULL时
	temp->next = (list1) ? list1 : list2;

	temp = mergedList->next;
	delete mergedList;

	return temp;
}

3.2合并k个有序单链表

最容易想到的就是按顺序来,先将前两个合并,然后依次与后面的合并

struct ListNode* mergeKLists(struct ListNode* [] lists , int size){
    struct ListNode* newList=NULL;
    
    //每次将newList和lists[m]合并
    for(int m=0;m<size;m++){
        
        newList=mergeTwoLists(newList , lists[index]);
    
    }


    return newList; 
}

3.3一道好题

leetcode力扣icon-default.png?t=N7T8https://leetcode.cn/problems/merge-in-between-linked-lists/description/如下图

示例:

这题的解题关键就在于要找到的 list1node(a-1)list1node(b+1) ,然后将 list1node(a-1)->next = list2node(0) , list2node(m-1)->next=list1node(b+1)。

ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) {
        ListNode* pre1=list1;
        ListNode* after1=list1;
        ListNode* cur2=list2;

        //找到remove段的前一个结点和remove段的后一个结点
        while(pre1!=NULL && after1!=NULL && b!=-1){

            if(a!=1){
            pre1=pre1->next;
            a--;
            }

            if(b!=-1){
            after1=after1->next;
            b--;
            }
        }

        //找到list2的最后一个结点
        while(cur2->next!=NULL){
            cur2=cur2->next;
        }
        //开始连接
        pre1->next=list2;
        cur2->next=after1;

        return list1;
    }

4.双指针专题

4.1寻找中间结点

leetcode力扣icon-default.png?t=N7T8https://leetcode.cn/problems/middle-of-the-linked-list/description/

题目: 给你单链表的头结点 head,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

例:    输入:head=[1,2,3,4,5]                                输入:head=[1,2,3,4,5,6]

           输出:[3,4,5]                                                输出:[4,5,6]

           只有一个中间结点                                        有两个中间结点,返回第二个

使用双指针法的快慢指针可以轻松解决,创建两个指针fast,slow都指向head,接着fast一次走两步,slow一次走一步,此时循环结束的条件是个问题。需要我们具体分析。

发现

只有一个中间结点时,fast走到最后一个结点时,slow刚刚好在中间结点

有两个中间结点时,fast走到NULL时,slow刚刚好在第二个中间结点

所以将while循环的条件写成 while(fast!=NULL && fast->next!=NULL)

//快慢指针法
ListNode* middleNode(ListNode* head) {
        struct ListNode* fast = head;
        struct ListNode* slow = head;
        
        while(fast!=NULL && fast->next!=NULL){
            
            fast=fast->next->next;
            slow=slow->next;

        }
        return slow;
    }

4.2寻找倒数第k个结点

leetcode力扣icon-default.png?t=N7T8https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/description/

题目:输入一个链表,返回该链表的倒数第k个结点,从1开始计数,即尾结点是倒数第一个

例:  head=[1,2,3,4,5]   , k=2

         返回 [4,5]

这题使用快慢指针也能轻松解决,创建两个指针fast,slow都指向head,接着fast先走k步,此时fast指向第k+1个结点,fast与slow之间相差k步,此时两个指针同时向后遍历,当fast==NULL时,slow恰好指向倒数第k个结点,返回slow即可。

但还有一个问题,就是有可能链表的长度小于k,所以需加判断fast!=NULL

struct ListNode* kthToLast(ListNode* head, int k) {
        struct ListNode* fast=head;
        struct ListNode* slow=head;
        
        //先让fast走k步,注意判断fast!=NULL
        while(fast!=NULL && k>0){
                fast=fast->next;
                k--;
        }

        while(fast!=NULL){
            fast=fast->next;
            slow=slow->next;
        }

        return slow;
    }

      

4.3旋转链表

leetcode力扣icon-default.png?t=N7T8https://leetcode.cn/problems/rotate-list/题目: 给你一个单链表和数字k,旋转链表,将每个结点向后移动k个位置

例:
                输入head = [1,2,3,4,5], k = 2

                输出[4,5,1,2,3]

我们只需要找到倒数第k+1个结点和最后一个结点,然后将最后一个结点的next=head,保存倒数第k+1个结点的下一个结点,接着倒数第k+1个结点的next=NULL,返回保存的那个结点

而找到倒数第k+1个结点刚好可以用4.1的方法,还需注意 if(head==NULL || k==0) return head;并且k>length时要k=k%length,所以之前还需遍历一般求length,期间又刚好可以找到尾结点,所以,代码如下

struct ListNode* rotateRight(ListNode* head, int k) {
        if(head==NULL || k==0) return head;

        int length=1;
        struct ListNode* temp = head;
        struct ListNode* cur =head;

        //获取链表的长度,同时temp指向尾结点
        while(temp->next!=NULL){
            length++;
            temp=temp->next;
        }

        k=k%length;
        if(k==0) return head;

        //cur指向倒数第k+1个结点
        cur=kthToLast(head,k+1);
        //尾结点next=head
        temp->next=head;
        //temp指向新头结点
        temp=cur->next;
        //倒数第k+1个结点变成新尾结点
        cur->next=NULL;
        
        return temp;
    }

5.删除链表元素专题

题目有很多

leetcode力扣237

leetcode力扣203

leetcode力扣19

leetcode力扣1474

leetcode力扣83

leetcode力扣82

5.1删除特定结点

leetcode力扣203icon-default.png?t=N7T8https://leetcode.cn/problems/remove-linked-list-elements/description/题目: 给你一个单链表和一个整数val,删除单链表上所有值为val的结点,返回新的链表头结点

例:        输入:head=[1,2,6,3,4,5,6]           val=6

                输出:head=[1,2,3,4,5]

对于删除操作,头结点的删除与后面的结点不同,为了统一操作,可以创建一个虚拟头结点dummyHead,使其的next=head,这样对于头结点的删除也与后面的结点统一了。

因为删除结点要知道删除结点的前驱结点,所有这里要用cur->next->val来判断是否是要删除的结点,并且不要忘了待删除的那个结点要delete掉。

ListNode* removeElements(ListNode* head, int val) {
            struct ListNode* dummyNode =new struct ListNode(-1,head);
            struct ListNode* cur=dummyNode;

            while(cur->next!=NULL){

                if(cur->next->val==val){
                    struct ListNode* temp=cur->next;
                    cur->next=cur->next->next;
                    delete temp;
                }
                else{
                    cur=cur->next;
                }

            }

            return dummyNode->next;
    }

5.2删除倒数第k个结点

leetcode力扣19icon-default.png?t=N7T8https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/

题目: 给你一个链表,删除链表的n个结点,并且返回链表的头结点。

例:           输入:head=[1,2,3,4,5]           n=2

                  输出:head=[1,2,3,5]

这其实就是4.2的小小拓展而已

方法1:先遍历一边算出length,要删除的结点其实就是第length-n+1个结点。

方法2:将全部结点入栈,然后出栈,第n个出栈的结点就是要删除的结点,但该方法需要O(n)的空间,所以不推荐

方法3:双指针法,也就是4.2的方法

方法1:

    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head==NULL) return head;
            struct ListNode* dummyNode =new struct ListNode(-1,head);
            struct ListNode* cur=dummyNode;
            struct ListNode* temp=head;
            int length =0;

            //遍历求出length
            while(temp!=NULL){
                length++;
                temp=temp->next;
            }
            

            int index=length-n+1;

            //遍历找到第length-n+1个结点的前驱结点,也就是length-n个结点,但因此时cur指向dummyNode,所以循环判断条件稍稍变化
            while( (index-1) !=0){
                cur=cur->next;
                index--;
            }

            temp=cur->next;
            cur->next=cur->next->next;
            delete temp;

            return dummyNode->next;
    }

方法3: 因为要找到删除结点的前一个结点,所以此时slow有稍稍变化

ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head==NULL) return head;
            struct ListNode* dummyNode =new struct ListNode(-1,head);
            struct ListNode* cur=dummyNode;
            struct ListNode* temp=head;
        
    
        struct ListNode* fast=head;
        struct ListNode* slow=dummyNode;//因为要找到删除结点的前一个结点,这样可以使slow指向倒数第n+1个结点
        
        //先让fast走n步,注意判断fast!=NULL
        while(fast!=NULL && n>0){
                fast=fast->next;
                n--;
        }

        while(fast!=NULL){
            fast=fast->next;
            slow=slow->next;
        }
            //此时slow为删除结点的前一个结点
            cur=slow;

            temp=cur->next;
            cur->next=cur->next->next;
            delete temp;

            return dummyNode->next;
    }

5.3删除重复元素

分以下两种情况

5.3.1重复元素留一个

leetcode力扣83icon-default.png?t=N7T8https://leetcode.cn/problems/remove-duplicates-from-sorted-list/description/

题目: 给定一个已排序的链表的头head, 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 

例:        输入: head=[1,1,2,3,3]

                输出: head=[1,2,3]

该题的关键是要知道,只有相邻的才有可能值相同,所以要用(cur->val==cur->nexr->val)来判断

   struct ListNode* deleteDuplicates(ListNode* head) {
            if(head==NULL) return NULL;
            struct ListNode* cur=head;
                
            //一直遍历至最后一个元素
            while(cur->next!=NULL){
            
                if(cur->val==cur->next->val){

                    struct ListNode* temp=cur->next;
                    cur->next=cur->next->next;
                    delete temp;

                }else{
                    cur=cur->next;
                }

            }

         return head;
    }

5.3.2重复元素一个都不留

leetcode力扣82icon-default.png?t=N7T8https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/description/这题与上一题的差别就是只要是重复的元素,一个都不留,全部删除。

(cur->next->val == cur->next->next->val)来判断,只要成立,若还有相等的,一定就挨在它们后面,所有用x记录一下相等的值,加一个while(cur->next!=NULL && cur->next->val ==x)就能将挨在一起相等的删干净

   struct ListNode* deleteDuplicates(ListNode* head) {
        if(head==NULL) return NULL;

        struct ListNode* dummyNode=new struct ListNode(-1,head);
        struct ListNode* cur=dummyNode;

        while(cur->next!=NULL && cur->next->next!=NULL){

                if(cur->next->val == cur->next->next->val){
                    int x=cur->next->val;
                    
                    //删除重复的结点
                    while(cur->next!=NULL && cur->next->val ==x){
                        struct ListNode* temp= cur->next; 
                        cur->next=cur->next->next;
                        delete temp;
                    }
                }else{
                    cur=cur->next;
                }
        }

            temp=dummyNode->next;
            delete dummyNode;
            return temp;
        }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值