이 포스팅은 Algorithm Problem Solving 시리즈 113 편 중 17 번째 글 입니다.

  • Part 1 - 백준(1003번): 피보나치 함수
  • Part 2 - 백준(1010번): 다리놓기
  • Part 3 - 백준(1012번): 유기농 배추
  • Part 4 - 백준(10159번): 저울
  • Part 5 - 백준(10164번): 격자상의 경로
  • Part 6 - 백준(1018번): 체스판 다시 칠하기
  • Part 7 - 백준(1018번): 체스판 다시 칠하기
  • Part 8 - 백준(1022번): 소용돌이 예쁘게 출력하기
  • Part 9 - 백준(10775번): 공항
  • Part 10 - 백준(10816번): 숫자 카드2
  • Part 11 - 백준(10816번): 숫자카드 2
  • Part 12 - 백준(10819번): 차이를 최대로
  • Part 13 - 백준(10830번): 행렬 곱셈
  • Part 14 - 백준(10844번): 쉬운 계단수
  • Part 15 - 백준(10868번): 최소값
  • Part 16 - 백준(1092번): 배
  • Part 17 - This Post
  • Part 18 - 백준(1102번): 발전소
  • Part 19 - 백준(11048번): 이동하기
  • Part 20 - 백준(11053번): 가장 긴 증가하는 부분 수열
  • Part 21 - 백준(11054번): 가장 긴 바이토닉 부분 수열
  • Part 22 - 백준(11055번): 가장 긴 증가하는 부분 수열2
  • Part 23 - 백준(11057번): 오르막 수
  • Part 24 - 백준(1120번): 문자열
  • Part 25 - 백준(11403번): 경로 찾기
  • Part 26 - 백준(11404번): 플루이드
  • Part 27 - 백준(1149번): RGB 거리
  • Part 28 - 백준(11559번): puyo puyo
  • Part 29 - 백준(11655번): ROT13
  • Part 30 - 백준(1167번): 트리의 지름
  • Part 31 - 백준(11722번): 가장 감소하는 부분 수열
  • Part 32 - 백준(12015번): 가장 긴 증가하는 부분 수열(LIS) 2
  • Part 33 - 백준(12851번): 숨바꼭질2
  • Part 34 - 백준(12852번): 1로 만들기 2
  • Part 35 - 백준(12865번): 평범한 배낭
  • Part 36 - 백준(1300번): K번째 수
  • Part 37 - 백준(1325번): 효율적인 해킹
  • Part 38 - 백준(13549번): 숨바꼭질3
  • Part 39 - 백준(13913번): 숨바꼭질4
  • Part 40 - 백준(14002번): 가장 긴 증가하는 부분 수열 4
  • Part 41 - 백준(1431번): 시리얼 넘버
  • Part 42 - 백준(1436번): 영화감독 숌
  • Part 43 - 백준(14499번): 주사위 굴리기
  • Part 44 - 백준(14888번): 연산자 끼워넣기
  • Part 45 - 백준(14889번): 스타트와 링크
  • Part 46 - 백준(14891번): 톱니바퀴
  • Part 47 - 백준(15658번): 연산자 끼워넣기 2
  • Part 48 - 백준(15686번): 치킨 배달
  • Part 49 - 백준(1697번): 숨바꼭질
  • Part 50 - 백준(1697번): 숨바꼭질
  • Part 51 - 백준(1707번): 이분 그래프(Bipartite Graph)
  • Part 52 - 백준(1708번): 볼록 껍질
  • Part 53 - 백준(17136번): 색종이 붙이기
  • Part 54 - 백준(1717번): 집합의 표현
  • Part 55 - 백준(17298번): 오큰수
  • Part 56 - 백준(17626번): Four Squares
  • Part 57 - 백준(18870번): 좌표 압축
  • Part 58 - 백준(1890번): 점프
  • Part 59 - 백준(1918번): 후위 표기식
  • Part 60 - 백준(1929번): 소수 구하기
  • Part 61 - 백준(1963번): 소수 경로
  • Part 62 - 백준(1965번): 상자넣기
  • Part 63 - 백준(1976번): 여행가자
  • Part 64 - 백준(1987번): 알파벳
  • Part 65 - 백준(1992번): 쿼드트리
  • Part 66 - 백준(2003번): 수들의 합2
  • Part 67 - 백준(20040번): 사이클 게임
  • Part 68 - 백준(2042번): 구간 합 구하기
  • Part 69 - 백준(2108번): 통계학
  • Part 70 - 백준(2110번): 공유기 설치
  • Part 71 - 백준(2156번): 포도주 시식
  • Part 72 - 백준(2193번): 이친수
  • Part 73 - 백준(2231번): 분해합
  • Part 74 - 백준(2250번): 트리의 높이와 너비
  • Part 75 - 백준(2293번): 동전 1
  • Part 76 - 백준(2294번): 동전 2
  • Part 77 - 백준(2343번): 기타 레슨
  • Part 78 - 백준(2468번): 안전 영역
  • Part 79 - 백준(2512번): 예산
  • Part 80 - 백준(2529번): 부등호
  • Part 81 - 백준(2565번): 전깃줄
  • Part 82 - 백준(2581번): 소수
  • Part 83 - 백준(2583번): 영역구하기
  • Part 84 - 백준(2630번): 색종이 만들기
  • Part 85 - 백준(2644번): 촌수계산
  • Part 86 - 백준(2667번): 단지번호붙이기
  • Part 87 - 백준(2751번): 수 정렬하기 2
  • Part 88 - 백준(2798번): 블랙잭
  • Part 89 - 백준(2904번): 수학은 너무 쉬워
  • Part 90 - 백준(2986번): 파스칼
  • Part 91 - 백준(3015번): 오아시스 재결합
  • Part 92 - 백준(3190번): 뱀
  • Part 93 - 백준(3190번): 벽 부수고 이동하기
  • Part 94 - 백준(4195번): 친구 네트워크
  • Part 95 - 백준(4948번): 베르트랑 공준
  • Part 96 - 백준(4963번): 섬의 개수
  • Part 97 - 백준(4991번): 로봇 청소기
  • Part 98 - 백준(5373번): 큐빙
  • Part 99 - 백준(5437번): 불
  • Part 100 - 백준(6171번): 땅따먹기
  • Part 101 - 백준(6603번): 로또
  • Part 102 - 백준(6603번): 로또
  • Part 103 - 백준(7562번): 나이트의 이동
  • Part 104 - 백준(7568번): 덩치
  • Part 105 - 백준(7576번): 토마토
  • Part 106 - 백준(7579번): 앱
  • Part 107 - 백준(7785번): 회사에 있는 사람
  • Part 108 - 백준(9012번): 괄호
  • Part 109 - 백준(9020번): 골드바흐의 추측
  • Part 110 - 백준(9184번): 신나는 함수 실행
  • Part 111 - 백준(9251번): LCS
  • Part 112 - 백준(9421번): 소수상근수
  • Part 113 - 프로그래머스: 가장 큰 정사각형 찾기
▼ 목록 보기

목차

▼ 내리기

플래티넘5 : 슬라이딩 윈도우 문제이다.

생각

N이 500만이라 세그먼트 트리로 풀면 터진다.

  • 세그먼트 트리를 만드는데 드는 시간
    • nlogn = 대략 22 * 5000000 = 110000000
  • 쿼리를 보는데 드는 시간
    • nlogn
  • 2nlogn

그런데 재귀이기 때문에 간당간당하다. 실제로 재귀로 풀면 터지고 비재귀로 풀면 들어온다고 한다. 해당 문제는 세그먼트 트리보다 간단한 구현을 원하는 듯하다.

문제는 간단하다. N개의 수가 들어왔을 때, L의 윈도우에서 최솟값을 차례대로 구하면 된다. 이 때 문제는, 10개의 원소가 있을 때 L이 3이라 가정하면, 순서대로 이 윈도우에 해당하는 최솟값을 구하면 되는데, 계속해서 다음 윈도우와 겹치는 부분이 발생한다는 것이다. 결국 중복이 발생하고, 이부분에서 오래 걸린다.

문제를 해결하는 방법은 그리디이다. 즉, 윈도우 내에서 최솟값을 구하는 데에는 어떠한 정답이 존재한다.

Sliding Window

input 배열, deque, result 세개의 구조가 필요하다.

위 동영상은 sliding window를 통해 max 값을 찾는 방법을 소개하고 있다. 이 방법의 핵심은 sliding window에서 감소하는 부분 수열을 찾는 것이다. 답은 감소하는 부분 수열에서 나올 수 밖에 없다. 내가 이 수열을 만들 수 있다면, 그 수열에서 가장 첫번째 값이 무조건 답이다.

그렇다면 고민해야 하는 부분은, window가 이동할 때, 어떤식으로 업데이트가 이루어져야 하냐는 점이다. 감소하는 부분 수열을 만들기 위해서 우리가 고려해야 하는 부분은 감소하는 부분 수열의 양단이다.

image

입력이 8, 6, 2가 들어왔을 때를 생각해보자. 첫번째 window에서 최대 감소 수열을 구하면 위와 같다.

image

이 상황에서 다음 window로 넘어가는 과정에, 2보다 작은 수가 들어왔다고 생각해보자. 그러면 맨 앞에 있는 8을 빼고, 1을 그냥 뒤에 추가하면 된다. 그리고 이 윈도우에서 최댓값은 여전히 맨 앞에 있는 요소이다.

image

그런데 같은 상황에서 이번에는 5가 들어왔다고 생각해보자. 그렇다면 이 window에서 최대 감소 수열은 변경된다. 맨 앞에 있는 요소는 여전히 빠지지만, window 내의 감소 수열은 위와 같이 변경되야 한다. 여전히 최댓값은 맨 앞의 요소이다.

Pop을 하는 과정에서 중요한 것이 있다. 무조건적으로 맨 앞에 있는 요소를 빼면 안된다. deq에 들어가 있는 것은 최대 감소수열을 가리키는 index이다. window가 이동하면서 제거되는 index가 deq에 들어가있는 첫번째 요소의 index와 같을 때 deq안의 원소를 지워줘야 한다. 그렇지 않을 경우 해당 요소는 여전히 넘어가는 window안에 있기 때문에 지워지면 안되는 값이다.

결론

  1. window내에서 최대 감소 수열을 만들었을 때 최댓값은 맨 앞 요소이다.
  2. window가 이동 할 때, 업데이트 방식이 존재한다.
  3. 이런 방법을 사용했을 때, 시간 복잡도는 N이다.

이런 방법을 사용하면 결국 한번의 탐색과정과 동시에 모든 윈도우에서 최댓값을 구할 수 있다. 그렇다면 최솟값 찾기에 이 방법을 도입해보자.

Code

#include<iostream>
#include<vector>
#include<algorithm>
#include<list>
#include<cmath>
using namespace std;
typedef long long ll;

int N, L;
vector<int> v;
list<int> deq;

void pop_front(int index){
    // 증가하는 수열의 맨 첫번째가 지금 윈도우의 시작점과 같다면 이동하면서 빼준다.
    // 만약 아니라면 그 값을 빼주면 안된다!
    if (!deq.empty() && deq.front() == index) {
        deq.pop_front();
    }
}

void push_back(int index){
    // 현재 새로들어온 index보다 작은 이전의 녀석들은 증가하는 수열에서 제거해준다.
    while (!deq.empty() && v[deq.back()] > v[index]) {
        deq.pop_back();
    }
    deq.push_back(index);
}


int main(){
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
    cin >> N >> L;
    v.resize(N+1);
    for (int i = 1; i <= N; i++) cin >> v[i];
    for (int i = 1; i <= N; i++) {
        push_back(i);
        cout << v[deq.front()] << ' ';
        // 시작할 때 window size보다 작은 index는 pop을 하지않고 넣어준다.
        if (i - L >= 0) {
            pop_front(i - L + 1);
        }
    }
}

Reference

백준(11003번) - 최솟값 찾기