2020年2月24日找工作中金所的筆試題之一
題目描述
n個數字(1, 2, … , n)形成一個圓圈。從數字1開始,每次從這個圓圈中刪除第m個數字(第一個爲當前數字本身,第二個爲當前數字的下一個數字)。當一個數字刪除後,從被刪除數字的下一個繼續刪除第m個數字。求出在這個圓圈中剩下的最後一個數字。
思路
- 思路1 個人筆試時候的做法,把問題複雜化了感覺
(1) 用循環隊列,初始化隊列元素的值爲n個連續的數
(2) 循環報數,用累加器來輔助找到每次需要被刪除的元素
(3) 每次有人被淘汰,先把這個數刪除掉,隊列的長度減1
(4) 當循環隊列長度爲1的時候,輸出僅存的這個數
(5) 用數組模擬循環隊列 or 用鏈表模擬
思路2 後來網上查閱到的容易理解的做法
先構建遞推公式,再使用循環或者遞歸都能輕鬆求解。
這個問題的其實就是約瑟夫問題的小變形,數據範圍由約瑟夫問題的0到N-1,變爲了 1 到 N
由於我們要求解的是n個元素,第m個數字,要找到最後留下的一個數字,那麼我在這裏假設得到的結果是f(n,m),
根據“左加右減”的數學原理和環形的特徵,得到遞推公式:
{ 0 ,n = 1
f(n,m)= {
{ (f(n-1,m) + m)%n ,n > 1
根據這個來調整代碼,就可以得到數據範圍是1 到 N , 目標值是M的答案
代碼
思路1代碼
public class Joseph {
public int getResult(int n, int m) {
if (m == 1) { // m 是 1,直接返回n 就行了,不需要多跑程序
return n;
}
ListNode head = new ListNode(1);
ListNode p = head;
int counts = 1; // 創建用的計數器
while (counts < n) {
// 尾插法創建單鏈表
ListNode q = new ListNode(counts + 1);
q.next = p.next;
p.next = q;
p = p.next;
counts ++;
}
// 使單鏈表變爲循環鏈表
p.next = head;
int count = 0; // 刪除用的計數器
ListNode del = head;
while (del.next != del) { // 使其一直遍歷、計數、刪除
count++;
System.out.println(del.val + " " + count);
if (count == m) {
// 刪除這個結點
del.val = del.next.val;
del.next = del.next.next;
count = 1; // 計數器清零
}
del = del.next;
}
return del.val;
}
}
思路2代碼
public class Jpseph2 {
public int getResult(int n, int m) {
int fn = 1;
for (int i = 2; i <= n; i++) {
fn = (fn + m - 1) % i;
}
return fn;
}
}