이전 포스팅에 이어 2024.10.14 - [알고리즘/프로그래머스] - 프로그래머스 - [PCCP 기출문제] 2번 / 퍼즐 게임 챌린지 C++ 3번 문제를 풀어보았다. 2번 문제보다 소요시간은 1.5배 정도 걸렸는데, 해결 방법은 간단하게 생각이 되었는데, 코딩을 하는 와중에 Crash가 발생하여 시간이 오래 걸렸다. 바보 같이 for문에서 index가 아니라 count를 ++ 하면서... 문제의 난이도는 이전 문제와 비슷하게 느껴졌다.
알고리즘 난이도 및 시간 ( ※ 개인적인 의견 )
- 난이도 : Level 3 ( ※ Min : 0, Max : 10 )
- 풀이 시간 : 1시간 30
- 제출 횟수 : 2번 ( 코드 실수 )
- 풀이 결과 : Pass
문제 설명
이번 문제는 해석하는데 큰 문제는 없었다. 오히려 내가 느끼기에는 같은 레벨이지만, 이전 문제보다 해석하기는 더 쉬웠다. 간략하게 문제를 설명하면 로봇 n대를 배달하는데 중간에 같은 칸에서 만나는 건수를 구하는 문제다. 해당 문제에서 주의해야 할 점은 r좌표가 변하는 이동을 c좌표가 변하는 이동보다 먼저 한다는 점과 보통 (x, y)를 생각하는데 r = y 고 c = x였다. 또한, 2대 이상 모인다면 충돌할 가능성이 있다는 이야기는 2대나 3대나 충돌위험이 있다고 판단하기에 동시에 2대 이상의 Case는 1건으로 본다는 점이다.
이번 문제 제한 사항에서는 같은 좌표에 여러 포인트가 존재하는 입력은 주어지지 않는다는 것과 같은 포인트롤 연속적으로 방문하는 입력은 주어지지 않는다는 것이다. 두 개의 문구가 왜 중요하냐면 혹시나 연속해서 같은 포인트를 이동하게 되는 경우 동일한 좌표를 계속해서 마주치게 된다. 이와 같은 경우 좌표가 같더라도 count를 해야 하기 때문에 결과 값이 달라질 수 있다.
입출력
입출력은 프로그래머스에 그림으로 예시가 너무 잘되어 있어 해당 포스팅 마지막에 있는 링크를 참조하여 이해하기 바란다.
문제풀이
일단 첫 번째로 나는 로봇별 Route Path를 미리 다 만들어서 하는 방법을 생각했다. 딱히 복잡도도 높지 않고, O(n)만에 끝낼 수 있을 것 같기 때문에 선택하였다. 일단 Routes 개수만큼 for문을 돌게 되는데, 여기서 Key Point는 우선순위가 R이기 때문에 R 차이만큼 먼저 이동 후 C를 이동하면 된다. 그렇기 때문에 알고리즘이 어렵지는 않다. 하지만 여기서 유의해야 할 점은 차이가 -인 경우를 대비해서 ABS를 활용하여 Cycle을 돈다. 또한 첫 번째 좌표는 무조건 Insert가 필요하기 때문에 Size가 0인 경우에는 좌표를 먼저 넣어주고 시작한다.
const uint8_t MAX_ROBOT_COUNT = 101;
vector<pair<uint32_t, uint32_t>> g_RobotRouteList[MAX_ROBOT_COUNT];
//=======================================================
// @ 다음 좌표까지 이동 좌표 경로 만들기
void MakeRoutePath(const uint32_t& iRobotNum,
const vector<int>& rCurrentPosition,
const vector<int>& rNextPosition)
//=======================================================
{
int iRDiff = rNextPosition[0] - rCurrentPosition[0]; // R 차이
int iCDiff = rNextPosition[1] - rCurrentPosition[1]; // D 차이
uint32_t iRDiffSize = abs(iRDiff); // for문 Cycle을 위해 절댓값 계산
uint32_t iCDiffSize = abs(iCDiff);
uint32_t iCurrRPosition = rCurrentPosition[0]; // 현재 위치를 계산하기 위험
uint32_t iCurrCPosition = rCurrentPosition[1];
uint32_t iTempLastRPosition = iCurrRPosition;
if (0 == g_RobotRouteList[iRobotNum].size()) // 첫 번째 위치는 삽입하기 위함
{
g_RobotRouteList[iRobotNum].push_back(make_pair(rCurrentPosition[0], rCurrentPosition[1]));
}
for (uint32_t iRIndex = 1; iRIndex <= iRDiffSize; ++iRIndex) // 이전 좌표부터 계산하기 위해서 1부터 시작
{
if (0 > iRDiff) // 음수인 경우 작게 이동해야함
{
g_RobotRouteList[iRobotNum].push_back(make_pair((iCurrRPosition - iRIndex), iCurrCPosition));
iTempLastRPosition = iCurrRPosition - iRIndex;
}
else
{
g_RobotRouteList[iRobotNum].push_back(make_pair((iCurrRPosition + iRIndex), iCurrCPosition));
iTempLastRPosition = iCurrRPosition + iRIndex;
}
}
for (uint32_t iCIndex = 1; iCIndex <= iCDiffSize; ++iCIndex)
{
if (0 > iCDiff) // 음수인 경우 작게 이동해야함
{
g_RobotRouteList[iRobotNum].push_back(make_pair(iTempLastRPosition, (iCurrCPosition - iCIndex)));
}
else
{
g_RobotRouteList[iRobotNum].push_back(make_pair(iTempLastRPosition, (iCurrCPosition + iCIndex)));
}
}
}
그다음은 MakeRoutePath 함수를 반복해서 로봇 개수 및 방문해야 하는 경유지 개수만큼 생성하여야 하기 때문에 Control역할을 하는 함수를 하나 만들어 준다. 아래 기능은 아마 개발을 하는 사람이라면 쉽게 이해할 수 있을 것으로 예상되어 설명은 패스한다.
//=======================================================
// @ 로봇 패스 좌표를 미리 계산하기
void CalculateRobotRoute(const vector<vector<int>>& rPoints,
const vector<vector<int>>& rRoutes)
//=======================================================
{
uint32_t iRobotCount = static_cast<uint32_t>(rRoutes.size());
for (uint32_t iRobotIndex = 0; iRobotIndex < iRobotCount; ++iRobotIndex)
{
uint32_t iRouteCount = static_cast<uint32_t>(rRoutes[iRobotIndex].size());
g_RobotRouteList[iRobotIndex].clear();
for (uint32_t iRouteIndex = 1; iRouteIndex < iRouteCount; ++iRouteIndex)
{
uint32_t current = rRoutes[iRobotIndex][iRouteIndex - 1];
uint32_t next = rRoutes[iRobotIndex][iRouteIndex];
MakeRoutePath(iRobotIndex, rPoints[current - 1], rPoints[next - 1]);
}
}
}
마지막은 로봇의 경로 계획을 모두 마무리하였다면, 시간 별로 동시에 같은 칸에 마주치는 개수를 구해야 한다. 여기서 나는 좌표를 Key값으로 활용하기 위해서 map <pair <uint32_t, uint32_t>, uint32_t> m_CrossBoard라는 변수를 활용하여, 매 순간 초기화 하면서 Check를 했다. 또한 해당 칸에 방문한 Robot이 1인 경우에만 사건 발생 변수를 ++ 하면서 진행했다. 여기서 주의했던 점은 로봇마다 경로 개수가 다를 수 있기 때문에 죽을 수 있는 가능성을 줄이기 위해 로봇별로 Size를 Check 하면서 진행하였고, 모든 로봇의 경로 계획을 다 순환하는 동시에 while문을 종료하게 하였다.
//=======================================================
// @ 마주치는 좌표 개수 구하기
int GetCrossPointCount(const uint32_t& iRobotCount)
//=======================================================
{
int iResult = 0;
int iCycleCount = 0;
uint32_t iCurrentNum = 0;
while (true)
{
iCycleCount = 0;
map<pair<uint32_t, uint32_t>, uint32_t> m_CrossBoard;
for (uint32_t iRobotIndex = 0; iRobotIndex < iRobotCount; ++iRobotIndex)
{
if (iCurrentNum < g_RobotRouteList[iRobotIndex].size())
{
if (m_CrossBoard.end() != m_CrossBoard.find(g_RobotRouteList[iRobotIndex][iCurrentNum]))
{
if (1 == m_CrossBoard[g_RobotRouteList[iRobotIndex][iCurrentNum]])
{
iResult++;
}
m_CrossBoard[g_RobotRouteList[iRobotIndex][iCurrentNum]] ++;
}
else
{
m_CrossBoard[g_RobotRouteList[iRobotIndex][iCurrentNum]] = 1;
}
iCycleCount++;
}
}
iCurrentNum++;
if (0 == iCycleCount) // Cycle이 0이라는 말은 이제 남은 로봇의 경로가 없기 때문에 while문을 종료한다는 의미다
{
break;
}
}
return iResult;
}
결과
다른 사람들의 풀이를 아직 보지 않았지만, dfs/bfs를 기반으로 알고리즘을 짠다면 코드가 더 간결하고 쉬웠을 것 같다. 하지만 나는 이전 포스팅에서도 언급을 했듯이 사고하는 방식이 중요하다고 생각하기 때문에 내가 생각한 방법을 논리적으로, 순서적으로 기재하여 내 코드를 누가 봤을 때 쉽게 이해할 수 있게 짜는 것이 중요하다고 생각한다. 또한, 성능에 큰 영향을 주는 것이 아니라면 공식을 외우고 문제를 풀기보다는 본인만의 방법을 찾는 것이 추후 문제를 풀 때 더 도움이 된다고 생각한다. 혹시 해당 풀이를 보고 이해가 안 가는 내용이 있다면 댓글로 남겨주기 바란다. ※ 혹시 특정 문제가 잘 안 풀려서 풀이가 필요한 경우에 댓글로 링크를 남겨주면 문제를 풀고 포스팅할 수 있도록 하겠다.
프로그래머스 문제 : https://school.programmers.co.kr/learn/courses/30/lessons/340211
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
'알고리즘 > 프로그래머스' 카테고리의 다른 글
프로그래머스 - [PCCE 기출문제] 2번 / 각도 합치기 C++ (2) | 2024.10.19 |
---|---|
프로그래머스 - [PCCE 기출문제] 1번 / 문자 출력 C++ (0) | 2024.10.19 |
프로그래머스 - [PCCP 기출문제] 4번 / 수식 복원하기 C++ (0) | 2024.10.15 |
프로그래머스 - [PCCP 기출문제] 2번 / 퍼즐 게임 챌린지 C++ (1) | 2024.10.14 |
프로그래머스 - [PCCP 기출문제] 1번 / 동영상 재생기 C++ (1) | 2024.10.12 |