数据结构——详解链表OJ

不知不觉都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;
}

最后送上一个花絮

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值