행렬 테두리 회전하기

rows x columns 크기인 행렬이 있습니다. 행렬에는 1부터 rows x columns까지의 숫자가 한 줄씩 순서대로 적혀있습니다. 이 행렬에서 직사각형 모양의 범위를 여러 번 선택해, 테두리 부분에 있는 숫자들을 시계방향으로 회전시키려 합니다. 각 회전은 (x1, y1, x2, y2)인 정수 4개로 표현하며, 그 의미는 다음과 같습니다.

  • x1 행 y1 열부터 x2 행 y2 열까지의 영역에 해당하는 직사각형에서 테두리에 있는 숫자들을 한 칸씩 시계방향으로 회전합니다.

다음은 6 x 6 크기 행렬의 예시입니다.

이 행렬에 (2, 2, 5, 4) 회전을 적용하면, 아래 그림과 같이 2행 2열부터 5행 4열까지 영역의 테두리가 시계방향으로 회전합니다. 이때, 중앙의 15와 21이 있는 영역은 회전하지 않는 것을 주의하세요.

행렬의 세로 길이(행 개수) rows, 가로 길이(열 개수) columns, 그리고 회전들의 목록 queries가 주어질 때, 각 회전들을 배열에 적용한 뒤, 그 회전에 의해 위치가 바뀐 숫자들 중 가장 작은 숫자들을 순서대로 배열에 담아 return 하도록 solution 함수를 완성해주세요.


제한사항
  • rows는 2 이상 100 이하인 자연수입니다.
  • columns는 2 이상 100 이하인 자연수입니다.
  • 처음에 행렬에는 가로 방향으로 숫자가 1부터 하나씩 증가하면서 적혀있습니다.
    • 즉, 아무 회전도 하지 않았을 때, i 행 j 열에 있는 숫자는 ((i-1) x columns + j)입니다.
  • queries의 행의 개수(회전의 개수)는 1 이상 10,000 이하입니다.
  • queries의 각 행은 4개의 정수 [x1, y1, x2, y2]입니다.
    • x1 행 y1 열부터 x2 행 y2 열까지 영역의 테두리를 시계방향으로 회전한다는 뜻입니다.
    • 1 ≤ x1 < x2 ≤ rows, 1 ≤ y1 < y2 ≤ columns입니다.
    • 모든 회전은 순서대로 이루어집니다.
    • 예를 들어, 두 번째 회전에 대한 답은 첫 번째 회전을 실행한 다음, 그 상태에서 두 번째 회전을 실행했을 때 이동한 숫자 중 최솟값을 구하면 됩니다.

 

입출력 예시
rows columns queries result
6 6 [[2,2,5,4],[3,3,6,6],[5,1,6,3]] [8, 10, 25]
3 3 [[1,1,2,2],[1,2,2,3],[2,1,3,2],[2,2,3,3]] [1, 1, 5, 3]
100 97 [[1,1,100,97]] [1]

 

내가 푼 풀이

- 회전을 구현하여 해당 범위를 교체하고, 최솟값을 저장한다.

- 따로 다른 알고리즘 없이 회전하는 동작을 구현하면 된다.

 

import Foundation

func solution(_ rows:Int, _ columns:Int, _ queries:[[Int]]) -> [Int] {
    var board = Array(repeating:Array(repeating: 0, count: columns+1), count: rows+1)
    var answer = [Int]()
    // 입력
    for i in 1...rows {
        for j in 1...columns {
            board[i][j] = ((i-1) * columns + j)
        }
    }
    
    // 쿼리 돌리기
    var b = board
    for query in queries {
        var a = moveNums(query, b)
        var minNum = a.removeLast()
        answer += minNum
        b = a
    }
    
    return answer
}

func moveNums(_ query: [Int],_ arr: [[Int]]) -> [[Int]] {
    var dx = [1, 0, -1, 0]
    var dy = [0, 1,  0, -1]
    var a = arr
    var mx = query[1]
    var my = query[0]
    var rc = query[3] - query[1]
    var cc = query[2] - query[0]
    var previous = a[my][mx]
    var minNum = a[my][mx]
    // 회전하며, 저장된 값과 그 다음의 값을 비교해 최솟값 갱신
    for i in 0..<4 {
        if i == 0 || i == 2 {
            for j in 0..<rc {
                mx += dx[i]
                my += dy[i]
                minNum = min(minNum, a[my][mx])
                var tmp = a[my][mx]
                a[my][mx] = previous
                previous = tmp
                
            }
        } else {
            for j in 0..<cc {
                mx += dx[i]
                my += dy[i]
                minNum = min(minNum, a[my][mx])
                var tmp = a[my][mx]
                a[my][mx] = previous
                previous = tmp
                
            }
        }
    }
    
    a.append([minNum])
    return a
}

나타난 오류 및 막힌점

RandomStudy 프로젝트 제작 중,

할 일을 추가하는 popUpView로 present 후, 데이터 추가한 뒤 dismiss로 돌아왔을때,

데이터를 전달하는 과정에서 어떻게 구성해야 할지 막히게 되었다.

 

첫번째 접근방법: viewWillAppear()

뷰가 나타날때 마다 실행되는 viewWillAppear 메서드를 이용해 갔다 온 뒤 데이터를 최신화하는 함수를 넣어두자.

그 전에 이 메서드가 작동하는지 확인하기 위해 메서드가 실행되면 viewWillAppear가 출력되도록 테스트 해봤다.

 // AddViewController.Swift
 override func viewWillAppear(_ animated: Bool) {
        print("viewWillAppear")
    }
 // AddPopUpViewController.Swift
 // 버튼을 누르면 dismiss하는 이벤트메서드
 @objc func addButtonTapped() {
        guard let text = self.textField.text else { return }
        if text == "" {
            errorLabel.text = "공백은 추가할 수 없습니다."
            errorLabel.isHidden = false
        } else {
            if viewModel.isContainsElement(str: text) {
                errorLabel.text = "같은 목록이 존재합니다."
                errorLabel.isHidden = false
            } else {
                errorLabel.isHidden = true
                viewModel.addData(str: text)
                self.dismiss(animated: true)
            }
        }
    }

 

<< 실행 결과 >>

 

 

버튼을 눌러서 dismiss를 실행해도 viewWillAppear가 프린트 되지 않았다.

이유를 찾아보니 이는 modalstyle과 연관이 있다고 한다.

fullscreen automatic(기본값) overscreen 

보통 위와같이 modalstyle을 설정하는데, 설정하지 않으면 automatic으로 설정된다.

세가지를 비교해보았을때,

- fullscreen: 이전 뷰를 제거하고, 다음 뷰로 present한다.

- automatic, overscreen: 뷰가 present되고 이전 뷰는 뷰 계층구조에서 제거되지 않는다.

 

따라서 fullscreen만 viewWillAppear메서드가 작동되는이유는 다음 뷰로 이동하면서 이전뷰가 뷰 계층구조에서 제거되었기 때문에,

이전 뷰로 다시 돌아온다면 viewWillAppear메서드를 이용해 뷰가 나타날것을 알려줘야하기 때문이다.

 

뷰를 여기저기 이동하면서 다시 돌아올 때, 데이터를 최신화해야하는 과정이 필요하거나, 레이아웃을 변경해야한다면 viewWillAppear 자주 호출했었는데, modalstyle에 따라 사용을 유념해야겠다.

 

하지만 이 프로젝트에서 PopUpVC는 팝업된 작은 창을 나타내기위해선 fullscreen 방식과는 맞지 않았다.(배경이 검은색으로 아예 다른 창으로 이동하는 느낌이난다..)

 

 

두번째 접근방법으로 delegate패턴을 이용했다. https://jenikeju.tistory.com/267

나타난 오류 및 막힌점

RandomStudy 프로젝트 제작 중,

할 일을 추가하는 popUpView로 present 후, 데이터 추가한 뒤 dismiss로 돌아왔을때,

데이터를 전달하는 과정에서 어떻게 구성해야 할지 막히게 되었다.

델리게이트 패턴을 사용했지만 delegate = nil로 위임자를 못찾는 오류

 

두번째 접근방법: Delegate

두번째 방법은 delegate 방법이다.

PopUpVC에서 프로토콜을 선언해 데이터를 추가한다면, 프로토콜안 메서드를 데이터 받는 VC에서 메서드를 구현해 데이터를 최신화 해보려고 한다.

 

<AddVC>

// AddPopUpViewController.swift

protocol AddPopUpViewControllerDelegate: AnyObject {
    func sendedDataFromPopUp()
}

class AddPopUpViewController: UIViewController {
    weak var delegate: AddPopUpViewControllerDelegate?
    
    
     @objc func addButtonTapped() {
        guard let text = self.textField.text else { return }
        if text == "" {
            errorLabel.text = "공백은 추가할 수 없습니다."
            errorLabel.isHidden = false
        } else {
            if viewModel.isContainsElement(str: text) {
                errorLabel.text = "같은 목록이 존재합니다."
                errorLabel.isHidden = false
            } else {
                errorLabel.isHidden = true
                viewModel.addData(str: text)
                delegate?.sendedDataFromPopUp() // <--- 델리게이트 메서드
                self.dismiss(animated: true)
            }
        }
    }
   
}

-> 데이터를 보낼 곳에서 delegate을 선언해두고, 프로토콜을 만들고, 버튼을 누르면 위임자가 프로토콜의 메서드를 작동하도록 하였다.

 

<PopUpVC>

// AddViewController.swift
class AddViewController: UIViewController {
    
    private var popupVC = AddPopUpViewController()
    // 데이터 바인딩
    private func bindings() {
        popupVC.delegate = self
    }
    // PopUpVC로 가는 이벤트 메서드
     @objc func addCategory() {
        let addPopUpView = AddPopUpViewController()
        addPopUpView.modalPresentationStyle = .overFullScreen
        addPopUpView.modalTransitionStyle = .crossDissolve
        self.present(addPopUpView, animated: true, completion: nil)
    }
}
// 위임자를 본인(AddVC)로 설정하고 프로토콜의 메서드를 여기서 구현
extension AddViewController: AddPopUpViewControllerDelegate {
    func sendedDataFromPopUp() {
        viewModel.fetchData()
        DispatchQueue.main.async { [weak self] in
            self?.tableView.reloadData()
        }
    }
}

-> delegate를 addVC로 설정하고, popupVC에서 버튼을 누르면 addVC에서 구현한 프로토콜의 메서드를 작동하도록 하였다.

 

<< 실행 결과 >>

 

delegate를 설정함에도 불구하고, 위임자가 누구인지 인식을 못하는듯 하다..

 

빼먹은게 있는가 코드를 확인해보고, 델리게이트 패턴의 과정을 되짚어보았다..

1. 프로토콜 구성 

2. 데이터 보내는곳에서 delegate 선언 형식은 프로토콜

3. 데이터 보내는 버튼에서 프로토콜의 메서드 동작

4. 데이터 받는곳에서 delegate 위임자 지정

5. 프로토콜을 상속받아 메서드의 세부동작 구현

 

분명히 이대로 했는데 왜 위임자를 인식하지 못할까?

 

생각해낸 것들

1. 메모리 주소가 다르다(아마 유력)

클래스는 참조형식이다.

delegate 위임자를 선언하기위해(self) delegate가 선언된 뷰컨을 객체로 생성할 것이다.

또한 popupVC로 가기위해 popupvc객체를 생성했다.

결론적으로 popupVC객체를 두개를 만들었지만, 두개의 메모리 주소는 다르다.

// AddViewController.swift
class AddViewController: UIViewController {
	private var popupvc = AddPopUpViewController() // popupVC 객체 생성 1
	private func bindings() {
       popupvc.delegate = self
    }
    
    @objc func addCategory() {
        let addPopUpView = AddPopUpViewController()	// popupVC 객체 생성 2
        addPopUpView.delegate = self
        addPopUpView.modalPresentationStyle = .overFullScreen
        addPopUpView.modalTransitionStyle = .crossDissolve
        self.present(addPopUpView, animated: true, completion: nil)
    }
}

 

첫번째 객체 생성했을때, 메모리 현황

 

두번째 객체 생성했을때, 메모리 현황

 

아마 첫번째 객체생성의 메모리주소는 0x1088ad400, 두번째 객체생성 메모리주소는 0x10881bc00 인것 같다.

첫번째 객체에서 delegate를 위임해줬지만

실제로 popupVC로 가는 객체의 메모리주소는 두번째이므로 delegate 위임을 인식하지 못한것같다.

 

present를 통해 vc를 이동했을때, 이동하기위해 생성한 객체로 delegate 선언을 해보니 프로토콜에 정의된 메서드가 실행이 되었다!

 

문제

'Dummy' 라는 도스게임이 있다. 이 게임에는 뱀이 나와서 기어다니는데, 사과를 먹으면 뱀 길이가 늘어난다. 뱀이 이리저리 기어다니다가 벽 또는 자기자신의 몸과 부딪히면 게임이 끝난다.

게임은 NxN 정사각 보드위에서 진행되고, 몇몇 칸에는 사과가 놓여져 있다. 보드의 상하좌우 끝에 벽이 있다. 게임이 시작할때 뱀은 맨위 맨좌측에 위치하고 뱀의 길이는 1 이다. 뱀은 처음에 오른쪽을 향한다.

뱀은 매 초마다 이동을 하는데 다음과 같은 규칙을 따른다.

  • 먼저 뱀은 몸길이를 늘려 머리를 다음칸에 위치시킨다.
  • 만약 벽이나 자기자신의 몸과 부딪히면 게임이 끝난다.
  • 만약 이동한 칸에 사과가 있다면, 그 칸에 있던 사과가 없어지고 꼬리는 움직이지 않는다.
  • 만약 이동한 칸에 사과가 없다면, 몸길이를 줄여서 꼬리가 위치한 칸을 비워준다. 즉, 몸길이는 변하지 않는다.

사과의 위치와 뱀의 이동경로가 주어질 때 이 게임이 몇 초에 끝나는지 계산하라.

입력

첫째 줄에 보드의 크기 N이 주어진다. (2 ≤ N ≤ 100) 다음 줄에 사과의 개수 K가 주어진다. (0 ≤ K ≤ 100)

다음 K개의 줄에는 사과의 위치가 주어지는데, 첫 번째 정수는 행, 두 번째 정수는 열 위치를 의미한다. 사과의 위치는 모두 다르며, 맨 위 맨 좌측 (1행 1열) 에는 사과가 없다.

다음 줄에는 뱀의 방향 변환 횟수 L 이 주어진다. (1 ≤ L ≤ 100)

다음 L개의 줄에는 뱀의 방향 변환 정보가 주어지는데, 정수 X와 문자 C로 이루어져 있으며. 게임 시작 시간으로부터 X초가 끝난 뒤에 왼쪽(C가 'L') 또는 오른쪽(C가 'D')로 90도 방향을 회전시킨다는 뜻이다. X는 10,000 이하의 양의 정수이며, 방향 전환 정보는 X가 증가하는 순으로 주어진다.

출력

첫째 줄에 게임이 몇 초에 끝나는지 출력한다.

내가 푼 풀이

- 주어진 규칙에 맞춰서 함수를 구현한다.

 

import Foundation

// 입력받기
let N = Int(readLine()!)!
var count = 0
var board = Array(repeating: Array(repeating: 0, count: N+1), count: N+1)
let K = Int(readLine()!)!
for i in 0..<K {
    let loc = readLine()!.split(separator: " ").map{Int(String($0))!}
    board[loc[0]][loc[1]] = 1
}

let L = Int(readLine()!)!
var moves = [Int: String]()
for i in 0..<L {
    let m = readLine()!.split(separator: " ").map{String($0)}
    moves[Int(m[0])!] = m[1]
}

// 뱀의 현위치와 방향배열
var snake = [(x: 1, y: 1)]
var d = ["r", "d", "l", "u"]
var di = 0

// 뱀의 위치를 큐 형식(FIFO)으로 위치를 갱신한다.
while true {
    count += 1
    
    let loc = snake.last!
    var moved: (x: Int, y: Int) = (x: 0, y: 0)
    switch d[di] {
    case "r":
        moved = (x: loc.x + 1, y: loc.y)
    case "d":
        moved = (x: loc.x, y: loc.y + 1)
    case "l":
        moved = (x: loc.x - 1, y: loc.y)
    case "u":
        moved = (x: loc.x, y: loc.y - 1)
    default:
        continue
    }
    
    // 뱀이 벽에 부딪히거나 자신의 몸을 부딪히면 끝
    if moved.x < 1 || moved.x >= N+1 || moved.y < 1 || moved.y >= N+1 {
        break
    }
    if snake.contains(where: { $0.x == moved.x && $0.y == moved.y }) {
        break
    }
    
    if board[moved.y][moved.x] != 1 {
        snake.removeFirst()
    } else {
        board[moved.y][moved.x] = 0
    }
    snake.append(moved)
    // 방향전환
    if moves[count] != nil {
        let changeDirect = moves[count]!
        if changeDirect == "D" {
            di += 1
        } else if changeDirect == "L" {
            di -= 1
        }
    }
    if di >= d.count {
        di = 0
    } else if di < 0 {
        di = d.count - 1
    }
}

print(count)

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-1916 최소비용 구하기 Swift  (0) 2024.04.24
BOJ-1027 고층 건물 Swift  (0) 2024.04.23
BOJ-1024 수열의 합 Swift  (1) 2024.04.20
BOJ-1004 어린 왕자 Swift  (1) 2024.04.20
BOJ-2447 별 찍기-10 Swift  (0) 2024.04.15

[카카오 인턴] 수식 최대화

IT 벤처 회사를 운영하고 있는 라이언은 매년 사내 해커톤 대회를 개최하여 우승자에게 상금을 지급하고 있습니다.
이번 대회에서는 우승자에게 지급되는 상금을 이전 대회와는 다르게 다음과 같은 방식으로 결정하려고 합니다.
해커톤 대회에 참가하는 모든 참가자들에게는 숫자들과 3가지의 연산문자(+, -, *) 만으로 이루어진 연산 수식이 전달되며, 참가자의 미션은 전달받은 수식에 포함된 연산자의 우선순위를 자유롭게 재정의하여 만들 수 있는 가장 큰 숫자를 제출하는 것입니다.
단, 연산자의 우선순위를 새로 정의할 때, 같은 순위의 연산자는 없어야 합니다. 즉, + > - > * 또는 - > * > + 등과 같이 연산자 우선순위를 정의할 수 있으나 +,* > - 또는 * > +,-처럼 2개 이상의 연산자가 동일한 순위를 가지도록 연산자 우선순위를 정의할 수는 없습니다. 수식에 포함된 연산자가 2개라면 정의할 수 있는 연산자 우선순위 조합은 2! = 2가지이며, 연산자가 3개라면 3! = 6가지 조합이 가능합니다.
만약 계산된 결과가 음수라면 해당 숫자의 절댓값으로 변환하여 제출하며 제출한 숫자가 가장 큰 참가자를 우승자로 선정하며, 우승자가 제출한 숫자를 우승상금으로 지급하게 됩니다.

예를 들어, 참가자 중 네오가 아래와 같은 수식을 전달받았다고 가정합니다.

"100-200*300-500+20"

일반적으로 수학 및 전산학에서 약속된 연산자 우선순위에 따르면 더하기와 빼기는 서로 동등하며 곱하기는 더하기, 빼기에 비해 우선순위가 높아 * > +,- 로 우선순위가 정의되어 있습니다.
대회 규칙에 따라 + > - > * 또는 - > * > + 등과 같이 연산자 우선순위를 정의할 수 있으나 +,* > - 또는 * > +,- 처럼 2개 이상의 연산자가 동일한 순위를 가지도록 연산자 우선순위를 정의할 수는 없습니다.
수식에 연산자가 3개 주어졌으므로 가능한 연산자 우선순위 조합은 3! = 6가지이며, 그 중 + > - > * 로 연산자 우선순위를 정한다면 결괏값은 22,000원이 됩니다.
반면에 * > + > - 로 연산자 우선순위를 정한다면 수식의 결괏값은 -60,420 이지만, 규칙에 따라 우승 시 상금은 절댓값인 60,420원이 됩니다.

참가자에게 주어진 연산 수식이 담긴 문자열 expression이 매개변수로 주어질 때, 우승 시 받을 수 있는 가장 큰 상금 금액을 return 하도록 solution 함수를 완성해주세요.

 

[제한사항]
  • expression은 길이가 3 이상 100 이하인 문자열입니다.
  • expression은 공백문자, 괄호문자 없이 오로지 숫자와 3가지의 연산자(+, -, *) 만으로 이루어진 올바른 중위표기법(연산의 두 대상 사이에 연산기호를 사용하는 방식)으로 표현된 연산식입니다. 잘못된 연산식은 입력으로 주어지지 않습니다.
    • 즉, "402+-561*"처럼 잘못된 수식은 올바른 중위표기법이 아니므로 주어지지 않습니다.
  • expression의 피연산자(operand)는 0 이상 999 이하의 숫자입니다.
    • 즉, "100-2145*458+12"처럼 999를 초과하는 피연산자가 포함된 수식은 입력으로 주어지지 않습니다.
    • "-56+100"처럼 피연산자가 음수인 수식도 입력으로 주어지지 않습니다.
  • expression은 적어도 1개 이상의 연산자를 포함하고 있습니다.
  • 연산자 우선순위를 어떻게 적용하더라도, expression의 중간 계산값과 최종 결괏값은 절댓값이 263 - 1 이하가 되도록 입력이 주어집니다.
  • 같은 연산자끼리는 앞에 있는 것의 우선순위가 더 높습니다.

입출력 예
expression result
"100-200*300-500+20" 60420
"50*6-3*2" 300

 

내가 푼 풀이

- 주어진 연산자의 우선순위를 모든 경우의수로 탐색해본다.

 

import Foundation

func solution(_ expression:String) -> Int64 {
    var operation = ["+", "-", "*"]
    var arr = [String]()
    var oop = [[String]]()
    var num = ""
    var answer = Int.min
    // 우선순위의 모든 경우의수 뽑기
    func combi(_ targetArr: [String],_ arr: [String]) {
        if arr.count == 3 {
            oop.append(arr)
        }
        for i in 0..<targetArr.count {
            if !arr.contains(targetArr[i]) {
                combi(targetArr, arr + [targetArr[i]])
            }
        }
    }
    combi(operation, [])
    
    // 연산자와 피연산자 구분하여 저장
    for i in expression {
        if operation.contains(String(i)) {
            arr.append(num)
            arr.append(String(i))
            num = ""
        } else {
            num += String(i)
        }
    }
    arr.append(num)
    
    // 모든경우의수 탐색하여 최댓값 구하기
    for i in oop {
        var a = arr
        for j in i {
            a = operate(String(j), a)
        }
        answer = max(answer, abs(Int(a[0])!))
    }
    
    return Int64(answer)
}

// 주어진 연산자로 계산
func operate(_ op: String,_ expression: [String]) -> [String] {
    var ex = [String]()
    var index = -1
    for i in 0..<expression.count {
        if i == index {
            index = -1
            continue
        }
        if expression[i] != op {
            ex.append(expression[i])
        } else {
            var n1 = Int(ex.removeLast())!
            var n2 = Int(expression[i+1])!
            switch op {
                case "*":
                ex.append(String(n1 * n2))
                case "-":
                ex.append(String(n1 - n2))
                case "+":
                ex.append(String(n1 + n2))
                default :
                continue
            }
            index = i+1
        }
    }
    return ex
}

 

괄호 변환

카카오에 신입 개발자로 입사한 "콘"은 선배 개발자로부터 개발역량 강화를 위해 다른 개발자가 작성한 소스 코드를 분석하여 문제점을 발견하고 수정하라는 업무 과제를 받았습니다. 소스를 컴파일하여 로그를 보니 대부분 소스 코드 내 작성된 괄호가 개수는 맞지만 짝이 맞지 않은 형태로 작성되어 오류가 나는 것을 알게 되었습니다.
수정해야 할 소스 파일이 너무 많아서 고민하던 "콘"은 소스 코드에 작성된 모든 괄호를 뽑아서 올바른 순서대로 배치된 괄호 문자열을 알려주는 프로그램을 다음과 같이 개발하려고 합니다.

용어의 정의

'('  ')' 로만 이루어진 문자열이 있을 경우, '(' 의 개수와 ')' 의 개수가 같다면 이를 균형잡힌 괄호 문자열이라고 부릅니다.
그리고 여기에 '('와 ')'의 괄호의 짝도 모두 맞을 경우에는 이를 올바른 괄호 문자열이라고 부릅니다.
예를 들어, "(()))("와 같은 문자열은 "균형잡힌 괄호 문자열" 이지만 "올바른 괄호 문자열"은 아닙니다.
반면에 "(())()"와 같은 문자열은 "균형잡힌 괄호 문자열" 이면서 동시에 "올바른 괄호 문자열" 입니다.

'(' 와 ')' 로만 이루어진 문자열 w가 "균형잡힌 괄호 문자열" 이라면 다음과 같은 과정을 통해 "올바른 괄호 문자열"로 변환할 수 있습니다.

1. 입력이 빈 문자열인 경우, 빈 문자열을 반환합니다. 
2. 문자열 w를 두 "균형잡힌 괄호 문자열" u, v로 분리합니다. 단, u는 "균형잡힌 괄호 문자열"로 더 이상 분리할 수 없어야 하며, v는 빈 문자열이 될 수 있습니다. 
3. 문자열 u가 "올바른 괄호 문자열" 이라면 문자열 v에 대해 1단계부터 다시 수행합니다. 
  3-1. 수행한 결과 문자열을 u에 이어 붙인 후 반환합니다. 
4. 문자열 u가 "올바른 괄호 문자열"이 아니라면 아래 과정을 수행합니다. 
  4-1. 빈 문자열에 첫 번째 문자로 '('를 붙입니다. 
  4-2. 문자열 v에 대해 1단계부터 재귀적으로 수행한 결과 문자열을 이어 붙입니다. 
  4-3. ')'를 다시 붙입니다. 
  4-4. u의 첫 번째와 마지막 문자를 제거하고, 나머지 문자열의 괄호 방향을 뒤집어서 뒤에 붙입니다. 
  4-5. 생성된 문자열을 반환합니다.

"균형잡힌 괄호 문자열" p가 매개변수로 주어질 때, 주어진 알고리즘을 수행해 "올바른 괄호 문자열"로 변환한 결과를 return 하도록 solution 함수를 완성해 주세요.

매개변수 설명

  • p는 '(' 와 ')' 로만 이루어진 문자열이며 길이는 2 이상 1,000 이하인 짝수입니다.
  • 문자열 p를 이루는 '(' 와 ')' 의 개수는 항상 같습니다.
  • 만약 p가 이미 "올바른 괄호 문자열"이라면 그대로 return 하면 됩니다.

입출력 예

p result
"(()())()" "(()())()"
")(" "()"
"()))((()" "()(())()"

 

내가 푼 풀이

- 주어진 동작들을 함수로 구현한 뒤, 재귀호출 형식으로 구현한다.

 

import Foundation

func solution(_ p:String) -> String {
    return correctSameString(p)
}

// 전체적인 동작
func correctSameString(_ str: String) -> String {
    // 올바른문자열이라면 재귀 탈출
    if isCorrectString(str) {
        return str
    } else {
        var arr = divideString(s: str)
        // 분리한 문자열 u가 올바른 문자열이면 v는 다시 재귀호출
        if isCorrectString(arr[0]) {
            return "\(arr[0])" + "\(correctSameString(arr[1]))"
        } else {
            // 분리한 문자열 u가 올바른 문자열이 아니라면, v는 재귀호출하고, (v)u 형식으로 출력
            var s = ""
            var cs = ""
            var sarr = arr[0]
            // u문자열의 앞뒤 자르기
            if sarr.count != 0 {
                s = String(sarr.prefix(sarr.count-1))
                s.remove(at: s.startIndex)
            }
            // u문자열 뒤집기
            for i in s {
                if i == "(" {
                    cs += ")"
                } else {
                    cs += "("
                }
            }
            // 형식에 맞게 재귀호출
            return "(" + correctSameString(arr[1]) + ")" + "\(cs)"
        }
    }
}

// 문자열을 두개의 올바른 괄호문자열로 나눈다.
func divideString(s: String) -> [String] {
    var str = s
    var leftStr = ""
    var rightStr = ""
    for i in 1...str.count/2 {
        var index = i * 2
        leftStr = String(str.prefix(index))
        rightStr = String(str.suffix(str.count - index))
        if isSameCountString(leftStr) && isSameCountString(rightStr) {
            return [leftStr, rightStr]
        }
    }
    return [leftStr, rightStr]
}

// 균형잡힌 문자열인지 판단
func isSameCountString(_ p: String) -> Bool {
    var str = p
    var count = 0
    for i in str {
        if i == "(" {
            count += 1
        } else if i == ")" {
            count -= 1
        }
    }
    
    if count != 0 {
        return false
    } else {
        return true
    }
}

// 올바른 문자열인지 판단
func isCorrectString(_ p: String) -> Bool {
    var stack = [String]()
    for i in p {
        switch i {
            case "(":
            stack.append("(")
            case ")":
            if !stack.isEmpty {
                stack.removeLast()
            }
            default:
            continue
        }
    }
    if stack.isEmpty {
        return true
    } else {
        return false
    }
}

문제

N과 L이 주어질 때, 합이 N이면서, 길이가 적어도 L인 가장 짧은 연속된 음이 아닌 정수 리스트를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N과 L이 주어진다. N은 1,000,000,000보다 작거나 같은 자연수이고, L은 2보다 크거나 같고, 100보다 작거나 같은 자연수이다.

출력

만약 리스트의 길이가 100보다 작거나 같으면, 연속된 수를 첫째 줄에 공백으로 구분하여 출력한다. 만약 길이가 100보다 크거나 그러한 수열이 없을 때는 -1을 출력한다.

내가 푼 풀이

- 문제의 난이도 치곤 낮은 정답률을 자랑한다..

- swift에서 int형식으로 나눗셈을 하면 나머지는 모두 버려버린다.

 

 

접근방법: 수학

1. 수열은 연속되어있다.

2. 연속된 수열은 다음과 나타낼 수 있다.

(1, 2, 3, 4, 5) --> (a, a+1, a+2, a+3, a+4) (a=1)

3. 위의방법으로 하였을때 연속된 수열의 합은 5a + 10이된다.

4. 리스트의 길이가 2부터 100까지 위 방법으로 a를 구할 수 있다면 연속된 수열이 존재한다.

 

코드로 구현하면 다음과 같다

import Foundation

//입력받기
let input = readLine()!.split(separator: " ").map{Int(String($0))!}
var answer = 0
var sum = 0
var result = [Int]()

// 수열의 길이 2부터 100까지 검색
while answer < 100 {
    answer += 1
    
    // 수열의 합을 구한다.
    var plus = 0
    for i in 1..<answer {
        plus += i
    }
    var dev = input[0] - plus
    
    // plus의 값이 N보다 커진다면, 수열로 나타낼 수 없다(수열의 길이가 너무 길다)
    if dev < 0 {
        break
    }
    // 수열의합을 구했을때, a가 N보다 작은 수라면 연속된 수열이 존재한다고 판단.
    var a = Double(dev) / Double(answer)
    if dev % answer == 0 && answer >= input[1] {
        result.append(Int(a))
        break
    }
    
}

if result.isEmpty {
    print(-1)
} else {
    for i in 1..<answer {
        result.append(result[0]+i)
    }
    print(result.map{String($0)}.joined(separator: " "))
}

 

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-1027 고층 건물 Swift  (0) 2024.04.23
BOJ-3190 뱀 Swift  (0) 2024.04.22
BOJ-1004 어린 왕자 Swift  (1) 2024.04.20
BOJ-2447 별 찍기-10 Swift  (0) 2024.04.15
BOJ-2580 스도쿠 Swift  (0) 2024.04.15

문제

어린 왕자는 소혹성 B-664에서 자신이 사랑하는 한 송이 장미를 위해 살아간다. 어느 날 장미가 위험에 빠지게 된 것을 알게 된 어린 왕자는, 장미를 구하기 위해 은하수를 따라 긴 여행을 하기 시작했다. 하지만 어린 왕자의 우주선은 그렇게 좋지 않아서 행성계 간의 이동을 최대한 피해서 여행해야 한다. 아래의 그림은 어린 왕자가 펼쳐본 은하수 지도의 일부이다.

빨간 실선은 어린 왕자가 출발점에서 도착점까지 도달하는데 있어서 필요한 행성계 진입/이탈 횟수를 최소화하는 경로이며, 원은 행성계의 경계를 의미한다. 이러한 경로는 여러 개 존재할 수 있지만 적어도 3번의 행성계 진입/이탈이 필요하다는 것을 알 수 있다.

위와 같은 은하수 지도, 출발점, 도착점이 주어졌을 때 어린 왕자에게 필요한 최소의 행성계 진입/이탈 횟수를 구하는 프로그램을 작성해 보자. 행성계의 경계가 맞닿거나 서로 교차하는 경우는 없다. 또한, 출발점이나 도착점이 행성계 경계에 걸쳐진 경우 역시 입력으로 주어지지 않는다.

입력

입력의 첫 줄에는 테스트 케이스의 개수 T가 주어진다. 그 다음 줄부터 각각의 테스트케이스에 대해 첫째 줄에 출발점 (x1, y1)과 도착점 (x2, y2)이 주어진다. 두 번째 줄에는 행성계의 개수 n이 주어지며, 세 번째 줄부터 n줄에 걸쳐 행성계의 중점과 반지름 (cx, cy, r)이 주어진다.

출력

각 테스트 케이스에 대해 어린 왕자가 거쳐야 할 최소의 행성계 진입/이탈 횟수를 출력한다.

내가 푼 풀이

- 주어진 행성 안으로 들어가면 진입, 행성밖으로 나가면 이탈 한 총 횟수를 구한다.

- 행성은 중점과 반지름이 주어진다.

- 행성이 안, 밖으로 나가는 경우의수를 구한다.

 

- 출발점과 도착점이 같은 행성에 있다면, 진입/이탈이 필요없음

- 출발점과 도착점이 다른 행성 안에있다면, 진입/이탈 필요함

- 출발점과 도착점중 한지점만 행성안에 있다면 이탈이 필요함

- 출발점과 도착점이 모두 행성밖에 있다면 진입/이탈 필요없음

위 경우의수를 종합해보면 출발점과 도착점이 행성 안에 존재하는지 알아야한다.

 

한지점이 행성안에 존재한다면 지점과 행성의 중점거리는 행성의 반지름보다 작아야한다.

한지점이 행성 밖에 존재한다면 지점과 행성의 중점거리는 행성의 반지름보다 커야한다.

 

이를 바탕으로 코드로 구현해보자

import Foundation

let T = Int(readLine()!)!

for i in 0..<T {
    //입력받기
    let input = readLine()!.split(separator: " ").map{Int(String($0))!}
    let start = [input[0], input[1]]
    let arrive = [input[2], input[3]]
    let N = Int(readLine()!)!
    var count = 0
    
    // 주어진 모든 행성안에 출발점, 도착점이 있는지 확인
    for j in 0..<N {
        // x,y좌표에서 피타고라스의정리를 이용한 두 지점의 거리를 구하는방법 사용
        let planet = readLine()!.split(separator: " ").map{Int(String($0))!}
        let sDist = ((start[0] - planet[0]) * (start[0] - planet[0])) + ((start[1] - planet[1]) * (start[1] - planet[1]))
        let aDist = ((arrive[0] - planet[0]) * (arrive[0] - planet[0])) + ((arrive[1] - planet[1]) * (arrive[1] - planet[1]))
        let powR = planet[2] * planet[2]
        
        // 출발점이 반지름보다 짧고, 도착점이 반지름보다 길다면, 출발점은 행성안에 존재 -> 이탈필요
        if sDist < powR {
            if aDist > powR {
                count += 1
            }
        }
        // 출발점이 반지름보다 길고, 도착점이 반지름보다 짧다면, 도착점은 행성안에 존재 -> 진입필요
        if aDist < powR {
            if sDist > powR {
                count += 1
            }
        }
        
        // 나머지 경우의수들은 모두 행성밖에 존재하거나, 같은행성 안에존재
    }
    print(count)
}

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-3190 뱀 Swift  (0) 2024.04.22
BOJ-1024 수열의 합 Swift  (1) 2024.04.20
BOJ-2447 별 찍기-10 Swift  (0) 2024.04.15
BOJ-2580 스도쿠 Swift  (0) 2024.04.15
BOJ-1013 Contact Swift  (0) 2024.04.15

+ Recent posts