1. 程式人生 > >LeetCode刷題Medium篇Insert Delete GetRandom O(1)

LeetCode刷題Medium篇Insert Delete GetRandom O(1)

題目

Design a data structure that supports all following operations in average O(1) time.

 

  1. insert(val): Inserts an item val to the set if not already present.
  2. remove(val): Removes an item val from the set if present.
  3. getRandom: Returns a random element from current set of elements. Each element must have the same probability
     of being returned.

 

Example:

// Init an empty set.
RandomizedSet randomSet = new RandomizedSet();

// Inserts 1 to the set. Returns true as 1 was inserted successfully.
randomSet.insert(1);

// Returns false as 2 does not exist in the set.
randomSet.remove(2);

// Inserts 2 to the set, returns true. Set now contains [1,2].
randomSet.insert(2);

// getRandom should return either 1 or 2 randomly.
randomSet.getRandom();

// Removes 1 from the set, returns true. Set now contains [2].
randomSet.remove(1);

// 2 was already in the set, so return false.
randomSet.insert(2);

// Since 2 is the only number in the set, getRandom always return 2.
randomSet.getRandom();

十分鐘嘗試

可以用HashSet嗎?新增,刪除可以,但是如何實現隨機返回呢?想起來陣列,陣列的讀取也是O(1)的,我們初始化一個隨機值當作陣列的索引就可以實現隨機讀取了。來,程式碼上:

class RandomizedSet {

    private Set<Integer>  set;
    
    private List<Integer> list;
    
    /** Initialize your data structure here. */
    public RandomizedSet() {
        set=new HashSet();
        list=new ArrayList();
    }
    
    /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
    public boolean insert(int val) {
        if(set.contains(val)) return false;
        else{
            return set.add(val);
        }
    }
    
    /** Removes a value from the set. Returns true if the set contained the specified element. */
    public boolean remove(int val) {
         if(!set.contains(val)) return false;
        else{
            return set.remove(val);
        }
    }
    
    /** Get a random element from the set. */
    public int getRandom() {
        //每次隨機之前,清空list,不然上次的資料都存在,即使remove也沒有用
        list.clear();
        for(Integer tmp:set){
            list.add(tmp);
        }
        Random random=new Random();
        int randIndex=random.nextInt(list.size());
        return list.get(randIndex);
    }
}

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * RandomizedSet obj = new RandomizedSet();
 * boolean param_1 = obj.insert(val);
 * boolean param_2 = obj.remove(val);
 * int param_3 = obj.getRandom();
 */

Runtime: 379 ms, faster than 1.72% of Java online submissions forInsert Delete GetRandom O(1).

記得getRandom前,清空list,否則以前的陣列仍然存在。但是這樣不符合題目要求,因為getRandom的時候需要clear,很顯然,clear方法不是O(1)的

再來。

class RandomizedSet {

    private Set<Integer>  set;
    
    private List<Integer> list;
    
    /** Initialize your data structure here. */
    public RandomizedSet() {
        set=new HashSet();
        list=new ArrayList();
    }
    
    /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
    public boolean insert(int val) {
        if(set.contains(val)) return false;
        else{
            list.add(val);
            return set.add(val);
        }
    }
    
    /** Removes a value from the set. Returns true if the set contained the specified element. */
    public boolean remove(int val) {
         if(!set.contains(val)) return false;
        else{
            list.remove(val);
            return set.remove(val);
        }
    }
    
    /** Get a random element from the set. */
    public int getRandom() {
        Random random=new Random();
        int randIndex=random.nextInt(list.size());
        return list.get(randIndex);
    }
}

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * RandomizedSet obj = new RandomizedSet();
 * boolean param_1 = obj.insert(val);
 * boolean param_2 = obj.remove(val);
 * int param_3 = obj.getRandom();
 */

remove後list還有資料,我判斷錯了,其實是remove按照索引處理了,不是值。修改後成功了。

list.remove有兩個方法,如果傳入的int,當索引處理,如果傳入物件,比如Integer,按值處理。

/**
 * Removes a value from the set. Returns true if the set contained the specified element.
 */
public boolean remove(int val) {
    if (!set.contains(val)) return false;
    else {
        list.remove(Integer.valueOf(val));
        return set.remove(val);
    }
}

修改後寫了下面一版本。成功了:
class RandomizedSet {

    private Set<Integer>  set;
    
    private List<Integer> list;
    

    
    /** Initialize your data structure here. */
    public RandomizedSet() {
        set=new HashSet();
        list=new ArrayList();
    }
    
    /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
    public boolean insert(int val) {
        if (set.contains(val)) return false;
        else {
            list.add(val);
            return set.add(val);
        }
    }
    
    /** Removes a value from the set. Returns true if the set contained the specified element. */
    public boolean remove(int val) {
          if (!set.contains(val)) return false;
        else {
            list.remove(Integer.valueOf(val));
            return set.remove(val);
        }
    }
    
    /** Get a random element from the set. */
    public int getRandom() {
        Random random=new Random();
        int randIndex=random.nextInt(list.size());
        return list.get(randIndex);
    }
}

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * RandomizedSet obj = new RandomizedSet();
 * boolean param_1 = obj.insert(val);
 * boolean param_2 = obj.remove(val);
 * int param_3 = obj.getRandom();
 */

雖然成功了,但是時間複雜度對嗎?getRandom對,insert也對,remove呢?關鍵是remove中的list.remove(Object obj)是O(1)嗎?如果是索引,需要移動陣列,不是常量時間,如果是值,也不是。這個方法也不對。

  • add() – takes O(1) time
  • add(index, element) – in average runs in O(n) time
  • get() – is always a constant time O(1) operation
  • remove() – runs in linear O(n) time. We have to iterate the entire array to find the element qualifying for removal,如果刪除的是最後一個元素,入參是索引應該是O(1
  • indexOf() – also runs in linear time. It iterates through the internal array and checking each element one by one. So the time complexity for this operation always requires O(n) time
  • contains() – implementation is based on indexOf(). So it will also run in O(n) time
  • set(index,element)時間複雜度是O(1)

寫了下面一版,主要思路是利用list的常量時間複雜度操作,set(index,element)和remove(lastindex) 完成。今天狀體也不太好,所以除錯了好久才發現很多問題,先把有問題程式碼貼出來,一個一個分析:

1. 定義了一個index,insert完之後增加,用來記錄新新增元素的索引。這個是嚴重錯誤的,因為刪除後,index是一直增加的,明顯不對,重大錯誤!

2. list的add(index,element)是增加元素到指定位置,後面的元素依次後移動,不是覆蓋,覆蓋指定位置元素,用set(index,elemment)

3. 看下面的兩行程式碼

   map.put(lastElement,index);
            map.remove(val);

先更新map中的索引,然後再刪除。順序剛開始寫反了,如果是一個元素,等於從map移除後,又新增進入了map

class RandomizedSet {

    private Map<Integer,Integer>  map;
    
    private List<Integer> list;
    
    private int index=0;
    
    /** Initialize your data structure here. */
    public RandomizedSet() {
        map=new HashMap();
        list=new ArrayList();
    }
    
    /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
    public boolean insert(int val) {
        if(map.containsKey(val)) return false;
        else{
            list.add(val);
            map.put(val,index++);
            return true;
        }
    }
    
    /** Removes a value from the set. Returns true if the set contained the specified element. */
    public boolean remove(int val) {
         if(!map.containsKey(val)) return false;
        else{
            //會產生不連續空間,因此記錄索引,直接用最後一個元素覆蓋
            int index=map.get(val);
            int lastElement=list.get(list.size()-1);
            list.set(index,lastElement);
            list.remove(list.size()-1);
            map.put(lastElement,index);
            map.remove(val);
            return true;
        }
    }
    
    /** Get a random element from the set. */
    public int getRandom() {
        Random random=new Random();
        int randIndex=random.nextInt(list.size());
        return list.get(randIndex);
    }
}

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * RandomizedSet obj = new RandomizedSet();
 * boolean param_1 = obj.insert(val);
 * boolean param_2 = obj.remove(val);
 * int param_3 = obj.getRandom();
 */

修改後程式碼如下:

效率有所提高

class RandomizedSet {

    private Map<Integer,Integer>  map;
    
    private List<Integer> list;
    

    
    /** Initialize your data structure here. */
    public RandomizedSet() {
        map=new HashMap();
        list=new ArrayList();
    }
    
    /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
    public boolean insert(int val) {
        if(map.containsKey(val)) return false;
        else{
            list.add(val);
            map.put(val,list.size()-1);
            return true;
        }
    }
    
    /** Removes a value from the set. Returns true if the set contained the specified element. */
    public boolean remove(int val) {
         if(!map.containsKey(val)) return false;
        else{
            //會產生不連續空間,因此記錄索引,直接用最後一個元素覆蓋
            int index=map.get(val);
            int lastElement=list.get(list.size()-1);
            list.set(index,lastElement);
            list.remove(list.size()-1);
            map.put(lastElement,index);
            map.remove(val);
            return true;
        }
    }
    
    /** Get a random element from the set. */
    public int getRandom() {
        Random random=new Random();
        int randIndex=random.nextInt(list.size());
        return list.get(randIndex);
    }
}

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * RandomizedSet obj = new RandomizedSet();
 * boolean param_1 = obj.insert(val);
 * boolean param_2 = obj.remove(val);
 * int param_3 = obj.getRandom();
 */