진짜 너무 어려웠던 시계 문제 ㅠㅠ
이번엔 도망치지 않고 맞붙었다!!
1. 출처
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
2. 설계
https://school.programmers.co.kr/questions/63464
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
도저히 모르겠어서 위 링크에서 힌트를 얻었다.
우리는 큰 설계안으로 다음과 같은 설계를 할 것이다.
00:00:00 ~ h1:m1:s1에서 울릴 수 있는 벨의 횟수
00:00:00 ~ h2:m2:s2에서 울릴 수 있는 벨의 횟수
2가지를 각각 구하고 후자에서 전자를 빼줄 것이다. 겹치는 벨의 횟수를 빼는 것이다.
왜 그런지는 아래 설명을 보며 차차 이해해보자
1. 벨은 언제 울리는가?
1분마다 초침은 360도를 돈다.
초침은 360도를 돌 때 마다 시침과 분침을 한 번씩 만난다. 그래서 1분에 2번 종을 울리는 것이다.
그래서 생성할 수 있는 수식은 다음과 같다.
벨이 울리는 횟수 = (시간 * 60 + 분) * 2
예를 들어 00:00:00 ~ 24:00:00 사이 벨이 울리는 횟수를 러프하게 생각해보자
24시간 = 24* 60 = 1440분이다.
1440번 돌며 초침은 시침과 분침 한 번씩 만나 종을 울릴 것이기 때문에 2880번 종이 울릴 수 있는 것이다.
2. (예외 체크) 벨은 언제 안울리는가?
그러나 24시간 중 2880번의 종이 울리는 것은 아니다. 벨이 안울리는 케이스가 3가지있다.
CASE1. --시 59분에서 정각으로 갈 때 1분 간은 초침과 분침이 만나지 않는다.
00시 59분에서 정각으로 갈 때는 울리지 않고, 딱 정각에서 만나서 다같이 울리기 때문이다.
때문에 1시간 씩 벨이 울리는 횟수-1을 해줘야한다.
만약 00:00:00~24:00:00 사이 벨이 울리는 횟수를 구한다면 24시간 고려해야하기 때문에 -24가 되겠다.
CASE2. 11:59 -> 12:00 or 23:59 -> 24:00로 갈 때 초침과 시침이 만나지 않는다.
분침이 매 시간 59분에서 정각으로 갈 때 초침과 만나지 않았다면,
시침은 12시, 00시가 될 때만 초침과 만나지 않는다. 정시에 한 번에 울리기 때문이다.
때문에 11:59 ~ 12:00를 지나는 케이스나 23:59~00:00를 지나는 케이스는 벨이 울리는 횟수 - 1씩 해줘야 한다.
CASE3. 시침 & 분침 & 초침이 한 자리에서 만난다.
시침과 분침 그리고 초침이 한 자리에서 다같이 만나 종이 딱 1번만 울리는 때가 있다.
2번 케이스에서 59 -> 정각으로 넘어갈 때 시침과 초침이 만나지 않아 종이 울리지 않는 대신 정각에 다같이 만나 단 1번만 울리는 것이다.
때문에 벨이 울리는 횟수 = (시간 * 60 + 분) * 2 이렇게 계산했다면, 모든 시간에 2번 씩 울리는 것으로 계산한 셈이나 다름 없으므로 -1씩 빼줘야 하는 것이다.
3. 왜 분만 고려하지 뒤에 남은 초는 어떻게 고려하나?
1번에서 1분마다 초침은 360도를 돌며 초침은 360도를 돌 때 마다 시침과 분침을 한 번씩 만난다. 그래서 1분에 2번 종을 울린다고 했다.
초는 고려하지 않는 뉘앙스인데...? 사실 이 초도 꼭 고려해줘야 한다.
만약 00:00:00 ~ 12:00:30을 구한다고 가정하자
12:00:00인 상황에서 30초를 이동할 때 초침이 시침과 분침을 마주쳤는지 확인을 해줘야 한다. 그리고 종이 울리는 횟수에 추가해야만 한다.
마주쳤는지 확인하는 방법은 각도를 구하는 것이다.
12:00:00에서 30초를 모두 이동했을 때 시침은 얼마나 이동했는지, 분침은 얼마나 이동했는지, 초침은 얼마나 이동했는지 각도를 계산한다. 그리하여 초침이 시침과 분침보다 각도가 크다면 지나친 것이기 때문에 종이 각각 1번 씩 울렸을 것이라고 판단하는 것이다.
각도를 구하기 위해선 다음의 수식을 이해해야 한다.
3-1) 시침은 얼마나 이동했을까?
총 12시간 기준 360도를 1시간으로 표현하면 30도이다. = 360/12 = 30도
1시간 30도를 이동할 때 1분은 30도 * 1/60을 이동했을 것이다. = 30/60 = 0.5도
1분 0.5도를 이동할 때 1초는 0.5도 * 1/60을 이동했을 것이다. = 0.5/60도
따라서 시침은 12:00:30가 되었을 때
(12 * 30 + 0*0.5 + 30*(0.5/60))%360 만큼의 각도를 가질 것이다.
3-2) 분침은 얼마나 이동했을까?
총 60분 기준 360도를 1분으로 표현한다면 6도이다. = 360/60 = 6도
1분 6도를 이동할 때 1초는 6도 * 1/60을 이동했을 것이다 = 6/60 = 0.1도
따라서 분침은 12:00:30가 되었을 때
(0*6 + 30*0.1)%360 만큼의 각도를 가질 것이다.
3-3) 시침은 얼마나 이동했을까?
총 60초 기준 360도를 1초로 표현한다면 6도이다. = 360/60 = 6도
따라서 초침은 12:00:30가 되었을 때
(30*6)%360 만큼의 각도를 가질 것이다.
4. 중복 빼기 주의!
00:00:00 ~ 12:00:30의 케이스나
00:00:00 ~ 23:59:59의 케이스가 중복 빼기의 대상이 될 수 있다.
h1:m1:s1이 00:00:00이거나 12:00:00인 경우
h2:m2:s2에서 벨이 울리는 경우에서 h1:m1:s1에서 벨이 울리는 경우를 빼면
00:00:00 or 12:00:00일 때 종이 1번만 울리는 경우만 -1이 되어 빠지게 되는 격이다.
따라서 시작지점에 종이 울리는 횟수가 빠져서 결과를 도출하게 된다. 그래서 테스트 케이스에서도 답이 1인데 0으로 도출되는 상황이 나온다.
그래서 이 경우를 예외로 h1:m1:s1이 00:00:00이거나 12:00:00인 경우는 종이 울리는 횟수를 +1을 해줘야 한다.
3. 전체 코드
class Solution {
public int solution(int h1, int m1, int s1, int h2, int m2, int s2) {
int bell1 = ringABell(h1, m1, s1);
int bell2 = ringABell(h2, m2, s2);
//bell2 - bell1을 해주되 시작하는 시간이 빠지면 안되니 고려한다.
int result = bell2-bell1;
if((h1==12 || h1==0)&&m1==0&&s1==0) result++;
return result;
}
public static int ringABell(int h, int m, int s){
//1. 00:00:00은 1번만 종이 울림
int bell=-1;
//2. 0초에서 시작해서 -> s초로 가기까지 시침과 분침을 마주쳤는지 확인
double hDeg = (h*30+m*0.5+s*0.5/60)%360;
double mDeg = (m*6+s*0.1)%360;
double sDeg = s*6%360;
if(sDeg>=hDeg) bell++;
if(sDeg>=mDeg) bell++;
//3. 잉여 초 고려 완료 -> 1분마다 초침은 시침/분침(*2) 만난다.
bell += (m+h*60)*2;
//4. 24시간 중 1분씩은 초침이 분침을 만나지 않는다.
bell-=h;
//5. 11:59 -> 12:00 시침은 초침과 만나지 않는다. + 12:00:00은 1번만 벨이 울린다.
if(h>=12) bell-=2;
return bell;
}
}
'알고리즘 > 프로그래머스' 카테고리의 다른 글
[프로그래머스] 그룹별 조건에 맞는 식당 목록 출력하기 [#SQL] (0) | 2024.03.06 |
---|---|
[JOIN] SQL 고득점 키트 문제 풀이 모음 (0) | 2024.01.22 |
[프로그래머스] lv3. 정수 삼각형 [#DP] (0) | 2023.12.09 |
[프로그래머스] lv2. 석유시추 [#BFS] (1) | 2023.12.07 |
[프로그래머스] lv2. 구명보트 [#투포인터] (0) | 2023.11.29 |