문제

세준시에는 고층 빌딩이 많다. 세준시의 서민 김지민은 가장 많은 고층 빌딩이 보이는 고층 빌딩을 찾으려고 한다. 빌딩은 총 N개가 있는데, 빌딩은 선분으로 나타낸다. i번째 빌딩 (1부터 시작)은 (i,0)부터 (i,높이)의 선분으로 나타낼 수 있다. 고층 빌딩 A에서 다른 고층 빌딩 B가 볼 수 있는 빌딩이 되려면, 두 지붕을 잇는 선분이 A와 B를 제외한 다른 고층 빌딩을 지나거나 접하지 않아야 한다. 가장 많은 고층 빌딩이 보이는 빌딩을 구하고, 거기서 보이는 빌딩의 수를 출력하는 프로그램을 작성하시오.

입력

첫째 줄에 빌딩의 수 N이 주어진다. N은 50보다 작거나 같은 자연수이다. 둘째 줄에 1번 빌딩부터 그 높이가 주어진다. 높이는 1,000,000,000보다 작거나 같은 자연수이다.

출력

첫째 줄에 문제의 정답을 출력한다.

내가 푼 풀이

 

접근방법: 브루탈포스와 기하학

- 문제에서 힌트를 얻었는데, 두 지붕을 잇는 선분이 있는데, 다른 빌딩이 그 선분을 접하거나 넘으면 안된다고 한다.

- 이는 두 지붕의 x,y좌표를 이용해 두 선을 잇는 직선의 방정식을 구하고, 그 사이의 빌딩들이 그 직선을 접하거나 넘으면 볼 수 없다고 판단한다.

- 직선의 방정식을 구해서 한 빌딩의 지붕 x,y좌표를 대입한다면, 주어진 방적식 y = ax + b 에 대입할 것이다.

- 여기서 대입하여 양쪽의 값을 비교하여 해당 빌딩이 직선을 넘는지, 접점인지 판단한다.

- y >= ax + b 의 경우, 해당 점은 직선과 접한 점이거나, 직선보다 위에 존재하는 점이다. (빌딩을 볼 수 없음)

- y < ax + b의 경우, 해당 점은 직선보다 아래 위치한 점이다. (빌딩을 볼 수 있음)

 

이 조건을 이용해 모든 경우의수를 따져보고 최댓값을 출력한다.

 

주의할점) a와 b를 구하고, 값을 비교하는 과정에서 소숫점이 날아가지 않게 적절한 형변환을 해주어야한다.

 

import Foundation

// 입력받기
let N = Int(readLine()!)!
let buildings = readLine()!.split(separator: " ").map{Int(String($0))!}
var converted = [(x: Int, y: Int)]()
var result = Int.min
// 주어진 빌딩을 지붕의 좌표로 변환
for i in 0..<buildings.count {
    converted.append((x: i+1,y: buildings[i]))
}

// 모든 경우 탐색
for i in 0..<converted.count {
    var count = 0
    for j in 0..<converted.count {
        // 같은 빌딩이면 패스
        if i == j { continue }
        var see = true
        let x1: Double = Double(converted[i].x)
        let x2: Double = Double(converted[j].x)
        let y1: Double = Double(converted[i].y)
        let y2: Double = Double(converted[j].y)
        
        // y = ax + b 의 방정식중 a,b를 구하는 과정
        let a = (y2 - y1) / (x2 - x1)
        let b = y1 - (x1 * a)
        
        // 현재 빌딩 이전에 위치한 빌딩들과의 탐색
        if i > j {
            for k in j+1..<i {
                let leftValue: Double = Double(converted[k].y)
                let rightValue: Double = Double(converted[k].x) * a + b
                if leftValue >= rightValue {
                    see = false
                    break
                }
            }
        } else {
            // 현재 빌딩 이후에 위치한 빌딩들과의 탐색
            for k in i+1..<j {
                let leftValue: Double = Double(converted[k].y)
                let rightValue: Double = Double(converted[k].x) * a + b
                if leftValue >= rightValue {
                    see = false
                    break
                }
            }
        }
        if see {
            count += 1
        } else {
        }
    }
    result = max(result, count)
}
print(result)

 

 

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

BOJ-18352 특정 거리의 도시 찾기 Swift  (0) 2024.04.24
BOJ-1916 최소비용 구하기 Swift  (0) 2024.04.24
BOJ-3190 뱀 Swift  (0) 2024.04.22
BOJ-1024 수열의 합 Swift  (1) 2024.04.20
BOJ-1004 어린 왕자 Swift  (1) 2024.04.20

문제

'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

문제

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

문제

재귀적인 패턴으로 별을 찍어 보자. N이 3의 거듭제곱(3, 9, 27, ...)이라고 할 때, 크기 N의 패턴은 N×N 정사각형 모양이다.

크기 3의 패턴은 가운데에 공백이 있고, 가운데를 제외한 모든 칸에 별이 하나씩 있는 패턴이다.

***
* *
***

N이 3보다 클 경우, 크기 N의 패턴은 공백으로 채워진 가운데의 (N/3)×(N/3) 정사각형을 크기 N/3의 패턴으로 둘러싼 형태이다. 예를 들어 크기 27의 패턴은 예제 출력 1과 같다.

입력

첫째 줄에 N이 주어진다. N은 3의 거듭제곱이다. 즉 어떤 정수 k에 대해 N=3k이며, 이때 1 ≤ k < 8이다.

출력

첫째 줄부터 N번째 줄까지 별을 출력한다.

내가 푼 풀이

- 재귀호출로 위 패턴을 주어진 N만큼 호출하는건 알겠는데... 규칙을 찾기 어려웠다.

- 다른사람의 풀이를 참고했다. 해당 문제는 분할정복기법이다.

N = 3일때, 가운데를 제외하고 8군데를 그린다.

N = 9일때, N=3일때의 경우를 8군데 그린다.

N = 27일때, N=9일때의 경우를 8군데 그린다. -> N= 3일때의 경우를 64군데 그리면된다.

이를 재귀호출해주면 되는데 규칙은 가운데를 비우는것이다.

 

0,0 0,1 0,2
1,0 1,1 1,2
2,0 2,1 2,2

N=3일때 [1, 1] 을 제외한 8군데를 그린다.

이는 x % 3 = 1, y % 3 = 1 이 된다는것이다.

 

N=9일때 위 세칸을 보자

0,0 0,1 0,2 0,3 0,4 0,5 0,6 0,7 0,8
1,0 1,1 1,2 1,3 1,4 1,5 1,6 1,7 1,8
2,0 2,1 2,2 2,3 2,4 2,5 2,6 2,7 2,8

공백인 위치는 [1, 1], [1, 4], [1,7] 이 된다.

첫번째  [1, 1]: x % 3 = 1, y % 3 = 1

두번째 [1, 4]: x %3 = 1, y % 3 = 1

세번째 [1, 7]: x %3 = 1, y % 3 = 1 가 된다.

 

문제는 N = 9일때의 중간 공백이다.

중간공백의 좌표는 [3, 3], [3, 4] [3, 5] 등 9개가 있지만, 3 % 3 = 0이 된다.

그래서 위의 규칙과 같이 만들어 재귀호출을 하기 위해 왼쪽값을 3보다 작은값으로 만든다.

-> 3 / 3 % 3 = 1형식으로 만든다.

또한 N이 9를 넘어간다면  9 /3 = 3 이되고, 3%3은 0이 되므로 위의 규칙과 맞지않는다.

따라서 나눗셈의 오른쪽값은 왼쪽값보다 크거나 같아야 3보다 작은값으로 나뉘어 3으로 나눠도 0이되지않는다.

 

-> 재귀호출할때마다 N으로 나누어 0이나오면 N을 3으로 나누어 재귀호출한다.

 

이를 코드로 구현하면 다음과 같다.

import Foundation

// 입력받기
var N = Int(readLine()!)!
var out = ""

// 그리기함수
// a / b 일때 b가 a보다 크면 0이다.
// 그래서 b를 3으로 나누고 다시 재귀호출
func draw(x: Int, y: Int, num: Int) {
    if x / num % 3 == 1 && y / num % 3 == 1 {
        out += " "
    } else {
        if num / 3 == 0 {
            out += "*"
        } else {
            draw(x: x, y: y, num: num/3)
        }
    }
}

for i in 0..<N {
    for j in 0..<N {
        draw(x: i, y: j, num: N)
    }
    out += "\n"
}
print(out)

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

BOJ-1024 수열의 합 Swift  (1) 2024.04.20
BOJ-1004 어린 왕자 Swift  (1) 2024.04.20
BOJ-2580 스도쿠 Swift  (0) 2024.04.15
BOJ-1013 Contact Swift  (0) 2024.04.15
BOJ-5430 AC Swift  (1) 2024.04.15

문제

스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다. 이 게임은 아래 그림과 같이 가로, 세로 각각 9개씩 총 81개의 작은 칸으로 이루어진 정사각형 판 위에서 이뤄지는데, 게임 시작 전 일부 칸에는 1부터 9까지의 숫자 중 하나가 쓰여 있다.

 

나머지 빈 칸을 채우는 방식은 다음과 같다.

  1. 각각의 가로줄과 세로줄에는 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.
  2. 굵은 선으로 구분되어 있는 3x3 정사각형 안에도 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.

위의 예의 경우, 첫째 줄에는 1을 제외한 나머지 2부터 9까지의 숫자들이 이미 나타나 있으므로 첫째 줄 빈칸에는 1이 들어가야 한다.

또한 위쪽 가운데 위치한 3x3 정사각형의 경우에는 3을 제외한 나머지 숫자들이 이미 쓰여있으므로 가운데 빈 칸에는 3이 들어가야 한다.

이와 같이 빈 칸을 차례로 채워 가면 다음과 같은 최종 결과를 얻을 수 있다.

게임 시작 전 스도쿠 판에 쓰여 있는 숫자들의 정보가 주어질 때 모든 빈 칸이 채워진 최종 모습을 출력하는 프로그램을 작성하시오.

입력

아홉 줄에 걸쳐 한 줄에 9개씩 게임 시작 전 스도쿠판 각 줄에 쓰여 있는 숫자가 한 칸씩 띄워서 차례로 주어진다. 스도쿠 판의 빈 칸의 경우에는 0이 주어진다. 스도쿠 판을 규칙대로 채울 수 없는 경우의 입력은 주어지지 않는다.

출력

모든 빈 칸이 채워진 스도쿠 판의 최종 모습을 아홉 줄에 걸쳐 한 줄에 9개씩 한 칸씩 띄워서 출력한다.

스도쿠 판을 채우는 방법이 여럿인 경우는 그 중 하나만을 출력한다.

내가 푼 풀이

- 빈칸의 좌표를 기록해둔 뒤, 백트래킹을 이용해 숫자를 넣는다.

- 백트래킹을 사용할 때 이전의 기록들을 가져가야한다는점을 잊지말자.

- 이전의 기록들이 조건에 포함되어 불필요한 경우의수까지 탐색하지 않게 해야한다.

 

import Foundation

//입력받기
var board = [[Int]]()
var result = [[Int]]()
var blank = [(x: Int, y: Int)]()
for i in 0..<9 {
    board.append(readLine()!.split(separator: " ").map{Int(String($0))!})
    for j in 0..<board[i].count {
        if board[i][j] == 0 {
            blank.append((x: i, y: j))
        }
    }
}

// 세로 비교
func col(loc: (x: Int, y: Int), num: Int) -> Bool {
    for i in 0..<9 {
        if board[i][loc.y] == num {
            return true
        }
    }
    return false
}

//가로 비교
func row(loc: (x: Int, y: Int), num: Int) -> Bool {
    return board[loc.x].contains(num)
}

// 인접된 사각형 비교
func square(loc: (x: Int, y: Int), num: Int) -> Bool {
    let leftUp = (x: loc.x / 3 * 3, y: loc.y / 3 * 3)
    for i in leftUp.x..<leftUp.x + 3 {
        for j in leftUp.y..<leftUp.y+3 {
            if board[i][j] == num {
                return true
            }
        }
    }
    return false
}
// dfs
func dfs(count: Int) {
    // 정답을 얻는다면 바로 리턴
    if !result.isEmpty {
        return
    }
    // 모든 빈칸을 조건에 맞춰 적었다면 리턴
    if count == blank.count {
        result = board
        return
    }
    // 현재 좌표
    var loc = blank[count]
    // 1부터 9까지 조건에 맞는 숫자를 넣는다.
    for i in 1...9 {
        if !row(loc: loc, num: i) && !col(loc: loc, num: i) && !square(loc: loc, num: i) {
            board[loc.x][loc.y] = i
            dfs(count: count+1)
            board[loc.x][loc.y] = 0
        }
    }
}
dfs(count: 0)

for k in 0..<result.count {
    let ans = result[k].map{String($0)}.joined(separator: " ")
    print(ans)
}

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

BOJ-1004 어린 왕자 Swift  (1) 2024.04.20
BOJ-2447 별 찍기-10 Swift  (0) 2024.04.15
BOJ-1013 Contact Swift  (0) 2024.04.15
BOJ-5430 AC Swift  (1) 2024.04.15
BOJ-1253 좋다 Swift  (0) 2024.04.14

문제

“무한히 넓은 저 우주에 인류만이 홀로 존재한다면, 그건 정말 슬픈 일이 아닐까요”

푸에르토리코 아레시보에 위치한 아레시보 전파망원경(Arecibo radio telescope)은 수십 년째 존재하지 않을 지도 모르는 외계 문명으로부터의 전파를 수신하기 위해 밤하늘을 바라보고 있다.

이 망원경이 수집한 전파 속에서 자연적으로 발생하기 힘든 패턴들을 찾아내어, 그것을 증거로 외계 문명의 존재 여부를 가리려는 노력은 줄곧 이어져왔지만 아직까지도 그러한 패턴은 발견되지 않았다. 한국 천문학계의 자존심 김동혁 박사는 국내 기술로 이러한 탐사를 진행하기 위하여 다음의 전파 표기를 표준으로 삼았다.

전파의 기본 단위는 { 0 , 1 } 두 가지로 구성되어있으며, x+ ( ) 는 임의의 개수(최소 1개) x의 반복으로 이루어진 전파의 집합을 나타낸다.

(xyx)+ ( ) 는 괄호 내의 xyx의 반복으로 이루어진 전파의 집합을 뜻한다. 아래는 이해를 돕기 위한 예제이다.

  • 1+ = { 1, 11, 111, 1111, 11111, … }
  • 10+ = { 10, 100, 1000, 10000, 100000, … }
  • (01)+ = { 01, 0101, 010101, 01010101, 0101010101, … }
  • (1001)+ = { 1001, 10011001, 100110011001, … }
  • 10+11 = { 1011, 10011, 100011, 1000011, 10000011, … }
  • (10+1)+ = { 101, 1001, 10001, 1011001, 1001101, 100011011000001, … }

반복을 의미하는 + 외에도 or 를 의미하는 | 기호가 있다. { x | y } 는 x 혹은 y 를 의미하는 것으로, { 0+ | 1+ } 는 { 0 , 1 , 00 , 11 , 000 , 111 , … } 의 집합을 의미한다. 아래는 두 기호를 복합적으로 사용한 예이다.

  • (100 | 11)+ = { 100 , 11 , 10011 , 11100 , 1110011100 , 100111111100100, … }

최근 김동혁 박사는 아레시보 전파망원경에서 star Vega(직녀성) 으로부터 수신한 전파 기록의 일부를 조사하여 그 전파들의 패턴을 분석하여 아래와 같이 기록하였다.

  • (100+1+ | 01)+

김동혁 박사는 다양한 전파 기록 중에서 위의 패턴을 지니는 전파를 가려내는 프로그램을 필요로 한다. 이를 수행할 수 있는 프로그램을 작성하라.

입력

입력의 첫 줄에는 테스트 케이스의 개수 T가 주어진다. 그 다음 줄부터 각각의 테스트 케이스에 대해 전파를 표현하는, { 0, 1 }만으로 이루어진 문자열이 공백 없이 주어진다. 문자열 길이는 (1 ≤ N ≤ 200)의 범위를 갖는다.

출력

각 테스트 케이스에 대해 주어진 전파가 문제에서 제시한 패턴이면 “YES”를 그렇지 않은 경우는 “NO”를 출력한다. 출력 문자열은 모두 대문자로 구성되어 있다.

내가 푼 풀이

- 처음엔 그리디기법으로 입력받는수 100, 01 에따라 다음에 올 수 있는 수들만 넣었더니 틀렸다.

- 그리디로 하니까 반례들이 너무많았고 정확히 답을 구할 수 없었다.

- 다른사람의 풀이를 참고해봤는데, 다양하게 풀이 방법이 있었다..

- 이 문제는 완전탐색 dfs를 이용했지만 조건을맞춰서 탐색하였다.

 

맨처음에 올수있는 문자는 100혹은 01이다.

100이 온다면 0, 1이 올 수 있다

01이 온다면 다시 100이 오거나, 01이 올 수 있다.

100다음에 0이 온다면 0또는 1이 올수있다 이는 위의 100이 왔을때의 조건과 같다.

100다음에 1이 온다면 1 또는 01 또는 100이 올 수 있다.

이렇게 각각 문자간의 관계가 형성되는데, 관계만 따지고 다음에 올 수 있는 모든 경우의 수를 탐색한다면 시간초과가 날 것이다.

조건을 더 추가해서, 앞의 문자를 미리 받고 해당문자와 이전문자의 관계있는문자가 동일하다면 탐색하는 방향으로 지었다.

 

예) 100101

1. 첫번째 올 수 있는 문자: [100, 01] , 앞의 문자 스캔: 100 , 10

100이 동일하므로 100 배치 -> 100

 

2. 100다음에 올 수 있는문자: [0,1] , 앞의 문자 스캔 1

1이 동일하므로 1 배치 -> 1001

 

3. 1다음에 올 수 있는문자: [1, 01, 100] , 앞의 문자 스캔 [0, 01, 01x]

01이 동일하므로 01 배치 -> 100101 결과 YES

 

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

 

import Foundation

// 문자 해당 범위를 출력하기위해 별도의 메서드 생성
// 문자를 받는중 주어진 문자열을 초과한다면 임의의 문자(여기선 2)를 더 붙여준다.
extension String {
    func getRangeString(start: Int, end: Int, max: Int) -> String {
        var result = ""
        for i in start...end {
            if i >= max {
                result += "2"
            } else {
                result += "\(self[self.index(startIndex, offsetBy: i)])"
            }
        }
        return result
    }
}

// 테스트케이스 수
let T = Int(readLine()!)!
var result = String()

// dfs
func dfs(str: String, targetStr: String, count: Int, compare: String) {
    var target = targetStr
    // 찾는도중에 이미 찾았거나, 찾는범위가 주어진 문자열을 넘어버린경우 종료
    if result == "YES" || count > target.count {
        return
    }
    // 배치한 문자가 주어진 문자열과 동일하다면 YES
    // 하지만 마지막문자가 100으로 끝난다면 주어진 규칙과 맞지않는다.
    if str == target && compare != "100" {
        result = "YES"
        return
    }
    
    // 이전 문자와의 관계에 따라서 다음에 올 문자를 찾는다.
    // 맨 처음시작할때나 이전문자가 01 일때 올수있는문자 100, 01
    if compare == "" {
        var s1 = ""
        var s2 = ""
        
        //뒤의문자를 탐색
        if count+2 <= target.count {
            s1 = target.getRangeString(start: count, end: count+2, max: target.count)
        }
        if count+1 < target.count {
            s2 = target.getRangeString(start: count, end: count+1, max: target.count)
        }
        
        //앞의문자와 그다음 올수있는문자가 동일하다면 추가
        if s1 == "100" {
            dfs(str: str + "100", targetStr: target, count: count+3, compare: "100")
        }
        if s2 == "01" {
            dfs(str: str + "01", targetStr: target, count: count+2, compare: "")
        }
    } else if compare == "100" {
        // 이전문자가 100일때, 올수있는문자 0,1
        var s = ""
        
        //뒤의문자 탐색
        if count+1 <= target.count {
            s = target.getRangeString(start: count, end: count, max: target.count)
        }
        
        //뒤의문자와 그다음 올수있는문자가 동일하다면 추가
        if s == "1" {
            dfs(str: str + "1", targetStr: target, count: count+1, compare: "1")
        }
        if s == "0" {
            dfs(str: str + "0", targetStr: target, count: count+1, compare: "100")
        }
    } else if compare == "1" {
        // 이전문자가 1일때, 올수있는문자 100, 01, 1
        var s1 = ""
        var s2 = ""
        var s3 = ""
        
        //뒤의 문자 탐색
        if count <= target.count {
            s1 = target.getRangeString(start: count, end: count, max: target.count)
        }
        if count + 1 <= target.count {
            s2 = target.getRangeString(start: count, end: count+1, max: target.count)
        }
        if count + 2 <= target.count {
            s3 = target.getRangeString(start: count, end: count+2, max: target.count)
        }
        
        //뒤의문자와 그다음 올수있는문자가 동일하다면 추가
        if s1 == "1" {
            dfs(str: str + "1", targetStr: target, count: count+1, compare: "1")
        }
        if s2 == "01" {
            dfs(str: str + "01", targetStr: target, count: count+2, compare: "")
        }
        if s3 == "100" {
            dfs(str: str + "100", targetStr: target, count: count+3, compare: "100")
        }
    }
    
}
for i in 0..<T {
    result = "NO"
    var target = readLine()!
    dfs(str: "", targetStr: target, count: 0, compare: "")
    print(result)
}

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

BOJ-2447 별 찍기-10 Swift  (0) 2024.04.15
BOJ-2580 스도쿠 Swift  (0) 2024.04.15
BOJ-5430 AC Swift  (1) 2024.04.15
BOJ-1253 좋다 Swift  (0) 2024.04.14
BOJ-5052 전화번호 목록 Swift  (0) 2024.04.14

문제

선영이는 주말에 할 일이 없어서 새로운 언어 AC를 만들었다. AC는 정수 배열에 연산을 하기 위해 만든 언어이다. 이 언어에는 두 가지 함수 R(뒤집기)과 D(버리기)가 있다.

함수 R은 배열에 있는 수의 순서를 뒤집는 함수이고, D는 첫 번째 수를 버리는 함수이다. 배열이 비어있는데 D를 사용한 경우에는 에러가 발생한다.

함수는 조합해서 한 번에 사용할 수 있다. 예를 들어, "AB"는 A를 수행한 다음에 바로 이어서 B를 수행하는 함수이다. 예를 들어, "RDD"는 배열을 뒤집은 다음 처음 두 수를 버리는 함수이다.

배열의 초기값과 수행할 함수가 주어졌을 때, 최종 결과를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 테스트 케이스의 개수 T가 주어진다. T는 최대 100이다.

각 테스트 케이스의 첫째 줄에는 수행할 함수 p가 주어진다. p의 길이는 1보다 크거나 같고, 100,000보다 작거나 같다.

다음 줄에는 배열에 들어있는 수의 개수 n이 주어진다. (0 ≤ n ≤ 100,000)

다음 줄에는 [x1,...,xn]과 같은 형태로 배열에 들어있는 정수가 주어진다. (1 ≤ xi ≤ 100)

전체 테스트 케이스에 주어지는 p의 길이의 합과 n의 합은 70만을 넘지 않는다.

출력

각 테스트 케이스에 대해서, 입력으로 주어진 정수 배열에 함수를 수행한 결과를 출력한다. 만약, 에러가 발생한 경우에는 error를 출력한다.

내가 푼 풀이

- 해당 기능 R, D를 구현하면 된다.

- 문제는 주어지는 함수가 최대 100,000에 테스트케이스는 최대 100개이므로 O(N^2)의 시간복잡도를 갖는 알고리즘은 아마 시간초과가 날것이다.

- 문자열과는 사이가 나쁜 Swift..

- R: 뒤집는 기능이지만 실제로 뒤집진 않는다(뒤집으면 최대 10만번 뒤집어야함)

- D: 첫째 수를 빼지만 실제로 removefirst()를 하지않는다(배열 특성상 첫번째수를 빼려면 모든 배열의 수를 읽어야함 N번)

 

다양하게 구현하는방법이 있겠지만, 이번문제는 배열의 첫번째인덱스 start,와 마지막인덱스 end를 이용해서 풀었다.

만약 뒤집혀있는배열이라면 end의 문자를 빼야하므로 end-1

뒤집혀있지않는 배열이라면 start+1

모두 빼내면 결국 start...end 범위의 배열만 남는다.

여기서 뒤집는함수 R이 짝수번 발생했다면 뒤집기의뒤집기는 결국 그대로라 그대로 출력하지만

홀수번 발생했다면 end...start 로 출력하면된다.

 

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

import Foundation

// 테스트케이스 수
let T = Int(readLine()!)!

for i in 0..<T {
    // 입력받기
    let p = readLine()!
    let n = Int(readLine()!)!
    var arr = [Int]()
    let strNum = readLine()!
    var str = ""
    
    // 입력받은 배열의문자열을 숫자배열로 변환
    for j in strNum {
        if let num = Int(String(j)) {
            str += String(j)
        } else {
            if str != "" {
                arr.append(Int(str)!)
                str = ""
            }
        }
    }
    
    // start, end
    var start = 0
    var end = arr.count - 1
    // reversed: 뒤집혀있는가?
    // check는 문제에 정의된 조건으로 빈배열일때 첫번째수를 빼려고한다면 error, 이를 판단하기위해
    var reversed = false
    var check = true
    
    // 주어진 함수의문자열 실행
    for k in p {
        // 뒤집혀있는지 확인
        if k == "R" {
            reversed = !reversed
        } else if k == "D" {
            // 배열이 비어있거나, 배열수 이상으로 빼내서 start인덱스가 end를 넘어버렸다면
            if arr.isEmpty || start > end {
                check = false
            } else {
                // 뒤집힘에 따라 인덱스 조절
                if reversed {
                    end -= 1
                } else {
                    start += 1
                }
            }
        }
    }
    // 결과 출력 문자열
    var result = ""
    
    // 빈배열에서 D함수 실행했다면 error
    if check {
        if arr.isEmpty || start > end{
            print("[]")
        } else {
            // 뒤집혀있다면 범위 거꾸로 출력
            if reversed {
                for j in (start...end).reversed() {
                    if j == start {
                        result += "\(arr[j])"
                    } else {
                        result += "\(arr[j]),"
                    }
                }
            } else {
                // 그대로라면 그대로 출력
                for j in start...end {
                    if j == end {
                        result += "\(arr[j])"
                    } else {
                        result += "\(arr[j]),"
                    }
                }
            }
            print("[\(result)]")
        }
    } else {
        print("error")
    }
}

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

BOJ-2580 스도쿠 Swift  (0) 2024.04.15
BOJ-1013 Contact Swift  (0) 2024.04.15
BOJ-1253 좋다 Swift  (0) 2024.04.14
BOJ-5052 전화번호 목록 Swift  (0) 2024.04.14
BOJ-2470 두 용액 Swift  (0) 2024.04.14

+ Recent posts