1"""
2ํด์ ํ
์ด๋ธ (Hash Table)
3Hash Table Implementation
4
5ํด์ ํจ์์ ์ถฉ๋ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๊ตฌํํฉ๋๋ค.
6"""
7
8from typing import Any, List, Optional, Tuple
9from collections import OrderedDict
10
11
12# =============================================================================
13# 1. ํด์ ํจ์ (Hash Functions)
14# =============================================================================
15
16def hash_division(key: int, table_size: int) -> int:
17 """๋๋์
ํด์ ํจ์ - O(1)"""
18 return key % table_size
19
20
21def hash_multiplication(key: int, table_size: int, A: float = 0.6180339887) -> int:
22 """๊ณฑ์
ํด์ ํจ์ (Knuth ์ ์ A๊ฐ) - O(1)"""
23 return int(table_size * ((key * A) % 1))
24
25
26def hash_string(s: str, table_size: int) -> int:
27 """๋ฌธ์์ด ํด์ ํจ์ (๋คํญ์ ๋กค๋ง ํด์) - O(n)"""
28 hash_val = 0
29 base = 31
30 for char in s:
31 hash_val = (hash_val * base + ord(char)) % table_size
32 return hash_val
33
34
35# =============================================================================
36# 2. ์ฒด์ด๋ (Chaining)
37# =============================================================================
38
39class HashTableChaining:
40 """์ฒด์ด๋ ๋ฐฉ์ ํด์ ํ
์ด๋ธ"""
41
42 def __init__(self, size: int = 10):
43 self.size = size
44 self.table: List[List[Tuple[Any, Any]]] = [[] for _ in range(size)]
45 self.count = 0
46
47 def _hash(self, key: Any) -> int:
48 """ํด์ ํจ์"""
49 if isinstance(key, int):
50 return key % self.size
51 return hash(key) % self.size
52
53 def put(self, key: Any, value: Any) -> None:
54 """ํค-๊ฐ ์ฝ์
- ํ๊ท O(1), ์ต์
O(n)"""
55 index = self._hash(key)
56
57 # ๊ธฐ์กด ํค ์
๋ฐ์ดํธ
58 for i, (k, v) in enumerate(self.table[index]):
59 if k == key:
60 self.table[index][i] = (key, value)
61 return
62
63 # ์ ํค ์ถ๊ฐ
64 self.table[index].append((key, value))
65 self.count += 1
66
67 def get(self, key: Any) -> Optional[Any]:
68 """ํค๋ก ๊ฐ ์กฐํ - ํ๊ท O(1), ์ต์
O(n)"""
69 index = self._hash(key)
70
71 for k, v in self.table[index]:
72 if k == key:
73 return v
74 return None
75
76 def remove(self, key: Any) -> bool:
77 """ํค-๊ฐ ์ญ์ - ํ๊ท O(1), ์ต์
O(n)"""
78 index = self._hash(key)
79
80 for i, (k, v) in enumerate(self.table[index]):
81 if k == key:
82 self.table[index].pop(i)
83 self.count -= 1
84 return True
85 return False
86
87 def __contains__(self, key: Any) -> bool:
88 return self.get(key) is not None
89
90 def load_factor(self) -> float:
91 return self.count / self.size
92
93
94# =============================================================================
95# 3. ์คํ ์ด๋๋ ์ฑ - ์ ํ ํ์ฌ (Linear Probing)
96# =============================================================================
97
98class HashTableLinearProbing:
99 """์ ํ ํ์ฌ ๋ฐฉ์ ํด์ ํ
์ด๋ธ"""
100
101 DELETED = object() # ์ญ์ ๋ง์ปค
102
103 def __init__(self, size: int = 10):
104 self.size = size
105 self.keys: List[Any] = [None] * size
106 self.values: List[Any] = [None] * size
107 self.count = 0
108
109 def _hash(self, key: Any) -> int:
110 if isinstance(key, int):
111 return key % self.size
112 return hash(key) % self.size
113
114 def _probe(self, key: Any, for_insert: bool = False) -> int:
115 """์ ํ ํ์ฌ๋ก ์ฌ๋กฏ ์ฐพ๊ธฐ"""
116 index = self._hash(key)
117 first_deleted = -1
118
119 for i in range(self.size):
120 probe_index = (index + i) % self.size
121
122 if self.keys[probe_index] is None:
123 if for_insert and first_deleted != -1:
124 return first_deleted
125 return probe_index
126
127 if self.keys[probe_index] is self.DELETED:
128 if first_deleted == -1:
129 first_deleted = probe_index
130 continue
131
132 if self.keys[probe_index] == key:
133 return probe_index
134
135 return first_deleted if first_deleted != -1 else -1
136
137 def put(self, key: Any, value: Any) -> bool:
138 """ํค-๊ฐ ์ฝ์
- ํ๊ท O(1), ์ต์
O(n)"""
139 if self.count >= self.size * 0.7: # ๋ก๋ ํฉํฐ 70%
140 self._resize()
141
142 index = self._probe(key, for_insert=True)
143 if index == -1:
144 return False
145
146 if self.keys[index] is None or self.keys[index] is self.DELETED:
147 self.count += 1
148
149 self.keys[index] = key
150 self.values[index] = value
151 return True
152
153 def get(self, key: Any) -> Optional[Any]:
154 """ํค๋ก ๊ฐ ์กฐํ - ํ๊ท O(1), ์ต์
O(n)"""
155 index = self._probe(key)
156
157 if index != -1 and self.keys[index] not in (None, self.DELETED):
158 return self.values[index]
159 return None
160
161 def remove(self, key: Any) -> bool:
162 """ํค-๊ฐ ์ญ์ (์ญ์ ๋ง์ปค ์ฌ์ฉ) - ํ๊ท O(1), ์ต์
O(n)"""
163 index = self._probe(key)
164
165 if index != -1 and self.keys[index] not in (None, self.DELETED):
166 self.keys[index] = self.DELETED
167 self.values[index] = None
168 self.count -= 1
169 return True
170 return False
171
172 def _resize(self) -> None:
173 """ํ
์ด๋ธ ํฌ๊ธฐ 2๋ฐฐ ํ์ฅ"""
174 old_keys = self.keys
175 old_values = self.values
176
177 self.size *= 2
178 self.keys = [None] * self.size
179 self.values = [None] * self.size
180 self.count = 0
181
182 for k, v in zip(old_keys, old_values):
183 if k is not None and k is not self.DELETED:
184 self.put(k, v)
185
186
187# =============================================================================
188# 4. ์คํ ์ด๋๋ ์ฑ - ์ด์ค ํด์ฑ (Double Hashing)
189# =============================================================================
190
191class HashTableDoubleHashing:
192 """์ด์ค ํด์ฑ ๋ฐฉ์ ํด์ ํ
์ด๋ธ"""
193
194 DELETED = object()
195
196 def __init__(self, size: int = 11): # ์์ ๊ถ์ฅ
197 self.size = size
198 self.keys: List[Any] = [None] * size
199 self.values: List[Any] = [None] * size
200 self.count = 0
201
202 def _hash1(self, key: Any) -> int:
203 if isinstance(key, int):
204 return key % self.size
205 return hash(key) % self.size
206
207 def _hash2(self, key: Any) -> int:
208 """๋ ๋ฒ์งธ ํด์ ํจ์ (0์ด ์๋ ๊ฐ ๋ฐํ)"""
209 if isinstance(key, int):
210 return 7 - (key % 7) # 7์ size๋ณด๋ค ์์ ์์
211 return 7 - (hash(key) % 7)
212
213 def _probe(self, key: Any, for_insert: bool = False) -> int:
214 """์ด์ค ํด์ฑ์ผ๋ก ์ฌ๋กฏ ์ฐพ๊ธฐ"""
215 h1 = self._hash1(key)
216 h2 = self._hash2(key)
217 first_deleted = -1
218
219 for i in range(self.size):
220 index = (h1 + i * h2) % self.size
221
222 if self.keys[index] is None:
223 if for_insert and first_deleted != -1:
224 return first_deleted
225 return index
226
227 if self.keys[index] is self.DELETED:
228 if first_deleted == -1:
229 first_deleted = index
230 continue
231
232 if self.keys[index] == key:
233 return index
234
235 return first_deleted if first_deleted != -1 else -1
236
237 def put(self, key: Any, value: Any) -> bool:
238 index = self._probe(key, for_insert=True)
239 if index == -1:
240 return False
241
242 if self.keys[index] is None or self.keys[index] is self.DELETED:
243 self.count += 1
244
245 self.keys[index] = key
246 self.values[index] = value
247 return True
248
249 def get(self, key: Any) -> Optional[Any]:
250 index = self._probe(key)
251 if index != -1 and self.keys[index] not in (None, self.DELETED):
252 return self.values[index]
253 return None
254
255 def remove(self, key: Any) -> bool:
256 index = self._probe(key)
257 if index != -1 and self.keys[index] not in (None, self.DELETED):
258 self.keys[index] = self.DELETED
259 self.values[index] = None
260 self.count -= 1
261 return True
262 return False
263
264
265# =============================================================================
266# 5. LRU ์บ์ (Least Recently Used Cache)
267# =============================================================================
268
269class LRUCache:
270 """
271 LRU ์บ์ ๊ตฌํ
272 OrderedDict๋ฅผ ์ฌ์ฉํ O(1) get/put
273 """
274
275 def __init__(self, capacity: int):
276 self.capacity = capacity
277 self.cache = OrderedDict()
278
279 def get(self, key: Any) -> Optional[Any]:
280 """ํค ์กฐํ ๋ฐ ์ต๊ทผ ์ฌ์ฉ์ผ๋ก ์ด๋ - O(1)"""
281 if key not in self.cache:
282 return None
283
284 # ์ต๊ทผ ์ฌ์ฉ์ผ๋ก ์ด๋
285 self.cache.move_to_end(key)
286 return self.cache[key]
287
288 def put(self, key: Any, value: Any) -> None:
289 """ํค-๊ฐ ์ฝ์
- O(1)"""
290 if key in self.cache:
291 self.cache.move_to_end(key)
292 else:
293 if len(self.cache) >= self.capacity:
294 # ๊ฐ์ฅ ์ค๋๋ ํญ๋ชฉ ์ ๊ฑฐ
295 self.cache.popitem(last=False)
296
297 self.cache[key] = value
298
299 def __str__(self) -> str:
300 return str(list(self.cache.items()))
301
302
303# =============================================================================
304# 6. ์ค์ ๋ฌธ์ : Two Sum
305# =============================================================================
306
307def two_sum(nums: List[int], target: int) -> Optional[Tuple[int, int]]:
308 """
309 ํฉ์ด target์ธ ๋ ์์ ์ธ๋ฑ์ค ์ฐพ๊ธฐ
310 ์๊ฐ๋ณต์ก๋: O(n), ๊ณต๊ฐ๋ณต์ก๋: O(n)
311 """
312 seen = {} # ๊ฐ -> ์ธ๋ฑ์ค
313
314 for i, num in enumerate(nums):
315 complement = target - num
316 if complement in seen:
317 return (seen[complement], i)
318 seen[num] = i
319
320 return None
321
322
323# =============================================================================
324# 7. ์ค์ ๋ฌธ์ : ๋น๋ ์ธ๊ธฐ
325# =============================================================================
326
327def frequency_count(arr: List[Any]) -> dict:
328 """
329 ๋ฐฐ์ด ์์์ ๋น๋ ๊ณ์ฐ
330 ์๊ฐ๋ณต์ก๋: O(n), ๊ณต๊ฐ๋ณต์ก๋: O(k) (k = ๊ณ ์ ์์ ์)
331 """
332 freq = {}
333 for item in arr:
334 freq[item] = freq.get(item, 0) + 1
335 return freq
336
337
338def most_frequent(arr: List[Any]) -> Optional[Any]:
339 """๊ฐ์ฅ ๋น๋ฒํ ์์ ์ฐพ๊ธฐ"""
340 freq = frequency_count(arr)
341 if not freq:
342 return None
343 return max(freq, key=freq.get)
344
345
346# =============================================================================
347# 8. ์ค์ ๋ฌธ์ : ๋ถ๋ถ ๋ฐฐ์ด ํฉ์ด K์ธ ๊ฐ์
348# =============================================================================
349
350def subarray_sum_count(nums: List[int], k: int) -> int:
351 """
352 ํฉ์ด k์ธ ์ฐ์ ๋ถ๋ถ ๋ฐฐ์ด ๊ฐ์
353 ํ๋ฆฌํฝ์ค ํฉ + ํด์๋งต ํ์ฉ
354 ์๊ฐ๋ณต์ก๋: O(n), ๊ณต๊ฐ๋ณต์ก๋: O(n)
355 """
356 count = 0
357 prefix_sum = 0
358 prefix_count = {0: 1} # ํ๋ฆฌํฝ์ค ํฉ -> ๋ฑ์ฅ ํ์
359
360 for num in nums:
361 prefix_sum += num
362
363 # prefix_sum - k๊ฐ ์ด์ ์ ์์๋ค๋ฉด, ๊ทธ ์ฌ์ด๊ฐ ํฉ k
364 if prefix_sum - k in prefix_count:
365 count += prefix_count[prefix_sum - k]
366
367 prefix_count[prefix_sum] = prefix_count.get(prefix_sum, 0) + 1
368
369 return count
370
371
372# =============================================================================
373# 9. ์ค์ ๋ฌธ์ : ์๋๊ทธ๋จ ๊ทธ๋ฃน
374# =============================================================================
375
376def group_anagrams(strs: List[str]) -> List[List[str]]:
377 """
378 ์๋๊ทธ๋จ๋ผ๋ฆฌ ๊ทธ๋ฃนํ
379 ์๊ฐ๋ณต์ก๋: O(n * k log k), n=๋ฌธ์์ด ์, k=์ต๋ ๋ฌธ์์ด ๊ธธ์ด
380 """
381 groups = {}
382
383 for s in strs:
384 # ์ ๋ ฌ๋ ๋ฌธ์์ด์ ํค๋ก ์ฌ์ฉ
385 key = ''.join(sorted(s))
386 if key not in groups:
387 groups[key] = []
388 groups[key].append(s)
389
390 return list(groups.values())
391
392
393# =============================================================================
394# ํ
์คํธ
395# =============================================================================
396
397def main():
398 print("=" * 60)
399 print("ํด์ ํ
์ด๋ธ (Hash Table) ์์ ")
400 print("=" * 60)
401
402 # 1. ํด์ ํจ์ ํ
์คํธ
403 print("\n[1] ํด์ ํจ์")
404 print(f" hash_division(42, 10) = {hash_division(42, 10)}")
405 print(f" hash_multiplication(42, 10) = {hash_multiplication(42, 10)}")
406 print(f" hash_string('hello', 10) = {hash_string('hello', 10)}")
407
408 # 2. ์ฒด์ด๋ ํ
์คํธ
409 print("\n[2] ์ฒด์ด๋ ๋ฐฉ์")
410 ht_chain = HashTableChaining(5)
411 for i, name in enumerate(['Alice', 'Bob', 'Charlie', 'David', 'Eve']):
412 ht_chain.put(name, i * 10)
413 print(f" get('Charlie') = {ht_chain.get('Charlie')}")
414 print(f" 'Bob' in table = {'Bob' in ht_chain}")
415 print(f" load_factor = {ht_chain.load_factor():.2f}")
416
417 # 3. ์ ํ ํ์ฌ ํ
์คํธ
418 print("\n[3] ์ ํ ํ์ฌ ๋ฐฉ์")
419 ht_linear = HashTableLinearProbing(10)
420 for i in range(7):
421 ht_linear.put(i * 5, f"value_{i}")
422 print(f" get(10) = {ht_linear.get(10)}")
423 ht_linear.remove(10)
424 print(f" get(10) after remove = {ht_linear.get(10)}")
425
426 # 4. LRU ์บ์ ํ
์คํธ
427 print("\n[4] LRU ์บ์")
428 cache = LRUCache(3)
429 cache.put('a', 1)
430 cache.put('b', 2)
431 cache.put('c', 3)
432 print(f" ์บ์: {cache}")
433 cache.get('a') # 'a'๋ฅผ ์ต๊ทผ์ผ๋ก ์ด๋
434 cache.put('d', 4) # 'b' ์ ๊ฑฐ๋จ
435 print(f" get('a'), put('d') ํ: {cache}")
436
437 # 5. Two Sum
438 print("\n[5] Two Sum")
439 nums = [2, 7, 11, 15]
440 target = 9
441 result = two_sum(nums, target)
442 print(f" nums = {nums}, target = {target}")
443 print(f" ๊ฒฐ๊ณผ: ์ธ๋ฑ์ค {result}")
444
445 # 6. ๋น๋ ์ธ๊ธฐ
446 print("\n[6] ๋น๋ ์ธ๊ธฐ")
447 arr = ['a', 'b', 'a', 'c', 'a', 'b']
448 freq = frequency_count(arr)
449 print(f" ๋ฐฐ์ด: {arr}")
450 print(f" ๋น๋: {freq}")
451 print(f" ์ต๋น๊ฐ: {most_frequent(arr)}")
452
453 # 7. ๋ถ๋ถ ๋ฐฐ์ด ํฉ
454 print("\n[7] ๋ถ๋ถ ๋ฐฐ์ด ํฉ์ด K์ธ ๊ฐ์")
455 nums = [1, 1, 1]
456 k = 2
457 count = subarray_sum_count(nums, k)
458 print(f" nums = {nums}, k = {k}")
459 print(f" ๊ฒฐ๊ณผ: {count}๊ฐ")
460
461 # 8. ์๋๊ทธ๋จ ๊ทธ๋ฃน
462 print("\n[8] ์๋๊ทธ๋จ ๊ทธ๋ฃน")
463 strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
464 groups = group_anagrams(strs)
465 print(f" ์
๋ ฅ: {strs}")
466 print(f" ๊ทธ๋ฃน: {groups}")
467
468 print("\n" + "=" * 60)
469
470
471if __name__ == "__main__":
472 main()