剑指---链表篇(C++)

链表篇

第一题: 从尾到头打印链表

在这里插入图片描述


解题思路:

方法一: 先将输入的值按组存入到vertor容器中,然后利用reverse函数反转vertor中的数据,最后再返回vertor数组。
方法二: 利用栈先入后出的思想,将输入的元素传入栈中,最后再依次弹出,用一个vector去存储栈结构弹出的元素。


代码部分:

/答题模板
/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        
    }
};
/方法一:
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
      vector<int> result;
        while(head){
            int a=head->val;
            result.push_back(a);
            head=head->next;
        }
        reverse(result.begin(), result.end());
        return result;
    }
};
/方法二
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
      vector<int> vec;  //定义一个vector元素用来存放反转后的元素,反转的方式依靠栈结构
      stack<int> stk;  //从尾到头,类似栈结构的先进后出
        ListNode *p = head;  //将传入的元素赋给p
        if(head)
        {
            while(p)
            {
                stk.push(p->val);
                p=p->next;
            }
            while(!stk.empty())
            {
                vec.push_back(stk.top());  //将栈顶元素添加到vector容器中
                stk.pop();  //已经传出的栈顶元素就弹出
            }
        }
        return vec;
    }
};

第二题: 反转链表

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


解题思路:
方法一: 利用三个指针,在原链表上变换 (这个有点复杂,可以看官方解析
这道题其实就是经过一轮循环,把每个指针的next指向前一个结点。但是我们要考虑到,如果把next指向前一个结点,那么原本的next结点就找不到了,所以我们要在赋值之前保存next结点。又因为我们我们要指向前一个结点,所以要定义一个指针指向前一个结点。最后我们用三个指针进行操作。
创建 左 中 右 三个指针,中间指针不断指向前一个(左指针),右边指针来判断移动边界!如下图所示:在这里插入图片描述
方法二: 最简单的一种方式就是使用栈,因为栈是先进后出的。
不过方法二也有两种小方法,一是使用栈的类型为int型栈,直接将栈中的元素进行修改;二是使用栈的类型为指针类型,将指针朝向进行修改。
实现原理就是把链表节点一个个入栈,当全部入栈完之后再一个个出栈,出栈的时候在把出栈的结点串成一个新的链表。原理如下在这里插入图片描述


代码部分:

/代码模板
/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {

    }
};
/方法一
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode *pre = nullptr;
        ListNode *cur = pHead;
        ListNode *nex = nullptr; // 这里可以指向nullptr,循环里面要重新指向
        while (cur) {
            nex = cur->next;
            cur->next = pre;
            pre = cur;
            cur = nex;
        }
        return pre;
    }
};
//方法二-1
//这种思路是直接修改指针内部的val
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        stack<int> sta;    //这里注意,定义的栈是int型的,不是ListNode*型的
        ListNode* p=head;
        while(p!=NULL)     //借助p将head中的元素全部压入栈中
        {
            sta.push(p->val);
            p=p->next;
        }    
        p=head;     //重新将p指向head位置,再借助p去修改head中的值
        while(!sta.empty())
        {
            p->val=sta.top();
            sta.pop();
            p=p->next;
        }
        return head;   
    }
};
//这种方法不用像其他借助栈的方法一样,最后一个指针还要置空
/方法二-2
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(!pHead || !pHead->next)
       	return pHead;
    stack<ListNode*>stk;
    //将链表的结点全部压进栈
    while(pHead){
        stk.push(pHead);
        pHead = pHead->next;
    }
    ListNode*cur,*nxt;
    cur = nxt = stk.top();
    stk.pop();
    while(!stk.empty()){
        nxt ->next = stk.top();
        nxt = nxt ->next;
        stk.pop();
    }
    //最后一个结点的next记得要指向nullptr,否则会形成环
    nxt ->next =nullptr;
    return cur;
    }
};

第三题: 合并两个排序的链表

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


解题思路:
方法1:迭代法
两个链表L1L2,从两个链表的第一个元素开始比较,两者中的较小者传递给新链表,传递完之后,链表指向下一个元素,新链表也要指向下一个空元素,用于接受下一次这两者中较小的比较值。

初始化: 定义cur指向新链表的头结点
小技巧: 一般创建单链表,都会设一个虚拟头结点,也叫哨兵,因为这样每一个结点都有一个前驱结点。具体表现为代码中的第四行
操作:

  • 如果L1指向的结点值小于等于L2指向的结点值,则将l1指向的结点链接到cur的next指针,然后L1指向下一个结点值
  • 否则,将l2指向的结点链接到cur的next指针,让L2指向下一个结点值
  • 循环上面两个步骤,直到L1或者L2为nullptr
  • L1或者L2剩下的部分链接到cur的后面

方法二: 利用数组进行排序,然后赋值给新链表

代码部分:

/代码模板
/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {

            
    }
};
//方法1
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2){
        ListNode *vhead = new ListNode(-1);
        ListNode *cur = vhead;
        while (pHead1 && pHead2) {
            if (pHead1->val <= pHead2->val) {
                cur->next = pHead1;
                pHead1 = pHead1->next;
            }
            else {
                cur->next = pHead2;
                pHead2 = pHead2->next;
            }
            cur = cur->next;
        }
        cur->next = pHead1 ? pHead1 : pHead2;
        return vhead->next;
    }
};
//方法二
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        if(pHead1 == nullptr || pHead2 == nullptr){     //没有元素的话,数组为空,就不能排序,所以要先判断
            return pHead1 == nullptr ? pHead2 : pHead1;
        }
        vector<int> arr;
        while(pHead1 != nullptr){
            arr.push_back(pHead1 -> val);
            pHead1 = pHead1 -> next;
        }
        while(pHead2 != nullptr){
            arr.push_back(pHead2 -> val);
            pHead2 = pHead2 -> next;
        }
        sort(arr.begin(),arr.end());
        ListNode* res = new ListNode(0);
        ListNode* p = res;
        for(int i = 0; i < arr.size(); i++){
            p -> next = new ListNode(arr[i]);
            p = p -> next;
        }
        return res->next;
    }
};

第四题: 两个链表的第一个公共结点

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


解题思路:
使用两个指针N1,N2,一个从链表1的头节点开始遍历,我们记为N1,一个从链表2的头节点开始遍历,我们记为N2。

让N1和N2一起遍历,当N1先走完链表1的尽头(为null)的时候,则从链表2的头节点继续遍历,同样,如果N2先走完了链表2的尽头,则从链表1的头节点继续遍历,也就是说,N1和N2都会遍历链表1和链表2。

因为两个指针,同样的速度,走完同样长度(链表1+链表2),不管两条链表有无相同节点,都能够到达同时到达终点

(N1最后肯定能到达链表2的终点,N2肯定能到达链表1的终点)。

所以,如何得到公共节点:

有公共节点的时候,N1和N2必会相遇,因为长度一样嘛,速度也一定,必会走到相同的地方的,所以当两者相等的时候,则会第一个公共的节点
无公共节点的时候,此时N1和N2则都会走到终点,那么他们此时都是null,所以也算是相等了。
下面看个动态图,可以更形象的表示这个过程~在这里插入图片描述


代码部分:

/答题模板
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        
    }
};
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        ListNode *l1 = pHead1, *l2 = pHead2;  
        while(L1 != L2){                    
            L1 = (NULL==L1)?pHead2:L1->next; 
            L2 = (NULL==L2)?pHead1:L2->next;
        }
        return L1;
    }
};

代码解读:
第4行:定义了两个链表头指针
第五行:如果l1不等于l2,说明两者指向不同,继续执行循环体中的语句,因为在解题思路中说过了,不管两条链表有无相同节点,都能够到达同时到达终点,所以这个while循环在满足条件后一定会退出,如果推出后返回的是{},说明两个链表没有公共节点,否则返回的就是两者的公共节点。
第六行和第七行:如果当前指针的内容为空,那么指针指向对方的头节点,否则继续指向自身链表的下一个节点


第五题: 链表中环的入口节点(先做第十题)

在这里插入图片描述
在这里插入图片描述
解题思路:
方法一: 利用哈希表

  • 遍历单链表的每个结点
  • 如果当前结点地址没有出现在set中,则存入set中
  • 否则,出现在set中,则当前结点就是环的入口结点
  • 整个单链表遍历完,若没出现在set中,则不存在环

方法二: 双指针(不推荐,数学烧脑加上比较特殊,泛用性不强)
想看具体解法点击此处


代码部分:

/答题模板
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {

    }
};
/方法一
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
    unordered_set<ListNode*> st;   //定义一个无序集合,用来记录出现的结点
        while(pHead){   //如果pHead为空,说明是单链表
            if(st.find(pHead)==st.end()){   //查看刚插入的数是否存在于集合中
                st.insert(pHead);   //将当前pHead指向的元素插入到集合中
                pHead=pHead->next;
            }
            else{
                return pHead;
            }
        }
        return nullptr;
    }
};

第六题: 链表中倒数最后k个结点

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

解题思路:
方法一: 数组法,将输入的集合中的数的每个地址用一个数组保存起来,然后根据k值,直接返回数组中第k个及之后的数。
方法二: 双指针法,该方法来自牛客网其中的一个解题思路。

在这里插入图片描述


代码部分:

/代码模板
/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 *	ListNode(int x) : val(x), next(nullptr) {}
 * };
 */
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pHead ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
     这里要返回的类型是ListNode*
    ListNode* FindKthToTail(ListNode* pHead, int k) {
        // write code here
    }
};
/方法一
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pHead, int k) {
        vector<ListNode*> res;   //定义一个数组,里面的变量是指针类型
        for (; pHead; pHead = pHead->next)
            res.emplace_back(pHead); // 把每个节点指针放入数组,
        if (k > res.size() || !k) return nullptr; // 判断k值是否合法
        return res[res.size() - k];  //返回
    }
};

解释1for循环语句括号中的各表达式可以省略,但表达式之间的
间隔符 ( 分号 )不能缺省

解释2:
在 C++11 之后,vector 容器中添加了新的方法:emplace_back() ,
和 push_back() 一样的是都是在容器末尾添加一个新的元素进去,不同的是
 emplace_back() 在效率上相比较于 push_back() 有了一定的提升。

过程:
输入:{1,2,3,4,5},2
一开始的时候:pHead指向1的地址,此时执行res.emplace_back(pHead);
res[0]=[1,2,3,4,5];
pHead=pHead->next;
此时pHead指向2的地址,继续执行res.emplace_back(pHead);
res[1]=[2,3,4,5];
res[2]=[3,4,5];
res[3]=[4,5];
res[4]=[5];

最后返回的是return res[res.size() - k];
因为res.size()=5,故返回的是res[3],正好是45
/方法二
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pHead, int k) {
        ListNode* r = pHead;
     while (k-- && r){
         r = r->next; // 移动右侧指针造成 k 的距离差
      }
     if (k >= 0) return nullptr; // 此时说明是因为r指向的是NULL,while才退出的,即k比链表长度长
     ListNode* l = pHead;
     while (r){
         r = r->next, l = l->next; // 两个指针一起移动找到倒数第 k 个节点
     }
     return l;
    }
};

第七题: 删除链表的节点

在这里插入图片描述

解题思路:
首先,如果要删除的是头节点,那么我们只需把头节点head指向下一个节点即可;如果要删除的节点是在链表内的,那么可以用下图的思想去进行删除。在这里插入图片描述
在上图中,假设我们要删除的是i节点,那么我们只需要把h节点的指向从i改成j


代码部分:

class Solution {
public
    ListNode* deleteNode(ListNode* head, int val) {
        if(head->val==val){  //如果头节点就是要删除的节点,那么直接把头节点向后移
            return head->next;
        }
        ListNode *cur=head;
        ListNode *res=cur;//指向cur的初始位置
        while(cur->next){//cur指针不断移动
            if(cur->next->val==val){  //这里可以看成是h->next=i中的值
                ListNode *temp=cur->next->next; //那么需要将h指向j
                cur->next=temp;
            }
            cur=cur->next;
        }
        return res;
    }
};

第八题: 删除有序链表中重复的元素-I

在这里插入图片描述

解题思路:
假设当前链表中有以下元素:1,1,2
定义一个*p=head
判断指针p->next->val和p->val是否相等
如果相等,则将指针p->next=p->next->next,


代码部分:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */
class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    ListNode* deleteDuplicates(ListNode* head) {
        if(head==NULL){  //如果链表长度为0或1,直接返回结果
            return head;
        }
        ListNode *cur=head;
        while(cur->next){//cur指针不断移动
            if(cur->val==cur->next->val){  //如果链表的下下个节点=下个节点
                cur->next=cur->next->next; //那么需要将h指向j
            }
            else{
                cur=cur->next;
            }
        }
        return head;
    }
};

第九题: 删除链表中的节点(很牛逼)

在这里插入图片描述
这道题目乍一看很简单,但是思考了两分钟后我没想出一点头绪,于是去看评论区,好家伙,一语惊醒梦中人!
如何让自己在世界上消失,但又不死? —— 将自己完全变成另一个人,再杀了那个人就行了。
解题思路:
将后面结点的值替换为当前的,然后将当前链表指向下下个结点。


代码部分:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    void deleteNode(ListNode* node) {
        node->val=node->next->val;
        node->next=node->next->next;
    }
};

第十题: 环形链表

在这里插入图片描述
在这里插入图片描述
解题思路:
解法一:哈希表
从头节点开始,使用哈希表记录下每个节点的地址,如果出现了地址重复的情况,说明这个链表形成了一个环。(set也是哈希表的类型)
代码部分:

class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> seen;
        while (head != nullptr) {
            if (seen.count(head)) {  //count()判断元素是否在set容器中。若在:返回1,若不在,返回0。
                return true;
            }
            seen.insert(head);  //注意这里插入的是链表的指针,而不是节点中的值,所以即使链表中有重复元素也没关系
            head = head->next;
        }
        return false;
    }
};

解法二:双指针
慢指针一次只走一步,快指针一次走两步,如果是一个环的话,那么一定会相遇,即两个指针所指向同一个地址。
代码部分:

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head==nullptr||head->next==nullptr){
            return false;
        }
        ListNode * p1=head;
        ListNode * p2=head;
        while(p2!=nullptr){
            if(p2->next==nullptr){  //不加这行代码的话,走两步可能会出问题,因为p2->next可能为空
                return false;
            }
            p2=p2->next->next;  //快指针每次走两步
            p1=p1->next;
            if(p1==p2){
                return true;
            }
        }
        return false;
    }
};


第十一题: 判断一个链表是否为回文结构

在这里插入图片描述
解题思路:
解法一:栈结构
解题思路:可以先复制当前链表,然后将其中的元素传入到栈中,再将栈中的元素依次弹出和原始链表进行值得比较
优化: 不需要全部比较,因为是回文字符,我么们只需要比较一半长度即可。

class Solution {
public:
    bool isPail(ListNode* head) {
        stack<int> sta;    //这里注意,定义的栈是int型的,不是ListNode*型的
        ListNode* p=head;
        while(p != nullptr){
            sta.push(p->val);
            p = p -> next;
        }
        while(head != nullptr){
            int a = sta.top();
            sta.pop();
            if(head -> val != a){
                return false;
            }
            head = head -> next;
        }
        return true;
    }
};
--优化
class Solution {
public:
    bool isPail(ListNode* head) {
        stack<int> sta;    //这里注意,定义的栈是int型的,不是ListNode*型的
        ListNode* p=head;
        int len = 0;
        while(p != nullptr){
            sta.push(p->val);
            p = p -> next;
            len += 1;
        }
        len = len /2;
        while(len != 0){
            int a = sta.top();
            sta.pop();
            if(head -> val != a){
                return false;
            }
            head = head -> next;
            len -= 1;
        }
        return true;
    }
};

解法2:
将链表中的值复制到数组中,然后使用双指针法

class Solution {
public:
    bool isPail(ListNode* head) {
        vector<int> arr;
        ListNode* p = head;
        while(p != nullptr){
            arr.push_back(p -> val);
            p = p -> next;
        }
        int len = arr.size();
        for(int i = 0; i < len/2; i++){
            if(arr[i] != arr[len - i - 1]){
                return false;
            }
        }
        return true;
    }
};

注: 以上题目都是在牛客网的剑指offer题库中刷的,有自己做的,也有不会做看别人精华解题思路,然后总结的。如果侵犯了您的权益,请私聊我。
最后,觉得本文内容对你有所帮助的话,感谢点赞收藏!
导航链接:剑指—队列&栈篇(C++)
导航链接:剑指—算法—动态规划篇(C++)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

枫恋蝶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值