目录
链表
1.1 单链表相关经典算法OJ题1:移除链表元素
方法一:设置新链表
设置一个新链表,遍历原链表,将不是val的节点复制到新链表中;
//移除链表中的元素
//方法一:设置一个新链表,遍历原链表
// 将不是val的节点复制到新链表中;
//题目给定的链表
struct ListNode {
int val;
struct ListNode* next;
};
typedef struct ListNode LN;
//题目给定移除链表实现函数。完善函数
struct ListNode* removeElements(struct ListNode* head, int val) {
//先判断head是否为空链表
if (head == NULL)
return head;
//设置一个空的头节点,即哨兵位
//避免分类第一个节点是否是NULL;
LN* newhead = (LN*)malloc(sizeof(LN));
LN* newtail = newhead;
LN* pcur = head;
//遍历原链表
while (pcur != NULL)
{
if (pcur->val != val)
{
newtail->next = pcur;
newtail = newtail->next;
}
pcur = pcur->next;
}
//循环结束,将newtail的下一个节点置为NULL
newtail->next = NULL;
//保存答案,释放哨兵位
LN* ans = newhead->next;
free(newhead);
newhead = NULL;
return ans;
}
方法二:释放节点
将是val的节点释放;
//方法二:将是val的节点释放
struct ListNode {
int val;
struct ListNode* next;
};
typedef struct ListNode LN;
struct ListNode* removeElements(struct ListNode* head, int val) {
if (head == NULL)
return head;
//释放val的链表,要知道val前后的节点的地址
while(head->val == val)
head = head->next;
//此时,第一个节点数据不是val
LN* pcur = head;
LN* pos = head;
while (pos != NULL)
{
if (pos->val == val)
{
pcur->next = pos->next;
//pcur不走
//释放pos空间
free(pos);
//pos走
pos = pcur->next;
continue;
}
pcur = pos;
pos = pos->next;
}
return head;
}
1.2 单链表相关经典算法OJ题2:反转链表

方法一:将链表中的->修改成<-
//反转链表
//方法一:将->修改成<-
struct ListNode {
int val;
struct ListNode* next;
};
typedef struct ListNode LN;
struct ListNode* reverseList(struct ListNode* head) {
if (head == NULL)
return head;
//设置前后指针来改变箭头的指向
LN* n1 = NULL;
LN* n2 = head;
LN* n3 = head->next;
while (n3 != NULL)
{
//n1 n2 n3
n2->next = n1;
n1 = n2;
n2 = n3;
n3 = n3->next;
}
//注意:最后n2此时next是NULL;要将其手动改成n1
n2->next = n1;
return n2;
}
方法二:创建一个新的链表,头插
创建新链表,将原链表的元素头插到新链表中。虽然叫做创建新链表,但是实际上还是在原链表上进行操作;
//方法二:创建新链表,进行头插
struct ListNode {
int val;
struct ListNode* next;
};
typedef struct ListNode LN;
struct ListNode* reverseList(struct ListNode* head) {
if (head == NULL)
return head;
//创建新链表
LN* newhead = NULL;
LN* pcur = head;
while (pcur)
{
//头插
LN* pnext = pcur->next;
pcur->next = newhead;
newhead = pcur;
pcur = pnext;
}
return newhead;
}
方法三:创建节点,头插
与方法二类似,但是我们创作新节点来保存每个节点的数据,在将其进行头插,此方法不会该表原链表。
//方法三:创建节点,进行头插,不改变原链表
struct ListNode {
int val;
struct ListNode* next;
};
typedef struct ListNode LN;
struct ListNode* reverseList(struct ListNode* head) {
if (head == NULL)
return NULL;
LN* newhead = NULL;
LN* pcur = head;
while (pcur)
{
//创建新节点
LN* newnode = (LN*)malloc(sizeof(LN));
newnode->val = pcur->val;
//进行头插
newnode->next = newhead;
newhead = newnode;
pcur = pcur->next;
}
return newhead;
}
1.3 单链表相关经典算法OJ题3:合并两个有序链表
创建新链表
创建新链表,比较两个来链表,将较小的插入到新链表中。
//方法一:创建新链表,将两个链表中较小的插入到新链表中
struct ListNode {
int val;
struct ListNode *next;
};
typedef struct ListNode LN;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
if (list1 == NULL)
return list2;
if (list2 == NULL)
return list1;
//创建哨兵位
LN* newhead = (LN*)malloc(sizeof(LN));
LN* newtail = newhead;
//创建两个指针遍历原链表
LN* p1 = list1;
LN* p2 = list2;
while (p1 && p2)
{
//p1的值小于p2的值
//将p1插入到newhead中
if ((p1->val) > (p2->val))
{
newtail->next = p1;
//newtail向后走,p1也向后走
newtail = newtail->next;
p1 = p1->next;
}
else
{
newtail->next = p2;
newtail = newtail->next;
p2 = p2->next;
}
}
//此时一个链表已经遍历完了
//将另外一个直接尾插
if (p1)
newtail->next = p1;
if (p2)
newtail->next = p2;
LN* ans = newhead->next;
//将哨兵位释放
free(newhead);
newhead = NULL;
return ans;
}
1.4 单链表相关经典算法OJ题4:链表的中间结点

方法一:快慢指针
设置两个指针,遍历原链表,一个指针每次走一步,另一个指针每次走两步;
//链表的中间节点
//方法一:运用快慢指针
struct ListNode {
int val;
struct ListNode *next;
};
typedef struct ListNode LN;
struct ListNode* middleNode(struct ListNode* head) {
if (head == NULL)
return head;
//设置两个指针
LN* fast = head;
LN* low = head;
//偶数个时,fast是NULL的时候结束
//奇数个时,fast->next是NULL的时候结束
while (fast != NULL && fast->next != NULL)
{
low = low->next;
fast = fast->next->next;
}
return low;
}
方法二:计数器
先计算一共有多少个节点,再找中间节点。
//方法二:先计算有多少个中间节点,再找中间节点
struct ListNode {
int val;
struct ListNode *next;
};
typedef struct ListNode LN;
struct ListNode* middleNode(struct ListNode* head) {
if (head == NULL)
return head;
LN* pcur = head;
int count = 0;
//计算节点个数
while (pcur)
{
count++;
pcur = pcur->next;
}
//不论是奇数个还是偶数个,中间节点都是第(n/2+1)个节点
pcur = head;
for (int i = 1; i < count / 2 + 1; i++)
pcur = pcur->next;
return pcur;
}
1.5 循环链表经典应⽤-环形链表的约瑟夫问题

方法一:环形链表
将尾节点指向头节点,在进行计数,是m的被释放;直到只有一个节点为止。
//环形链表的约瑟夫问题
typedef struct Slistnode
{
int date;
struct Slistnode* next;
}SL;
int ysf(int n, int m) {
//先创建第一个节点
SL* head = (SL*)malloc(sizeof(SL));
SL* tail = head;
tail->next = NULL;
//循环创建
for (int i = 2; i <= n; i++)
{
SL* newnode = (SL*)malloc(sizeof(SL));
newnode->date = i;
newnode->next = NULL;
tail->next = newnode;
tail = tail->next;
}
//形成环形链表
tail->next = head;
int count = 1;
SL* pcur = head;
SL* p1= head;
//只剩下一个节点的时候,停止循环,此时其下一个节点的指针也指向其本身
while (pcur->next != pcur)
{
//当是m的时候,将pcur释放,并且将pl->pcur->next
if (count == m)
{
SL* pnext = pcur->next;
p1->next = pnext;
free(pcur);
pcur = pnext;
count = 1;
}
else
{
p1 = pcur;
pcur = pcur->next;
count++;
}
}
//保留答案,释放最后一个节点
int ans = pcur->date;
free(pcur);
pcur = NULL;
return ans;
}
方法二:运用数组
创建一个数组,将数组中所有的值初始化为1,将被杀的赋值为0;此方法,需要多次循化,比较浪费时间。
int ysf(int n, int m) {
int arr[n] ;
int kill = 0; //用kill来统计被杀掉的人数
int count = 1; //用count来报数
int people = 0; //用people来表示下标
for(int i=0;i<n;i++)
arr[i]=0;
//通过count的计数,来结束循环
while (kill < n - 1) {
if (arr[people]!=1&&count == m) {
arr[people] = 1;
kill++;
count = 1;
}
if (arr[people] != 1) {
count++;
}
people++;
people %= n;
}
//找到数组中是0的元素,并反复;
for (int i = 0; i < n; i++) {
if (arr[i] == 0)
return i + 1;
}
return 0;
}
1.6 单链表相关经典算法OJ题5:分割链表

方法一:创建两个链表
将大于x的节点和小于x的节点,分开存放,最后再将他们合并。
//分割链表
//方法一:创建两个链表,将节点分开存放
struct ListNode {
int val;
struct ListNode* next;
};
typedef struct ListNode LN;
struct ListNode* partition(struct ListNode* head, int x) {
if (head == NULL)
return head;
//创建大小链表
LN* big = (LN*)malloc(sizeof(LN));
LN* bigtail = big;
bigtail->next = NULL;
LN* small = (LN*)malloc(sizeof(LN));
LN* smalltail = small;
smalltail->next = NULL;
LN* pcur = head;
//遍历原链表,将节点分配到新链表中
while (pcur)
{
//val>x,插入到big中
if ((pcur->val) >= x)
{
bigtail->next = pcur;
bigtail = bigtail->next;
}
else
{
smalltail->next = pcur;
smalltail = smalltail->next;
}
pcur = pcur->next;
}
//注意:此处不要忘记把bigtail的下一个节点置为NULL
bigtail->next = NULL;
//将大链表插入到小链表后面,将哨兵位释放掉
smalltail->next = big->next;
free(big);
big = NULL;
LN* ans = small->next;
free(small);
small = NULL;
return ans;
}
方法二:将大于x的尾插
在原链表上进行修改,将大于等于x的节点尾插,并将原来位置的节点释放。
//方法二:将大于等于x的尾插
struct ListNode {
int val;
struct ListNode* next;
};
typedef struct ListNode LN;
struct ListNode* partition(struct ListNode* head, int x) {
if (head == NULL)
return head;
//先找到尾节点
LN* ptail = head;
while (ptail->next != NULL)
ptail = ptail->next;
LN* node = ptail;
//创建哨兵位,避免对第一个节点分类讨论
LN* newhead = (LN*)malloc(sizeof(LN));
newhead->next = head;
//定义两个指针遍历原链表
LN* p1 = newhead;
LN* p2 = head;
while (p2!=node)
{
//p2大于等于x
if ((p2->val) >= x)
{
p1->next = p2->next;
ptail->next = p2;
ptail = ptail->next;
}
else
{
p1 = p2;
}
p2 = p2->next;
}
ptail->next = NULL;
LN* ans = newhead->next;
free(newhead);
newhead = NULL;
return ans;
}
方法三:创建新链表,运用尾插和头插
创建新链表将大于等于x的尾插,将小于x的头插。
//方法三:创建新链表,将大于等于x的尾插,将小于x的头插
struct ListNode {
int val;
struct ListNode* next;
};
typedef struct ListNode LN;
struct ListNode* partition(struct ListNode* head, int x) {
if (head == NULL)
return head;
//创建头节点
LN* newhead = (LN*)malloc(sizeof(LN));
newhead->val = head->val;
LN* newtail = newhead;
newtail->next = NULL;
LN* pcur = head->next;
while (pcur)
{
//创建新节点
LN* newnode = (LN*)malloc(sizeof(LN));
newnode->val = pcur->val;
newnode->next = NULL;
//进行尾插
if ((pcur->val) >= x)
{
newtail->next = newnode;
newtail = newtail->next;
}
else//头插
{
newnode->next = newhead;
newhead = newnode;
}
pcur = pcur->next;
}
return newhead;
}
2.1 链表的倒数第k节点
题:返回链表倒数第k节点的值。
快慢指针
解析:运用快慢指针,先让快指针走k步,再让快指针和慢指针(从头开始走)一起走,当快指针走到尾的时候,慢指针也刚好走到倒数第k节点处。
//定义链表
typedef struct ListNode
{
int val;
struct ListNode* next;
}LN;
//返回链表的倒数第k个节点
LN* ReturnK(LN* list,int k)
{
if (list == NULL)
return NULL;
LN* fast = list;
LN* slow = list;
while (k--)
{
fast = fast->next;
}
while (fast)
{
fast = fast->next;
slow = slow->next;
}
return slow->val;
}
2.2 链表的回文结构
找中间节点+反转链表
解析:先找到中间节点,再将中间节点后面的节点反转;用两个指针前和后分别遍历链表进行对比。
//找中间节点
LN* FindMid(LN* list)
{
LN* fast = list;
LN* slow = list;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
//反转链表
LN* reverseList(LN* list)
{
LN* last = NULL;
LN* cur = list;
LN* pre = list->next;
while (cur)
{
cur->next = last;
last = cur;
cur = pre;
if (pre)
pre = pre->next;
}
return last;
}
//判断链表的回文结构
bool chkPalindrome(LN* list)
{
if (list == NULL)
return true;
//先找到中间节点
LN* mid = FindMid(list);
//将中间节点后面的节点反转,返回尾节点
LN* tail=reverseList(mid);
LN* head = list;
//将头尾进行比较
while (head!=mid)
{
if (head->val != tail->val)
return false;
head = head->next;
tail = tail->next;
}
return true;
}
2.3 相交链表
快慢指针
解析:首先判断是否是相交链表(直接判断最后一个节点相同还是不相同就行),然后分别遍历两个链表,记录两个链表的长度;让长的链表先走多的步数(相对于短的链表),再让长短链表一起走,第一次遇见的位置就是相交节点种的第一个节点。
//相交链表,返回相交链表的第一个节点
LN* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)
{
//判断是否相交,记录链表长度
if (headA == NULL || headB == NULL)
return NULL;
LN* tailA = headA;
LN* tailB = headB;
int Alenth = 0;
int Blenth = 0;
while (tailA->next)
{
Alenth++;
tailA = tailA->next;
}
while (tailB->next)
{
Blenth++;
tailB = tailB->next;
}
if (tailA != tailB)
return NULL;
//计算多出的长度,并记录长链表和短链表
int num = abs(Alenth - Blenth);
LN* more = headA;
LN* less = headB;
if (Blenth > Alenth)
{
more = headB;
less = headA;
}
//长链表先走
while (num--)
more = more->next;
while (more != less)
{
more = more->next;
less = less->next;
}
return less;
}
2.4 环形链表
快慢指针
快慢指针,让快指针先进入圈内,慢指针进入圈后,快指针开始追慢指针。注意:此处快指针一次只能走两步,慢指针一次走一步,如果快指针超过两步可能会导致一直追不到。
//环形链表
bool hasCycle(struct ListNode* head)
{
if (head == NULL)
return false;
LN* fast = head;
LN* slow = head;
while (fast&&fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
return true;
}
return false;
}
2.4.2 环形链表2
基于环形链表1,此处我们需要返回环形链表的入环位置。
分析
根据上方图可以看到:当快慢指针第一次相遇的位置距离到入环点还有C-X而入环点距离起始位置L恰好也是C-X+nC。此时再让一个指针从起始位置开始走,同时slow继续走,他们两个第一次相遇的位置就是入环点,在此期间slow可能会绕环走多圈(n*C)。
快慢指针+分析
//环形链表2,返回入环节点
struct ListNode* detectCycle(struct ListNode* head)
{
if (head == NULL)
return NULL;
//先找第一次相交位置
LN* fast = head;
LN* slow = head;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
break;
}
if (fast == NULL || fast->next == NULL)
return NULL;
else
{
//再设置指针从起始位置开始走
LN* cur = head;
while (cur != slow)
{
cur = cur->next;
slow = slow->next;
}
return cur;
}
}
2.5 随机链表的复制
方法一:相对尾部的长度
先设置出链表的整体部分(val值和next指针),再通过原链表random指针指向的位置距离NULL的长度来找到节点的位置。
typedef struct Node
{
int val;
struct Node* next;
struct Node* random;
}Node;
//链表的深度复制
struct Node* copyRandomList(struct Node* head)
{
if (head == NULL)
return NULL;
//先复制val和next
Node* copyhead = (Node*)malloc(sizeof(Node));
Node* copytail = copyhead;
copyhead->val = head->val;
copyhead->next = NULL;
copyhead->random = NULL;
int num = 1; //记录链表长度
Node* cur = head->next;
while (cur)
{
num++;
Node* newnode = (Node*)malloc(sizeof(Node));
newnode->val = cur->val;
newnode->next = NULL;
copytail->next = newnode;
copytail = copytail->next;
cur = cur->next;
}
//再找random值
cur = head;
Node* copycur = copyhead;
while(cur)
{
int a = num; //用a来记录相对于尾部NULL的长度
Node* rand = cur->random;
while (rand)
{
a--;
rand = rand->next;
}
//让新链表去找
Node* copyrand = copyhead;
while (a--)
{
copyrand = copyrand->next;
}
copycur->random = copyrand;
cur = cur->next;
copycur = copycur->next;
}
return copyhead;
}
方法二
分析:方法一需要多次遍历链表,时间复杂度很高。此处我们进行优化。
如图:直接在原链表的基础上,给每一个节点的后面增加一个相同的节点;当我们找到原链表一个节点的random时,其next就是新建的节点。eg:原13的random是7,而7的next就是设置的新节点。
//链表的深度复制
struct Node* copyRandomList(struct Node* head)
{
if (head == NULL)
return NULL;
//对原链表进行增加
Node* cur = head;
while (cur)
{
Node* newnode = (Node*)malloc(sizeof(Node));
newnode->val = cur->val;
newnode->next = cur->next;
newnode->random = NULL;
cur->next = newnode;
cur = cur->next->next;
}
//对复制链表中的random进行修改
cur = head;
while (cur)
{
Node* rand = cur->random;
if (rand == NULL)
{
cur->next->random = NULL;
}
else
{
cur->next->random = rand->next;
}
cur = cur->next->next;
}
//将每一个新增的节点取下来
cur = head->next;
Node* copyhead = cur;
Node* copytail = copyhead;
while (cur&&cur->next)
{
cur = cur->next->next;
copytail->next = cur;
copytail = copytail->next;
}
copytail->next = NULL;
return copyhead;
}