有一个数组,每次从中间随机取一个,然后放回去,当所有的元素都被取过,返回总共的取的次数。写一个函数实现。复杂度是什么。


import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

/**
 * http://weibo.com/1915548291/z7HtOF4sx
 * #面试题#有一个数组,每次从中间随机取一个,然后放回去,当所有的元素都被取过,返回总共的取的次数。
 * 写一个函数实现。复杂度是什么。
 * 
 * 我认为这道题的考点是如何节省空间。
 * 当然可以用一个与原数组长度相同的数组来标记对应元素是否被取过,但这样太浪费空间
 * 用bitmap的话,只要一个bit就可以标记是否被取过,可参考《编程珠玑》的位图排序
 * 
 * 时间复杂度的话,我不太会算,以下是引用https://github.com/vyan/test/blob/master/accessTimes.cpp:
 * 使用bit打点记录已经取的数, 
 * 复杂度分析,假设数组总长度为n 
 * 取到第1个之前未被取到的数的期望 E(1)=1 
 * 取到第2个之前未被取到的数的期望 E(2)=n/n-1 
 * 取到第3个之前未被取到的数的期望 E(3)=n/n-2 
 * ...
 * 取到第n个之前未被取到的数的期望 E(n)=n/1 
 * 总得期望次数E=n+n/(n-1)+n/(n-2)+...+n/1;
 *              =n(1+1/(n-1)+1/(n-2)+...+1/1) 
 *              =nln(n) 
 * 所以算法平均复杂度为nlogn 
 * 
 * 下面的代码里面,除法和求模运算我都用位运算来实现(为了练习位运算),事实上直接用java提供的(/,%)也可以
 * 同时,为了验证是否真的取到了数组的所有元素,我用了TreeSet保存已选中的下标(去重)
 * 
 * 最后,还有一个问题是,可能取了很多次,都没能全选中,这个时候可以设置一个最长时间或者最大尝试次数,超过则结束程序,
 * 避免程序长时间运行甚至死循环
 * 
 * @author lijinnan
 * 
 */
public class TimesOfAccessArray {

    public static final int SHIFT = 5;
    public static final int BLOCK_SIZE = (1 << SHIFT);  //32
    public static final int MASK = (1 << SHIFT) - 1;  //31

    public static void main(String[] args) {
        int[] array = new int[200];
        long times = accessTimes(array);
        System.out.println(times);
    }
    
    public static long accessTimes(int[] array) {
        if (array == null || array.length == 0) {
            return -1L;
        }
        long result = 0L;
        int len = array.length;
        int[] bitmap = new int[divide(len, BLOCK_SIZE) + 1];
        int setTimes = 0;
        Set<Integer> set = new TreeSet<Integer>();
        while (setTimes < len) {
            int pos = new Random().nextInt(len);
            set.add(pos);
            if (set(bitmap, pos)) {
                setTimes++;
            }
            result++;
        }
        System.out.println(set);
        return result;
    }
    
    public static boolean set(int[] bitmap, int pos) {
        boolean result = false;
        int blockNo = divide(pos, BLOCK_SIZE);
        int index = mod(pos, BLOCK_SIZE);
        boolean notExist = (bitmap[blockNo] & (1 << index))== 0;
        if (notExist) {
            bitmap[blockNo] |= (1 << index);
            result = true;
        }
        return result;
    }

    public static boolean exist(int[] bitmap, int pos) {
        int blockNo = divide(pos, BLOCK_SIZE);
        int index = mod(pos, BLOCK_SIZE);
        return (bitmap[blockNo] & (1 << index)) != 0;
    }
    
    private static int divide(int x, int y) {
        return x >> offSet(y);
    }

    private static int mod(int x, int y) {
        int z = x;
        int i = offSet(y);
        return z - ((z >> i) << i);
    }
    
    //e.g. 32=2^5, return 5 只考虑2的幂
    private static int offSet(int y) {
        return howManyBits(y) - 1;
    }

    //二进制的表示里面,有多少位。例如32是6位
    private static int howManyBits(int y) {
        int bitNum = 0;
        while (y != 0) {
            y = (y >> 1);
            bitNum++;
        }
        return bitNum;
    }
   

}

你可能感兴趣的:(java,算法,面试)