不知不觉都2019年了,新的一年,咱都得偿所愿<祈祷>。这篇文章算是对前面链表的复习吧。好久没写博客了。要找回状态。
上面是不带头节点的不循环的单向的链表。后面的OJ是围绕着上面这个图进行的。我们先把链表定义在这儿。
//C语言
#pragma once
typedef struct SListNode
{
int data; //数据 ,默认的是int型的
struct SListNode* next; //指针
}SL;
typedef struct SLHEAD
{
SL* head; //图上的小圆圈。为了找到链表。指向第一个结点。注意这不是头节点
}HEAD;
再回顾一些接口。
//C语言
//获取结点
SL* gain_data(int num)
{
SL* node = (SL*)malloc(sizeof(SL));
assert(node);
node->data = num;
node->next = NULL;
return node;
}
void sl_init(HEAD* sql)
{
sql->head = NULL;
}
//打印链表
void sl_print(HEAD* psl)
{
SL* cur = psl->head;
while (cur != NULL)
{
printf("%d-->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
//链表的头插
void sl_add_front(HEAD* psl, int num)
{
SL* node = gain_data(num);
assert(node);
node->next=psl ->head;
psl->head = node;
}
//链表的尾插
void sl_add_end(HEAD* psl, int num)
{
if (psl->head == NULL)
{
sl_add_front(psl, num);
return;
}
SL* last = psl->head;
while (last->next != NULL)
{
last = last->next;
}
SL* node = (SL*)malloc(sizeof(SL));
node->data = num;
node->next = NULL;
assert(node);
last->next = node;
}
下面开始OJ题的书写,代码里都是把函数名称进行简化了的,提供思路,方便阅读,这里多了第一个结点的指针——head。可以直接把 psl->head 换成 head .再有就是这样就不会太官方,就会很放松的理解和解决。
1)删除链表中等于给定值 val 的所有节点。struct ListNode* removeElements(struct ListNode* head, int val)
题目理解:给一个值,在链表里找到这个值,找到了就删掉。没有就不管。这里是所有结点,不是删除一个相等值的结点。
代码书写:
//C语言
void sl_delall(HEAD* psl, int num)
{
if (psl == NULL) { //如果链表为空就啥都不做
return;
}
SL* prev = psl->head; //定义一个指针指向第一个结点
SL* cur = psl->head->next;//定义一个指针指向第二个结点
while (cur != NULL) {
if (cur->data != num) {//如果不相等的话,就继续往后走
prev = cur;
}
else {
prev->next = cur->next;//相等的话,就把prev指向的结点的 next 指向
//cur这个指针指向的结点 下一个结点。有点绕。
free(cur); //然后释放cur指向的结点。
}
cur = prev->next;
}
SL* newHead = psl->head;
if (psl->head->data == num) {
newHead = psl->head->next;//如果第一个结点就相等,我们定义一个指针指向第二个结点
free(psl); //释放第一个结点
}
psl->head = newHead;
}
(2)反转一个单链表。struct ListNode* reverseList(struct ListNode* head)
这道题比较抽象所以,用图来说。这道题有两种方式来实现。下面是其中一种。
//C语言
void sl_reverse(HEAD* psl)
{
SL* p0 = NULL;
SL* p1 = psl->head; //指向第一个结点
SL* p2 = psl->head->next; //指向第二个结点
while (p1 != NULL) //当没有达到最后一个结点的时候,一直循环
{ //这个我们必须把最后一个结点的next 也要翻转过来
p1 ->next= p0;
p0 = p1;
p1 = p2;
if (p2 != NULL)
{
p2 = p2->next; //因为p1还没有到NULL,所以加个if
}
}
psl->head = p0; //最终的第一个结点是p0所指向的。
}
//给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。struct ListNode* middleNode(struct ListNode* head)
解法是这样的:一个走两步一个走一步。那么一段路程,肯定快的走完后,慢的走了一半。恰巧的是如果是偶数个结点,快的走完的时候,慢的正好走到中间结点的第二个结点的。思路很简单。可能不太好想到。
//C语言
SL* sl_midnode(HEAD* psl)
{
SL* fast = psl->head; //指针快慢法。
SL* slow = psl->head;
while (fast != NULL)
{
fast = fast->next;
if (fast == NULL) //这一步走完,如果fast走到最后的NULL就停止循环,不进行后面的语句
{
break;
}
slow = slow->next;
fast = fast->next;//这里有循环的条件进行判断。
}
return slow;
}
//输入一个链表,输出该链表中倒数第k个结点。 ListNode* FindKthToTail(ListNode* pListHead, unsigned int k)
这道题和上面是一样的。既然是倒数第K个,那么先让fast走K步,那么slow和fast一定是相隔K步,当fast走到结尾,slow就是倒数第K个结点了。
//C语言
SL* sl_botk(HEAD* psl, int k)
{
SL* fast = psl->head;
SL* slow = psl->head;
while ((fast!=NULL)&&(k--))//要保证K小于链表的长度的。
{
fast = fast->next;
}
if ((fast == NULL)) //如果 K 太大,导致fast到了结尾,直接反回空,
{
return NULL;
}
while (fast != NULL)//现在就是双指针往后移动
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
//将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
保证两个链表是有序的,这里只用遍历完一个链表就好,因为是有序的。
//C语言
SL* sl_link(HEAD* psl1, HEAD* psl2)
{
if (psl1 == NULL) //如果第一个链表是空的,我们就返回第二个链表第一个结点的地址就行
{
return psl1->head;
}
if (psl2 == NULL) //如果第二个链表是空的,我们就返回第一个链表的第一个结点的指针
{
return psl2->head;
}
SL* newsl = NULL; //定义四个指针,新链表的第一个结点的地址
SL* newend = NULL; // 新链表的最后一个结点的指针
SL* p1 = psl1->head; //第一个链表的第一个结点的指针
SL* p2 = psl2->head; //第二个链表的第一个结点的指针
while (p1 != NULL && p2 != NULL) //遍历完一个链表就好,因为是有序的
{
if (p1->data <= p2->data) //如果第一个链表的结点数据小于第二个
{
if (newend == NULL) //如果新链表的结尾结点是空的,说明还没开始合并
{
newsl = newend = p1; //两结点同时指向第一个结点
}
newend->next = p1; //如果已经有了结尾结点,就直接在结尾结点插上就好
newend = newend->next;
p1 = p1->next; //指针向后移动
}
else //这是大于的情况,说明第二个结点的数据比较小。就插第二个链表的结点
{
if (newend == NULL)
{
newsl = newend = p2;
}
newend->next = p2;
newend = newend->next;
p2 = p2->next;
}
}
if (p1 == NULL) //遍历完第一个链表,尾节点直接指向第二个就好
{
newend->next = p2;
}
else 遍历完第而个链表,尾节点直接指向第一个就好
{
newend->next = p1;
}
return newsl;
}
//编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。ListNode* partition(ListNode* pHead, int x)
这两部分用四个指针定义两部分的开头和结尾,最后再合起来。
//C语言
SL* sl_comp(HEAD* psl, int x)
{
SL* minp = NULL; //小的部分的开头
SL* minpend = NULL; //小的部分的结尾
SL* maxp = NULL; //大的部分的开头
SL* maxpend = NULL; //大的部分的结尾
SL* temp = psl->head; //指向第一个结点
while (temp != NULL) //遍历链表
{
if (temp->data < x) //小的部分
{
if (minp == NULL) //如果是初识状态,第一次比较后的情况
{
minp = minpend = temp;
}
else
{
minpend->next = temp; //尾节点不为空
minpend = minpend->next; //新的结尾
}
}
else
{
if (maxpend == NULL) //大的同理
{
maxp = maxpend = temp;
}
else
{
maxpend->next = temp;
maxpend = maxpend->next;
}
}
temp = temp->next;
}
if(min == NULL) //如果全是比 x 大的结点
{
return max;
}
if(max != NULL) //有比 x 的 也有比 x 小的,进行合并
{
minend->next = max;
}
rerurn min; //不管有没有max都返回min
}
//在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。ListNode* deleteDuplication(ListNode* pHead)
这道题,需要定义三个结点,两个结点进行比较,一个结点记录前一个位置。画图来理解。下面的代码参考这个图来理解。
//C语言
SL* sl_delsameall(HEAD* psl)
{
if (psl->head == NULL) //如果链表是空的。没必要删除,直接返回空
{
return NULL;
}
SL* p0 = NULL;
SL* p1 = psl->head; //定义两个结点进行重复数据的比较
SL *p2 = psl->head;
while (p2 != NULL) {
if (p1->data != p2->data) { //如果不相等,进行后移
p0 = p1;
p1 = p2;
p2 = p2->next;
}
else {
while (p2 != NULL && p2->data == p1->data) {//相等,一直向后移动
p2 = p2->next;
}
// 2.
if (p2 == NULL) {// p2 走到链表结尾
if (p0 != NULL) {
p0->next = p2; // 全部删除
}
else {
return NULL; //一个结点都没有了
}
break;
}
if (p0 != NULL) {//p2 遇到不相等的值了
p0->next = p2;
}
else {
psl->head->next = p2;
}
p1 = p2;
p2 = p2->next;
}
}
return psl->head;
}
//链表的回文结构。bool chkPalindrome(ListNode* A)
回文:123321 12321 这道题会借用前面写的翻转链表和查找中间链表的函数。
//C语言
int sl_judpal(HEAD* psl)
{
if (psl == NULL)//如果是空链表的话,我算的是回文的,返回1
{
return 1;
}
HEAD temp;
temp.head = sl_midnode(psl);//这是获取中间结点的函数,上面写过
sl_reverse(&temp); //这是翻转一个链表,也写过。
SL* newp = temp.head; //保存翻转后的链表的头节点
while (newp != NULL)
{
if (psl->head->next != newp->next)//如果有不相等的,就不是回文的
{
return 0;
}
psl->head = psl->head->next;
newp = newp->next;
}
return 1;//遍历完反转的链表,都相等,肯定是回文的
}
//输入两个链表,找出它们的第一个公共结点。struct ListNode* getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
这道题需要知道链表的next只能有一个。
//C语言
//写一个求链表长度的函数
int get_length(HEAD* psl)
{
SL* temp;
temp = psl->head;
int len = 0;
while (temp != NULL)
{
temp = temp->next;
++len;
}
return len;
}
SL* sl_findcon(HEAD* psl1,HEAD* psl2)
{
int len1 = get_length(psl1);
int len2 = get_length(psl2);//确定两个链表的长度
SL* longer;
SL* shorter; //定义两个临时的指针,来遍历两个链表
int diff;
diff = len1 - len2;//确定两个相差的长度
if (diff < 0)//小于0说明第二个链表长,反之第一个链表长
{
diff = 0 - diff;
longer = psl2->head;
shorter = psl1->head;
}
else
{
longer = psl1->head;
shorter = psl2->head;//确定谁来遍历长的链表。长的先走
}
for (int i = 0; i < diff; ++i)
{
longer = longer->next;
}
while (longer != shorter)//现在都是相同的长度了。当她们相同后,就停止循环。
{ //注意这是结点相同,不是数据相同。
longer = longer->next;
shorter = shorter->next;
}
return longer;
}
//给定一个链表,判断链表中是否有环。bool hasCycle(struct ListNode *head)
和前面提到的快慢指针很像。这里的思想是一个走两步一个走一步。如果有环肯定会遇见。有环就要是有三个结点以上的结点数。
//C语言
int sl_cycle(HEAD* psl)
{
SL* fast = psl->head;
SL* slow = psl->head;
do
{
if (fast == NULL) //没有结点
{
break;
}
fast = fast->next;
if (fast == NULL) //一个结点
{
break;
}
fast = fast->next;
slow = slow->next;
} while (fast != NULL);
if (fast == NULL) //始终没有遇见
{
return -1;
} //遇见
return 1;
}
最后送上一个花絮