Jeg hadde problemet nedenfor i en kodetest, og jeg fikk 28/30 tester og 2 mislyktes på grunn til en time-out.
Problem
Du har opprettet et programmeringsspråk, og nå har du bestemt deg for å legge tilhashmap
støtte til det. Det ble funnet at det i vanlige programmeringsspråk er umulig å legge til et tall til allehashmap
nøkler / verdier. Så du har bestemt deg for å implementere din egenhashmap
på det nye språket ditt med følgende operasjoner.
insert x y
– sett inn og objekt med nøkkelx
og verdiy
get x
– returner verdien til et objekt med nøkkelx
addToKey x
– legg tilx
til alle nøkler i kartaddToValue y
– legg tily
til alle verdiene i kartetDin oppgave er å implementere denne
hashmap
, bruke de gitte spørsmålene og finne sum av alle resultatene forget
operasjoner
For eksempel
- For
queryType=["insert","insert","addToValue","addToKey","get"]
ogquery=[[1,2],[2,3],[2],[1],[3]]
, skal utgangen være .
Forklaring
-
insert 1 2
– hashmap vil være{1:2}
-
insert 2 3
– hashmap vil være{1:2,2:3}
-
addToValue 2
– hashmap vil være{1:4,2:5}
-
addToKey 1
– hashmap vil være{2:4,3:5}
-
get 3
– verdien er 5
Input / Utgang
- [tidsgrense for utførelse] 3 sekunder (Java)
- [input] array.string queryType
Array of søketyper. det er garantert at hverqueryType[i]
en av de ovennevnte operasjonene
1 < = queryType.length < = 10 ^ 5- [input] array.array.integer query
Array of queries, der hvert søk er nevnt av 2 tall for innsetting og ett nummer for andre Nøkkelverdier er i området [-10 ^ 9,10 ^ 9]
Nedenfor er løsningen min i 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; }
Er det noen annen datastruktur jeg kan bruke i stedet for hashmap
eller kan jeg forbedre koden min for å være mer lineær?
Kommentarer
Svar
Jeg vil foreslå at du oppretter din egen OffsetIntegerMap
som kan kartlegge mellom heltall og håndtere en forskyvning på tastene og verdiene.
Du trenger ikke nødvendigvis å implementere HashMap
fra grunnen av, definere ditt eget begrensede grensesnitt og implementere det med en eksisterende Map<Integer, Integer>
gjennom komposisjon.
Ved å håndtere forskyvningene atskilt fra tastene og verdiene, vil kompleksiteten til forskyvningsoperasjonene ende opp O (1) i stedet for O (n) når du foretar omberegninger, og > sett og få operasjoner, hold deg på sin opprinnelige O (1).
Et eksempel på » 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; } }
Ved å kapsle inn forskyvningslogikken blir prosesseringsløyfen også mye enklere uten å omformulere mye av noe 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; }
Kommentarer
- Takk. Ser ut som en bedre implementering. Jeg vil sjekke denne logikken.
- Jeg finner dette svaret øverst. Bruke en klasse skilt fra å teste alle saker.
keyWithoutOffset
ogvalueWithoutOffset
(jeg tror jeg så en feil i den opprinnelige koden w.r..t. til det). De klare navnene (forskjøvet). Bare metodenavnene er kart-sentriske i stedet for de i kravene - Du kan bruke eksemplet fra spørsmålet. Bare bytt ut
[]
med{}
.queryType
erString[]
ogquery
erint[][]
. - Ah, oversett det. Og jeg ‘ er også bortskjemt med koding av utfordringssider som bare gir meg en » Kjør » knapp :-). Modifiserte denne løsningen i mitt eget svar nå.
- Offset vil ikke være den samme for hver tast i hashmap! – start med nøkkelset (1,2,3) – legg til 10 til alle nøklene, nå er nøkkelsettet (10,11,12) – sett inn ny nøkkel (5), nå er nøkkelsettet (10,11,12,5) – legg til 10 til alle nøkler, nå er nøkkelsettet (20,21,22,15). Så de første 3 tastene hadde offset 20 lagt til dem, men den siste nøkkelen hadde bare forskjøvet 10 (dvs. at nøkkeltillegg gjort før denne nøkkelen (5) ble satt inn vil bli ignorert).
Svar
Jeg har noen forslag til deg.
Trekk ut noe av logikken til metoder.
I din kode, når spørringen er insert
og get
, har du to store kodeblokker som er like; du kan trekke ut til en metode og bruke metoden i begge seksjoner.
Jeg foreslår en metode som returnerer en boolsk basert på if
-tilstanden, så du blir i stand til å sette variablene currValue
og currKey
.
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; }
Hvis du vil sjekke gjeldende spørring currQuery
, kan du trekke ut hver av dem i en metode.
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); }
Bruk alltid primitivene når det er mulig
Når du vet at det er umulig å få en nullverdi med tallet, prøv å bruke primitivene. De tar mindre minne og er raskere enn wrapper-klassen.
Før
Integer currKey = 0; Integer currValue = 0;
Etter
int currKey = 0; int currValue = 0;
Prøv å sette mindre kode i switch
blokker
Etter min mening blir koden mindre lesbar når det er mer enn 3 linjer med koder i en bryterblokk; Jeg foreslår at du konverterer den til en is-else-if
. Denne konverteringen vil gjøre koden kortere og mer lesbar.
Før
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]); }
Etter
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]); }
Refactored code
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; }
Svar
Det mest kostbar operasjon er addToKey x
som legger til x til alle nøkler i kartet, fordi du i det vesentlige må opprette en ny inngangsnøkkel, verdi + x i hashmap
og slett den gamle inngangsnøkkelen, verdien. For å unngå behovet for å cache den gamle oppføringen mens den gjentas over kartet, kan du skille mellom to tilfeller:
x > 0, så hvis du har iterering over en keyset
ordnet synkende, det er ikke behov for å cache de gamle oppføringene
x < 0, samme tilnærming men keyset
bestilles stigende
Fordi du bruker hashmap
, er det ingen nøkkelordre garantert, så du trenger data struktur for å lagre nøkler som skal bestilles, før den gjentas over nøkler som nedenfor:
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); } } }
Jeg ekskluderte saken 0
fordi map
forblir uberørt. Andre operasjoner trenger ikke rekkefølge på nøklene, og som allerede antydet, kan det være bedre å prøve å isolere hver operasjon i en privat metode.
Kommentarer
- Takk @dariosicily for svaret. Er ikke ‘ t sortering hver gang mens du gjør
addToKey
operasjonen er også kostbar ?. Eller kan jeg bruke aSortedMap
for å holde innsettingsrekkefølgen synkende. Som,SortedMap<Integer, Integer>values = new TreeMap<Integer, Integer>(Collections.reverseOrder());
- @Praveen Du er velkommen. Ja det er sortering hver gang, men med
ArrayList
etter sortering fortsetter du på en lineær måte.Jeg ble dømt for at du bare kunne brukeHashMap
; hvis du kan brukeTreeMap
i stedet forHashMap
kan du bruke en iterator og en omvendt iterator og iterere overTreeMap
på en rett måte.
Svar
Endret versjon av Johnbot s svar uten en ekstra klasse. Jeg tror den ekstra klassen er overkill og heller distraherer fra algoritmen, da jeg må søke gjennom mye kode (mye av det kjele) for å se hva som skjer. Det er ikke den ekstra klassen som gjør prosesseringsløyfen mye enklere. Det er algoritmen.
Ytterligere endringer:
-
keyOffset
er ikke tydelig for meg i hvilken retning den er forskjøvet, så jeg omdøpte det tiladdedToKey
(også for verdi). - Bestilte operasjonen navn som i problemspesifikasjonen, både for å holde seg nær spesifikasjonen og fordi den rekkefølgen gir mer mening for meg.
- Introdusert
args
for å lagre litt kodegjenta. - Brukt
long
/Long
til alt, ikke bare for summen. Tross alt kan det å legge til nøklene / verdiene få dem til å renne over hvis vi bare brukerint
/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; }
Kommentarer
- Hvorfor legger
addedToKey
jevnt til verdi ‘ s nøkkel fungerer ikke, men trekker den fra forinsert
ogget
handlinger arbeid?
Svar
Hva med bare å lagre en forskjøvet verdi for nøkler og verdier og bygge omslagsmetoder rundt hashmaps få / put-metoder for å gjøre rede for denne forskyvningen.
Map
hver gang du lagerinsert
ellerget
spørsmål, hvis du kan forklare meg hvorfor jeg setter pris på det.