题目描述
100个人围成一圈,每个人有一个编码,编号从1开始到100。
他们从1开始依次报数,报到为M的人自动退出圈圈,然后下一个人接着从1开始报数,直到剩余的人数小于M。
请问最后剩余的人在原先的编号为多少?
输入描述
输入一个整数参数 M
输出描述
如果输入参数M小于等于1或者大于等于100,输出“ERROR!”;
否则按照原先的编号从小到大的顺序,以英文逗号分割输出编号字符串
用例
输入 | 3 |
输出 | 58,91 |
说明 | 输入M为3,最后剩下两个人。 |
输入 | 4 |
输出 | 34,45,97 |
说明 | 输入M为4,最后剩下三个人。 |
题目解析
本题是经典的约瑟夫环问题,可以利用循环链表解决。
解法一:循环链表
定义一个循环链表,头节点val=1,尾节点val=100,头.prev = 尾,尾.next = 头
这样的话,就构造出了一个循环链表。
我们还需要为循环链表设置,尾部追加新节点,删除任意节点的操作。
另外,我们还需要维护一个报数变量idx,从1开始。
这样的话,我们从链表头head开始查询,此时报数idx=1,
- 如果idx===m,则删除当前节点cur,下一个查询节点变为删除节点的下一个节点,并且重置报数idx=1,
- 如果idx!==m,则继续查询下一节点,即cur = cur.next,并且报数idx++
按上面逻辑,直到链表的size < m时,结束。
然后,for(i=0; i<size; i++) 遍历出循环链表的每一个节点值,就是题解。
Java算法源码
import java.util.Scanner;
import java.util.StringJoiner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
System.out.println(getResult(m));
}
public static String getResult(int m) {
if (m <= 1 || m >= 100) return "ERROR!";
CycleLinkedList list = new CycleLinkedList();
for (int i = 1; i <= 100; i++) list.append(i);
int idx = 1;
Node cur = list.head;
while (list.size >= m) {
if (idx == m) {
idx = 1;
cur = list.remove(cur);
} else {
idx++;
cur = cur.next;
}
}
return list.toString();
}
}
class Node {
int val;
Node prev;
Node next;
public Node(int val, Node prev, Node next) {
this.val = val;
this.prev = prev;
this.next = next;
}
}
class CycleLinkedList {
Node head;
Node tail;
int size = 0;
public void append(int val) {
Node node = new Node(val, null, null);
if (this.size > 0) {
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
} else {
this.head = node;
this.tail = node;
}
this.head.prev = this.tail;
this.tail.next = this.head;
this.size++;
}
public Node remove(Node cur) {
Node pre = cur.prev;
Node nxt = cur.next;
pre.next = nxt;
nxt.prev = pre;
cur.next = cur.prev = null;
if (this.head == cur) {
this.head = nxt;
}
if (this.tail == cur) {
this.tail = pre;
}
this.size--;
return nxt;
}
@Override
public String toString() {
StringJoiner sj = new StringJoiner(",");
Node cur = this.head;
for (int i = 0; i < this.size; i++) {
sj.add(cur.val + "");
cur = cur.next;
}
return sj.toString();
}
}
JS算法源码
/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on("line", (line) => {
const m = line - 0;
console.log(getResult(m));
});
function getResult(m) {
if (m <= 1 || m >= 100) {
return "ERROR!";
}
const list = new CycleLinkedList();
for (let i = 1; i <= 100; i++) list.append(i);
let idx = 1;
let cur = list.head;
while (list.size >= m) {
if (idx === m) {
idx = 1;
cur = list.remove(cur);
} else {
idx++;
cur = cur.next;
}
}
return list.toString();
}
// 节点定义(双向)
class Node {
constructor(val) {
this.val = val;
this.next = null;
this.prev = null;
}
}
// 循环链表定义
class CycleLinkedList {
constructor() {
this.head = null;
this.tail = null;
this.size = 0;
}
append(val) {
const node = new Node(val);
if (this.size) {
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
} else {
this.head = node;
this.tail = node;
}
this.head.prev = this.tail;
this.tail.next = this.head;
this.size++;
}
remove(cur) {
const pre = cur.prev;
const nxt = cur.next;
pre.next = nxt;
nxt.prev = pre;
cur.next = cur.prev = null;
if (this.head === cur) {
this.head = nxt;
}
if (this.tail === cur) {
this.tail = pre;
}
this.size--;
return nxt;
}
toString() {
const arr = [];
let cur = this.head;
for (let i = 0; i < this.size; i++) {
arr.push(cur.val);
cur = cur.next;
}
return arr.join();
}
}
Python算法源码
# 双向节点
class Node:
def __init__(self, val):
self.val = val
self.next = None
self.prev = None
# 循环链表
class CycleLinkedList:
def __init__(self):
self.head = None
self.tail = None
self.size = 0
def append(self, val):
node = Node(val)
if self.size > 0:
self.tail.next = node
node.prev = self.tail
self.tail = node
else:
self.head = node
self.tail = node
self.head.prev = self.tail
self.tail.next = self.head
self.size += 1
def remove(self, cur):
pre = cur.prev
nxt = cur.next
pre.next = nxt
nxt.prev = pre
cur.next = cur.prev = None
if self.head == cur:
self.head = nxt
if self.tail == cur:
self.tail = pre
self.size -= 1
return nxt
def __str__(self):
arr = []
cur = self.head
for i in range(self.size):
arr.append(str(cur.val))
cur = cur.next
return ",".join(arr)
# 输入获取
m = int(input())
# 算法入口
def getResult():
if m <= 1 or m >= 100:
return "ERROR!"
cycList = CycleLinkedList()
for i in range(1, 101):
cycList.append(i)
idx = 1
cur = cycList.head
while cycList.size >= m:
if idx == m:
idx = 1
cur = cycList.remove(cur)
else:
idx += 1
cur = cur.next
return str(cycList)
# 算法调用
print(getResult())
解法二:递归
逻辑分析,需要先考虑两点
- 找到报数M的人
- 维持连续报数
我这边维护了一个外部变量count,让他初始值为1,因为报数从1开始。
遍历数组,每遍历完一次,则count++,下次遍历时判断count是不是已经超过M了,若超过则count重置为1,以此模拟报数。
当count%M===0时,表示当前遍历的数组元素就是报数M的人,我们将他filter踢出去了。
维持连续报数,指的是,当我遍历完数组后,如果数组元素个数不少于M个,则需要新的报数轮次,此时新一轮的遍历时,第一个元素的报数需要接着上一轮结束时的报数,这个也可以通过外部变量count来维护。
Java算法源码
import java.util.ArrayList;
import java.util.Scanner;
import java.util.StringJoiner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
System.out.println(getResult(m));
}
public static String getResult(int m) {
if (m <= 1 || m >= 100) return "ERROR!";
ArrayList<Integer> arr = new ArrayList<>();
for (int i = 1; i <= 100; i++) arr.add(i);
return recursive(arr, m, 1);
}
public static String recursive(ArrayList<Integer> arr, int m, int count) {
ArrayList<Integer> nxt = new ArrayList<>();
for (Integer v : arr) {
if (count == m + 1) {
count = 1;
}
if (count++ % m != 0) {
nxt.add(v);
}
}
if (nxt.size() >= m) {
return recursive(nxt, m, count);
} else {
StringJoiner sj = new StringJoiner(",");
for (Integer v : nxt) sj.add(v + "");
return sj.toString();
}
}
}
JS算法源码
/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on("line", (line) => {
const m = parseInt(line);
if (m <= 1 || m >= 100) {
return console.log("ERROR!");
}
const arr = new Array(100).fill().map((_, index) => index + 1);
console.log(recursive(arr, m, 1));
});
// 1 2 3 4 5 6 7 8 9 10
// 1 2 _ 4 5 _ 7 8 _ 10
// 1 _ _ 4 5 _ _ 8 _ 10
// _ _ _ 4 5 _ _ _ _ 10
// _ _ _ 4 _ _ _ _ _ 10
/* 算法逻辑 */
function recursive(arr, m, count) {
arr = arr.filter(() => {
if (count == m + 1) count = 1;
return count++ % m;
});
if (arr.length >= m) {
return recursive(arr, m, count);
} else {
return arr.join(",");
}
}
Python算法源码
# 输入获取
m = int(input())
def recursive(arr, m, count):
nxt = []
for v in arr:
if count == m + 1:
count = 1
if count % m != 0:
nxt.append(v)
count += 1
if len(nxt) >= m:
return recursive(nxt, m, count)
else:
return ",".join(map(str, nxt))
# 算法入口
def getResult():
if m <= 1 or m >= 100:
return "ERROR!"
arr = [i for i in range(1, 101)]
return recursive(arr, m, 1)
# 算法调用
print(getResult())
解法三:双端队列
还有一种非常简单,非常好理解的做法就是双端队列。
双端队列本质也是维护一个循环结构。
即,
当报数===m时,则队头出队,报数重置为1,对应此时新对头。
当报数 !== m时,则队头加入队尾,报数++,对应此时新队头。
Java算法源码
import java.util.ArrayDeque;
import java.util.Scanner;
import java.util.StringJoiner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
System.out.println(getResult(m));
}
public static String getResult(int m) {
if (m <= 1 || m >= 100) return "ERROR!";
ArrayDeque<Integer> dq = new ArrayDeque<>();
for (int i = 1; i <= 100; i++) dq.add(i);
int idx = 1;
while (dq.size() >= m) {
if (idx == m) {
dq.removeFirst();
idx = 1;
} else {
dq.addLast(dq.removeFirst());
idx++;
}
}
StringJoiner sj = new StringJoiner(",");
dq.stream().sorted((a, b) -> a - b).forEach(v -> sj.add(v + ""));
return sj.toString();
}
}
JS算法源码
/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on("line", (line) => {
const m = parseInt(line);
if (m <= 1 || m >= 100) {
return console.log("ERROR!");
}
const dq = new Array(100).fill(0).map((_, idx) => idx + 1);
let idx = 1;
while (dq.length >= m) {
if (idx === m) {
dq.shift();
idx = 1;
} else {
dq.push(dq.shift());
idx++;
}
}
console.log(dq.sort((a, b) => a - b).join());
});
Python算法源码
# 输入获取
m = int(input())
# 算法入口
def getResult():
if m <= 1 or m >= 100:
return "ERROR!"
dq = [i for i in range(1, 101)]
idx = 1
while len(dq) >= m:
if idx == m:
dq.pop(0)
idx = 1
else:
dq.append(dq.pop(0))
idx += 1
dq.sort()
return ",".join(map(str, dq))
# 算法调用
print(getResult())
总结
上面三种解题思路,循环链表的性能是最优的,而基于数组filter的解法,会创建大量零时数组,造成内存浪费,基于双端队列的解法,本质也是基于数组,队头出队,对于数组来说,会导致整体数组元素前移一位,即每次队头出队,都有一次O(n)时间复杂度的元素位置调整,而同样的操作,对应于循环链表删除节点的操作, 只需要O(1)时间复杂度。
免责声明:
评论0