ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Swift] 조이스틱 프로그래머스 코딩테스트
    카테고리 없음 2021. 12. 9. 17:26

    뻘짓 기록

     

    우선 문제를 해결하기 위해서는 최소 횟수를 낼 "방법" 을 찾아야한다.

    어차피 한글자씩 다 바꿔줘야하니 첫위치부터 오른쪽으로 순차적으로 바꾸면 되지 싶을까 했지만

    역시 예외상황이 있었다.

    예시의 JAZ처럼 가운데 A가있는 경우 오른쪽으로 두번 가는게 아니라 왼쪽으로 한번만 움직이면

    해결이 가능하다. 이런 예외상황을 좀 더 찾아보고 패턴을 찾기로 했다.

    그리고 왼쪽으로 갔다가 다시 오른쪽 혹은 오른쪽 갔다가 왼쪽은 비효율적이라 그럴일은 없다 판단하고

    그럴경우 두가지 경우만 나온다

    1. 왼쪽으로 이동하면서 문자를 바꿔준다.
    2. 오른쪽으로 이동하면서 문자를 바꿔준다.
    JAZ  //왼쪽 이 이득
    JAZZ //왼쪽 이 이득
    JJAZZ // 둘다 같음 
    JJJAZZZ //오른쪽이 이득
    JAA //안움직여야 이득
    
    

    위 패턴을 보아하니 이런 결과값이 도출되었다.

    1. 두번째 글자가 A면 왼쪽으로 움직이는게 이득
    2. 그외는 모두 오른쪽으로 움직이는게 이득 혹은 본전
    3. 첫번쨰 이외 모든 글자가 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 값을쉽게 받아올수 있기 때문이다.

    결과 값을 받기위해 이런 조건들을 거친다.

    1. str1 에있나 str2에있나 확인
    2. str1 에있으면 인덱스를 받아옴 = 결과값
    3. 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)
    

    댓글

Designed by Tistory.