https://school.programmers.co.kr/learn/courses/30/lessons/250135
문제 설명
시침, 분침, 초침이 있는 아날로그시계가 있습니다. 시계의 시침은 12시간마다, 분침은 60분마다, 초침은 60초마다 시계를 한 바퀴 돕니다. 따라서 시침, 분침, 초침이 움직이는 속도는 일정하며 각각 다릅니다. 이 시계에는 초침이 시침/분침과 겹칠 때마다 알람이 울리는 기능이 있습니다. 당신은 특정 시간 동안 알람이 울린 횟수를 알고 싶습니다.
다음은 0시 5분 30초부터 0시 7분 0초까지 알람이 울린 횟수를 세는 예시입니다.

- 가장 짧은 바늘이 시침, 중간 길이인 바늘이 분침, 가장 긴 바늘이 초침입니다.
- 알람이 울리는 횟수를 세기 시작한 시각은 0시 5분 30초입니다.
- 이후 0시 6분 0초까지 초침과 시침/분침이 겹치는 일은 없습니다.

- 약 0시 6분 0.501초에 초침과 시침이 겹칩니다. 이때 알람이 한 번 울립니다.
- 이후 0시 6분 6초까지 초침과 시침/분침이 겹치는 일은 없습니다.

- 약 0시 6분 6.102초에 초침과 분침이 겹칩니다. 이때 알람이 한 번 울립니다.
- 이후 0시 7분 0초까지 초침과 시침/분침이 겹치는 일은 없습니다.
0시 5분 30초부터 0시 7분 0초까지는 알람이 두 번 울립니다. 이후 약 0시 7분 0.584초에 초침과 시침이 겹쳐서 울리는 세 번째 알람은 횟수에 포함되지 않습니다.
다음은 12시 0분 0초부터 12시 0분 30초까지 알람이 울린 횟수를 세는 예시입니다.

- 알람이 울리는 횟수를 세기 시작한 시각은 12시 0분 0초입니다.
- 초침과 시침, 분침이 겹칩니다. 이때 알람이 한 번 울립니다. 이와 같이 0시 정각, 12시 정각에 초침과 시침, 분침이 모두 겹칠 때는 알람이 한 번만 울립니다.

- 이후 12시 0분 30초까지 초침과 시침/분침이 겹치는 일은 없습니다.
12시 0분 0초부터 12시 0분 30초까지는 알람이 한 번 울립니다.
알람이 울리는 횟수를 센 시간을 나타내는 정수 h1, m1, s1, h2, m2, s2가 매개변수로 주어집니다. 이때, 알람이 울리는 횟수를 return 하도록 solution 함수를 완성해주세요.
- 0 ≤ h1, h2 ≤ 23
- 0 ≤ m1, m2 ≤ 59
- 0 ≤ s1, s2 ≤ 59
- h1시 m1분 s1초부터 h2시 m2분 s2초까지 알람이 울리는 횟수를 센다는 의미입니다.
- h1시 m1분 s1초 < h2시 m2분 s2초
- 시간이 23시 59분 59초를 초과해서 0시 0분 0초로 돌아가는 경우는 주어지지 않습니다.

첫번째 풀이
초침이 한 바퀴 돌때 시침과 분침을 각각 지나쳐 2번 알람이 울린다.
1분동안 2번 울리므로
60분동안 2번 * 60분 = 120번 울린다
이 논리대로라면
0시~1시 사이120번
1시~2시 사이 120번
2시~3시 사이 120번
...
22시~23시 사이 120번
23시~0시 사이 120번
0시~24시 24시간 동안 120*24 = 2880번이 울린다.
하지만 자세히 생각해보면
- 분침과 초침은 59분 => 00분으로 갈때마다 만나지 않는다 (매 시각 이 간격마다 울리는 횟수를 하나씩 줄인다 -24)
- 시침과 초침은 11시 => 12시, 23시 => 0시로 갈 때마다 만나지 않는다 (-2)
- 시침, 초침, 분침이 다 같이 겹친 경우 0시0분0초, 12시0분0초에는 카운트를 1로 친다 (-2)
따라서 0시0분0초에서 23시59분 59초까지 울리는 횟수는 2880-24-2-2 = 2852번이다.
시작시각에서 끝시각 사이 울리는 횟수는 어떻게 구할까?
시작시각 ~ 끝시각 사이 울린 횟수를 구하기는 까다로워 다음과 같이 구한다.
시작시각 ~ 끝시각 사이 울린 횟수 = 0시0분0초부터 끝시각까지 울린 횟수 - 0시0분0초부터 시작시각까지 울린 횟수
그럼 0시0분0초부터 특정 시각까지 울리는 횟수는 어떻게 구할까
0시0분0초에서 1시00분30초까지 울리는 횟수를 구한다고 가정하면
1시0분까지 울리는 횟수 + 30초간 울리는 횟수를 더하면 된다.
30초간 울리는 횟수는 0시0분0초를 각도 0이라고 봤을 때 시침, 분침, 초침의 각도에 따라 판단할 수 있다.
초침의 각도 >= 시침의 각도이면 초침은 이미 시침을 지나친것이므로 종이 1번 울렸다
초침의 각도 >= 분침의 각도이면 마찬가지로 종이 1번 울렸다.
그럼 각 각도는 어떻게 구할까
시침은
1시간당 360도 / 12시간 = 30도 회전한다
1분당 30도 / 60 = 0.5도 회전한다
1초당 0.5 / 60 = 0.0083도 회전한다
=> 시침의 각도 = ( 30 *시 + 0.5*분 + 0.0083 *초 ) %360 도 (마지막에 360도로 나누는 이유는 360도 = 0도로 변환하기 위해서이다.)
분침은
1분당 360도 / 60분 = 6도 회전한다
1초당 6 / 60 = 0.1도 회전한다
=> 분침의 각도 = (6*분 + 0.1*초) % 360 도
초침은
1초당 360도 / 60 = 6도 회전한다
=> 초침의 각도 = (6*초) % 360 도
참고이 분의 글을 참고했다.
function solution(h1, m1, s1, h2, m2, s2) {
function getCount(h, m, s) {
let bell = -1; // 0시0분0초는 1번만 울린다
bell += (h*60+m)*2; // 초침이 한바퀴 돌때 시침, 분침 총 2번 만난다
bell -= h; // 59분 -> 00분일때 분침은 초침과 만나지 않는다
if (h >= 12) bell -= 2; // 11시59분59초->12시경우 분,초침과 만나지 않고 12시에 1번 만난다
const hDegree = (h*30+m*0.5+s*(0.5/60))%360;
const mDegree = (m*6 + 0.1*s) % 360;
const sDegree = (6*s) % 360;
if (sDegree >= hDegree) bell++; // 초침이 시침을 지나쳤다
if (sDegree >= mDegree) bell++; // 초침이 분침을 지나쳤다
return bell;
}
// 0시0분0초부터 시작, 끝 시각까지 횟수
let totalBell = getCount(h2, m2, s2) - getCount(h1, m1, s1);
// 시작시간이 0시거나 12시이면 +1
if ((h1 === 0 || h1 === 12) && m1 === 0 && s1 === 0) totalBell++;
return totalBell;
}
느낀점
어려운 수학문제인 것 처럼 느껴졌다. 테스트케이스를 보며 시계가 돌아가는 걸 손으로 그려봐도 문제에 나온 예제조차 이해가 잘 가지 않았다. 몇 시간 고민하다가 결국 다른 분의 풀이를 봤다. 대략의 방법은 알 수 있었지만 내 맘에 쏙 들게 이해가 완벽하게 되진 않았다. 솔직히 이런 문제가 코테에 나올까 의문스럽기도 했다. 알고리즘, 자료구조를 쓰지않는 단순한 구현이라고 생각하기 때문이다. 변별력을 기르는데는 도움이 되겠지만 난 이것보다 다른 문제를 정확히 풀 것 같다.
두번째 풀이
스터디 멤버님의 풀이다. 개인적으로 첫번째 풀이보다 이해가 더 잘 된다.
1. 초마다 검사를 해줘야하니 총 걸린시간을 초단위로 바꾸자.
2. 시침, 분침, 초침의 초당 움직이는 각도를 구하자.
- 시침, 초침과 분침, 초침이 겹쳤는지를 확인하려면 1초마다 각도를 확인하면 됩니다.
- 현재 시간 기준으로 초침의 각도가 시침의 각도보다 작고, 1초 뒤에 시침의 각도보다 크거나 같다면 겹쳐진 것이므로 카운트를 1 증가합니다 분침도 마찬가지입니다.
- 다만 3가지가 겹쳤을 경우에는 1번만 카운트 해야 하므로 1을 빼줍니다.
시간을 아예 초단위로 변경후 while문 안에서 1초마다 증가하며 초침이 시침/분침을 만나면 카운트를++해준다
function solution(h1, m1, s1, h2, m2, s2) {
//초당 움직이는 초침, 분침, 시침 각도
const SECONDS_ANGLE = 360 / 60;
const MINUTES_ANGLE = 360 / 60 / 60;
const HOURS_ANGLE = 360 / 12 / 60 / 60;
let cnt = 0;
//시작 시간 초단위
let start_time = h1 * 60 * 60 + m1 * 60 + s1;
//끝나는 시간 초단위
let end_time = h2 * 60 * 60 + m2 * 60 + s2;
const start_hour_angle = (start_time * HOURS_ANGLE) % 360;
const start_minute_angle = (start_time * MINUTES_ANGLE) % 360;
const start_seconds_angle = (start_time * SECONDS_ANGLE) % 360;
//시작 시간에 초침과 시침이 겹쳐있으면 카운트
if (start_seconds_angle === start_hour_angle) cnt++;
//시작 시간에 초침과 분침이 겹쳐있으면 카운트
if (start_seconds_angle === start_minute_angle) cnt++;
//시작 시간에 시침, 분침, 초침 3가지가 겹쳐있는 경우 -1
if (
start_seconds_angle === start_hour_angle &&
start_seconds_angle === start_minute_angle
)
cnt--;
//1초마다 겹쳤는지 확인
//현재 초침의 각도가 시침, 분침 각도보다 작다가 1초 뒤에 크거나 같으면 겹쳐진 것
while (start_time < end_time) {
//현재 시간 시침, 분침, 초침 각도
const current_hours_angle = (start_time * HOURS_ANGLE) % 360;
const current_minutes_angle = (start_time * MINUTES_ANGLE) % 360;
const current_seconds_angle = (start_time * SECONDS_ANGLE) % 360;
//1초 뒤
const next_time = start_time + 1;
//1초 뒤 시침, 분침, 초침 각도
//360도일 때 모듈러 연산을 하면 0이 되므로 0일 경우에는 360으로 설정해줌
const next_hours_angle = (next_time * HOURS_ANGLE) % 360 || 360;
const next_minutes_angle = (next_time * MINUTES_ANGLE) % 360 || 360;
const next_seconds_angle = (next_time * SECONDS_ANGLE) % 360 || 360;
//초침이 시침 뒤에 있었는데 1초 뒤에 시침 앞으로 가면 카운트+1
if (
current_seconds_angle < current_hours_angle &&
next_seconds_angle >= next_hours_angle
)
cnt++;
//초침이 분침 뒤에 있었는데 1초 뒤에 분침 앞으로 가면 카운트+1
if (
current_seconds_angle < current_minutes_angle &&
next_seconds_angle >= next_minutes_angle
)
cnt++;
//1초 뒤에 시침 분침 초침 3개가 겹치면 -1(0시, 12시)
if (
next_seconds_angle === next_hours_angle &&
next_seconds_angle === next_minutes_angle
)
cnt--;
//시작 시간 업데이트
start_time = next_time;
}
return cnt;
}
'알고리즘' 카테고리의 다른 글
[프로그래머스 lv2] 두 원 사이의 정수 쌍 [JS] (0) | 2023.12.27 |
---|---|
[프로그래머스 lv2] 요격 시스템 JS [그리디] (0) | 2023.12.27 |
[PCCP 기출문제] 2번 / 석유 시추 JS (0) | 2023.12.25 |
[프로그래머스 lv2] 하노이의 탑 JS풀이 (0) | 2023.12.22 |
[프로그래머스] 혼자서하는 틱택토 JS (0) | 2023.12.20 |