코딩테스트

해시 추가문제

개발자 김마늘 2025. 5. 5. 23:38

217. Contains Duplicate

정수 배열 nums가 주어졌을 때, 배열에 값이 두 번 이상 나타나면 true를 반환하고, 모든 요소가 다르면 false를 반환합니다.

 

제약사항

  • 1 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
class Solution {
    public boolean containsDuplicate(int[] nums) {
        Set<Integer> appear = new HashSet<>();

        for(int num : nums){
            if (!appear.add(num)) 
                return true;
        }

        return false;
    }
}

 


1436. Destination City

배열 paths가 주어집니다. 여기서 paths[i] = [cityAi, cityBi]는 cityAi에서 cityBi로 가는 직접 경로가 존재함을 의미합니다. 목적지 도시, 즉 다른 도시로 나가는 경로가 없는 도시를 반환합니다.

경로 그래프는 루프가 없는 직선을 형성하므로 목적지 도시는 정확히 하나라는 것이 보장됩니다.

 

제약사항

  • 1 <= paths.length <= 100
  • path[i].length == 2
  • 1 <= cityAi.length, cityBi.length <= 10
  • cityAi != cityBi
  • 모든 문자열은 소문자와 대문자의 영어 문자와 공백 문자로 구성됩니다.

아이디어

hashmap에 출발지에 있었던 횟수를 담는다. 만약 도착지가 Hashmap에 없다면 0으로 담습니다.

최종적으로 value값이 0인 것이 답이 됩니다.

class Solution {
    public String destCity(List<List<String>> paths) {
        Map<String, Integer> mp = new HashMap<>();

        for(List<String> path : paths){
            String st = path.get(0);
            String dt = path.get(1);
            mp.put(st, mp.getOrDefault(st, 0) + 1);
            if (mp.getOrDefault(dt, 0) == 0) {
                mp.put(dt, 0);
            }
        }

        for(String key : mp.keySet()){
            if (mp.get(key) == 0) return key;
        }

        return "error";
    }
}

 

모범답안

class Solution {
    public String destCity(List<List<String>> paths) {
        HashMap<String, String> map = new HashMap<>();

        for(List<String> path: paths){
            map.put(path.get(0), path.get(1));
        }

        String city = paths.get(0).get(0);

        while(map.keySet().contains(city)){
            city = map.get(city);
        }
        
        return city;
    }
}

 


1496. Path Crossing

문자열 경로가 주어지고, path[i] = 'N', 'S', 'E' 또는 'W'는 각각 북쪽, 남쪽, 동쪽, 서쪽으로 한 단위씩 이동함을 나타냅니다. 2D 평면의 원점 (0, 0)에서 출발하여 path로 지정된 경로를 따라 이동합니다. 경로가 임의의 지점에서 교차하는 경우, 즉 이전에 방문한 위치에 있는 경우 true를 반환합니다. 그렇지 않으면 false를 반환합니다.

 

제약사항

  • 1 <= path.length <= 10^4
  • path[i]는 'N', 'S', 'E' 또는 'W'입니다.
class Solution {
    private int[] diff(char dir){
        int[] arr = new int[2];
        switch(dir){
            case 'N': arr[0] = 1; break;
            case 'S': arr[0] = -1; break;
            case 'E': arr[1] = 1; break;
            case 'W': arr[1] = -1; break;
        }

        return arr;
    }
    
    public boolean isPathCrossing(String path) {
        int x = 0;
        int y = 0;
        Set<String> points = new HashSet<>();
        points.add(x + " " + y);
        for(char ch : path.toCharArray()){
            int[] arr = diff(ch);
            y += arr[0];
            x += arr[1];
            if (!points.add(x + " " + y)){
                return true;
            }
        }

        return false;
    }
}


1748. Sum of Unique Elements

정수 배열 nums가 주어집니다. 배열의 고유한 요소는 배열에 정확히 한 번 나타나는 요소입니다.

nums의 모든 고유한 요소의 합계를 반환합니다.

 

제약사항

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 100
class Solution {
    public int sumOfUnique(int[] nums) {
        Map<Integer, Integer> cnt = new HashMap<>();

        for(int num : nums){
            cnt.put(num, cnt.getOrDefault(num, 0) + 1);
        }

        int sum = 0;
        for(int key : cnt.keySet()){
            int value = cnt.get(key);
            if (value == 1) sum += key;
        }

        return sum;
    }
}


3005. Count Elements With Maximum Frequency

양의 정수로 구성된 nums 배열이 주어집니다. nums 배열에 있는 모든 요소의 총 빈도를 반환하되, 해당 요소가 모두 최대 빈도를 갖도록 합니다. 요소의 빈도는 배열에서 해당 요소가 나타나는 횟수입니다.

 

제약사항

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 100
class Solution {
    public int maxFrequencyElements(int[] nums) {
        Map<Integer, Integer> map = new HashMap<>();
        int max = 0;

        for(int num : nums){
            map.put(num, map.getOrDefault(num, 0) + 1);
            max = Math.max(max, map.get(num));
        }

        int sum = 0;
        for(int value : map.values()){
            if (value == max) sum += value;
        }

        return sum;
    }
}

1394. Find Lucky Integer in an Array

정수 arr 배열이 주어졌을 때, 행운의 정수는 배열에서 해당 값과 같은 빈도를 갖는 정수입니다. 배열에서 가장 큰 행운의 정수를 반환합니다. 행운의 정수가 없으면 -1을 반환합니다.

 

제약사항

  • 1 <= arr.length <= 500
  • 1 <= arr[i] <= 500
class Solution {
    public int findLucky(int[] arr) {
        Map<Integer, Integer> cnt = new HashMap<>();

        for(int num : arr){
            cnt.put(num, cnt.getOrDefault(num, 0) + 1);
        }

        int max = -1;
        for(int key : cnt.keySet()){
            if (key == cnt.get(key)) max = key;
        }

        return max;
    }
}


1207. Unique Number of Occurrences

정수 arr 배열이 주어졌을 때, 배열 내 각 값의 발생 횟수가 고유한 경우 true를 반환하고 그렇지 않으면 false를 반환합니다.

 

제약사항

  • 1 <= arr.length <= 1000
  • -1000 <= arr[i] <= 1000
class Solution {
    public boolean uniqueOccurrences(int[] arr) {
        Map<Integer, Integer> cnt = new HashMap<>();
        Set<Integer> occurrences = new HashSet<>();

        for(int num : arr){
            cnt.put(num, cnt.getOrDefault(num, 0) + 1);
        }

        for(int key : cnt.keySet()){
            if (!occurrences.add(cnt.get(key))) return false;
        }

        return true;
    }
}


451. Sort Characters By Frequency

문자열 s가 주어졌을 때, 문자의 빈도를 기준으로 내림차순으로 정렬합니다. 문자의 빈도는 문자열에 나타나는 횟수입니다. 정렬된 문자열을 반환합니다. 답이 여러 개인 경우, 그중 하나를 반환합니다.

 

제약사항

  • 1 <= s.length <= 5 * 10^5
  • s는 대문자, 소문자 영어 문자와 숫자로 구성됩니다.
class Solution {
    public class Pair {
        char key;
        int value;

        public Pair(char key, int value){
            this.key = key;
            this.value = value;
        }
    }
    public String frequencySort(String s) {
        Map<Character, Integer> cnt = new HashMap<>();

        for(int i = 0; i < s.length(); i++){
            char ch = s.charAt(i);
            cnt.put(ch, cnt.getOrDefault(ch, 0) + 1);
        }

        List<Pair> list = new ArrayList<>();
        for(Map.Entry<Character, Integer> entry : cnt.entrySet()){
            char key = entry.getKey();
            int value = entry.getValue();
            list.add(new Pair(key, value));
        }
        Collections.sort(list, (p1, p2) -> Integer.compare(p2.value, p1.value));

        StringBuilder sb = new StringBuilder();
        for(Pair pair : list){
            for(int i = 0; i < pair.value; i++){
                sb.append(pair.key);
            }
        }

        return sb.toString();
    }
}

굳이 Pair를 쓰지 않아도 될 것 같다. 정렬기준은 value만 쓰기 때문이다. 그리고 필요한건 character이기 떄문에 Pair 사용하지 말자


2958. Length of Longest Subarray With at Most K Frequency

정수 배열 nums와 정수 k가 주어집니다. 요소 x의 빈도는 배열에서 해당 요소가 나타나는 횟수입니다. 각 요소의 빈도가 k보다 작거나 같으면 해당 배열을 good 배열이라고 합니다. nums의 가장 긴 good 부분 배열의 길이를 반환합니다. 부분 배열은 배열 내에서 비어 있지 않고 연속된 요소 시퀀스입니다.

 

제약사항

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • 1 <= k <= nums.length

아이디어: substring -> k보다 작거나 같은 조건 -> 윈도우 슬라이딩

class Solution {
    public int maxSubarrayLength(int[] nums, int k) {
        int n = nums.length;
        if (n == 1) return 1;
        Map<Integer, Integer> cnt = new HashMap<>();
        int l = 0;
        int ans = 0;
        for(int r = 0; r < n; r++){
            cnt.put(nums[r], cnt.getOrDefault(nums[r], 0) + 1);
            while(cnt.get(nums[r]) > k) {
                cnt.put(nums[l], cnt.get(nums[l]) - 1);
                l++;
            }
            ans = Math.max(ans, r - l + 1);
        }

        return ans;
    }
}

보니까 nums.length를 캐싱하지 않아서 조금 느려진거였다. 풀이는 비슷했다.


1512. Number of Good Pairs

정수 nums 배열이 주어졌을 때, 좋은 쌍의 개수를 반환합니다. (i, j) 쌍은 nums[i] == nums[j]이고 i < j일 때 좋은 쌍이라고 합니다.

 

제약사항

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 100

아이디어

원소값을 키, 나타난 인덱스 리스트를 value로 갖는 해시맵을 만든다.

나중에 nums를 돌면서 해시맵을 채우면 value인 리스트는 오름차순 정렬이 알아서 된다.

그러면 리스트 가지고 조합!

class Solution {
    private int countPair(List<Integer> list){
        int n = list.size();
        int cnt = 0;
        for(int i = 0; i < n; i++){
            for(int j = i + 1; j < n; j++){
                cnt++;
            }
        }

        return cnt;
    }
    public int numIdenticalPairs(int[] nums) {
        int n = nums.length;
        Map<Integer, List<Integer>> cnt = new HashMap<>();
        for(int i = 0; i < n; i++) {
            if (!cnt.containsKey(nums[i])){
                cnt.put(nums[i], new ArrayList<>());
            }
            cnt.get(nums[i]).add(i);
        }
        int ans = 0;
        for(List<Integer> value : cnt.values()){
            ans += countPair(value);
        }

        return ans;
    }
}


930. Binary Subarrays With Sum

이진 배열 nums와 정수 goal이 주어졌을 때, 비어 있지 않은 부분 배열의 개수를 합을 구하는 goal과 함께 반환합니다. 부분 배열은 배열의 연속된 부분입니다.

 

제약조건

  • 1 <= nums.length <= 4 * 10^4
  • nums[i] is either 0 or 1
  • 0 <= goal <= nums.length

아이디어

특정 조건을 만족하는 subarray -> prefix sum과 hashmap 조합으로 가보자

prefix 업데이트

특정 조건 만족하면 +1

prefixsum - k 개수만큼 +

해시맵 업데이트

class Solution {
    public int numSubarraysWithSum(int[] nums, int goal) {
        Map<Integer, Integer> cnt = new HashMap<>();
        int psum = 0;
        int ans = 0;

        int n = nums.length;
        for(int i = 0; i < n; i++){
            psum += nums[i];
            if (psum == goal) ans += 1;
            if (cnt.containsKey(psum - goal)){
                ans += cnt.get(psum - goal);
            }
            cnt.put(psum, cnt.getOrDefault(psum, 0) + 1);
        }

        return ans;
    }
}


1695. Maximum Erasure Value

양의 정수 nums로 구성된 배열이 주어지고 고유한 요소를 포함하는 부분 배열을 지우려고 합니다. 부분 배열을 지워서 얻는 점수는 요소의 합과 같습니다. 부분 배열 하나만 지워서 얻을 수 있는 최대 점수를 반환합니다. 배열 b가 a의 연속적인 부분 시퀀스를 형성할 때, 즉 (l, r)에 대해 a[l], a[l 1],..., a[r]과 같을 때, b가 a의 부분 배열이라고 합니다.

 

제약사항

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^4

아이디어

슬라이딩 윈도우

class Solution {
    public int maximumUniqueSubarray(int[] nums) {
        Set<Integer> set = new HashSet<>();
        int l = 0;
        int max = 0;
        int psum = 0;
        int n = nums.length;
        for(int r = 0; r < n; r++){
            psum += nums[r];
            while (!set.add(nums[r])){
                set.remove(nums[l]);
                psum -= nums[l++];
            }
            max = Math.max(max, psum);
        }

        return max;
    }
}


567. Permutation in String

두 문자열 s1과 s2가 주어졌을 때, s2가 s1의 순열을 포함하면 true를 반환하고, 그렇지 않으면 false를 반환합니다. 즉, s1의 순열 중 하나가 s2의 부분 문자열이면 true를 반환합니다.

 

제약사항

  • 1 <= s1.length, s2.length <= 10^4
  • s1 and s2는 영어 소문자로 이루어져있다.

아이디어

순열을 구해서 대입하는 방식은 n!이라서 시간복잡도 초과

순열인지 확인하는 방법 -> s1의 알파벳이 포함되었는가 (횟수도)

s1의 알파벳과 그 횟수를 정확히 가진 substring이 있는가

고정된 window 크기 = s1의 길이

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        char[] arr = s2.toCharArray();
        int n = arr.length;
        int k = s1.length();
        if (k > n) return false;

        Map<Character, Integer> cnt = new HashMap<>();
        for(char ch : s1.toCharArray()){
            cnt.put(ch, cnt.getOrDefault(ch, 0) + 1);
        }

        Map<Character, Integer> temp = new HashMap<>();
        for(int i = 0; i < k; i++){
            temp.put(arr[i], temp.getOrDefault(arr[i], 0) + 1);
        }
        if (cnt.equals(temp)) return true;

        for(int r = k; r < n; r++){
            temp.put(arr[r], temp.getOrDefault(arr[r], 0) + 1);
            int l = r - k;
            temp.put(arr[l], temp.get(arr[l]) - 1);
            if (temp.get(arr[l]) <= 0) temp.remove(arr[l]);
            if (cnt.equals(temp)) {
                return true;
            }
        }
        
        return false;
    }
}

가장 빠른 답변의 경우 해시맵이 아닌 int[26] 배열로 해결 -> 제약사항 s1, s2가 모두 영어 소문자로 이루어져있음을 이용

윈도우로 오른쪽거 빼주고 왼쪽거 더해준다음 모두 0인지 확인.


205. Isomorphic Strings

두 문자열 s와 t가 주어졌을 때, 두 문자열이 동형인지 판별하십시오. 두 문자열 s와 t는 s의 문자를 t로 바꿀 수 있을 때 동형입니다. 모든 문자는 문자 순서를 유지하면서 다른 문자로 바뀌어야 합니다. 두 문자가 같은 문자에 대응될 수는 없지만, 문자는 자기 자신에 대응될 수 있습니다.

 

제약사항

  • 1 <= s.length <= 5 * 10^4
  • t.length == s.length
  • s and t는 아스키 문자로 이루어져있다.

아이디어

동형 => 문자 순서를 유지하면서 문자가 바뀐다.

122 == 122

s1을 숫자로 바꾼다. s2도 숫자로 바꿔서 둘이 같은지 비교

class Solution {
    public boolean isIsomorphic(String s, String t) {
        Map<Character, Integer> mp1 = new HashMap<>();
        StringBuilder sb1 = new StringBuilder();
        int i = 0;
        for(char ch : s.toCharArray()){
            if (!mp1.containsKey(ch)) mp1.put(ch, i++);
            sb1.append(mp1.get(ch) + " ");
        }

        Map<Character, Integer> mp2 = new HashMap<>();
        StringBuilder sb2 = new StringBuilder();
        int j = 0;
        for(char ch : t.toCharArray()){
            if (!mp2.containsKey(ch)) mp2.put(ch, j++);
            sb2.append(mp2.get(ch) + " "); 
        }
        
        return sb1.toString().equals(sb2.toString());
    }
}

 

시간을 줄여보자

아이디어

s를 숫자로 매칭하고 t를 볼 때 s와 같은 숫자를 매칭하는지 확인

예를 들어 s = "egg", t = "add"일 때

e: 0 g: 1일테니까 a: 0이고 d: 1일테니까 지금 보고 있는 문자의 value가 s의 Value와 같은지

class Solution {
    public boolean isIsomorphic(String s, String t) {
        Map<Character, Integer> mp1 = new HashMap<>();
        Map<Character, Integer> mp2 = new HashMap<>();

        int num = 0;
        for(int i = 0; i < s.length(); i++){
            char s_ch = s.charAt(i);
            char t_ch = t.charAt(i);
            if (!mp1.containsKey(s_ch)) mp1.put(s_ch, num);
            if (!mp2.containsKey(t_ch)) mp2.put(t_ch, num);
            if (mp1.get(s_ch) != mp2.get(t_ch)) return false;
            num++;
        }

        return true;
    }
}


290. Word Pattern

패턴과 문자열 s가 주어졌을 때, s가 같은 패턴을 따르는지 확인하세요. 여기서 follow는 pattern의 글자와 s의 비어 있지 않은 단어 사이에 단사 관계가 존재하는 완전 일치를 의미합니다. 구체적으로는 

  • pattern의 각 글자는 s의 고유한 단어 하나에 정확히 매핑됩니다. 
  • s의 고유한 단어는 pattern의 글자 하나에 정확히 매핑됩니다. 
  • 두 글자가 같은 단어에 매핑되지 않고, 두 단어가 같은 글자에 매핑되지 않습니다.

제약사항

  • 1 <= pattern.length <= 300
  • pattern은 오직 영어 소문자로 이루어져있다.
  • 1 <= s.length <= 3000
  • s는 영어 소문자랑 공백으로만 이루어져있다.
  • s는 처음과 맨 끝에 공백을 포함하지 않는다.
  • s의 모든 단어들은 한 칸 공백으로 분리되어있다.

아이디어

s를 공백으로 나누고 포인터로 같이 이동하면서 확인한다.

class Solution {
    public boolean wordPattern(String pattern, String s) {
        Map<Character, String> map = new HashMap<>();
        Set<String> already = new HashSet<>();
        String[] tokens = s.split(" ");
        int n1 = pattern.length();
        int n2 = tokens.length;
        if (n1 != n2) return false;
        for(int i = 0; i < n1; i++){
            char ch = pattern.charAt(i);
            String token = tokens[i];
            if (!map.containsKey(ch)){
                if (!already.add(token)) return false;
                map.put(ch, token);
            } else { 
                if (!map.get(ch).equals(token)) return false;
            }
        }

        return true;
    }
}


791. Custom Sort String

두 문자열 order와 s가 주어집니다. order의 모든 문자는 고유하며 이전에 사용자 지정 순서로 정렬되었습니다.

s의 문자를 order가 정렬된 순서와 일치하도록 순열합니다. 더 구체적으로, 문자 x가 order에서 문자 y보다 앞에 오면 순열된 문자열에서 x는 y보다 앞에 와야 합니다. 이 속성을 만족하는 s의 모든 순열을 반환합니다.

 

제약사항

  • 1 <= order.length <= 26
  • 1 <= s.length <= 200
  • order and s는 영어 소문자로 이루어져있다.
  • order의 모든 문자는 unique하다.

아이디어

문자열 재배치

문자열 분해 -> 다시 이어붙이기(기준:order)

1. s를 hashmap으로 어떤 문자가 몇 개 있는지 분해

2. order를 돌면서 hashmap에 있으면 그 갯수만큼 이어붙이기 그리고 map에서 삭제하기

3. order를 다 돌면 hashmap에 나머지 문자가 있을테니 순서대로 이어붙이기

class Solution {
    public String customSortString(String order, String s) {
        Map<Character, Integer> map = new HashMap<>();
        for(char ch : s.toCharArray()){
            map.put(ch, map.getOrDefault(ch, 0) + 1);
        }

        StringBuilder sb = new StringBuilder();
        for(char ch : order.toCharArray()){
            if (map.containsKey(ch)){
                for(int i = 0; i < map.get(ch); i++){
                    sb.append(ch);
                }
                map.remove(ch);
            }
        }

        for(char key : map.keySet()){
            for(int i = 0; i < map.get(key); i++){
                sb.append(key);
            }
        }

        return sb.toString();
    }
}

 

Another Questions

class Solution {
    public String customSortString(String order, String s) {
        Map<Character, Integer> orderMap = new HashMap<>();
        for (int i = 0; i < order.length(); i++) {
            orderMap.put(order.charAt(i), i); // (order 문자, 순서)
        }

        List<Character> S = new ArrayList<>();
        for (char c : s.toCharArray()) {
            S.add(c);
        }

        return S.stream()
                .sorted((o1, o2) -> { // Null의 의미는 order 순서에 구애받지 않는 친구
                    Integer index1 = orderMap.get(o1);
                    Integer index2 = orderMap.get(o2);
                    if (index1 == null && index2 == null) {
                        return 0; // no order (don't care)
                    } else if (index1 == null) {
                        return 1; // o1 to the end
                    } else if (index2 == null) {
                        return -1; // o2 to the end
                    } else {
                        return index1.compareTo(index2);
                    }
                })
                .map(Object::toString)
                .collect(Collectors.joining());
    }
}

1657. Determine if Two Strings Are Close

다음 연산을 사용하여 한 문자열에서 다른 문자열을 찾을 수 있으면 두 문자열이 가까운 것으로 간주됩니다. 

  • 연산 1: 기존 문자 두 개를 바꿉니다. 예: abcde -> aecdb 
  • 연산 2: 기존 문자 하나를 다른 문자로 변환하고, 나머지 문자에도 동일한 연산을 수행합니다. 예: aacabb -> bbcbaa (모든 a는 b로, 모든 b는 a로 변환) 

각 문자열에 필요한 만큼 연산을 사용할 수 있습니다. 두 문자열 word1과 word2가 주어졌을 때, word1과 word2가 가까우면 true를 반환하고, 그렇지 않으면 false를 반환합니다.

 

제약사항

  • 1 <= word1.length, word2.length <= 10^5
  • word1 and word2는 영어 소문자로 이루어져있다.

 

아이디어

연산 1은 위치 바꾸기

연산 2는 문자 개수 바꾸기

일단 word1과 word2의 길이가 같아야한다.

사용한 문자 종류와 개수 분포가 같아야한다.

class Solution {
    public boolean closeStrings(String word1, String word2) {
        int n = word1.length();
        int m = word2.length();
        if (n != m) return false;

        Map<Character, Integer> mp1 = new HashMap<>();
        Map<Character, Integer> mp2 = new HashMap<>();

        for(char ch : word1.toCharArray()){
            mp1.put(ch, mp1.getOrDefault(ch, 0) + 1);
        }

        for(char ch : word2.toCharArray()){
            mp2.put(ch, mp2.getOrDefault(ch, 0) + 1);
        }
        if (!mp1.keySet().equals(mp2.keySet())) return false;

        List<Integer> list1 = new ArrayList<>(mp1.values());
        List<Integer> list2 = new ArrayList<>(mp2.values());
        Collections.sort(list1);
        Collections.sort(list2);

        return list1.equals(list2);
    }
}


해시 문제를 보면서 느낀 점

영어 소문자로 이루어진 것들을 다룰 때는 hashMap보다 26개의 배열을 쓰는 것이 훨씬 빠를 수 있다는 점

'코딩테스트' 카테고리의 다른 글

Binary Trees  (1) 2025.05.21
힙 (Heap)  (1) 2025.05.20
해시 (Hash)  (1) 2025.04.15
Two pointer  (0) 2025.04.05
Prefix sum  (0) 2025.04.02