-
[Swift] 조이스틱 프로그래머스 코딩테스트카테고리 없음 2021. 12. 9. 17:26
뻘짓 기록
우선 문제를 해결하기 위해서는 최소 횟수를 낼 "방법" 을 찾아야한다.
어차피 한글자씩 다 바꿔줘야하니 첫위치부터 오른쪽으로 순차적으로 바꾸면 되지 싶을까 했지만
역시 예외상황이 있었다.
예시의 JAZ처럼 가운데 A가있는 경우 오른쪽으로 두번 가는게 아니라 왼쪽으로 한번만 움직이면
해결이 가능하다. 이런 예외상황을 좀 더 찾아보고 패턴을 찾기로 했다.
그리고 왼쪽으로 갔다가 다시 오른쪽 혹은 오른쪽 갔다가 왼쪽은 비효율적이라 그럴일은 없다 판단하고
그럴경우 두가지 경우만 나온다
- 왼쪽으로 이동하면서 문자를 바꿔준다.
- 오른쪽으로 이동하면서 문자를 바꿔준다.
JAZ //왼쪽 이 이득 JAZZ //왼쪽 이 이득 JJAZZ // 둘다 같음 JJJAZZZ //오른쪽이 이득 JAA //안움직여야 이득
위 패턴을 보아하니 이런 결과값이 도출되었다.
- 두번째 글자가 A면 왼쪽으로 움직이는게 이득
- 그외는 모두 오른쪽으로 움직이는게 이득 혹은 본전
- 첫번쨰 이외 모든 글자가 A 인경우 안움직여야함
3번의 경우 지금까지 봤던 문자들 + 지금 보고있는 문자
말고 남은 모든 문자가 A인경우 움직이지않고 counting을 종료해야한다.
이제 두번째로 알파벳을 A - Z 사이에서 위로 올라갈지 아래로 올라갈지의 차이다.
ABCDEFGHIJKLM / NOPQRSTUVWXYZ
A- M 이면 순차적( 위로 )
N -Z 이면 반대로 (아래로) 바꿔야 가장 최소로 바꿀수있다.
B -> 1 M -> 12 Z -> 1 N -> 13
이러한 패턴이 나온다. 나는 우선 알파벳 순서를 저 두개로 나눈뒤
해당 알파벳의 인덱스를 받아오려한다.
let str1 = "ABCDEFGHIJKLM" let str2 = "ZYXWVUTSRQPON"
N-Z 알파벳은 역순으로했다. 그래야 위 결과값처럼 Z = 1 ,N =13 값을쉽게 받아올수 있기 때문이다.
결과 값을 받기위해 이런 조건들을 거친다.
- str1 에있나 str2에있나 확인
- str1 에있으면 인덱스를 받아옴 = 결과값
- str2 에있으면 인덱스값 +1 을 받아옴 = 결과값
E 찾을경우 -> str1 에있네? -> str1 에서 E 인덱스 받아와 -> 4 S 찾을경우 -> str2 에있네? -> str2에서 S인덱스 받아와 그리고 +1 -> 8
이제 모든 방법은 찾았으니 코드로 옮겨보자
- 수정사항
알파벳순 String을 사용하려했으나 (배열만들기 귀찮아서) String은 특정문자의 인덱스를 받아오는 메소드가없어서 배열로 변경함
let str1 = "ABCDEFGHIJKLM" let str2 = "ZYXWVUTSRQPON" ----------------------------------- let str1 = ["A","B","C","D","E","F","G","H","I","J","K","L","M"] let str2 = ["Z","Y","X","W","V","U","T","S","R","Q","P","O","N"]
그러나 더쉬운 방법이있었음
Array(str1)을 사용하면 자동으로 배열로바꿔줌
func solution(_ name:String) -> Int { var nameArray = Array(name) //name을 Array로 변경하여 작업이 쉽게함 var count = 0 ; if(nameArray[1] == "A"){ //두번째 문자 A인경우 왼쪽으로 이동 //첫번쨰꺼 세고 나서 첫번째 문자 제거후 //배열 역순으로 변경하면 오른쪽으로 이동할때랑 로직이 같다. count += getChangeCount(character:nameArray[0]) nameArray.remove(at:0) //나머지 문자가 모두 A인경우 끝내야함 let isFinish = checkIsAllA(name:nameArray) if !isFinish { count += 1 nameArray = nameArray.reversed() //첫번째거 카운팅 -> 제거 -> 카운팅1 추가(왼쪽으로 한칸 이동) -> 역순변경 //이후론 같은 로직 startCount(nameArray:&nameArray,count:&count) } }else { //그외는 오른쪽으로 이동 startCount(nameArray:&nameArray,count:&count) } return count } func startCount(nameArray: inout [Character], count: inout Int){ for (index,element) in nameArray.enumerated() { //문자 변경 카운팅 count += getChangeCount(character:element) //해당문자 제거 nameArray.remove(at:0) //남은문자 확인후 all A인경우 종료 let isFinish = checkIsAllA(name:nameArray) if isFinish { break }else { count += 1 } } } func checkIsAllA(name:[Character]) -> Bool{ var result: Bool = true for char in name { if (char != "A") { result = false break } } return result } func getChangeCount(character:Character) -> Int{ let str1 = Array("ABCDEFGHIJKLM") let str2 = Array("ZYXWVUTSRQPON") if(str1.contains(character)){ return str1.firstIndex(of: character)! }else{ return str2.firstIndex(of: character)! + 1 } }
위 코드 실행후 테스트 11에서 실패가 났다.
간과했던 문제는 왼쪽으로 갔다가 오른쪽으로 갈일은 없다고 판단했던 것이었다.
JJJAAAAAAAAAAJ
위와같은 경우 두번째가 A가 아니지만 왼쪽으로 갔다 오른쪽으로 움직이는게
훨신 더 적은 카운팅을 할수있다. 댓글 오류에서 왼쪽으로 갔다 오른쪽으로 가는 케이스가 고려가 안되어있고
오른쪽 → 왼쪽 으로만 고려가 되어있다 해서 그렇게만 풀어보려한다.
즉 두번째 A인 케이스는 잘못되었고
오른쪽으로 가는것 과 오른쪽→왼쪽으로 가는것 두개를 비교해서
적은값을 도출하는게 방법이다.
풀다 포기 후 모범 답안을 분석하기로 하였다....
모범답안 분석하기
우선 타입문제 → String을 받아서 Array(string) 하는방법을 사용했으나 여기선 이렇게 함
let name = name.map {$0} // ["J", "E", "R", "O", "E", "N"]
반복문에서 index만 필요하다면 range를 사용해 for문을 이렇게 사용했다
let range = 0..<name.count for i in range{ print(i) // 0,1,2,3,4,5 }
문자 변환 카운팅을 위해서 아스키 코드를 활용했다.
A는 65 → Z 는 90이다.
위로가는 방법 , 아래로 가는 방법 두개를 비교해 최솟값을 구하면된다.
위로가는 방법은 = 아스키코드번호 - 65
아래는 = 91 - 아스키코드번호 이렇게 계산하면 쉽다.
for i in range{ if name[i] != "A"{ let up = name[i].asciiValue! - 65 let down = 91 - name[i].asciiValue! changeCount += Int(min(up, down)) } }
이리 푸니 참 쉽다.
문제는 이동횟수였다.
해답에서는 이렇게 되어있었다.
var minMove = name.count - 1 for i in 0..<name.count { if name[i] != "A" { var next = i + 1 while next<name.count && name[next] == "A" { next += 1 } let move = 2 * i + name.count - next minMove = min(move, minMove) } }
봐도 모르겠어서 하나하나 대입해가면서 이해해본 결과 이 코드의 해답은 이러하다.''
BAAABB //2번쨰 A 앞에서 돌아가기 BABAAB //2번쨰 A 앞에서 돌아가기 BBAAAB //3번쨰 A 앞에서 돌아가기 AABABB // 오른쪽으로 세기 ABAABB // 3번쨰 A에서 돌아가기
오른쪽으로 순차적으로 움직이기 방법 이외에 A앞에서 돌아가는 방법만 생각했지만
몇번째 A에서 돌아갈지는 문자에 따라 달라진다.
BABAAB 는 맨처음 나오는 A 에서 돌아가지만
ABAABB 는 두번쨰 나오는 A에서 돌아가야 한다.
결국 이렇게 나뉜다.
오른쪽으로 순차적으로 움직이기
첫번쨰 A 앞에서 돌아가기
두번쨰 A 앞에서 돌아가기
세번쟤.... 네번째...
결국 다 세보고 비교해야한다.
반복문을 돌린후 다음값에 A가 나오면 최솟값을 계산해낸후 기존 최솟값과 비교하여
계속 최소값을 최신화하면서 풀이 해야한다.
위 반복문에서 i 는 A가 발견된 직전 index 이며
next 는 A의 위치이다. A가 연달아 나올수 있으므로 while문을 돌려 next를 +1 하면서
연달아 나온 마지막 A값을 next에 대입했다.
while next<name.count && name[next] == "A" { next += 1 }
되돌아간 무빙 횟수 = A직전 index * 2(오른쪽으로 갔다가 되돌아 왔으므로) + A 이후 A아닌 문자 갯수
이렇게 계산한후 직전 최소값과 지금 최소값을 비교해 값을 도출해 냈다.
let move = 2 * i + name.count - next minMove = min(move, minMove)