05.链表高频面试题目

目录

链表简介

面试题总体分析

一些例题

    例1.链表的插入与(懒)删除

    例2.链表翻转

    例3.单链表找环及起点和环长度(经典)LeetCode-141+142

    例4.两个链表找交点

    例5.复制带有随机指针的链表 

    例6.链表partition过程

总结

 

链表简介

链表:一个元素和下一个元素靠指针连接(松散),不能以O(1)直接访问到第k个元素 

单(向)链表、

双(向)链表、

循环(单、双)链表、

java:LinkedList、 C++:STL list    C:指针

面试题总体分析

链表的基本操作:
插入、删除、(分组)翻转、排序partition、归并、复制、归并排序

找环,起点,长度、 (倒数)第k个节点、随机返回一个节点、和其他数据结构(二叉树)互相转换 

 

一些例题

    例1.链表的插入与(懒)删除

//在head之前插入
now->next=head;
head=now;
 
//一般情况:在pre后面插入
now->next=pre->next;
pre->next=now;
//找到待删除节点之前的那个节点
//删除head
temp=head->next;
delete head;
head=temp;
//一般情况
temp=pre->next;
pre->next=temp->next;
delete temp;

思考:双向链表插入,删除、循环有序链表的插入删除、建议断开再连上、

懒删除,

现在已经到了now节点;

要删除now这个节点(不是last-node)

把now复制成now->next( now->x=now->next->x;) 

delete now->next;

    例2.链表翻转

单链表翻转,很简单,不多说

//单链表翻转:把当前节点head拿过来作为已经翻转结果result的表头
LisiNode *result=0;
while(head){
    temp=head->next;
    head->next=result;
    result=head;
    head=temp;

}
return result;

思考:

如何翻转部分链表;LeetCode-92

  如何找到第m个元素和第n个元素

  如何处理前面和后面?保存前一部分的最后一个元素,后一部分的首个元素,特殊情况呢?

每k个元素翻转一次; Leetcode-25

  前面翻好的部分(小链表)

  要翻转的部分(k个)

  后面没处理的部分(小链表)

  不足k个怎么办?

 

    例3.单链表找环及起点和环长度(经典)LeetCode-141+142

单链表里是否有环?如果有起点在哪?环长度是多大?【最后一个节点next不是空,而是前面某个节点】

方法1. 用一个set存放每个节点的地址

class Solution{
public:
    ListNode * detectCycle(LiistNode *head){
        std::set<ListNode *> node_set;
        while(head){
            if(node_set.find(head)!=end()){
                return head; 
            }
            node_set.push(head);
            headA=headA->next;
        }
       return null;
    }

};

方法2.快慢指针

用两个指针p1,p2,p1每次走一步,p2每次走两步,如果有圈,一定相遇(套圈)

变量:圈长:0 ;起点到圈的起点的距离a ; p1到圈起点时,p2在圈中的位置x(0<=x<=n) 

核心:a+b是圈长的整数倍 

class Solution{
public:
    ListNode * detectCycle(LiistNode *head){
        ListNode *p1=head,*p2=head;
        do{
            if(p1==null || p2==null) return null;
            p2=p2->next->next;
            p1=p1->next;
        }while(p1!=p2);
        //p2停在交点处 p1拉回head
        for(p1=head; p1!=p2; p1=p1->next,p2=p2->next); //再次相遇一定在圈起点
        return p1; //起点
    }

};

 

 

 

    例4.两个链表找交点

已知链表a和链表b(确保a,b没有环)的头结点,两个链表相交,求两个链表的交点,【要求:时间复杂度O(n),空间:O(1)】

方法1.

遍历链表a,将a中的节点的指针(地址),插入set ; 遍历遍历链表b,将b中的节点的指针(地址),在set中查找;

发现的在set中第一个重复的节点地址,就是a、b交点

class Solution{
public:
    ListNode * getIntersectionNode(LiistNode *headA, LiistNode *headB){
        std::set<ListNode *> node_set;
        while(headA){
            node_set.push(headA);
            headA=headA->next;
        }

        while(headB){
            if(node_set.find(headB)!=node_set.end()){
                return headB;
            }
            headB=headB->next;
        }
        return null;
    }

};

 方法2.  空间O(1)

 

    例5.复制带有随机指针的链表 

一个单链表,除了next指针外,还有一个random指针域随机指向任何一个元素(random也可能为空),请复制这个链表 LC-138

难点在于:我们不知道random指针在链表复制后,所指向的地址,——复制链表后,节点的地址变了

方法1.map<旧地址,新地址> 先按照普通方法复制链表;再两个链表同时走,复制random 

(设:旧节点a,新节点a')    a'->random=map[a->random] ; 空单独处理 

class Solution{
public:
    RandomListNode * copyRandomList(RandomLiistNode *head){
       map<RandomListNode *,int>node_map; //地址-节点位置 映射
       vector<RandomListNode *> node_vec;  //根据节点的index(节点位置)访问新链表的节点地址   node_vec 存新链表 
        RandomListNode * ptr=head; 
        int i=0;
        while(ptr){
            node_vec.push_back(new RandomListNode(ptr->label)); 
            //if(i) node_vec[i-1]->next=node_vec[i];
            node_map[ptr]=i; //记录原始链表节点地址-节点位置的映射 
            ptr=ptr->next;
            i++;
        }
        
        node->vec.push_back(null); 
        ptr=head;
        i=0;
         while(ptr){
            if(i) node_vec[i-1]->next=node_vec[i];
            if(ptr->random){
                int index=node_map[ptr];
                node_vec[i]->random=node_vec[index];
            }

            ptr=ptr->next;
            i++;
        }
            return node-vec[0];
    }
};

 

    例6.链表partition过程

链表里存放整数,给定x把比x小的节点扔到x左边,大的扔到x右边  LC-86

 

class Solution{
public:
    ListNode * partition(LiistNode *head,int x){
       ListNode * h1=null,* t1=null,*h2=null,*t2=null;
       while(head){
            if(head->val<x){
                if(t1){
                    t1=t1->next=head;
                }else{
                    h1=t1=head;
                }
            }else if(t2){
                t2=t2->next=head;
            }else{
                h2=t2=head;
            }
            head=head->next;
        }

        if(t2) t2->next=null;
        if(t1) t1->next=null;
           return h1?h1:h2;     
    }
};

续:

两个排序链表的合并;不说了,太简单

多个排序链表的合并; 

方法1.最普通,k个链表按顺序合并k-1次,设有k个链表,平均每个链表有n个节点,时间复杂度: O(k^2*n) 超时

方法2.将所有节点(k*n个)放到vector中,再将vector排序,再将节点顺序相连,时间复杂度: O(kn*logkn)

 方法3.分而治之思想(类似归并排序)

class Solution{
public:
    ListNode * mergeKLists(vector<ListNode *>& lists){
        if(lists.size()==0) return null;
        if(lists.size()==1) return lists[0];
        if(lists.size()==2) return mergeTwoLists(lists[0],lists[1]);
        int mid=lists.size()/2;
        vector<ListNode*> sub1_lists;
        vector<ListNode*> sub1_lists; //把lists拆分为两个子list
        for(int i=0;i<mid;i++){
            sub1_lists.push_back(lists[i]);
        }
        
        for(int i=mid;i<lists.size();i++){
            sub2_lists.push_back(lists[i]);
        }

        ListNode *l1=mergeKLists(sub1_lists);
        ListNode *l2=mergeKLists(sub2_lists);
        return  mergeTwoLists(l1,l2);
    }
    
};

总结

细致,多练,

哪些指针要修改,修改前保存(防止链表断掉),

特点:可以重新建立表头 例2+例6

指针:就是int(地址)

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值