总结:链表的中级算法实际上就是链表的基础操作的实际应用。题目1涉及到链表的遍历,节点的创建及添加;题目2涉及链表的遍历和链表节点的移位;题目3涉及到链表的遍历,当然题目3也可以使用快慢指针来解决问题,我们可以将链表1头尾相连成为环,之后用快慢指针遍历链表2,判断其有没有环,若有环则两链表相连,反之不相连。这种方法之后二刷的时候更新。
题目1:两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
思路:首先遍历两个链表,得到两个链表长度,将l1指向较长链表,将l2指向较短链表。之后用两个指针同时遍历两个链表,计算两链表对应节点的和,以及新节点的值和进位值,为了节省空间,我们直接在l1上进行更新。当较短链表更新完成之后,若进位值不为0,则继续更新长链表后续节点,若处理完最后一个节点之后进位值仍不为0,那么就要创建一个新节点并将其放入链表。
ListNode* addTwoNumbers(ListNode* l1,ListNode* l2)
{
if(l1==NULL||l2==NULL)
return l1==NULL?l2:l1;
ListNode* point = NULL;
int len1 = 0;
int len2 = 0;
//将l1指向较长链表,将l2指向较短链表
point = l1;
while(point!=NULL)
{
len1 += 1;
point = point->next;
}
point = l2;
while(point!=NULL)
{
len2 += 1;
point = point->next;
}
if(len1<len2)
{
point = l1;
l1 = l2;
l2 = point;
}
point = l1;
ListNode* p1 = l1;
ListNode* p2 = l2;
int num = 0;
//进位
int extra = 0;
//新节点的值
int value = 0;
//链表相加
while(p2!=NULL)
{
num = p1->val+p2->val+extra;
extra = num/10;
value = num%10;
point->val = value;
p1 = p1->next;
p2 = p2->next;
if(p1!=NULL)
point = point->next;
}
//处理长链表剩余节点
while(extra!=0)
{
if(p1==NULL)
{
ListNode* node = new ListNode(extra);
point->next = node;
break;
}
num = p1->val+extra;
extra = num/10;
value = num%10;
point->val = value;
p1 = p1->next;
if(p1!=NULL)
point = point->next;
}
return l1;
}
题目2:奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:
1.应当保持奇数节点和偶数节点的相对顺序。
2.链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
思路:在链表中不停遍历奇节点,之后用尾插法将其插入链表前半部分的奇节点链表中,当所有奇节点插入到前半部分链表中,偶节点就自然而然再链表后半部分排列完成。
PS:如果不是太明白可以参考下面这张图
ListNode* oddEvenList(ListNode* head)
{
if(head==NULL||head->next==NULL)
return head;
//奇数节点链表最后一个节点
ListNode* point1 = head;
//指向奇节点前一个节点的指针
ListNode* point2 = head->next;
ListNode* temp = NULL;
while(point2!=NULL&&point2->next!=NULL)
{
temp = point2->next;
point2->next = temp->next;
temp->next = point1->next;
point1->next = temp;
point1 = temp;
point2 = point2->next;
}
return head;
}
题目3:相交链表
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在节点 c1 开始相交。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。
注意:
1.如果两个链表没有交点,返回 null.
2.在返回结果后,两个链表仍须保持原有的结构。
3.可假定整个链表结构中没有循环。
4.程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
思路:用两个指针指向两个链表,然后两个指针同时移动,如果相遇则说明有交点。但是要注意一点,两个指针不一定指向两个链表的头结点。当两个链表的长度不一样长时,将两个指针指向两个链表头结点,那么这两个指针永远不会相遇,指向长链表的指针永远在指向短链表指针的后面。所以当长度不一样长时,我们要先将长链表的指针移动到剩余节点长度和短链表相同的节点处,之后再同时移动,判断两指针是否会相遇。
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
if(headA==NULL||headB==NULL)
return NULL;
ListNode* p1 = headA;
ListNode* p2 = headB;
int lenA = 0,lenB = 0;
//得到两个链表长度
while(p1!=NULL)
{
lenA++;
p1 = p1->next;
}
while(p2!=NULL)
{
lenB++;
p2 = p2->next;
}
p1 = headA;
p2 = headB;
//将p1永远指向长链表
if(lenB>lenA)
{
p1 = headB;
p2 = headA;
}
//p1指针需要移动的长度
int len = abs(lenA-lenB);
while(len>0)
{
p1 = p1->next;
len--;
}
while(p1!=p2)
{
p1 = p1->next;
p2 = p2->next;
}
return p1;
}