[코딩테스트 합격자 되기 스터디] 3주차 - 해시 (7.21 ~ 7.27)
코딩테스트 합격자 되기 스터디 3주차
강의 보기 -> 해당되는 부분 책 다시 읽기 -> 블로그 정리
강의는 C++로 되어 있지만 개념적인 내용이라 언어 상관 없이 보고 코드는 각 언어에 맞게 바꾸면 된다.
참고 강의 및 책
인프런 : 코딩테스트 합격자 되기 파이썬, C+_+ 저자님 인프런 강의 링크
Youtube : 코딩테스트 합격자 되기 파이썬, C+_+ 저자님 Youtube 링크
종이책 : 코딩테스트 합격자 되기 파이썬 편
종이책 : 코딩테스트 합격자 되기 C++ 편
종이책 : 코딩테스트 합격자 되기 자바스크립트 편
종이책 : 코딩테스트 합격자 되기 자바 편
해시
- 해시함수를 사용해서 변환한 값을 인덱스로 삼아 키-값을 저장해 빠른 데이터를 탐색하는 자료구조
해시 함수
- 임의의 키를 해시테이블의 인덱스로 변경해주는 함수
- 해시 테이블의 크기가 N이라면 해시 함수는 0 ~ (N - 1) 사이 값
- 충돌을 최소화 할수록 좋은 해시 함수
- 나눗셈법
- 값을 해시 테이블(N)의 크기로 나눈 나머지를 해시값으로 사용하는 방법
- h(x) = x mod k (x는 키, k는 소수)
- k로 나눈 나머지는 0 ~ (k - 1) 이므로 해시 테이블의 크기는 k
- k가 소수인 이유는 충돌을 줄이기 위함(소수가 아니면 k주기로 해시 값 반복)
- 7 % 2 -> 2로 나누면 0 또는 1로 반복, 충돌이 엄청 많이 발생
- 곱셈법
- 소수를 활용하지 않고 황금비를 곱하는 방식
- h(x) = (((x * A) mod 1) * m)
- 문자열 해싱
- 문자열의 아스키 코드 값을 활용한 해싱
- hash(s) = (s[0] + s[1] * p + s[2] * p^2 + … + s[n-1] * p^n-1) mod m
해시 충돌
- 서로 다른키에 대해 해시 함수 결과 값이 같을 경우 충돌 발생
- 체이닝, 개방 주소법이 충돌을 처리하는 대표적인 방법
- 체이닝
- 충돌 발생 시, 해당 버킷에 링크드리스트로 데이터를 연결
- 특정 버킷에 데이터가 N개인 경우, 원하는 값을 찾는데 O(N)이 걸릴 수 있음
- 개방 주소법 - 선형 탐사
- 충돌 발생 시, 다른 빈 버킷을 찾아 충돌 값을 삽입
- 최대한 기존 테이블을 사용하자는 방식(메모리를 아낄 수 있음)
- h(k,i) = (h(k) + i) mod m
- k는 키, i는 충돌 시 이동할 버킷 수, m은 버킷 사이즈
- 개방 주소법 - 이중 해싱
- 해시 함수를 2개 사용(경우에 따라 N개 까지 확장)
- 기존에 i를 더했던 선형 탐사와 다르게 2번째 해시함수에 i를 곱하는 방식도 가능
- 이 때 클러스터를 줄이기 위해 m을 제곱수나 소수로 함
- h(k,i) = (h1(k) + i * h2(k)) mod m
- k는 키, i는 임의의 수, h1은 첫 번째 해시 함수, h2는 두 번째 해시 함수, m은 테이블 크기
해시 활용
- 키, 값 쌍으로 연관된 데이터가 존재하며, 해당 값에 대한 접근이 빈번한 경우
- 사전(단어를 검색)이나 연락처(이름을 검색)
- 중복되지 않는 키를 사용하는 경우
- 학번이나 집 주소(중복되지 않음)
해시를 사용하지 말아야 하는 경우
- 특정 키에 여러 가지 값을 매칭해야 하는 경우
- 키 1개에 2개의 값이 생기지 않고 값이 갱신되어 저장
오픈 채팅방 문제 풀이
- 오픈 채팅방
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public String[] solution(String[] record) { HashMap<String, String> map = new HashMap<>(); List<String[]> list = new ArrayList<>(); for (String value : record) { String[] str = value.split(" "); if (str[0].equals("Enter") || str[0].equals("Change")) { map.put(str[1], str[2]); } if (str[0].equals("Enter")) { list.add(new String[] {str[1], "님이 들어왔습니다."}); } else if (str[0].equals("Leave")) { list.add(new String[] {str[1], "님이 나갔습니다."}); } } String[] result = new String[list.size()]; for (int i = 0; i < list.size(); i++) { String[] strValue = list.get(i); result[i] = map.get(strValue[0]) + strValue[1]; } return result; }- 시간복잡도: O(N * M)
메뉴 리뉴얼 문제 풀이
- 메뉴 리뉴얼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private static HashMap<Integer, HashMap<String, Integer>> courseMap; public String[] solution(String[] orders, int[] course) { courseMap = new HashMap<>(); for (int i : course) { courseMap.put(i, new HashMap<>()); } for (String order : orders) { char[] orderArray = order.toCharArray(); Arrays.sort(orderArray); combinations(0, orderArray, ""); } ArrayList<String> answer = new ArrayList<>(); for (HashMap<String, Integer> count : courseMap.values()) { count.values() .stream() .max(Comparator.comparingInt(o -> o)) .ifPresent(cnt -> count.entrySet() .stream() .filter(entry -> cnt.equals(entry.getValue()) && cnt > 1) .forEach(entry -> answer.add(entry.getKey()))); } Collections.sort(answer); return answer.toArray(new String[0]); } public static void combinations(int idx, char[] order, String result) { if (courseMap.containsKey(result.length())) { HashMap<String, Integer> map = courseMap.get(result.length()); map.put(result, map.getOrDefault(result, 0) + 1); } for (int i = idx; i < order.length; i++) { combinations(i + 1, order, result + order[i]); } }- 못 풀었다… ↑ 다른 사람의 풀이 ↑
- 시간복잡도: O(N * 2^M)