一、分割两个链表
思路:我们可以创建两个新的链表,原链表用一个指针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)