Jag hade problemet nedan i ett kodningstest och jag fick 28/30 testpass och 2 misslyckades på grund av till en timeout.
Problem
Du har skapat ett programmeringsspråk och nu har du beslutat att lägga tillhashmap
support till det. Det visade sig att det i vanliga programmeringsspråk är omöjligt att lägga till ett nummer till allahashmap
nycklar / värden. Så du har bestämt dig för att implementera din egenhashmap
på ditt nya språk med följande åtgärder.
insert x y
– infoga och objekt med nyckelx
och värdey
get x
– returnera värdet på ett objekt med tangentenx
addToKey x
– lägg tillx
till alla nycklar på kartanaddToValue y
– lägg tilly
till alla värden på kartanDin uppgift är att implementera denna
hashmap
, tillämpa givna frågor och hitta summa av alla resultat förget
operationer
Till exempel
- För
queryType=["insert","insert","addToValue","addToKey","get"]
ochquery=[[1,2],[2,3],[2],[1],[3]]
ska utgången vara .
Förklaring
-
insert 1 2
– hashmap blir{1:2}
-
insert 2 3
– hashmap blir{1:2,2:3}
-
addToValue 2
– hashmap blir{1:4,2:5}
-
addToKey 1
– hashmap blir{2:4,3:5}
-
get 3
– värdet är 5
Input / Utdata
- [tidsgräns för körning] 3 sekunder (Java)
- [input] array.string queryType
Array of frågetyper. det garanteras att varjequeryType[i]
någon av ovan nämnda operationer
1 < = queryType.length < = 10 ^ 5- [input] array.array.integer fråga
Array of queries, där varje fråga nämns av två siffror för infoga och ett nummer för andra Nyckelvärden ligger inom intervallet [-10 ^ 9,10 ^ 9]
Nedan är 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; }
Finns det någon annan datastruktur som jag kan använda istället för hashmap
eller kan jag förbättra min kod för att vara mer linjär?
Kommentarer
Svar
Jag föreslår att du skapar din egen OffsetIntegerMap
som kan mappa mellan heltal och hantera en förskjutning på tangenter och värden.
Du behöver inte nödvändigtvis implementera HashMap
från grunden, definiera ditt eget begränsade gränssnitt och implementera det med en befintlig Map<Integer, Integer>
genom komposition.
Genom att hantera förskjutningarna separat från tangenterna och värdena kommer komplexiteten i förskjutningsoperationerna att hamna O (1) istället för O (n) vid omberäkningar och Map<>
sätt och få operationer stanna vid sin ursprungliga O (1).
Ett exempel 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; } }
Genom att inkapsla offsetlogiken blir bearbetningsslingan också mycket enklare utan att refactorera mycket av någonting 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
- Tack. Ser ut som ett bättre genomförande. Jag kommer att kontrollera den här logiken.
- Jag hittar det här svaret högst upp. Använda en klass som är skild från att testa alla fall.
keyWithoutOffset
ochvalueWithoutOffset
(jag tror att jag såg ett fel i den ursprungliga koden w.r..t. till det). De tydliga namnen (offset). Bara metodnamnen är kartcentrerade istället för de i kraven - Du kan använda exemplet från frågan. Byt bara ut
[]
med{}
.queryType
ärString[]
ochquery
ärint[][]
. - Ah, förbises det. Och jag ’ är också bortskämd av att koda utmaningssidor bara ger mig en ” Kör ” knapp :-). Modifierade den här lösningen till mitt eget svar nu.
- Offset kommer inte att vara samma för varje nyckel i hashmap! – börja med tangentuppsättning (1,2,3) – lägg till 10 till alla tangenter, nu är tangentuppsättning (10,11,12) – infoga ny tangent (5), nu är tangentuppsättning (10,11,12,5) – lägg till 10 till alla nycklar, nu är nyckeluppsättningen (20,21,22,15). Så de första tre tangenterna hade faktiskt förskjutning 20 lagt till dem, men den sista tangenten hade bara förskjutit 10 (dvs att tangenttillägg gjorda innan denna nyckel (5) infördes ignoreras).
Svar
Jag har några förslag till dig.
Extrahera en del av logiken till metoder.
I din kod, när frågan är insert
och get
har du två stora kodblock som liknar; du kan extrahera till en metod och återanvända metoden i båda sektionerna.
Jag föreslår en metod som returnerar en boolean baserat på if
-villkoret så att du blir kunna ställa in variablerna currValue
och 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; }
För att kontrollera den aktuella frågan currQuery
kan du extrahera var och en av dem med en metod.
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); }
Använd alltid primitiverna när det är möjligt
När du vet att det är omöjligt att få ett nollvärde med numret, försök att använda primitiverna. De tar mindre minne och är snabbare än omslagsklassen.
Före
Integer currKey = 0; Integer currValue = 0;
Efter
int currKey = 0; int currValue = 0;
Försök sätta mindre kod i switch
block
Enligt min mening blir koden mindre läsbar när det finns mer än 3 rader koder i ett växelblock; Jag föreslår att du konverterar den till en is-else-if
. Den här konverteringen gör koden kortare och mer läsbar.
Före
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 är addToKey x
som lägger till x till alla nycklar på kartan, eftersom du i huvudsak måste skapa en ny inmatningsnyckel, värde + x i din hashmap
och ta bort den gamla inmatningsnyckeln, värdet. För att undvika behovet av att cacha den gamla posten medan den itererar över kartan kan du skilja mellan två fall:
x > 0, om du har iterera över en keyset
beställt fallande, det finns inget behov av att cacha de gamla posterna
x < 0, samma tillvägagångssätt men keyset
ordnas stigande
Eftersom du använder hashmap
finns ingen nyckelordning garanterad, så du behöver en data struktur för att lagra nycklar som ska beställas, innan det går igenom nycklar som nedan:
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); } } }
Jag utesluter fallet 0
eftersom map
förblir orörd. Andra operationer behöver inte ordning på nycklarna och som redan föreslagits kan det vara bättre att försöka isolera alla operationer i en privat metod.
Kommentarer
- Tack @dariosicily för svaret. Är inte ’ t sortering varje gång medan
addToKey
är också kostsamt ?. Eller kan jag använda aSortedMap
för att hålla infogningsordningen fallande. Gilla,SortedMap<Integer, Integer>values = new TreeMap<Integer, Integer>(Collections.reverseOrder());
- @Praveen Du är välkommen. Ja det är sortering varje gång, men med
ArrayList
efter sortering fortsätter du linjärt.Jag dömdes till att du bara kunde användaHashMap
; om du kan användaTreeMap
istället förHashMap
kan du använda en iterator och en omvänd iterator och itera över dinTreeMap
på ett rakt sätt.
Svar
Ändrad version av Johnbots svar utan en extra klass. Jag tror att den extra klassen är överdriven och snarare distraherar från algoritmen, eftersom jag måste söka igenom mycket kod (en hel del panna) för att se vad som händer. Det är inte den extra klassen som gör bearbetningsslingan mycket enklare. Det är algoritmen.
Ytterligare ändringar:
-
keyOffset
är inte klart för mig i vilken riktning det förskjuts, så jag döpte om det tilladdedToKey
(även för värde). - Beställde operationen namn som i problemspecifikationen, både för att hålla mig nära specifikationen och för att den ordningen är mer vettig för mig.
- Introducerade
args
för att spara lite kodupprepning. - Används
long
/Long
för allt, inte bara för summan. När allt kommer omkring kan tillägg till nycklarna / värdena få dem att rinna över om vi bara använderint
/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
- Varför lägger du enhetligt till
addedToKey
till värde ’ s-tangenten fungerar inte, men subtraherar den förinsert
ochget
-åtgärderna arbete?
Svar
Vad sägs om att bara lagra ett offsetvärde för nycklar och värden och bygga omslagsmetoder runt hashmaps get / put-metoder för att ta hänsyn till denna förskjutning.
Map
varje gång du görinsert
ellerget
frågor om du kan förklara varför jag uppskattar det.