【经典算法】LCR187:破冰游戏(约瑟夫问题,Java/C/Python3/JavaScript实现含注释说明,Easy)

简介: 【经典算法】LCR187:破冰游戏(约瑟夫问题,Java/C/Python3/JavaScript实现含注释说明,Easy)

题目

社团共有 num 位成员参与破冰游戏,编号为 0 ~ num-1。成员们按照编号顺序围绕圆桌而坐。社长抽取一个数字 target,
从 0 号成员起开始计数,排在第 target 位的成员离开圆桌,且成员离开后从下一个成员开始计数。
请返回游戏结束时最后一位成员的编号。
示例 1:
输入:num = 7, target = 4
输出:1
示例 2:
输入:num = 12, target = 5
输出:0
提示:
1 <= num <= 10^5
1 <= target <= 10^6

原题:LeetCode LCR187

思路及实现

约瑟夫问题

这个问题是以弗拉维奥·约瑟夫命名的,他是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。

—— 【约瑟夫问题】

详见:约瑟夫问题

方式一:迭代模拟(用链表模拟这个游戏)

思路

这是经典的约瑟夫问题(Josephus Problem)。我们可以模拟这个过程,使用一个列表来存储成员编号,每次计数到 target 时,将当前成员移除列表,然后计数到下一个成员。重复此过程,直到列表里只剩下一个成员,返回该成员的编号。

代码实现

Java版本
public int lastRemaining(int num, int target) {
    List<Integer> members = new ArrayList<>();
    for (int i = 0; i < num; i++) {
        members.add(i);
    }
    int index = 0;
    while (num > 1) {
        index = (index + target - 1) % num; // 减1因为从0开始计数,取余是因为是圆桌
        members.remove(index);
        num--;
    }
    return members.get(0);
}

说明:

迭代地模拟成员被移出的过程,index 表示每次需要移除成员的位置。

C语言版本
#include <stdio.h>
#include <stdlib.h>
int lastRemaining(int num, int target) {
    // 创建一个动态数组来模拟成员围坐一圈的情况
    int *members = (int *)malloc(num * sizeof(int));
    
    // 初始化成员编号
    for (int i = 0; i < num; i++) {
        members[i] = i;
    }
    int current = 0; // 当前计数开始的位置
    int remaining = num; // 剩余成员数
    while (remaining > 1) {
        // 计算要移除成员的索引位置
        int removeIndex = (current + target - 1) % remaining;
        
        // 从数组中移除成员
        for (int j = removeIndex; j < remaining - 1; j++) {
            members[j] = members[j + 1];
        }
        // 更新当前计数开始的位置
        current = removeIndex % (remaining - 1);
        
        // 更新剩余成员数
        remaining--;
    }
    // 记录最后剩下的成员编号
    int lastMember = members[0];
    
    // 释放动态数组所占用的内存
    free(members);
    
    return lastMember;
}
// 测试程序
int main() {
    int num = 7, target = 4;
    printf("The last remaining member is: %d\n", lastRemaining(num, target));
    return 0;
}

说明:

代码实现了迭代模拟方式来解决约瑟夫环问题。首先初始化成员编号,然后根据游戏规则逐一模拟计数与成员被移除的过程。注意,由于成员编号是从0开始,所以移除成员的索引位置需要进行 target - 1 处理。每次有成员移除后,都需要更新计数的起始位置以及剩余的成员数量。最终剩下的成员的编号即为所求。

此外,代码还处理了动态分配内存的释放,以避免内存泄漏问题。

Python3版本
def last_remaining(num, target):
    members = list(range(num))
    index = 0
    while num > 1:
        index = (index + target - 1) % num # 减1因为从0开始计数,取余是因为是圆桌
        members.pop(index)
        num -= 1
    return members[0]

说明:

Python版本的实现思路与Java版本相同,使用列表和迭代的方式模拟约瑟夫环的过程。

复杂度分析

  • 时间复杂度:O(num^2),因为每次删除操作都需要 O(num) 的时间
  • 空间复杂度:O(num),存储成员编号需要的空间

方式二:数学+迭代

思路

在约瑟夫问题中,可以找到递归的关系f(n, m) = (f(n-1, m) + m) % n,其中f(n, m)表示第n轮中以m开始计数的最后胜利者的位置。

代码实现

Java版本
public int lastRemaining(int num, int target) {
    int res = 0; // num=1时最后剩下的成员编号
    for (int i = 2; i <= num; i++) {
        res = (res + target) % i;
    }
    return res;
}

说明:

基于递归关系迭代地求解最后剩下成员的编号,避免了昂贵的数组删除操作。

C语言版本
#include <stdio.h>
int lastRemaining(int num, int target) {
    int res = 0; // 最开始,编号为0的成员肯定会留下
    // 从第二位成员开始迭代,直到num位成员
    for(int i = 2; i <= num; i++) {
        res = (res + target) % i;
    }
    return res;
}
int main() {
    int num = 7, target = 4;
    printf("The last remaining member is: %d\n", lastRemaining(num, target));
    return 0;
}

说明

从1计数到 num,代表每一轮的成员数。在每轮计算中,

res 的值为上一轮中剩下成员的位置,将其与 target 相加后对当前轮的成员数取余数,得到新一轮中剩余成员的位置。

最后返回 res,即为最后剩下成员的编号。

Python3版本
def last_remaining(num, target):
    res = 0  # num=1时最后剩下的成员编号
    for i in range(2, num + 1):
        res = (res + target) % i
    return res

说明:

利用递归关系进行迭代求解

复杂度分析

  • 时间复杂度:O(num),只需迭代 num-1 次
  • 空间复杂度:O(1),仅需常数个变量存储中间结果

方式三:递归

思路

约瑟夫问题还可以采用递归的思路来解决。对于 num 个人的情况,如果我们知道了 num-1 个人的情况下的胜利者的索引,那么我们可以通过递归关系得到 num 个人时的最终胜利者。

递归关系如下:

f(n, m) = (f(n-1, m) + m) % n

其中 f(1, m) = 0,f(n, m) 表示总数为 n,计数为 m的情况下最后胜利者的索引。

代码实现

Java版本
public int lastRemaining(int num, int target) {
    return lastRemainingRec(num, target);
}
private int lastRemainingRec(int num, int target) {
    if (num == 1) {
        // 只有一个成员时,他肯定是胜利者
        return 0;
    } else {
        // 递归计算 num-1 个成员时的胜利者的索引,并应用递归关系
        return (lastRemainingRec(num - 1, target) + target) % num;
    }
}

说明:递归在每次调用中计算 num-1 的情况,并将结果使用到 num 个成员的情况。

C语言版本
#include <stdio.h>
int lastRemainingRec(int num, int target) {
    if (num == 1) {
        // 只有一个成员时,他肯定是胜利者
        return 0;
    } else {
        // 递归计算 num-1 个成员时的胜利者的索引,并应用递归关系
        return (lastRemainingRec(num - 1, target) + target) % num;
    }
}
int lastRemaining(int num, int target) {
    return lastRemainingRec(num, target);
}
int main() {
    int num = 7, target = 4;
    printf("The last remaining member is: %d\n", lastRemaining(num, target));
    return 0;
}

说明:采用递归方式,递归的边界情况是只剩一个成员时,其编号为0。非边界情况使用递归函数计算。

Python3版本
def last_remaining_rec(num, target):
    if num == 1:
        # 只有一个成员时,他肯定是胜利者
        return 0
    else:
        # 递归计算 num-1 个成员时的胜利者的索引,并应用递归关系
        return (last_remaining_rec(num - 1, target) + target) % num
def last_remaining(num, target):
    return last_remaining_rec(num, target)
# 示例
print(last_remaining(7, 4))  # 输出: 1
print(last_remaining(12, 5)) # 输出: 0

说明:Python 版本的实现中同样使用递归,直观地展示了解法的递归逻辑结构。

复杂度分析

  • 时间复杂度:O(num),因为递归函数将被调用 num 次。
  • 空间复杂度:O(num),递归需要使用栈空间,其大小取决于递归的深度,最大为 num。

总结

方式 描述 优点 缺点 时间复杂度 空间复杂度
迭代模拟 直接根据规则模拟整个游戏过程,依次淘汰成员 直观和易理解 当成员数目较大时,效率较低 O(num^2) O(num)
数学+迭代 通过数学公式递推最终结果,逐步缩小问题规模 时间效率高,不需要昂贵的删除操作 需要数学知识,公式推导可能不够直观 O(num) O(1)
递归 通过递归函数,从基础情况逐步返回最终答案 代码简洁,易编写 栈空间开销大,可能会栈溢出 O(num) O(num)
迭代改进 递归方法的迭代版本,避免了栈溢出的问题 避免了递归引起的栈溢出 相对于直接递归,可能理解起来稍微复杂 O(num) O(1)

相似题目

题号 名称 难度 相似点
LeetCode-141 Linked List Cycle Easy 使用快慢指针判断链表是否有环
LeetCode-142 Linked List Cycle II Medium 寻找链表中环的入口点
LeetCode-202 Happy Number Easy 利用快慢指针寻找循环
LeetCode-287 Find the Duplicate Number Medium 数组可以视为链表,寻找环的入口
LeetCode-206 Reverse Linked List Easy 链表的基本操作
LeetCode-234 Palindrome Linked List Easy 链表操作和快慢指针
LeetCode-160 Intersection of Two Linked Lists Easy 寻找两个链表的交点
相关文章
|
20天前
|
机器学习/深度学习 人工智能 算法
猫狗宠物识别系统Python+TensorFlow+人工智能+深度学习+卷积网络算法
宠物识别系统使用Python和TensorFlow搭建卷积神经网络,基于37种常见猫狗数据集训练高精度模型,并保存为h5格式。通过Django框架搭建Web平台,用户上传宠物图片即可识别其名称,提供便捷的宠物识别服务。
219 55
|
9天前
|
存储 缓存 监控
局域网屏幕监控系统中的Python数据结构与算法实现
局域网屏幕监控系统用于实时捕获和监控局域网内多台设备的屏幕内容。本文介绍了一种基于Python双端队列(Deque)实现的滑动窗口数据缓存机制,以处理连续的屏幕帧数据流。通过固定长度的窗口,高效增删数据,确保低延迟显示和存储。该算法适用于数据压缩、异常检测等场景,保证系统在高负载下稳定运行。 本文转载自:https://www.vipshare.com
102 66
|
4天前
|
Python
课程设计项目之基于Python实现围棋游戏代码
游戏进去默认为九路玩法,当然也可以选择十三路或是十九路玩法 使用pycharam打开项目,pip安装模块并引用,然后运行即可, 代码每行都有详细的注释,可以做课程设计或者毕业设计项目参考
50 33
|
2月前
|
搜索推荐 Python
利用Python内置函数实现的冒泡排序算法
在上述代码中,`bubble_sort` 函数接受一个列表 `arr` 作为输入。通过两层循环,外层循环控制排序的轮数,内层循环用于比较相邻的元素并进行交换。如果前一个元素大于后一个元素,就将它们交换位置。
140 67
|
2月前
|
存储 搜索推荐 Python
用 Python 实现快速排序算法。
快速排序的平均时间复杂度为$O(nlogn)$,空间复杂度为$O(logn)$。它在大多数情况下表现良好,但在某些特殊情况下可能会退化为最坏情况,时间复杂度为$O(n^2)$。你可以根据实际需求对代码进行调整和修改,或者尝试使用其他优化策略来提高快速排序的性能
128 61
|
2月前
|
算法 数据安全/隐私保护 开发者
马特赛特旋转算法:Python的随机模块背后的力量
马特赛特旋转算法是Python `random`模块的核心,由松本真和西村拓士于1997年提出。它基于线性反馈移位寄存器,具有超长周期和高维均匀性,适用于模拟、密码学等领域。Python中通过设置种子值初始化状态数组,经状态更新和输出提取生成随机数,代码简单高效。
118 63
|
30天前
|
机器学习/深度学习 人工智能 算法
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
宠物识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了37种常见的猫狗宠物种类数据集【'阿比西尼亚猫(Abyssinian)', '孟加拉猫(Bengal)', '暹罗猫(Birman)', '孟买猫(Bombay)', '英国短毛猫(British Shorthair)', '埃及猫(Egyptian Mau)', '缅因猫(Maine Coon)', '波斯猫(Persian)', '布偶猫(Ragdoll)', '俄罗斯蓝猫(Russian Blue)', '暹罗猫(Siamese)', '斯芬克斯猫(Sphynx)', '美国斗牛犬
155 29
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
|
6天前
|
算法 网络协议 Python
探秘Win11共享文件夹之Python网络通信算法实现
本文探讨了Win11共享文件夹背后的网络通信算法,重点介绍基于TCP的文件传输机制,并提供Python代码示例。Win11共享文件夹利用SMB协议实现局域网内的文件共享,通过TCP协议确保文件传输的完整性和可靠性。服务器端监听客户端连接请求,接收文件请求并分块发送文件内容;客户端则连接服务器、接收数据并保存为本地文件。文中通过Python代码详细展示了这一过程,帮助读者理解并优化文件共享系统。
|
11天前
|
存储 算法 Python
文件管理系统中基于 Python 语言的二叉树查找算法探秘
在数字化时代,文件管理系统至关重要。本文探讨了二叉树查找算法在文件管理中的应用,并通过Python代码展示了其实现过程。二叉树是一种非线性数据结构,每个节点最多有两个子节点。通过文件名的字典序构建和查找二叉树,能高效地管理和检索文件。相较于顺序查找,二叉树查找每次比较可排除一半子树,极大提升了查找效率,尤其适用于海量文件管理。Python代码示例包括定义节点类、插入和查找函数,展示了如何快速定位目标文件。二叉树查找算法为文件管理系统的优化提供了有效途径。
42 5
|
11天前
|
存储 缓存 算法
探索企业文件管理软件:Python中的哈希表算法应用
企业文件管理软件依赖哈希表实现高效的数据管理和安全保障。哈希表通过键值映射,提供平均O(1)时间复杂度的快速访问,适用于海量文件处理。在Python中,字典类型基于哈希表实现,可用于管理文件元数据、缓存机制、版本控制及快速搜索等功能,极大提升工作效率和数据安全性。
46 0