約瑟夫環問題【劍指offer-45】

約瑟夫環問題的原來描述爲,設有編號爲1,2,……,n的n(n>0)我的圍成一個圈,從第1我的開始報數,報到m時中止報數,報m的人出圈,再從他的下一我的起從新報數,報到m時中止報數,報m的出圈,……,如此下去,直到全部人所有出圈爲止。當任意給定n和m後,設計算法求n我的出圈的次序。  稍微簡化一下。 算法

        問題描述:n我的(編號0~(n-1)),從0開始報數,報到(m-1)的退出,剩下的人繼續從0開始報數。求勝利者的編號。  spa

思路:容易想到的就是用環鏈表來作,構建一個環鏈表,每一個結點的編號爲0, 1, ...... n-1。每次從當前位置向前移動m-1步,而後刪除這個結點。最後剩下的結點就是勝利者。給出兩種方法實現,一種是自定義鏈表操做,另外一種用是STL庫的單鏈表。不難發現,用STL庫能夠提升編寫速度設計

struct ListNode
{
int num;//編號
ListNode *next;
};
//自定義鏈表實現
int JosephusProblem_Solution1(int n, int m)
{
if(n < 1 || m <1)return -1;
ListNode *pHead = new ListNode();//頭結點
ListNode *pCurrentNode = pHead;//當前結點
ListNode *pLastNode = NULL;//前一個結點
unsigned i;
//構造環鏈表
for(i = 1; i<n ; i++)
{ 
pCurrentNode->next = new ListNode(i);
pCurrentNode = pCurrentNode->next;
}
pCurrentNode->next = pHead;

//循環遍歷
pLastNode = pCurrentNode;
pCurrentNode = pHead;

while(pCurrentNode->next != pCurrentNode)
{
//前進m-1步
for(i = 0; i < m-1; i++)
{
pLastNode = pCurrentNode;
pCurrentNode->next = pLastNode;
}
//刪除報m-1的數
pLastNode->next = pCurrentNode->next;
delete pCurrentNode;
pCurrentNode = pLastNode->next;
}
//釋放空間
int result = pCurrentNode->num;
delete pCurrentNode;

return result;
}
//使用標準庫STL
int JosephusProblem_Solution2(int n,int m)
{
if(n<1||m<1)return -1;
list<int> listInt;
unsigned i;
for(i=0;i<n;i++)
listInt.push_back(i);
list<int>::iterator iterCurrent = listInt.begin();
while(listInt.size()>1)
{
//前進m-1步
for(i=0;i<m-1;i++)
{
if(++iterCurrent == listInt.end())
iterCurrent = listInt.begin();
}
//臨時保存刪除的結點
list<int>::iterator iterDel = iterCurrent;
if(++iterCurrent == listInt.end())
   iterCurrent = listInt.begin();
   //刪除結點
   listInt.erase(iterDel);
}
return *iterCurrent;
}
上述方法的效率很低,其時間複雜度爲O(mn)。當m和n很大時,很難在短期內得出結果。不過好處就是能夠給出n我的出圈的次序。

下面利用數學推導,若是能得出一個通式,就能夠利用遞歸、循環等手段解決。下面給出推導的過程: code

(1)第一個被刪除的數爲(m-1)%n; 遞歸

(2)第二論的開始數字爲k,那麼這n-1個數構成的約瑟夫環爲k,k+1,k+2,...k-3,k-2作一個簡單映射。 數學

(p(x)=(x-k)%n) it

k--->0 io

k+1--->1 ast

k+2--->2 class

---

---

k-2--->n-2

這是一個n-1我的的問題,若是能從n-1我的的問題的解退出n我的問題的解,從而獲得一個遞推公式,那麼問題就解決了。假如咱們已經知道了n-1我的時,最後勝利者的編號爲x,利用映射關係逆推,就能夠得出n我的時,勝利者的編號爲(x+k)%n。其中k=m%n。代入(x+k)%n<=>(x+(m%n))%n<=>(x%n + (m%n)%n)%n<=> (x%n+m%n)%n <=> (x+m)%n

(3)第二個被刪除的數爲(m-1)%n-1

(4)假設第三輪的開始數字爲o,那這n-2個數構成的約瑟夫環爲o,o+1,o+2,...,o-3,o-2。繼續作映射

p(x)=(y-o)%(n-2)

o--->0

o+1--->1

o+2--->2

---

---

o-2--->n-3

這是一個n-2我的的問題。假設最後勝利者爲y,那麼n-1我的時,勝利者爲(y+o)%(n-1),其中o等於m%(n-1)。代入可得(y+m)%(n-1)

要獲得n-1我的問題的解,只須要獲得n-2我的問題的解,倒退下去。只有一我的時,勝利者就是編號0.小面給出遞推式:

f(1)=0;

f(i)=(f[i-1]+m)%i;(i>1)

有了遞推公式,實現就很是簡單了,給出循環的兩種實現。再次代表使用標準庫的便捷性。

int JosephusProblem_Solution3(int n, int m)
{
  if(n<1||m<1)return -1;
  int *f = new int[n+1];
  f[0]=f[1]=0;
  for(unsigned i=2;i<n;i++)
    f[i]=(f[i-1]+m)%i;//按遞推公式進行計算

  int return = f[n];
  delete []f;
  return result;
}
int JosephusProblem_Solution4(int n, int m)
{
  if(n<1||m<1)return -1;
  vector<int> f(n+1,0);
  for(unsigned i = 2;i<=n;i++)
     f[i]=(f[i-1]+m)%i;
  return f[n];
}
相關文章
相關標籤/搜索