Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions kakao/jjh/solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 문제 풀이

## 🎯 접근 전략

- 최소 공배수까지의 노란불 누적합 리스트 생성(get_lcm, get_cumsum 함수)
- 노란불이 1초면 그냥 누적합 리스트 사용
- 2초 이상인 경우, 생성한 누적합 리스트 복사 + 슬라이싱으로 사용할 노란불 누적합 리스트 자르기 때문에 정렬
- 복사한 리스트에 2초 이상인 범위 추가
- 슬라이싱을 통한 필요한 노란불 누적합 리스트 구하기
- 중복된 것이 있을 수도 있기 때문에 중복 검사 및 최소값 찾기(solution 함수)

---

## ✅ 테스트 결과

- 1차: 14/25
- 2차: 24/25
- 3차: 25/25

---

## ⚠️ Edge Case

- [[1,1,1], [1,2,1], [2,1,1]] <- 노란불 기준으로 정렬하지 않으면 슬라이싱 범위 바깥에 필요한 누적합 리스트 생성

---

## 🕰️ 시간 / 공간 복잡도

### Time Complexity

- min:
- max: O(n\*\*4)
Comment on lines +32 to +33
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

시간 복잡도를 O(n**4)로 분석하셨는데, 이 분석의 근거가 명확하지 않습니다. 알고리즘의 실제 복잡도는 신호 주기의 최소공배수(LCM) 값에 크게 좌우됩니다. LCM 값을 L, 신호의 개수를 N, 평균 주기 길이를 P_avg, 평균 노란불 길이를 Y_avg라고 가정하면, get_cumsum 함수에서 생성하는 데이터의 총량은 대략 O(N * (L / P_avg) * Y_avg)에 비례합니다. set 교집합 연산의 시간 복잡도 또한 이 데이터 크기에 영향을 받습니다. 따라서 입력 signals의 값에 따라 복잡도가 크게 변동될 수 있으므로, L과 같은 요소를 포함하여 복잡도를 더 상세하게 기술하는 것이 정확합니다.

References
  1. 명시된 시간/공간 복잡도가 실제 코드 구조와 일치하는지 확인해야 합니다. 현재 복잡도 분석은 코드의 핵심 요소인 LCM의 영향을 고려하지 않아 부정확할 수 있습니다. (link)


### Space Complexity

- min: O(n\*\*2)
- max: O(n\*\*2)
Comment on lines +30 to +38
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

작성해주신 시간 복잡도 O(n^4)와 공간 복잡도 O(n^2)가 현재 코드 구현과 어떻게 연결되는지 이해하기 어렵습니다. 현재 알고리즘의 실제 성능은 신호등 주기들의 최소공배수(LCM) 값에 크게 좌우됩니다. 이 값은 n(신호등 개수)보다는 신호 주기의 값 자체에 더 큰 영향을 받습니다. 예를 들어, 신호 주기가 서로소인 큰 수들이라면 LCM은 매우 커질 수 있습니다. 복잡도를 어떻게 도출하셨는지 다시 한번 검토해보시고, LCM의 영향을 고려하여 복잡도를 수정하거나 설명을 보충해주시면 좋겠습니다. 이는 리포지토리 스타일 가이드의 "Consistency check" 항목(9행)에 해당합니다.

References
  1. Stated time/space complexity should be consistent with the actual code structure. (link)

87 changes: 87 additions & 0 deletions kakao/jjh/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""
각 신호등별 누적합 저장 and y인 경우만 저장 + 2초 이상인 경우 전부 저장(이 경우엔 리스트 길이가 꽤나 길어질 가능성 존재, 해시로 해야 하나?)
각 신호등의 누적합 결과 중 같은 값이 있으면 해당 값 리턴, 없으면 -1리턴
"""
import math
import copy

# 1. 최소 공배수 구하기
def get_lcm(signals):
lcm = 1
for signal in signals:
lcm = (lcm * sum(signal)) // math.gcd(lcm, sum(signal))
return lcm

# 2. 각 신호등 누적합 구하기
def get_cumsum(signals):
n = len(signals)
signals.sort(key=lambda x: x[1], reverse=True)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

signals.sort(...)는 입력으로 받은 signals 리스트를 직접 수정합니다. 함수가 인자를 직접 수정(in-place modification)하면, 함수 외부에서 예기치 않은 동작을 유발할 수 있습니다. sorted() 내장 함수를 사용하여 정렬된 새로운 리스트를 만들고, 그 리스트를 사용해 로직을 진행하는 것이 더 안전합니다. 그 후 for i, signal in enumerate(signals): 부분을 for i, signal in enumerate(sorted_signals):로 변경하여 사용하세요.

Suggested change
signals.sort(key=lambda x: x[1], reverse=True)
sorted_signals = sorted(signals, key=lambda x: x[1], reverse=True)
References
  1. 코드는 안전해야 합니다. 입력 파라미터를 직접 수정하는 것은 예기치 않은 부작용(side effect)을 일으킬 수 있으므로 지양하는 것이 좋습니다. (link)

lcm = get_lcm(signals)
cumsum = []

for _ in range(n):
cumsum.append([])

cnt = 0
for i, signal in enumerate(signals):
f = signal[0] + signal[1]
r = sum(signal)
cumsum[i].append(f)
for j in range(1, int(lcm/sum(signal))):
cumsum[i].append(f + (r * j))
if signal[1] != 1:
cnt += 1
x = copy.deepcopy(cumsum[i])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

cumsum[i]는 숫자(정수)만 담고 있는 1차원 리스트이므로, copy.deepcopy()를 사용할 필요 없이 cumsum[i].copy()를 사용하는 것이 더 효율적입니다. deepcopy는 중첩된 객체 구조를 복사할 때 필요하며, 단순한 리스트에는 과도한 연산이 될 수 있습니다. 이는 리포지토리 스타일 가이드의 "Pythonic and built-in optimization" 항목(13-15행)에 해당합니다.

Suggested change
x = copy.deepcopy(cumsum[i])
x = cumsum[i].copy()
References
  1. Detect wheel-reinvention that can be replaced with Python standard libraries or more efficient choices. (link)

cumsum.append(x)
std = signal[1] - 1
for q in cumsum[i]:
for p in range(1, std + 1):
cumsum[-1].append(q-p)
Comment on lines +27 to +39
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

get_cumsum 함수 내의 변수명(f, r, std, q, p, cnt)이 축약되어 있어 코드의 의도를 파악하기 어렵습니다. 예를 들어, ffirst_yellow_end_time, rcycle_duration 과 같이 명확한 이름으로 변경하면 가독성이 크게 향상될 것입니다.

References
  1. 변수/함수 네이밍이 의도를 명확하게 설명하는지 확인해야 합니다. 축약된 변수명은 코드의 가독성을 저해합니다. (link)

cumsum = cumsum[cnt:]

return cumsum # [[4, 11, 18, 25], [3, 7, 11, 15, 19, 23, 27], [5, 12, 19, 26, 4, 3, 11, 10, 18, 17, 25, 24]]
Comment on lines +16 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

get_cumsum 함수의 로직이 매우 복잡하고 이해하기 어렵습니다. 특히 cumsum 리스트를 순회하는 중에 리스트 자체를 변경하고(append), cnt 변수를 사용해 마지막에 슬라이싱(cumsum[cnt:])하는 방식은 로직을 따라가기 어렵게 만들고 잠재적인 버그를 유발할 수 있습니다.

각 신호에 대한 노란불 시간 집합을 독립적으로 생성한 후, 이들을 리스트에 추가하는 방식으로 리팩토링하면 코드가 훨씬 명확하고 안전해질 것입니다.

예를 들어, 다음과 같은 구조를 고려해볼 수 있습니다.

def get_all_yellow_times(signals):
    lcm = get_lcm(signals)
    all_times = []
    for signal in signals:
        # 현재 signal의 노란불 시간들을 계산하여 set으로 만듭니다.
        current_yellow_times = set()
        # ... 시간 계산 로직 ...
        all_times.append(current_yellow_times)
    return all_times

이렇게 하면 copy.deepcopy, cnt 변수, 복잡한 리스트 조작 없이 간결하게 로직을 구현할 수 있습니다. 또한, 각 신호등의 노란불 시간을 set으로 바로 만들면 solution 함수에서 set(cumsum[0])과 같이 다시 변환할 필요가 없어집니다.

References
  1. 코드는 읽기 쉽고 안전해야 합니다. 현재 get_cumsum 함수는 복잡한 로직과 불필요한 조건들로 인해 가독성이 떨어지고 잠재적인 오류를 포함할 수 있습니다. (link)

Comment on lines +16 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

get_cumsum 함수의 로직이 복잡하여 가독성과 유지보수성이 떨어집니다. 아래 사항들을 개선하는 것을 제안합니다.

  • Side Effect 주의: signals.sort()는 입력 리스트 signals를 직접 변경합니다. 함수가 입력값을 수정하는 것은 예상치 못한 버그를 유발할 수 있으므로, sorted(signals, ...)를 사용해 정렬된 새 리스트를 만드는 것이 더 안전한 방법입니다.
  • 복잡한 리스트 관리: cumsum 리스트에 원소를 추가(append)했다가 나중에 잘라내는(slicing) 방식은 코드의 흐름을 따라가기 어렵게 만듭니다. 각 신호등의 노란불 시간 목록을 독립적으로 계산하여 별도의 리스트에 모으는 방식으로 리팩토링하면 코드가 훨씬 명확해질 것입니다.
  • 변수명 개선: f, r, q, p와 같은 한 글자 변수명은 의도를 파악하기 어렵습니다. first_yellow_end_time, period, yellow_end_time, time_offset처럼 의미를 명확히 드러내는 이름으로 변경하면 좋겠습니다.
References
  1. Variable/function naming should explain intent clearly. Unnecessary or duplicated logic should be avoided. (link)



def solution(signals):
cumsum = get_cumsum(signals)
is_common = set(cumsum[0]).intersection(*cumsum[1:])
is_common = sorted(list(is_common))

if is_common:
return is_common[0]
else:
return -1
Comment on lines +50 to +53
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

if/else 문을 사용하여 결과를 반환하는 로직은 Python의 삼항 연산자(ternary operator)를 사용하면 더 간결하게 표현할 수 있습니다.

    return is_common[0] if is_common else -1
References
  1. 간결한 최신 Python 사용법을 제안합니다. 삼항 연산자를 사용하면 조건부 반환을 한 줄로 깔끔하게 작성할 수 있습니다. (link)



if __name__ == '__main__':
signals = [[1, 2, 1], [1, 1, 1], [2, 1, 1]] # [[2, 1, 2], [5, 1, 1]]
print(solution(signals))

# [[2, 1, 2], [5, 1, 1]] 13
# [[2, 3, 2], [3, 1, 3], [2, 1, 1]] 11 7,7,4 => 28
# [[3, 3, 3], [5, 4, 2], [2, 1, 2]] 193
# [[1, 1, 4], [2, 1, 3], [3, 1, 2], [4, 1, 1]] -1

"""
1) 2 3 5/ 7 8 10/ 12 13 15/ 17 18 20/ 22 23 25/ 27 28 30/ 32 33 35
2) 5 6 7/ 12 13 14/ 19 20 21/ 26 27 28/ 33 34 35

1) [3, 8, 13, 18, 23, 28, 33] -> int(lcm/sum(signal))번 반복 == 7 // 공비: sum(signal) == 5 // 초항: signal[0] + signal[1] == 3 => 3, 3 + (5 * i == 1)
2) [6, 13, 20, 27, 34]
"""

"""
1) 2 5(3,4,5) 7/ 9 12(10,11,12) 14/ 16 19(17,18,19) 21/ 23 26(24,25,26) 28
2) 3 4 7/ 10 11 14/ 17 18 21/ 24 25 28
3) 2 3 4/ 6 7 8/ 10 11 12/ 14 15 16/ 18 19 20/ 22 23 24/ 26 27 28

0) [5, 12, 19, 26]
1) [3, 4, 5, 10, 11, 12, 17, 18, 19, 24, 25, 26]
2) [4, 11, 18, 25]
3) [3, 7, 11, 15, 19, 23, 27]
"""
"""
[[5, 12, 19, 26, 33, 40, 47, 54, 61, 68, 75, 82, 89, 96, 103, 110, 117, 124, 131, 138, 145, 152, 159, 166, 173, 180, 187, 194],
[4, 11, 18, 25, 32, 39, 46, 53, 60, 67, 74, 81, 88, 95, 102, 109, 116, 123, 130, 137, 144, 151, 158, 165, 172, 179, 186, 193],
[3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, 55, 59, 63, 67, 71, 75, 79, 83, 87, 91, 95, 99, 103, 107, 111, 115, 119, 123, 127, 131, 135, 139, 143, 147, 151, 155, 159, 163, 167, 171, 175, 179, 183, 187, 191, 195, 4, 3, 11, 10, 18, 17, 25, 24, 32, 31, 39, 38, 46, 45, 53, 52, 60, 59, 67, 66, 74, 73, 81, 80, 88, 87, 95, 94, 102, 101, 109, 108, 116, 115, 123, 122, 130, 129, 137, 136, 144, 143, 151, 150, 158, 157, 165, 164, 172, 171, 179, 178, 186, 185, 193, 192]]
"""
Comment on lines +60 to +87
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

코드 리뷰나 제출 시에는 테스트에 사용했던 예시나 디버깅을 위한 중간 결과 출력 등은 제거하는 것이 좋습니다. 최종 코드에는 실제 로직과 관련된 부분만 남겨두어 가독성을 높이는 것을 권장합니다. 이는 리포지토리 스타일 가이드의 "Readability and safety" 항목(19행)에 해당합니다.

References
  1. Detect leftover debug prints or unnecessary comments. (link)