문제 정보
- 프로그래머스 Lv.3
- DFS, 백트래킹
- 문제 링크
풀이
user_id, banned_id 배열과 각 원소의 크기가 크지 않기 때문에 완전 탐색해도 무방하다.
banned_id[i]에 대해 user_id를 DFS로 순회 및 백트래킹한다.
boolean[] visited를 통해 이미 선택된 ID는 선택하지 않는다.- 백트래킹을 통해 가능한 모든 조합을 탐색한다.
banned_id의 조합이 같더라도 순서만 다르면 중복으로 집계될 수 있다. 예를 들어 {user_1, user_2}와 {user_2, user_1}은 같은 조합이지만 DFS 탐색 순서에 따라 다른 결과로 처리될 수 있다.
이를 방지하기 위해 선택된 user_id의 조합을 비트마스크로 표현한다. user_id의 각 인덱스를 비트 하나에 대응시켜, 선택된 user_id 조합을 하나의 정수로 표현하는 방식이다.
예를 들어, user_id = {"a", "b", "c", "d", "e"]일 때, 0번째와 2번째 유저가 선택되었다면 비트마스크는 가 된다.
bitmask | (1 << i)1 << i는 i번째 비트만 1인 값이고, 이를 bitmask에 OR 연산하면 i번째 비트를 1로 설정한다. 즉, i번째 user_id가 선택되었음을 기록하는 것이다.
이렇게 하면 {0번, 2번} 순서로 선택된 조합과 {2번, 0번} 순서로 선택된 조합 모두 동일한 비트마스크 값 5를 가지므로, Set에 저장할 때 중복이 제거된다.
이하는 입출력 예시 2의 경우로 든 예시다.
dfs(0, 0)
├── frodo 선택 -> dfs(1, 1)
│ ├── crodo 선택 -> dfs(2, 0101)
│ │ ├── abc123 선택 -> dfs(3, 1101) -> HashSet.add(13)
│ │ └── frodoc 선택 -> dfs(3, 10101) -> HashSet.add(21)
│ │
│ └── 여기서 frodo 선택은 하지 않음 (∵ 이 시점에 visited[0] = true)
│
│
백트래킹 (visited[0] = false)
│
└── crodo 선택 -> dfs(1, 0100) ┐
└── frodo 선택 -> dfs(2, 0101) │=> 추가 안됨 (∵ Set)
⋮
코드
import java.util.*;
class Solution {
private Set<Integer> result = new HashSet<>();
boolean[] visited;
String[] userId;
String[] bannedId;
public int solution(String[] user_id, String[] banned_id) {
userId = user_id;
bannedId = banned_id;
visited = new boolean[user_id.length];
dfs(0,0);
return result.size();
}
private void dfs(int bannedIdx, int bitmask) {
if (bannedIdx == bannedId.length) {
result.add(bitmask);
return;
}
for (int i = 0; i < userId.length; i++) {
if (visited[i])
continue;
if (!isMatch(userId[i], bannedId[bannedIdx]))
continue;
visited[i] = true;
dfs(bannedIdx + 1, bitmask | (1 << i));
visited[i] = false;
}
}
private boolean isMatch(String userId, String bannedId) {
if (userId.length() != bannedId.length())
return false;
for (int i = 0; i < userId.length(); i++) {
if (bannedId.charAt(i) == '*')
continue;
if (userId.charAt(i) != bannedId.charAt(i))
return false;
}
return true;
}
}