链表OJ题(二)

一、分割两个链表

思路:我们可以创建两个新的链表,原链表用一个指针cur去遍历比较,小于x的结点放在less链表中,大于x的放在greater链表中,最后再将两个链表链接起来即可

 动画演示:

为了方便操作找尾,开两个哨兵位头结点,方便尾插操作,我们两个链表分别定义lessHead,lessTail和greaterHead,greaterTail指针

注意在最后的时候,greaterTali还链接这lessTail,这样会形成带环结构,这样就会造成程序超过内存限制,要将greaterTail->next=NULL;即可。这一点很容易忽略!!!

代码如下:


struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x)
    {
        ListNode*greaterHead,*greaterTail,*lessHead,*lessTail;
        greaterHead=greaterTail=(ListNode*)malloc(sizeof(ListNode));
        lessHead=lessTail=(ListNode*)malloc(sizeof(ListNode));
        //开两个哨兵位头结点,方便尾插操作
        ListNode*cur=pHead;
        greaterTail->next = NULL;//尾指针的指针域置空
		lessTail->next = NULL;//尾指针的指针域置空
        while(cur)
        {
            if(cur->val<x)
            {
                lessTail->next=cur;
                lessTail=cur;
            }
            else
            {
                greaterTail->next=cur;
                greaterTail=cur;
                
            }
            cur=cur->next;
        }
        lessTail->next=greaterHead->next;
        greaterTail->next=NULL;
        struct ListNode*newhead=lessHead->next;
        free(lessHead);
        free(greaterHead);
      return newhead;
    }
};

二、链表的回文结构

题目传送门

思路:我们需要找到传入链表的中间结点,并将中间结点及其后面结点进行反转,然后再将原链表的前半部分与反转后的后半部分进行比较,若相同,则该链表是回文结构,否则,不是回文结构。

动图演示:

当结点数为偶数时

 当结点数为奇数时:

注意:这里就算我们是奇数个结点,到最后时2结点依旧指向5结点,并没有改变前半部分的结点指向,所以该思路是成立的。

 代码如下:

struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
}; 

class PalindromeList {
public:
	//查找链表的中间结点
	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)//链表中结点头插完毕时停止循环
		{
			struct ListNode* next = cur->next;//记录下一个待头插的结点
			cur->next = newhead;//将结点头插至新链表
			newhead = cur;//新链表头指针后移
			cur = next;//指向下一个待头插的结点
		}
		return newhead;//返回反转后的头指针
	}
	bool chkPalindrome(ListNode* A) 
    {
		ListNode* mid = middleNode(A);//查找链表的中间结点
		ListNode* rHead = reverseList(mid);//反转后半段
		while (rHead&&A)//比较结束的条件
		{
			if (A->val != rHead->val)//比较得到不是回文结构
				return false;
			A = A->next;//指针后移
			rHead = rHead->next;//指针后移
		}
		return true;//是回文结构
	}
};

我们这里是引用了之前写过的反转链表查找中间链表的结点

C++版本:

将值复制到数组中使用双指针法

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        vector<int> v;
        ListNode* cur=head;
        while(cur)
        {
            v.push_back(cur->val);
            cur=cur->next;
        }
        int i=0,j=v.size()-1;
        while(i<j)
        {
            if(v[i]!=v[j]) return false;
            i++;
            j--;
        }
        // 链表长度为奇数时 i=j时,最中间的元素不用判断了,一定是回文了
        return true;
    }
};

时间复杂度O(N)

空间复杂度O(N)

快慢指针法:将空间复杂度优化到O(1)

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if (head == nullptr) {
            return true;
        }

        // 找到前半部分链表的尾节点并反转后半部分链表
        ListNode* firstHalfEnd = endOfFirstHalf(head);
        ListNode* secondHalfStart = reverseList(firstHalfEnd->next);

        // 判断是否回文
        ListNode* p1 = head;
        ListNode* p2 = secondHalfStart;
        bool result = true;
        while (result && p2 != nullptr) {
            if (p1->val != p2->val) {
                result = false;
            }
            p1 = p1->next;
            p2 = p2->next;
        }        

        // 还原链表并返回结果
        firstHalfEnd->next = reverseList(secondHalfStart);
        return result;
    }

    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr;
        ListNode* curr = head;
        while (curr != nullptr) {
            ListNode* nextTemp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }

    ListNode* endOfFirstHalf(ListNode* head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while (fast->next != nullptr && fast->next->next != nullptr) {
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
};

三、合并两个有序链表

题目传送门

思路:我们可以创建一个头结点,然后从两个链表的表头开始依次比较传入的两个链表的结点的大小,并将两个链表中较小的结点尾插到新链表的后面即可。

动图演示:

 代码如下:

struct ListNode {
	int val;
	struct ListNode *next;
};

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{
	struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));//申请一个头结点
	struct ListNode* tail = guard;//找尾
	struct ListNode* cur1 = l1;//记录当前遍历到的l1链表的结点位置
	struct ListNode* cur2 = l2;//记录当前遍历到的l2链表的结点位置
	while (cur1&&cur2)//当l1,l2中有一个链表遍历完毕时便停止
	{
		//取小的结点尾插到新链表后面
		if (cur1->val < cur2->val)
		{
			tail->next = cur1;
			cur1 = cur1->next;
		}
		else
		{
			tail->next = cur2;
			cur2 = cur2->next;
		}
		tail = tail->next;//结点增加,尾指针后移
	}
	//将未遍历完的链表的剩余结点接到新链表后面
	if (cur1)
		tail->next = cur1;
	else
		tail->next = cur2;

	struct ListNode* newhead = guard->next;//新链表的头指针
	free(guard);//释放头结点
	return newhead;//返回新链表
}

C++版本:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* head=new ListNode(-1);// 哨兵位节点,方便尾插
        ListNode* cur=head;
        while(list1!=nullptr && list2!=nullptr)
        {
            if(list1->val<=list2->val)
            {
                cur->next=list1;
                list1=list1->next;
            }
            else
            {
                cur->next=list2;
                list2=list2->next;
            }
            cur=cur->next;
        }
        // 现在不知道哪个链表走完了
        if(list1==nullptr) cur->next=list2;
        if(list2==nullptr) cur->next=list1;

        return head->next;
    }
};

 

四、相交链表

题目传送门

思路一:(暴力求解—穷举法)依次取A链表和B链表中的每个结点比较,如果有地址相同的,那么就是相交,而且第一个相交的就是交点,但是这样时间复杂度就太高了为 O(N^2)

思路二:(优化到O(N))

1、尾结点相同就是相交了

2、求交点,长的链表先走(长度差步),然后两个链表一起走,第一个相同结点地址,就是交点

代码如下:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
	struct ListNode *tailA = headA, *tailB = headB;
	int lenA = 1, lenB = 1;
	while (tailA->next)
	{
		++lenA;
		tailA = tailA->next;
	}
	while (tailB->next)
	{
		++lenB;
		tailB = tailB->next;
	}

	if (tailA != tailB)
	{
		return NULL;
	}

	int gap = abs(lenA - lenB); // 长度差
	struct ListNode *longlist = headA;
	struct ListNode *shortlist = headB;
	if (lenA < lenB) // 假设A短,B长
	{
		longlist = headB;
		shortlist = headA;
	}
	while (gap--) // 长的先走长度差步
	{
		longlist = longlist->next;
	}
	while (longlist != shortlist) // 同时走
	{
		longlist = longlist->next;
		shortlist = shortlist->next;
	}
	return longlist;
}

C++版本:

大佬做的:走到尽头见不到你,于是走过你来时的路,等到相遇时才发现,你也走过我来时的路。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) 
    {
        if(headA==nullptr||headB==nullptr) return nullptr;
        ListNode* pA=headA,*pB=headB;
        while(pA!=pB)
        {
            pA=pA==nullptr?headB:pA->next;
            pB=pB==nullptr?headA:pB->next;
        }
        return pA;
    }
};

时间复杂度O(N)

空间复杂度O(1)

  • 18
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值