【编织时空三:探究顺序表与链表的数据之旅】

本章重点

  • 链表OJ题

1. 删除链表中等于给定值 val 的所有结点。 OJ链接

思路一:删除头结点时另做考虑(由于头结点没有前一个结点)

struct ListNode* removeElements(struct ListNode* head, int val) {
    assert(head);
    struct ListNode* cur = head;
    struct ListNode* curPrev = NULL;
    while (cur != NULL)
    {
        if (cur->val != val)
        {
            curPrev = cur;
            cur = cur->next;
        }
        else
        {
            if (cur == head)
            {
                head = cur->next;
                free(cur);
                cur = head;
            }
            else
            {
                curPrev->next = cur->next;
                free(cur);
                cur = curPrev->next;
            }
        }
    }
    return head;
}

【编织时空三:探究顺序表与链表的数据之旅】_第1张图片

思路二:添加一个虚拟头结点,删除头结点就不用另做考虑

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* cur = head;
    struct ListNode* newhead = NULL;
    struct ListNode* tail = NULL;
    while (cur != NULL)
    {
        if (cur->val == val)
        {
            struct ListNode* del = cur;
            cur = cur->next;
            free(del);
        }
        else
        {
            //尾插
            if (tail == NULL)
            {
                newhead = tail = cur;
            }
            else
            {
                tail->next = cur;
                tail = tail->next;
            }
            cur = cur->next;
        }
    }
    if (tail)//如果最后一个数是要删除的,tail就需要置空
        tail->next = NULL;
    return newhead;
}

【编织时空三:探究顺序表与链表的数据之旅】_第2张图片

2. 反转一个单链表。OJ链接

思路:通过三个指针的操作,每次将当前节点反转并向前移动

struct ListNode* reverseList(struct ListNode* head) 
{	
    assert(head);    
    struct ListNode* n1, * n2, * n3;
	n1 = NULL;
	n2 = head;
	n3 = n2->next;
	while (n2)
	{
		//翻转
		n2->next = n1;
		//交换
		n1 = n2;
		n2 = n3;
		//记录位置
        if(n2 != NULL)
		    n3 = n3->next;
	}
	return n1;
}

【编织时空三:探究顺序表与链表的数据之旅】_第3张图片

思路:头插法

struct ListNode* reverseList(struct ListNode* head) 
{
	struct ListNode* cur = head;
	struct ListNode* newhead = NULL;
	while (cur)
	{
		//保存cur下一个结点的位置
		struct ListNode* next = cur->next;
		//头插
		next = newhead;
		newhead = cur;
		//更新
		cur = next;
	}
	return newhead;
}

【编织时空三:探究顺序表与链表的数据之旅】_第4张图片

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

思路:快慢指针的前进方向相同,且它们步伐的「差」是恒定的,使用两个指针变量,刚开始都位于链表的第 1 个结点,一个永远一次只走 1 步,一个永远一次只走 2 步,一个在前,一个在后,同时走。这样当快指针走完的时候,慢指针就来到了链表的中间位置。

struct ListNode* middleNode(struct ListNode* head) 
{
	struct ListNode* fast = head;
	struct ListNode* slow = head;
	while (fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	return slow;
}

【编织时空三:探究顺序表与链表的数据之旅】_第5张图片

4. 输入一个链表,输出该链表中倒数第k个结点。OJ链接

首先让快指针先行k步,然后让快慢指针每次同行一步,直到快指针指向空节点,慢指针就是倒数第K个节点。

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k)
{
	struct ListNode* fast = pListHead;
	struct ListNode* slow = pListHead;
	while (k--)//走k步
	{
		//链表没有k步长,那么此时倒数就是空
		if (fast == NULL)
			return NULL;
		fast = fast->next;
	}
	while (fast)
	{
		slow = slow->next;
		fast = fast->next;
	}
	return slow;
}

【编织时空三:探究顺序表与链表的数据之旅】_第6张图片

5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。OJ链接

思路一:我们可以用迭代的方法来实现上述算法。当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
	if (list1 == NULL)
		return list2;
	if (list2 == NULL)
		return list1;
	struct ListNode* newhead = NULL;
	struct ListNode* tail = NULL;
	while (list1 && list2)
	{
		//小值给到新链表上
		if (list1->val < list2->val)
		{
			if (tail == NULL)
			{
				newhead = tail = list1;
			}
			else
			{
				tail->next = list1;
				tail = tail->next;
			}
			list1 = list1->next;
		}
		else
		{
			if (tail == NULL)
			{
				newhead = tail = list2;
			}
			else
			{
				tail->next = list2;
				tail = tail->next;
			}
			list2 = list2->next;
		}
	}
	if (list1)
		tail->next = list1;
	if (list2)
		tail->next = list2;
	return newhead;
}

【编织时空三:探究顺序表与链表的数据之旅】_第7张图片

思路二:哨兵位法,创建一个带头结点的链表,尾插的时候就不需要判断链表是不是为空的尾插情况,最后再释放哨兵位即可。

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
	if (list1 == NULL)
		return list2;
	if (list2 == NULL)
		return list1;
	struct ListNode* newhead = NULL;
	struct ListNode* tail = NULL;
	//哨兵位,方便尾插
	newhead = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
	while (list1 && list2)
	{
		//小值给到新链表上
		if (list1->val < list2->val)
		{
			tail->next = list1;
			tail = tail->next;
			list1 = list1->next;
		}
		else
		{
			tail->next = list2;
			tail = tail->next;
			list2 = list2->next;
		}
	}
	if (list1)
		tail->next = list1;
	if (list2)
		tail->next = list2;
	struct ListNode* del = newhead;
	newhead = newhead->next;
	//释放哨兵位
	free(del);
	return newhead;
}

6. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。OJ链接

思路:首先创建四个节点lessHead,greaterHead,lessTail,greaterTail ,遍历整个链表,比x小的尾插到lessHead为哨兵位的那个链表,比x大的尾插到greaterHead为哨兵位的那个链表,再把两个链表连接起来 ,创建一个list节点指向这个链表 ,把greaterTail->next置空,避免成环 ,释放lessHead,greaterHead,返回list 

struct ListNode* partition(struct ListNode* pHead, int x)
{
    struct ListNode* lessHead, * greaterHead;
    lessHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    greaterHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* lessTail = lessHead;
    struct ListNode* greaterTail = greaterHead;
    struct ListNode* cur = pHead;
    while (cur)
    {
        if (cur->val < x)
        {
            lessTail->next = cur;
            lessTail = lessTail->next;
        }
        else
        {
            greaterTail->next = cur;
            greaterTail = greaterTail->next;
        }
        cur = cur->next;
    }
    lessTail->next = greaterHead->next;

    //此时greaterTail->next仍然链接初始链表的结点,需要置空,否则连环
    greaterTail->next = NULL;
    
    struct ListNode* newhead = lessHead->next;
    free(lessHead);
    free(greaterHead);

    return newhead;
}

【编织时空三:探究顺序表与链表的数据之旅】_第8张图片

7. 链表的回文结构。OJ链接

思路:先找到链表的中间结点,再把中间结点之后的逆序,和之前的链表比较值是否相等。

struct ListNode* middleNode(struct ListNode* head)//找中间结点
{
	struct ListNode* fast = head;
	struct ListNode* slow = head;
	while (fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	return slow;
}
struct ListNode* reverseList(struct ListNode* head)//反转链表
{
	struct ListNode* cur = head;
	struct ListNode* newhead = NULL;
	while (cur)
	{
		//保存cur下一个结点的位置
		struct ListNode* next = cur->next;
		//头插
		next = newhead;
		newhead = cur;
		//更新
		cur = next;
	}
	return newhead;
}
bool chkPalindrome(struct ListNode* A) //查看值是否相等
{
	struct ListNode* midnode = middleNode(A);
	struct ListNode* reversemidnode = reverseList(midnode);
	while (A && reversemidnode)
	{
		if(A->val != reversemidnode->val)
		{
			return false;
		}
		A = A->next;
		reversemidnode = reversemidnode->next;
	}
	return true;
}

【编织时空三:探究顺序表与链表的数据之旅】_第9张图片

8. 输入两个链表,找出它们的第一个公共结点。OJ链接

思路:先计算两个链表的长度,再让较长的链表走差距步abs(lenA-LenB)长度,然后再依次比较是否相等。

struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB) 
{
	struct ListNode* curA = headA, * curB = headB;
	//找尾结点 - 结点总数会少一个
	int lenA = 1;//设置为1
	int lenB = 1;//设置为1
	while (curA->next)
	{
		curA = curA->next;
		lenA++;
	}
	while (curB->next)
	{
		curB = curB->next;
		lenB++;
	}
	//两个链表不相交
	if (curA != curB)
		return NULL;

	//找长链表
	struct ListNode* LongList = headA, * ShortList = headB;
	if (lenA < lenB)
	{
		LongList = headB;
		ShortList = headA;
	}

	//长链表走绝对值(lenA - lenB)步
	int count = abs(lenA - lenB);
	while (count--)
	{
		LongList = LongList->next;
	}

	//同时向后走,相同就停下来
	while (LongList != ShortList)
	{
		LongList = LongList->next;
		ShortList = ShortList->next;
	}
	return ShortList;
}

【编织时空三:探究顺序表与链表的数据之旅】_第10张图片9. 给定一个链表,判断链表中是否有环。OJ链接

思路:快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表其实位置开始运行, 如果链表 环则一定会在环中相遇,否则快指针率先走到链表的末尾。

bool hasCycle(struct ListNode *head) {
    struct ListNode* fast = head ,* slow = head;
    while(fast && fast->next)
    {
        fast = fast->next;
        slow = slow->next->next;

        if(fast == slow)
           return true;
    }
    return false;
}

【编织时空三:探究顺序表与链表的数据之旅】_第11张图片

 为什么快指针每次走两步,慢指针走一步可以?

  • 假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚 进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。 此时,两个指针每移动一次,快指针走两次,慢指针走一次,之间的距离就缩小一步,直至最后差距为0,不会出现每次刚好是套圈的情况,因此:在满指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。

快指针一次走3步,走4步,...n步行吗?

  • 【编织时空三:探究顺序表与链表的数据之旅】_第12张图片
  • 【编织时空三:探究顺序表与链表的数据之旅】_第13张图片

10. 给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL。OJ链接

思路一:一个指针从链表起始位置运行,一个指针从相遇点位置绕环,每次都走一步,两个指针最终会在入口点的位置相遇

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *fast = head,*slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fase->next->next;

        //相遇在环内任意一个位置
        if(slow == fast)
        {
            struct ListNode *meet = slow;
            //两结点关系L = (N-1) * C + C - X;
            while(head != meet)
            {
                head = head->next;
                meet = meet->next;
            }
            return meet;
        }
    }
    return NULL;
}

​

【编织时空三:探究顺序表与链表的数据之旅】_第14张图片

 思路二:两个指针相遇的地方的下一个结点置空,下一个结点位置和链表头指针此时就可以转为两条链表求解公共点的问题。

struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)//相交点 
{
	struct ListNode* curA = headA, * curB = headB;
	//找尾结点 - 结点总数会少一个
	int lenA = 1;//设置为1
	int lenB = 1;//设置为1
	while (curA->next)
	{
		curA = curA->next;
		lenA++;
	}
	while (curB->next)
	{
		curB = curB->next;
		lenB++;
	}
	//两个链表不相交
	if (curA != curB)
		return NULL;

	//找长链表
	struct ListNode* LongList = headA, * ShortList = headB;
	if (lenA < lenB)
	{
		LongList = headB;
		ShortList = headA;
	}

	//长链表走绝对值(lenA - lenB)步
	int count = abs(lenA - lenB);
	while (count--)
	{
		LongList = LongList->next;
	}

	//同时向后走,相同就停下来
	while (LongList != ShortList)
	{
		LongList = LongList->next;
		ShortList = ShortList->next;
	}
	return ShortList;
}
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *fast = head,*slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fase->next->next;

        //相遇在环内任意一个位置
        if(slow == fast)
        {
            struct ListNode *meet = slow;
            struct ListNode *newhead = meet->next;
            meet->next = NULL;
            return getIntersectionNode(head,newhead);
        }
    }
    return NULL;
}

11.给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空结点。 要求返回这个链表的深度拷贝。OJ链接

思路:迭代 + 节点拆分

方法:

【编织时空三:探究顺序表与链表的数据之旅】_第15张图片

struct Node* copyRandomList(struct Node* head) {
	struct Node* cur =  head;
    while(cur)
    {
        struct Node* next = cur->next;
        struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
        copy->val = cur->val;

        //插入
        cur->next = copy;
        copy->next = next;

        //往后走
        cur = next;
    }

    cur = head;
    while(cur)
    {
        struct Node* copy = cur->next;

        //置 copy random
        if(cur->random == NULL)
            copy->random = NULL;
        else
            copy->random = cur->random->next;
        cur = copy->next;
    }

    cur = head;
    struct Node* copyhead = NULL,*cpoytail = NULL;
    while(cur)
    {
        struct Node* copy = cur->next;
        struct Node* next = copy->next;

        //copy结点尾插到新链表
        if(cpoytail == NULL)
        {
            copyhead = copytail = copy;
        }
        else
        {
            copytail->next = copy;
            copytail = copytail->next;
        }

        //恢复原链表
        cur->next = next;
        cur = next;
    }
    return copyhead;
}

【编织时空三:探究顺序表与链表的数据之旅】_第16张图片

 本章结束啦!!!

【编织时空三:探究顺序表与链表的数据之旅】_第17张图片

你可能感兴趣的:(数据结构,链表,数据结构)