数据结构与算法_线性表_单链表_七个问题

一共记录单链表操作的七个问题:单链表逆序,求倒数第K个节点,判断单链表是否有环,并求环的入口节点;合并两个有序单链表;判断两个链表是否相交,并求相交节点;删除链表倒数第N个节点;旋转链表。

问题1:单链表逆序

双指针思想:将head置空,然后用两个指针P和Q,P指向链表的第一个节点,q指向p的下一个节点(防止头插后,后面的节点丢失);然后采用头插,让p->next = head->next; head->next = p; p = q;
在这里插入图片描述

void ReverseLink(Clink &link)
{
	Node *head = link.head_;
	Node *p = head->next_;
	if (p == nullptr)
	{
		return;
	}

	head->next_ = nullptr;

	while (p != nullptr)
	{
		Node *q = p->next_;
		// 头插
		p->next_ = head->next_;
		head->next_ = p;
		
		p = q;
	}
}

问题2:单链表求倒数第K个节点

第一步:假设p q同时指针head, p先移动正数第k个节点;

第二步:这时候p 和 q同时移动,当p等于空时,q指向的就是倒数第k个节点;

梳理正数k和倒数k关系:假设K=1,即求倒数第一个节点,用两个指针p和q,并且p和q间距是1,p先走1步,然后pq同时向后走,当p为空的时候,q指向倒数第1个节点;这种方法本质上就是p和q之间的差距是K,这样p是空时候,q恰好是倒数第K个节点。

单链表中判断p和p->next时候的区别?

假设p指向当前节点,如果判断p->next != nullptr ,会漏掉最后一个节点,因为最后一个节点的下一个节点是空的;同时如果是双指针,q也会少走一个节点,
假设p指向当前节点,如果p != nullptr,那么会将从p到最后的节点都输出。

// 求链表倒数第k个节点的值
bool GetLastKNode(Clink &link, int k, int &val)
{
	Node *head = link.head_;
	Node *pre = head;
	Node *p = head;

	if (k < 1) // 程序的健壮性判断
	{
		return false;
	}
	// p 先指向正数第k个节点
	for (int i = 0; i < k; i++)
	{
		p = p->next_;
		if (p == nullptr) // 判断如果正数第k个不存在,那么倒数第k个必不存在
		{
			return false;
		}
	}
	// while的判断条件,当P!=nullptr时候,q指向的是倒数第K个节点;当判断条件是p->next != nullptr时候,q指向的是倒数第k+1个节点
	// 同时移动p pre 直到p为空
	while (p != nullptr)d
	{
		p = p->next_;
		pre = pre->next_;
		val = pre->data_;
		
	}
	return true;
}

问题3:合并两个有序单链表

假定让两个链表合并到链表1中,一共三个节点:last,p ,q;其中, last始终指向新链表的最后一个节点,初始时指向head1节点;p指向head1.next;q指向head2.next; 循环比较p和q,如果p<q,那么last.next = p; p = p.next;
当p为空,q不为空时候,last.next = q;
当q为空,p不为空时候,last.next = p;
在这里插入图片描述

void MergeLink(Link &link1, Link &link2)
{
	Node *last = link1.head;
	Node *p = link1.head->next_;
	Node *q = link2.head->next_;

	// last = nullptr;
	link2.head->next_ = nullptr;
	while (p != nullptr && q != nullptr)
	{
		if (p->data_ < q->data_)
		{
			last->next_ = p;
			p = p->next_;
			
		}
		else
		{
			last->next_ = q;
			q = q->next_;
		}
		last = last->next_;
	}
	if (p == nullptr && q !=nullptr)
	{
		last->next_ = q;
	}
	if (p != nullptr && q == nullptr)
	{
		last->next_ = p;
	}

}

问题4:单链表判断是否有环? 求环的入口节点

见上一篇笔记。

问题5:判断两个单链表是否相交,求相交节点

思路:两个指针分别指向两个链表,第一个指针走完第一个链表,并计算链表个数,假设为num1;第二个同理,数量为Num2;num1-num2得到一个差值=dif;
让长链表先走dif个元素,然后同时开始走,每走一步都判断是否相等,相等的节点就是两个链表相交的节点。
在这里插入图片描述

// 判断两个节点是否相交,如果相交,返回相交节点的值
bool IsLinkHasMerge(Node *head1, Node *head2, int &val)
{
	int cnt1 = 0, cnt2 = 0; // 记录两个链表长度 
	Node *p = head1->next_;
	Node *q = head2->next_;

	while (p != nullptr)
	{
		cnt1++;
		p = p->next_;

	}
	while (q != nullptr)
	{
		cnt2++;
		q = q->next_;
	}
	p = head1;
	q = head2;
	if (cnt1 > cnt2)
	{
		// 第一个链表长 
		int offset = cnt1 - cnt2;

		while (offset-- > 0)
		{
			p = p->next_;
		}
	}
	else
	{
		int offset = cnt2 - cnt1;

		while (offset-- > 0)
		{
			q = q->next_;
		}
	}

	while (p != nullptr && q != nullptr)
	{
		if (p == q)
		{
			val = p->data_;
			return true;
		}
		p = p->next_;
		q = q->next_;
	}
	return false;
}
int main()
{

	Node head1;
	Node n1(1), n2(11), n3(22), n4(33), n5(44);
	head1.next_ = &n1;
	n1.next_ = &n2;
	n2.next_ = &n3;
	n3.next_ = &n4;
	n4.next_ = &n5;

	Node head2;
	Node n6(33);
	head2.next_ = &n6;
	n6.next_ = &n3;

	int val;
	if (IsLinkHasMerge(&head1, &head2,val))
	{
		cout << "链表相交节点是" << "" << val << endl;
	}

	system("pause");
	return 0;
}

问题6:删除链表的倒数第 N 个结点

倒数第N个节点,应该立即想到问题2;
在这里插入图片描述
可以看出这是一个不带头结点的链表,遇到这种情况,为其添加一个头结点。

struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    struct ListNode head_;
    head_.next = head;
    struct ListNode *p = &head_;
    struct ListNode *q = &head_;

    // p 走 n
    for (int i = 0; i < n; i++)
    {
        if (p == NULL)
        {
            return head;  
        }
        p = p->next;
    }

    // q指向了倒数n+1个节点
    while (p->next != NULL)
    {
        q = q->next;
        p = p->next;
        
    }
    // 判断如果p是第一个节点
 


    struct ListNode *del = q->next;
    q->next = q->next->next;
    free(del);

    return head_.next;
}

问题7:旋转链表61

在这里插入图片描述
思路:旋转链表,就是向后旋转链表。

第一步:求链表总个数n;
第二步:p q 指向head, p先移动到整数第k个节点,然后q开始移动;当判断条件是p->next != nullptr,q指向的就是倒数第K+1个节点。
第三步:p->next = head; head = q->next; q->next = nullptr;
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        ListNode *p = head;
        ListNode *q = head;
        
        if (head == nullptr || k ==0)
        {
            return head;
        }
        // 第一步:求链表的个数
        int count = 0;
        for (ListNode *p = head; p != nullptr; p=p->next)
        {
             count++ ;
        }
        k = k % count;
        // 第二步:p先移动到正数第k个节点,然后q开始移动
        for (int i = 0; i < k; i++)
        {
            p = p->next;
            /* code */
        }
        // q指向了倒数第k+1个节点
        while (p->next != nullptr)
        {
            p = p->next;
            q = q->next;
            /* code */
        }
        
        p->next = head;
        head = q->next;
        q->next = nullptr;
        return head;
    }
};
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值