数据结构_第五关:单链表OJ题练习
OJ题题目简介和链接:
1.删除链表中等于给定值 val 的所有结点。OJ题链接
2.反转一个单链表。OJ题链接
3.给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。OJ题链接
4.. 输入一个链表,输出该链表中倒数第k个结点。OJ题链接
5.将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。OJ链接
6.编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。OJ题链接
7.链表的回文结构。OJ链接
8.输入两个链表,找出它们的第一个公共结点。OJ题链接
9.给定一个链表,判断链表中是否有环。OJ题链接
10.给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL。OJ题链接
11.给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空结点。OJ题链接
目录
OJ题题目简介和链接:
1.移除链表元素
2.反转一个单链表
3.链表的中间结点
4.链表中倒数第k个结点
5.合并两个有序链表
6.链表分割
7.链表的回文
8.相交链表
9.环形链表,判断是否有环
10.环形链表2,如果有环,返回入环点
11.复制带随机指针的链表
1.移除链表元素
思路:
- 先判度链表是否为NULL
- 定义一个新的头结点newhead(保存头结点地址用),再定义一个尾结点tail(进行插入用),刚开始置为空,
就是让新的头结点,尾插,源链表不是val的值 - 判断cur->val是否等于val,
不等于,再判断这是不是第一个结点,
如果是第一个结点,那此时,说明第一个结点的值不是val,可以插入
让newhead和tail=cur - 如果不是第一个结点,则用tail进行尾插,tail->next=cur; tail=tail->next;
- 如果cur->val的值等于val,则利用temp将该值free掉,并继续往后走
- 重复3,4,5,直到cur的值为NULL
- 最后给新的链表的尾加上NULL即可
代码:
struct ListNode* removeElements(struct ListNode* head, int val)
{
//判断链表是否为空
if(head==NULL)
return NULL;
struct ListNode*cur=head;
//定义新一个的链表
struct ListNode*newhead=NULL;//newhead用来保存新链表的头节点地址
struct ListNode*tail=NULL;//tail用来往新链表里面插入
while(cur)
{
if(cur->val!=val)//判断原链表的值是否是val
{
if(tail==NULL)//判断是不是新链表的第一个结点,头节点的值不是val可以往新链表里面插入
{
newhead=tail=cur;
}
else//不是新链表的第一个结点,直接头插
{
tail->next=cur;
tail=tail->next;
}
cur=cur->next;
}
else//原链表的值等于val的时候进行跳过处理
{
struct ListNode*tmp=cur->next;
free(cur);
cur=tmp;
}
}
if(tail)
tail->next=NULL;
return newhead;
}
2.反转一个单链表
思路1:
- 和上面的方法一样,创建一个新的链表,将原链表的值头插进新的链表,并返回即可
代码:
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode*newhead=NULL;//创建新的链表
struct ListNode*tmp=NULL;//记录cur的next
struct ListNode*cur=head;
while(cur)
{
tmp=cur->next;
//头插
cur->next=newhead;
newhead=cur;
cur=tmp;
}
return newhead;
}
思路2:
- 用1指向空,2指向1...
- 需要用三个指针进行操作
- 用n1和n2进行指向的转换,n3记录下一个结点地址
代码:
struct ListNode* reverseList(struct ListNode* head)
{
if(head==NULL)
return NULL;
struct ListNode*n1,*n2,*n3;
n1=NULL;
n2=head;
n3=n2->next;
while(n2)
{
n2->next=n1;
n1=n2;
n2=n3;
if(n3)
n3=n3->next;
}
return n1;
}
3.链表的中间结点
思路:
- 利用快慢指针,快指针每次走两步,满指针每次走一步,当快指针走到头时,满指针刚好走到中间
- 这里要分两种情况:单链表为奇数和单链表为偶数,
奇数时,快指针走到最后一个结点处,慢指针走到中间
偶数时,快指针走到NULL处,满指针走到中间的下一个
代码:
class Solution {
public:
ListNode* middleNode(ListNode* head) {
struct ListNode*slow,*fast;
fast=slow=head;
//因为不知道链表时奇数还是偶数,所以用fast&&fast->next来判断
while(fast && fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
};
4.链表中倒数第k个结点
思路:
- 快慢指针,可以先让快指针走k个结点,然后快指针和慢指针一起走
-
根据示例可以看到第三组,第四组的k都比数组大,所以应该加上一个判断,判断快指针走了K个结点后是否为空
代码:
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
struct ListNode* fast,* slow;
fast = slow = pListHead;
while(k--)
{
if(fast==NULL)
return NULL;
fast=fast->next;
}
while(fast)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
};
5.合并两个有序链表
思路:
创建一个新的链表,然后对原来的链表进行对比,数据小的,尾插进新的链表里面
代码:
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
//判断链表是否为空
if(list1==NULL)
return list2;
if(list2==NULL)
return list1;
if(list1==NULL || list2==NULL)
return NULL;
//定义新的头来记录两个链表里面的数据
struct ListNode*newhead=NULL,*tail=NULL;
struct ListNode*cur1=list1,*cur2=list2;
struct ListNode*n1=cur1->next,*n2=cur2->next;
//确定新的链表的头是哪个
if(cur1->val <= cur2->val)
{
tail = cur1;
newhead = tail;
cur1 = n1;
if(cur1 != NULL)
n1 = n1->next;
}
else
{
tail = cur2;
newhead = tail;
cur2 = n2;
if(cur2 != NULL)
n2 = n2->next;
}
//进行循环比较,当一个链表为空的时候退出循环
while(cur1 && cur2)
{
if (cur1->val <= cur2->val)
{
tail->next = cur1;
tail = tail->next;
cur1 = n1;
if (cur1 != NULL)
n1 = n1->next;
}
else
{
tail->next = cur2;
tail = tail->next;
cur2 = n2;
if (cur2 != NULL)
n2 = n2->next;
}
}
//判断哪个链表是空,然后将不是空的链表链接的新链表后面
if (cur1 == NULL)
{
while (cur2)
{
tail->next = cur2;
tail = tail->next;
cur2 = n2;
if (cur2 != NULL)
n2 = n2->next;
}
}
else
{
while (cur1)
{
tail->next = cur1;
tail = tail->next;
cur1 = n1;
if (cur1 != NULL)
n1 = n1->next;
}
}
return newhead;
}
6.链表分割
比如:
思路:
- 加上哨兵位的头节点进行处理
- 注意:要让greaterHead的最后一个数指向NULL不然会发生死循环
代码:
class Partition {
public:
ListNode* partition(ListNode* pHead, int x)
{
//定义四个变量,less里面放小于x的数,greater里面放大于x的数
//Head里面存放该链表的头结点地址,Tail进行判断和插入
struct ListNode* lessHead,*lessTail,*greaterHead,*greaterTail;
//为这两个链表开辟一个哨兵位的头结点
lessHead=lessTail = (struct ListNode*)malloc(sizeof(struct listNode));
greaterHead=greaterTail = (struct ListNode*)malloc(sizeof(struct listNode));
lessTail->next = greaterTail->next = NULL;
struct listNode* cur = pHead;
while(cur)//当cur为空时结束
{
if(cur->val < x)//小于x的尾插list链表里面
{
lessTail->next=cur;
lessTail=lessTail->next;
}
else
{
greaterTail->next=cur;
greaterTail=greaterTail->next;
}
cur = cur->next;
}
//让小的链表尾的next指向大的链表的哨兵位头节点的next
lessTail->next=greaterHead->next;
//大数组的尾置空,以防止生成环
greaterTail->next=NULL;
//用phead指向lessHead的next完成
pHead=lessHead->next;
//最后释放两个哨兵头结点
free(lessHead);
free(greaterHead);
return pHead;
}
};
7.链表的回文
思路:
- 回文就是两边对称
- 反转后半段,然后和前半段进行比较
- 借助之前的快慢指针找到中间节点,然后再进行头插反转
- 如果时偶数,直接进行判断就行,
- 如果是奇数,因为没有改变2的next,所以2的next还是3,依然可以进行判断,走到NULL结束即可
代码:
class PalindromeList {
public:
//找出链表的中点
struct ListNode* middleNode(ListNode* head)
{
struct ListNode*slow,*fast;
fast=slow=head;
while(fast && fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
//将链表反转
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode*newhead=NULL;//创建新的链表
struct ListNode*tmp=NULL;//记录cur的next
struct ListNode*cur=head;
while(cur)
{
tmp=cur->next;
//头插
cur->next=newhead;
newhead=cur;
cur=tmp;
}
return newhead;
}
bool chkPalindrome(ListNode* A)
{
struct ListNode*mid=middleNode(A);
struct ListNode*rhead=reverseList(mid);
//不知到链表是奇数还是偶数就两个链表的结束条件一起判断
while(A && rhead)
{
if(A->val!=rhead->val)
return false;
A=A->next;
rhead=rhead->next;
}
return true;
}
};
8.相交链表
难点:
- 单链表,两个数组长度不同,要判断是否相交和找出相交结点
思路:
- 还是利用快慢指针,先算出两个数组的大小,相减差值的绝对值就是,快指针需要多走的结点
- 要计算处数组的大小,就需要遍历数组,同时也能找到尾结点,如果尾结点不相同
则说明这两个链表不相交,返回NULL - 尾结点数相同就相交,尾结点不同就返回NULL,所以我们可以直接找尾直接用
while(cur->next)判断,之后如果有空链表,再前面加一步判断即可 - 否则,在快指针走了差值步之后,快慢指针一起走,直到两个指针相等时,就是这两个链表的交点位置
代码:
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode*A=headA,*B=headB;
int lenA=0,lenB=0;
//找尾,计算每个链表的大小
while(A->next)
{
lenA++;
A=A->next;
}
while(B->next)
{
lenB++;
B=B->next;
}
//尾结点不相等就不相交
if(A!=B)
{
return NULL;
}
//计算差值,并找出长链表和短链表
int gap=abs(lenA-lenB);
struct ListNode*longlist = headA,*shortlist = headB;
if(lenB>lenA)
{
longlist=headB;
shortlist=headA;
}
//长链表走gap步
while(gap--)
{
longlist=longlist->next;
}
//同时走,找交点
while(longlist!=shortlist)
{
longlist=longlist->next;
shortlist=shortlist->next;
}
return longlist;
}
9.环形链表,判断是否有环
思路:
-
快慢指针,fast走两步,slow走一步
-
当slow也走进环里面的时候,这是就会形成一个fast追slow的追击问题
-
只要fast和slow差一步,并且是个环,就一定会相遇
bool hasCycle(struct ListNode *head) {
struct ListNode*fast=head,*slow=head;
//这里也要判断fast->next是因为如果链表只有一个数,没有fast->next->next,就会出错
while(fast && fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
return true;
}
return false;
}
10.环形链表2,如果有环,返回入环点
这个题主要是一个智力题,对代码能力的要求不大,主要是看你能不能想明白其中的道理
这里先说结果:
再相遇的时候,指针head重新从头走,指针meet从环的相遇点处走,
在head与meet相遇的时候,这个点就是环的入口点
解释:
- 所以在圈很大的时候(特殊情况),走的距离:慢指针=L+N,快指针=L+N+C
- 由快指针是满指针走的距离的2倍可以得到:2*(L+N)=L+N+C,C=L+N,可得出结论
- 但是这只是一个特殊情况,不足以证明环小的时候
- 应该为:
代码如下:
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode*fast=head,*slow=head;
//这里也要判断fast->next是因为如果链表只有一个数,没有fast->next->next,就会出错
while(fast && fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
{
struct ListNode*meet =slow;
//让头结点和meet一起走,找到他们相交的点
while(head!=meet)
{
head=head->next;
meet=meet->next;
}
return meet;
}
}
return NULL;
}
11.复制带随机指针的链表
思路1:
-
可以利用相对位置求解,我们要找random指向的是第几个结点
-
就比如,先对7这个结点的random进行复制,就对数组进行一次遍历,遍历过程中记录num,该结点是第几个结点,当random指向的地址和遍历到的地址位置相同时,就返回num的值
-
在进行复制random的时候,就直接利用相对位置num进行复制
-
但是这样的话,得到的时间复杂度是O(n^2)
如果要求时间复杂度是O(n)的话,要怎么做?
思路2:
1.拷贝结点链接在源节点的后面:
2.设置拷贝结点的random
拷贝结点的random=cur的random的next
3.拷贝结点解下来,连接组成拷贝链表
代码(思路2的实现):
struct Node* copyRandomList(struct Node* head)
{
//1.拷贝结点链接到源节点的后面
struct Node* cur=head;
while(cur)
{
struct Node* Next=cur->next;
//创建新的结点空间
struct Node*copy=(struct Node*)malloc(sizeof(struct Node));
//让新的结点的值=源结点的值
copy->val=cur->val;
//将新节点插入到源节点的后面
cur->next=copy;
copy->next=Next;
cur=Next;
}
//2.将源结点的random进行复制
cur=head;
while(cur)
{
struct Node*copy=cur->next;
if(cur->random==NULL)
{
copy->random=NULL;
}
else
{
copy->random=cur->random->next;
}
cur=cur->next->next;
}
//3.将copy的在源节点后面的结点解下来放到一起
cur=head;
struct Node*CopyHead=NULL,*tail=NULL;
while(cur)
{
struct Node*copy=cur->next;
struct Node*Next=copy->next;
cur->next=Next;
//尾插
if(CopyHead==NULL)
{
CopyHead=tail=copy;
}
else
{
tail->next=copy;
tail=tail->next;
}
cur=Next;
}
return CopyHead;
}