数据结构第四讲:单链表OJ题
这一讲全部都是关于单链表的数据结构题目,这里有几个注意点:
1.博客中不会出现关于题目的讲解,讲解通过注释或画图来表现出来
2.所有题目都会上传两种代码:一种是代码纯享版,一种是添加了注释之后的代码,所有的画图都会在代码最后进行展示
3.理解是自己在写代码时自己的一些理解,可能不太成熟,请见谅
1.移除链表元素
链接: OJ题目链接
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
//创建一个头节点和尾节点
ListNode *phead, *ptail;
phead = ptail = NULL;
ListNode* pcur = head;
while (pcur) {
if (pcur->val != val) {
if (phead == NULL) {
phead = ptail = pcur;
} else {
ptail->next = pcur;
ptail = ptail->next;
}
}
pcur = pcur->next;
}
if (ptail) {
ptail->next = NULL;
}
return phead;
}
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
//首先创建了一个头节点和尾节点
//理解1:对于单链表OJ题,知道了一个头节点和尾节点就可以了,头节点方便找到链表的头部
//尾节点用来找到在哪个地方插入新的节点
ListNode *phead, *ptail;
//对链表指针进行初始化
phead = ptail = NULL;
//使用一个指针来保存头节点,防止头节点移动
ListNode* pcur = head;
while (pcur) {
//如果节点中的数据值不等于要删除的数据,将该节点进行链接
if (pcur->val != val) {
if (phead == NULL) {
phead = ptail = pcur;
} else {
//这里一定要写成pcur,不能写成head
ptail->next = pcur;
ptail = ptail->next;
}
}
pcur = pcur->next;
}
if (ptail) {
ptail->next = NULL;
}
return phead;
}
2.反转链表
链接: OJ题目链接
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
if (head==NULL || head->next==NULL) {
return head;
}
ListNode *left, *phead, *right;
left = NULL;
phead = head;
right = head->next;
while (phead) {
phead->next = left;
left = phead;
phead = right;
if(right)
right = right->next;
}
return left;
}
typedef struct ListNode ListNode;
//使用三个指针即可
struct ListNode* reverseList(struct ListNode* head) {
if (head==NULL || head->next==NULL) {
return head;
}
ListNode *left, *phead, *right;
left = NULL;
phead = head;
right = head->next;
while (phead) {
phead->next = left;
left = phead;
phead = right;
if(right)
right = right->next;
}
return left;
}
3.链表的中间节点
链接: OJ题目链接
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
ListNode *fast, *slow;
fast = slow = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
//这道题定义了两个指针,使用的是快慢指针的思想
//想像两个人A和B在走路,B每次走的距离都比A走的长二倍,那么当B走到终点时,A恰好走到中点
//这样我们就找到了中间节点的位置了
ListNode *fast, *slow;
fast = slow = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
4.合并两个有序链表
链接: OJ题目链接
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
if (list1 == NULL) {
return list2;
}
if (list2 == NULL) {
return list1;
}
ListNode *phead, *ptail;
phead = ptail = NULL;
ListNode* p1 = list1;
ListNode* p2 = list2;
while (p1 && p2) {
if (p1->val <= p2->val) {
if (phead == NULL) {
phead = ptail = p1;
} else {
ptail->next = p1;
ptail = p1;
}
p1 = p1->next;
} else {
if (phead == NULL) {
phead = ptail = p2;
} else {
ptail->next = p2;
ptail = p2;
}
p2 = p2->next;
}
}
if (p1 == NULL) {
ptail->next = p2;
} else {
ptail->next = p1;
}
return phead;
}
typedef struct ListNode ListNode;
//这道题的思路是:
//先创建一个新的链表
//再创建两个指针变量p1和p2,比较p1->val和p2->val的大小,谁小就尾插新的链表
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
if (list1 == NULL) {
return list2;
}
if (list2 == NULL) {
return list1;
}
ListNode *phead, *ptail;
phead = ptail = NULL;
ListNode* p1 = list1;
ListNode* p2 = list2;
while (p1 && p2) {
if (p1->val <= p2->val) {
if (phead == NULL) {
phead = ptail = p1;
} else {
ptail->next = p1;
ptail = p1;
}
p1 = p1->next;
} else {
if (phead == NULL) {
phead = ptail = p2;
} else {
ptail->next = p2;
ptail = p2;
}
p2 = p2->next;
}
}
if (p1 == NULL) {
ptail->next = p2;
} else {
ptail->next = p1;
}
return phead;
}
5.链表分割
链接: OJ题目链接
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
ListNode* lesshead = (ListNode*)malloc(sizeof(ListNode));
ListNode* lesstail = lesshead;
ListNode* bighead = (ListNode*)malloc(sizeof(ListNode));
ListNode* bigtail = bighead;
ListNode* pcur = pHead;
while(pcur)
{
if(pcur->val < x)
{
lesstail->next = pcur;
lesstail = lesstail->next;
}
else
{
bigtail->next = pcur;
bigtail = bigtail->next;
}
pcur = pcur->next;
}
lesstail->next = bighead->next;
bigtail->next = NULL;
return lesshead->next;
}
};
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
//直接创建两个节点lesshead和bighead
//val值小于x的尾插到lesshead节点
//val值大于x的尾插到bighead节点
ListNode* lesshead = (ListNode*)malloc(sizeof(ListNode));
ListNode* lesstail = lesshead;
ListNode* bighead = (ListNode*)malloc(sizeof(ListNode));
ListNode* bigtail = bighead;
ListNode* pcur = pHead;
while(pcur)
{
if(pcur->val < x)
{
lesstail->next = pcur;
lesstail = lesstail->next;
}
else
{
bigtail->next = pcur;
bigtail = bigtail->next;
}
pcur = pcur->next;
}
lesstail->next = bighead->next;
//这里必须要将最后一个节点的指针指向NULL
bigtail->next = NULL;
return lesshead->next;
}
};
6.链表的回文结构
链接: OJ题目链接
6.1方法1:创建数组
这个方法并不使用于所有情况,题目要求为空间复杂度为O(1),由于题目要求链表长度不大于900,所以我们创建了一个常量的数组,如果链表长度不确定,该题的空间复杂度就变成了O(n)
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
//创建一个数组,将节点中的值存储在数组中,通过数组判断是否是回文数
ListNode* pcur = A;
int arr[900];
int sz = 0;
while(pcur)
{
arr[sz++] = pcur->val;
pcur = pcur->next;
}
int left = 0;
int right = sz-1;
while(left < right)
{
if(arr[left++] != arr[right--])
{
return false;
}
}
return true;
}
};
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
//创建一个数组,将节点中的值存储在数组中,通过数组判断是否是回文数
ListNode* pcur = A;
int arr[900];
int sz = 0;
while(pcur)
{
arr[sz++] = pcur->val;
pcur = pcur->next;
}
//使用数组进行检查的思路为:
//定义两个头尾指针,判断头尾指针指向的值是否相同
int left = 0;
int right = sz-1;
//left不能够大于right,这可以作为循环条件
while(left < right)
{
if(arr[left++] != arr[right--])
{
//当存在一个数不同时,就不是回文数
return false;
}
}
return true;
}
};
6.2方法2:反转链表
class PalindromeList {
public:
ListNode* FindBet(ListNode* phead)
{
//快慢指针进行查找
ListNode* slow, *fast;
slow = fast = phead;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode* Reserve(ListNode* A, ListNode* bet)
{
ListNode* left = NULL;
ListNode* phead = A;
ListNode* right = A->next;
while(phead != bet)
{
phead->next = left;
left = phead;
phead = right;
if(right != bet)
right = right->next;
}
return left;
}
bool chkPalindrome(ListNode* A) {
//先查找链表中有几个节点
int count = 0;
ListNode* pcur = A;
while(pcur)
{
count++;
pcur = pcur->next;
}
//先找到中间节点
ListNode* bet = FindBet(A);
//然后进行逆置
ListNode* phead = Reserve(A, bet);
if(count%2 != 0)
bet = bet->next;
while(bet)
{
if(phead->val != bet->val)
{
return false;
}
phead = phead->next;
bet = bet->next;
}
return true;
}
};
7.相交链表
链接: OJ题目链接
typedef struct ListNode ListNode;
struct ListNode* getIntersectionNode(struct ListNode* headA,
struct ListNode* headB) {
// 首先先判断是否存在相交节点
// 存在相交节点的充要条件为:两个链表中的最后一个节点是同一个节点
ListNode *ptail1, *ptail2;
ptail1 = headA;
ptail2 = headB;
int count1 = 0;
int count2 = 0;
while (ptail1->next) {
count1++;
ptail1 = ptail1->next;
}
while (ptail2->next) {
count2++;
ptail2 = ptail2->next;
}
if (ptail1 != ptail2) {
return NULL;
} else {
// 将长链表截取一部分
int cut = abs(count1 - count2);
ListNode* lesslong = headA;
ListNode* morelong = headB;
if (count1 > count2) {
lesslong = headB;
morelong = headA;
}
while (cut--) {
morelong = morelong->next;
}
while (lesslong != morelong) {
lesslong = lesslong->next;
morelong = morelong->next;
}
return lesslong;
}
}
typedef struct ListNode ListNode;
//思路为:
//先分别求出两个链表的长度,将长的那个链表截取几个节点,使得两个链表一样长
//分别从两个链表的头节点进行遍历,当两个节点相同时,这个相同的节点就是相交的节点
struct ListNode* getIntersectionNode(struct ListNode* headA,
struct ListNode* headB) {
// 首先先判断是否存在相交节点
// 存在相交节点的充要条件为:两个链表中的最后一个节点是同一个节点
ListNode *ptail1, *ptail2;
ptail1 = headA;
ptail2 = headB;
//后续我们要求长链表向后移动多少,所以我们要知道每一个链表的长度
int count1 = 0;
int count2 = 0;
while (ptail1->next) {
count1++;
ptail1 = ptail1->next;
}
while (ptail2->next) {
count2++;
ptail2 = ptail2->next;
}
if (ptail1 != ptail2) {
return NULL;
} else {
// 将长链表截取一部分
int cut = abs(count1 - count2);
ListNode* lesslong = headA;
ListNode* morelong = headB;
if (count1 > count2) {
lesslong = headB;
morelong = headA;
}
while (cut--) {
morelong = morelong->next;
}
while (lesslong != morelong) {
lesslong = lesslong->next;
morelong = morelong->next;
}
return lesslong;
}
}
8.环形链表一
链接: OJ题目链接
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {
ListNode* slow, *fast;
slow = fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
return true;
}
return false;
}
在这串代码中,我们使用了快慢指针的方法,让慢指针一次走一步,快指针一次走两步,如果两个指针相同了,就证明存在环形链表,那这是为什么呢?
8.1原理解释
8.2拓展思维
那么如果我让fast指针一次走三个单位,那么两个指针还会不会相遇呢?
所以我们知道了:追不上只有一种情况:N为奇数,C为偶数
我们再来看:
对于2L = (x+1)C-N这个公式来说,公式左边一定为偶数,当N为奇数时,为让等式成立,那么(x+1)C也必须为奇数,所以说根本就不存在N为奇数,C为偶数的情况
所以说,不管fast比slow快多少,如果它们两个能够相遇,就证明存在环形链表,但是一般我们还是让fast比slow快两倍来求
9.环形链表二
链接: OJ题目链接
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
//先判断是否存在环形链表
ListNode* slow, *fast;
slow = fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
break;
}
if(fast==NULL || fast->next==NULL)
return NULL;
//此时说明肯定存在环形链表,这时需要找到环形节点存在的位置
ListNode* pcur = head;
while(1)
{
if(fast == pcur)
return pcur;
fast = fast->next;
pcur = pcur->next;
}
}
这次的代码还是很简单,只需要先通过快慢指针找到两指针的相遇点,再让一个指针指向头指针,将头指针和相遇点指针向后移,两指针相遇的位置就是环形链表成环的位置
9.1原理解释
10.随机链表的复制
链接: OJ题目链接
10.1题目解析
题目就是指:
一个节点中多出了一个random指针,该指针指向的位置不确定,可以指向NULL,可以指向自己,可以指向链表中其他节点的地址。
我们要做的是对这样的一个链表进行深拷贝,也就是需要单独创建空间并进行赋值
10.解题代码
typedef struct Node Node;
//申请空间的函数
Node* BuyNode(int x)
{
Node* newnode = (Node*)malloc(sizeof(Node));
newnode->val = x;
newnode->next = newnode->random = NULL;
return newnode;
}
struct Node* copyRandomList(struct Node* head) {
if(head == NULL)
return NULL;
Node* pcur = head;
//将链表间链接起来
while(pcur)
{
Node* Next = pcur->next;
Node* newnode = BuyNode(pcur->val);
pcur->next = newnode;
newnode->next = Next;
pcur = Next;
}
//对加入的新的节点进行赋值
pcur = head;
while(pcur)
{
Node* copy = pcur->next;
if(pcur->random != NULL)
{
copy->random = pcur->random->next;
}
pcur = copy->next;
}
//将两个链表分开
pcur = head;
Node* newHead, *newTail;
newHead = newTail = pcur->next;
while(pcur->next->next)
{
pcur = pcur->next->next;
newTail->next = pcur->next;
newTail = newTail->next;
}
return newHead;
}