链表相关代码(数据结构笔试、复测、Leecode、牛客)

本文详细介绍了链表数据结构,包括模板链表实现、反转链表、合并排序链表、删除节点、环形链表问题、判断环和公共节点等。同时涵盖中等难度的链表操作如区间反转、节点翻转、环入口找寻和删除倒数节点,以及设计LRU缓存结构。
摘要由CSDN通过智能技术生成

提示:这些是自己整理 可以借鉴 也可能存在错误 欢迎指正


数据结构介绍

链表

链表存储结构 :以指针指向来表示数据元素之间的逻辑关系。

(单)链表是由节点指针构成的数据结构,每个节点存有一个值,和一个指向下一个节点的指针,因此很多链表问题可以用递归来处理。

在这里插入图片描述
在这里插入图片描述

简单

1. 模板链表

请你实现一个链表。
操作:

insert x y:将y加入链表,插入在第一个值为x的结点之前。若链表中不存在值为x的结点,则插入在链表末尾。保证x,y为int型整数。
delete x:删除链表中第一个值为xx的结点。若不存在值为xx的结点,则不删除。

输入描述:

第一行输入一个整数n ,表示操作次数。
接下来的n行,每行一个字符串,表示一个操作。保证操作是题目描述中的一种。

输出描述:

输出一行,将链表中所有结点的值按顺序输出。若链表为空,输出"NULL"(不含引号)。

例子:

输入:
5
insert 0 1
insert 0 3
insert 1 2
insert 3 4
delete 4
输出:2 1 3
#include <bits/stdc++.h>
using namespace std;

typedef struct List{
    int data;
    struct List *next;
}List, *LinkList;

LinkList Insert(int x, int y, LinkList p) {// 在inv前插入x;
    LinkList Pre, t, Init;
    Pre = p; 
    t = p->next;
    //插入新结点、需要先new
    Init = new List;
    Init->data = y;
    Init->next = NULL;
    
    //先判断链表为不为空
    if(t == NULL)
    {
        Pre->next = Init;
        return p;
    }
    
    while(t)
    {
        if(t->data == x)
        {
            Init->next = t;
            Pre->next = Init;
            return p;
        }else
        {
            t = t->next;
            Pre = Pre->next;
        }
    }
    
    if(t==NULL)
    {
        Pre->next = Init;
        return p;
    }
  
    return p;
}


//删除结点
LinkList Deletenode(int x, LinkList p){
    LinkList t, q;
    t = p->next;
    q = p;
    while(t!=NULL)
    {
        if(t->data != x){
             t = t->next;
             q = q->next;
        }else
        {
             q->next = t->next;
             free(t);
             return p;
        }       
    }
    return p;
   
}

int main()
{
    int n;
    cin>>n;//输入操作次数
    LinkList head = new List;//新建链表
    head->next = NULL;
    while(n--){
        string c;
        cin>>c;
        if(c == "insert"){
            int x, y;
            cin>>x>>y;
            head = Insert(x, y, head);
        }else{
            int x;
            cin>>x;
            head = Deletenode(x, head);
        }
    }
    
    //输出链表
    LinkList t;
    t = head->next;
    if(t == NULL)
    {
        cout<<"NULL";
        return  0;
    }
    while(t){
        cout<<t->data<<" ";
        t = t->next;
    }
    
}

2. Reverse Linked List(反转链表)

翻转链表

输入输出样式:
输入:一个链表
输出:链表翻转后的结果
例如:
输入: 1->2->3->4->5->NULL
输出:5->4->3->2->1->NULL
/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 *
 * C语言声明定义全局变量请加上static,防止重复定义
 */

/**
 * 
 * @param pHead ListNode类 
 * @return ListNode类
 */
struct ListNode* ReverseList(struct ListNode* pHead ) {
    // write code here
    if(pHead == NULL)
        return pHead;
    
    struct ListNode* A = (struct ListNode*)malloc(sizeof(struct ListNode));
    A->next = NULL;
    struct ListNode* t, *p;
    t = A;
    p = pHead->next;
    //头插法建链表
    while(pHead != NULL)
    {
        pHead->next = t->next;
        t->next = pHead;
        pHead = p;
        p = p->next;
        
    }
    return A->next;
}

3.合并两个排序链表

输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。

如输入{1,3,5},{2,4,6}时,合并后的链表为{1,2,3,4,5,6},所以对应的输出为{1,2,3,4,5,6}

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

/**
 * 
 * @param pHead1 ListNode类 
 * @param pHead2 ListNode类 
 * @return ListNode类
 */
struct ListNode* Merge(struct ListNode* pHead1, struct ListNode* pHead2){
    
    if(pHead1 == NULL)
        return pHead2;
    if(pHead2 == NULL)
        return pHead1;
    
    //构建新结点
    struct ListNode* C = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* Ct;
    Ct = C;
    
    //头结点包含数据
    while(pHead2 != NULL && pHead1 !=NULL)
    {
        if(pHead1->val <= pHead2->val){
            Ct->next = pHead1;
            pHead1 = pHead1->next;
            Ct = Ct->next;
           
        }else {
            Ct->next = pHead2;
            pHead2 = pHead2->next;
            Ct = Ct->next;
           
        }
        
    }
    Ct->next = NULL;
    if(pHead1 != NULL)
    {
        Ct->next = pHead1;
    }
    if(pHead2 != NULL)
    {
        Ct->next = pHead2;
    }
    
     return C->next;
}

4. 删除链表的节点

输入:{2,5,1,9},5
返回值:{2,1,9}
说明:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 2 -> 1 -> 9 
/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 *
 * C语言声明定义全局变量请加上static,防止重复定义
 */
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param head ListNode类 
 * @param val int整型 
 * @return ListNode类
 */
struct ListNode* deleteNode(struct ListNode* head, int val ) {
    // write code here
    struct ListNode* t, *pre;
    pre = head;
    t = head;
    
    if(head->val == val)
        return head->next;
    
    t = t->next;
    while(t != NULL)
    {
        if(t->val == val)
        {
            pre->next = t->next;
            return head;
        }
        t = t->next;
        pre = pre->next;
    }
    return head;
}

5. 环形链表的约瑟夫问题

描述

编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。
下一个人继续从 1 开始报数。
n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?
class Solution {
public:
    /**
     * 
     * @param n int整型 
     * @param m int整型 
     * @return int整型
     */
    int ysf(int n, int m) {
        // write code here
        vector<int> nums(n);
        //初始
        for(int i=0; i<n; i++)
            nums[i] = i;
        
        int s = 0;
        while(nums.size() != 1){
            int id_away = (s+m-1)%nums.size();
            
            nums.erase(nums.begin() + id_away);//移除下标为id_away
            //erase(position)函数---移除position处的一个字符
            
            s = id_away;
        }
        return nums[0]+1;
    }
};

6.判断链表中是否有环

描述

判断给定的链表中是否有环。如果有环则返回true,否则返回false。

解题思路

  • 哈希
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_map<ListNode*, int> mp;//哈希记录结点出现的次数
        bool flag = false;
        ListNode* p = head;
        while(p !=NULL){
            if(mp.count(p)){
                flag = true;
                break;
            }
            else
                mp[p]++;
            p = p->next;
        }
        return flag;
        
    }
};

7. 两个链表的第一个公共结点

描述

输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

具体做法:

  • step 1:单独的遍历两个链表,得到各自的长度。
  • step 2:求得两链表的长度差nnn,其中较长的链表的指针从头先走nnn步。
  • step 3:两链表指针同步向后遍历,遇到第一个相同的节点就是第一个公共节点。
/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    //计算链表长度
    int Listlenth(ListNode* head){
        int res = 0;
        ListNode* p = head;
        while(p != NULL){
            res++;
            p = p->next;
        }
        return res;
    }
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        int n1 = Listlenth(pHead1);
        int n2 = Listlenth(pHead2);
        //链表1长于链表2
        if(n1 >= n2){
            for(int i=0; i<(n1-n2); i++){
                pHead1 = pHead1->next;
            }
        }else{
            for(int i=0; i<(n2-n1); i++)
                pHead2 = pHead2->next;
        }
        while((pHead1!=NULL) && (pHead2!=NULL) && (pHead1 != pHead2)){
            pHead1 = pHead1->next;
            pHead2 = pHead2->next;
        }
        return pHead1;
    }
    
};

中等

1. 链表内指定区间反转

   将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)。
例如:
给出的链表为 1→2→3→4→5→NULL, m=2,n=4
返回 1→4→3→2→5→NULL.     
/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param m int整型 
     * @param n int整型 
     * @return ListNode类
     */
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        // write code here
        ListNode* rehead = new ListNode(0);
        rehead->next = head;
        ListNode *p = rehead, *q = rehead;
        for(int i=1; i<m; i++)
            p = p->next;
        
        ListNode *s = p->next;
        for(int i=0; i<n-m; i++){
            ListNode *t = s->next;
            s->next = t->next;
            t->next = p->next;
            //t->next = s; 不能为s s一直在变换
            p->next = t;  
        }
        return rehead->next;
    }
};

2.链表中的节点每k个一组翻转

将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表
如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。
/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        // write code here
        ListNode* rehead = new ListNode(0);
        rehead->next = head;
        ListNode *p = rehead, *q = rehead;
        for(int i=1; i<m; i++)
            p = p->next;
        
        ListNode *s = p->next;
        for(int i=0; i<n-m; i++){
            ListNode *t = s->next;
            s->next = t->next;
            t->next = p->next;
            //t->next = s; 不能为s s一直在变换
            p->next = t;  
        }
        return rehead->next;
    }
    
    ListNode* reverseKGroup(ListNode* head, int k){
        if(head == NULL ||head->next==NULL || k==1)
            return head;
        int length = 0;
        ListNode *t = head;
        while(t){
            length++;
            t = t->next;
        }
        t = head;
        
        for(int i=1; i<=length; i+=k){
            if(i+k-1 > length)
                break;//请注意!!!
            ListNode* p = reverseBetween(t, i, k+i-1);//请注意!!! 直接t = reverseBetween(); 会发生数组越界
            t = p;
        }
 
        return t;
    }
};

3.链表中环的入口结点

描述

给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

解题思路

  • 哈希,记录结点出现次数
  • 超过1 ,表示环的入口出现
  • 同“判断链表中是否有环”
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        unordered_map<ListNode*, int> mp;
        ListNode* p = pHead;
        ListNode* res;
        while(p!=NULL){
            if(mp[p] > 1){
                res = p;
                break;
            }else
                mp[p]++;
            p = p->next;
        }
        return res;
    }
};

4.删除链表的倒数第n个节点

描述

给定一个链表,删除链表的倒数第 n 个节点并返回链表的头指针
例如,
给出的链表为: 1→2→3→4→5, n=2.
删除了链表的倒数第 n 个节点之后,链表变为1→2→3→5.

解题思路

  • 三个指针
  • 指向当前结点now、指向当前结点的前一个结点pre、指向想隔n的结点p
  • 直到p为空,删除now结点
/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param n int整型 
     * @return ListNode类
     */
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        // write code here
        ListNode* res = new ListNode(-1);
        res->next = head;
        ListNode* now = head;//当前结点
        ListNode* pre = res;//当前结点的前一个结点
        ListNode* p = head;//相隔n结点
        while(n--){
            p = p->next;
        }
        while(p!=NULL){
            p = p->next;
            now = now->next;
            pre = pre->next;
        }
        pre->next = now->next;//删除结点
        return res->next;
        
    }
};

5.链表相加

描述

假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
给定两个这种链表,请生成代表两个整数相加值的结果链表。

数据范围:0≤n,m≤1000000,链表任意值 0≤val≤9
要求:空间复杂度 O(n),时间复杂度 O(n)

解题思路:

  • 先反转
  • 应用大数加法的逻辑
  • 注意:建立新的链表更适合
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head1 ListNode类 
     * @param head2 ListNode类 
     * @return ListNode类
     */
    
    //将链表先反转 然后再从头开始相加
    
    ListNode* reverseList(ListNode* head){
        if(head == NULL)
            return head;
        if(head->next == NULL){
            return head;
        }
            
        ListNode* pre = new ListNode(0);
        pre->next = head;
        ListNode* p = head->next;//从头结点的下一个结点开始一个一个头插法

        while(p != NULL){
            head->next = p->next;
            p->next = pre->next;
            pre->next = p;
            p = head->next;
        }
        return pre->next;
    }
    
    ListNode* addInList(ListNode* head1, ListNode* head2) {
        // write code here
        if(head1 == NULL)
            return head2;
        if(head2 == NULL)
            return head1;
        //反转链表
        ListNode* p1 = reverseList(head1);
        ListNode* p2 = reverseList(head2);
        ListNode* res = new ListNode(0), *cur = res;
        int flag = 0;
        while(p1 || p2 || flag){
            int x,y;
            if(p1)
                x = p1->val;
            else
                x = 0;
            if(p2)
                y = p2->val;
            else
                y = 0;
            int sum = x+y+flag;
            flag = sum/10;
            sum = sum%10;
            cur->next = new ListNode(sum);
            cur = cur->next;
            if(p1) p1=p1->next;
            if(p2) p2=p2->next;
        }
        res = res->next;
        res = reverseList(res);
        return res;   
    }
};

较难

合并k个已排序的链表

解题思路:

在两个有序链表合并的基础上,依次合并----时间复杂度高
解决办法:折半法
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:

    //合并两个链表
    struct ListNode* Merge(struct ListNode* pHead1, struct ListNode* pHead2){
    
    if(pHead1 == NULL)
        return pHead2;
    if(pHead2 == NULL)
        return pHead1;
    
    //构建新结点
    struct ListNode* C = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* Ct;
    Ct = C;
    
    //头结点包含数据
    while(pHead2 != NULL && pHead1 !=NULL)
    {
        if(pHead1->val <= pHead2->val){
            Ct->next = pHead1;
            pHead1 = pHead1->next;
            Ct = Ct->next;}
        else {
            Ct->next = pHead2;
            pHead2 = pHead2->next;
            Ct = Ct->next;
        } 
    }
    Ct->next = NULL;
    if(pHead1 != NULL){
        Ct->next = pHead1;
    }
    if(pHead2 != NULL){
        Ct->next = pHead2;
    }    
     return C->next;
}
    ListNode *mergeKLists(vector<ListNode *> &lists) {
      //  ListNode *res = nullptr;
      //  for(int i=0; i<lists.size(); i++){
      //      res = Merge(res, lists[i]);
      //  }
      //  return res;
        //超出时间
        return divideMerge(lists, 0, lists.size()-1);
    }
    //划分合并区间函数
    ListNode *divideMerge(vector<ListNode *> &lists, int left, int right){
        if(left > right)
            return NULL;
        else if(left == right)
            return lists[left];
        
        //从中间分成两段,再讲合并好的两端合并
        int mid = (left+right)/2;
        return Merge(divideMerge(lists, left, mid), divideMerge(lists, mid+1, right));
    }
};

设计LRU缓存结构描述

描述

设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为 capacity ,操作次数是 n ,并有如下功能:
1. Solution(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
2. get(key):如果关键字 key 存在于缓存中,则返回key对应的value值,否则返回 -1 。
3. set(key, value):将记录(key, value)插入该结构,如果关键字 key 已经存在,则变更其数据值 value,如果不存在,则向缓存中插入该组 key-value ,如果key-value的数量超过capacity,弹出最久未使用的key-value

提示

提示:

1.某个key的set或get操作一旦发生,则认为这个key的记录成了最常使用的,然后都会刷新缓存。
2.当缓存的大小超过capacity时,移除最不经常使用的记录。
3.返回的value都以字符串形式表达,如果是set,则会输出"null"来表示(不需要用户返回,系统会自动输出),方便观察
4.函数set和get必须以O(1)的方式运行
5.为了方便区分缓存里key与value,下面说明的缓存里key用""号包裹
    //构造结点
    struct Node{
        int key;
        int val;
        Node* pre;
        Node* next;
        //初始化
        Node(int k, int v): key(k), val(v), pre(NULL), next(NULL){};
    };
    

class Solution {
public:
    //全局变量,记录双向链表的头和尾、LRU剩余的大小
    int size = 0;
    int cap;
    Node* head = NULL;
    Node* tail = NULL;//双向链表
    //哈希表
    unordered_map<int, Node*> mp;
    
    Solution(int capacity){
         // write code here
        //初始化
        size = 0;
        cap = capacity; 
        head = new Node(0,0);
        tail = new Node(0,0);
        head->next = tail;
        tail->pre = head;
        mp.clear();
    }
    
    int get(int key) {
         // write code here
        if(mp.count(key)){
            movehead(mp[key]);
            return mp[key]->val;
        }
        else 
            return -1;
    }
    
    void set(int key, int value){
         // write code here
        if(mp.count(key)){
            movehead(mp[key]);
            mp[key]->val = value;
        }
        else{
            size++;
            if(size > cap){
                //缓存已满,更新尾,将尾删除
                Node* ans = deletenode();
                if(mp.count(ans->key)){
                    mp.erase(ans->key);
                    delete ans;
                    size--;
                }
                Node* t = new Node(key, value);
                mp[key] = t;
                addhead(t);
            }
            else{
                //内存未满,直接在头部插入
                Node* t  = new Node(key, value);
                mp[key] = t;
                addhead(t);
            }
        }
    }

    //移到头结点
    void movehead(Node* p){
        removenode(p);
        addhead(p);
    }

    //移除结点
    void removenode(Node* p){
        p->next->pre = p->pre;
        p->pre->next = p->next;
    }

    //添加头结点
    void addhead(Node* p){
        p->pre = head;
        p->next = head->next;
        head->next->pre = p;
        head->next = p;
    }
    
    Node* deletenode(){
        Node* t = tail->pre;
        removenode(t);
        return t;
    }
    
};

关于哈希表的用法

unordered_map是一个将key和value关联起来的容器,它可以高效的根据单个key值查找对应的value。
key值应该是唯一的,key和value的数据类型可以不相同。
定义:
unordered_map<int, Node*> haxi;
增加:
haxi.insert(p);
删除
haxi.erase(1);//根据key删除
查找
haxi(count(key))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值