Tive o problema abaixo em um teste de codificação e obtive 28/30 aprovações e 2 reprovações devido para um tempo limite.

Problema
Você criou uma linguagem de programação e agora decidiu adicionar hashmap suporte a ela. Foi descoberto que em linguagens de programação comuns, é impossível adicionar um número a todas as hashmap chaves / valores. Portanto, você decidiu implementar seu próprio hashmap em seu novo idioma com as seguintes operações.

  • insert x y – inserir e objeto com chave x e valor y
  • get x – retorna o valor de um objeto com a chave x
  • addToKey x – adicione x para todas as chaves no mapa
  • addToValue y – adicione y para todos os valores no mapa

Sua tarefa é implementar isso hashmap, aplicar as consultas fornecidas e encontrar o soma de todos os resultados de get operações

Por exemplo

  • Para queryType=["insert","insert","addToValue","addToKey","get"] e query=[[1,2],[2,3],[2],[1],[3]], a saída deve ser .

Explicação

  1. insert 1 2 – o hashmap será {1:2}
  2. insert 2 3 – o hashmap será {1:2,2:3}
  3. addToValue 2 – o hashmap será {1:4,2:5}
  4. addToKey 1 – o hashmap será {2:4,3:5}
  5. get 3 – o valor é 5

Input / Saída

  • [limite de tempo de execução] 3 segundos (Java)
  • [input] array.string queryType
    Matriz de tipos de consulta. é garantido que cada queryType[i] qualquer uma das operações mencionadas acima
    1 < = queryType.length < = 10 ^ 5
  • [input] array.array.integer query
    Matriz de consultas, em que cada consulta é mencionada por 2 números para inserção e um número para outros Os valores-chave estão no intervalo [-10 ^ 9,10 ^ 9]

Abaixo está minha solução em Java

long hashMap(String[] queryType, int[][] query) { long sum = 0; Integer currKey = 0; Integer currValue = 0; Map<Integer, Integer> values = new HashMap<>(); for (int i = 0; i < queryType.length; i++) { String currQuery = queryType[i]; switch (currQuery) { case "insert": HashMap<Integer, Integer> copiedValues = new HashMap<>(); if (currKey != 0 || currValue != 0) { Set<Integer> keys = values.keySet(); for (Integer key : keys) { copiedValues.put(key + currKey, values.get(key) + currValue); } values.clear(); values.putAll(copiedValues); currValue = 0; currKey = 0; } values.put(query[i][0], query[i][1]); break; case "addToValue": currValue += values.isEmpty() ? 0 : query[i][0]; break; case "addToKey": currKey += values.isEmpty() ? 0 : query[i][0]; break; case "get": copiedValues = new HashMap<>(); if (currKey != 0 || currValue != 0) { Set<Integer> keys = values.keySet(); for (Integer key : keys) { copiedValues.put(key + currKey, values.get(key) + currValue); } values.clear(); values.putAll(copiedValues); currValue = 0; currKey = 0; } sum += values.get(query[i][0]); } } return sum; } 

Existe alguma outra estrutura de dados que eu possa usar em vez de hashmap ou Posso melhorar meu código para ser mais linear?

Comentários

  • Bem-vindo à revisão do código. Não ‘ não entendo por que você cria um novo Map toda vez que você faz insert ou get consultas, se você puder me explicar por que eu aprecio isso.
  • @dariosicily, é porque eu não ‘ t deseja sobrescrever o valor existente ao atualizar uma chave ou mapa. Exemplo: Para {2: 3,3: 1}, se você deseja adicionar a chave 1 e o valor 1. Na primeira iteração, ele se tornará {3: 4}. Aqui, perderei o 3: 1 real, que é o próximo par de valores-chave. Resumindo, para evitar sobrescrever / colisão de pares de valores-chave.
  • Obrigado, agora entendi.

Resposta

Eu sugiro que você crie seu próprio OffsetIntegerMap que pode mapear entre inteiros e lidar com um deslocamento nas chaves e valores.

Você não precisa necessariamente implementar HashMap do zero, defina sua própria interface limitada e implemente-a com um Map<Integer, Integer> existente por meio de composição.

Ao lidar com os deslocamentos separadamente das chaves e valores, a complexidade das operações de deslocamento termina em O (1) em vez de O (n) ao fazer recálculos e Map<> as operações colocar e obter permanecem em seu O (1) original.

Um exemplo de ” OffsetIntegerMap “:

import java.util.HashMap; import java.util.Map; public class OffsetIntegerMap { private final Map<Integer, Integer> actualMap; private int keyOffset = 0; private int valueOffset = 0; public OffsetIntegerMap() { actualMap = new HashMap<>(); } public int size() { return actualMap.size(); } public boolean isEmpty() { return actualMap.isEmpty(); } public boolean containsKey(int key) { var keyWithoutOffset = key - keyOffset; return actualMap.containsKey(keyWithoutOffset); } public boolean containsValue(int value) { var valueWithoutOffset = value - valueOffset; return actualMap.containsValue(valueWithoutOffset); } public Integer get(int key) { var keyWithoutOffset = key - keyOffset; var value = actualMap.get(keyWithoutOffset); if (value == null) return null; return value + valueOffset; } public Integer put(int key, int value) { var keyWithoutOffset = key - keyOffset; var valueWithoutOffset = value - valueOffset; var oldValue = actualMap.put(keyWithoutOffset, valueWithoutOffset); if (oldValue == null) return null; return oldValue + valueOffset; } public Integer remove(int key) { var keyWithoutOffset = key - keyOffset; var oldValue = actualMap.remove(keyWithoutOffset); if (oldValue == null) return null; return oldValue + valueOffset; } public void clear() { actualMap.clear(); keyOffset = 0; valueOffset = 0; } public int getKeyOffset() { return keyOffset; } public void setKeyOffset(int keyOffset) { this.keyOffset = keyOffset; } public int getValueOffset() { return valueOffset; } public void setValueOffset(int valueOffset) { this.valueOffset = valueOffset; } public void addToValues(int toAdd) { this.valueOffset += toAdd; } public void addToKeys(int toAdd) { this.keyOffset += toAdd; } } 

Encapsulando a lógica de deslocamento, o loop de processamento também se torna muito mais simples sem refatorar muito de nada ing:

static long hashMap(String[] queryType, int[][] query) { long sum = 0; var map = new OffsetIntegerMap(); for (int i = 0; i < queryType.length; i++) { String currQuery = queryType[i]; switch (currQuery) { case "insert": map.put(query[i][0], query[i][1]); break; case "addToValue": map.addToValues(query[i][0]); break; case "addToKey": map.addToKeys(query[i][0]); break; case "get": sum += map.get(query[i][0]); } } return sum; } 

Comentários

  • Obrigado. Parece uma implementação melhor. Vou verificar essa lógica.
  • Acho esta resposta no topo. Usar uma classe separada do teste de todos os casos.keyWithoutOffset e valueWithoutOffset (acho que vi um bug no código original w.r..t. para isso). Os nomes claros (deslocamento). Apenas os nomes dos métodos são centrados no mapa ao invés daqueles nos requisitos
  • Você pode usar o exemplo da questão. Basta substituir [] por {}. queryType é String[] e query é int[][].
  • Ah, esqueci isso. E eu ‘ estou muito estragado por codificar sites de desafio apenas me dando uma ” Executar ” botão :-). Modifiquei esta solução em minha própria resposta agora.
  • O deslocamento não será o mesmo para todas as chaves no hashmap! – começar com o conjunto de chaves (1,2,3) – adicionar 10 a todas as chaves, agora o conjunto de chaves é (10,11,12) – inserir a nova chave (5), agora o conjunto de chaves é (10,11,12,5) – adicionar 10 para todas as chaves, agora o conjunto de chaves é (20,21,22,15). Portanto, as 3 primeiras chaves efetivamente tiveram o deslocamento 20 adicionado a elas, mas a última chave teve apenas o deslocamento 10 (ou seja, as adições de chave feitas antes da inserção desta chave (5) serão ignoradas).

Resposta

Tenho algumas sugestões para você.

Extraia um pouco da lógica dos métodos.

Em seu código, quando a consulta é insert e get, você tem dois grandes blocos de código que são semelhantes; você pode extrair para um método e reutilizar o método em ambas as seções.

Eu sugiro um método que retorne um booleano baseado na condição if, então você será capaz de definir as variáveis currValue e currKey como zero.

  long hashMap(String[] queryType, int[][] query) { //[...] switch (currQuery) { //[...] case "insert": if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } values.put(query[i][0], query[i][1]); break; //[...] } //[...] } private boolean didWeCopiedValuesToMap(Integer currKey, Integer currValue, Map<Integer, Integer> values, HashMap<Integer, Integer> copiedValues) { if (currKey != 0 || currValue != 0) { Set<Integer> keys = values.keySet(); for (Integer key : keys) { copiedValues.put(key + currKey, values.get(key) + currValue); } values.clear(); values.putAll(copiedValues); return true; } return false; }  

Além disso, para verificar a consulta atual currQuery, você pode extrair cada um deles em um método.

 private boolean isGet(String currQuery) { return "get".equals(currQuery); } private boolean isAddToKey(String currQuery) { return "addToKey".equals(currQuery); } private boolean isAddToValue(String currQuery) { return "addToValue".equals(currQuery); } private boolean isInsert(String currQuery) { return "insert".equals(currQuery); }  

Sempre use os primitivos quando possível

Quando você saiba que é impossível obter um valor nulo com o número, tente usar os primitivos; eles ocupam menos memória e são mais rápidos do que a classe wrapper.

Antes

 Integer currKey = 0; Integer currValue = 0;  

Depois

 int currKey = 0; int currValue = 0;  

Tente colocar menos código em switch blocos

Em minha opinião, o código se torna menos legível quando há mais de 3 linhas de códigos em um bloco de switch; Eu sugiro que você o converta em um is-else-if. Essa conversão tornará o código mais curto e legível.

Antes

switch (currQuery) { case "insert": if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } values.put(query[i][0], query[i][1]); break; case "addToValue": currValue += values.isEmpty() ? 0 : query[i][0]; break; case "addToKey": currKey += values.isEmpty() ? 0 : query[i][0]; break; case "get": if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } sum += values.get(query[i][0]); }

Após

 if ("insert".equals(currQuery)) { if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } values.put(query[i][0], query[i][1]); } else if ("addToValue".equals(currQuery)) { currValue += values.isEmpty() ? 0 : query[i][0]; } else if ("addToKey".equals(currQuery)) { currKey += values.isEmpty() ? 0 : query[i][0]; } else if ("get".equals(currQuery)) { if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } sum += values.get(query[i][0]); }  

Código refatorado

  long hashMap(String[] queryType, int[][] query) { long sum = 0; int currKey = 0; int currValue = 0; Map<Integer, Integer> values = new HashMap<>(); for (int i = 0; i < queryType.length; i++) { String currQuery = queryType[i]; if (isInsert(currQuery)) { if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } values.put(query[i][0], query[i][1]); } else if (isAddToValue(currQuery)) { currValue += values.isEmpty() ? 0 : query[i][0]; } else if (isAddToKey(currQuery)) { currKey += values.isEmpty() ? 0 : query[i][0]; } else if (isGet(currQuery)) { if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } sum += values.get(query[i][0]); } } return sum; } private boolean isGet(String currQuery) { return "get".equals(currQuery); } private boolean isAddToKey(String currQuery) { return "addToKey".equals(currQuery); } private boolean isAddToValue(String currQuery) { return "addToValue".equals(currQuery); } private boolean isInsert(String currQuery) { return "insert".equals(currQuery); } private boolean didWeCopiedValuesToMap(int currKey, int currValue, Map<Integer, Integer> values) { HashMap<Integer, Integer> copiedValues = new HashMap<>(); if (currKey != 0 || currValue != 0) { Set<Integer> keys = values.keySet(); for (Integer key : keys) { copiedValues.put(key + currKey, values.get(key) + currValue); } values.clear(); values.putAll(copiedValues); return true; } return false; }  

Resposta

O máximo operação cara é a addToKey x que adiciona x a todas as chaves no mapa, porque substancialmente você tem que criar uma nova chave de entrada, valor + x em seu hashmap e exclua a chave de entrada antiga, valor. Para evitar a necessidade de armazenar em cache a entrada antiga durante a iteração no mapa, você pode distinguir dois casos:

x > 0, então se você tiver iterado sobre um keyset decrescente ordenado não há necessidade de armazenar em cache as entradas antigas

x < 0, mesma abordagem, mas o keyset está em ordem crescente

Como você está usando hashmap, não há ordem de chave garantida, então você precisa de dados estrutura para armazenar as chaves a serem ordenadas, antes de iterar sobre as chaves como a seguir:

private static void addtoKey(Map<Integer, Integer> map, int i) { if (i != 0) { List<Integer> list = new ArrayList<>(map.keySet()); if (i > 0) { Collections.sort(list, Collections.reverseOrder()); } else { Collections.sort(list); } for(int key : list) { map.put(key + i, map.get(key)); map.remove(key); } } } 

Excluí o caso 0 porque map permanece intocado. Outras operações não precisam da ordem das chaves e, conforme já sugerido, seria melhor tentar isolar todas as operações em um método privado.

Comentários

  • Obrigado @dariosicily pela resposta. Isn ‘ t classificar todas as vezes ao fazer addToKey operação também é caro ?. Ou posso usar a SortedMap para manter a ordem de inserção decrescente. Por exemplo, SortedMap<Integer, Integer>values = new TreeMap<Integer, Integer>(Collections.reverseOrder());
  • @Praveen Você é bem-vindo. Sim, é classificando sempre, mas com ArrayList após a classificação, você procederá de forma linear.Fui condenado por você só poderia usar HashMap; se você pode usar TreeMap em vez de HashMap, você pode usar um iterador e um iterador reverso e iterar sobre seu TreeMap de maneira direta.

Resposta

Versão modificada de Resposta de Johnbot” sem uma classe extra. Eu acho que a classe extra é um exagero e me distrai do algoritmo, já que tenho que pesquisar muitos códigos (muito boilerplate) para ver o que está acontecendo. Não é essa classe extra que torna o loop de processamento muito mais simples. É o algoritmo.

Outras mudanças:

  • keyOffset não está claro para mim em qual direção ele está deslocado, então renomei isso para addedToKey (o mesmo para o valor).
  • Ordenou a operação nomes como na especificação do problema, tanto para ficar perto da especificação quanto porque essa ordem faz mais sentido para mim.
  • Introduzido args para economizar algumas repetições de código.
  • Usado long / Long para tudo, não apenas para a soma. Afinal, adicionar às chaves / valores pode sobrecarregá-los se apenas usarmos int / Integer.
static long hashMap(String[] queryType, int[][] query) { Map<Long, Long> map = new HashMap<>(); long sum = 0, addedToKey = 0, addedToValue = 0; for (int i = 0; i < query.length; i++) { int[] args = query[i]; switch (queryType[i]) { case "insert": map.put(args[0] - addedToKey, args[1] - addedToValue); break; case "get": sum += map.get(args[0] - addedToKey) + addedToValue; break; case "addToKey": addedToKey += args[0]; break; case "addToValue": addedToValue += args[0]; } } return sum; } 

Comentários

  • Por que adicionar uniformemente addedToKey ao a chave do valor ‘ s não funciona, mas subtraí-la para as ações insert e get sim funciona?

Resposta

Que tal apenas armazenar um valor de deslocamento para chaves e valores e construir métodos de wrapper em torno do métodos get / put hashmaps para compensar esse deslocamento.

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *