猴子选大王游戏,或者报数游戏。
给定人数,选择一个要报的数,报到这个数的人退出报数队列,最后计算出剩下的人,也就选择出的大王。
两种方法:
1、记录每次遍历报数人群中最后一个人报出的数,计算下次遍历时,偏移多少的人应该退出 (逻辑略复杂,其代码麻烦)
2、记录每次的报数,报到指定数就退出队伍,然后从1开始继续报,报到最后一个人再从第一个开始报(逻辑清楚,代码简单)
package api;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Algorithm {
public static void main(String[] args) {
final int specialValue = 3;
final int allDataCount = 123;
method1(specialValue, allDataCount, 2);
// method2(specialValue, allDataCount);
}
/**
* 记录报数,满5归一, 遍历到最后,通过修改报数下标,绕回到最开始
* @param specialValue
* @param allDataCount
*/
public static void method2(int specialValue, int allDataCount) {
String[] allDataArray = initArray(allDataCount);
List<String> tmpData = new ArrayList<>(Arrays.asList(allDataArray));
// 报数
int count = 1;
// 报数下标
int index = 1;
// 循环总数
int sum = 0;
while (tmpData.size() != 2) {
if (count%specialValue == 0) {
tmpData.remove(index);
count = 1;
} else {
count++;
index++;
}
if (index > (tmpData.size() -1 )) {
index = 1;
}
sum++;
}
System.out.println("calulate over!");
System.out.println("循环次数:" + sum);
System.out.println("最终获胜的是第" + tmpData.get(1) + "个人");
}
/**
* 不同的methodType值,决定不同的计算每次遍历后最后一个人喊出的数,需要两次遍历
* @param specialValue 指定要喊出的值
* @param allDataCount 总人数
* @param methodType 计算不同
*/
public static void method1(int specialValue, int allDataCount, int methodType) {
String[] allDataArray = initArray(allDataCount);
List<String> tmpData = new ArrayList<>(Arrays.asList(allDataArray));
// 偏移量
int offset = 0;
// 最后一个人喊出的数
int countOfLastData = 0;
int a = 1;
while (true) {
System.out.println("此次为第" + a + "次遍历,第一个人喊出的数是:" + (countOfLastData < specialValue? countOfLastData + 1 : 1));
System.out.println("此次遍历原始数据");
for(int i=1; i < tmpData.size(); i++) {
System.out.println(tmpData.get(i));
}
// 核心逻辑
// start
// 1、 标记本次遍历需要删除的数据
int dataSize = tmpData.size() - 1;
// 标记此次遍历删除的元素
List<String> removedData = new ArrayList<String>();
// 标记喊出指定数的元素
for(int i = 1; i <= dataSize; i++) {
if (i%specialValue == offset) {
// 记录下要删除的数,并做下标记
removedData.add(tmpData.get(i));
tmpData.set(i, "*");
}
}
// 2、 计算本次遍历最后一个元素喊出的数
switch (methodType) {
case 1:
// 最后一个喊出指定数的元素的索引
int lastRemoveDataIndex = tmpData.lastIndexOf("*");
// 此次遍历有人喊出指定的数
if (-1 != lastRemoveDataIndex) {
// 如果是最后一个人喊出
if (lastRemoveDataIndex == dataSize) {
countOfLastData = specialValue;
} else {
// 如果不是最后一个人喊,用总数 - 最后一个喊出指定数的人的位置
countOfLastData = dataSize - lastRemoveDataIndex;
}
} else {
// 此次遍历没人喊出指定的数
// 两种情况: 1、上一轮最后一个人喊出5, 2、最后一个人喊的不是5
if (0 == offset) {
// 第一种情况
//最后一个人喊出的数 = 剩余人数
countOfLastData = dataSize;
} else {
// 第二种情况:
// 最后一个人喊出的数 = 上一轮最后一个人喊出的数 + 剩余人数
countOfLastData = (specialValue - offset) + dataSize;
}
}
break;
case 2:
//2、 计算本次遍历最后一个元素喊出的数
countOfLastData = (countOfLastData + dataSize) % specialValue;
if (countOfLastData == 0) {
countOfLastData = specialValue;
}
break;
default:
break;
}
// 3、计算偏移量
offset = specialValue - countOfLastData;
// 4、删除被标记的元素
for(int i=0; i < tmpData.size(); i++) {
if (tmpData.get(i) == "*") {
tmpData.remove(i);
}
}
// end
System.out.println("此次遍历删除的内容:");
for (int i = 0; i < removedData.size(); i++) {
System.out.println(removedData.get(i));
}
System.out.println("此次遍历后的结果:");
for(int i=1; i < tmpData.size(); i++) {
System.out.println(tmpData.get(i));
}
System.out.println("此次为第" + a + "次遍历,最后一个人喊出的数是:" + countOfLastData);
System.out.println("------------------------");
if (tmpData.size() == 2) {
break;
}
a++;
}
System.out.println("calulate over!");
System.out.println("遍历次数:" + a);
System.out.println("最终获胜的是第" + tmpData.get(1) + "个人");
}
public static String[] initArray(int count) {
String[] dataArray = new String[count + 1];
for (int i = 1; i <= count; i++) {
dataArray[i] = String.valueOf(i);
}
return dataArray;
}
}