链表题目汇总~ 持续更新(2021.06.05)

前言:
  本篇针对链表的一些场用题目做一个汇总和总结,链表的场用操作如删除、合并、插入、查找、排序等等,善于使用头结点dump来定位,游走节点temp来操作,一些链表题目经常可以用用到递归的思路返回,一般是head->next后接递归等等,同时注意到链表中每个元素是可以存入哈希表的,他们和数字一样也存在唯一性!

1.Leetcode21 合并两个有续链表

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    ListNode * dump=new ListNode(-1);
    ListNode*  pre=dump;
    while(l1!=nullptr && l2!=nullptr){
        if(l1->val<=l2->val){
           pre->next=l1;
           l1=l1->next; //没事 这个l1等于l1的next保证了后面的元素不会丢失,我已经next了 相当于位置索引,之前在新链表的元素势必会给他一个归属的!没事就是说他的next会安排
        }
        else{
            pre->next=l2;
            l2=l2->next;
        }
        pre=pre->next;
    }
       pre->next= l1==nullptr ? l2:l1;
       return dump->next;
    }
};

合并两个有续链表的另外一种方法是通过建立新的链表,不要把链表看成地址,而是看成一个结构体存着下次的地址,他们整体构成了一个数组而已嘛。所以可以在vector或者栈、队列中存他们本身,或者存他的val,然后根据val来造一个新的“数组”就是链表。有些是在原来链表上操作,相当于把数组顺序换下。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        priority_queue<int,vector<int>,greater<int> >q;
        while(l1){
            q.push(l1->val);
            l1=l1->next;
        }
        while(l2){
            q.push(l2->val);
            l2=l2->next;
        }
        /
        ListNode* dump=new ListNode(-1);
        ListNode* temp=dump;
        while(!q.empty()){
            temp->next=new ListNode(q.top());
            q.pop();  
            temp=temp->next;      
        }
        return dump->next;
    }
};
在这里插入代码片

2.Leetcode203 移除链表元素
题目: 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
思路: 注意到链表的删除操作通常需要通过他的前一个结点辅助操作,如temp->next=temp->next->next. 假设要删除的元素在第一个,我们必须要制造一个dump头结点指向head。同时dump->next也给我们最终返回找到方向。所以dump不能移动,再生成一个temp=dump来移动操作即可。循环过程中注意到我们对初始点开始判断肯定是while(temp->next)但是是不是每一次temp都要移动呢,如果删除元素了,然后temp当前值是null,你再判断temp的next会出错。所以删除了就不移动。不删除那说明next没问题移动。

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dump=new ListNode(0);
        dump->next=head;
        ListNode* temp=dump;
        
        while(temp->next){
            if(temp->next->val==val)
               temp->next=temp->next->next;
            else
            temp=temp->next;
        }
        return dump->next;
    }
};

此外,链表的题目通常用递归来解决,从后往前面判断返回,思路就是如果等于当前如果head和val相同就返回下一个节点,不然就返回当前的,然后下一个节点我也做同样的操作,注意操作后是返回给上一层,上一层属于head的next,所以有递归的趋势,就在前面增加一个head->next=。

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if (head == nullptr) {
            return head;
        }
        head->next = removeElements(head->next, val);
        return head->val == val ? head->next : head;
    }
};

3.Leetcode 160:相交链表
题目: 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

方法1:哈希集合
  判断两个链表是否相交,可以使用哈希集合存储链表节点。
  首先遍历链表 \textit{headA}headA,并将链表 \textit{headA}headA 中的每个节点加入哈希集合中。 
  然后遍历链表 \textit{headB}headB,对于遍历到的每个节点,判断该节点是否在哈希集合中:
  如果当前节点不在哈希集合中,则继续遍历下一个节点;
  如果当前节点在哈希集合中,则后面的节点都在哈希集合中,即从当前节点开始的所有节点都在两个链表的相交部分,因此在链表 \textit{headB}headB 中遍历到的第一个在哈希集合中的节点就是两个链表相交的节点,返回该节点。
  如果链表 \textit{headB}headB 中的所有节点都不在哈希集合中,则两个链表不相交,返回 NULL

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    if(!headA || !headB)
    return NULL;
    unordered_set<ListNode*> visited;
    ListNode* temp=headA;
    while(temp!=NULL){
        visited.insert(temp);
        temp=temp->next;
    }
    temp=headB;
    while(temp!=NULL){
        if(visited.count(temp))
          return temp;
        temp=temp->next;
    }
    return NULL;
    }
};

方法2:双指针,就是两个指针分别走,走到链表头走另外一条链表,走到交点时候步数一样,所以等链表位置相同了,就返回。注意到链表的元素是否相同不是看val 直接拿出来比较就行,这是包含地址的比较! 放到哈希表里面也是,这是链表中的一个元素,包含地址,和元素值!

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == nullptr || headB == nullptr) {
            return nullptr;
        }
        ListNode *pA = headA, *pB = headB;
        while (pA != pB) {
            pA = pA == nullptr ? headB : pA->next;
            pB = pB == nullptr ? headA : pB->next;
        }
        return pA;
    }
};

方法3:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode*dumpA=headA;
        ListNode*dumpB=headB; int len1=0,len2=0;
        while(dumpA || dumpB){
            if(dumpA){
                len1++;
                dumpA=dumpA->next;
            }
            if(dumpB){
                len2++;
                dumpB=dumpB->next;
            }
        }
        int diff=len1-len2;
        if(diff>0){
            dumpA=headA;
            dumpB=headB;
            for(int i=0;i<diff;i++)
                dumpA=dumpA->next;
            while(dumpA){
                if(dumpA==dumpB)
                    return dumpA;
                dumpA=dumpA->next;
                dumpB=dumpB->next;
            }
        }
        else{
            dumpA=headA;
            dumpB=headB;
            for(int i=0;i<-diff;i++)
                dumpB=dumpB->next;
            while(dumpB){
                if(dumpB==dumpA)
                    return dumpB;
                dumpA=dumpA->next;
                dumpB=dumpB->next;
            }  
        }
        return nullptr;
    }
};

4.面试题 02.05. 链表求和
题目:
   给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。
思路:
   一开始想用栈来做,然后把数字都取出来算出来后返回,后来在最后几个测试中失败了,数字太长了,无语!!!!直说不让取得了,那就不能取出来了。下面附上取出来的操作:

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    //用栈先入后出
    stack<ListNode*>stack1;
    int count2=0;
    while(l1){
      stack1.push(l1);
      l1=l1->next;
    }
    while(l2){
      stack1.push(l2);
      l2=l2->next;
      count2++;
    }
    long sum2=0;
    int size=stack1.size();
    for(int i=1;i<=count2;i++){
        sum2=sum2*10+stack1.top()->val;
        stack1.pop();
    }
    long sum1=0;
     for(int i=1;i<=size-count2;i++){
        sum1=sum1*10+stack1.top()->val;
        stack1.pop();
    }
   long sum=sum1+sum2;
    //新链表
    ListNode* newlist=new ListNode;
    ListNode* temp=newlist;
    if(sum==0){
      temp->next=new ListNode;
      temp->next->val=0;
    }
      
    while(sum){
      temp->next=new ListNode;
      temp->next->val=sum%10;
      temp=temp->next;
      sum=sum/10;
    }
    return newlist->next; //每次都只对next操作就行了。
    }
};

好,既然不能取出来,那就直接操作,这个数字是倒着放的,我们直接按照加法规律每次两个链表都往前进,对当前位计算加法值然后赋值给新链表。递归下一个位置,传入count进位,直到两个链表都为null,且进位为0即可。注意到链表题目的递归一般都是res->next=递归表达式,每次对当前值赋值最后return res;

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        return addTwoNumbersCore(l1, l2, 0);
    }

    ListNode* addTwoNumbersCore(ListNode* l1, ListNode* l2, int carry) {
        if (!l1 && !l2 && carry == 0) {
            // 当输入的节点均为null且无需处理进位时,结束
            return nullptr;
        }
        int val = carry + (l1 ? l1 -> val : 0) + (l2 ? l2 -> val : 0); // 计算当前的和
        auto res = new ListNode(val % 10);
        res -> next = addTwoNumbersCore((l1 ? l1 -> next : nullptr), (l2 ? l2 -> next : nullptr), val / 10);
        return res;
    }
};

==面试题02.04.分割链表 ==
题目:
  编写程序以 x 为基准分割链表,使得所有小于 x 的节点排在大于或等于 x 的节点之前。如果链表中包含 x,x 只需出现在小于 x 的元素之后(如下所示)。分割元素 x 只需处于“右半部分”即可,其不需要被置于左右两部分之间。
思路:
  注意到这种题目,相当于对链表做个自定义的排序。通常我们需要借助其他的手段对链表做处理,最常见的操作是生成指向链表头部的节点,用这个来不断改变链表的结构产生几个字链表。
  这道题目可以采用模拟的方法,我们可以创建两个dump节点,分别用来连接val值大于等于目标x的链表元素和val值小于目标值x的元素。然后将这两个子链表的头尾相连即可。注意到在连接过程中我们需要规避一个链表出现两次连接方向,或者说链表连错错乱一个链表元素收到两次的连接。因为我们的双链表元素是原始链表的完备情况,所以不存在漏解,而且我们应该就是确定了某个元素是这条链表的,那这条链表的next指向它,什么意思?注意到这条链表的已有元素本身存在一个指向,我现在改变他的指向了。但是注意到 尽管中间过程可以改变他的指向,但是每条链表的最后一个元素,他的指向我们没改变!! 所以两条链表的next都要给他给null。new的产生只需要在刚开始。
而且有人问为什么用头结点不直接产生一个节点呢。因为我们的初衷是这个链表节点判断可行我们加入。然后链表要移动!移动! 如果直接是small=temp,那small的下一个位置如何确定呢,循环中无法给出!所以一开始就是small的next,然后不断让small回到已经完备的位置,每次都很安心。如果循环中是temp的next,那开始的节点怎么办呢 要处理啊。包括这篇文章的第二个问题,循环中也是原始链表的第一个,只不过用dump的next!

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
    //用模拟的思路,遍历原始链表
    //创建两个新链表,一个存比目标值小于等于的,一个存比目标值大的,最后合并就行了。
    ListNode* small=new ListNode;
    ListNode* large=new ListNode;
    ListNode* sm1=small;
    ListNode* lar1=large;
    ListNode* temp= new ListNode;
    temp=head;
    while(temp){
       if(temp->val<x){
           small->next=temp;
           small=small->next;
       }
       else{
           large->next=temp;
           large=large->next;
       }
       temp=temp->next;
    }
    // 合并   
       large->next=NULL;
       small->next=NULL;
       small->next=lar1->next;
       return sm1->next;
    }
};

—————————————————————————————————————————————————————————————
NC50 链表中的节点每k个一组翻转


class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        if(head==nullptr || !head->next || k<2) return head;
        stack<ListNode*>st;
        ListNode* cur=head;
        ListNode* tmp=new ListNode(0);
        ListNode* node=tmp;
        while(head!=nullptr)
        {
            cur=head;
            int i=k;
            while(i>0 && head!=nullptr)
            {
                st.push(head);
                head=head->next;
                i--;
            }
            if(st.size()==k)
            {
                while(!st.empty())
                {
                    tmp->next=st.top();
                    st.pop();
                    tmp=tmp->next;
                    tmp->next=nullptr; //增加一行!!
                }
            }
            else
            {
                tmp->next=cur;
                break;
            }
        }
        return node->next;
    }
};
class Solution {
public:
    /// 参考翻转pairs,翻转x~x+(k-1)之间的节点, x->next = reverseKGroup(x+k,k)
    ListNode* reverse(ListNode *first,ListNode *last)
    {
        ListNode *pre = nullptr;
        while(first!=last)
        {
            ListNode *temp = first->next;
            first->next = pre;
            pre = first;
            first = temp;
        }
        return pre;
    }
    ListNode *reverseKGroup(ListNode *head, int k) 
    {
        if(!head)
            return nullptr;
        ListNode *node = head;
        for(int i=0;i<k;i++)
        {
            if(!node)
                return head;
            node = node->next;
        }
        ListNode *newHead = reverse(head,node);
        head->next = reverseKGroup(node,k);
        return newHead;
    }
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值