반응형

https://school.programmers.co.kr/learn/courses/30/lessons/72412

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

문제 설명

[본 문제는 정확성과 효율성 테스트 각각 점수가 있는 문제입니다.]

카카오는 하반기 경력 개발자 공개채용을 진행 중에 있으며 현재 지원서 접수와 코딩테스트가 종료되었습니다. 이번 채용에서 지원자는 지원서 작성 시 아래와 같이 4가지 항목을 반드시 선택하도록 하였습니다.

  • 코딩테스트 참여 개발언어 항목에 cpp, java, python 중 하나를 선택해야 합니다.
  • 지원 직군 항목에 backend와 frontend 중 하나를 선택해야 합니다.
  • 지원 경력구분 항목에 junior와 senior 중 하나를 선택해야 합니다.
  • 선호하는 소울푸드로 chicken과 pizza 중 하나를 선택해야 합니다.

인재영입팀에 근무하고 있는 니니즈는 코딩테스트 결과를 분석하여 채용에 참여한 개발팀들에 제공하기 위해 지원자들의 지원 조건을 선택하면 해당 조건에 맞는 지원자가 몇 명인 지 쉽게 알 수 있는 도구를 만들고 있습니다.예를 들어, 개발팀에서 궁금해하는 문의사항은 다음과 같은 형태가 될 수 있습니다.코딩테스트에 java로 참여했으며, backend 직군을 선택했고, junior 경력이면서, 소울푸드로 pizza를 선택한 사람 중 코딩테스트 점수를 50점 이상 받은 지원자는 몇 명인가?

물론 이 외에도 각 개발팀의 상황에 따라 아래와 같이 다양한 형태의 문의가 있을 수 있습니다.

  • 코딩테스트에 python으로 참여했으며, frontend 직군을 선택했고, senior 경력이면서, 소울푸드로 chicken을 선택한 사람 중 코딩테스트 점수를 100점 이상 받은 사람은 모두 몇 명인가?
  • 코딩테스트에 cpp로 참여했으며, senior 경력이면서, 소울푸드로 pizza를 선택한 사람 중 코딩테스트 점수를 100점 이상 받은 사람은 모두 몇 명인가?
  • backend 직군을 선택했고, senior 경력이면서 코딩테스트 점수를 200점 이상 받은 사람은 모두 몇 명인가?
  • 소울푸드로 chicken을 선택한 사람 중 코딩테스트 점수를 250점 이상 받은 사람은 모두 몇 명인가?
  • 코딩테스트 점수를 150점 이상 받은 사람은 모두 몇 명인가?

즉, 개발팀에서 궁금해하는 내용은 다음과 같은 형태를 갖습니다.

  • [조건]을 만족하는 사람 중 코딩테스트 점수를 X점 이상 받은 사람은 모두 몇 명인가?

[문제]

지원자가 지원서에 입력한 4가지의 정보와 획득한 코딩테스트 점수를 하나의 문자열로 구성한 값의 배열 info, 개발팀이 궁금해하는 문의조건이 문자열 형태로 담긴 배열 query가 매개변수로 주어질 때,각 문의조건에 해당하는 사람들의 숫자를 순서대로 배열에 담아 return 하도록 solution 함수를 완성해 주세요.

[제한사항]

  • info 배열의 크기는 1 이상 50,000 이하입니다.
  • info 배열 각 원소의 값은 지원자가 지원서에 입력한 4가지 값과 코딩테스트 점수를 합친 "개발언어 직군 경력 소울푸드 점수" 형식입니다.
    • 개발언어는 cpp, java, python 중 하나입니다.
    • 직군은 backend, frontend 중 하나입니다.
    • 경력은 junior, senior 중 하나입니다.
    • 소울푸드는 chicken, pizza 중 하나입니다.
    • 점수는 코딩테스트 점수를 의미하며, 1 이상 100,000 이하인 자연수입니다.
    • 각 단어는 공백문자(스페이스 바) 하나로 구분되어 있습니다.
  • query 배열의 크기는 1 이상 100,000 이하입니다.
  • query의 각 문자열은 "[조건] X" 형식입니다.
    • [조건]은 "개발언어 and 직군 and 경력 and 소울푸드" 형식의 문자열입니다.
    • 언어는 cpp, java, python, - 중 하나입니다.
    • 직군은 backend, frontend, - 중 하나입니다.
    • 경력은 junior, senior, - 중 하나입니다.
    • 소울푸드는 chicken, pizza, - 중 하나입니다.
    • '-' 표시는 해당 조건을 고려하지 않겠다는 의미입니다.
    • X는 코딩테스트 점수를 의미하며 조건을 만족하는 사람 중 X점 이상 받은 사람은 모두 몇 명인 지를 의미합니다.
    • 각 단어는 공백문자(스페이스 바) 하나로 구분되어 있습니다.
    • 예를 들면, "cpp and - and senior and pizza 500"은 "cpp로 코딩테스트를 봤으며, 경력은 senior 이면서 소울푸드로 pizza를 선택한 지원자 중 코딩테스트 점수를 500점 이상 받은 사람은 모두 몇 명인가?"를 의미합니다.

코드

import collections
from itertools import combinations
def solution(info, query):
    answer = []
    info = list(map(lambda x: x.split(), info))
    query = list(map(lambda x: x.split(), query)) #1 3 5sms and
    
    d = collections.defaultdict(list)
    
    for inf in info:
        key = inf[:-1]
        val = int(inf[-1])
        for i in range(5):
            for c in combinations(key,i):
                d["".join(c)].append(val)
    for k in d.keys():
        d[k].sort()
    
        
    for q in query:
        tmp = ''
        if q[0] != '-':
            tmp += q[0]
        if q[2] != '-':
            tmp += q[2]
        if q[4] != '-':
            tmp += q[4]
        if q[6] != '-':
            tmp += q[6]
        val = int(q[7])
        if len(d[tmp]) == 0 :
            answer.append(0)
            continue
        if len(d[tmp]) == 1 :
            if d[tmp][0] >= val:
                answer.append(1)
            else:
                answer.append(0)
            continue
        left = 0; right = len(d[tmp]);
        while left < right:
            mid = (left + right) // 2
            if d[tmp][mid] >= val:
                right = mid 
            else:
                left = mid + 1
        answer.append(len(d[tmp]) - left)
 
    return answer

print(solution(["java backend junior pizza 150","python frontend senior chicken 210","python frontend senior chicken 150","cpp backend senior pizza 260","java backend junior chicken 80","python backend senior chicken 50"],
         ["java and backend and junior and pizza 100","python and frontend and senior and chicken 200","cpp and - and senior and pizza 250","- and backend and senior and - 150","- and - and - and chicken 100","- and - and - and - 150"]))

후기

정말정말 어려웠는데 난이도가 파랑이라는게 믿기지 않는다... 푸는 방법은 순열 + 이분탐색 + 해쉬를 사용해야만 효용성을 통과 할 수 있다. 처음에 단순 비교를 했는데 정답은 다 맞았지만 효용성을 하나도 통과하지 못했다.

알고리즘

  • 순열과 조합
  • 해쉬
  • 이분탐색
반응형
반응형

안녕하세요. 이전 포스팅에서는 코딩테스트 기초에 대해서 알아봤었습니다. 이번 포스팅에서는 코딩테스트 꿀팁과 더 추가로 공부하면 좋은 심화 알고리즘들에 대해 소개하겠습니다. 기초편을 안보신 분들은 아래 링크에서 확인해보시길 바랍니다.

 

https://nakco.tistory.com/entry/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A1%9C-%EC%B7%A8%EC%97%85%ED%95%98%EA%B8%B0-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B8%B0%EC%B4%88%ED%8E%B8

 

개발자로 취업하기 - 코딩테스트 (기초편)

안녕하세요. 이번 포스팅에서는 코딩테스트에 대해 다뤄보겠습니다. 개발자 스펙과 자기소개서 작성법을 보고 싶으시다면 이전 포스팅들을 참조해 주시길 바랍니다. https://nakco.tistory.com/ 낙서

nakco.tistory.com

 

프로그래머스 고득점 Kit을 전부 완료하였다면 이젠 실전 연습을 해야할 때입니다. 기업의 코딩테스트는 보틍 2~4시간 정도 주고, 4~7문제 정도를 풀어야합니다. 이때 중요한 점은, 부분점수는 아무 의미가 없다는 것입니다. 한 문제당 100점 or 0점이라고 생각하셔도 무방합니다. 보통 50%이상 맞추면 합격권이기 때문에, 선택과 집중을 잘하여 풀수 있는 문제를 완벽하게 푸는 것이 핵심입니다.

 

코딩테스트 전략

코딩테스트는 제한 시간이 있고, 빠르게 최대한 많은 문제를 풀어야 합니다. 하지만 그렇다고 조바심 내서 이문제 저문제 풀다보면 결국 풀 수 있는 문제도 못풀고 시간만 낭비하게 될 것입니다. 그렇다면 어떻게 전략을 짜야 할까요? 네, 바로 풀 수 있을 문제에 선택과 집중을 해야합니다. 어려운 문제에 더 높은 점수를 주는 기업도 있지만, 보통은 쉽든 어렵든 푼 문제 수로 갈리기 때문에, 내가 풀 수 있는 문제가 무엇인지 파악하는 것이 중요합니다.

 

1. 문제 파악하기

 문제를 읽으면서 어떤 알고리즘을 써야할지 먼저 알아내야합니다. 저희는 고득점 Kit를 푼 상태이기 때문에 해당 유형의 문제가 나오면 무조건 파악할 수 있습니다. 여러 코딩테스트를 봐보았을때 문제가 다 거기서 거기고, 그냥 내용만 살짝 바뀌는 정도였습니다. 따라서 내가 아는 유형인지 먼저 파악하고, 파악이 안될 시 우선 과감히 버리는 것을 추천드립니다. 그렇게 먼저 내가 아는 유형부터 문제를 풀도록 합니다. 

 

2. 문제 손으로 먼저 풀기

 알고리즘 문제를 풀 때 절대 문제를 읽으면서 바로 코딩해서는 안됩니다. 우선 문제를 모두 읽으면서 이해하고, 손으로 직접 풀어본 후, 그것을 코딩으로 옮기는 것이 순서입니다. 무작정 시작한다면 중간에 꼬여서 코드를 수정하고, 또 다시 문제를 읽고 예외를 발견하여 또 수정하고, 이러다 보면 코드도 더러워지고 나중엔 마구마구 꼬여버릴 수 있습니다. 제 경험상 다 이해하고 손으로 풀고, 코드로 옮기는 것이 훨씬 빨랐습니다.

 

3. 30분 내로 못풀면 넘어가기

 문제를 풀다보면, 분명 잘 푼거 같은데 정답이 아닌 경우가 있습니다. 이런 경우는 보통 3가지 이유로 나뉩니다.

 

(1). 내가 생각 못한 예외 상황이 있다.

(2). 아예 잘못된 알고리즘을 선택했다. (시간 복잡도를 고려 못했을 경우)

(3). 코드상 문법이 잘못된 부분이 있다. (Segmentation Fault, Runtime Error 등..)

 

어떤 이유든지 이러한 경우에는 우선 넘어가도록 합시다. 거의 다 풀었다고 생각하지만, 해당 문제만 붙잡고 있을 순 없는 노릇입니다. 넘어가서 다른 문제를 풀다보면 해결방안이 생각 날 수도 있고, 그때 돌아와서 풀는게 낫습니다. (2)번 같은 경우에는 코드를 싹 다 갈아 엎어야하는 문제가 있고, 또한 모르는 알고리즘이여서 원래부터 못푸는 문제였을 확률이 매우 높습니다. (3)번 또한 컴파일러를 보통 사용할 수 없기 때문에, 디버깅하는데 너무 많은 시간을 허비해야하기 때문에, 우선 풀 수 있는 다른 문제들로 넘어가고, 시간이 남거나 해결방안이 떠올랐을 때 돌아가도록 합시다. 시간 배분하는 것이 매우 중요합니다.

 

 

 

추가로 알아두면 좋은 알고리즘

  코딩테스트를 볼때 보통 맨 밑에 보면 조건이 있습니다. 데이터의 사이즈와, 시간 제한이 있는데, 저희가 사용하려는 알고리즘이 해당 제한 내에서 돌아갈 수 있는지 판단하는 것이 중요합니다. 계산해보고 안되는데 더 줄일 방법이 없는 경우에는 저희가 모르는 알고리즘이 있다는 뜻이겠죠!? 따라서 시간복잡도를 줄이는데 주로 사용되는 알고리즘들에 대해서 소개하겠습니다.

 

1. 이분 탐색

https://nakco.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9D%B4%EC%A7%84-%ED%83%90%EC%83%89%EC%9D%B4%EB%B6%84-%ED%83%90%EC%83%89

 

알고리즘 - 이진 탐색(이분 탐색)

개념 이진 탐색이란 데이터가 정렬돼 있는 배열에서 특정한 값을 찾아내는 알고리즘이다. 배열의 중간에 있는 임의의 값을 선택하여 찾고자 하는 값 X와 비교한다. X가 중간 값보다 작으면 중간

nakco.tistory.com

 

2. Union & Find

https://nakco.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Union-Find

 

알고리즘 - Union & Find

개념 그룹을 지을 때 사용할 수 있는 알고리즘이다. 처음에 자기 자신을 부모로 설정하고 조건에 따라 부모를 설정한다. Find는 자신의 최상위 부모를 찾는 역할을 한다. Union은 두 변수의 부모가

nakco.tistory.com

 

3. 누적합 (Prefix Sum)

https://nakco.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%88%84%EC%A0%81%ED%95%A9Prefix-Sum

 

알고리즘 - 누적합(Prefix Sum)

개념 말 그대로 구간의 누적의 합을 구하는 알고리즘이다. 배열에 값을 저장하고 하나씩 더해가는 방식은 O(n^2)의 시간 복잡도를 갖는다. 하지만 누적합 알고리즘을 사용한다면 O(n)으로 시간복

nakco.tistory.com

 

4. 크루스칼 알고리즘 (Kruskal)

https://nakco.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%81%AC%EB%A3%A8%EC%8A%A4%EC%B9%BCKruskal

 

알고리즘 - 크루스칼(Kruskal)

개념 가장 적은 비용으로 모든 노드를 연결하기 위해 사용하는 알고리즘이다. 최소 비용 신장 트리를 만들기 위한 대표적인 알고리즘으로 흔히 여러개의 도시가 있을 때 각 도시를 도로를 이용

nakco.tistory.com

 

5. 다익스트라 (dijkstra)

https://nakco.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%8B%A4%EC%9D%B5%EC%8A%A4%ED%8A%B8%EB%9D%BCdijkstra

 

알고리즘 - 다익스트라(dijkstra)

개념 다익스트라(dijkstra)알고리즘은 다이나믹 프로그래밍(dp)를 활용한 대표적인 최단 경로 탐색 알고리즘이다. 다익스트라 알고리즘은 하나의 정점에서 다른 모든 정점으로 가는 최단 경로를

nakco.tistory.com

 

6. 인덱스 트리 (Index Tree)

https://nakco.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%ED%8A%B8%EB%A6%ACIndex-Tree

 

알고리즘 - 인덱스 트리(Index Tree)

개념 이진 트리 중 하나로 구간 합을 구하는데 사용된다. 말단 노드에는 원소들이 들어 있고 부모는 자식들의 합이 된다. 부모는 모든 자식들의 합이 되기 때문에 연속되는 구간의 합을 구할 수

nakco.tistory.com

 

마무리

 저는 이러한 공부 방법으로 라인을 제외하고는 모든 기업의 코딩테스트에서 합격했습니다. 라인은 시간 분배를 못하고, 앞에 문제에서 끙끙대다가 풀 수 있는 문제도 못풀고, 시간분배를 못해 떨어졌다고 생각합니다. 여러분은 꼭 시간 분배를 잘 하시길 바랍니다! 제가 코딩테스트를 본 기업은 네이버, 카카오, 신한은행, 삼성전자, 네이버 클라우드 등이 있는데 나중에 시간이 된다면 각 기업의 코딩테스트에 대해 리뷰해보도록 하겠습니다. 오늘도 읽어주셔서 감사합니다. 개발자, 취준생 여러분 항상 응원하겠습니다.

반응형
반응형

안녕하세요. 이번 포스팅에서는 코딩테스트에 대해 다뤄보겠습니다. 개발자 스펙과 자기소개서 작성법을 보고 싶으시다면 이전 포스팅들을 참조해 주시길 바랍니다.

https://nakco.tistory.com/

 

낙서 코딩

삼성전자 개발자 출신의 취업, 코딩 낙서장입니다. 코딩 공부를 하고, 개발자로 취업하는 길이 매우 힘들었기 때문에 꿀팁을 공유하기 위해 블로그를 개설했습니다.

nakco.tistory.com

 

자기소개서까지 합격하신 여러분 축하드립니다. 하지만 아무리 자기소개서를 잘 썼다고 해도 코딩테스트에서 떨어지면 말짱 도루묵이겠죠! 코딩테스트는 갑자기 준비할 수 없는 영역입니다. 따라서 미리미리 준비를 해놓아야 합니다. 보통은 4문제 ~ 7문제로 이루어져있고, 문제의 절반정도 풀면 합격할 수 있습니다. 문제의 난이도는 회사마다 매우 상이하지만, 보통 1문제는 엄청 쉽고 2문제 부터 백준 실버 ~ 골드, 프로그래머스 level 2 ~3 정도라고 생각합니다.

 

코딩테스트 준비 사이트

 이전에 알고리즘을 해본적이 없거나, 기초가 부족하다면 바로 문제를 풀기는 어려울 것입니다. 코딩 문제를 풀 수 있는 사이트는 백준, 프로그래머스 등이 있는데, 백준도 물론 좋은 사이트지만 저는 프로그래머스를 강력 추천드립니다. 백준에는 너무도 많은 문제가 있어, 무엇을 풀어야할 지 감이 안오고, 또한 컴파일러가 저는 프로그래머스가 훨씬 좋다고 느꼈습니다. 시험 자체도 프로그래머스 사이트에서 보는 기업이 꽤 있기에, 프로그래머스 사이트를 강력 추천드립니다.

 

프로그래머스에 알고리즘을 입문하기 굉장히 좋은 페이지가 있어 소개해드리겠습니다.

https://school.programmers.co.kr/learn/challenges?tab=algorithm_practice_kit 

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

바로 코딩테스트 고득점 Kit 입니다. 해당 페이지에는 자주 나오는 유형별로 문제가 나누어져있고, 각 테마마다 level 1~3까지의 문제들이 구비되어 있습니다. 

 

자, 그렇다면 해당 사이트에서 어떻게 공부를 해야할까요? 우선 해당 개념을 먼저 익혀야합니다. 무작정 문제 먼저 푼다고해서 절대 문제를 풀 수 없습니다. 코딩도 수학과 마찬가지로 공식을 먼저 이해해야 문제를 풀 수 있습니다. 알고리즘은 수학의 공식과 똑같습니다. 이를 이해하지 못한다면 문제는 손도 못댈것입니다.

 

코딩테스트 고득점 Kit 공부 방법

한 항목의 개념을 익히셨다면, level 1 문제만 우선 풀어보도록 합시다. level 2 부터는 절대 쉽지 않습니다. 우선 각 항목의 개념을 익히면서 level 1 문제만 푸는 것을 목표로 합니다. 그렇게 모든 항목의 level 1 문제를 풀고나면, 다시 시작입니다. 다시 각 항목의 개념을 공부합니다. 항목이 10개가 되기 때문에 기억이 잘 안날 수 있고, 다시 보면 분명 더 알아가는 것이 있을 것입니다. 그 후에 level 2 문제에 도전합니다. 아마 이때부터 쉽지 않을 것입니다. 하지만 지금부터가 중요합니다. level 2 정도만 스스로 풀 수 있어도 웬만한 코딩테스트는 합격할 수 있을 것입니다. 

 

level 2 부터는 무조건, 스스로 풀어야합니다. 시간을 1 ~ 2시간정도로 잡아두고, 문제 풀이를 시작합니다. 이 시간 동안에는 절대 남의 코드를 찾아보지 않기로 약속해야합니다. 계속 틀리고 고치고를 반복하다가, 기준 시간이 다 됐음에도 못풀겠을때, 그때 해당 문제를 검색해서 다른 사람의 풀이를 보면서 이해합니다. 이때 그 사람의 코드를 그대로 따라하면, 그건 그 문제를 푼게 아니라, 그냥 타이핑 연습하는 것이겠죠? 머리로 이해했다해도, 남의 코드를 그대로 적는건 기억에 남지 않습니다. 따라서 다른 사람의 코드를 이해만 하고, 다시 자신의 코드로 직접 문제를 푸는것이 핵심입니다. 절대로 조급해 하지마세요. 여러 부분을 얕게 아는 것보다, 하나를 정확히 아는것이 코딩테스트에 도움이 됩니다. 왜냐면 코딩테스트는 부분점수가 없기 때문이죠. 0점이나 90점이나 똑같이 fail입니다. 무조건 100점을 목표로 문제를 풀어야합니다.

 

level 2까지 모두 푸셨다면 여러분은 이미 합격권에 있을 확률이 높습니다. 하지만 naver, kakao 같은 IT 기업은 문제가 조금 더 어렵습니다. 따라서 level 3까지 도전해봐야 안정적으로 대부분의 코딩테스트를 합격할 수 있을 것입니다. level 3 부터는 아마 남의 코드를 봐도 어렵고 짜증날 수 있습니다. 하지만 한번 풀어놓으면, 해당 알고리즘을 사용하는 문제들은 그냥 껌씹듯이 풀 수 있을 것입니다. 조급해하지말고, 기출문제 풀려하지말고, 그냥 우선 코딩테스트 고득점 Kit 부터 정복하시길 추천드립니다. 

 

마무리

프로그래머스에서 고득점 Kit를 모두 정복하셨다면, 아마 대부분 기업의 코딩테스트에서 절반 이상은 맞출 수 있을 것입니다. 하지만 해당 사이트에 없는 알고리즘도 분명 나올 수 있습니다. 다음 포스팅에서는 코딩테스트 꿀팁과, 심화 알고리즘에 대해 소개하도록 하겠습니다. 개발자, 취준생 여러분 모두 응원하며 마무리하겠습니다. 감사합니다.

 

반응형
반응형

개념

이진 트리 중 하나로 구간 합을 구하는데 사용된다. 말단 노드에는 원소들이 들어 있고 부모는 자식들의 합이 된다. 부모는 모든 자식들의 합이 되기 때문에 연속되는 구간의 합을 구할 수 있다. 또한 특정 위치의 값을 지속적으로 바꿔야하는 경우에도 사용된다.

https://blog.hexabrain.net/246

 

알고리즘 2-2강. 탐색 알고리즘 - 이진 탐색(Binary Search)

[탐색 알고리즘 강좌] 데이터를 찾아보자! 이진 탐색(Binary Search) 이번에는 순차 탐색에 이어 이진 탐색(Binary Search)에 대해 알아보도록 할텐데, 이 '이진 탐색(Binary Search)'이 왜 이진인지 짐작이 가

blog.hexabrain.net

 

조건

  • 부분 합을 계속해서 구해야 한다.
  • 특정 인덱스 변경이 지속적으로 일어 날 수 있다.

사용 하면 좋은 경우

  1. 부분 합을 계속 구해야 하는 경우
    • list에서 부분 합 배열을 구하는 경우 O(MN)의 시간이 필요하지만 Index Tree를 이용 할 경우 O(MlogN)의 시간으로 단축 시킬 수 있다.
  2. 특정 인덱스의 변경이 지속적으로 일어 날 경우
    • 부분 합 배열에서 특정 인덱스를 고칠 경우 그 인덱스를 포함하는 모든 합을 다시 구해야 하기 때문에 O(MN)의 시간이 필요한데 Index Tree를 이용 할 경우 O(MlogN)으로 시간을 단축 시킬 수 있다.

구현 방법

기본 이진 탐색

  1. 트리 사이즈 정하기
tydef long long ll;

ll getIndex(int num) { //num은 원소의 갯수
    ll tmp = 1;
    while(tmp < num) {
        tmp <<= 1;
    }
    return tmp * 2;
}

트리의 말단 노드에 모든 원소를 배치해야 하고 부모까지 배치해야하기 때문에 트리 배열의 사이즈는 $2^n$ ≥ (원소의 개수) 가 되는 n을 찾고 그거의 2배를 해야 부모 노드 까지 배치 할 수 있다.

  1. 노드 값 채우기
ll index = getIndex(n);
ll p = index/2;
for(int i = 0; i < n; i++) { //말단 노드에 원소 입력
    scanf("%lld", &t[p]);
    p++;
}
for(ll i = (index/2) - 1; i > 0; i--) {
    t[i] = t[i*2] + t[i*2 + 1]; //왼쪽 자식과 오른쪽 자식의 합
}

트리의 말단 노드에 원소들을 입력 받고 부모 노드들에 자식들의 합을 대입한다. 부모 노드는 아래에서 부터 대입해야 한다.

b 위치의 값을 c로 바꾸기

ll idx = (index/2) + (b-1);
ll pp = idx;
if(t[idx] >= c) {
  ll gap = t[idx] - c; //바꾸려는 값과 원래의 값 차이 
  t[idx] -= gap; //차이만큼 빼기
  pp >>= 1; // 부모 노드로 가기
  while(pp > 0) { //최상단 노드(1번째 인덱스)까지 업데이트
    t[pp] -= gap;
    pp >>= 1;
  }
}
else {
  ll gap = c - t[idx];
  t[idx] += gap;
  pp >>= 1;
  while(pp > 0) {
    t[pp] += gap;
    pp >>= 1;
  }
}

특정 말단 노드의 값을 바꾸면 해당 노드의 부모 노드들의 값을 같이 바꿔준다.

구간 합 출력하기 (b~c번째 인덱스)

ll left = (index / 2) + (b - 1);
ll right = (index / 2) + (c - 1);
ll answer = 0;
while(left <= right) {
    if(left % 2 == 0) { //왼쪽에 있는 노드가 부모기준에 왼쪽에 있을때
        left /= 2; // 부모 노드로 이동 
    }
    else{//부모 기준 오른쪽에 있을때
        answer += t[left]; 
        left ++; // 해당 값을 더해주고 오른쪽 옆으로 이동
        if(left == right) { //왼쪽과 오른쪽이 만나면 해당 값 더해주고 종료
            answer += t[left];
            break;
        }
        else { //왼쪽과 오른쪽이 다르면 왼쪽의 부모로 이동
            left /= 2;
        }
    }
    if(right % 2 == 0) { //오른쪽 노드가 부모 기준 왼쪽에 있을 때
        answer += t[right]; 
        right --; //해당 값을 더해주고 왼쪽 옆으로 이동
        right /= 2; //새로운 노드의 부모로 이동
    }
    else {
        right /= 2; //부모 노드로 이동
    }
}

노드의 위치를 통해 부모 노드로 값을 구할 수 없으면 정답에 더해주고 부모 노드에 포함 된다면 값을 더하지 않고 부모 노드로 이동한다.

전체 코드

int main(int argc, const char * argv[]) {
    vector<ll> t(8194304 + 1, 0);
    scanf("%d%d%d", &n, &m, &k);
    ll index = getIndex(n);
    ll p = index/2;
    for(int i = 0; i < n; i++) {
        scanf("%lld", &t[p]);
        p++;
    }
    for(ll i = (index/2) - 1; i > 0; i--) {
        t[i] = t[i*2] + t[i*2 + 1];
    }
    for(int i = 0; i < m + k; i++) {
        scanf("%d%lld%lld", &a, &b, &c);
        if(a == 1) {
            ll idx = (index/2) + (b-1);
            ll pp = idx;
            if(t[idx] >= c) {
                ll gap = t[idx] - c;
                t[idx] -= gap;
                pp >>= 1;
                while(pp > 0) {
                    t[pp] -= gap;
                    pp >>= 1;
                }
            }
            else {
                ll gap = c - t[idx];
                t[idx] += gap;
                pp >>= 1;
                while(pp > 0) {
                    t[pp] += gap;
                    pp >>= 1;
                }
            }
        }
        else if(a == 2) {
            //구간 합 출력
            ll left = (index / 2) + (b - 1);
            ll right = (index / 2) + (c - 1);
            ll answer = 0;
            while(left <= right) {
                if(left % 2 == 0) {
                    left /= 2;
                }
                else{
                    answer += t[left];
                    left ++;
                    if(left == right) {
                        answer += t[left];
                        break;
                    }
                    else {
                        left /= 2;
                    }
                }
                if(right % 2 == 0) {
                    answer += t[right];
                    right --;
                    right /= 2;
                }
                else {
                    right /= 2;
                }
            }
            printf("%lld\\n", answer);
        }
    }
    return 0;
}

시간 복잡도

  • O(MlogN)
반응형
반응형

개념

다익스트라(dijkstra)알고리즘은 다이나믹 프로그래밍(dp)를 활용한 대표적인 최단 경로 탐색 알고리즘이다. 다익스트라 알고리즘은 하나의 정점에서 다른 모든 정점으로 가는 최단 경로를 알려준다.

참고: https://blog.naver.com/PostView.naver?blogId=ndb796&logNo=221234424646&redirect=Dlog&widgetTypeCall=true&directAccess=false

 

23. 다익스트라(Dijkstra) 알고리즘

  다익스트라(Dijkstra) 알고리즘은 다이나믹 프로그래밍을 활용한 대표적인 최단 경로(Shortest P...

blog.naver.com

2차원 배열로 간선의 정보를 저장한다. 서로 연결되어 있지 않은 경우는 무한의 수로 설정한다.

  1. 음의 간선 정보는 포함 할 수 없다.
  2. 방문하지 않은 노드중 가장 비용이 적은 노드를 선택

구현 방법

  1. 간선 정보 2차원 배열로 생성, 최단 거리 배열 생성
#define INF 1e9 //엄청 큰 값인 10억

vector<pair<int,int>> v[10001]; // 비용, 연결점
int d[10001];
fill(d, d+10001, INF);

최단 거리 배열은 가장 작은 값을 나타내야 하기 때문에 우선은 가장 큰 값으로 초기화해 놓는다.

  1. priority_queue를 이용하여 최소비용 순서로 다익스트라 알고리즘 적용
void dijkstra(int start) {
	priority_queue<pair<int,int>> pq;
	pq.push({0,start});
	d[start] = 0;
	
	while(!pq.empty()) {
		int dist = -pq.top().fist; // 현재 노드까지의 비용, 작은것부터 뽑기위해 -로 저장해놨음
		int now = pq.top().second; // 현재 노드
		pq.pop();
	
		if(d[now] < dist) continue; // 현재 노드까지의 비용이 더 크면 볼 것도 없음
	
		for(int i = 0; i < v[now].size(); i++) {
			int cost = dist + v[now][i].first; //현재노드까지의 비용 + 다음 노드로 가는 비용
			if(cost < d[v[now][i].second]) {
				d[v[now][i].second] = cost
				pq.push(make_pair(-cost, v[now][i].second));
			}
		}
	}
}

현재까지의 비용이 가장 적은 노드 선택 후 그 노드와 연결된 노드들의 비용 값을 구한 후 만약 구한 값이 원래의 값보다 더 작다면 값을 업데이트 해 준 후 우선순위 큐에 해당 노드를 넣는다.

전체 코드

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
#define INF 1e9 // 무한을 의미하는 값으로 10억을 설정

// 노드의 개수(N), 간선의 개수(M), 시작 노드 번호(Start)
// 노드의 개수는 최대 100,000개라고 가정
int n, m, start;

vector<pair<int, int> > graph[100001]; // 각 노드에 연결되어 있는 노드에 대한 정보를 담는 배열
int d[100001]; // 최단 거리 테이블 만들기

void dijkstra(int start)
{
    priority_queue<pair<int,int>>pq; // 거리, 노드 인덱스
    
    pq.push({0,start}); //시작 노드로 가기위한 최단 경로는 0으로 설정하여, 큐에 삽입.
    d[start]=0;
    
    while(!pq.empty())
    {
        int dist = -pq.top().first; //현재 노드까지의 비용
        int now = pq.top().second; // 현재 노드
        pq.pop();
        
        if(d[now]<dist) // 이미 최단경로를 체크한 노드인 경우 패스
            continue;
        
        for(int i=0; i<graph[now].size(); i++)
        {
            int cost = dist+graph[now][i].second; // 거쳐서 가는 노드의 비용을 계산
                                                  // 현재노드까지 비용 + 다음 노드 비용
            if(cost<d[graph[now][i].first]) // 비용이 더 작다면 최단경로 테이블 값을 갱신.
            {
                d[graph[now][i].first]=cost;
                pq.push(make_pair(-cost,graph[now][i].first));
            }
        }
    }
}

int main(void)
{
    cin >> n >> m >> start;
    
    // 모든 간선 정보를 입력받기
    for (int i = 0; i < m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        // a번 노드에서 b번 노드로 가는 비용이 c라는 의미
        graph[a].push_back({b, c});
    }
    
    // 최단 거리 테이블을 모두 무한으로 초기화
    fill(d, d + 100001, INF);
    
    // 다익스트라 알고리즘을 수행
    dijkstra(start);
    
    // 모든 노드로 가기 위한 최단 거리를 출력
    for (int i = 1; i <= n; i++)
    {
        // 도달할 수 없는 경우, 무한(INFINITY)이라고 출력
        if (d[i] == INF) {
            cout << "INFINITY" << '\\n';
        }
        // 도달할 수 있는 경우 거리를 출력
        else {
            cout << d[i] << '\\n';
        }
    }
}

시간 복잡도

O(N * logN)

 

반응형

'코딩 낙서' 카테고리의 다른 글

카카오기출 - 순위 검색  (0) 2023.06.23
알고리즘 - 인덱스 트리(Index Tree)  (0) 2023.06.21
알고리즘 - 크루스칼(Kruskal)  (0) 2023.06.21
알고리즘 - 누적합(Prefix Sum)  (0) 2023.06.21
알고리즘 - Union & Find  (0) 2023.06.21
반응형

개념

가장 적은 비용으로 모든 노드를 연결하기 위해 사용하는 알고리즘이다. 최소 비용 신장 트리를 만들기 위한 대표적인 알고리즘으로 흔히 여러개의 도시가 있을 때 각 도시를 도로를 이용해 연결하고자 할 때 비용을 최소로 하기 위해 실제로 적용되는 알고리즘이다.

노드 = 정점 = 도시

간선 = 거리 = 비용

일단 모든 노드를 최대한 적은 비용으로 연결 시켜야하기 때문에 간선 정보를 오름차순으로 정렬 한 후 비용이 적은 간선부터 차근차근 그래프에 포함시킨다. 단 이때 사이클을 형성하게 될 경우 간선을 포함하지 않는다.

 

조건

  1. 간선 정보를 오름차순으로 정렬
  2. 사이클이 형성되지 않게 연결

사용하기 좋은 경우

  1. 모든 노드를 최소 비용으로 연결할 경우
  2. 연결되어있는 그룹의 수를 구하는 경우 → 크루스칼 알고리즘으로 다 연결 한 후 최상단 부모의 개수가 그룹의 수이다.

구현 방법

  1. 비용, 출발, 도착의 정보를 갖는 노드들을 비용을 기준으로 오름차순 정렬하기
typedef pair<int, int> pii;
for(int i = 0; i < m; i++) { //a: 출발지점, b: 도착지점, c: 비용
        scanf("%d%d%d", &a, &b, &c);
        v.push_back(pair<int, pii>(c, pii(a,b)));
    }
    int p = 0;
    sort(v.begin(), v.end()); //비용 기준으로 정렬
  1. 최상단 부모 찾기
int parent[10001];

int findParent(int child) {
    if(parent[child] == 0) {
        return child;
    }
    return parent[child] = findParent(parent[child]);
}

parent 배열의 인덱스는 자기 자신을 나타내고 값은 자신의 부모를 나타낸다. 값이 0일경우 부모가 존재하지 않는 것으로 최상단 노드를 의미하기 때문에 값이 0일때를 찾아서 return 한다.

  1. 부모 자식 관계 설정하기, 최소 비용 구하기
int unionSet(pair<int, pii> xy) {
    int xp = findParent(xy.second.first); //출발 지점의 부모 찾기
    int yp = findParent(xy.second.second); //도착 지점의 부모 찾기
    
    if(xp == yp) return xp; //부모가 같으면 이미 연결되어 있는 것이므로 종료
    else { //부모가 다르면 서로 다른 선에 연결되어 있기 때문에 한 쪽의 부모를 다른 쪽 부모에 연결
        cost[xp] += cost[yp] + xy.first; //연결 후 최상단 노드의 비용을 더해줌
        parent[yp] = xp; //한 쪽의 최상단 부모를 다른 쪽 최상단 부모로 설정
    }
    return xp; //새로운 연결선의 최상단 노드
}

부모가 같으면 이미 연결되어있는 상태이고 다르면 서로의 최상단 부모를 연결해준다.

전체 코드

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
typedef pair<int, int> pii;
int n, m, a, b, c, answer;

vector<pair<int, pii>> v;

int parent[10001];
int cost[10001];

int findParent(int child) {
    if(parent[child] == 0) {
        return child;
    }
    return parent[child] = findParent(parent[child]);
}

int unionSet(pair<int, pii> xy) {
    int xp = findParent(xy.second.first);
    int yp = findParent(xy.second.second);
    
    if(xp == yp) return xp;
    else {
        cost[xp] += cost[yp] + xy.first;
        parent[yp] = xp;
    }
    return xp;
}

int main(int argc, const char * argv[]) {
    scanf("%d", &n);scanf("%d", &m);
    for(int i = 0; i < m; i++) {
        scanf("%d%d%d", &a, &b, &c);
        v.push_back(pair<int, pii>(c, pii(a,b)));
    }
    int p = 0;
    sort(v.begin(), v.end());
    for(int i = 0; i < v.size(); i++) {
        p = unionSet(v[i]);
    }
    answer = cost[p];
    printf("%d", answer);
    
    return 0;
}

시간복잡도

O(logN)

반응형
반응형

개념

말 그대로 구간의 누적의 합을 구하는 알고리즘이다. 배열에 값을 저장하고 하나씩 더해가는 방식은 O(n^2)의 시간 복잡도를 갖는다. 하지만 누적합 알고리즘을 사용한다면 O(n)으로 시간복잡도를 줄일 수 있다. 보통 누적합은 부분 합을 구하기 위해서 사용하는데 만약 a부터 b까지의 합을 구한다고 하면 먼저 누적합을 만들고, b번째 요소에서 a-1번째 요소를 빼주면 구할 수 있다.

사용 하면 좋은 경우

  1. 리스트에 일정 구간의 값의 합을 구할 때
  2. 리스트에 일정 구간에 값을 넣어야 할 경우(시작과 끝 부분에 값 넣어놓고 누적합)

사용 방법

  1. 구간의 합 구하기
import itertools

a = [3,2,1,6,8,4]
result = list(itertools.accumulate(a))

print(result)

🖨️ [3, 5, 6, 12, 20, 24]

  1. 부분 합 구하기
import itertools

a = [3,2,1,6,8,4]
start = 3
end = 5
acc = list(itertools.accumulate(a))

result = acc[end] - acc[start-1]

🖨️ 18

누적합을 우선 구한 후 마지막 부분의 누적합 값에서 시작부분 전의 누적합을 빼서 부분 합을 구할 수 있다,

시간 복잡도

  • O(N)
반응형
반응형

개념

그룹을 지을 때 사용할 수 있는 알고리즘이다. 처음에 자기 자신을 부모로 설정하고 조건에 따라 부모를 설정한다. Find는 자신의 최상위 부모를 찾는 역할을 한다. Union은 두 변수의 부모가 다르다면 하나의 최상위 부모를 다른 하나의 최상위 부모의 자식으로 만들어 두개의 그룹이 합쳐지는 기능을 수행한다.

  • Union by Rank : 트리가 균형이 맞도록 한다. 각 집합에 대해 이 집합을 나타내는 트리의 높이를 rank 변수에 저장한다. 그리고 두 집합을 합칠 때 rank가 작은 집합을 rank가 큰 집합 아래에 붙힌다.
  • Path Compression : 경로 압축(path compression)은 find 과정 가운데 경로 상의 모든 점들을 곧바로 루트 정점에 연결하는 방법이다. 이렇게 하면 위 그림과 같은 구조로 시간 복잡도가 훨씬 줄어들게 된다.

조건

  • Cycle 구조가 생기면 안된다. 따라서 Union은 서로의 부모가 다를때만 해야한다.

사용하기 좋은 경우

  1. 서로 중복되지 않는 부분 집합들로 나눌 때
  2. 그룹의 수를 구할 때

구현 방법

  1. Find
parent = [x for x in range(10)] # 자기 자신이 부모로 초기화

def find_parent(x, parent):
	if parent[x] == x: # x가 최상위 부모
			return x
	#x의 부모중 최상위 부모 찾기
	parent[x] = find_parent(parent[x], parent)
	return parent[x]

  1. Union
def union_parent(a, b, parent):
	pa = find_parent(a)
	pb = find_parent(b)
	if pa == pb :
		pass
	elif pa < pb : # 더 큰애가 부모가 되게 설정
		parent[pa] = pb
	else:
		parent[pb] = pa

시간 복잡도

  • O(M)+O(Mlog⋆N)+O(Nlog⋆N)=O((M+N)log⋆N)
  • (M : find 연산 수, N : 트리가 바뀌는 횟수)
반응형

'코딩 낙서' 카테고리의 다른 글

알고리즘 - 크루스칼(Kruskal)  (0) 2023.06.21
알고리즘 - 누적합(Prefix Sum)  (0) 2023.06.21
알고리즘 - 이진 탐색(이분 탐색)  (0) 2023.06.21
[LINUX] 기본 명령어 정리  (0) 2023.06.20
Linux  (0) 2023.06.14

+ Recent posts