链表专题

0 数据结构

链表结构体及构造函数

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

1 合并两个有序序列

【链接】
https://www.nowcoder.com/questionTerminal/d8b6b4358f774294a89de2a6ac4d9337

【题目描述】
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

【注意点】

  • Node为NULL,不要取next,会异常出错
  • 构造函数初始化
  • 非递归版本速度快于递归版本

1.1 非递归版本

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        ListNode* pHead = new ListNode(-1);
        ListNode* pNode = pHead;

        while(pHead1 != NULL && pHead2 != NULL){
            if(pHead1->val < pHead2->val){
                pNode->next = pHead1;
                pHead1 = pHead1->next;
            }
            else{
                pNode->next = pHead2;
                pHead2 = pHead2->next;
            }
            pNode = pNode->next;
        }

        if(pHead1 == NULL)
            pNode->next = pHead2;
        if(pHead2 == NULL)
            pNode->next = pHead1;

        return pHead->next;
    }
};

1.2 递归版本

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        ListNode* pHead = NULL;

        if(pHead1 == NULL)
            return pHead2;

        if(pHead2 == NULL)
            return pHead1;

        if(pHead1->val < pHead2->val){
            pHead = new ListNode(pHead1->val);
            pHead1 = pHead1->next;
        }
        else{
            pHead = new ListNode(pHead2->val);
            pHead2 = pHead2->next;
        }

        pHead->next = Merge(pHead1, pHead2);

        return pHead;
    }
};

2 翻转链表

单链表反转,包括头插法和递归法

2.1 全翻转

【链接】
https://www.nowcoder.com/questionTerminal/75e878df47f24fdc9dc3e400ec6058ca

【题目描述】
输入一个链表,反转链表后,输出新链表的表头。

【整体思路】
详见方案2.2,全翻转是指定下标区间翻转的特例

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode* dummy = new ListNode(-1);
        dummy->next = pHead;
        
        ListNode* pPre = dummy;
        ListNode* pNode = pHead;
        ListNode* pNext = NULL;
        
        while(pNode){
            pNext = pNode->next;
            if(pNext){
                pNode->next = pNext->next;
                pNext->next = pPre->next;
                pPre->next = pNext;
            }
            else
                break;
        }
        
        return dummy->next;
    }
};

2.2 指定下标区间翻转

【链接】
https://www.nowcoder.com/questionTerminal/b58434e200a648c589ca2063f1faf58c

【题目描述】
将一个链表m位置到n位置之间的区间反转,要求使用原地算法,并且在一次扫描之内完成反转。
例如:
给出的链表为1->2->3->4->5->NULL, m = 2 ,n = 4,
返回1->4->3->2->5->NULL.
注意:
给出的m,n满足以下条件:
1 ≤ m ≤ n ≤ 链表长度

【解析】

对于reverse部分有点迷糊。网上看到的解释,也许更能帮助理解.https://yq.aliyun.com/articles/3867
不妨拿出四本书,摞成一摞(自上而下为 A B C D),要让这四本书的位置完全颠倒过来(即自上而下为 D C B A):

盯住书A,每次操作把A下面的那本书放到最上面

初始位置:自上而下为 A B C D

第一次操作后:自上而下为 B A C D

第二次操作后:自上而下为 C B A D

第三次操作后:自上而下为 D C B A

在这里插入图片描述

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        //头插法,考虑翻转头节点情况
        ListNode* pHead = new ListNode(-1);
        pHead->next = head;
        
        ListNode* pPre = pHead;
        ListNode* pNode = head;
        ListNode* pNext = NULL;
        int cur = 1; //下标从1开始
        
        while(pNode){
            pNext = pNode->next;
            if(cur < m){
                pPre = pNode;
                pNode = pNext;
            }
            else if(cur >= m && cur < n){
                //翻转
                pNode->next = pNext->next; //n位置的必然存在,所以pNext->next不会越界
                pNext->next = pPre->next;
                pPre->next = pNext;
            }
            else{
                pNode = pNext;
            }
            cur += 1;
        }
        
        return pHead->next;
    }
};

2.3 k个值一组翻转

【链接】
https://www.nowcoder.com/questionTerminal/b49c3dc907814e9bbfa8437c251b028e

[特例] k=2时,https://www.nowcoder.com/profile/351750300/codeBookDetail?submissionId=81128930

【题目描述】
将给出的链表中的节点每k个一组翻转,返回翻转后的链表
如果链表中的节点数不是k的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。
只允许使用常数级的空间
例如:
给定的链表是1->2->3->4->5

对于 k = 2, 你应该返回 2->1->4->3->5

对于 k = 3, y你应该返回 3->2->1->4->5

2.3.1 递归版本

【注意】

  • 一轮翻转后,如何保留表头及其下一个信息
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        if(head == NULL || k < 2)
            return head;

        ListNode* dummy = new ListNode(-1);
        dummy->next = head;
        
        ListNode* pNode = head;

        //统计链表长度
        int len = 0;
        while(pNode){
            len++;
            pNode = pNode->next;
        }

        //无需翻转
        if(len < k)
            return dummy->next;

        //需要进行翻转
        ListNode* pPre = dummy;
        pNode = head;
        ListNode* pNext = NULL;
        int cur = 1;
        while(pNode){
            pNext = pNode->next;
            if(pNext){
                pNode->next = pNext->next;
                pNext->next = pPre->next;
                pPre->next = pNext;
                
                cur += 1;
                if(cur == k)
                    break;
            }
            //else //这条路径因为上面判断所以不会走到
            //    break;
        }

        if(pNode->next == NULL) //恰巧走到结尾
            return dummy->next;
        else{
            pNode->next = reverseKGroup(pNode->next, k);
            return dummy->next;
        }
    }
};

3 两个链表的第一个公共节点

【链接】
https://www.nowcoder.com/questionTerminal/6ab1d9a29e88450685099d45c9e31e46

【题目描述】
输入两个链表,找出它们的第一个公共结点

【题意理解】
从链表的定义可以看出,这两个链表是单链表,如果两个链表有公共节点,那么这两个链表从某一节点开始,它们的m_pNext都指向同一个节点,之后它们所有的节点都是重合的,不可能再出现分叉。所以拓扑形状看起来是Y型

一个简单的方法是:首先遍历两个链表得到它们的长度,就能知道哪个链表比较长,以及长的链表比短的链表多几个节点。在第二次遍历的时候,先在较长的节点上走若干步,接着同时在两个链表上遍历,找到的第一个相同的节点就是它们的公共的节点。

class Solution {
public:
    ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2){
        ListNode *p1 = pHead1;
        ListNode *p2 = pHead2;
        int len1 = 0, len2 = 0, diff = 0;
        while(p1 != NULL){
            p1=p1->next;
            len1++;
        }
        while(p2 != NULL){
            p2=p2->next;
            len2++;
        }
        
        //p1指向较长的串,p2指向较短的串
        if(len1 > len2){
            diff = len1 - len2;
            p1 = pHead1;
            p2 = pHead2;
        }
        else{
            diff = len2 - len1;
            p1 = pHead2;
            p2 = pHead1;
        }
        
        //较长的串先移动到与较短的串相同长度的位置
        for(int i=0;i<diff;i++){
            p1 = p1->next;
        }
        while(p1 != NULL && p2 != NULL){
            if(p1 == p2)
                break;
            p1 = p1->next;
            p2 = p2->next;
        }
        return p1;
    }
};

4 删除链表中倒数第n个节点(快慢指针)

【链接】
https://www.nowcoder.com/questionTerminal/f95dcdafbde44b22a6d741baf71653f6

【题目描述】
给定一个链表,删除链表的倒数第n个节点并返回链表的头指针
例如,
给出的链表为:1->2->3->4->5, n= 2.↵↵ 删除了链表的倒数第n个节点之后,链表变为1->2->3->5.
备注:
题目保证n一定是合法的
请尝试只用一步操作完成该功能

【题解】
问题关键在于找到倒数第N个节点,并且尽量只使用一次循环。
采用两个指针,对前指针,使其先走出N步,随后两个指针同时前进,当前指针到达链表尾部时,后指针到达倒数第N个节点的位置。

【注意点】

  • 删除时注意待删除节点为头结点时的情况
  • 删除倒数第n个节点,先找到倒数第n+1个节点(链表删除结点的特殊性)
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        //定义快、慢指针
        ListNode *pFast = head;
        ListNode *pLow = head;
        
        //快指针先走n步
        for(int i = 0; i < n; i++)
            pFast = pFast->next;
        
        //考虑删除头结点的情况
        if(pFast == NULL)
            return head->next;
        
        //当快指针走到结尾时停下来
        //因为要删除倒数第n个节点,所以要找到倒数第n+1个节点
        while(pFast->next != NULL){
            pFast = pFast->next;
            pLow = pLow->next;
        }
        
        //删除pLow->next结点,与上面pFast->next对应
        ListNode *tmp = pLow->next;
        pLow->next = pLow->next->next;
        delete tmp;
        
        return head;
    }
};

5 在链表中删除指定值的节点(输入&输出)

【链接】
https://www.nowcoder.com/questionTerminal/1a5fd679e31f4145a10d46bb8fd3d211

【题目描述】
给出一个链表和一个整数 num,输出删除链表中节点值等于 num 的节点之后的链表。

输入描述:
第一行一个整数 n,n 表示单链表的节点数量。

第二行 n 个整数表示单链表的各个节点的值。

第三行一个整数 num。

输出描述:
在给定的函数中返回指定链表的头指针。

示例1
输入
4
1 2 3 4
3
输出
1 2 4

【题解】
头插法即可,考虑删除结点为头结点

#include <iostream>

using namespace std;

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

int main()
{
	//根据输入构建链表
	int len;
	cin >> len;
	ListNode* pHead = new ListNode(-1);
	ListNode* pNode = pHead;

	int x;
	for(int i = 0; i < len; i++){
		cin >> x;
		pNode->next = new ListNode(x);
		pNode = pNode->next;
	}

	int num; //指定删除的值
	cin >> num;

	//删除列表中指定值
	pNode = pHead->next;
	ListNode* pPre = pHead;
	while(pNode){
		ListNode* pNext = pNode->next;
		if(pNode->val == num){ //需要进行删除
			ListNode* tmp = pNode;
			pPre->next = pNext;
			delete tmp;
		}
		else{
			pPre = pNode;
		}
		pNode = pNext;
	}

	//输出链表
	pNode = pHead->next;
	while(pNode){
		cout << pNode->val << " ";
		pNode = pNode->next;
	}
	cout << endl;

	return 0;	
};

6 删除重复元素

6.1 去重

【链接】
https://www.nowcoder.com/questionTerminal/c087914fae584da886a0091e877f2c79

【题目描述】
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
例如:
给出的链表为1->1->2,返回1->2.
给出的链表为1->1->2->3->3,返回1->2->3.

Given a sorted linked list, delete all duplicates such that each element appear only once.
For example,
Given1->1->2, return1->2.
Given1->1->2->3->3, return1->2->3.

示例1
输入
复制
{1,1,2}
输出
复制
{1,2}

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* pNode = head;
        while(pNode !=  NULL && pNode->next != NULL){
            if(pNode->val == pNode->next->val){
                ListNode* tmp = pNode->next;
                pNode->next = pNode->next->next;
                delete tmp;
            }
            else
                pNode = pNode->next;
        }
        return head;
    }
};

6.2 只出现一次

【链接】
https://www.nowcoder.com/questionTerminal/71cef9f8b5564579bf7ed93fbe0b2024

【题目描述】
给出一个排好序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。
例如:
给出的链表为1->2->3->3->4->4->5, 返回1->2->5.
给出的链表为1->1->1->2->3, 返回2->3.

示例1
输入
{1,2,2}
输出
{1}

【注意点】

  • 注意边界,空节点,只有1或2个节点
  • 一些测试case
    • 删除头节点、尾节点
    • 连续删除多个值

6.2.1 非递归版本(思路值得学习)

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(head == NULL)
            return NULL;

        //这样定义两个变量,不会出现边界问题
        ListNode* pPre = NULL;
        ListNode* pNode = head;

        while(pNode){
            ListNode* pNext = pNode->next; //妙

            if(pNext && pNode->val == pNext->val){
                int val = pNode->val; //秒
                while(pNode && pNode->val == val){
                    pNext = pNode->next;
                    delete pNode;
                    pNode = pNext;
                }

                if(pPre == NULL) //妙
                    head = pNode;
                else
                    pPre->next = pNode;
            }
            else{ //当前节点与下一个节点不相等
                pPre = pNode;
                pNode = pNext;
            }
             
        }
        return head;
    }
};

6.2.2 递归版本

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(head == NULL || head->next == NULL)
            return head;

        ListNode* pNext = head->next;

        if (head->val == pNext->val){
            while (pNext && head->val == pNext->val){
                ListNode* tmp = pNext;
                pNext = pNext->next;
                delete tmp;
            }
            delete head;
            return deleteDuplicates(pNext);

            //将上面两行替换为注释的这两行,即可解决drop duplicates I
            //node->next = drop_duplicates(p);
            //return node;
        } 
        else{
            head->next = deleteDuplicates(head->next);
            return head;
        }
    }
};

7 划分链表

【链接】
https://www.nowcoder.com/questionTerminal/1dc1036be38f45f19000e48abe00b12f

【题目描述】
给出一个链表和一个值x,以x为参照将链表划分成两部分,使所有小于x的节点都位于大于或等于x的节点之前。
两个部分之内的节点之间要保持的原始相对顺序。
例如:
给出1->4->3->2->5->2和x = 3,
返回1->2->2->4->3->5.

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        ListNode* pSmallHead = new ListNode(-1);
        ListNode* pSmall = pSmallHead;

        ListNode* pBigHead = new ListNode(-1);
        ListNode* pBig = pBigHead;

        while(head){
            if(head->val < x){
                pSmall->next = new ListNode(head->val);
                pSmall = pSmall->next;
            }
            else{
                pBig->next = new ListNode(head->val);
                pBig = pBig->next;
            }
            head = head->next;
        }
        pSmall->next = pBigHead->next;
        return pSmallHead->next;
    }
};

8 找到环的入口节点(数学证明)

【链接】https://www.nowcoder.com/questionTerminal/6e630519bf86480296d0f1c868d425ad

【题目描述】
对于一个给定的链表,返回环的入口节点,如果没有环,返回null

【解析】
思路:
1)同linked-list-cycle-i一题,使用快慢指针方法,判定是否存在环,并记录两指针相遇位置(Z);
2)将两指针分别放在链表头(X)和相遇位置(Z),并改为相同速度推进,则两指针在环开始位置相遇(Y)。

证明如下:
如下图所示,X,Y,Z分别为链表起始位置、环开始位置和两指针相遇位置
在这里插入图片描述

相遇Z时,慢针走了(a+b)步,快针是其二倍2(a+b);同时,快针走了a+b之后又绕了n圈

如何存在环,走得快的总会和走得慢的指针相遇

得到2*(a + b) = a + b + n * (b + c);【1】

即a = (n - 1) * b + n * c = (n - 1)(b + c) +c; 【2】

注意到b+c恰好为环的长度,故可以推出,如将此时两指针分别放在起始位置和相遇位置,并以相同速度前进,当一个指针走完距离a时,另一个指针恰好走出 绕环n-1圈加上c的距离。
故两指针会在环开始位置相遇。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head == NULL)
            return NULL;

        ListNode* slow = head;
        ListNode* fast = head;

        //快慢指针找到相遇位置
        while(fast != NULL && fast->next != NULL){
            slow = slow->next; //走一步
            fast = fast->next->next; //走两步
            if(slow == fast)
                break;
        }

        if(fast == NULL || fast->next == NULL)
            return NULL;

        //慢指针从头开始,快指针从相遇点开始,当再次相遇时,为环开始位置(图中Y)
        slow = head;
        while(slow != fast){
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
};

9 合并K个有序序列

【链接】
https://www.nowcoder.com/questionTerminal/65cfde9e5b9b4cf2b6bafa5f3ef33fa6

【题目描述】
合并k个已排序的链表并将其作为一个已排序的链表返回。分析并描述其复杂度

【题解】
不断从k条序列中,取出第1和第2条排序并push_back,直至只剩一条序列

class Solution {
public:
    ListNode* merge_two_sorted_lists(ListNode* pHead1, ListNode* pHead2){
        ListNode* pHead = new ListNode(-1);
        ListNode* pNode = pHead;

        while(pHead1 != NULL && pHead2 != NULL){
            if(pHead1->val < pHead2->val){
                pNode->next = pHead1;
                pHead1 = pHead1->next;
            }
            else{
                pNode->next = pHead2;
                pHead2 = pHead2->next;
            }
            pNode = pNode->next;
        }

        if(pHead1 == NULL)
            pNode->next = pHead2;
        if(pHead2 == NULL)
            pNode->next = pHead1;

        return pHead->next;
    }
    
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        if(lists.empty())
            return NULL;

        while(lists.size() > 1)
        {
            lists.push_back(merge_two_sorted_lists(lists[0], lists[1]));
            lists.erase(lists.begin());
            lists.erase(lists.begin());
        }

        return lists[0];
    }
};

10 两个数(链表)求和

【链接】
https://www.nowcoder.com/questionTerminal/56f8d422eae04f129c8e5a05299ae275

【题目描述】
给定两个代表非负数的链表,数字在链表中是反向存储的(链表头结点处的数字是个位数,第二个结点上的数字是十位数…),求这个两个数的和,结果也用链表表示。
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 0 -> 8

【注意点&技巧】

  • 进位计算
  • 不断用NULL/0填充,使两条链表长度相同,精简代码
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* pHead = new ListNode(-1);
        ListNode* pNode = pHead;
        int carry = 0; //10进位
        
        while(l1 || l2){
            int val_l1 = l1 == NULL ? 0 : l1->val;
            int val_l2 = l2 == NULL ? 0 : l2->val;
            int val = val_l1 + val_l2 + carry;
            pNode->next = new ListNode(val % 10);
            carry = val / 10;
            
            pNode = pNode->next;
            l1 = l1 == NULL ? NULL : l1->next; //这样写可以避免next越界
            l2 = l2 == NULL ? NULL : l2->next;
        }
        
        if(carry > 0)
            pNode->next = new ListNode(carry);
        
        return pHead->next;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值