约瑟夫问题

约瑟夫问题

有 n 个小朋友围成一圈玩游戏,小朋友从 1 至 n 编号,2 号小朋友坐在 1 号小朋友的顺时针方向,3 号小朋友坐在 2 号小朋友的顺时针方向,……,1 号小朋友坐在 n 号小朋友的顺时针方向。

游戏开始,从 1 号小朋友开始顺时针报数,接下来每个小朋友的报数是上一个小朋友报的数加 1。若一个小朋友报的数为 k 的倍数或其末位数(即数的个位)为 k,则该小朋友被淘汰出局,不再参加以后的报数。当游戏中只剩下一个小朋友时,该小朋友获胜。

例如,当 n=5, k=2 时:

1 号小朋友报数 1;

2 号小朋友报数 2 淘汰;

3 号小朋友报数 3;

4 号小朋友报数 4 淘汰;

5 号小朋友报数 5;

1 号小朋友报数 6 淘汰;

3 号小朋友报数 7;

5 号小朋友报数 8 淘汰;

3 号小朋友获胜。

给定 n 和 k,请问最后获胜的小朋友编号为多少?

输入格式

输入一行,包括两个整数 n 和 k,意义如题目所述。

输出格式 输出一行,包含一个整数,表示获胜的小朋友编号。

样例输入

5 2

样例输出

3

样例输入

7 3

样例输出

4

数据规模和约定

对于所有评测用例,1 ≤ n ≤ 1000,1 ≤ k ≤ 9。


解法一

双向链表法

#include <iostream>
#include <cstdlib>

typedef struct child{
    int data;
    struct child*prior,* next;
}*linklist;//创建小朋友节点

linklist loopInit(linklist &c, int n){
    c = (linklist)malloc(sizeof(child));
    if(c == NULL){
        std::cerr << "Memory allocation failed!" << std::endl;
        exit(EXIT_FAILURE);
    }
    c->data = 1;
    child*s,*r = c;
    for(int i = 2;i<=n;i++){
        s = (linklist)malloc(sizeof(child));
        if (s == NULL) {
            std::cerr << "Memory allocation failed!" << std::endl;
            exit(EXIT_FAILURE);
        }
        s->data = i;
        s->prior = r;
        r->next = s;
        r = s;
    }
    r->next = c;
    c->prior = r;
    return c;
}//创建小朋友环

void loopdelete(linklist &c){
    linklist temp = c->next;
    c->prior->next = temp;
    temp->prior = c->prior;
    free(c);
    c = temp;
}//淘汰小朋友

int solution(linklist c,int n, int k){
    int count = 1;
    while(n>1){
        if(count%k==0||(count%10)==k){
            loopdelete(c);
            n--;
        }else{
            c = c->next;
        }
        count++;
    }
    return c->data;
}//执行整个报数流程

int main() {
    int n,k;
    std::cin >> n >> k;
    linklist c = nullptr;
    c = loopInit(c, n);
    int result = solution(c,n,k);
    std::cout << result << std::endl;
    free(c);
    return 0;
}

代码解释

  1. 初始化循环链表loopInit 函数创建一个包含 n 个小朋友的循环链表,并初始化他们的编号。
  2. 删除节点处理loopdelete 函数删除当前节点并调整指针,保持链表结构的完整性。
  3. 游戏逻辑solution 函数模拟游戏过程,通过 count 变量追踪当前节点的编号。
    • 每当 countk 的倍数或 count 的个位数是 k 时,删除当前节点。
  4. 内存释放:在主函数结束前释放所有分配的内存,避免内存泄漏。

通过这种方式,我们可以正确处理任意位数的 count 并判断其个位数是否符合条件。

解法二

循环数组法

#include <iostream>
#include <vector>

int solution(int n, int k) {
    std::vector<bool> eliminated(n, false);
    int count = 0;  // 报数计数器
    int remaining = n;  // 剩余的小朋友数量
    int currentIndex = 0;  // 当前小朋友的索引

    while (remaining > 1) {
        if (!eliminated[currentIndex]) {
            count++;
            if (count % k == 0 || (count % 10) == k) {
                eliminated[currentIndex] = true;
                remaining--;
            }
        }
        currentIndex = (currentIndex + 1) % n;  // 环形处理
    }

    // 找到最后一个未被淘汰的小朋友
    for (int i = 0; i < n; ++i) {
        if (!eliminated[i]) {
            return i + 1;  // 返回编号(从1开始)
        }
    }
    return -1;  // 理论上不应该到达这里
}

int main() {
    int n, k;
    std::cin >> n >> k;
    int result = solution(n, k);
    std::cout << result << std::endl;
    return 0;
}

代码解释

  1. 初始化:创建一个大小为 n 的布尔数组 eliminated 来标记每个小朋友是否被淘汰,初始值都为 false
  2. 主循环:当剩余的小朋友数量大于 1 时,进行循环。
    • 如果当前小朋友未被淘汰,则计数器 count 增加。
    • 检查当前计数器是否满足淘汰条件:countk 的倍数或 count 的个位数是 k
    • 如果满足淘汰条件,则将当前小朋友标记为已淘汰,并减少剩余的小朋友数量。
  3. 更新索引:将当前索引移动到下一个小朋友(环形处理)。
  4. 找出最后一个未被淘汰的小朋友:循环结束后,遍历数组找出最后一个未被淘汰的小朋友,并返回其编号(从1开始)。
  5. 输入检查:确保输入的 nk 在规定的范围内,否则输出错误信息并退出。

优点

  • 时间复杂度:使用数组进行标记和环形遍历,时间复杂度为 O(n^2),虽然看似较高,但由于每次操作都是常数时间,实际运行速度会比链表实现快很多。
  • 空间复杂度:使用一个大小为 n 的布尔数组来存储淘汰标记,空间复杂度为 O(n)。

这个方法通过减少复杂的内存操作和指针操作,提高了代码的运行效率。在实际应用中,这种方法更容易理解和维护。

  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值