Jeg havde nedenstående problem i en kodningstest, og jeg fik 28/30 testpas og 2 mislykkedes på grund til en time-out.
Problem
Du har oprettet et programmeringssprog, og nu har du besluttet at tilføjehashmap
support til det. Det blev fundet, at det i almindelige programmeringssprog er umuligt at tilføje et tal til allehashmap
nøgler / værdier. Så du har besluttet at implementere din egenhashmap
på dit nye sprog med følgende handlinger.
insert x y
– indsæt og objekt med nøglex
og værdiy
get x
– returner værdien af et objekt med nøglenx
addToKey x
– tilføjx
til alle nøgler på kortetaddToValue y
– tilføjy
til alle værdier på kortDin opgave er at implementere denne
hashmap
, anvende de givne forespørgsler og finde sum af alle resultaterne forget
operationer
For eksempel
- For
queryType=["insert","insert","addToValue","addToKey","get"]
ogquery=[[1,2],[2,3],[2],[1],[3]]
, output skal 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
– værdien er 5
Input / Output
- [tidsbegrænsning for udførelse] 3 sekunder (Java)
- [input] array.string queryType
Array of forespørgselstyper. det er garanteret, at hverqueryType[i]
en af ovennævnte operationer
1 < = queryType.length < = 10 ^ 5- [input] array.array.integer-forespørgsel
Matrix med forespørgsler, hvor hver forespørgsel er nævnt med 2 tal til indsættelse og et nummer for andre Nøgleværdier er i området [-10 ^ 9,10 ^ 9]
Nedenfor er min løsning 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 der nogen anden datastruktur, jeg kan bruge i stedet for hashmap
eller kan jeg forbedre min kode for at være mere lineær?
Kommentarer
Svar
Jeg vil foreslå, at du opretter din egen OffsetIntegerMap
, der kan kortlægge mellem heltal og håndtere en forskydning på tasterne og værdierne.
Du behøver ikke nødvendigvis at implementere HashMap
fra bunden, definere din egen begrænsede grænseflade og implementere den med en eksisterende Map<Integer, Integer>
gennem komposition.
Ved at håndtere forskydningerne adskilt fra tasterne og værdier ender kompleksiteten af forskydningsoperationerne O (1) i stedet for O (n), når du foretager genberegninger, og Map<>
sæt og få operationer forblive ved deres oprindelige O (1).
Et eksempel på et ” 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 at indkapsle forskydningslogikken bliver behandlingssløjfen også meget enklere uden at refactoring meget af noget 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
- Tak. Ser ud til en bedre implementering. Jeg vil kontrollere denne logik.
- Jeg finder dette svar øverst. Brug af en klasse, der er adskilt fra at teste alle sager.
keyWithoutOffset
ogvalueWithoutOffset
(jeg tror, jeg så en fejl i den oprindelige kode w.r..t. til det). De klare navne (forskudt). Bare metodens navne er kortcentrerede i stedet for dem i kravene - Du kan bruge eksemplet fra spørgsmålet. Udskift bare
[]
med{}
.queryType
erString[]
ogquery
erint[][]
. - Ah, overset det. Og jeg ‘ er også forkælet med kodning af udfordringswebsteder, der bare giver mig en ” Kør ” knap :-). Ændret denne løsning til mit eget svar nu.
- Forskydning vil ikke være den samme for hver tast i hashmap! – start med nøglesæt (1,2,3) – tilføj 10 til alle nøgler, nu er nøglesæt (10,11,12) – indsæt ny nøgle (5), nu er nøglesæt (10,11,12,5) – tilføj 10 til alle nøgler, nu er nøglesættet (20,21,22,15). Så de første 3 taster havde faktisk forskudt 20 tilføjet dem, men sidste nøgle havde kun forskudt 10 (dvs. nøgletilføjelser udført før denne nøgle (5) blev indsat vil blive ignoreret).
Svar
Jeg har nogle forslag til dig.
Uddrag noget af logikken til metoder.
I din kode, når forespørgslen er insert
og get
, har du to store blokke af kode, der er ens; du kan udtrække til en metode og genbruge metoden i begge sektioner.
Jeg foreslår en metode, der returnerer en boolsk baseret på if
-tilstanden, så du bliver i stand til at sætte currValue
og currKey
variablerne til nul.
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; }
For at kontrollere den aktuelle forespørgsel currQuery
kan du også udpakke hver af 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); }
Brug altid primitiverne, når det er muligt
Når du ved, at det er umuligt at få en nulværdi med tallet, prøv at bruge primitiverne; de tager mindre hukommelse og er hurtigere end indpakningsklassen.
Før
Integer currKey = 0; Integer currValue = 0;
Efter
int currKey = 0; int currValue = 0;
Prøv at placere mindre kode i switch
blokke
Efter min mening bliver koden mindre læselig, når der er mere end 3 linjer med koder i en switchblok; Jeg foreslår, at du konverterer det til en is-else-if
. Denne konvertering gør koden kortere og mere læselig.
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]); }
Efter
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 dyr operation er addToKey x
, der tilføjer x til alle nøgler på kortet, fordi du i det væsentlige skal oprette en ny indtastningsnøgle, værdi + x i din hashmap
og slet den gamle indtastningsnøgle, værdi. For at undgå behovet for at cache den gamle post, mens det gentages over kortet, kan du skelne mellem to tilfælde:
x > 0, så hvis du har iterering over en keyset
bestilt faldende, der er ikke behov for at cache de gamle poster
x < 0, samme tilgang men keyset
bestilles stigende
Fordi du bruger hashmap
, er der ingen garanteret nøgleordre, så du har brug for data struktur for at gemme nøgler, der skal bestilles, før det gentages over nøgler 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 udelukkede sagen 0
fordi map
forbliver uberørt. Andre operationer behøver ikke rækkefølgen af nøglerne, og som allerede antydet kan det være bedre at prøve at isolere hver operation i en privat metode.
Kommentarer
- Tak @dariosicily for svaret. Er ‘ ikke sortering hver gang, mens du foretager
addToKey
, er det også dyrt. Eller kan jeg bruge aSortedMap
for at holde indsættelsesrækkefølgen faldende. LigesomSortedMap<Integer, Integer>values = new TreeMap<Integer, Integer>(Collections.reverseOrder());
- @Praveen Du er velkommen. Ja det er sortering hver gang, men med
ArrayList
efter sortering fortsætter du lineært.Jeg blev dømt for, at du kun kunne brugeHashMap
; hvis du kan brugeTreeMap
i stedet forHashMap
kan du bruge en iterator og en omvendt iterator og iterere over dinTreeMap
på en lige måde.
Svar
Ændret version af Johnbots svar uden en ekstra klasse. Jeg synes, at den ekstra klasse er overkill og snarere distraherer fra algoritmen, da jeg er nødt til at søge gennem en masse kode (meget af det kedelplade) for at se, hvad der foregår. Det er ikke den ekstra klasse, der gør behandlingssløjfen meget enklere. Det er algoritmen.
Yderligere ændringer:
-
keyOffset
er ikke klart for mig i hvilken retning det er forskudt, så jeg omdøbte det tiladdedToKey
(ligeledes for værdi). - Bestilte operationen navne som i problemspecifikationen, både for at holde mig tæt på specifikationen, og fordi den rækkefølge giver mig mere mening.
- Introduceret
args
for at gemme nogle gentagelser af kode. - Brugt
long
/Long
til alt, ikke kun for summen. Når alt kommer til alt kan tilføjelse til nøglerne / værdierne få dem til at løbe over, hvis vi bare brugerint
/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 tilføjer
addedToKey
ensartet til værdi ‘ s nøgle fungerer ikke, men alligevel trækkes den frainsert
ogget
handlinger arbejde?
Svar
Hvad med bare at gemme en forskydningsværdi for nøgler og værdier og opbygge indpakningsmetoder rundt om hashmaps få / sæt metoder til at tage højde for denne forskydning.
Map
hver gang du laverinsert
ellerget
forespørgsler, hvis du kan forklare mig, hvorfor jeg sætter pris på det.