本文參考自《劍指offer》一書,代碼採用Java語言。html
更多:《劍指Offer》Java實現合集 java
0, 1, …, n-1這n個數字排成一個圓圈,從數字0開始每次從這個圓圈裏刪除第m個數字。求出這個圓圈裏剩下的最後一個數字。數組
方法一:採用鏈表來存放數據,每次對長度取餘來實現循環post
將全部數字放入LinkedList鏈表中(LinkedList比ArrayList更適合增刪操做)。假設當前刪除的結點下標爲removeIndex,則下一個要刪除的結點的下標爲:(removeIndex+m-1)%list.size(),經過取餘符號能夠實現類型循環的操做。性能
注:不必用循環鏈表,反而會更麻煩了。測試
方法二:數學推導規律url
n個數字的圓圈,不斷刪除第m個數字,咱們把最後剩下的數字記爲f(n,m)。code
n個數字中第一個被刪除的數字是(m-1)%n, 咱們記做k,k=(m-1)%n。htm
那麼剩下的n-1個數字就變成了:0,1,……k-1,k+1,……,n-1,咱們把下一輪第一個數字排在最前面,而且將這個長度爲n-1的數組映射到0~n-2。blog
原始數字:k+1,……, n-1, 0, 1,……k-1
映射數字:0 ,……,n-k-2, n-k-1, n-k,……n-2
把映射數字記爲x,原始數字記爲y,那麼映射數字變回原始數字的公式爲 y=(x+k+1)%n。
在映射數字中,n-1個數字,不斷刪除第m個數字,由定義能夠知道,最後剩下的數字爲f(n-1,m)。咱們把它變回原始數字,由上一個公式能夠獲得最後剩下的原始數字是(f(n-1,m)+k+1)%n,而這個數字就是也就是一開始咱們標記爲的f(n,m),因此能夠推得遞歸公式以下:
f(n,m) =(f(n-1,m)+k+1)%n
將k=(m-1)%n代入,化簡獲得:
f(n,m) =(f(n-1,m)+m)%n
f(1,m) = 0
代碼中能夠採用循環或者遞歸的方法實現該遞歸公式。時間複雜度爲O(n),空間複雜度爲O(1)。
測試算例
1.功能測試(m大於/小於/等於n)
2.特殊測試(n、m<=0)
3.性能測試(n=4000,n=997)
//題目:0, 1, …, n-1這n個數字排成一個圓圈,從數字0開始每次從這個圓圈裏 //刪除第m個數字。求出這個圓圈裏剩下的最後一個數字。 public class LastNumberInCircle { /* * 方法一:採用推導出來的方法 */ public int LastRemaining_Solution(int n, int m) { if(n<1 || m<1) return -1; //出錯 int last=0; for(int i=2;i<=n;i++){ last=(last+m)% i; //這裏是i不是n!!! } return last; } /* * 方法二:採用鏈表來存放,每次對長度取餘來實現循環 */ public int LastRemaining_Solution2(int n, int m) { if(n<1 || m<1) return -1; //出錯 LinkedList<Integer> list = new LinkedList<Integer>(); for(int i=0;i<n;i++) list.add(i); int removeIndex=0; while(list.size()>1){ removeIndex=(removeIndex+m-1)%list.size(); list.remove(removeIndex); } return list.getFirst(); } }
1.對於下標循環一圈相似的問題,經過%能夠很好地實現循環,而不須要咱們本身構造循環鏈表;
2.(a%n+b)%n=(a+b)%n
3.儘可能學會本題的數學方法,特別是要掌握好數字間映射的方法。
4.公式法中,last=(last+m)% i;
//這裏是i不是n!!!