【算法】約瑟夫環問題求解

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


"如果你不能簡單地解釋某一概念,說明你沒有很好地掌握它。"——艾爾伯特·愛因斯坦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章