- 문제 :https://www.acmicpc.net/problem/11659

 

 

Silver Lv3 문제다!

누적 합은 처음 보는 문제인데, 풀이 논리를 알아둬야할 것 같아서 포스팅한다. 

정수로 이루어진 배열에서 일정 구간의 합을 구할 때 파이썬의 경우 인덱싱으로 바로 구해도 되겠지만 각 M개의 케이스에 대해서 전체 배열을 매번 탐색해야하므로 O(M * N)의 시간복잡도가 된다.

문제의 경우 N, M 모두 최대 10만이므로 최악의 경우 시간 복잡도는 100초(100억개 탐색)가 걸릴 수 있다. 그래서 시간 복잡도를 줄이는 방향으로 로직을 설정해야하는데, 이 때 fast_sum이라는 배열을 이용한다. 

 

fast_sum[i]는 nums[1] ~ nums[i]까지의 누적합을 나타낸다. 따라서 [a, b] 구간의 합을 구하려면 

fast_sum[b](인덱스 1부터 b까지의 합) - fast_sum[a - 1](인덱스 1부터 a - 1까지의 합)을 계산하면된다. 

위 연산으로 수행하면 O(1)의 시간복잡도를 가져서 O(N)보다 훨씬 빠른 속도로 진행할 수 있다.

총 M개의 케이스이므로 전체 시간 복잡도는 O(M)

 

먼저 문제 정보를 저장한 다음에 fast_sum을 계산한 뒤 각 M개의 케이스에 대해서 누적합의 차이를 프린트 해주면 정답이다. 

 

- 정답 풀이 : 

import sys
input = sys.stdin.readline

n, m = map(int, input().split())
nums = [0] + list(map(int, input().split()))

fast_sum = [0] * (n + 1) 
fast_sum[1] = nums[1]
for i in range(1, n + 1):
    fast_sum[i] = nums[i] + fast_sum[i - 1]

answer = [] 
for _ in range(m):
    a, b = map(int, input().split())
    answer.append(fast_sum[b] - fast_sum[a - 1])
    
for i in range(len(answer)):
    print(answer[i])

 

관련 내용은 이 블로그에 잘 설명 되어 있다. 

DFS

참고한 블로그

Depth First Search의 약자로, 그래프 자료에서 데이터를 탐색하는 알고리즘

데이터를 위에서 아래로 찾는 방식을 DFS라고 부른다(위에서 아래로 탐색할 때 왼쪽으로 가는지 오른쪽으로 가는지는 전혀 상관 없다)

 

DFS 기본 원칙

방문하지 않은 노드만 방문해야한다. 이미 방문한 노드는 다시 방문하지 않는다.

 

구현

  • 스택을 이용
    • 성능면에서 떨어지는 단점이 있음
def dfs(graph, start_node):

## 기본은 항상 두개의 리스트를 별도로 관리해주는 것
need_visited, visited = list(), list()

## 시작 노드를 시정하기
need_visited.append(start_node)

## 만약 아직도 방문이 필요한 노드가 있다면,
while need_visited:

    ## 그 중에서 가장 마지막 데이터를 추출 (스택 구조의 활용)
    node = need_visited.pop()

    ## 만약 그 노드가 방문한 목록에 없다면
    if node not in visited:

        ## 방문한 목록에 추가하기
        visited.append(node)

        ## 그 노드에 연결된 노드를
        need_visited.extend(graph[node])

return visited

 

  • 재귀함수를 이용
    • 스택을 이용한 것보다 비교적 단순한 구조
def dfs_recursive(graph, start, visited = []):
## 데이터를 추가하는 명령어 / 재귀가 이루어짐 
    visited.append(start)
 
    for node in graph[start]:
        if node not in visited:
            dfs_recursive(graph, node, visited)
    return visited

BFS

참고한 블로그

 

Breadth First Search의 약자로, 그래프에서 시작 노드에 인접한 노드부터 탐색하는 알고리즘

이 특징 때문에 FIFO 방식의 큐 자료구조를 활용한다. 즉 인접한 노드를 반복적으로 큐에 삽입하고 먼저 삽입된 노드부터 차례로 큐에서 꺼내도록 알고리즘을 작성하면 된다.

 

기본 원칙

방문한 노드는 방문처리해서 중복해서 탐색하지 않게 한다.

 

언제 사용?

그래프에서 모든 간선의 비용이 동일한 조건에서 최단 거리를 구하는 문제를 효과적으로 해결할 수 있다

 

BFS 동작과정

  1. 탐색 시작 노드 정보를 큐에 삽입하고, 방문처리한다
  2. 큐에서 노드를 꺼내 방문하지 않은 인접 노드 정보를 모두 큐에 삽입하고 방문처리한다
  3. 2번의 과정을 더이상 수행할 수 없을 때까지 반복한다
# deque 라이브러리 불러오기
from collections import deque

# BFS 메서드 정의
def bfs (graph, node, visited):
    # 큐 구현을 위한 deque 라이브러리 활용
    queue = deque([node])
    # 현재 노드를 방문 처리
    visited[node] = True
    
    # 큐가 완전히 빌 때까지 반복
    while queue:
        # 큐에 삽입된 순서대로 노드 하나 꺼내기
        v = queue.popleft()
        # 탐색 순서 출력
        print(v, end = ' ')
        # 현재 처리 중인 노드에서 방문하지 않은 인접 노드를 모두 큐에 삽입
        for i in graph[v]:
            if not (visited[i]):
                queue.append(i)
                visited[i] = True

Heap

  • 완전 이진 트리 형태의 우선순위 큐를 위하여 만들어진 자료구조
  • 최소값(최소 힙) 또는 최대값(최대 힙)을 빠르게 찾아내는데 유용한 자료구조
  • 중복 값을 허용(이진 탐색 트리에서는 중복값을 허용하지 않음)
  • 반 정렬 상태(큰 값이 상위 레벨에 있고, 작은 값이 하위 레벨에 있다는 정도)

최소힙

key(부모 노드) ≥ key(자식 노드) 의 형태를 가지는 힙

 

최대힙

key(부모 노드) ≤ key(자식 노드) 의 형태를 가지는 힙

 

Heap의 구현

  • 힙을 저장하는 표준적인 자료구조는 배열이다
  • 구현을 쉽게 하기 위하여 인덱스 0은 사용되지 않는다
  • 특정 위치의 노드 번호는 새로운 노드가 추가되어도 변하지 않는다
  • 힙에서의 부모 노드와 자식 노드의 관계
    • 부모 인덱스 = (자식 인덱스) // 2
    • 왼쪽 자식 인덱스 = (부모 인덱스) * 2
    • 오른쪽 자식 인덱스 = (부모 인덱스) * 2 + 1

Heap의 삽입 (시간 복잡도 : O(logn))

  1. 힙에 새로운 요소가 들어오면, 일단 새로운 노드를 힙의 마지막 노드에 이어서 삽입한다
  2. 해당 힙의 규칙(부모 노드 이하이거나 이상이거나)을 만족할 때까지 새로운 노드를 부모 노드와 교환한다

Heap의 삭제 (시간 복잡도 : O(logn))

  1. 최대 힙에서 최대값은 루트 노드이므로 루트 노드가 삭제 된다
  2. 삭제된 루트 노드에는 힙의 마지막 노드를 가져온다
  3. 힙을 재구성한다

 

구현

참고 블로그

 

Heap 삽입

# 힙 노드 삽입
    def insert(self, n):
        self.queue.append(n)
        last_index = len(self.queue) - 1
        while 0 <= last_index:
            parent_index = self.parent(last_index)
            if 0 <= parent_index and self.queue[parent_index] < self.queue[last_index]:
                self.swap(last_index, parent_index)
                last_index = parent_index
            else:
                break
            
    def swap(self, i, parent_index):
        self.queue[i], self.queue[parent_index] = self.queue[parent_index], self.queue[i]
    
    def parent(self, index):
        return (index - 1) // 2

 

Heap 삭제

# 힙 노드 삭제
    def delete(self):
        last_index = len(self.queue) - 1
        if last_index < 0 :
            return -1
        
        self.swap(0, last_index) # 루트노드와 끝노드 교환
        max_value = self.queue.pop() # 끝노드(이전의 루트 노드 삭제)
        self.maxHeapify(0) # root에서부터 재정렬 
        print(max_value)
        return max_value
    
    def maxHeapify(self, i):
        left_index = self.leftChild(i)
        right_index = self.rightChild(i)
        max_index = i
        
        # max_index 보다 자식노드가 있는지 확인
        if left_index <= len(self.queue) - 1 and self.queue[max_index] < self.queue[left_index]:
            max_index = left_index
        if right_index <= len(self.queue) -1 and self.queue[max_index] < self.queue[right_index]:
            max_index = right_index
        
        if max_index != i :
            self.swap(i, max_index)
            self.maxHeapify(max_index)
            
    def leftChild(self, index):
        return index * 2 + 1
    
    def rightChild(self, index):
        return index * 2 + 2

 

전체

class MaxHeap(object):
    def __init__(self):
        self.queue = []
    
    # 힙 노드 삽입
    def insert(self, n):
        self.queue.append(n)
        last_index = len(self.queue) - 1
        while 0 <= last_index:
            parent_index = self.parent(last_index)
            if 0 <= parent_index and self.queue[parent_index] < self.queue[last_index]:
                self.swap(last_index, parent_index)
                last_index = parent_index
            else:
                break
            
    def swap(self, i, parent_index):
        self.queue[i], self.queue[parent_index] = self.queue[parent_index], self.queue[i]
    
    def parent(self, index):
        return (index - 1) // 2
    
    # 힙 노드 삭제
    def delete(self):
        last_index = len(self.queue) - 1
        if last_index < 0 :
            return -1
        
        self.swap(0, last_index) # 루트노드와 끝노드 교환
        max_value = self.queue.pop() # 끝노드(이전의 루트 노드 삭제)
        self.maxHeapify(0) # root에서부터 재정렬 
        print(max_value)
        return max_value
    
    def maxHeapify(self, i):
        left_index = self.leftChild(i)
        right_index = self.rightChild(i)
        max_index = i
        
        # max_index 보다 자식노드가 있는지 확인
        if left_index <= len(self.queue) - 1 and self.queue[max_index] < self.queue[left_index]:
            max_index = left_index
        if right_index <= len(self.queue) -1 and self.queue[max_index] < self.queue[right_index]:
            max_index = right_index
        
        if max_index != i :
            self.swap(i, max_index)
            self.maxHeapify(max_index)
            
    def leftChild(self, index):
        return index * 2 + 1
    
    def rightChild(self, index):
        return index * 2 + 2

Deque (Doubly-ended Queue)

양쪽에서 삽입과 삭제가 모두 가능한 자료구조

Stack과 Queue를 합한 구조

 

기본 구조

  • 왼쪽과 오른쪽 모두에서 삽입, 삭제가 가능한 구조
  • 일부 기능을 제한하여 용도에 맞게 변경 가능

Scroll, 입력제한 데크

  • 한 쪽의 입력을 제한한 데크

 

Shelf, 출력제한 데크

  • 한 쪽의 출력은 제한한 데크

+ Recent posts