0.約瑟夫環問題描述
已知n個人(編號1,2,3,…,n表示)圍坐在一張圓桌周圍。從編號爲1的人開始報數,數到k的人出列;與他相鄰的下一個人又從1開始報數,數到k的人又出列;依此規律重複,直到所有人出列,求最後一個出列的人。
1.模擬數組解法
思路:將所有元素標識初始化爲0,每次將報到k的值置爲1,下一輪不再加入計數。
使用sum計數,每次移除一個,sum加一,直到sum爲n-1時退出循環。
// 模擬數組解法
int josephus_solver(int a[], int size, int k)
{
// 數組無效時,返回-1作爲提示,因爲數組中的元素均爲正數
if (a==NULL || size<=0)
return -1;
// i爲數組索引
int i = 0;
// 用一個removed數組作爲每個元素是否被移除的標識,1表示移除
int *removed = (int *)malloc(sizeof(int)*size);
memset(removed, 0, sizeof(int)*size);
int j = 0; // 用來模擬報數
int sum = 0; // 統計被移除的元素數量,當只剩一個元素時終止
i = 0;
while (sum<size-1)
{
while (removed[i]==1) i++;
if (i==size) i = 0;
// 報數報到k並且當前的元素尚未被移除,則將其移除
if (removed[i]!=1 && j%k==0) {
printf("remove a[%d]=%d\n", i, a[i]);
removed[i] = 1;
sum++;
}
if (++j==k+1) j = 1;
if (++i==size) i = 0;
}
for (i=0; i<size; i++)
if (removed[i])
break;
// 釋放內存資源
free(removed);
// 指向NULL,防止成爲野指針
removed = NULL;
return a[i];
}
2.遞推求解
詳細遞推過程如下
已知隊裏有n個人,編號爲: 0, 1, 2, ..., n-1
進行一輪報數,報到k的人出隊,第一次出隊的人編號必爲(k-1)%n
這時我們可以把隊伍編號記爲: 0, 1, 2, ..., k-2, k-1, k, ..., n-1
將k-1標記爲X,表示已經出隊。 0, 1, 2, ..., k-2, X , k, ..., n-1
重新編號讓k爲0,則有: n-k, n-k+1, n-k+2, ..., n-2, (X), 0, 1, ..., n-1-k
此時的編號與前一輪之間的關係不難看出來,記前一輪爲index[n],表示前一輪中共有n個人,編號爲0~(n-1)
當前的編號記爲index[n-1],因爲相比於前一輪被移除一個,並從k重新編號0~(n-2)
則編號之間的對應關係:index[n]=(index[n-1]+k)%n
我們可以一直推下去:有n-2人,有n-3人, ..., 有1人。
這樣就得到了遞推式:index[n] = (index[n-1]+k)%n
當只有1人時:index[1] = 0
前一輪的編號=(新一輪的編號+k)%前一輪的人數
也可以理解爲:新一輪編號向前移動k位再通過取模形成環即可得到前一輪的編號。
我們很容易知道最後一個出隊的人的編號一定是0,因爲此時只剩下他一個人。再通過上述的遞推關係式,我們就能夠得到他對應的第一輪的編號。
// 遞推求解
// 時間複雜度爲O(n),空間複雜度爲O(1),只能求得最後出局的人。
int josephus_solver_recursive(int a[], int size, int k)
{
// 數組無效時,返回-1作爲提示,因爲數組中的元素均爲正數
if (a==NULL || size<=0)
return -1;
// 最後一個出隊的人編號一定是0,因爲此時只剩下他一個人
int index = 0; // 當只有1人時編號爲0的人最後出列
for (int i=2; i<=size; i++) {
index = (index+k)%i;
}
return a[index];
}
3.鏈表解法
類似於數組法,不過不適用輔助標誌數組,而是直接使用鏈表來表示剩餘的人,只剩一個時即是最後的結果。
// 鏈表解法
#include <list>
int josephus_solver_list(int n, int k)
{
std::list<int> ltmp;
for(int i=1; i<=n; ++i)
ltmp.push_back(i);
auto pos = ltmp.begin();
while(ltmp.size() > 1) {
for(int i=1; i<k; ++i) {
++pos;
if(pos==ltmp.end())
pos = ltmp.begin();
}
pos = ltmp.erase(pos);
if(pos==ltmp.end())
pos = ltmp.begin();
}
return ltmp.front();
}
4.LeetCode題目
LeetCode上的約瑟夫環問題 1823-findTheWinner
"如果你不能簡單地解釋某一概念,說明你沒有很好地掌握它。"——艾爾伯特·愛因斯坦