【链表】链表常见笔试题和面试题(C语言) | 您所在的位置:网站首页 › 数据结构c语言试题 › 【链表】链表常见笔试题和面试题(C语言) |
本文为链表常见的笔试题和面试题,包含链表反转、判断链表是否有环、查找环形入口、查找公共结点、合并两个有序链表、查找倒数第K个结点、查找中间结点、删除有序链表重复结点。 链表反转 时间复杂度O(n)链表反转其实就是反向改变链表结点的指向;先改变第一个结点,定义指针p指向头结点,原链表中p是指向第二个结点的,反转后头结点变尾结点,即p->next=NULL;这个时候我们就会发现链表的第一个结点和第二个结点断了,如下图所示:![]() ![]() ![]() ![]() 定义两个指针slow和fast,开始时都指向头结点,然后slow指针每次移动一个结点,fast每次移动两个结点,如果他们能够相遇在同一结点,则链表有环。 bool has_cycle(Node* head) { Node* slow = head; Node* fast = head; while(fast) { slow = slow->next; if(fast->next == NULL) return false; fast = fast->next->next; if(slow == fast) return true; } return false; } 查找环形入口 方法 1.使用两个指针slow和fast先判断是否有环,找到相遇的结点;2.设头结点到环形入口的距离为a,环形入口到两个指针相遇结点的距离为b,环的长度为c,如下图;![]() 写递归我们要有框架思维,不用一层一层的分解,找出规律最重要 我们假设链表是从小到大排列的,我们可以总结出规律为:比较两个结点的大小,较小的结点的下一个结点等于接下来两个结点中较小的结点。写成公式:node_min->next = func(node1,node2);再看函数返回值,node_min下一个结点是node1, node2中较小的,所返回值就是较小的结点;我们再从两个头结点head1,head2分析,先比较head1,head2谁大谁小,有两种情况,分别是: head1小,继续比较接下来的两个结点,即套用上面的公式。将head1指向小的,所以我们可以知道最后返回的是较小的结点,对于head1和head2来说,head1小,所以return head1;head2小,同理。 Node* merge_two_list(Node* head1, Node* head2) { if(head1==NULL) return head2; if(head2==NULL) return head1; if(head1->data data) { head1->next=merge_two_list(head1->next,head2); return head1; } else { head2->next=merge_two_list(head1,head2->next); return head2; } } 查找倒数第K个结点 方法一先遍历整个链表,获取链表的长度n,然后从头再次遍历到n-k个结点 方法二 查找倒数第k个结点,其实就是查找第n-k个结点,k是已知的,n是未知的,那有没有方法不需要知道n但是能查找到n-k呢?我们只有一个已知数k,那我们先来试试让一个指针从头结点移动到第k个结点,我们发现从第k个结点到尾结点就是n-k了;此时我们再让该指针从第k个结点出发,另一个指针从头结点出发,第一个指针到达尾结点时,第二个指针就到了n-k个结点,即到达倒数第k个结点。时间复杂度O(n) 我们使用第二个方法可以分解为以下几个步骤 1.使用两个指针,开始都指向头结点;2.接着让第一个指针先移动到第k个结点;3.让两个指针同时移动,当第一个指针移到尾结点时,第二个指针也就指向了倒数第k个结点。 Node* last_k(Node* head, int k) { if(head == NULL) return NULL; Node* src = head; Node* dst = head; //这里需要注意,从头结点移动到第k个结点,移动了k-1次 for(int i=0;i src = src->next; dst = dst->next; } return dst; } 查找中间结点查找中间结点, 如果中间结点为两个,返回较小的那个 方法一先遍历整个链表,获取链表的长度n,然后从头再次遍历到n/2个结点 方法二 1.两个指针slow和fast指针都先指向头结点;2.slow指针每次移动一个结点,fast指针每次移动两个结点,当fast指针移到尾结点的时候,slow刚好移到中间结点;注意:考虑链表长度的可能是奇数也可能是偶数 链表长度为奇数时,fast结点移到尾结点时,slow移到中间结点;链表长度偶数时,中间结点有两个,fast结点移到倒数第二个结点时,slow结点移到较小的中间结点。 -时间复杂度O(n) Node* find_middle(Node* head) { if(head == NULL || head->next == NULL) return head; Node* slow = head; Node* fast = head; while(fast->next && fast->next->next) { slow = slow->next; fast = fast->next->next; } return slow; } 删除有序链表重复结点删除所有含有重复数字的节点,只保留原始链表中 没有重复出现的数字。 方法 删除重复的结点,需要判断结点中的数据是否相等,显然需要两个指针,一个指向当前结点cur,一个指向后面的结点sec,然后判断指针指向的结点上的数据是否相等;还要考虑在头结点的时候就有重复的数据,如果删了头结点,新的头结点在哪,所以要一个指针pre指向当前结点cur的上一个结点,一个指针备份头结点head_tmp,所以这里需要四个指针。 步骤 1.先判断链表是否为空或者是否只含一个结点;2.定义四个指针head_tmp备份头结点,pre为NULL,cur指向头结点,sec指向头结点的下一个结点;3.判断相邻的结点数据是否相等,如果cur->data等于sec->data 让sec往后移,直到两个指针指向结点的数据不相等,然后将cur,sec之间的结点都删掉,并将cur指向sec指向的结点;如果第一次即cur指向头结点的时候就执行了这个操作那么pre就是NULL,所以我们需要将head_tmp指向新的头结点cur,否则就将pre->next=cur;但是还没完,先看下pre,cur,sec三个结点的指向,pre->next=cur,cur和sec指向了同一个结点,和最开始的状态不一样,所以要将sec的指向往后移一个结点; 如果cur->data不等于sec->data,将pre,cur,sec指针都往后移一个结点。 Node* del_repeat(Node* head) { if(head == NULL || head->next==NULL) return head; Node* pre = NULL; Node* head_tmp = head; Node* cur = head; Node* sec = head->next; while(sec) { //判断相邻的结点数据是否相等 if(cur->data == sec->data) { sec = sec->next; //移动sec 直到cur->data和sec->data不相等 while(sec && cur->data==sec->data) { sec = sec->next; } //逐个删除重复的数据 while(cur != sec) { Node* tmp = cur; cur = cur->next; free(tmp); } if(pre == NULL) head_tmp = cur; else pre->next=cur; if(sec) sec = sec->next; } else { pre = cur; cur = cur->next; sec = sec->next; } } return head_tmp; } |
CopyRight 2018-2019 实验室设备网 版权所有 |