LeetCode刷题——链表相关
1、链表逆序(全)
1.1 题目描述
已知链表头结点指针head,将链表逆序,要求不能申请额外的空间。
1.2 实现思想
依次遍历链表中的每一个节点,每遍历一个节点就逆序一个节点。具体流程如下:
初始时,我们声明一个指针new_head,用于保存逆置后的链表。只需要声明,无需申请内存。
下面开始迭代:
第一轮:
在该轮中,我们要做的是一下三步:
- 保存head->next,防止在更新过程中,后面的链表丢失,我们这里用 next = head->next
此时next指向的是2。 - 将head->next指针进行逆置,令其指向新的头结点new_head,也就是让1的next指向new_head
- 将new_head向前移动一个,让new_head指向1,将将next赋值为head,让head指向2。
第一轮迭代之后的结果为:
第二轮:
第二轮的过程与第一轮的循环过程类似,这里就不具体描述了。直接给出执行之后的结果图。
其他迭代过程与之前完全相同,这里直接给出示意图:
当第5轮的迭代结束之后,所有的链表节点已经逆序结束,迭代结束,返回结果。
1.3 代码实现
Node* Solution::reverse(Node * head)
{
Node * new_head = NULL;
while (head != NULL)
{
Node * next = head->next;
head->next = new_head;
new_head = head;
head = next;
}
return new_head;
}
2 链表逆序(部分)
2.1 题目描述
已知链表的头结点指针head,将链表从位置m到n进行逆序,要求不能申请额外的空间
2.2 解题思路
与第一道题相比,这道题的大致思想就是在例题1的基础上确定待逆序的部分链表,在确定位置之后,将这部链表进行逆序,再与原链表进行连接即可。问题在于我们要确定哪些位置?
- 我们首先要确定的是逆序的部分链表的头、尾节点。
- 为了保证这部分链表在逆序之后,仍然可以和之前的链表相连接,我们要确定部分链表头部的前一个节点和部分链表尾部的后一个节点。
下面我们进行具体步骤:
a. 我们首先通过将head指针后移m-个位置来获取待逆置段的头结点。在移动的过程中同时确定逆置段节点的前一个节点。
b. 从当前的head节点开始,逆置n-m+1个节点。
此时,通过逆置,原来的head指针已经到指向了逆置段的下一个节点。
c. 将逆置后的部分链表与原来的链表进行连接
2.3 代码实现
Node * Solution::reverse(Node * head, int m, int n)
{
int chageNum = n - m + 1;//确定了待逆转的长度
Node * pre_head = NULL;//用于指向待逆转的前一个节点
Node * final_head = head;//保存最终的head,用于返回整个链表
while (head && --m)
{
pre_head = head;
head = head->next;
}
//保存一下待逆置的段的头部,稍后用于和待逆置段的尾部的下一个节点相连接。
Node * wait_modify_head = head;
Node * new_head = NULL;
while (head && chageNum)
{
Node * next = head->next;
head->next = new_head;
new_head = head;
head = next;
chageNum--;
}
//经过逆置之后,head处于逆置段的下一个节点,将其和逆置段的尾部进行连接
wait_modify_head->next = head;
//现在考虑,当不是从第一个位置开始逆置的时候,直接让pre_head的next直线new_head即可
if (pre_head)
pre_head->next = new_head;
//否则,如果m等于1的时候,表示此时的pre_head是NULL,此时pre_head 指向逆置之后的new_head即可
else
pre_head = new_head;
return final_head;
}
3 求两个链表的交点
3.1 问题描述
已知链表A的头结点指针headA,链表B的头结点指针headB,两个链表相交,求两个链表交点对应的节点。
要求:
- 如果两个链表没有交点,则返回NULL
- 在求交点的过程中,不能够破坏原有链表的节后或者修改链表的数据域。
- 可以保证传入的链表A和链表B没有任何的环结构。
- 尽可能实现的算法应该尽可能使时间复杂度O(n),空间复杂度O(1)
3.2 知识扩充——STL容器
所谓的“容器”,指的就是存储数据的数组,列表等等。容器具体可以分成两种,第一种是顺序容器。第二种是关联容器。
-
顺序容器
std::vector STL中的动态数组容器,操作方式和普通的动态数组类似,可以按照下标访问。
std::deque 与STL::vector的操作类似,但是允许在开头或者结尾插入数据。
std::list 操作和双向链表类似,可以在任何的位置插入或者删除元素。
std::forward_list 类似于std::list,但是是一个单向链表,只能沿着一个方向进行遍历。 -
关联容器:按照事先指定的顺序进行存储数据,在插入数据的时候,需要进行排序。
std::set 存储不相同的值,在插入的时候需要排序。
std::map 存储键值对,对键进行排序,要求键值唯一。
std::multi 类似于set,但是存储是可以存储相同的值。 -
基本方法:
std::insert:向集合中插入数据
std::find:在集合中查找。
std::find_if:根据条件在集合中查找
std::reverse 翻转集合中的值
std:: remove_if:根据条件删除容器中的值。
std::end:返回set中末尾元素的下一个元素
3.3 基本思路一
该思路采用的是STL模板中的set容器,基本步骤如下:
- 将链表A的各个节点存入到set中。
- 遍历链表B,B中的第一个在set中出现的节点就是相交的节点。
3.4 代码实现一
Node * Solution::getIntersection(Node * headA, Node * headB)
{
std::set<Node*> node_set;
//将A中的节点送入到set中去
while (headA)
{
node_set.insert(headA);
headA = headA->next;
}
//迭代B,查找重复
while (headB)
{
if (node_set.find(headB) != node_set.end())
return headB;
headB = headB->next;
}
return NULL;
}
3.5 基本思路二
这种方法的核心思想是将指针对齐,然后令headA和headB一起向后进行比较。我们先给出图示:
在上面的图中,我们可以发现在A,B两条链表相交之前,B的长度要比A长出两个单位,我们的操作就是直接跳过这个两个节点,进行后面的比较。
在对齐之后,headA和headB一起向后移动,直到找到相同节点,或者没有相同节点。
3.6 代码实现
int listLength(Node * head)
{
int len = 0;
while (head)
{
len++;
head = head->next;
}
return len;
}
Node *changePos(Node * head,int long_len,int short_len)
{
int changeLen = long_len - short_len;
while (head && changeLen)
{
head = head->next;
changeLen--;
}
return head;
}
Node * Solution::getIntersection2(Node * headA, Node * headB)
{
int Alen = listLength(headA);
int Blen = listLength(headB);
if (Alen >= Blen)
{
headA= changePos(headA, Alen, Blen);
}
else
{
headB = changePos(headB, Blen, Alen);
}
while (headA&&headB) {
if (headA == headB)
return headA;
headA = headA->next;
headB = headB->next;
}
return NULL;
}