Ключ приращения, Ключ уменьшения, Поиск ключа Макса, Поиск ключа Минуса за время O (1) - PullRequest
0 голосов
/ 09 ноября 2019

Мне задали этот вопрос в интервью, но я не смог его решить. Разработайте структуру данных, которая выполняет следующие действия:

Inc (Ключ) -> Принимает ключ и увеличивает его значение на 1. Если ключ приходит впервые, установите его значение как 1.

Dec(Ключ) -> Принимает ключ и уменьшает его значение на 1. Дано, что его значение минимально 1.

Findmaxkey () -> Возвращает ключ, имеющий максимальное значение, соответствующее ему. Если таких ключей несколько, вы можете вывести любой из них.

Findminkey () -> Возвращает ключ с минимальным значением, соответствующим ему. Если таких ключей несколько, вы можете вывести любую из них.

Все действия необходимо выполнить за O (1) раз.

Подсказка: интервьюер просил меня использоватьсловарь (hashmap) с двусвязным списком.

Ответы [ 3 ]

0 голосов
/ 09 ноября 2019

Ключом является проблема, запрашивает только dec (1) или inc (1). Следовательно, алгоритм должен только перемещать блок вперед или назад. Это сильный предварительный подход и дает много информации.

Мой проверенный код:

template <typename K, uint32_t N>
struct DumbStructure {
 private:
  const int head_ = 0, tail_ = N - 1;

  std::unordered_map<K, int> dic_;

  int l_[N], r_[N], min_ = -1, max_ = -1;
  std::unordered_set<K> keys_[N];

  void NewKey(const K &key) {
    if (min_ < 0) {
      // nothing on the list
      l_[1] = head_;
      r_[1] = tail_;
      r_[head_] = 1;
      l_[tail_] = 1;

      min_ = max_ = 1;
    } else if (min_ == 1) {
    } else {
      // min_ > 1
      l_[1] = head_;
      r_[1] = min_;
      r_[head_] = 1;
      l_[min_] = 1;

      min_ = 1;
    }
    keys_[1].insert(key);
  }

  void MoveKey(const K &key, int from_value, int to_value) {
    int prev_from_value = l_[from_value];
    int succ_from_value = r_[from_value];

    if (keys_[from_value].size() >= 2) {
    } else {
      r_[prev_from_value] = succ_from_value;
      l_[succ_from_value] = prev_from_value;

      if (min_ == from_value) min_ = succ_from_value;
      if (max_ == from_value) max_ = prev_from_value;
    }
    keys_[from_value].erase(key);

    if (keys_[to_value].size() >= 1) {
    } else {
      if (to_value > from_value) {
        // move forward
        l_[to_value] =
            keys_[from_value].size() > 0 ? from_value : prev_from_value;
        r_[to_value] = succ_from_value;
        r_[l_[to_value]] = to_value;
        l_[r_[to_value]] = to_value;
      } else {
        // move backward
        l_[to_value] = prev_from_value;
        r_[to_value] =
            keys_[from_value].size() > 0 ? from_value : succ_from_value;
        r_[l_[to_value]] = to_value;
        l_[r_[to_value]] = to_value;
      }
    }
    keys_[to_value].insert(key);

    min_ = std::min(min_, to_value);
    max_ = std::max(max_, to_value);
  }

 public:
  DumbStructure() {
    l_[head_] = -1;
    r_[head_] = tail_;
    l_[tail_] = head_;
    r_[tail_] = -1;
  }

  void Inc(const K &key) {
    if (dic_.count(key) == 0) {
      dic_[key] = 1;
      NewKey(key);
    } else {
      MoveKey(key, dic_[key], dic_[key] + 1);
      dic_[key] += 1;
    }
  }

  void Dec(const K &key) {
    if (dic_.count(key) == 0 || dic_[key] == 1) {
      // invalid
      return;
    } else {
      MoveKey(key, dic_[key], dic_[key] - 1);
      dic_[key] -= 1;
    }
  }

  K GetMaxKey() const { return *keys_[max_].begin(); }

  K GetMinKey() const { return *keys_[min_].begin(); }
};
0 голосов
/ 09 ноября 2019

Структура данных может быть построена следующим образом:

  • Хранить все ключи с одинаковым количеством в HashSet keys и сопровождать этот набор значением count: давайте назовем эту пару count и keys «корзиной».

  • Для каждого значения счетчика, для которого есть хотя бы ключ, у вас будет такая корзина,Поместите сегменты в двусвязный список bucketList и упорядочите их по количеству.

  • Также создайте HashMap bucketsByKey, который сопоставляет ключ с корзиной, в которой этот ключ находится в данный момент. сохранено (ключ указан в наборе keys корзины)

Операция FindMinKey проста: получить первый контейнер из bucketList, получить ключ из его keys установить (не важно, какой) и вернуть его. Аналогично для FindMaxKey.

Операция Inc(key) будет выполнять следующие шаги:

  1. Получить корзину, соответствующую key из bucketsByKey
  2. Если этот контейнер существует, удалите ключ из набора keys.
  3. Если этот набор становится пустым, удалите контейнер из bucketList
  4. Если следующий контейнер в bucketList имеет число, равное еще одному, добавьте ключ к его наборуи обновите bucketsByKey, чтобы он ссылался на этот сегмент для этого ключа.
  5. Если следующий блок в bucketList имеет другое количество (или больше нет блоков), то создайте новый блок справильный счетчик и ключ и вставьте его непосредственно перед ранее найденным интервалом в bucketList - или, если следующий интервал не был найден, просто добавьте новый в конце.
  6. Если на шаге 2 не былоДля этого ключа найден интервал, затем предположим, что его счет был равен 0, и возьмите первый интервал из bucketList и используйте его как «следующий интервал», начиная с шага 4.

Процесс для Dec(key) аналогично, за исключением того, что когда установлено, что счетчик уже равен 1, ничего не происходит.

Вот интерактивный фрагмент на JavaScript, который вы можете запустить здесь. Он использует собственный Map для HashMap, собственный Set для HashSet и реализует двусвязный список в виде кругового, где начало / конец помечен узлом "страж" (без данных).

Вы можете нажимать кнопки Inc / Dec для выбранной вами клавиши и контролировать вывод FindMinKey и FindMaxKey, а также просто просматривать структуру данных.

class Bucket {
    constructor(count) {
        this.keys = new Set; // keys in this hashset all have the same count:
        this.count = count; // will never change. It's the unique key identifying this bucket
        this.next = this; // next bucket in a doubly linked, circular list
        this.prev = this; // previous bucket in the list
    }
    delete() { // detach this bucket from the list it is in
        this.next.prev = this.prev;
        this.prev.next = this.next;
        this.next = this;
        this.prev = this;
    }
    insertBefore(node) { // inject `this` into the list that `node` is in, right before it
        this.next = node;
        this.prev = node.prev;
        this.prev.next = this;
        this.next.prev = this;
    }
    * nextBuckets() { // iterate all following buckets until the "sentinel" bucket is encountered
        for (let bucket = this.next; bucket.count; bucket = bucket.next) {
            yield bucket;
        }
    }
}

class MinMaxMap {
    constructor() {
        this.bucketsByKey = new Map; // hashmap of key -> bucket
        this.bucketList = new Bucket(0); // a sentinel node of a circular doubly linked list of buckets
    }
    inc(key) {
        this.add(key, 1);
    }
    dec(key) {
        this.add(key, -1);
    }
    add(key, one) {
        let nextBucket, count = 1;
        let bucket = this.bucketsByKey.get(key);
        if (bucket === undefined) {
            nextBucket = this.bucketList.next;
        } else {
            count = bucket.count + one;
            if (count < 1) return;
            bucket.keys.delete(key);
            nextBucket = one === 1 ? bucket.next : bucket.prev;
            if (bucket.keys.size === 0) bucket.delete(); // remove from its list
        }
        if (nextBucket.count !== count) {
            bucket = new Bucket(count);
            bucket.insertBefore(one === 1 ? nextBucket : nextBucket.next);
        } else {
            bucket = nextBucket;
        }
        bucket.keys.add(key);
        this.bucketsByKey.set(key, bucket);
    }
    findMaxKey() {
        if (this.bucketList.prev.count === 0) return null; // the list is empty
        return this.bucketList.prev.keys.values().next().value; // get any key from first bucket
    }
    findMinKey() {
        if (this.bucketList.next.count === 0) return null; // the list is empty
        return this.bucketList.next.keys.values().next().value; // get any key from last bucket
    }
    toString() {
        return JSON.stringify(Array.from(this.bucketList.nextBuckets(), ({count, keys}) => [count, ...keys]))
    }
}

// I/O handling
let inpKey = document.querySelector("input");
let [btnInc, btnDec] = document.querySelectorAll("button");
let [outData, outMin, outMax] = document.querySelectorAll("span");

let minMaxMap = new MinMaxMap;
btnInc.addEventListener("click", function () {
    minMaxMap.inc(inpKey.value);
    refresh();
});
btnDec.addEventListener("click", function () {
    minMaxMap.dec(inpKey.value);
    refresh();
});
function refresh() {
    outData.textContent = minMaxMap.toString();
    outMin.textContent = minMaxMap.findMinKey();
    outMax.textContent = minMaxMap.findMaxKey();
}
key: <input> <button>Inc</button> <button>Dec</button><br>
data structure (linked list): <span></span><br>
findMinKey = <span></span><br>
findMaxKey = <span></span>
0 голосов
/ 09 ноября 2019

Вот мой ответ, но я не уверен, что не нарушил ни одного из обстоятельств, которые имел в виду ваш интервьюер.

Мы будем хранить LinkedList, где каждый элемент имеет ключ и значенияон соответствует и указывает на свой предыдущий и следующий элемент и всегда сортируется по значениям. Мы храним указатель для каждого ключа, где он находится в LinkedList. Кроме того, для каждого нового числа, которое мы видим, мы добавляем два элемента, которые должны просматривать начальный и конечный элемент каждого числа, и мы будем хранить указатель на них. Поскольку мы добавляем эти дополнительные элементы не более двух для каждой операции, он по-прежнему равен O (1).

теперь для каждой операции (скажем, приращения), мы можем найти, где находится элемент, соответствующий этому ключу. LinkedList, использующий словарь (предполагая, что словари работают с временной сложностью O (1)), мы находим последний элемент в LinkedList, который имеет то же значение (мы можем сделать это, используя элемент, соответствующий концу этого значения, иодин элемент в обратном направлении) и поменять местами указатели этих двух (это всего лишь простой обмен, и этот обмен не влияет на другие элементы), затем мы поменяем местами этот элемент со следующим следующим, чтобы он попал в сегмент следующего числа (мыможет потребоваться добавить и это число), последними вещами, которые нужно отслеживать, является значение минимума и максимума, которое должно быть обновлено, если изменяемый элемент является текущим минимумом или максимумом, а число сто же значение (начальный и конечный элементы для этого значения являются последовательнымив LinkedList)

Тем не менее, я думаю, что этот подход можно улучшить.

...