文章目錄
- 問題描述
- 思路
- 代碼實現
問題描述
有 1~N
個數字,從 1~m
依次報數,數到 m
的數字要被刪掉,求最后剩下的數字是?
思路
第一次報數 | 第二次報數 |
---|---|
1 | n-m+1 |
2 | n-m+2 |
… | … |
m-2 | n-2 |
m-1 | n-1 |
m | 被刪掉了 |
m+1 | 1 |
m+2 | 2 |
… | … |
n-1 | n-1-m |
n | n-m |
通過上面的表格,我們可以發現這樣的規律:
將某數字第一次報數設為 first
,第二次報數設為 second
。那么存在這樣的關系:first=(second+m?1)%n+1first = (second + m - 1) \% n + 1first=(second+m?1)%n+1(公式一)
為什么不是 first=(second+m)%nfirst = (second + m) \% nfirst=(second+m)%n (公式二)呢?其實一開始我確實總結出來的是公式二,但是發現有個漏洞,數字編號是從 1
開始的,而公式二的編號是從 0
開始的,具體來說,就是當 second = n-m
時,first = 0
。可以看到并不符合實際,first=n
才對。換言之,也就是如果我們從 0
開始計數,那么公式二是可用的,如何從 0
開始計數呢? 答案就是把數字序列存到數組里嘛~
因此,將公式二可以進化為 first=(second+m)%n+1first = (second + m) \% n + 1first=(second+m)%n+1(公式三),但是簡單的為公式二的結果 +1
,就導致在公式二中,本來只有 second = n-m
結果不符合實際,而在公式三中,變成了只有 second = n-m
的結果符合實際,原本沒問題的都變得有問題了……這是因為我們沒有做到加減均衡,只有 +1
,而沒有 -1
。因此公式一應運而生~
那么我們可以得到這樣的規律:
當n>1時,f(n)=(f(n?1)+m?1)%n+1當 n>1 時,f(n) = (f(n-1) + m - 1) \% n + 1當n>1時,f(n)=(f(n?1)+m?1)%n+1
當n=1時,f(1)=1當 n=1 時,f(1) = 1當n=1時,f(1)=1
解釋一下就是,剩最后一個數的時候直接返回最后一次報數為 1
的數,反之則需要繼續刪除一個數。
而當 n-1=1
時,f(n)
也就意味著,最后一次報數為 1 的數,倒數第二次報數的時候它報的是幾。
那么對于 f(n-1)
的調用也就意味著,本次報數為 n 的數,下一次報數為幾。
說到這里已經很清楚了,顯而易見的遞歸思想。
代碼實現
int m;
int yuesefu(int n){if(n == 1) return 1; // 最后一次報數為 1,開始回溯return (yuesefu(n-1) + m - 1) % n + 1; // f(n-1)==1的時候開始回溯,求最后一次報數為 1 的數,第一次報數為幾?
}// 還可以再簡化
int m;
int yuesefu(int n){return n == 1 ? 1 : (yuesefu(n-1) + m - 1) % n + 1;
}// 如果將數字序列存入數組中,則可用公式二
int m;
int yuesefu(int n){return n == 0 ? 0 : (yuesefu(n-1) + m) % n;
}