劍指Offer(Java版):圓圈中最後剩下的數字

題目:0,1,,,,,n-1這n 個數字排成一個圓圈,從數字0開始每次從這個圓圈中刪除第m個數字。求出這個圓圈裏剩下的最後一個數字。java

例如,0,1,2,3,4這5個數字組成的一個圓圈,從數字0開始每次刪除第3個數字,則刪除的前四個數字依次是2,0,4,1所以最後剩下的數字是3.算法

本題就是著名的約瑟夫環的問題。咱們介紹兩種方法:一種方法是用環形鏈表模擬圓圈的經典解法,第二種方法是分析每次被刪除的數字的規律並直接計算出圓圈中最後剩下的數字。數據結構

 

經典的解法,用環形鏈表模擬圓圈:函數

既然題目中有一個數字圓圈,很天然的想法就是用個數據結構來模擬這個圓圈。在經常使用的數據結構中,咱們很容易的想到環形鏈表。咱們能夠建立一個共有n個結點的環形鏈表,而後每次都從這個鏈表中刪除第m個結點。spa

咱們發現使用環形鏈表裏重複遍歷不少遍。重複遍歷固然對時間效率有負 面的影響。這種方法每刪除一個數字須要m步運算,總共有n個數字,所以總的時間複雜度爲O(mn)。同時這種思路還須要一個輔助的鏈表來模擬圓圈,其空間 複雜度O(n)。接下來咱們試着找到每次被刪除的數字有哪些規律,但願可以找到更加高效的算法。遞歸

 

package cglib;索引

import java.util.ArrayList;
import java.util.List;rem

public class jiekou {get

    public void lastRemaining(int totalNum,int index){  
        //初始化人數  
        List<Integer> start = new ArrayList<Integer>();  
        for(int i = 0;i< totalNum;i++){  
            start.add(i);  
        }  
        //從第k個開始計數  
        int k = 0;  
        while(start.size() > 0){  //0,1,2,3,4;0,1,3,4;1,3,4;1,3;3
            k = k+ index;  // 3,要刪除元素的位置
            //第m我的的索引位置  
            k = k %(start.size()) -1;  //3%5-1=3-1=2
            //判斷是否到隊尾  
            if(k < 0){  
                System.out.println(start.get(start.size()-1));  
                start.remove(start.size()-1);  
                k = 0;  
            }else{  
                System.out.println(start.get(k));  
                start.remove(k);  
            }  
        }  
    }  
    public static void main(String[] args){  
        jiekou test = new jiekou();  
        test.lastRemaining(5, 3);  
    }  
    }ast

 

輸出:

2
0
4
1
3

 

 

創新的解法,拿到Offer不在話下:

首先咱們定義一個關於n和m的方程f(n,m),表示每次在n個數字0,1,。。。n-1中每次刪除第m個數字最後剩下的數字。

在這n個數字中,第一個被刪除的數字是(m-1)%n.爲了簡單起 見,咱們把(m-1)%n記爲k,那麼刪除k以後剩下的n-1個數字爲0,1,。。。。k-1,k+1,.....n-1。而且下一次刪除從數字 k+1,......n-1,0,1,....k-1。該序列最後剩下的數字也應該是關於n和m的函數。因爲這個序列的規律和前面最初的序列不同(最初 的序列是從0開始的連續序列),所以該函數不一樣於前面的函數,即爲f'(n-1,m)。最初序列最後剩下的數字f(n,m)必定是刪除一個數字以後的序列 最後剩下的數字,即f(n,m)=f'(n-1,m).

接下來我麼把剩下的這n-1個數字的序列k+1,....n-1,0,1,,,,,,,k-1作一個映射,映射的結果是造成一個從0到n-2的序列

k+1        ------>     0

k+2      --------->    1

。。。。

n-1      -----    > n-k-2

0         ------->    n-k-1

1       --------->    n-k

.....

k-1   --------->    n-k

咱們把映射定義爲p,則p(x) = (x-k-1)%n。它表示若是映射前的數字是x,那麼映射後的數字是(x-k-1)%n.該映射的逆映射是p-1(x)= (x+k+1)%n.

因爲映射以後的序列和最初的序列具備一樣的形式,即都是從0開始的連 續序列,所以仍然能夠用函數f來表示,記爲f(n-1,m).根據咱們的映射規則,映射以前的序列中最後剩下的數字f'(n-1,m) = p-1[(n-1,m)] = [f(n-1,m)+k+1]%n ,把k= (m-1)%n代入f(n,m) = f'(n-1,m) =[f(n-1,m)+m]%n.

通過上面的複雜的分析,咱們終於找到一個遞歸的公示。要獲得n個數字的序列中最後剩下的數字,只須要獲得n-1個數字的序列和最後剩下的數字,並以此類推。當n-1時,也就是序列中開始只有一個數字0,那麼很顯然最後剩下的數字就是0.咱們把這種關係表示爲:

用代碼實現以下所示:

package cglib;


public class jiekou {

    public static void main(String[] args) {
        jiekou p=new jiekou();
        System.out.println(p.lastRemaining(3, 2));//0,1,2;0,2;2
        }
        public int lastRemaining(int n,int m){
        if(n<1||m<1)
        return -1;
        int fn=0;
        for(int i=2;i<=n;i++){
            fn=(fn+m)%i;
        }
        return fn;  
        }
    }


輸出:2

 

這種算法的時間複雜度爲O(n),空間複雜度爲O(1)。所以不管在時間效率和空間效率上都優於第一個方法。

相關文章
相關標籤/搜索