作为最近的工作申请的一部分,我被要求为此问题编写解决方案。
鉴于,
每个人都有一个唯一的(递增的)id。从第一个人(最低身份证)开始,他们从1到k开始计数。
然后移除k处的人并且圆圈关闭。下一个剩余的人(在被淘汰的人之后)恢复计数为1.这个过程重复,直到只剩下一个人为胜者。
解决方案必须提供:
性能限制:
我记得多年前在我的CS课程中做过类似的事情,但在这次测试时无法回忆起细节。我现在意识到这是一个众所周知的经典问题,有多种解决方案。 (我不会提到它的名字,因为有些人可能只是'维基百科'的答案)。
我已经提交了我的解决方案,所以我绝对不会找人为我解答。如果其他人提供了一些答案,我会稍后提供一次。
我提出这个问题的主要目的是看看我的解决方案在满足要求和约束条件下与其他解决方案的比较。
(请仔细注意要求,因为我认为它们可能会使某些“经典”解决方案失效。)
答案 0 :(得分:11)
Manuel Gonzalez 正确地注意到这是着名的Josephus problem的一般形式。
如果我们只对大小为N的圆的幸存者f(N,K)和大小为K的跳跃感兴趣,那么我们可以通过一个非常简单的动态编程循环(线性时间和常数存储器)来解决这个问题。请注意,ID从0开始:
int remaining(int n, int k) {
int r = 0;
for (int i = 2; i <= n; i++)
r = (r + k) % i;
return r;
}
它基于以下递归关系:
f(N,K)=(f(N-1,K)+ K)mod N
这种关系可以通过模拟消除过程来解释,并且在每次消除之后重新分配从0开始的新id。旧的索引是具有k个位置的循环移位的新索引。有关此公式的更详细说明,请参阅http://blue.butler.edu/~phenders/InRoads/MathCounts8.pdf。
我知道OP要求所有按正确顺序排除已删除项目的索引。但是,我相信上述见解也可用于解决这个问题。
答案 1 :(得分:2)
您可以使用boolean
数组执行此操作。
这是一个伪代码:
让alive
成为boolean
大小为N
的数组。如果alive[i]
为true
,那么ith
人还活着就死了。最初true
每个1>=i<=N
为numAlive
让numAlive = N
成为活着的人数。所以i = 1 # Counting starts from 1st person.
count = 0;
# keep looping till we've more than 1 persons.
while numAlive > 1 do
if alive[i]
count++
end-if
# time to kill ?
if count == K
print Person i killed
numAlive --
alive[i] = false
count = 0
end-if
i = (i%N)+1 # Counting starts from next person.
end-while
# Find the only alive person who is the winner.
while alive[i] != true do
i = (i%N)+1
end-while
print Person i is the winner
一开始。
{{1}}
上述解决方案节省空间,但由于正在检查死者,因此没有时间效率。
为了提高时间效率,您可以使用 循环链接列表 。每次杀死一个人时,都会从列表中删除一个节点。您将继续,直到列表中保留单个节点。
答案 2 :(得分:2)
确定'第k'人的问题被称为约瑟夫斯问题。 Ferdowsi Mashhad大学的Armin Shams-Baragh发表了约瑟夫斯问题的一些公式及其扩展版本。 该文件位于:http://www.cs.man.ac.uk/~shamsbaa/Josephus.pdf
答案 3 :(得分:1)
这是我的解决方案,用C#编码。有什么可以改进的?
public class Person
{
public Person(int n)
{
Number = n;
}
public int Number { get; private set; }
}
static void Main(string[] args)
{
int n = 10; int k = 4;
var circle = new List<Person>();
for (int i = 1; i <= n; i++)
{
circle.Add(new Person(i));
}
var index = 0;
while (circle.Count > 1)
{
index = (index + k - 1) % circle.Count;
var person = circle[index];
circle.RemoveAt(index);
Console.WriteLine("Removed {0}", person.Number);
}
Console.ReadLine();
}
Console.WriteLine("Winner is {0}", circle[0].Number);
答案 4 :(得分:1)
与Ash的答案基本相同,但有一个自定义链表:
using System;
using System.Linq;
namespace Circle
{
class Program
{
static void Main(string[] args)
{
Circle(20, 3);
}
static void Circle(int k, int n)
{
// circle is a linked list representing the circle.
// Each element contains the index of the next member
// of the circle.
int[] circle = Enumerable.Range(1, k).ToArray();
circle[k - 1] = 0; // Member 0 follows member k-1
int prev = -1; // Used for tracking the previous member so we can delete a member from the list
int curr = 0; // The member we're currently inspecting
for (int i = 0; i < k; i++) // There are k members to remove from the circle
{
// Skip over n members
for (int j = 0; j < n; j++)
{
prev = curr;
curr = circle[curr];
}
Console.WriteLine(curr);
circle[prev] = circle[curr]; // Delete the nth member
curr = prev; // Start counting again from the previous member
}
}
}
}
答案 5 :(得分:1)
以下是Clojure中的解决方案:
(ns kthperson.core
(:use clojure.set))
(defn get-winner-and-losers [number-of-people hops]
(loop [people (range 1 (inc number-of-people))
losers []
last-scan-start-index (dec hops)]
(if (= 1 (count people))
{:winner (first people) :losers losers}
(let [people-to-filter (subvec (vec people) last-scan-start-index)
additional-losers (take-nth hops people-to-filter)
remaining-people (difference (set people)
(set additional-losers))
new-losers (concat losers additional-losers)
index-of-last-removed-person (* hops (count additional-losers))]
(recur remaining-people
new-losers
(mod last-scan-start-index (count people-to-filter)))))))
说明:
开始一个循环,有一群人1..n
如果只剩下一个人,他们就是赢家,我们会返回他们的身份证,以及输家的身份证明(按他们失败的顺序)
我们通过抓住剩下的潜在获胜者名单中的每N个人来计算每个循环中的额外输家
通过从先前计算的潜在获胜者中移除其他输家来确定新的,更短的潜在获胜者名单。
冲洗&amp;重复(使用模数确定下一轮开始计算的剩余人员列表中的位置)
答案 6 :(得分:1)
这是 Josephus problem 的变体。
描述了一般解决方案here。
提供了Perl,Ruby和Python中的解决方案here。下面提供了使用circular doubly-linked list表示人群环的C语言中的简单解决方案。然而,这些解决方案都没有确定每个人被移除时的位置。
#include <stdio.h>
#include <stdlib.h>
/* remove every k-th soldier from a circle of n */
#define n 40
#define k 3
struct man {
int pos;
struct man *next;
struct man *prev;
};
int main(int argc, char *argv[])
{
/* initialize the circle of n soldiers */
struct man *head = (struct man *) malloc(sizeof(struct man));
struct man *curr;
int i;
curr = head;
for (i = 1; i < n; ++i) {
curr->pos = i;
curr->next = (struct man *) malloc(sizeof(struct man));
curr->next->prev = curr;
curr = curr->next;
}
curr->pos = n;
curr->next = head;
curr->next->prev = curr;
/* remove every k-th */
while (curr->next != curr) {
for (i = 0; i < k; ++i) {
curr = curr->next;
}
curr->prev->next = curr->next;
curr->next->prev = curr->prev;
}
/* announce last person standing */
printf("Last person standing: #%d.\n", curr->pos);
return 0;
}
答案 7 :(得分:0)
这是我提交的C#答案。随意批评,嘲笑,嘲笑等;)
public static IEnumerable<int> Move(int n, int k)
{
// Use an Iterator block to 'yield return' one item at a time.
int children = n;
int childrenToSkip = k - 1;
LinkedList<int> linkedList = new LinkedList<int>();
// Set up the linked list with children IDs
for (int i = 0; i < children; i++)
{
linkedList.AddLast(i);
}
LinkedListNode<int> currentNode = linkedList.First;
while (true)
{
// Skip over children by traversing forward
for (int skipped = 0; skipped < childrenToSkip; skipped++)
{
currentNode = currentNode.Next;
if (currentNode == null) currentNode = linkedList.First;
}
// Store the next node of the node to be removed.
LinkedListNode<int> nextNode = currentNode.Next;
// Return ID of the removed child to caller
yield return currentNode.Value;
linkedList.Remove(currentNode);
// Start again from the next node
currentNode = nextNode;
if (currentNode== null) currentNode = linkedList.First;
// Only one node left, the winner
if (linkedList.Count == 1) break;
}
// Finally return the ID of the winner
yield return currentNode.Value;
}