346. Moving Average from Data Stream

Google电面题,15min,bug-free。考虑优化时间复杂度可以过关,考虑滚动数组会加分。

最直接的想法自然是弄一个list或者array,每次来一个数就放进去一个,然后再计算window size内的所有数的平均值。

这样做的时间复杂度是O(n * size),如果我们利用前缀和的话,可以把时间复杂度降低至O(n),当然这题最低也就是O(n)了,因为每一个进来的数你至少要读一次。

前缀和顾名思义就是前面总共的和,sum[i] = d[1] + d[2] + d[3] + ... + d[i]

如果window size是 x 加到 y 的话,我们有d[x] + ... + d[y] = sum[y] - sum[x - 1]。

所以说如果我们维持一个前缀和,就可以在O(1)的时间内计算出我们要的平均值。如下面的代码。

public class MovingAverage {

    double[] sum;
    int size;
    int count;
    /** Initialize your data structure here. */
    public MovingAverage(int size) {
        sum = new double[1000000];
        count = 0;
        this.size = size;
    }

    public double next(int val) {
        count++;
        sum[count] = sum[count - 1] + val;
        if (count <= size) return sum[count] / count;
        else return (sum[count] - sum[count - size]) / size;
    }
}

至此我们降低了时间复杂度的问题,那么空间复杂度怎么办?上面的代码中关于前缀和数组的声明看起来也十分的丑,因为我们不知道到底要读几个数,所以要准备一个尽量大的数组,上面是一百万个元素空间。但是这始终不是一个好的解决方案,更何况很占用空间。

其实在计算前缀和的时候就应该能够注意到,我们在计算后面的平均值的时候,前面用过的前缀和就失去价值了。所以如果 window 大小是 size 的话,其实我们一共只需要 size + 1 个元素空间就够了。比如说我们计算大小为 3 的 window 的平均值,每次算的时候:

sum(3) - sum(0)
sum(4) - sum(1)
sum(5) - sum(2)
sum(6) - sum(3)
sum(7) - sum(4)

我们只用到了 4 个元素,当sum(0)空出来之后,可以把sum(4)放到sum(0)的位置去。只需要对下标取模就可以做到。

这就是滚动数组。

别忘了计算结果是double,不是int。

public class MovingAverage {

    double[] sum;
    int count;

    public MovingAverage(int size) {
        sum = new double[size + 1];
        count = 0;
    }

    public double next(int val) {
        count++;
        sum[count % sum.length] = sum[(count - 1) % sum.length] + val;
        if (count < sum.length) return sum[count] / count;
        else return (sum[count % sum.length] - sum[(count - sum.length + 1) % sum.length]) 
                    / (sum.length - 1);
    }
}

看起来有点乱,对于每一个涉及到的下标都进行size + 1的取模。两点注意:

  • 写完主体程序没问题了之后再加滚动数组,上来直接写滚动数组容易乱

  • 滚动数组如果没有把握的话,就开大一点,size + 2甚至size + 10,这是保证不会出错的方法

这道题的答案是用queue做的,但是实际上queue的底层也是用这种滚动数组的方法实现的。

public class MovingAverage {

    int size;
    Queue<Integer> queue;
    double sum;
    /** Initialize your data structure here. */
    public MovingAverage(int size) {
        queue = new LinkedList<>();
        this.size = size;
        sum = 0;
    }

    public double next(int val) {
        sum += val;
        if (queue.size() == size) {
            sum = sum - queue.poll();
        }
        queue.offer(val);
        return sum / queue.size();  
    }
}

170. Two Sum III - Data structure design

这题的testcase有点恶心人。

class TwoSum {

    Map<Integer, Integer> map;

    /** Initialize your data structure here. */
    public TwoSum() {
        map = new HashMap<>();
    }

    /** Add the number to an internal data structure.. */
    public void add(int number) {
        map.put(number, map.getOrDefault(number, 0) + 1);
    }

    /** Find if there exists any pair of numbers which sum is equal to the value. */
    public boolean find(int value) {
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            int num1 = entry.getKey();
            int num2 = value - num1;
            if (num1 == num2 && entry.getValue() > 1 || num1 != num2 && map.containsKey(num2)) return true;
        }
        return false;
    }
}

/**
 * Your TwoSum object will be instantiated and called as such:
 * TwoSum obj = new TwoSum();
 * obj.add(number);
 * boolean param_2 = obj.find(value);
 */

146. LRU Cache

class LRUCache {    
    class Node{
        private Node prev;
        private Node next;
        private int key;
        private int value;

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    private Map<Integer, Node> map;
    private int capacity;
    private Node head;
    private Node tail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>();
        head = new Node(-1, -1);
        tail = new Node(-1, -1);
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        if (!map.containsKey(key)) return -1;

        Node curt = map.get(key);
        curt.next.prev = curt.prev;
        curt.prev.next = curt.next;

        moveLast(curt);

        return map.get(key).value;
    }

    public void put(int key, int value) {
        if (get(key) != -1) {
            map.get(key).value = value;
            return;
        }

        if (map.size() == capacity) {
            Node curt = head.next;
            curt.next.prev = head;
            head.next = curt.next;
            map.remove(curt.key);
        }

        Node node = new Node(key, value);
        map.put(key, node);

        moveLast(node);
    }
    private void moveLast(Node node) {
        node.prev = tail.prev;
        tail.prev.next = node;
        node.next = tail;
        tail.prev = node;
    }
}

results matching ""

    No results matching ""