타겟 넘버
- 주어진 숫자를 적절히 더하고 빼서 타겟 넘버를 만드는 방법의 수 구하기 (숫자의 순서 유지)
int answer = 0;
void dfs(vector<int>& numbers, int idx, int target, int val) {
if(idx == numbers.size()) {
if(val == target) answer++;
return;
}
dfs(numbers, idx+1, target, val+numbers[idx]);
dfs(numbers, idx+1, target, val-numbers[idx]);
}
int solution(vector<int> numbers, int target) {
dfs(numbers, 0, target, 0);
return answer;
}
// 재귀함수 이용하여 dfs 해버렸다..
네트워크
- 이차원 배열로 컴퓨터들끼리의 연결 정보가 주어졌을 때 네트워크의 개수 구하기
vector<int> graph[200];
bool visited[200] = {false};
void dfs(int v) {
visited[v] = true;
// 연결되어있는 노드들 방문
for(int neighbor : graph[v]) {
if(!visited[neighbor]) dfs(neighbor);
}
}
int solution(int n, vector<vector<int>> computers) {
// 그래프 생성
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++) {
if(i!=j && computers[i][j]==1) graph[i].push_back(j);
}
}
// dfs 탐색
int answer = 0;
for(int i=0; i<n; i++) {
if(!visited[i]) {
answer++;
dfs(i);
}
}
return answer;
}
// 굳이 그래프 생성하지 않고도 computers 벡터를 넘겨주면서 neighbor를 확인해도 되겠다 (인접 노드 확인용)
게임 맵 최단거리
- 게임 뱁 상태 maps가 매개변수로 주어질 때, 상대 팀 진영에 도달하기 위해 지나야하는 칸의 개수의 최솟값 구하기
int answer, m, n;
bool visited[100][100] = {false};
void dfs(vector<vector<int>>& maps, int i, int j, int cnt) {
if(i>=n || i<0 || j>=m || j<0) return; // 유효하지 않은 주소이거나
if(maps[i][j]==0 || visited[i][j]) return; // 벽이 있거나, 이미 방문한 경로이면 리턴 (무한루프 방지)
if(i==n-1 && j==m-1) {
if(answer==-1) answer = cnt;
else answer = min(answer, cnt);
return;
}
// 인접 경로 방문해보기
visited[i][j] = true;
dfs(maps, i+1, j, cnt+1);
dfs(maps, i-1, j, cnt+1);
dfs(maps, i, j+1, cnt+1);
dfs(maps, i, j-1, cnt+1);
visited[i][j] = false;
}
int solution(vector<vector<int>> maps)
{
n = maps.size(); m = maps[0].size(); answer = -1;
dfs(maps, 0, 0, 1);
return answer;
}
// 처음엔 이렇게 DFS로 풀었는데, 효율성 테스트에서 전부 시간 초과가 떴다
// '최단거리 문제'는 BFS로 풀어야한다. 어찌보면 당연한게 너비우선 탐색으로 depth를 1씩 증가시켜가며 찾으면서 목적지 도달 시 바로 return 해버리는 게 훨씬 효율적
int answer, m, n;
bool visited[100][100] = {false};
void bfs(vector<vector<int>>& maps) {
queue<vector<int>> q;
q.push(vector<int>{0,0,1}); visited[0][0] = true; // (0,0) 에서 시작
int di[4] = {1,-1,0,0};
int dj[4] = {0,0,1,-1};
while(!q.empty()) {
auto v = q.front(); q.pop();
int i = v[0], j = v[1], cnt = v[2];
for(int t=0; t<4; t++) {
int new_i = i+di[t];
int new_j = j+dj[t];
if(new_i>=n || new_i<0 || new_j>=m || new_j<0) continue; // 유효하지 않은 주소거나
if(visited[new_i][new_j] || maps[new_i][new_j]==0) continue; // 이미 방문한 주소거나(무한루프 방지) 벽이면 패스
if(new_i==n-1 && new_j==m-1) {
answer = cnt+1;
return;
}
visited[new_i][new_j] = true;
q.push(vector<int>{new_i, new_j, cnt+1});
}
}
}
int solution(vector<vector<int>> maps)
{
n = maps.size(); m = maps[0].size(); answer = -1;
bfs(maps);
return answer;
}
// bfs로 풀 때에도 효율성 측면에서 크게 영향을 미치는 요소가 있었음
// 다음 방문지를 queue에 넣기 전에 visited를 미리 체크해두어야 함. queue에서 pop한 후 visited 체크하면 동일 layer에서 중복된 값이 큐에 너무 많이 들어감
단어 변환
- 두 단어 begin, target과 단어의 집합 words가 주어지고, 매 단계에서는 한 개의 알파벳만 바꾸고 words에 있는 단어로만 변환 가능
- 최소 몇 단계의 과정을 거쳐 begin을 target으로 변환 가능한지? => BFS
int findDiff(string s1, string s2) {
int n = s1.length(), cnt = 0;
for(int i=0; i<n; i++) {
if(s1[i]!=s2[i]) cnt++;
}
return cnt;
}
int solution(string begin, string target, vector<string> words) {
int diff[51][51] = {0};
bool visited[51] = {false};
int n = words.size(), fin = -1, answer = 0;
for(int i=1; i<=n; i++) {
diff[0][i] = findDiff(begin, words[i-1]); // begin 단어와 words 단어(1~n) 간 차이
if(words[i-1] == target) fin = i;
for(int j=i+1; j<=n; j++) {
diff[i][j] = findDiff(words[i-1], words[j-1]); // words 단어(1~n) 간 차이
diff[j][i] = diff[i][j];
}
}
// words 안에 target이 없는 경우
if(fin==-1) return 0;
// bfs 탐색
queue<pair<int,int>> q;
for(int i=1; i<=n; i++) {
if(diff[0][i] == 1) {
if(i==fin) return 1;
visited[i] = true;
q.push(make_pair(i, 1));
}
}
while(!q.empty()) {
auto p = q.front(); q.pop();
int idx = p.first, cnt = p.second;
for(int i=1; i<=n; i++) {
if(diff[idx][i] == 1 && !visited[i]) {
if(i==fin) return cnt+1;
visited[i] = true;
q.push(make_pair(i, cnt+1));
}
}
}
return 0;
}
// diff 배열을 생성하여 글자 수 차이를 저장해놓았다, 근데 굳이 그럴 필요 없이 매번 검사해도 될 듯
bool isOneLetterDiff(string s1, string s2) {
int n = s1.length(), cnt = 0;
for(int i=0; i<n; i++) {
if(s1[i]!=s2[i]) cnt++;
}
return cnt==1;
}
int solution(string begin, string target, vector<string> words) {
int n = words.size(), answer = 0;
bool visited[50] = {false};
// bfs 탐색
queue<pair<string, int>> q;
q.push(make_pair(begin,0));
while(!q.empty()) {
auto p = q.front(); q.pop();
string tmp = p.first;
int cnt = p.second;
if(tmp == target) {
answer = cnt;
break;
}
for(int i=0; i<n; i++) {
if(isOneLetterDiff(tmp, words[i]) && !visited[i]) {
visited[i] = true;
q.push(make_pair(words[i], cnt+1));
}
}
}
return answer;
}
// 이런 식으로 매번 isOneLetterDiff 검사하여 bfs 수행해도 됨, 굳이 diff 배열 만들 필요 없으므로 공간 효율성
아이템 줍기
- 최대 4개의 직사각형 정보가 담긴 rectangle 배열이 주어지고, 초기 위치와 아이템의 위치가 주어짐
- 아래 사진과 같이 직사각형 겹친 모양의 테두리를 따라서만 이동 간으할 때, 아이템 줍기 위해 이동해야 하는 최소 거리
int solution(vector<vector<int>> rectangle, int characterX, int characterY, int itemX, int itemY) {
int grid[102][102] = {0};
int dx[8] = {1,-1,0,0,1,1,-1,-1};
int dy[8] = {0,0,1,-1,1,-1,1,-1};
bool inner[102][102] = {false};
bool visited[102][102] = {false};
// 사각형이 영역 전부 색칠 (2배 해야지 너비/높이 1인 경우 테두리 구분 가능)
for(auto r : rectangle) {
for(int x=r[0]*2; x<=r[2]*2; x++) {
for(int y=r[1]*2; y<=r[3]*2; y++) {
grid[x][y] = 1;
}
}
}
// 테두리만 남겨놓고 비우기
for(int x=2; x<=100; x++) {
for(int y=2; y<=100; y++) {
if(grid[x][y]==1) {
bool isInner = true;
for(int i=0; i<8; i++) if(grid[x+dx[i]][y+dy[i]]==0) isInner = false;
inner[x][y] = isInner;
}
}
}
for(int x=2; x<=100; x++) {
for(int y=2; y<=100; y++) {
if(inner[x][y]) grid[x][y] = 0;
}
}
// 최단거리 구하기 => use bfs
queue<vector<int>> q;
q.push(vector<int>{characterX*2, characterY*2, 0});
visited[characterX*2][characterY*2] = true;
while(!q.empty()) {
auto cur = q.front(); q.pop();
int x = cur[0], y = cur[1], moves = cur[2];
if(x==2*itemX && y==2*itemY) {
return moves/2;
}
for(int i=0; i<4; i++) {
int newX = x+dx[i];
int newY = y+dy[i];
if(newX>=2 && newX<=100 && newY>=2 && newY<=100 && !visited[newX][newY] && grid[newX][newY]) {
q.push(vector<int>{newX, newY, moves+1});
visited[newX][newY] = true;
}
}
}
return 0;
}
// 최소 거리니까 BFS 하면 된다 (queue에는 현재 위치 + 이동 수 해서 저장해두면 될 듯)
// 테두리 정보를 담은 grid(map) 배열을 마련하고 시작 위치부터 bfs 돌리면 됨
// scale up (2배) 해야지 너비나 높이가 1인 직사각형의 마주보는 변을 구분할 수 있음!!
여행경로
- 주어진 항공권을 모두 한 번씩 이용하여 여행경로 구현, "ICN" 공항에서 출발하고 가능한 경로가 2개 이상일 경우 알파벳 순서가 앞서는 경로를 리턴함
set<int> used;
vector<string> answer;
bool finished = false;
void dfs(vector<vector<string>>& tickets, string curr, vector<string> path) {
if(finished) return;
path.push_back(curr);
vector<pair<string, int>> nexts;
for(int i=0; i<tickets.size(); i++) {
if(tickets[i][0] == curr && used.find(i)==used.end()) {
nexts.push_back(make_pair(tickets[i][1], i));
}
}
if(nexts.empty()) { // 더 이상 선택할 경로가 없을 때, 모든 티켓을 소진했는지 확인
if(used.size() == tickets.size()) {
answer = path;
finished = true;
return;
}
}
sort(nexts.begin(), nexts.end()); // 알파벳 순으로 정렬
for(auto next : nexts) {
// 해당 티켓 선택
used.insert(next.second);
dfs(tickets, next.first, path);
// 원복
used.erase(next.second);
}
path.erase(path.end()-1);
}
vector<string> solution(vector<vector<string>> tickets) {
// ICN에서 시작
dfs(tickets, "ICN", vector<string>());
return answer;
}
// DFS 방식 사용, 하지만 더 이상 티켓 사용 불가능할 때 (depth 끝 도달했을 때) 해당 경로가 모든 티켓을 소진하는 유효한 경로인지 확인 필요, 아니면 다른 경로를 탐색해야 함
// 백트래킹 방식 사용, 유효한 경로가 아니면 무효화하고 다른 경로를 탐색 (재귀함수 호출 뒷부분에 원복 코드 추가)
// 알파벳 순으로 정렬된 상태에서 유효 경로 찾기 때문에 하나 찾는 순간 finished 플래그 활성화, 이후 경로들 무시
퍼즐 조각 채우기
- 게임 보드의 빈 공간에 퍼즐 조각을 적절히 올려놓을 때, 채울 수 있는 최대 칸 수를 구하기
- 0/90/180/270도 회전 가능하고, 각 빈칸에는 해당 빈칸을 완전히 채우는 퍼즐 한 조각씩 놓을 수 있음.
#include <string>
#include <vector>
using namespace std;
vector< vector<vector<int>> > puzzles; // 퍼즐 정보
vector< vector<int> > puzzles_info; // 좌상단 (i,j)좌표, 우하단 (i,j) 좌표, 블록개수
vector<vector<int>> blanks; // 빈칸 정보
int puzzleNum = 0, N;
int minI, maxI, minJ, maxJ, blocks;
bool used[500] = {false}; // 충분히 크게 해야 됨! (200 했을 땐 세그폴트 떴음..)
int di[4] = {-1,1,0,0};
int dj[4] = {0,0,1,-1};
bool validAddr(int i, int j) {
if(i<0 || i>=N || j<0 || j>=N) return false;
return true;
}
void dfs(vector<vector<int>>& table, int i, int j) {
// 퍼즐 조각 찾기 위한 dfs
if(minI>i) minI = i; if(maxI<i) maxI = i;
if(minJ>j) minJ = j; if(maxJ<j) maxJ = j;
blocks++;
puzzles[puzzleNum][i][j] = 1;
table[i][j] = 0;
int newI, newJ;
for(int t=0; t<4; t++) {
newI = i+di[t];
newJ = j+dj[t];
if(validAddr(newI, newJ) && table[newI][newJ]==1) dfs(table, newI, newJ);
}
}
void dfs2(vector<vector<int>>& game_board, int i, int j) {
// 빈칸 찾기 위한 dfs
if(minI>i) minI = i; if(maxI<i) maxI = i;
if(minJ>j) minJ = j; if(maxJ<j) maxJ = j;
blocks++;
blanks[i][j] = 1;
game_board[i][j] = 1;
int newI, newJ;
for(int t=0; t<4; t++) {
newI = i+di[t];
newJ = j+dj[t];
if(validAddr(newI, newJ) && game_board[newI][newJ]==0) dfs2(game_board, newI, newJ);
}
}
void initialize() {
minI = minJ = N-1; maxI = maxJ = 0; blocks = 0;
for(int i=0; i<N; i++) {
for(int j=0; j<N; j++) {
blanks[i][j] = 0;
}
}
}
int findPuzzle() {
int p_x, p_y, b_x, b_y, p_minI, p_maxI, p_minJ, p_maxJ;
bool same1, same2, same3, same4;
for(int idx=0; idx<puzzleNum; idx++) {
// 한 조각씩 검사함
if(used[idx]) continue;
if(puzzles_info[idx][4] != blocks) continue;
p_minI = puzzles_info[idx][0]; p_minJ = puzzles_info[idx][1];
p_maxI = puzzles_info[idx][2]; p_maxJ = puzzles_info[idx][3];
p_x = p_maxI - p_minI; p_y = p_maxJ - p_minJ;
b_x = maxI - minI; b_y = maxJ - minJ;
same1 = same2 = same3 = same4 = false;
if(p_x == b_x && p_y == b_y) {
// 0도 회전, 180도 회전
same1 = same2 = true;
for(int di=0; di<=p_x; di++) {
for(int dj=0; dj<=p_y; dj++) {
if(puzzles[idx][p_minI+di][p_minJ+dj] != blanks[minI+di][minJ+dj]) same1 = false;
if(puzzles[idx][p_minI+di][p_minJ+dj] != blanks[maxI-di][maxJ-dj]) same2 = false;
}
}
}
if(p_x == b_y && p_y == b_x) {
// 90도 회전, 270도 회전
same3 = same4 = true;
for(int di=0; di<=p_x; di++) {
for(int dj=0; dj<=p_y; dj++) {
if(puzzles[idx][p_minI+di][p_minJ+dj] != blanks[minI+dj][maxJ-di]) same3 = false;
if(puzzles[idx][p_minI+di][p_minJ+dj] != blanks[maxI-dj][minJ+di]) same4 = false;
}
}
}
if(same1 || same2 || same3 || same4) {
used[idx] = true;
return puzzles_info[idx][4];
}
}
return 0;
}
int solution(vector<vector<int>> game_board, vector<vector<int>> table) {
int answer = 0;
N = game_board.size();
// puzzle 초기화
for(int i=0; i<N; i++) {
for(int j=0; j<N; j++) {
if(table[i][j]) {
puzzles.push_back(vector<vector<int>>(N, vector<int>(N,0)));
minI = minJ = N-1; maxI = maxJ = 0; blocks = 0;
dfs(table, i, j);
puzzles_info.push_back(vector<int>{minI, minJ, maxI, maxJ, blocks});
puzzleNum++;
}
}
}
// 이제 gameboard를 탐색하며 확인!!
int CNT = 0;
blanks = vector<vector<int>>(N, vector<int>(N,0));
for(int i=0; i<N; i++) {
for(int j=0; j<N; j++) {
if(game_board[i][j]==0) {
// gameboard의 비어있는 공간 확인
initialize();
dfs2(game_board, i, j);
// 적합한 퍼즐조각이 있는지 탐색
answer += findPuzzle();
}
}
}
return answer;
}
// 빡구현문제...
// 각 퍼즐 조각 상태를 puzzles 벡터에 저장하고, puzzles_info 벡터에는 범위 정보와 타일 갯수를 저장해둠
// gameboard를 탐색하며 빈칸을 찾는다, 각 빈칸에 대해 사용하지 않은 퍼즐 조각 중 돌려서 맞는 것이 있나 확인
// 돌려서 맞는지 확인하기 위해 빈칸의 범위 정보(i/j 좌표의 최대 최솟값)와 파일 조각의 범위 정보(i/j 좌표의 최대 최솟값)을 별도로 저장해두어야 함. 해당 범위 내에서 동일한지 확인하면 된다
troubleshoot1.
findPuzzle에서 두 if문을 if - else if 로 구성했는데, 들어갈 수 있는 퍼즐 조각을 불가능하다고 판단하는 경우가 있었음.
=> 가로 = 세로인 퍼즐 조각에서 90도 돌려야 들어가지는데, 첫번째 if문에서 0도 혹은 180도 돌려보고 안된다고 판단했던 거였음
=> if문 두 개로 구성함
troubleshoot2.
퍼즐 조각의 개수를 알 방법이 없음. 일부 테케에서 세그폴트가 떠서 인덱스 문제라고 추측
=> 만약 50*50 그리드에서 1개짜리 퍼즐조각이 엄청 많다면 puzzleNum은 충분히 200을 넘어갈 수 있겠구나 생각함.
=> bool used[500]; 이렇게 원소 갯수를 더 늘려줌
'web > 알고리즘' 카테고리의 다른 글
프로그래머스 알고리즘 고득점 Kit - 그래프 (1) | 2024.02.25 |
---|---|
프로그래머스 알고리즘 고득점 Kit - 동적계획법 (DP) (0) | 2024.02.22 |
프로그래머스 알고리즘 고득점 Kit - 이분 탐색 (0) | 2024.02.21 |
프로그래머스 알고리즘 고득점 Kit - 탐욕법(Greedy) (0) | 2024.02.19 |
프로그래머스 알고리즘 고득점 Kit - 완전탐색 (0) | 2024.02.17 |