文中的速度测试部分,时间是通过简单的 System.currentTimeMillis() 计算得到的,
又由于 Java 的特性,每次测试的结果都不一定相同,
对于低数量级的情况有 ± 20 的浮动,对于高数量级的情况有的能有 ± 1000 的浮动。
这道题本质上是个约瑟夫环问题,最佳解法在最下面,本文只是探究一下数组暴力和链表的表现差异。
题目
N 个人围成一圈,顺序排号。从第一个人开始报数(从1数到3),凡是到3的人退出圈子,问最后留下的是原来第几号。
样例
2 个人时留下的是第二个;
3个人时留下的是第二个;
5个人时留下的是第四个;
12个人时留下的是第十个;
100,000个人时留下的是第92620个人。
机器环境
CPU Intel Xeon E3-1231 v3 @ 3.40GHz
RAM 16 GB
暴力解决
虽然第一反应是用链表,但对于人数在1000以下的量级感觉数组也足以胜任,因此先用数组试试。
对于这种会 退出 的情况,数组显然不能像链表一样直接断开,因此采用标记法:
先生成一个长度为 N 的布尔型数组,用 true 填充。
报号时,对于报到 3 的位置,用 false 来标记该位置,下次循环如果遇到 false 则可以直接跳过。
那么等到数组内只剩一个 true 的时候,找到其位置,即是最后留下来的人的位置。
既然暴力,那干脆彻底一点:
public static int findIndex(final int N) {
boolean[] map = new boolean[N];
Arrays.fill(map, true);
int walk = 1;
// 因为是站成一个圆,所以在遍历到最后时需要将下标重新指向 0
// count(map) 就是遍历整个数组计算还剩余的 true 的数量
for (int index = 0; count(map) > 1; index = (index == N - 1) ? 0 : (index + 1)) {
// 对于 false 可以直接跳过,因为它们相当于不存在
if (! map[index]) continue;
// 报号时如果不是3 则继续找下一位;
if (walk++ != 3) continue;
// 如果是 3,则重置报号,并将当前位置的值改为 false
walk &