数据结构:链表和经典链表OJ题合集(纯享版)

 一、单向链表

1、链表的概念及结构

概念:链表是一种物理结构上非连续、非顺序的存储结构,数据结构的逻辑顺序是通过链表中的指针链接次序实现的。

就像图中的小火车,每节车厢都是一个节点,每个节点都存储着一个数据。它们本身并不是顺序存储的,而是由詹式车钩相互连起来的。而在链表中这个詹式车钩就是指针链接

火车在淡季时会减去几个车厢,而在旺季时会加上几个车厢。火车是可以灵活改变的。链表也是同理。

每个节点是在不同的空间存放,可能乱七八糟,但是有了指针链接将他们连接起来,从而在逻辑结构上好像是连续的。

在链表里,每节 "车厢" 是什么样子的呢?

与顺序表不同的是,每节 "车厢" 都是独立申请下来的,我们称之为 "节点/结点"。

节点的组成主要有两部分:当前节点要保存的数据和保存的下一个节点的地址(指针变量)。

图中指针变量plist保存的是第一个节点的地址,我们称plist此时 "指向" 第一个节点。如果我们想让plist指向第二个节点就让plist保存第二个节点的地址:0x0012FFA0。

为什么还需要指针变量来保存下一个节点的地址?

链表的每一个节点都是独立申请的(即需要插入一个数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点的位置才能从当前节点找到下一个节点。

2、单向链表的实现

结合前面学到的结构体知识,我们可以通过结构体来创建每个节点对应的结构体代码:

typedef int SLTypeData;
//创建每个节点类型
typedef struct SList
{
	SLTypeData data;  //存储数据
	struct SList* next;  //下一个节点的地址
}*pList;

这就是链表每个节点的类型创建,关于单向链表的接口都有以下这些。

void SLPushBack(pList* phead, SLTypeData val);//链表节点尾部插入
pList SLBuyNode(int val);//申请节点空间
void SLPrint(pList* phead);//打印链表每个节点的数据
void SLPushFront(pList* phead, SLTypeData val);//链表节点头部插入
void SLPopBack(pList* phead);//链表节点尾部删除
void SLPopFront(pList* phead);//链表节点头部删除
void SLInsert(pList* phead, pList pos, SLTypeData val);//指定位置之前插入
pList SLfind(pList* phead, SLTypeData val);//查找该存储数据的节点并返回
void SLInsertAfter(pList pos, SLTypeData val);//指定位置之后插入
void SLEarse(pList* phead, pList pos);//指定位置节点删除
void SLDesTroy(pList* phead);//链表销毁

完整代码:分别在三个文件,test.c文件调用函数、pList.c实现函数、pList.h函数声明。

pList.h 声明:

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int SLTypeData;

//创建每个节点类型
typedef struct SList
{
	SLTypeData data;  //存储数据
	struct SList* next;  //下一个节点的地址
}*pList;

void SLPushBack(pList* phead, SLTypeData val);//链表节点尾部插入
pList SLBuyNode(int val);//申请节点空间
void SLPrint(pList* phead);//打印链表每个节点的数据
void SLPushFront(pList* phead, SLTypeData val);//链表节点头部插入
void SLPopBack(pList* phead);//链表节点尾部删除
void SLPopFront(pList* phead);//链表节点头部删除
void SLInsert(pList* phead, pList pos, SLTypeData val);//指定位置之前插入
pList SLfind(pList* phead, SLTypeData val);//查找该存储数据的节点并返回
void SLInsertAfter(pList pos, SLTypeData val);//指定位置之后插入
void SLEarse(pList* phead, pList pos);//指定位置节点删除
void SLDesTroy(pList* phead);//链表销毁

pList.c 函数实现:

#include "pList.h"

pList SLBuyNode(SLTypeData val)
{
	pList ret = (pList)malloc(sizeof(struct SList));
	if (ret == NULL)
	{
		perror("malloc");
		return NULL;
	}
	ret->data = val;
	ret->next = NULL;
	return ret;
}

void SLPushBack(pList* phead, SLTypeData val)
{
	assert(phead != NULL);
	pList node = SLBuyNode(val);
	if (node == NULL)
	{
		perror("SLBuyNode");
		return;
	}
	if (*phead == NULL)
	{
		*phead = node;
		return;
	}
	pList pcur = *phead;
	//找到尾节点
	while (pcur->next)
	{
		pcur = pcur->next;
	}
	//给尾结点下一个节点的地址
	pcur->next = node;
}

void SLPrint(pList* phead)
{
	assert(phead);
	assert(*phead);
	pList pcur = *phead;
	while (pcur)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

void SLPushFront(pList* phead, SLTypeData val)
{
	assert(phead != NULL);
	pList node = SLBuyNode(val);
	if (node == NULL)
	{
		perror("SLBuyNode");
		return;
	}
	//头部插入操作
	node->next = *phead;
	*phead = node;
}

void SLPopBack(pList* phead)
{
	assert(*phead != NULL);
	//首先判断一下链表是否只有一个节点的情况,如果是则直接释放头结点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
		return;
	}
	pList pcur = *phead;
	while (pcur->next->next)
	{
		pcur = pcur->next;
	}
	//完成尾部删除
	free(pcur->next);
	pcur->next = NULL;
}

void SLPopFront(pList* phead)
{
	assert(phead != NULL);
	pList pcur = *phead;
	*phead = (*phead)->next;
	free(pcur);
	pcur = NULL;
}

void SLInsert(pList* phead, pList pos, SLTypeData val)
{
	assert(phead);
	assert(*phead);
	assert(pos);
	pList node = SLBuyNode(val);
	//判断指定位置是否等于头结点
	if (pos == *phead)
	{
		node->next = *phead;
		*phead = node;
		return;
	}
	pList prve = *phead;
	//找到链表中pos位置之前的节点
	while (prve->next != pos)
	{
		prve = prve->next;
	}
	//插入操作
	prve->next = node;
	node->next = pos;
}

pList SLfind(pList* phead, SLTypeData val)
{
	assert(phead);
	pList pcur = *phead;
	while (pcur)
	{
		if (pcur->data == val)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;//如果遍历了一遍链表未找到存储该数据的节点则返回NULL 
}

void SLInsertAfter(pList pos, SLTypeData val)
{
	assert(pos);
	pList node = SLBuyNode(val);
	node->next = pos->next;
	pos->next = node;
}

void SLEarse(pList* phead, pList pos)
{
	assert(phead);
	assert(*phead);
	assert(pos);
	if (pos == *phead)
	{
		*phead = (*phead)->next;
		free(pos);
		return;
	}
	pList prev = *phead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

void SLDesTroy(pList* phead)
{
	assert(phead);
	assert(*phead);
	pList pcur = *phead;
	while (pcur)
	{
		pList next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*phead = NULL;
	printf("销毁成功\n");
}

test.c 函数调用:

#include "pList.h"

void SLttext()
{
	pList plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLPrint(&plist); //打印:1 -> 2 -> 3 -> 4 -> NULL

	SLPushFront(&plist, 5);
	SLPushFront(&plist, 6);
	SLPushFront(&plist, 7);
	SLPrint(&plist);//打印:7 -> 6 -> 5 -> 1 -> 2 -> 3 -> 4 -> NULL

	SLPopBack(&plist);
	SLPrint(&plist);//打印:7 -> 6 -> 5 -> 1 -> 2 -> 3 -> NULL

	SLPopFront(&plist);
	SLPrint(&plist);//打印:6 -> 5 -> 1 -> 2 -> 3 -> NULL

	pList find = SLfind(&plist, 3);
	SLInsert(&plist, find, 11);
	SLPrint(&plist);//打印:6 -> 5 -> 1 -> 2 -> 11 -> 3 -> NULL

    find = SLfind(&plist, 1);
	SLInsertAfter(find, 12);
	SLPrint(&plist);//打印:6 -> 5 -> 1 -> 12 -> 2 -> 11 -> 3 -> NULL
	
	find = SLfind(&plist, 5);
	SLEarse(&plist, find);
	SLPrint(&plist);//打印:6 -> 1 -> 12 -> 2 -> 11 -> 3 -> NULL

	SLDesTroy(&plist);
}
int main()
{
	SLttext();
	return 0;
}

3、链表的分类

链表的结构非常多样,以下结构组合起来就有8种(2 x 2 x 2)链表结构。

链表结构:

1. 单向或双向

2. 带头或不带头

3. 循环或不循环

虽然有这么多链表的结构,但是我们实际中最常用的还是两种结构:单链表和双向带头循环链表

1. 不带头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用链表数据结构,都是带头双向循环链表。另外这个结构虽然复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现就知道了。

二、双向链表

1、双向链表的结构

注意:这里的 "带头" 跟前面我们说的 "头结点" 是两个概念,实际前面的在单链表阶段称呼不严谨,但是为了同学们更好的理解就直接称为单链表的头结点。

带头链表里的头结点,实际为 "哨兵位" ,哨兵位节点不存储任何有效元素,只是站在这里 "放哨的"。

"哨兵位" 存在的意义:

遍历循环链表避免死循环。

注意:双向链表中,哨兵位的下一个节点是链表的第一个节点(头结点)。哨兵位的前一个节点是链表的最后一个节点(尾结点)。所以双向链表的头插是在哨兵位的后面插入数据。尾插则是在哨兵位之前插入数据。

哨兵位是作为头结点和尾结点的中点,是头结点的起点也是尾节点的终点。这样解释更容易理解。

2、 双向链表的实现

List.h 链表函数链表节点类型的声明:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLDataType;
typedef struct ListNode
{
	SLDataType data;//存储数据
	struct ListNode* prve;//存储前一个节点的地址
	struct ListNode* next;//存储下一个节点的地址
}SList;
SList* SLInit();

void SLPushBack(SList* phead, SLDataType x);//尾插
void SLPrint(SList* phead);//显示链表数据
void SLPustFront(SList* phead, SLDataType x);//头插
void SLPopBack(SList* phead);//尾删
void SLPopFront(SList* phead);//头删
void SLInsert(SList* pos, SLDataType x);//指定位置插入
SList* SLfind(SList* phead, SLDataType x);//查找节点
void SLEarse(SList* pos);//指定位置删除
void SLDestory(SList** pphead);//链表销毁

List.c 链表函数的实现:

#include "List.h"
//链表初始化
SList* SLInit()
{
	SList* phead = (SList*)malloc(sizeof(SList));
	if (phead == NULL)
	{
		perror("malloc error");
		return NULL;
	}
	phead->data = -1;
	//因为是循环链表,所以初始化要遵循循环格式
	phead->next = phead;
	phead->prve = phead;
	return phead;
}
//创建链表节点
SList* ListBuyNode(SLDataType x)
{
	SList* retNode = (SList*)malloc(sizeof(SList));
	if (retNode == NULL)
	{
		perror("malloc error");
		return NULL;
	}
	retNode->data = x;
	retNode->prve = retNode;
	retNode->next = retNode;
	return retNode;
}
//链表尾插
void SLPushBack(SList* phead, SLDataType x)
{
	assert(phead);
	SList* Node = ListBuyNode(x);
	Node->prve = phead->prve;
	Node->next = phead;
	phead->prve->next = Node;
	phead->prve = Node;
}
//链表数据显示
void SLPrint(SList* phead)
{
	assert(phead);
	SList* pcur = phead->next;
	while (pcur != phead)//哨兵位作为结束标识
	{
		printf("%d", pcur->data);
		if (pcur->next != phead)
			printf("->");
		pcur = pcur->next;
	}
	printf("\n");
}
//链表头插
void SLPustFront(SList* phead, SLDataType x)
{
	assert(phead);
	SList* Node = ListBuyNode(x);
	Node->next = phead->next;
	Node->prve = phead;
	phead->next->prve = Node;
	phead->next = Node;
}
//链表尾删
void SLPopBack(SList* phead)
{
	assert(phead);
	assert(phead->next != phead);
	SList* del = phead->prve;
	del->prve->next = phead;
	phead->prve = del->prve;
	free(del);
	del = NULL;
}
//链表头删
void SLPopFront(SList* phead)
{
	assert(phead);
	assert(phead->next != phead);
	SList* del = phead->next;
	del->next->prve = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}
//指定位置插入
void SLInsert(SList* pos, SLDataType x)
{
	assert(pos);
	SList* Node = ListBuyNode(x);
	Node->next = pos->next;
	Node->prve = pos;
	pos->next = Node;
	Node->next->prve = Node;
}
//查找节点
SList* SLfind(SList* phead, SLDataType x)
{
	assert(phead);
	SList* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//指定位置删除
void SLEarse(SList* pos)
{
	assert(pos);
	pos->prve->next = pos->next;
	pos->next->prve = pos->prve;
	free(pos);
	pos = NULL;
}
//链表销毁
void SLDestory(SList** pphead)
{
	assert(pphead && *pphead);
	SList* pcur = (*pphead)->next;
	while (pcur != *pphead)
	{
		SList* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(*pphead);
	*pphead = NULL;
}

test.c 函数的调用:

#include "List.h"

void SLtest()
{
	SList* plist = NULL;
	plist = SLInit();
	//尾插
	SLPushBack(plist, 1);
	SLPushBack(plist, 2);
	SLPushBack(plist, 3);
	SLPushBack(plist, 4);
	SLPrint(plist);//打印:1->2->3->4
	//头插
	SLPustFront(plist, 5);
	SLPustFront(plist, 6);
	SLPustFront(plist, 7);
	SLPrint(plist);//打印:7->6->5->1->2->3->4
	//尾删
	SLPopBack(plist);
	SLPrint(plist);//打印:7->6->5->1->2->3
	//头删
	SLPopFront(plist);
	SLPrint(plist);//打印:6->5->1->2->3
	//指定位置插入
	SList* find = SLfind(plist, 5);
	SLInsert(find, 11);
	SLPrint(plist);//打印:6->5->11->1->2->3
	//指定位置删除
	find = SLfind(plist, 1);
	SLEarse(find);
	SLPrint(plist);//打印:6->5->11->2->3
	//链表销毁
	SLDestory(&plist);
}
int main()
{
	SLtest();
	return 0;
}

3、顺序表和双向链表的优缺点分析

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低O(N)只需要修改指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁

三、链表经典OJ题

 1、 移除链表元素

题目要求:

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

解题思路:

思路1:

定义两个指针,一个指向当前节点,一个指向当前节点的下一个节点,如果下一个节点是要删除的节点就将当前节点的next存储下一个节点的再下一个节点的地址,然后free那个节点。知道遍历完原链表,该思路是改变原链表。

思路2:

建立一个新的链表,遍历原链表,将原链表中的非删除节点给新的链表,遍历结束后新链表中没有要删除节点。

struct ListNode{
    int val;
    struct ListNode *next;
};
struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* Newhead, * NewTail;//一个头链表,一个链表尾部
    Newhead = NewTail = NULL;
    //遍历原链表
    struct ListNode* pcur = head;
    while (pcur){
        if (pcur->val != val){
            if (Newhead == NULL){
                Newhead = NewTail = pcur;
            }
            else{
                NewTail->next = pcur;
                NewTail = NewTail->next;
            }
        }
        pcur = pcur->next;
    }
    //如果要删除值在原链表为节点,新链表的尾节点就不会指向NULL,所以这里要判断
    if (NewTail){
        NewTail->next = NULL;
    }
    return Newhead;
}

2、反转链表

题目要求:

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

解题思路:

可以定义三个新的节点类型的指针变量n1、n2、n3。然后就可以使用这三个指针来反转链表。首先n1初始化为NULL,n2初始化为head链表头结点,n3则是head头结点的下一个节点。然后循环n2->next = n1, n1 = n2, n2 = n3,n3 = n3->next。循环往复就完成了反转链表的操作。

typedef struct ListNode{
    int val;
    struct ListNode *next;
}*pList;

//反转链表函数实现
pList reverselist(pList head){
    if (head == NULL){
        return NULL;
    }
    pList n1, n2, n3;
    n1 = NULL, n2 = head, n3 = head->next;
    while (n2){
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if(n3)//如果n3为NULL就不再向后访问了
           n3 = n3->next;
    }
    return n1;
}

//以下调用上面函数的main是我自己写的,可以供大家参考
int main()
{
    //创建链表
    struct ListNode* phead, *pTail, *p;
    phead = pTail = NULL;
    int n = 0;
    printf("请输入要创建链表节点个数:>");
    scanf("%d", &n);
    int i = 0;
    for (i = 0; i < n; i++)
    {
        p = (pList)malloc(sizeof(struct ListNode));
        printf("请输入第%d个节点的值:>", i + 1);
        scanf("%d", &(p->val));
        p->next = NULL;
        if (phead == NULL)
        {
            phead = p;
            pTail = phead;
        }
        else
        {
            pTail->next = p;
            pTail = pTail->next;
        }
    }
    //调用反转链表函数
    pList Newhead = reverselist(phead);
    if (Newhead == NULL)
    {
        perror("reverselist");
        return;
    }
    phead = Newhead;
    //打印链表节点数据
    while (Newhead)
    {
        printf("%d->", Newhead->val);
        Newhead = Newhead->next;
    }
    printf("NULL\n");
    i = 1;
    Newhead = phead;
    //销毁链表
    while (Newhead)
    {
        phead = phead->next;
        free(Newhead);
        Newhead = phead;
        printf("已释放第%d条节点\n", i);
        i++;
    }
    return 0;
}

3、合并两个有序链表

题目要求:

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

解题思路:

定义两个新的节点指针,一个头节点,一个尾结点。比较两个原链表当前节点的数据大小,然后插入新的链表,知道插完为止

typedef struct ListNode {
    int val;
    struct ListNode* next;
}*pList;
//合并两个有序链表函数实现
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if (list1 == NULL){
        return list2;
    }
    if (list2 == NULL){
        return list1;
    }
    struct ListNode* cur1, *cur2;
    cur1 = list1, cur2 = list2;
    struct ListNode* phead, * pTail;
    phead = pTail = (struct ListNode*)malloc(sizeof(struct ListNode));
    while (cur1 && cur2) {
        if (cur1->val < cur2->val){
            pTail->next = cur1;
            pTail = pTail->next;
            cur1 = cur1->next;
        }
        else{
            pTail->next = cur2;
            pTail = pTail->next;
            cur2 = cur2->next;
        }
    }
    if (cur1) {
        pTail->next = cur1;
    }
    if (cur2) {
        pTail->next = cur2;
    }
    struct ListNode* retList = phead->next;
    free(phead);
    return retList;
}
//以下创建链表调用函数的代码是自己写的,只为调用上面函数,不是链表规范创建。仅供参考
int main()
{
    struct ListNode* phead1, pTail1, p1;
    struct ListNode* phead2, pTail2, p2;
    phead1 = pTail1 = NULL;
    phead2 = pTail2 = NULL;
    int n = 0;
    printf("请输入要创建p1和p2链表节点个数:>");
    scanf("%d", &n);
    int i = 0;
    for (i = 0; i < n; i++)
    {
        p1 = (pList)malloc(sizeof(struct ListNode));
        p2 = (pList)malloc(sizeof(struct ListNode));
        printf("请输入p1链表第%d个节点的值:>", i + 1);
        scanf("%d", &(p1->val));
        printf("请输入p2链表第%d个节点的值:>", i + 1);
        scanf("%d", &(p2->val));
        p1->next = NULL;
        p2->next = NULL;
        if (phead1 == NULL && phead2 == NULL)
        {
            phead1 = p1;
            pTail1 = phead1;
            phead2 = p2;
            pTail2 = phead2;
        }
        else
        {
            pTail1->next = p1;
            pTail1 = pTail1->next;
            pTail2->next = p2;
            pTail2 = pTail2->next;
        }
    }
    pList Newhead = mergeTwoLists(phead1,phead2);
    if (Newhead == NULL)
    {
        perror("reverselist");
        return;
    }
    pList phead = Newhead;
    while (Newhead)
    {
        printf("%d->", Newhead->val);
        Newhead = Newhead->next;
    }
    printf("NULL\n");
    i = 1;
    Newhead = phead;
    while (Newhead)
    {
        phead = phead->next;
        free(Newhead);
        Newhead = phead;
        printf("已释放第%d条节点\n", i);
        i++;
    }
    return 0;
}

4、链表的中间节点

题目要求:

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 。

示例 2:

输入:head = [1,2,3,4,5,6]
输出:[4,5,6]
解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。

解题思路:

快慢指针:使用快慢指针来遍历链表,慢指针每次走一步,快指针每次走两步,当快指针走到最后慢指针刚好走到中间节点。因为快指针是慢指针的2倍。所以快指针从起点到终点时慢指针就是这个路程的一半,是中点。

typedef struct ListNode{
    int val;
    struct ListNode* next;
}*pList;
//函数实现
struct ListNode* middleNode(struct ListNode* head) {
    if (head == NULL){
        return NULL;
    }
    struct ListNode* slow, *fast;
    slow = fast = head;
    //节点数可能是奇数个也可能是偶数个,所以要两种判断
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

5、环形链表的约瑟夫问题

著名的Josephus问题

据说著名的历史学家 Josephus有过以下的故事:故事据说发生在古罗马时期,在罗马人占领乔塔帕特后,39个犹太人与约瑟夫及他的朋友躲到一个洞中,他们宁愿死也不要被敌人抓到,于是约定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下1个人重新报数,直到所有人都自杀身亡为止。

然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

题目要求:

编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。

下一个人继续从 1 开始报数。

n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?

数据范围: 1≤𝑛,𝑚≤100001≤n,m≤10000

进阶:空间复杂度 𝑂(1)O(1),时间复杂度 𝑂(𝑛)O(n)

示例1

输入:

5,2     

复制返回值:

3    

复制说明:

开始5个人 1,2,3,4,5 ,从1开始报数,1->1,2->2编号为2的人离开
1,3,4,5,从3开始报数,3->1,4->2编号为4的人离开
1,3,5,从5开始报数,5->1,1->2编号为1的人离开
3,5,从3开始报数,3->1,5->2编号为5的人离开
最后留下人的编号是3      

示例2

输入:

1,1

返回值:

1
typedef struct ListNode ListNode;

创建一个节点并返回
ListNode* ListBuyNode(int val)
{
    ListNode* ret = (ListNode*)malloc(sizeof(ListNode));
    if(ret==NULL)
    {
        perror("malloc");
        return NULL;
    }
    ret->val = val;
    ret->next = NULL;
    return ret;
}
//创建一个环形链表并返回
ListNode* CreatNode(int n)
{
    ListNode* phead = ListBuyNode(1);
    ListNode* pTail = phead;
    int i = 0;
    for(i=2;i<=n;i++)
    {
        pTail->next = ListBuyNode(i);
        pTail = pTail->next;
    }
    pTail->next = phead;//将链表循环
    return pTail;
}

int ysf(int n, int m ) {
    // write code here
    ListNode* prev = CreatNode(n);
    ListNode* cur = prev->next;
    int count = 1;
    //遍历判断
    while(cur->next!=cur){
        if(count == m){
            prev->next = cur->next;
            free(cur);
            cur = prev->next;
            count = 1;
        }
        else{
            prev = cur;
            cur = cur->next;
            count++;
        }
    }
    return cur->val;
}

6、分隔链表

题目要求:

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

示例 1:

输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]

示例 2:

输入:head = [2,1], x = 2
输出:[1,2]

解题思路:

大小链表:我们可以创建两个新的链表,为大小链表。遍历原链表。大于等于x的节点连接在大链表,小于x的节点连接在小链表。最后将大链表的头结点连接在小链表的尾结点。返回小链表的头结点

typedef struct ListNode {
    int val;
    struct ListNode* next;
}*pList;

//分隔链表的函数实现
struct ListNode* partition(struct ListNode* head, int x) {
    if (head == NULL){
        return NULL;
    }
    struct ListNode* lessHead, * lessTail;
    struct ListNode* greaterHead, * greaterTail;
    //定义哨兵位
    lessHead = lessTail = (struct ListNode*)malloc(sizeof(struct ListNode));
    greaterHead = greaterTail = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* pcur = head;//用来遍历原链表
    //分隔操作
    while (pcur){
        if (pcur->val >= x){
            greaterTail->next = pcur;
            greaterTail = greaterTail->next;
        }
        else{
            lessTail->next = pcur;
            lessTail = lessTail->next;
        }
        pcur = pcur->next;
    }
    //将大小链表连接起来
    lessTail->next = greaterHead->next;
    if(greaterTail)
       greaterTail->next = NULL;
    //释放哨兵位
    free(greaterHead);
    struct ListNode* retList = lessHead->next;
    free(lessHead);
    //返回头结点
    return retList;
}
int main()
{
    pList phead, pTail, p;
    phead = pTail = NULL;
    int n = 0;
    printf("请输入要创建链表节点个数:>");
    scanf("%d", &n);
    int i = 0;
    for (i = 0; i < n; i++)
    {
        p = (pList)malloc(sizeof(struct ListNode));
        printf("请输入第%d个节点的值:>", i + 1);
        scanf("%d", &(p->val));
        p->next = NULL;
        if (phead == NULL)
        {
            phead = p;
            pTail = phead;
        }
        else
        {
            pTail->next = p;
            pTail = pTail->next;
        }
    }
    pList Newhead = partition(phead, 3);
    if (Newhead == NULL)
    {
        perror("reverselist");
        return;
    }
    phead = Newhead;
    while (Newhead)
    {
        printf("%d->", Newhead->val);
        Newhead = Newhead->next;
    }
    printf("NULL\n");
    i = 1;
    Newhead = phead;
    while (Newhead)
    {
        phead = phead->next;
        free(Newhead);
        Newhead = phead;
        printf("已释放第%d条节点\n", i);
        i++;
    }
    return 0;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值