单链表的相关问题
链表经典算法OJ题目
移除链表元素
题目链接:移除链表元素
/**
* Definition for singly-linked list.
* struct ListNode
* {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val)
{
//创建一个空链表
struct ListNode* newHead = NULL;
struct ListNode* newTail = NULL;
//遍历原链表
struct ListNode* pcur = head;
while(pcur)
{
//找到值不为value的节点,尾插到新链表中
if(pcur->val != val)
{
//链表为空
if(newHead == NULL)
{
newHead = newTail = pcur;
}
//链表不为空
else
{
newTail->next = pcur;
newTail = newTail->next;
}
}
pcur = pcur->next;
}
if(newTail)
{
newTail->next = NULL;
}
return newHead;
}
反转链表
题目链接:反转链表
/**
* Definition for singly-linked list.
* struct ListNode
* {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head)
{
if(head == NULL)
{
return head;
}
//创建三个指针
struct ListNode* n1 = NULL;
struct ListNode* n2 = head;
struct ListNode* n3 = head->next;
while(n2)
{
n2->next = n1;
n1 = n2;
n2 = n3;
if(n3)
{
n3 = n3->next;
}
}
return n1;
}
合并两个有序链表
题目链接:合并两个有序链表
/**
* Definition for singly-linked list.
* struct ListNode
* {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
if(list1 == NULL)
{
return list2;
}
if(list2 == NULL)
{
return list1;
}
struct ListNode* l1 = list1;
struct ListNode* l2 = list2;
//创建新的链表
struct ListNode* newHead = NULL;
struct ListNode* newTail = NULL;
newHead = newTail = (struct ListNode*)malloc(sizeof(struct ListNode));
while(l1 && l2)
{
if(l1->val < l2->val)
{
//l1尾插
if(newHead == NULL)
{
newHead = newTail = l1;
}
else
{
newTail->next = l1;
newTail = newTail->next;
}
l1 = l1->next;
}
else
{
//l2尾插
if(newHead == NULL)
{
newHead = newTail = l2;
}
else
{
newTail->next = l2;
newTail = newTail->next;
}
l2 = l2->next;
}
}
//跳出循环代表至少有一个为空
if(l1)
{
newTail->next = l1;
}
if(l2)
{
newTail->next = l2;
}
//手动释放掉动态申请的空间
struct ListNode* ret = newHead->next;
free(newHead);
newHead = NULL;
return ret;
}
链表的中间结点
题目链接:链表的中间结点
/**
* Definition for singly-linked list.
* struct ListNode
* {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode* slow = head;
struct ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
环形链表的约瑟夫问题
题目链接:环形链表的约瑟夫问题
著名的Josephus问题
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
然而Josephus和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param n int整型
* @param m int整型
* @return int整型
*/
typedef struct ListNode ListNode;
ListNode* BuyNode(int x)//创建节点
{
ListNode* node = (ListNode*) malloc(sizeof(ListNode));
if(node == NULL)
{
perror("malloc failed");
exit(1);
}
node->val = x;
node->next = NULL;
return node;
}
ListNode* CreateListNode(int n)//创建带环链表
{
//先创建头节点
ListNode* phead = BuyNode(1);
ListNode* ptail = phead;
for(int i = 2; i <= n; i++)//尾插
{
ptail->next = BuyNode(i);
ptail = ptail->next;
}
//首尾相连
ptail->next = phead;
return ptail;
}
int ysf(int n, int m )
{
//1. 根据n创建带环链表
ListNode* prev = CreateListNode(n);
ListNode* pcur = prev->next;
//2. 报数
int count = 1;
while(pcur->next != pcur)//指针指向自己,证明只剩一个
{
if(count == m)
{
//销毁pcur节点
prev->next = pcur->next;
free(pcur);
pcur = prev->next;
count = 1; //重新报数
}
else
{
prev = pcur;
pcur = pcur->next;
count++;
}
}
return pcur->val;
}
基于单链表再实现通讯录项目
contact.h
#pragma once
#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 11
#define ADDR_MAX 100
//前置声明
typedef struct SListNode contact;
//用户数据
typedef struct PersonInfo
{
char name[NAME_MAX];
char sex[SEX_MAX];
int age;
char tel[TEL_MAX];
char addr[ADDR_MAX];
}PeoInfo;
//初始化通讯录
void InitContact(contact** con);//实际调用的是链表的初始化接口(可以简单做一个头结点的初
//添加通讯录数据
void AddContact(contact** con);// 链表尾插/头插
//删除通讯录数据
void DelContact(contact** con);//链表的删除指定位置的数据
//展示通讯录数据
void ShowContact(contact* con);//链表的打印
//查找通讯录数据
void FindContact(contact* con);
//修改通讯录数据
void ModifyContact(contact** con);
//销毁通讯录数据
void DestroyContact(contact** con);
SList.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"contact.h"
typedef struct PersonInfo SLTDataType;
//typedef int SLTDataType;
typedef struct SListNode {
struct SListNode* next;
SLTDataType data;
}SLTNode;
void SLTPrint(SLTNode* phead);
//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
contact.c
#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"
#include"SList.h"
void LoadContact(contact** con)
{
FILE* pf = fopen("contact.txt", "rb");
if (pf == NULL) {
perror("fopen error!\n");
return;
}
//循环读取文件数据
PeoInfo info;
while (fread(&info, sizeof(info), 1, pf))
{
SLTPushBack(con, info);
}
printf("历史数据导入通讯录成功!\n");
}
void InitContact(contact** con)
{
LoadContact(con);//把本地的通讯录数据导入到链表结构
}
void AddContact(contact** con)
{
PeoInfo info;
printf("请输入姓名:\n");
scanf("%s", &info.name);
printf("请输入性别:\n");
scanf("%s", &info.sex);
printf("请输入年龄:\n");
scanf("%d", &info.age);
printf("请输入联系电话:\n");
scanf("%s", &info.tel);
printf("请输入地址:\n");
scanf("%s", &info.addr);
SLTPushBack(con, info);
printf("插入成功!\n");
}
contact* FindByName(contact* con, char name[])
{
contact* cur = con;
while (cur)
{
if (strcmp(cur->data.name, name) == 0)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void DelContact(contact** con)
{
char name[NAME_MAX];
printf("请输入要删除的用户姓名:\n");
scanf("%s", name);
contact* pos = FindByName(*con, name);
if (pos == NULL)
{
printf("要删除的用户不存在,删除失败!\n");
return;
}
SLTErase(con, pos);
printf("删除成功!\n");
}
void ShowContact(contact* con)
{
printf("%-10s %-4s %-4s %15s %-20s\n", "姓名", "性别", "年龄", "联系电话", "地址");
contact * cur = con;
while (cur)
{
printf("%-10s %-4s %-4d %15s %-20s\n",
cur->data.name,
cur->data.sex,
cur->data.age,
cur->data.tel,
cur->data.addr);
cur = cur->next;
}
}
void FindContact(contact* con)
{
char name[NAME_MAX];
printf("请输入要查找的用户姓名:\n");
scanf("%s", name);
contact* pos = FindByName(con, name);
if (pos == NULL)
{
printf("要查找的用户不存在,查找失败!\n");
return;
}
printf("查找成功!\n");
printf("%-10s %-4s %-4d %15s %-20s\n",
pos->data.name,
pos->data.sex,
pos->data.age,
pos->data.tel,
pos->data.addr);
}
void ModifyContact(contact** con)
{
char name[NAME_MAX];
printf("请输入要修改的用户名称:\n");
scanf("%s", &name);
contact* pos = FindByName(*con, name);
if (pos == NULL)
{
printf("要查找的用户不存在,修改失败!\n");
return;
}
printf("请输入要修改的姓名:\n");
scanf("%s", pos->data.name);
printf("请输入要修改的性别:\n");
scanf("%s", pos->data.sex);
printf("请输入要修改的年龄:\n");
scanf("%d", &pos->data.age);
printf("请输入要修改的联系电话:\n");
scanf("%s", pos->data.tel);
printf("请输入要修改的地址:\n");
scanf("%s", pos->data.addr);
printf("修改成功!\n");
}
void SaveContact(contact* con)
{
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL)
{
perror("fopen error!\n");
return;
}
//将通讯录数据写入文件
contact* cur = con;
while (cur)
{
fwrite(&(cur->data), sizeof(cur->data), 1, pf);
cur = cur->next;
}
printf("通讯录数据保存成功!\n");
}
void DestroyContact(contact** con)
{
SaveContact(*con);//在通讯录销毁之前,先把历史数据保存到本地文件中contact.txt
SListDesTroy(con);
}
拓展
返回倒数第 k 个节点
题目链接:返回倒数第 k 个节点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
//方法一:暴力解法
/*
int kthToLast(struct ListNode* head, int k)
{
int ListSize = 0;
struct ListNode* pcur = head;
while(pcur->next != NULL)
{
ListSize++;
pcur = pcur->next;
}
pcur = head;
for(int i = 0; i <= ListSize - k; i++)
{
pcur = pcur->next;
}
return pcur->val;
}
*/
//方法二:快慢指针
//满足要求:时间复杂度O(1);只能遍历一次
int kthToLast(struct ListNode* head, int k)
{
struct ListNode* fast = head;
struct ListNode* slow = head;
//快指针先走k步
while(k--)
{
fast = fast->next;
}
//同时走
while(fast)
{
fast = fast->next;
slow = slow->next;
}
return slow->val;
}
链表的回文结构
题目链接:链表的回文结构
/*
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* pcur = head;
struct ListNode* newhead = NULL;
while(pcur)
{
struct ListNode* next = pcur->next;
//头插
pcur->next = newhead;
newhead = pcur;
pcur = next;
}
return newhead;
}
bool chkPalindrome(ListNode* A)
{
struct ListNode* mid = middleNode(A);
struct ListNode* rmid = reverseList(mid);
while(rmid && A)
{
if(rmid->val != A->val)
{
return false;
}
rmid = rmid->next;
A = A->next;
}
return true;
}
};
相交链表
题目链接:相交链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
//方法一:暴力解法
//时间复杂度:O(n^2)
/*
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
struct ListNode* pcura = headA;
while(pcura)
{
struct ListNode* pcurb = headB;
while(pcurb)
{
if(pcura->val == pcurb->val)
{
if(pcura == pcurb)
{
return pcura;
}
}
pcurb = pcurb->next;
}
pcura = pcura->next;
}
return NULL;
}
*/
//方法二:双指针
//时间复杂度:O(m + n)
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
struct ListNode* pcura = headA;
int counta = 0;
while(pcura)//计算链表A的长度
{
counta++;
pcura = pcura->next;
}
struct ListNode* pcurb = headB;
int countb = 0;
while(pcurb)//计算链表B的长度
{
countb++;
pcurb = pcurb->next;
}
if(pcura != pcurb)//尾节点不相交代表不会相交
{
return NULL;
}
//pcura = headA;//重新指向头节点
//pcurb = headB;
//长的先走差距
//假设法
int gap = abs(counta - countb);
struct ListNode* longList = headA;
struct ListNode* shortList = headB;
if(counta < countb)
{
longList = headB;
shortList = headA;
}
while(gap--)
{
longList = longList->next;
}
while(longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return longList;
}
环形链表
题目链接:环形链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head)
{
struct ListNode* slow = head;
struct ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(fast == slow)
{
return true;
}
}
return false;
}
Q1:为什么一定会相遇,会不会错过,永远追不上?
A1:追及问题,两者之间的距离为n, n - 1, n - 2, …… , 3, 2, 1, 0。
Q2:slow一次走一步,fast一次走3步、4步、……、n步可以吗?
A2:假设N为slow进环时与fast的差距,C为带环部分长度
假设每次走3步
N为偶数 | N为奇数 |
---|---|
N | N |
N - 2 | N - 2 |
N - 4 | N - 4 |
…… | …… |
4 | 3 |
2 | 1 |
0 | -1 |
- N是偶数,第一轮就追上了。
- N是奇数:
- 如果C - 1是偶数,下一轮就追上了。
- 如果C - 1是奇数,那么永远追不上。
真的是这样吗
假设slow进环时,fast跟slow距离是N
slow走距离是:L
fast走的距离:L+x*C+C-N
slow进环时,假设fast已经在环里面转了x圈
fast走的距离是slow的3倍
3
∗
L
=
L
+
x
∗
C
+
C
−
N
3 * L = L + x * C + C - N
3∗L=L+x∗C+C−N
2
∗
L
=
(
x
+
1
)
∗
C
−
N
2 * L = (x + 1) * C - N
2∗L=(x+1)∗C−N
偶数=(x+1)*偶数-奇数
N是奇数时,C也是奇数
N是偶数时,C也是偶数
同时存在N是奇数且C是偶数,那么就永远追不上
偶数=(x+1)*偶数-奇数
只有奇数-奇数才能等于偶数
(x+1)*偶数一定是偶数
所以反正出N是奇数且C的偶数不能同时存在,永远追不上的条件不成立
结论:一定能追上
N偶数第一轮就追上了
N是奇数第一轮追不上,C-1是偶数第二轮就追上
环形链表 II
题目链接:环形链表 II
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(fast == slow)
{
struct ListNode* meet = slow;
struct ListNode* phead = head;
while(meet != phead)
{
meet = meet->next;
phead = phead->next;
}
return meet;
}
}
return NULL;
}
Q:为什么后面meet和phead走相同的距离
A:设从头到相交节点的距离为L,slow进环后走的距离为N(N不可能大于环的长度,自行论证),fast在环内转了x圈(N >= 1,自行论证),环的长度为C
slow和fast相遇时:
slow走的距离:
L
+
N
L + N
L+N
fast走的距离:
L
+
x
∗
C
+
N
L + x * C + N
L+x∗C+N
fast走的距离是slow走的距离的2倍:
2
∗
(
L
+
N
)
=
L
+
x
∗
C
+
N
2 * ( L + N ) = L + x * C + N
2∗(L+N)=L+x∗C+N
得:
L
=
x
∗
C
−
N
L = x * C - N
L=x∗C−N
化简得:
L
=
(
x
−
1
)
∗
C
+
C
−
N
L = (x - 1) * C + C - N
L=(x−1)∗C+C−N
随机链表的复制
题目链接:随机链表的复制
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
struct Node* copyRandomList(struct Node* head)
{
struct Node* pcur = head;
//拷贝节点插入到原节点的后面
while(pcur)
{
struct Node* newnode = (struct Node*)malloc(sizeof(struct Node));
newnode->val = pcur->val;
newnode->next = pcur->next;
pcur->next = newnode;
pcur = newnode->next;
}
//拷贝random
pcur = head;
while(pcur)
{
struct Node* newnode = pcur->next;
if(pcur->random == NULL)
{
newnode->random = NULL;
}
else
{
newnode->random = pcur->random->next;
}
pcur = newnode->next;
}
//取下拷贝的链表
struct Node* newHead = NULL;
struct Node* newTail = NULL;
pcur = head;
while(pcur)
{
struct Node* newnode = pcur->next;
struct Node* next = newnode->next;
if(newTail == NULL)
{
newHead = newTail = newnode;
}
else
{
newTail->next = newnode;
newTail = newTail->next;
}
pcur->next = next;
pcur = next;
}
return newHead;
}