数据结构与算法4:单链表相关算法(反转链表、查找链表中的中间结点、合并两个有序链表、环形链表相遇问题)

算法1:反转链表

给定单链表的头结点head,请你反转链表,并返回反转后的链表
思路1:翻指针方向
思路2:头插法

在这里插入图片描述

思路1:翻指针方向

struct ListNode* reverseList(struct ListNode* head) {
	if (head == NULL) {
		return NULL;
	}
	//初始条件
	struct ListNode* n1 = NULL, * n2 = head, * n3 = n2->next;
	//结束条件
	while (n2) {
		//迭代过程
		n2->next = n1;
		n1 = n2;
		n2 = n3;
		if (n3)
			n3 = n3->next;
	}
	return n1;
}

思路2:头插法

struct ListNode* reverseList(struct ListNode* head) {
	struct ListNode* cur = head;
	struct ListNode* newHead = NULL;
	while (cur) {
		struct ListNode* next = cur->next;
		//头插
		cur->next = newHead;
		newHead = cur;
		cur = next;
	}
	return newHead;
}
算法2:链表的中间结点

给定一个头结点为head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点

示例:

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为3。(测评系统对该结点序列化表述是 [3,4,5])
注意,返回了一个ListNode类型的对象 ans ,这样:
ans.val = 3,ans.next.val = 4,ans.next.next.val = 5.以及ans.next.next.next = NULL

struct ListNode* middleNode(struct ListNode* head) {
	struct ListNode* slow = head, * fast = head;
	while (fast && fast->next) {
		slow = slow->next;
		fast = fast->next->next;
	}
	return slow;		
}
算法3:合并两个有序链表

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。
思路:从头开始,去两个链表中小的那个尾插到新链表

示例:
在这里插入图片描述

方法一:

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
	if (l1 == NULL) {
		return l2;
	}
	if (l2 == NULL) {
		return l1;
	}
	struct ListNode* head = NULL, * tail = NULL;
	while (l1 != NULL&&l2 != NULL) {
		if (l1->val < l2->val) {
			if (tail = NULL) {
				head = tail = l1;
			}
			else {
				tail->next = l1;
				tail = tail->next;
			}
			l1 = l1->next;
		}
		else {
			if (tail = NULL) {
				head = tail = l2;
			}
			else {
				tail->next = l2;
				tail = tail->next;
			}
			l2 = l2->next;
		}
	}
	if (l1) {
		tail->next = l1;
	}
	if (l2) {
		tail->next = l2;
	}
	return head;
}

方法二:将第一个结点提出来写

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
	if (l1 == NULL) {
		return l2;
	}
	if (l2 == NULL) {
		return l1;
	}
	struct ListNode* head = NULL, * tail = NULL;
	if (l1->val < l2->val) {
		head = tail = l1;
		l1 = l1->next;
	}
	else {
		head = tail = l2;
		l2 = l2->next;
	}

	while (l1 != NULL && l2 != NULL) {
		if (l1->val < l2->val) {
			tail->next = l1;
			l1 = l1->next;
		}
		else {
			tail->next = l2;
			l2 = l2->next;
		}
		tail = tail->next;
	}

	if (l1) {
		tail->next = l1;
	}
	if (l2) {
		tail->next = l2;
	}
	return head;
}

方法三:定义哨兵结点

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
	if (l1 == NULL) {
		return l2;
	}
	if (l2 == NULL) {
		return l1;
	}
	struct ListNode* head = NULL, * tail = NULL;
	//哨兵位
	head = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
	while ( l1 != NULL && l2 != NULL) {
		if (l1->val < l2->val) {
			tail->next = l1;
			l1 = l1->next;
		}
		else {
			tail->next = l2;
			l2 = l2->next;
		}
		tail = tail->next;
	}
	if (l1) {
		tail->next = l1;
	}
	if (l2) {
		tail->next = l2;
	}
	struct ListNode* first = head->next;
	free(head);
	return first;
}
算法4:环形链表

给定一个链表,判断链表中是否有环,如果链表中有某个结点,可以通过连续跟踪next指针再次到达,则链表中存在环。为了表示给定链表中的环,我们使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)。如果pos是-1,则在该链表中没有环。注意:pos不作为参数进行传递,仅仅是为了标识链表的实际情况。如果链表中存在环,则返回True。否则,返回False。

思路分析:定义快慢指针(slow和fast),slow每次走一步,fast每次走两步。
问题1:slow每次走一步,fast每次走两步一定会在环中相遇吗?为什么?
问题2:slow每次走一步,fast每次走三步一定会在环中相遇吗?为什么?
问题2:slow每次走一步,fast每次走四步一定会在环中相遇吗?为什么?

结论:slow每次走一步,fast每次走两步一定会在环中相遇,slow进环之后,fast开始追,假设fast和slow之间的距离是N,在追的过程中,他们的距离每次会缩短1,因此最终会减少为0,距离为0的时候相遇。如果slow每次走一步,fast每次走三步不一定会在环中相遇,slow进入环后,fast开始追slow,假设他们之间的距离是N,那么fast和slow都各走一步,两者的距离缩短2步,如果N为偶数是可以追到,如果N为奇数时直接从距离为1到-1,代表fast反超了slow而并没有相遇,就进入了新的追逐过程,他们之间的距离是C-1(假设C是环的长度),如果C-1是偶数,就能追上,如果C-1是奇数,那就永远追不上。因为距离是奇数就意味着快追上时,又会距离为-1,然后fast翻炒slow,这里的距离又是C-1,那么就死循环了,永远追不上了。同理slow每次走一步,fast每次走四步不一定会在环中相遇。

在这里插入图片描述

bool hasCycle(struct ListNode* head) {
	struct ListNode* slow = head, * fast = head;
	while (fast && fast->next) {
		slow = slow->next;
		fast = fast->next->next;
		if (slow == fast)
			return true;//相遇
	}
	return false;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值