所有人围成一个圈,数到3的人就退出,问最后留下的那个人的位置在那里

本文详细介绍了两种解决约瑟夫环问题的方法,包括使用数组进行迭代的常规方法和面向对象思想的实现方式。通过代码示例,解释了如何在有限人数的圆圈中,每数到3即淘汰一人,最终确定剩下的人的位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一种方法

public class Count3Quit{
public static void main(String []args){
//定义一个数组用来当做500个人.并且初始化为true表示都在圈内
boolean arr[] =new boolean[500];
for(int i=0; i<arr.length; i++){
arr[i]=true;
}
//定义一个总人数
int count=arr.length;
//定义一个索引位置,表示当前在那个位置
int index=0;
//计数器,数到3退出
int countNum=0;
//如果只剩下最后一个人就退出循环
while(count>1){

if(arr[index]==true){ //如果当前的这个人在圈内就开始数数

countNum++;

if(countNum==3){ 

countNum=0; // 如果数到3了就要把计数器归0

arr[index]=false; //表示这个人已经退出

count--; //总人数减去一个


}

index++;

if(index==arr.length){
index=0;
}
}
//找到最后一个在圈子内的人的下标位置
for(int i=0 ;i<arr.length; i++){
if(arr[i]==true){
System.out.println("最后剩下的一个人在第"+(i+1)+"个位置");
}
}
}
}

第二种方法(面向对象的思想)

public class Count3Quit2 {
public static void main(String[] args) {
KidCircle kc = new KidCircle(500);
int countNum = 0;
Kid k = kc.first;
while(kc.count > 1) {
countNum ++;
if(countNum == 3) {
countNum = 0;
kc.delete(k);
}
k = k.right;
}

System.out.println(kc.first.id);
}
}

class Kid {
int id;
Kid left;
Kid right;
}

class KidCircle {
int count = 0;
Kid first, last;

KidCircle(int n) {
for(int i=0; i<n; i++) {
add();
}
}

void add() {
Kid k = new Kid();
k.id = count;
if(count <= 0) {
first = k;
last = k;
k.left = k;
k.right = k;
} else {
last.right = k;
k.left = last;
k.right = first;
first.left = k;
last = k;
}
count ++;
}

void delete(Kid k) {
if(count <= 0) {
return;
} else if (count == 1) {
first = last = null;
} else {
k.left.right = k.right;
k.right.left = k.left;

if(k == first) {
first = k.right;
} else if( k == last) {
last = k.left;
}
}
count --;
}
}

<think>题目:有n个围成一圈,顺序排号。从第一个开始报(从1到3),凡报到3退出圈子,最后留下的是原来第几号的那位。这是一个经典的约瑟夫环题。我们可以使用循环链表或者组来模拟这个过程。这里我们使用组来模拟,因为组实现起来比较简单。思路:1.定义一个组,长度为n,初始化为0,表示所有人都在圈内(0表示在圈内,非0表示出圈)。2.使用一个变量count来记录当前报(从1开始报,报到3就出圈)。3.使用一个变量remain记录当前圈内剩余的,初始为n。4.使用一个变量index表示当前报位置,从0开始(即第一个)。5.循环直到圈内只剩一个(remain==1):如果当前这个还在圈内(即组值为0),则count加1。如果count等于3,说明这个要出圈:将组值置为1(表示出圈),count重置为0,remain减1。然后index移动到下一个位置(index=(index+1) %n),如果下一个位置已经出圈,则继续往下找,直到找到下一个在圈内的(注意:我们每次移动一个位置,然后判断是否在圈内,所以循环中每次只移动一步)。6.最后遍历组,找到那个值为0的位置,即为最后留下。注意:题目的是原来的第几号,而组下标从0开始,所以最后结果要加1。但是上面的思路在移动时,每次只移动一步,然后判断,这样效率较低。我们可以优化一下:每次报时,我们只关心还在圈内的。因此,我们可以这样:在报过程中,如果当前位置已经出圈,则跳过;否则,报count加1,然后判断是否报到3。具体步骤:初始化:int circle[n]= {0};//0表示在圈内int remain= n;intcount =0;//报器,从0开始,到2就重置,这样相当于1,2,3int index=0;//当前位置while (remain >1) {if(circle[index]==0){//如果当前在圈内count++;//报if (count ==3) {//报到3circle[index]=1;//标记为出圈remain--;count=0;//重置报器}}index =(index+1) %n;//移动到下一个}但是上面的方法有一个题:当n很大时,已经出圈的会被重复跳过,但不会影响逻辑。因为出圈的我们不会再报,只是移动位置时经过他们。最后,我们需要找到圈内剩下的那个,即circle[i]为0的位置,然后输出i+1。但是,我们也可以用一个变量last记录最后剩下的位置,但上面的循环结束后,我们还需要遍历一次组。我们可以避免最后的遍历吗?实际上,我们可以在循环中记录最后剩下的位置,但这样并不容易,因为循环中一直在改变index。所以最后遍历一次组是简单可行的。然而,我们可以优化:在循环中,当remain==1时,我们跳出循环,然后从当前index开始往后找第一个还在圈内的。但是这样可能还需要转一圈。所以不如最后遍历整个组一次。但是注意:循环结束后,index的位置并不一定是最后剩下的那个,因为循环中index一直在移动。所以最后我们还是要遍历组。因此,我们采用最后遍历组的方法。代码实现:注意:这里我们假设n>=1。例子:n=5初始:circle= [0,0,0,0,0]报过程:index=0:count=1 ->不处理index=1:count=2 ->不处理index=2: count=3->出圈:circle[2]=1, remain=4, count重置为0 ->circle=[0,0,1,0,0]index=3:count=1 ->不处理index=4: count=2->不处理index=0:count=3 ->出圈:circle[0]=1,remain=3,count重置-> circle=[1,0,1,0,0]index=1:count=1 ->不处理index=2:已经出圈,跳过,index=3: count=2->不处理index=4:count=3 ->出圈:circle[4]=1,remain=2 ->circle=[1,0,1,0,1]index=0:出圈,跳过;index=1: count=1->不处理;index=2:出圈,跳过;index=3:count=2 ->不处理;index=4:出圈,跳过index=0:出圈;index=1:count=3 ->出圈:circle[1]=1,remain=1 ->结束最后遍历组,发现circle[3]还是0,所以最后留下的是第4号(因为下标3对应第4位)。但是按照上面的过程,最后出圈的是1号位置(下标1)?实际上,最后剩下的是下标3位置(即第4个)。所以我们的代码逻辑正确。但是上面的例子中,最后一步:当remain=2时,只有index=1和index=3在圈内。从index=0(上一轮结束位置是4,然后4+1=5mod5=0)开始:0:出圈,跳过,count不变(此时count=0,因为上一轮在index=4时重置了?不对,上一轮在index=4出圈后重置count=0,然后index=0(出圈)跳过,count不变还是0,然后index=1:在圈内,count=1,不出圈;index=2出圈跳过,count不变;index=3:在圈内,count=2;index=4出圈跳过;然后index=0(出圈)跳过;index=1:count=3,所以将1出圈,此时remain=1,结束。那么最后剩下的是3(下标3)吗?因为此时只有3还在圈内。所以最后输出4。因此,代码实现如下:但是注意:上面的例子中,最后一步报3时,将index=1出圈,然后剩余的就是index=3。因此,我们最后需要遍历组,找到那个值为0的位置。然而,我们也可以用一个变量last来记录最后剩下的位置,但需要小心:每次出圈后,如果剩余为1,那么当前圈内剩下的就是最后一个吗?不是,因为出圈后,我们不知道下一个在圈内的是谁。所以还是需要遍历。所以,我们采用最后遍历组的方法。代码:注意:如果n=1,那么直接返回1。下面我们写代码:但是,上面的方法在n很大时,效率较低,因为已经出圈的我们每次都要跳过,但是循环次3*(n-1)次左右,因为每出圈一个需要报3次(实际上,每出圈一个需要经过3个在圈内的,但是已经出圈的也会被扫描到,所以总次3*(n-1)加上一些跳过出圈的次,但总次不会超过3*n)。所以对于n不是特别大(比如百万级别)是可以接受的。另外,我们也可以使用链表,但这里要求用C语言,组更简单。实现:我们用一个循环来模拟整个过程。代码示例:</think>以下是解决约瑟夫环题的C语言代码实现。该题描述了n个围成一圈,从第一个开始报(1到3),每报到3退出最后留下原始编号是多少。 ```c #include <stdio.h> int main() { int n; printf("请输入总n: "); scanf("%d", &n); // 创建并初始化组(0表示在圈内,1表示已退出) int circle[n]; for (int i = 0; i < n; i++) { circle[i] = 0; } int remain = n; // 剩余 int count = 0; // 报器(0,1,2循环) int index = 0; // 当前位置索引 // 模拟报过程 while (remain > 1) { if (circle[index] == 0) { // 只处理仍在圈内的 count = (count + 1) % 3; // 报递增(0->1->2->0...) if (count == 0) { // 等价于报3 circle[index] = 1; // 标记为退出 remain--; // 剩余减1 } } index = (index + 1) % n; // 移动到下一个(循环组) } // 查找最后留下 for (int i = 0; i < n; i++) { if (circle[i] == 0) { printf("最后留下的是第%d号\n", i + 1); // 索引转编号(+1) break; } } return 0; } ``` ### 代码解释: 1. **初始化**: - 创建组`circle[]`表示员状态(0在圈内,1已退出)。 - `remain`记录剩余,初始为`n`。 - `count`是报器(0、1、2循环,0等价于报3)。 - `index`跟踪当前报位置。 2. **报循环**: - 当剩余大于1时持续循环: - 若当前位置在圈内(`circle[index]==0`),则报器`count`递增(模3循环)。 - 当`count==0`(即报3)时,标记该位置退出(`circle[index]=1`),剩余减1。 - 无论是否处理,索引`index`循环移动到下一位(`(index+1)%n`)。 3. **结果输出**: - 循环结束后,遍历组找到唯一状态为0的位置,输出`索引+1`(原始编号)。 ### 示例运行: 输入`n=5`时: ``` 报过程: 初始: [0,0,0,0,0] 第1轮:索引2报3退出 → [0,0,1,0,0](剩余4) 第2轮:索引0报3退出 → [1,0,1,0,0](剩余3) 第3轮:索引4报3退出 → [1,0,1,0,1](剩余2) 第4轮:索引1报3退出 → [1,1,1,0,1](剩余1) 输出:索引3 → 第4号 ``` ### 算法特点: - **时间复杂度**:O(n×k),其中k=3(报上限)。每轮至少处理1,最多扫描n个位置。 - **空间复杂度**:O(n),使用组存储状态。 - **核心技巧**:循环组(`%n`实现环形遍历)和状态标记。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值