[알고리즘 스터디] Do it 알고리즘 코딩테스트 with Python #2

오예찬·2023년 9월 12일
0

삽질 시작

괜한 곳 붙잡고 열심히 삽질한 이야기 시작...

자료구조

03-1 배열과 리스트

파이썬으로만 배열과 리스트를 다루다보니 배열과 리스트가 다르단 것을 잠시 잊고 살았는데 이번 기회로 둘의 차이에 대해 확실하게 알았다.

배열

배열은 메모리의 연속 공간에 값이 채워져 있는 형태의 자료구조이다. 배열의 값은 인덱스를 통해 참조할 수 있으며, 선언한 자료형의 값만 저장할 수 있다. 따라서 크기를 한 번 선언하면 크기를 늘리거나 줄일 수 없다!

배열의 특징

  • 인덱스를 사용하여 값에 바로 접근할 수 있다.
  • 새로운 값을 삽입하거나 특정 인덱스에 있는 값을 삭제하기 어렵다. 값을 삽입하거나 삭제하려면 해당 인덱스 주변에 있는 값을 이동시키는 과정이 필요하다.
  • 배열의 크기는 선언할 때 지정할 수 있으며, 한 번 선언하면 크기를 늘리거나 줄일 수 없다.
  • 구조가 간단하므로 코테에 많이 나온다(당연)

리스트

리스트는 값과 포인터를 묶은 노드라는 것을 포인터로 연결한 자료구조이다.

리스트의 특징

  • 인덱스가 없으므로 값에 접근하려면 Head 포인터부터 순서대로 접근해야 한다. 다시 말해 값에 접근하는 속도가 느리다.
  • 포인터로 연결되어 있으므로 데이터를 삽입하거나 삭제하는 연산 속도가 빠르다.
  • 선언할 때 크기를 별도로 지정하지 않아도 된다. 다시 말해 리스트의 크기는 정해져 있지 않으며, 크기가 변하기 쉬운 데이터를 다룰 때 적절하다.
  • 포인터를 저장할 공간이 필요하므로 배열보다 구조가 복잡하다.

하지만 파이썬에선 배열과 리스트를 구분하지 않는다. 왜냐하면 파이썬은 누구랑은 달리 갓갓 언어이기 때문이다. 파이썬의 리스트는 리스트의 특징은 물론, 배열의 특징까지 모두 가지도록 구현되어 있기 때문이다.

03-2 구간 합

"용량을 잃는 대신 시간을 얻는다" 전략

구간 합의 핵심 이론

구간 합 알고리즘을 활용하려면 먼저 합 배열을 구해야 한다. 리스트 A가 있을 때 합 배열 S는 다음과 같이 정의한다.

합 배열 S의 정의

  • S[i] = A[0] + A[1] + A[2] + ... + A[i-1] + A[i]

합 배열은 기존의 리스트 데이터를 전처리한 배열이다. 이렇게 합 배열을 미리 만들어 놓으면 일정 범위의 합을 구하는 시간 복잡도가 O(N) 에서 O(1)로 줄어든다!

합 배열 S를 만드는 공식

  • S[i] = S[i-1] + A[i]

이렇게 구현된 합 배열을 이용하여 구간 합 역시 쉽게 구할 수 있다. i에서 j까지 구간 합을 구하는 공식은 다음과 같다.

구간 합을 구하는 공식 (i에서 j까지 구간 합)

  • S[j] - S[i-1]

이쯤 되면 구간 합 구하는 공식은 이해가 되니 자세한 설명은 생략하자.


문제 003 구간 합 구하기 1

문제
수 N개가 주어졌을 때, i번째 수부터 j번째 수까지 합을 구하는 프로그램을 작성하시오.

입력
첫째 줄에 수의 개수 N과 합을 구해야 하는 횟수 M이 주어진다. 둘째 줄에는 N개의 수가 주어진다. 수는 1,000보다 작거나 같은 자연수이다. 셋째 줄부터 M개의 줄에는 합을 구해야 하는 구간 i와 j가 주어진다.

출력
총 M개의 줄에 입력으로 주어진 i번째 수부터 j번째 수까지 합을 출력한다.

제한
1 ≤ N ≤ 100,000
1 ≤ M ≤ 100,000
1 ≤ i ≤ j ≤ N

예제 입력 1
5 3
5 4 3 2 1
1 3
2 4
5 5

예제 출력 1
12
9
1


사실 이 문제를 처음 봤을 때 생각난 풀이법은 인덱스를 이용한 풀이법이었다. 직관적으로 필요한 구간만 끊어내어 계산하면 사실 답은 바로 구할 수 있다.

n, m = map(int, input().split())  
a = list(map(int, input().split()))
for _ in range(m):
    sum = 0
    s, e = map(int, input().split())   
    for i in range(s-1, e):       #인덱스를 이용해 구간을 바로 접근 
        sum += a[i]
    print(sum)

하지만 이런 방법으로 풀 경우 100,000 * 100,000 O(N^2)으로 연산되어 총 100억 번의 연산을 수행하게 된다. 따라서 문제의 제한 시간인 1초를 넘겨 풀이에 실패하게 된다. 그래서 이럴 때 시간 복잡도를 획기적으로 줄일 수 있는 알고리즘이 바로 배열 합이다. 다음은 배열 합으로 푼 코드이다.

n, m = map(int, input().split())
a = list(map(int, input().split()))
sum = [0]
tmp = 0
for x in a:
    tmp += x
    sum.append(tmp)   # 합 배열 만들기

for _ in range(m):
    s, e = map(int, input().split())
    res = sum[e] - sum[s-1] # 합 배열에서 구간 합 구하기
    print(res)

위와 같이 합 배열 알고리즘을 사용하면 알고리즘의 시간 복잡도를 줄일 수 있다. 하지만 배열 합은 N개 만큼의 메모리를 차지하기 때문에 시간과 용량의 트레이드 오프 관계를 가지고 있다. 따라서 용량의 최적화가 중요하다면 배열 합을 쓰는 것은 고려해봐야 할 것이다.

그렇다면 삽질은 어디서 했나?

import sys
input = sys.stdin.readline

백준에서 채점 받을 때 이 코드를 앞에 붙여주지 않으면 시간 초과가 난다,,, 이유는 다음에 알아보도록 하자...!

profile
안녕하세요. 반갑습니다.

0개의 댓글