目录
例3.单链表找环及起点和环长度(经典)LeetCode-141+142
链表简介
链表:一个元素和下一个元素靠指针连接(松散),不能以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(地址)