TIL/알고리즘 공부
프로그래머스 Lv. 2 Swift 알고리즘 - 조이스틱_그리디
여의도사노비
2023. 2. 1. 18:59
728x90
3일만에 알고리즘 문제를 풀었는데... 거의 2주만에 푼 느낌..? 그리고 매일 풀다가 3일 쉬니까 죄짓는 느낌이 들었다.
이제부터 알고리즘 유형에 맞게 문제를 풀어보려고 하는데 처음부터 상당히 문제를 푸는데 오래걸렸다 ㅠㅠ
그리디 유형의 문제인데 그리디는 아무리 이론을 이해하고 있어도 막상 풀 때 적용시키는 것과는 또 다른 문제가 있는 것 같다.
A 상황에서 가장 최적의 방법을 선택 -> B 상황으로 이동 -> B 상황에서 가장 최적의 방법을 선택 -> ...
이런 식의 개념으로 접근하였는데 역시나 예상 외의 변수를 전부 생각하지 못했는지 틀리고 말았다!
그래서 구글링하여 코드를 참고하였다 ^^...
이 문제를 풀면서 생각한 논리적 흐름
- 우선 A - Z 까지 Character끼리 값을 비교하는 것은 굉장히 불편하기 때문에 각 알파벳마다 값을 부여한다.
- A라면 값을 바꾸지 않아도 되니 0이라고 가정하고 Z는 25의 값을 갖는다.
- 각 위치와 상관없이 (위 / 아래) 버튼을 이용하여 알파벳을 바꿔주어야하는 경우는 고정적으로 발생하기 때문에
- 값이 0인 원소를 제외하고 모든 값들을 전부 더해준다.
- 값이 0인 원소가 있을 경우 이제 위치를 움직여줘야하는데
- 첫 번째 위치에서 시작했을 때 오른쪽으로 가는 것과 왼쪽(맨 끝에서 다시 시작)으로 가는 것 중 어떤 방식이 덜 움직이는지 파악한다.
- 이를 idxR, idxL로 기록하여 몇칸 움직였고 현재 위치가 어디인지를 파악한다.
- 이렇게 위치가 바뀐 경우 시작했던 자리를 0으로 만들어주고 다시 새로운 위치에서 좌, 우 값을 사용하여 최소 길이의 위치를 찾는다.
- 이를 반복하면 끝
- 하지만 내 코드는 뭔가 오류가 있었다...
- 그래서 코드를 참고했는데 내가 생각했던 방식과 비슷하게 푼 분들은 2022년 1월 이후로 개정된 테스트 케이스를 맞추지 못한 분들이 많았다.
- 그래서 또 찾아보니까 이를 해결한 분이 계셨는데... 왼쪽, 오른쪽 한계점을 기준으로 왕복 거리를 이용한 분이셨다... 솔직히 이건 생각도 못했다... 오늘도 한 수 배워 갑니다...
* 처음 틀린 코드
import Foundation
func solution(_ name:String) -> Int {
let alphabet = (97...122).map({Character(UnicodeScalar($0)).uppercased()})
var dict: [String : Int] = [:] // 알파벳에 값을 붙여 몇칸 이동해야하는지 확인
var compare: [Int] = []
var idx: Int = 0 // 현재 위치
var moveCount: Int = 0
var ans: Int = 0
for (j,i) in alphabet.enumerated() {
dict[i] = j
}
for i in name {
compare.append(dict[i.uppercased()]! > 13 ? 26 - dict[i.uppercased()]! : dict[i.uppercased()]!)
}
if !compare.contains(0) {
ans = compare.reduce(0, +) + compare.count-1
} else {
ans = compare.reduce(0, +)
for _ in 0...4 {
var idxR: Int = 0 // 오른쪽으로 몇 칸?
var idxL: Int = 0 // 왼쪽으로 몇 칸?
// 몇 칸이나 가는지 돌려보자... 오른쪽
for i in 1... {
// 헛돌기 시작하면 끝내주자
if i >= compare.count {
break
}
if compare[(idx + i) % compare.count] == 0 {
continue
} else {
idxR = i
break
}
}
// 몇 칸이나 가는지 돌려보자... 왼쪽
for i in 1... {
// 헛돌기 시작하면 끝내주자
if i >= compare.count {
break
}
if compare[abs(compare.count - i + idx) % compare.count] == 0 {
continue
} else {
idxL = i
break
}
}
// 더이상 없으면 탈출
if idxR == 0 && idxL == 0 {
break
}
compare[idx] = 0
// 오른쪽이 더 많이 움직였다면? -> 왼쪽으로 가는게 낫다!
if idxR > idxL {
idx = abs(compare.count - idxL) % compare.count
moveCount += idxL
} else {
idx = (idx + idxR) % compare.count
moveCount += idxR
}
}
ans += moveCount
}
return ans
}
* 참고한 코드
import Foundation
func solution(_ name:String) -> Int {
let name = name.utf8.map{ min(Int($0) - 65, 90 - Int($0) + 1) }
let countUD = name.reduce(0, +)
var countRL = name.count - 1
let idx = 0
for idxR in 0..<name.count {
var idxL = idxR + 1
while idxL < name.count && name[idxL] == 0 {
idxL += 1
}
let compareDistance = min(idxR - idx, name.count - idxL)
countRL = min(countRL, (idxR - idx) + (name.count - idxL) + compareDistance)
}
return countUD + countRL
}
정리(Today I Learned)
1. 그리디의 유형이 어떤 방식으로 나오는지는 이제 좀 알것 같다. 하지만 DFS, DP 등과 같이 확실하게 컨셉이 잡힌 문제와 달리 그리디는 방식만 차용할 뿐 결국 이를 풀어낼 풀이과정이 필요한데 그게 참 어렵다. 그리고 그게 정말 최적인지, 영향을 미치는 다른 요인은 없는지 고민하는 부분이 가장 어려운 일인거 같다!