定义如下:
struct ListNode{ int val; ListNode *next; ListNode(int x):val(x),next(NULL){} };
第一题:链表翻转 easy
ListNode * reverseList(ListNode * head) { ListNode *new_head=NULL;//指向新链表头节点的指针 while(head){ ListNode *next= head->next;//备份head->next head->next=new_head;//更新hea睨zhid->next; new_head=head;//移动new_head; head=next;//遍历链表 } return new_head;//返回新链表头节点 }
第二题 链表翻转 变式
描述:
已知链表头节点指针head,将链表从位置m到n逆序。(不申请额 外空间)
比如m=2,n=4,表示从2个节点到第四个节点逆向 1<=m<=n<=链表长度
思路:
寻找关键点
1 逆置段头节点的前驱
2 逆置前头节点变为你只后尾节点
3 逆置前尾节点变为你只后头节点
4 逆置段尾节点的后继
步骤一:将head向前移动m-1个位置,找到开始逆置的节点,记录该节点的前驱和该节点
步骤二:从head开始,逆置change_len=n-m+1d个节点
步骤三:将pre_head与new_head连接,modify_list_tail与head相连
ListNode * reverseBetween(ListNode *head,int m ,int n){ int change_len=n-m+1;//计算需要逆置的节点个数 ListNode *pre_head=NULL;//初始化开始逆置的节点的前驱 ListNode *result=head;//最终转换后的链表头节点,非特殊情况即为head while (head&&--m) {//将head向前移动m-1个位置 pre_head=head; head=head->next; } //将modify_list_tail指向当前的head,即逆置后的链表尾 ListNode *modify_list_tail=head; ListNode * new_head=NULL; while (head&&change_len) {//逆置change_len个节点 ListNode *next=head->next; head->next=new_head; new_head=head; head=next; change_len--;//每完成一个节点逆置,change_len--; } modify_list_tail->next=head;//连接逆置后的链表尾与逆置段的后一个节点 //如果pre_head不为空,说明不是从第一个节点开始逆置的 m>1 //将逆置链表开始的节点前驱与逆置后的头节点连接 if(pre_head){ pre_head->next=new_head; } else{ //如果pre_head为空,说明m=1从第一个节点逆置,结果为逆置后的头节点 result=new_head; } return result; }
第三题 求两个链表的交点
描述:
已知链表A的头节点指针headA,链表B的头节点指针headB,两 个链表相交,求两链表交点对应的节点
要求:
1.如果两个链表没有交点,则返回NULL
2.在求交点的过程中,不可以破坏链表的结构或者修改链表的数据域
3.可以确保传入的链表A与链表B没有任何环
4*.实现算法尽可能使时间复杂度O(n),空间复杂度O(1)
思路:
步骤一:计算headA链表长度,计算headB链表长度,较长的链表多出的长度
步骤二:将较长链表的指针移动到和较短链表指针对其的位置
步骤三:headA和headB同时移动,当两指针h指向同一个节点时,找到了
//计算链表长度 int get_list_length(ListNode *head){ int len=0; while (head) { len++; head=head->next; } return len; } //向前移动 ListNode *forward_long_list(int long_len,int short_len,ListNode *head){ int delta=long_len-short_len; while (head&&delta) { head=head->next; delta--; } return head; } //得到交点 ListNode *getIntersectionNode(ListNode *headA,ListNode *headB){ int list_A_length=get_list_length(headA); int list_B_length=get_list_length(headB); if(list_A_length>list_B_length){ headA=forward_long_list(list_A_length, list_B_length, headA); } else{ headB=forward_long_list(list_B_length, list_A_length, headB); } while (headB&&headA) { if(headB==headA){ return headA; } headA=headA->next; headB=headB->next; } return NULL; }
第四题:链表求环
描述:
已知链表中可能存在环,若有环返回环起始节点,否则返回NULL。
思路:
快慢指针赛跑
先求出相遇点,然后根据数学公式
从相遇点出发和从head出发,相遇点就是 环入口点
ListNode *detectCycle(ListNode *head){ ListNode *fast=head; ListNode * slow=head; ListNode *meet=NULL; while (fast) { slow=slow->next; fast=fast->next; if(!fast){ return NULL; } fast=fast->next; if(fast==slow){ meet=fast; break; } } if(meet==NULL){ return NULL; } while (head&&meet) { if(head==meet){ return head; } head=head->next; meet=meet->next; } return NULL; }
第五题:链表划分
描述:
已知链表头指针head与数值x,将所有小于x的节点放在大于或等于x 的节点前,且保持这些节点的原来的相对位置。
思路:
巧用临时头节点,然后用链表尾插法
ListNode * partition(ListNode *head,int x){ ListNode less_head(0); ListNode more_head(0); ListNode *less_ptr=&less_head; ListNode *more_ptr=&more_head; while (head) { if(head->val<x){ less_ptr->next=head; less_ptr=head; } else{ more_ptr->next=head; more_ptr=head; } head=head->next; } less_ptr->next=more_head.next; more_ptr->next=NULL; return less_head.next; }
第六题:复杂链表的深度拷贝
描述:
已知一个复杂的链表,节点中有一个指向本链表任意某个节点的随机指 针(也可以为空),求这个链表的深度拷贝。
思路:
通过Map映射,这里用2个Map
第一个Map
遍历老链表,Map[节点地址]=节点索引位置 保存
第二个Map
遍历老链表,Map[节点位置]=新链表节点地址
struct RandomListNode{ int label; RandomListNode *next,*random; RandomListNode(int x):label(x),next(NULL),random(NULL){} }; RandomListNode * copyRandomList(RandomListNode *head) { std::map<RandomListNode*,int> node_map;//地址到节点位置的map std::vector<RandomListNode*> node_vec;//使用vector根据存储节点位置访问的地址 RandomListNode *ptr=head; int i=0; while (ptr) { node_vec.push_back(new RandomListNode(ptr->label)); node_map[ptr]=i; ptr=ptr->next; i++; } node_vec.push_back(0);//多加一个元素,后面i+1的时候方便处理 ptr=head; i=0; //再次遍历原始列表 连接新链表的next指针,random指针 while (ptr) { node_vec[i]->next=node_vec[i+1];//连接新链表的next指针 if(ptr->random){ int id=node_map[ptr->random];//获取索引 node_vec[i]->random=node_vec[id]; } ptr=ptr->next; i++; } return node_vec[0]; }
第七题:2个链表的合并
描述:
已知两个已排序链表头节点指针l1与l2,将这两个链表合并,合并后仍为 有序的,返回合并后的头节点。
思路:
借助头节点
ListNode *mergeTwoLists(ListNode *l1,ListNode *l2){ ListNode temp_head(0); ListNode *pre=&temp_head; while (l1&&l2) { if(l1->val < l2->val){ pre->next=l1; l1=l1->next; } else{ pre->next=l2; l2=l2->next; } pre=pre->next; } if(l1){ pre->next=l1; } if(l2){ pre->next=l2; } return temp_head.next; }
第八题:多个排序链表的合并
思路:
采用分治法效率最高
算法负责度 为O(kNlogk),k为链表个数,n为每个链表的长度
比如 每个链表长度为3,k为8,第一轮,进行k/2=4次比较(8/2=4,4/2=2),每次处理2n个数字,也就是6个(2个链表为6)
第二轮进行8/4=2次比较,每次比较4n个数字,第三轮是最后一轮,进行1次比较,处理8*3个数
ListNode* mergeKLists(std::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=(int)lists.size()/2; std::vector<ListNode*>sub1_lists; std::vector<ListNode*>sub2_lists;//拆分lists为两个子lists 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);//分制处理 }
第九题:交换一个单链表 两两相邻的两个元素
思路1:借助3个指针进行移动
ListNode *swapPairs(ListNode *head){ if(head==NULL) return NULL; ListNode preHead(0); preHead.next=head; ListNode *left=&preHead; ListNode *mid=head; if(head->next==NULL){ return head; } ListNode *right=head->next; while (mid&&mid->next) { mid->next=right->next; right->next=mid; left->next=right; left=mid; mid=left->next; if(mid){ right=mid->next; } } return preHead.next; }
思路2:递归
借助temp保存节点的下一个节点,然后本节点的next指向递归函数返回的指针,最后temp的next指向head
ListNode *swapPairs(ListNode *head){ if(!head) return NULL; if(!head->next) return head; ListNode* temp=head->next; head->next=swapPairs(temp->next); temp->next=head; return temp; }