괄호 변환

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

용어의 정의

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

'(' 와 ')' 로만 이루어진 문자열 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

무인도 여행

메리는 여름을 맞아 무인도로 여행을 가기 위해 지도를 보고 있습니다. 지도에는 바다와 무인도들에 대한 정보가 표시돼 있습니다. 지도는 1 x 1크기의 사각형들로 이루어진 직사각형 격자 형태이며, 격자의 각 칸에는 'X' 또는 1에서 9 사이의 자연수가 적혀있습니다. 지도의 'X'는 바다를 나타내며, 숫자는 무인도를 나타냅니다. 이때, 상, 하, 좌, 우로 연결되는 땅들은 하나의 무인도를 이룹니다. 지도의 각 칸에 적힌 숫자는 식량을 나타내는데, 상, 하, 좌, 우로 연결되는 칸에 적힌 숫자를 모두 합한 값은 해당 무인도에서 최대 며칠동안 머물 수 있는지를 나타냅니다. 어떤 섬으로 놀러 갈지 못 정한 메리는 우선 각 섬에서 최대 며칠씩 머물 수 있는지 알아본 후 놀러갈 섬을 결정하려 합니다.

지도를 나타내는 문자열 배열 maps가 매개변수로 주어질 때, 각 섬에서 최대 며칠씩 머무를 수 있는지 배열에 오름차순으로 담아 return 하는 solution 함수를 완성해주세요. 만약 지낼 수 있는 무인도가 없다면 -1을 배열에 담아 return 해주세요.

 
 
제한사항

 

  • 3 ≤ maps의 길이 ≤ 100
  • 3 ≤ maps[i]의 길이 ≤ 100
  • maps[i]는 'X' 또는 1 과 9 사이의 자연수로 이루어진 문자열입니다.
  • 지도는 직사각형 형태입니다.

 

입출력 예
maps result
["X591X","X1X5X","X231X", "1XXX1"] [1, 1, 27]
["XXX","XXX","XXX"] [-1]

입출력 예 설명

입출력 예 #1

위 문자열은 다음과 같은 지도를 나타냅니다.

 

내가 푼 풀이

접근방법: 완전탐색 DFS(BFS도 상관없을듯 하다)

 

1. 주어진 맵 배열을 2차원 배열로 변환

2. 배열의 모든 원소를 순회하며 인접한(상 하 좌 우)곳이 X가 아니고 이미 방문한곳이 아니면 탐색

3. 탐색한 모든 위치의원소값 합계

4.합계 배열 출력

 

import Foundation

func solution(_ maps:[String]) -> [Int] {
    var result = [Int]()
    var visited = Array(repeating: Array(repeating:false, count: maps[0].count), count: maps.count)
    // 배열로 변환
    var map = [[String]]()
    for i in maps {
        let arr = i.map{String($0)}
        map.append(arr)
    }
    
    // 모든 배열의 원소 탐색(완전탐색 DFS)
    for i in 0..<map.count {
        for j in 0..<map[i].count {
            // 바다가 아니거나 이미 방문한곳이 아니면 탐색시작
            if map[i][j] == "X" || visited[i][j] { continue }
            var sum = 0
            var needVisit = [(x: i,y: j)]
            
            // 인접한곳을 needVisit에 넣고 모든 인접한위치 탐색
            while !needVisit.isEmpty {
                let cur = needVisit.removeLast()
                if visited[cur.x][cur.y] { continue }
                sum += Int(map[cur.x][cur.y])!
                visited[cur.x][cur.y] = true
                var dx = [1, 0, -1, 0]
                var dy = [0, 1, 0, -1]
                for i in 0..<4 {
                    if (cur.y + dy[i] < map[0].count && cur.x + dx[i] < map.count) && (cur.y + dy[i] >= 0 && cur.x + dx[i] >= 0) {
                        if map[cur.x + dx[i]][cur.y + dy[i]] != "X" && !visited[cur.x + dx[i]][cur.y + dy[i]] {
                            needVisit.append((x: cur.x + dx[i], y: cur.y + dy[i]))
                        }
                    }
                }
            }
            result.append(sum)
        }
    }
    result.sort{$0 < $1}
    if result.isEmpty {
        result = [-1]
    }
    return result
}

[3차] 방금그곡

방금그곡

라디오를 자주 듣는 네오는 라디오에서 방금 나왔던 음악이 무슨 음악인지 궁금해질 때가 많다. 그럴 때 네오는 다음 포털의 '방금그곡' 서비스를 이용하곤 한다. 방금그곡에서는 TV, 라디오 등에서 나온 음악에 관해 제목 등의 정보를 제공하는 서비스이다.

네오는 자신이 기억한 멜로디를 가지고 방금그곡을 이용해 음악을 찾는다. 그런데 라디오 방송에서는 한 음악을 반복해서 재생할 때도 있어서 네오가 기억하고 있는 멜로디는 음악 끝부분과 처음 부분이 이어서 재생된 멜로디일 수도 있다. 반대로, 한 음악을 중간에 끊을 경우 원본 음악에는 네오가 기억한 멜로디가 들어있다 해도 그 곡이 네오가 들은 곡이 아닐 수도 있다. 그렇기 때문에 네오는 기억한 멜로디를 재생 시간과 제공된 악보를 직접 보면서 비교하려고 한다. 다음과 같은 가정을 할 때 네오가 찾으려는 음악의 제목을 구하여라.

  • 방금그곡 서비스에서는 음악 제목, 재생이 시작되고 끝난 시각, 악보를 제공한다.
  • 네오가 기억한 멜로디와 악보에 사용되는 음은 C, C#, D, D#, E, F, F#, G, G#, A, A#, B 12개이다.
  • 각 음은 1분에 1개씩 재생된다. 음악은 반드시 처음부터 재생되며 음악 길이보다 재생된 시간이 길 때는 음악이 끊김 없이 처음부터 반복해서 재생된다. 음악 길이보다 재생된 시간이 짧을 때는 처음부터 재생 시간만큼만 재생된다.
  • 음악이 00:00를 넘겨서까지 재생되는 일은 없다.
  • 조건이 일치하는 음악이 여러 개일 때에는 라디오에서 재생된 시간이 제일 긴 음악 제목을 반환한다. 재생된 시간도 같을 경우 먼저 입력된 음악 제목을 반환한다.
  • 조건이 일치하는 음악이 없을 때에는 “(None)”을 반환한다.

입력 형식

입력으로 네오가 기억한 멜로디를 담은 문자열 m과 방송된 곡의 정보를 담고 있는 배열 musicinfos가 주어진다.

  • m은 음 1개 이상 1439개 이하로 구성되어 있다.
  • musicinfos는 100개 이하의 곡 정보를 담고 있는 배열로, 각각의 곡 정보는 음악이 시작한 시각, 끝난 시각, 음악 제목, 악보 정보가 ','로 구분된 문자열이다.
  • 음악의 시작 시각과 끝난 시각은 24시간 HH:MM 형식이다.
  • 음악 제목은 ',' 이외의 출력 가능한 문자로 표현된 길이 1 이상 64 이하의 문자열이다.
  • 악보 정보는 음 1개 이상 1439개 이하로 구성되어 있다.

출력 형식

조건과 일치하는 음악 제목을 출력한다.

입출력 예시

m musicinfos answer
"ABCDEFG" ["12:00,12:14,HELLO,CDEFGAB", "13:00,13:05,WORLD,ABCDEF"] "HELLO"
"CC#BCC#BCC#BCC#B" ["03:00,03:30,FOO,CC#B", "04:00,04:08,BAR,CC#BCC#BCC#B"] "FOO"
"ABC" ["12:00,12:14,HELLO,C#DEFGAB", "13:00,13:05,WORLD,ABCDEF"] "WORLD"

 

내가 푼 풀이

- 이문제의 구현방법은 어렵지않지만, 예외처리를 굉장히 엄격하게 해야한다.

 

접근방법:

1. 주어진 배열을 받아서 해당 노래의 음을 재생시간에 맞춰서 늘리거나 줄여줌(문제조건3번)

2. 조절된 음을 m의크기만큼 부분문자열로 만듬

3. 예외처리에 따라서 m과 동일한 문자열이 있는지 확인

4. 같은음이 다른 재생시간에서도 발견된다면, 음악길이가 긴것먼저, 음악길이도 같다면 먼저 재생된것으로 출력한다.

 

예외처리가 너무 빡셌다.. 특히 Swift는 문자열에대해 아주 가혹해서 더 어려웠던것같다.

확실히 카카오 문제는 구현도 어렵게 예외처리도 어렵게,,

 

 

 

*** 여러가지 예외들)

1. 음뒤에 #이 붙어있는것은 다른음으로 취급한다. #문자열 처리가 잘 되어있어야한다.

2. 노래의길이는 재생시간만큼 반복재생하거나 줄여야한다.

3. 부분문자열이 중간에 있을때, m과 동일한지 비교할 수 있어야한다.

 

 

코드로 구현하면 다음과 같다.(매우김)

import Foundation

func solution(_ m:String, _ musicinfos:[String]) -> String {
    var answer = ["0", "(None)"]
    var index = 0
    var musics = [[String]]()
    var mCount = m.count
    
    // 주어진 음악정보를 음악길이만큼 늘리거나 줄여서 따로 저장한다.
    while index < musicinfos.count {

        var infos = musicinfos[index].split(separator: ",")
        // 재생시간 계산
        let start = infos[0].split(separator: ":").joined(separator: "")
        let end = infos[1].split(separator: ":").joined(separator: "")
        let startNum = Int(start.prefix(2))! * 60 + Int(start.suffix(2))!
        let endNum = Int(end.prefix(2))! * 60 + Int(end.suffix(2))!
        let total = (endNum - startNum)
        
        // 음악이름, 음악의 음정
        let name = infos[2]
        var music = infos[3].map{String($0)}
        var converted = [String]()
        var musicIndex = 0
        
        // 음정 문자열을 배열로 변환
        while musicIndex < music.count {
            var str = music[musicIndex]
            if str == "#" {
                musicIndex += 1
                continue 
            }
            if musicIndex == music.count-1 {
                converted.append(str)
                musicIndex += 1 
                continue
            }
            if music[musicIndex+1] == "#" {
                str = str + "\(music[musicIndex+1])"
                converted.append(str)
            } else {
                converted.append(str)
            }
            musicIndex += 1
        }
        
        // 변환한 배열을 통해 재생시간만큼 줄이거나 늘린다.
        let a = converted.joined(separator: "")
        var musicString = ""
        var sin = 0
        for i in 0..<total {
            musicString += converted[sin]
            sin += 1
            if sin == converted.count {
                sin = 0
            }
        }
        // 음악길이로 조절한 음악을 따로저장
        musics.append([String(musicString.count),String(musicString),String(name)])
        index += 1
    }
    
    // 같은 음악인지 판단
    for i in musics {
        let length = i[0]
        var str = i[1]
        let name = i[2]
        
        var sine = 0
        var strs = [String]()
        
        // m의 길이만큼 부분문자열 배열 생성
        while true {
            if sine + mCount > Int(length)! { break }
            sine += 1
            strs.append(String(str.prefix(mCount)))
            str.remove(at: str.startIndex)
        }
        """
        같은 문자열인지 확인
        1. i == strs.count-1
        뒷자리가 #이 아닌문자열이 들어온다면 뒤에 #이 이어지는지 확인해야한다. 
        하지만 뒤에 오는 문자가 없다면 같은문자로 판단
        2. (i+1 < strs.count && strs[i+1].suffix(1) != "#") 
        뒷자리가 더 올수있는상황에서 그다음 부분문자열의 마지막문자가 #이 아니라면, 그 뒤로 #이 올 수 없다는 뜻
        그래서 같은문자로 판단 (#이 안오는 일반 음정이다.)
        (부분 문자열 순서는 ex.ABCD의 2길이 문자열 이라면 AB, BC, CD 이므로 BC 뒤에 오는문자는 CD의 마지막문자 D)
        """
        for i in 0..<strs.count {
            if strs[i] == m {
                if i == strs.count-1 || (i+1 < strs.count && strs[i+1].suffix(1) != "#"){
                    if Int(answer[0])! < Int(length)! {
                        answer = [String(length), String(name)]
                    }
                }
            }
        }
    }
    return answer[1]
}

숫자 카드 나누기

철수와 영희는 선생님으로부터 숫자가 하나씩 적힌 카드들을 절반씩 나눠서 가진 후, 다음 두 조건 중 하나를 만족하는 가장 큰 양의 정수 a의 값을 구하려고 합니다.

  1. 철수가 가진 카드들에 적힌 모든 숫자를 나눌 수 있고 영희가 가진 카드들에 적힌 모든 숫자들 중 하나도 나눌 수 없는 양의 정수 a
  2. 영희가 가진 카드들에 적힌 모든 숫자를 나눌 수 있고, 철수가 가진 카드들에 적힌 모든 숫자들 중 하나도 나눌 수 없는 양의 정수 a

예를 들어, 카드들에 10, 5, 20, 17이 적혀 있는 경우에 대해 생각해 봅시다. 만약, 철수가 [10, 17]이 적힌 카드를 갖고, 영희가 [5, 20]이 적힌 카드를 갖는다면 두 조건 중 하나를 만족하는 양의 정수 a는 존재하지 않습니다. 하지만, 철수가 [10, 20]이 적힌 카드를 갖고, 영희가 [5, 17]이 적힌 카드를 갖는다면, 철수가 가진 카드들의 숫자는 모두 10으로 나눌 수 있고, 영희가 가진 카드들의 숫자는 모두 10으로 나눌 수 없습니다. 따라서 철수와 영희는 각각 [10, 20]이 적힌 카드, [5, 17]이 적힌 카드로 나눠 가졌다면 조건에 해당하는 양의 정수 a는 10이 됩니다.

철수가 가진 카드에 적힌 숫자들을 나타내는 정수 배열 arrayA와 영희가 가진 카드에 적힌 숫자들을 나타내는 정수 배열 arrayB가 주어졌을 때, 주어진 조건을 만족하는 가장 큰 양의 정수 a를 return하도록 solution 함수를 완성해 주세요. 만약, 조건을 만족하는 a가 없다면, 0을 return 해 주세요.


제한사항
  • 1 ≤ arrayA의 길이 = arrayB의 길이 ≤ 500,000
  • 1 ≤ arrayA의 원소, arrayB의 원소 ≤ 100,000,000
  • arrayA와 arrayB에는 중복된 원소가 있을 수 있습니다.

 

입출력 예
arrayA arrayB result
[10, 17] [5, 20] 0
[10, 20] [5, 17] 10
[14, 35, 119] [18, 30, 102] 7

 

 

내가 푼 풀이

첫번째 접근법: 당연히 완전탐색

1. 각 배열마다 최대공약수를 구한다.

2. 구한 최대공약수를 상대 배열에 전체적으로 나누어본다.

3. 나누어지지 않는 숫자중 최댓값 출력

4. 없다면 0

 

-> 시간초과

주어진 원소의 최댓값이 1억이라서 이방법으로 한다면 최대공약수를 구하는 반복문만 최대 1억번 돌리게된다..

 

두번째 접근법: 유클리드 호제법

시간을 단축하기위해 단순반복방법이 아닌 유클리드호제법으로 최대공약수를 구하는 시간을 줄인다.

1. 배열마다 유클리드호제법으로 최대공약수를 구한다. 반복은 배열의 갯수만큼(최대 50만번)

2. 구한 최대공약수로 상대배열을 나눠본다.

3. 나눠지면 0으로 초기화, 안나누어지면 그대로

4. 두개의 최대공약수중 최댓값 출력(둘다 나누어졌다면 0이되고, 한쪽만 나누어진다면 그 값이 최대가됨)

 

-> 성공

 

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

import Foundation

// 유클리드를 이용한 gcd 재귀호출
func gcd(_ num1: Int,_ num2: Int) -> Int {
    if num2 == 0 {
        return num1
    } else {
        return gcd(num2, num1 % num2)
    }
}
func solution(_ arrayA:[Int], _ arrayB:[Int]) -> Int {
    var fA = arrayA[0]
    var fB = arrayB[0]
    
    //A의 최대공약수
    for i in arrayA {
        fA = gcd(fA,i)
    }
    //B의 최대공약수
    for i in arrayB {
        fB = gcd(fB,i)
    }
    // A의 최대공약수로 B를 나눠봄, 나눠지면 0
    for i in arrayB {
        if i % fA == 0 {
            fA = 0
            break
        }
    }
    // B의 최대공약수로 A를 나눠봄, 나눠지면 0
    for i in arrayA {
        if i % fB == 0 {
            fB = 0
            break
        }
    }

    return max(fA, fB)
}

문제 설명

호텔을 운영 중인 코니는 최소한의 객실만을 사용하여 예약 손님들을 받으려고 합니다. 한 번 사용한 객실은 퇴실 시간을 기준으로 10분간 청소를 하고 다음 손님들이 사용할 수 있습니다.
예약 시각이 문자열 형태로 담긴 2차원 배열 book_time이 매개변수로 주어질 때, 코니에게 필요한 최소 객실의 수를 return 하는 solution 함수를 완성해주세요.

제한사항

  • 1 ≤ book_time의 길이 ≤ 1,000
    • book_time[i]는 ["HH:MM", "HH:MM"]의 형태로 이루어진 배열입니다
    • [대실 시작 시각, 대실 종료 시각] 형태입니다. 
    • 시각은 HH:MM 형태로 24시간 표기법을 따르며, "00:00" 부터 "23:59" 까지로 주어집니다.
    • 약 시각이 자정을 넘어가는 경우는 없습니다.
    • 작 시각은 항상 종료 시각보다 빠릅니다. 
 
입출력 예

 

book_time result
[["15:00", "17:00"], ["16:40", "18:20"], ["14:20", "15:20"], ["14:10", "19:20"], ["18:20", "21:20"]] 3
[["09:10", "10:10"], ["10:20", "12:20"]] 1
[["10:20", "12:30"], ["10:20", "12:30"], ["10:20", "12:30"]] 3

 

내가 푼 풀이

어디선가 많이 본 익숙한 문제다.

 

1. 시작시간을 기준으로 오름차순 정렬한다.

2. 시작시간이 가장 빠른 예약부터 방에 넣는다.

3. 시작시간이 동일한경우, 종료시간이 빠른 예약부터 넣는다.

4. 방 중에 종료시간+10분과 다음예약이 같거나 크다면 해당방에 다음예약을 넣는다.

 

주의: 종료시간뒤 10분의 청소시간이 주어지므로 종료시간 + 10분 해야하지만, 50...59분 종료시간엔 시 단위가 바뀐다.

 

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

import Foundation

func solution(_ book_time:[[String]]) -> Int {
    var reservation = [[Int]]()
    var rooms = [[Int]]()
    var answer = 0
    var index = 0
    // 시작시간과 종료시간을 숫자로 변환
    for i in 0..<book_time.count {
        var res = book_time[i]
        var start = res[0].split(separator: ":").joined(separator: "")
        var end = res[1].split(separator: ":").joined(separator: "")
        reservation.append([Int(start)!, Int(end)!])
    }
    
    // 예약배열을 시작시간 기준 오름차순으로 정렬
    // 시작시간이 같다면 종료시간이 빠른 순으로 정렬
    var sorted = reservation.sorted{ 
        if $0[0] == $1[0] {
        return $0[1] < $1[1]
    } else {
        return $0[0] < $1[0]
    }}
    // index: 정렬된 예약배열 인덱스
    // answer: 정답출력 변수
    while index < sorted.count {
        // 이용중인 방이 없다면 바로 넣는다.
        if rooms.isEmpty {
            rooms.append(sorted[index])
            index += 1
            answer += 1
            continue
        }
        // 다음예약자의 시작시간과 종료시간
        var resStart = sorted[index][0]
        var resEnd = sorted[index][1]
        var find = false
        // 이용중인 방이 있다면 종료시간들을 검사한다.
        for i in 0..<rooms.count {
            var end = rooms[i][1]
            end += 10
            var str = String(end)
            // 종료후 청소시간이 60~69 사이라면 시단위가 올라가야한다.
            if Int(str.suffix(2))! >= 60 {
                end += 100
                end -= 60
            }
            // 종료시간과 다음예약의 시작시간이 동일하거나 작다면 이 방으로 다음예약을 넣는다.
            if end <= resStart {
                rooms[i] = [resStart, resEnd]
                find = true
                break
            } 
        }
        // 교체가능한 방이 없다면 추가
        if !find {
            rooms.append([resStart,resEnd])
            answer += 1
        }
        index += 1
    }
    return answer
}

문제 설명

어느 공원 놀이터에는 시소가 하나 설치되어 있습니다. 이 시소는 중심으로부터 2(m), 3(m), 4(m) 거리의 지점에 좌석이 하나씩 있습니다.
이 시소를 두 명이 마주 보고 탄다고 할 때, 시소가 평형인 상태에서 각각에 의해 시소에 걸리는 토크의 크기가 서로 상쇄되어 완전한 균형을 이룰 수 있다면 그 두 사람을 시소 짝꿍이라고 합니다. 즉, 탑승한 사람의 무게와 시소 축과 좌석 간의 거리의 곱이 양쪽 다 같다면 시소 짝꿍이라고 할 수 있습니다.
사람들의 몸무게 목록 weights이 주어질 때, 시소 짝꿍이 몇 쌍 존재하는지 구하여 return 하도록 solution 함수를 완성해주세요.

제한 사항
  • 2 ≤ weights의 길이 ≤ 100,000
  • 100 ≤ weights[i] ≤ 1,000
    • 몸무게 단위는 N(뉴턴)으로 주어집니다.
    • 몸무게는 모두 정수입니다.

 

입출력 예
weights result
[100,180,360,100,270] 4

 

내가 푼 풀이

 

첫번째 접근방법(완전탐색) (시간초과)

- 두개의 포인터를 이용해 각 포인터의 원소가 시소짝궁인경우 세주는 방식을 사용

- 모두 같은 원소인 100인경우 각 포인터가 n씩 움직여야하므로 O(N^2)이 걸려서 시간초과가 걸림..

 

 

두번째 접근방법(자료구조를 이용)

- 한개의 원소로 다른 원소들을 하나씩 찾는방식이였다면 이제는 한개원소로 모든 원소를 비교해보는 방식을 사용

 

원소 a가 2m에 있다면 원소b가 a의 시소짝궁이 되려면

1. a * 2 = b * 2 ------> b = a * 2 / 2  -> b = a

2. a * 2 = b * 3 ------> b = a * 2 / 3

3. a * 2 = b * 4  -----> b = a * 2 / 4

가 성립해야한다.

 

원소 a가 3m에 있다면 원소 b가 a의 시소짝궁이 되려면

1. a * 3 = b * 3 -------> a = b

2. a * 3 = b * 4 -------> b = a * 3 / 4

가 성립해야한다.

 

원소 a가 4m에 있다면 

원소 b는 원소 a의 두배면서 2m에 있는경우

 

위의 경우의수를 종합하자면

원소 a의 시소짝궁은 아래와 같다.

1. 같은원소

2. a의 두배 (a: 4m, b: 2m)

3. a의 2/3배 (a: 2m, b: 3m)

4. a의 3/4배 (a: 3m, b: 4m)

 

시소의 갯수를 구하기위해 딕셔너리를 이용해서 각각 무게의 수를 저장한다.

저장한 딕셔너리를 이용해 위의 경우의수와 맞는 모든 수를 찾고 출력한다.

 

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

 

import Foundation

func solution(_ weights:[Int]) -> Int64 {
    var answer = 0
    var dict = [Double: Int]()
    // 딕셔너리에 저장
    // Swift는 나눗셈을 하면 소숫점을 모두 버리므로 정확한계산을 위해 무게는 Double형식으로 입력
    for i in weights {
        if dict[Double(i)] == nil {
            dict[Double(i)] = 1
        } else {
            dict[Double(i)]! += 1
        }
    }
    
    // 시소짝궁 찾기
    // a: 4, b:3 이라면 a와 b를 조합할 수 있는 경우의수: a x b = 12
    for i in dict {
        // 같은 무게
        if i.value > 1 {
            answer += (i.value * (i.value - 1)) / 2
        }
        // 2배의 무게
        if dict[i.key * 2] != nil {
            answer += (i.value * dict[i.key * 2]!)
        }
        // 2/3배의 무게
        if dict[i.key * 2 / 3] != nil {
            answer += (i.value * dict[i.key * 2 / 3]!)
        }
        // 3/4배의 무게
        if dict[i.key * 3 / 4] != nil {
            answer += (i.value * dict[i.key * 3 / 4]!)
        }
        
    }
    return Int64(answer)
}

 

+ Recent posts