链表的经典问题
单纯记录学习过程
定义一个结构体
typedef _ListNode *PtrToNode
struct _ListNode {
int data;
PtrToNode next;
};
typedef PtrToNode List;
1.如何判断两个单链表是否相交,如果相交,找出交点(两个链表都不存在环)
思路:设两个单链表的长度分别为L1、L2,(假设L1>L2),则(L1-L2)的值就是交汇之前两个链表的长度差;
因此,只有让更长的链表先走L1-L2步,然后两个链表开始一起走,如果某次走到一个相同的节点,该节点即为交点。
static int GetListLength(List T)
{
int n;
for(n=0; T; n++) {
T = T->next;
}
return n;
}
static List FindFirstCommonNode(List T1, List T2)
{
int i;
int n1 = GetListLength(T1);
int n2 = GetListLength(T2);
// T1 always own longer list
if (n1 < n2) {
return FindFirstCommonNode(T2, T1);
}
for (i=0; i<n1-n2; i++) {
T1 = T1->next;
}
while (T1 && T1 != T2) {
T1 = T1->next;
T2 = T2->next;
}
return T1;
}
2.判断一个链表是否有环,并找到环的入口点
思路:快慢指针
这个地方网上很多人解释 slow = Head开始有点困难,我的理解是这样的:两者相遇,设慢的指针走了 s ,那么快的指针走了 2s ,这里有两点要清楚:
1,这时候快的指针已经在环里至少转了1圈,
2,而慢的指针不可能转满了1圈。
- 前面这一点比较好理解,他们正好在环入口相遇,也可能进入环的路程相对环比较大,快的指针在里面转了几圈才相遇。
- 后面这一点难理解一点,假设两个指针在环的同一点出发,快的指针正好在转满一圈的时候与慢的指针相遇,所以在这个情况下不论慢的指针入环的时候快的指针在他前面还是后面,都会在一圈之内追上。
我们可以做一个简单的数学计算,设环的大小为r,快的指针在里面转了n圈就有:
2s = s+nr
----->
s=nr
这个关系可能看不出什么东西
再来一个关系,设慢的指针进入环之前的路程为x,进入环之后为y,(y<r前面提到过)
x+y=s
两者结合的得到:
x+y=nr
x=nr-y
也就是说进入环之前的距离x 和 从环的入口开始转n圈 之间差了 y ,而这个y是两个指针相遇的地方,也就是快指针现在在的地方,在这个地方快指针如果改为1步1步的走,将会在入口和慢指针相遇。
换句话说,如果慢指针从头开始走,快指针从环的入口开始走,每次都是走1步,那么慢指针到达环的入口时,快指针已经转了n-1圈,第n圈还差y的距离到达入口。所以当我们把快指针放到两者相遇的地方,也就是离入口y的地方,他们正好在入口相遇。
static List FindLoopPort(List Head)
{
List slow = Head;
List fast = Head;
// 找到相遇点
while ( fast && fast->next ) {
slow = slow->next;
fast = fast->next->next;
if ( slow == fast ) break;
}
if (fast == NULL || fast->next == NULL)
return NULL;
// 找到环入口点
slow = Head;
while (slow != fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
3.求一个单链表(无环)的中间节点
思路:设置两个指针(fast, slow),初始值都指向头节点,slow每次前进一步,fast每次前进二步,当fast走到末尾时,slow刚好指向中间节点。
4.假如链表长度为N,如何返回链表的倒数第K个结点
思路:用两个指针,指针P1先走K-1步,然后指针P2才开始走,当指针P1遍历完链表时,P2还剩K个结点没有遍历。
List FindLastKNode(List Head, int K)
{
if (NULL == Head)
return NULL;
List T1 = Head;
List T2 = Head;
while (T1 && --K)
T1 = T1->next;
if (K) // here, K must be 0
return NULL;
while (T1->next) {
T1 = T1->next;
T2 = T2->next;
}
return T2;
}
5.在O(1)时间删除链表结点
思路:可以知道其下一个结点是B=A->next,将结点B值拷贝给A,然后删除B即可。
6.如何反转一个单链表
思路1:改变next指针指向,后面元素指向前面元素
static List ReverseList(List Head)
{
List pNode = Head;
List pNext = NULL;
List pPrev = NULL;
while (pNode) {
pNext = pNode->next;
if (NULL == pNext)
Head = pNode;
pNode->next = pPrev;
pPrev = pNode;
pNode = pNext;
}
return Head;
}
思路2:新建一个链表,头插法插入数据
List change(List L1)
{
int flag =0;
List p1 = (List)malloc(sizeof(struct Node));
p1->Next = NULL;
List temp1 = p1;
L1 = L1->Next;
while(L1->Next != NULL){
List temp_1 = (List)malloc(sizeof(struct Node));
temp_1->Data = L1->Data;
temp_1->Next = temp1->Next;
temp1->Next = temp_1;
L1 = L1->Next;
}
if(L1->Next == NULL){
List temp = (List)malloc(sizeof(struct Node));
temp->Data = L1->Data;
temp->Next = temp1->Next;
temp1->Next = temp;
}
return p1;
}
7.复杂链表的复制
思路
static void CloneNodes(ListNode *Head)
{
ListNode *p = Head;
while (p) {
ListNode *pCloned = malloc(sizeof(ListNode));
pCloned->data = p->data;
pCloned->next = p->next;
pCloned->other = NULL;
p->next = pCloned;
p = pCloned->next;
}
}
static void ConnectNodes(ListNode *Head)
{
ListNode *pCloned;
ListNode *p = Head;
while(p){
pCloned = p->next;
if (p->other) {
pCloned->other = p->other->next;
}
p = pCloned->next;
}
}
8.翻转部分链表
思路:按部分拆分链表,分别翻转每一部分。每一部分的第一个元素为部分尾元素,最后一个元素为头元素,往后部分的头元素接在上一部分的为尾元素之后
所以需要五个指针 : 当前位置指针,部分模块头元素指针,部分模块尾元素指针,记录上一部分尾元素的指针,新的链表头指针
List RolloverList(List list, int k)
{
int flag=0;
if (k <= 1 || list == NULL) {
return list;
}
List cur = list; //指向链表的头节点
List newHead = NULL; //指向逆置后的链表
List sectionHead = NULL; //指向需要逆置部分的头节点
List sectionTail = NULL; //指向需要逆置部分的尾节点
List prevsectionTail = NULL; //指向部分尾节点的指针
while (cur) {
int count = k;
prevsectionTail = sectionTail;
sectionTail = cur;
while (count-- && cur != NULL) {
List tmp = cur;
cur = cur->Next;
tmp->Next = sectionHead;
sectionHead = tmp;
}
if (flag == 0){
flag=1;
newHead = sectionHead;
}
else{
prevsectionTail->Next = sectionHead;
}
}
sectionTail->Next = NULL;
return newHead;
}
9.合并有序链表
思路:比较两个链表数据,数据大的向后移动一位
List Merge(List L1, List L2)
{
List p = (List)malloc(sizeof(struct Node)); //带头结点的链表
List p1 = L1->Next;
List p2 = L2->Next;
List p3 = p;
while (NULL != p1 && NULL != p2){
if (p1->Data <= p2->Data){
p3->Next = p1;
p3 = p3->Next;
p1 = p1->Next;
}
else{
p3->Next = p2;
p3 = p3->Next;
p2 = p2->Next;
}
}
if (NULL == p1){
p3->Next = p2;
}
if (NULL == p2){
p3->Next = p1;
}
L1->Next = NULL;
L2->Next = NULL;
return p;
}
10.获取两个升序链表相同元素
void getSameElement(List L1,List L2)
{
if(L1->Next != NULL){
L1 = L1->Next;
}
if(L2->Next != NULL){
L2 = L2->Next;
}
while(L1->Next != NULL && L2->Next != NULL){
if(L1->Data > L2->Data){
L2 = L2->Next;
}
else if(L2->Data > L1->Data){
L1 = L1->Next;
}
else if(L2->Data == L1->Data){
printf("%d ",L1->Data);
L1 = L1->Next;
L2 = L2->Next;
}
}
if(L1->Next == NULL && L2->Next == NULL){
if(L2->Data == L1->Data){
printf("%d ",L1->Data);
}
}
while(L1->Next != NULL){
if(L1->Data>L2->Data){
break;
}
else if(L2->Data>L1->Data){
L1 = L1->Next;
}
else if(L2->Data==L1->Data){
printf("%d ",L1->Data);
break;
}
}
while(L2->Next != NULL){
if(L2->Data>L1->Data){
break;
}
else if(L1->Data>L2->Data){
L2 = L2->Next;
}
else if(L2->Data==L1->Data){
printf("%d ",L1->Data);
break;
}
}
}
11.删除倒数第k个元素
思路:让一个指针先走k,随后两个指针同时走
List dele_k(List L1,int k)
{
List p1 = L1;
List p2 = L1;
List del_p;
List head = L1;
if(L1 == NULL){
return head;
}
while(p1->Next != NULL && k>0){
p1 = p1->Next;
k--;
}
if(k>0 && p1->Next == NULL){
return head;
}
else if(k == 0 && p1->Next != NULL){
while(p1->Next != NULL){
p1 = p1->Next;
p2 = p2->Next;
}
del_p = p2->Next;
p2->Next = p2->Next->Next;
free(del_p);
}
else if(k == 0 && p1->Next == NULL){
head = head->Next;
}
return head;
}
12.初始化链表
List Read(int n)
{
List p = (List)malloc(sizeof(struct Node)); //带头结点的链表
p->Next = NULL;
List temp1 = p;
for (int i = 0; i < n; i++){
List temp = (List)malloc(sizeof(struct Node));
scanf("%d", &temp->Data);
temp1->Next = temp;
temp1 = temp1->Next;
}
temp1->Next = NULL; //链表结尾指向NULL
return p;
}
13.遍历打印链表
void Print(List L)
{
List temp = L->Next;
if (NULL == temp){
printf("空链表!");
}
while (NULL != temp){
printf("%d ",temp->Data);
temp = temp->Next;
}
}
主函数,方便学习直接使用以上模块
int main()
{
int n;
List L1;
scanf("%d", &n);
L1 = Read(n);
Print(L1);
return 0;
}