链表是数据结构中重要的一个章节,他的重要性也不言而喻,在未来不管是笔试还是面试都会遇到这类的题目,所以接下来我就会把一些链表的常考的题目全部整理出来供大家学习指正。
点击下面链接即可进行刷题学习
开始刷题
刷题网站何其多,但好的刷题网站却不多,以下几点就是我推荐的原因:
1️⃣全面
里面有很多资料,不管是刷题还是学习还是面经等等
2️⃣大众
首先用的人很多,可以看到很多的题解,其次如果有问题也会有很多人回答
3️⃣熟悉oj环境
我们以后找工作的时候很多公司都会用这个网站,我们可以提前熟悉环境
先说明一下一些题目取自牛客网面试必刷TOP101
里面的一些题目在我以前的文章详细写到过,如果没有用新的方法就不会再做讲解
链表题目(一)
链表题目(二)
环状链表
题目链接
描述:
输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
数据范围:0 ≤ n ≤ 1000,−1000 ≤ 节点值 ≤ 1000
要求:空间复杂度 O(1),时间复杂度 O(n)
如输入{1,3,5},{2,4,6}时,合并后的链表为{1,2,3,4,5,6},所以对应的输出为{1,2,3,4,5,6},转换过程如下图所示:
或输入{-1,2,4},{1,3,4}时,合并后的链表为{-1,1,2,3,4,4},所以对应的输出为{-1,1,2,3,4,4},转换过程如下图所示:
示例1:
输入:{1,3,5},{2,4,6}
返回值:{1,2,3,4,5,6}
示例2:
输入:{},{}
返回值:{}
示例3:
输入:{-1,2,4},{1,3,4}
返回值:{-1,1,2,3,4,4}
思路分析:
前面的文章讲解过这道题,用的是迭代法,用两个指针,找到小就往后移动,直到一个到空指针,再链接到另一个非空指针节点。
现在我们要用另一种方法:
首先想明白两个问题:
1️⃣递归函数结束的条件是什么?
2️⃣递归函数一定是缩小递归区间的,那么下一步的递归区间是什么?
结束的条件当然是为空,对于第二个问题:跟迭代方法中的一样,如果PHead1的所指节点值小于等于pHead2所指的结点值,那么phead1后续节点和pHead节点继续递归
struct ListNode* Merge(struct ListNode* pHead1, struct ListNode* pHead2 ) {
if(pHead1 == NULL)
{
return pHead2;
}
if(pHead2 == NULL)
{
return pHead1;
}
struct ListNode* head;
if(pHead1->val < pHead2->val)
{
head = pHead1;
head->next = Merge(pHead1->next, pHead2);
}
else
{
head = pHead2;
head->next = Merge(pHead1, pHead2->next);
}
return head;
}
题目链接
描述:
合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。
数据范围:节点总数满足 数据范围:节点总数满足 0 ≤ n ≤ 10^5,链表个数满足 1 ≤ k ≤ 10^5,每个链表的长度满足1 ≤ len ≤ 200 ,每个节点的值满足 |val| <= 1000
要求:时间复杂度 O(nlogk)
示例1
输入:[{1,2,3},{4,5,6,7}]
返回值:{1,2,3,4,5,6,7}
示例2
输入:[{1,2},{1,4,5},{6}]
返回值:{1,1,2,4,5,6}
思路分析:
上一道题是两个链表合并,我们可以用归并思想,用俩指针,小的元素合并到大链表。
这道题是k个链表,我们也可以按照上述思路合并,然后新链表再与后一个继续合并,如此循环,但是这样效率太低了。
归并排序是什么?简单来说就是将一个数组每次划分成等长的两部分,对两部分进行排序即是子问题。对子问题继续划分,直到子问题只有1个元素。还原的时候呢,将每个子问题和它相邻的另一个子问题利用上述双指针的方式,1个与1个合并成2个,2个与2个合并成4个,因为这每个单独的子问题合并好的都是有序的,直到合并成原本长度的数组。
在我以前的文章也有详细讲解:
八大排序,你都掌握了吗?
struct ListNode* Merge(struct ListNode* pHead1, struct ListNode* pHead2 ) {
if(pHead1 == NULL)
{
return pHead2;
}
if(pHead2 == NULL)
{
return pHead1;
}
struct ListNode* head;
if(pHead1->val < pHead2->val)
{
head = pHead1;
head->next = Merge(pHead1->next, pHead2);
}
else
{
head = pHead2;
head->next = Merge(pHead1, pHead2->next);
}
return head;
}
struct ListNode* DivMerge(struct ListNode** list, int left, int right)
{
if(left > right)
{
return NULL;
}
//中间一个的情况
if(left == right)
{
return list[left];
}
int mid = (left + right) >> 1;
struct ListNode* head1 = DivMerge(list, left, mid);
struct ListNode* head2 = DivMerge(list, mid + 1, right);
return Merge(head1, head2);
}
struct ListNode* mergeKLists(struct ListNode** lists, int listsLen ) {
return DivMerge(lists, 0, listsLen - 1);
}
描述
给定一个链表,删除链表的倒数第 n 个节点并返回链表的头指针
例如,
给出的链表为: 1→2→3→4→5, n= 2.
删除了链表的倒数第 n 个节点之后,链表变为1→2→3→5.
数据范围: 链表长度 10000 ≤ n≤ 1000,链表中任意节点的值满足0 ≤ val ≤ 100
要求:空间复杂度 O(1),时间复杂度 O(n)
备注:
题目保证 n 一定是有效的
示例1
输入:{1,2},2
返回值:{2}
思路分析:
前面的文章写过类似的,只不过这个是要删除,这个就要考虑头指针位置的删除,直接加一个前序节点就行。
还有慢指针得找到他的前驱和后继节点。
struct ListNode* removeNthFromEnd(struct ListNode* head, int n ) {
struct ListNode* slow = head, *fast = head;
struct ListNode* NewHead = (struct ListNode*)malloc(sizeof(struct ListNode));
NewHead->next = head;
struct ListNode* prev = NewHead, *next = slow->next;
//fast先走n步
while(n--)
{
fast = fast->next;
}
while(fast)
{
prev = slow;
slow = slow->next;
next = slow->next;
fast = fast->next;
}
prev->next = next;
free(slow);
head = NewHead->next;
free(NewHead);
return head;
}
题目链接
描述
假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
给定两个这种链表,请生成代表两个整数相加值的结果链表。
数据范围:0 ≤ n,m ≤ 1000000,链表任意值 90 ≤ val ≤ 9
要求:空间复杂度 O(n),时间复杂度 O(n)
例如:链表 1 为 9->3->7,链表 2 为 6->3,最后生成新的结果链表为 1->0->0->0。
输入:[9,3,7],[6,3]
返回值:{1,0,0,0}
说明:
如题面解释
示例2
输入:[0],[6,3]
返回值:{6,3}
备注:
1 ≤ n,m ≤ 10^6
思路分析:
我们可以结合之前做过的题目,把两个链表反转,然后依次相加,相加的结果头插到新的链表中,即可得出答案。
struct ListNode* reverse(struct ListNode* head)
{
if(head == NULL || head->next == NULL)
{
return head;
}
struct ListNode* tmp = reverse(head->next);
head->next->next = head;
head->next = NULL;
return tmp;
}
struct ListNode* addInList(struct ListNode* head1, struct ListNode* head2 ) {
if(head1 == NULL)
return head2;
if(head2 == NULL)
return head1;
head1 = reverse(head1);
head2 = reverse(head2);
struct ListNode* tail = NULL;
int add = 0;
while(head1 && head2)
{
int ret = head1->val + head2->val + add;
add = 0;
if(ret >= 10)
{
ret %= 10;
add = 1;
}
struct ListNode* node = (struct ListNdoe*)malloc(sizeof(struct ListNode));
node->val = ret;
node->next = tail;
tail = node;
head1 = head1->next;
head2 = head2->next;
}
if(head1 == NULL && head2 == NULL)
{
return tail;
}
struct ListNode* Nhead = head1 == NULL ? head2 : head1;
while(Nhead)
{
int ret = Nhead->val + add;
add = 0;
if(ret >= 10)
{
ret %= 10;
add = 1;
}
struct ListNode* node = (struct ListNdoe*)malloc(sizeof(struct ListNode));
node->val = ret;
node->next = tail;
tail = node;
Nhead = Nhead->next;
}
if(add == 1)
{
struct ListNode* node = (struct ListNdoe*)malloc(sizeof(struct ListNode));
node->val = 1;
node->next = tail;
tail = node;
}
return tail;
}
这些题目都是相互关联,要达到做一个会多个的效果。