剑指---链表篇(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结点。又因为我们我们要指向前一个结点,所以要定义一个指针指向前一个结点。最后我们用三个指针进行操作。
创建 左 中 右 三个指针,中间指针不断指向前一个(左指针),右边指针来判断移动边界!如下图所示:在这里插入图片描述
方法二: 最简单的一种方式就是使用栈,因为栈是先进后出的。实现原理就是把链表节点一个个入栈,当全部入栈完之后再一个个出栈,出栈的时候在把出栈的结点串成一个新的链表。原理如下在这里插入图片描述


代码部分:

/代码模板
/*
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;
    }
};
/方法二
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;
    }
};

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

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


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

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

  • 如果L1指向的结点值小于等于L2指向的结点值,则将l1指向的结点值链接到cur的next指针,然后L1指向下一个结点值
  • 否则,让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) {
    
    

            
    }
};
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;
    }
};

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

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


解题思路:
使用两个指针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;
    }
};

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

猜你喜欢

转载自blog.csdn.net/qq_40077565/article/details/121217416