이진탐색을 사용하기 위한 조건 : 정렬되어 있어야 한다.

찾으려는 데이터와 중간점(middle)위치에 있는 데이터를 반복적으로 비교해서 원하는 데이터를 찾는다.

시간 복잡도 : O(log(N))

 

1) 재귀를 이용한 구현

#include <iostream>
#include <vector>

using namespace std;

int n, target;
vector<int> v;

int binarySearch(int s, int e, int t) {
    if (s > e)
        return -1;

    int mid = (s + e) / 2;
    if (v[mid] == t) {
        return mid;
    }
    else if (v[mid] < t) {
        return binarySearch(mid+1, e, t);
    }
    else {
        return binarySearch(s, mid-1, t);
    }
}

int main(void) {
    cin >> n >> target;

    for (int i = 0; i < n; i++) {
        int a;
        cin >> a;
        v.push_back(a);
    }

    cout << binarySearch(0, n-1, target) + 1 << "\n";

    return 0 ;
}

2) while문을 이용한 구현

#include <iostream>
#include <vector>

using namespace std;

int n, target;
vector<int> v;

int binarySearch(int t) {
    int s = 0;
    int e = v.size() - 1;

    while (s <= e) {
        int mid = (s + e) / 2;
        if (v[mid] == t) {
            return mid;
        }
        else if (v[mid] < t) {
            s = mid+1;
        }
        else {
            e = mid - 1;
        }
    }

    return - 1;
}
int main(void) {
    cin >> n >> target;

    for (int i = 0; i < n; i++) {
        int a;
        cin >> a;
        v.push_back(a);
    }

    cout << binarySearch(target) + 1 << "\n";

    return 0 ;
}

나의 경우 2번이 편하다.

1번, 2번 아무거나 사용하여도 상관 없다!

 

파라메트릭 서치(Parametric Search)

  • 최적화 문제(문제의 상황을 만족하는 특정 변수의 최솟값, 최댓값을 구하는 문제)를 결정 문제(decision problem)로 바꾸어 해결하는 기법
  • 예를 들어 범위 내에서 조건을 만족하는 가장 큰 값을 찾으라는 최적화 문제라면 이분 탐색으로 결정 문제를 해결하면서 범위를 좁혀갈 수 있다.

Lower Bound, Upper Bound

  • 이진 탐색으로 돌음
  • countByRange 처럼 정렬된 배열에서 a~b까지의 숫자의 개수를 O(logN)으로 찾고자 할때 주로 사용한다.

Lower Bound

  • 키값보다 크거나 같은 첫번째 값
  • 찾으려고 하는 key 값이 없으면, key 값 초과인 값들 중, 가장 작은 값

Upper Bound

  • 키값보다 큰 첫번째 값
  • 최댓값보다 더 큰 값을 찾고자 하는 경우에는, 마지막 요소 다음 메모리를 가리키는 iterator(end()와 같은)를 반환한다. 따라서, 이 iterator에 접근하면 쓰레기값을 반환.

직접 구현할 경우는 기존 이진 탐색에서 값을 찾았을 경우 left를 이동할지 right를 이동할지를 생각해 로직을 수정하면된다.

또한 증명하는 방법은 이진탐색은  l~r의 크기가 1 또는 2일 경우를 결국은 지나므로 해당하는 경우를 모두생각해보면 편하다.

 

1) 직접 구현

#include <iostream>
#include <vector>

using namespace std;

int n, key;
vector<int> v;

// Key 보다 크거나 같은 첫번째 값 반환
int lowerBound() {
    int l = 0, r = n-1;

    while (l <= r) {
        int mid = (l + r) / 2;
        if (v[mid] < key) {
            l = mid + 1;
        }
        else {
            r = mid - 1;
        }
    }

    return l;
}

// Key보다 큰 첫번째 값
int upperBound() {
    int l = 0, r = n-1;

    while (l <= r) {
        int mid = (l + r) / 2;
        if ( v[mid] <= key) {
            l = mid + 1;
        }
        else {
            r = mid - 1;
        }
    }

    return l;
}

// 값이 [left, right]인 데이터의 개수를 반환하는 함수
int countByRange() {
    int left = lowerBound();
    int right = upperBound();

    return right - left;
}

int main(void) {
    cin >> n >> key;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        v.push_back(x);
    }

    int cnt = countByRange();
    if (cnt == 0) {
        cout << -1 << "\n";
    }
    else {
        cout << cnt << "\n";
    }

    return 0 ;
}

2) STL

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

using namespace std;

int n, key;
vector<int> v;

// 값이 [left, right]인 데이터의 개수를 반환하는 함수
int countByRange() {
    // Key 보다 크거나 같은 첫번째 값 반환
    vector<int>::iterator left = lower_bound(v.begin(), v.end(), key);
    // Key보다 큰 첫번째 값
    vector<int>::iterator right = upper_bound(v.begin(), v.end(), key);

    return right - left;
}

int main(void) {
    cin >> n >> key;
    for (int i = 0; i < n; i++) {
        int t;
        cin >> t;
        v.push_back(t);
    }

    int cnt = countByRange();
    if (cnt == 0) {
        cout << -1 << "\n"; 
    }
    else {
        cout << cnt << "\n";
    }

    return 0 ;
}

문제

편의점 주인인 동빈이는 N개의 동전을 가지고 있다.
N개의 동전을 이용하여 만들 수 없는 양의 정수 금액 중 최솟값을 구하라.

예를 들어,

N = 5이고, 각 동전이 각각 3원, 2원, 1원, 1원, 9원짜리 동전이라고 가정하자. 이때 동빈이가 만들 수 없는 양의 정수 금액 중 최솟값은 8원이다.

N = 3이고 각 동전이 각각 3원, 5원, 7원이면, 만들 수 없는 양의 정수 금액 중 최솟값은 1원이다.

입력 

  • 첫째 줄에는 동전의 개수를 나타내는 양의 정수 N이 주어진다. ( 1 <= N 1,000)
  • 둘째 줄에는 각 동전의 화폐 단위를 나타내는 N개의 자연수가 주어지며 각 자연수는 공백으로 구분된다. 각 화폐 단위는 1,000,000 이하의 자연수이다.

출력 

  • 첫째 줄에 주어진 동전들로 만들 수 없는 양의 정수 금액 중 최솟값을 출력한다.

입력 예시

입력


3 2 1 1 9
8

느낀점

이 문제는 처음 보면 그리디 알고리즘이라고 생각하기 힘든 문제였다.

하지만 알고나면 너무 쉬운 문제다.

하나의 유형이라고 생각하고 외워두면 좋을 것 같다.

 

문제를 풀어내는 사고방식은 다음과 같다.

작은 동전부터 탐색하면서, 만들고자 하는 금액(target)을 아직 이용하지 않고 가장 작은 금액의 동전이 만들고자 하는 금액 이하이면 해당 금액을 그 전에 이용한 동전과 조합해 만들 수 있다.

그 전에 이용했던 동전들로 1~target-1의 금액을 만들 수 있고 이용하지 않은 가장 작은 금액의 동전이 target 보다 작다면
     이용하지 않은 가장 작은 금액의 동전 ( <= target) + (1~target-1)  target

이 성립한다.

따라서 동전들을 정렬한 후, 하나씩 뽑으면서 다음 target을 업데이트해주면 된다.

 

#include <iostream>
#include <vector>

using namespace std;

int n;

vector<int> v;
int main(void) {
    cin >> n;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        v.push_back(x);
    }

    // 1. 정렬
    sort(v.begin(), v.end());

    // 2. 첫번째 target은 1, 이용하기 전의 가장 작은 동전이 target이하여야 target을 만들 수 있다.
    int target = 1;
    for (int i = 0; i < n; i++) {
        if (v[i] <= target) {
            target += v[i];
        }
        else {
            // 만들 수 없는 금액 종료
            break;
        }
    }

    cout << target << "\n";

    return 0 ;
}

정렬에는 여러가지 방식이 있고 다음 4가지 정렬만 우선 다루려고 한다.

설명은 오름차순 기준으로 진행하므로 내림차순은 반대로 생각하면 된다.

 

1. 선택정렬(Selection Sort)

    - O(N^2)

    - k번째 작은 데이터를 가져와 k번째 위치에 놓는다.

#include <iostream>
#include <vector>

using namespace std;

vector<int> arr {7, 5, 9, 0, 3, 1, 6, 2, 4, 8};

int main(void) {

    // 삽입정렬
    for (int i = 0; i < arr.size(); i++) {
        for (int j = i; j > 0; j--) {
            if (arr[j] < arr[j-1]) {
                swap(arr[j], arr[j-1]);
            }
        }
    }

    for (int i = 0; i < arr.size(); i++) {
        cout << arr[i] << " ";
    }
    cout << "\n";
    return 0 ;
}

2. 삽입정렬(Insertion Sort)

    - O(N^2)

    - 데이터를 하나씩 확인하며, 각 데이터를 적절한 위치에 삽입한다.

#include <iostream>
#include <vector>

using namespace std;

vector<int> arr {7, 5, 9, 0, 3, 1, 6, 2, 4, 8};

int main(void) {

    // 선택정렬
    for (int i = 0; i < arr.size(); i++) {
        int min_index = i;
        for (int j = i+1; j < arr.size(); j++) {
            if (arr[min_index] > arr[j]) {
                min_index = j;
            }
        }
        swap(arr[min_index], arr[i]);
    }

    for (int i = 0; i < arr.size(); i++) {
        cout << arr[i] << " ";
    }
    cout << "\n";
    return 0 ;
}

3. 퀵정렬(Quick Sort)

    - O(N^2)

    - 호어 분할 방식 : 리스트에서 첫번째 데이터를 pivot으로 정한다. 이외에도 많은 pivot의 기준을 잡는 방식은 많다.

    1) pivot을 기준으로 왼쪽에서부터 pivot보다 큰 데이터를 찾고  오른쪽부터 pivot보다 작은 데이터를 찾아 교환한다.

    2) 1) 을 반복하다 작은 데이터와 큰 데이터가 바뀌면 작은 데이터와 pivot을 바꾼다.

    3) pivot으로 분할된 왼쪽 리스트와 오른쪽 리스트에 대해 각각 1), 2)를 반복수행한다.

   *C++ sort의 경우 퀵 정렬을 기반으로 하지만 피벗값을 설정할 때 추가적인 로직을 더해 O(NlogN)을 보장한다.

#include <iostream>
#include <vector>

using namespace std;

vector<int> arr {7, 5, 9, 0, 3, 1, 6, 2, 4, 8};

void quickSort(int start, int end) {
    // 원소가 한개인 경우 종료
    if (start >= end) {
        return;
    }

    // 호어 분할 방식 : pivot은 첫번째 원소
    int pivot = start;
    int left = pivot+1;
    int right = end;

    while (left <= right) {
        // pivot보다 큰 데이터를 찾을 때까지 반복 (배열을 초과하지 않을 정도로만)
        while (left <= end && arr[left] <= arr[pivot]) {
            left++;
        }
        // pivot보다 작은 데이터를 찾을 때까지 반복 (pivot index보다 커야함)
        while (right > start && arr[right] >= arr[pivot]) {
            right--;
        }
        // 엇갈렸다면 작은 데이터와 pivot을 교체
        if (left > right) {
            swap(arr[right], arr[pivot]);
        }
        // 엇갈리지 않았다면 큰 데이터와 작은 데이터를 교체
        else {
            swap(arr[left], arr[right]);
        }
    }

    // 분할 후, 왼쪽 리스트와 오른쪽 리스트에서 각각 반복수행
    quickSort(start, right-1);
    quickSort(right+1, end);

}

int main(void) {

    quickSort(0, arr.size()-1);

    for (int i = 0; i < arr.size(); i++) {
        cout << arr[i] << " ";
    }
    cout << "\n";
    return 0 ;
}

계수 정렬(Counting Sort)

  • O(N+K)
  • 정렬된 리스트에 가장 작은 데이터부터 큰 데이터까지의 빈도를 세서 저장해주는 것이다. 말 그대로 count다.
  • 가장 큰 데이터와 가장 작은 데이터의 차이가 1,000,000을 넘지 않을 때 효과적이다. (이를 넘어가면 공간 복잡도를 초과한다.)
  • 즉, 시간 복잡도를 위해 공간 복잡도를 희생한다.
  • 말도 안되게 빠른 정렬을 요하는 문제는 무조건 계수정렬이다.
#include <iostream>
#include <vector>

#define MAX_VALUE 9

using namespace std;

int n = 15;
// 모든 원소의 값이 0보다 크거나 같다고 가정
int arr[15] = {7, 5, 9, 0, 3, 1, 6, 2, 9, 1, 4, 8, 0, 5, 2};
// 모든 범위를 포함하는 배열 선언(모든 값은 0으로 초기화)
int cnt[MAX_VALUE + 1];

int main(void) {
    for (int i = 0; i < n; i++) {
        cnt[arr[i]] += 1; // 각 데이터에 해당하는 인덱스의 값 증가
    }
    for (int i = 0; i <= MAX_VALUE; i++) { // 배열에 기록된 정렬 정보 확인
        for (int j = 0; j < cnt[i]; j++) {
            cout << i << ' '; // 띄어쓰기를 기준으로 등장한 횟수만큼 인덱스 출력
        }
    }
}

'알고리즘' 카테고리의 다른 글

[Algorithm] 그리디 알고리즘  (0) 2022.02.05
  DFS BFS
동작원리 스택
구현방법 *재귀함수 이용 큐 자료구조 이용
시간복잡도 O(N) O(N)

*재귀함수 : 자기 자신을 다시 호출하는 함수, 종료조건을 반드시 명시해야 한다.

 

DFS 동작과정

  1. 탐색 시작 노드를 스택에 삽입하고 방문 처리를 한다.
  2. 스택의 최상단 노드에 방문하지 않은 인접 노드가 있으면 그 인접 노드를 스택에 넣고 방문 처리를 한다. 방문하지 않은 인접 노드가 없으면 스택에서 최상단 노드를 꺼낸다. (방문처리 순서가 중요하다. 스택에 삽입되면서 준비상태가 되면 방문처리가 되어야 한다.)
  3. 2번 과정을 더 이상 수행할 수 없을 때까지 반복한다.

    - 재귀함수 자체가 스택형식으로 작동한다. (함수호출 - 스택에 삽입, 함수 종료 - 스택에서 제거)

    - 즉, DFS는 재귀함수를 이용하면 스택을 따로 선언하여 사용할 필요가 없다. 따라서 BFS보다 구현이 간편하다.

 

#include <iostream>
#include <vector>

using namespace std;

vector<vector<int> > graph = {
    vector<int> {},
    vector<int> {2, 3, 8},
    vector<int> {1, 7},
    vector<int> {1, 4, 5},
    vector<int> {3, 5},
    vector<int> {3, 4},
    vector<int> {7},
    vector<int> {2, 6, 8},
    vector<int> {1, 7}
};
bool visited[101];

// DFS 함수 정의
// 탐색 시작 노드 스택 삽입
void dfs(int now) {
    // 현재 노드를 방문 처리
    visited[now] = true;
    cout << now << " ";
    // 현재 노드의 인접 노드 중 방문하지 않은 노드 재귀적으로 방문
    for (int next : graph[now]) {
        if (!visited[next]) {
            dfs(next);
        }
    }
// 현재(최상단) 노드 스택에서 꺼내기
}
int main(void) {

    dfs(1);
    cout << "\n";

    return 0 ;
}

 

BFS 동작과정

  1. 탐색 시작 노드를 큐에 삽입하고 방문 처리를 한다.
  2. 큐에서 노드를 꺼내 해당 노드의 인접 노드 중에서 방문하지 않은 노드를 전부 큐에 삽입하고 방문처리를 한다. (방문처리 순서가 중요하다. 큐에 삽입되면서 준비상태가 되면 방문처리가 되어야 한다.)
  3. 2번 과정을 더 이상 수행할 수 없을 때까지 반복한다.

    - 일반적인 경우 BFS가 DFS보다 성능이 좋다.

 

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

vector<vector<int> > graph = {
    vector<int> {},
    vector<int> {2, 3, 8},
    vector<int> {1, 7},
    vector<int> {1, 4, 5},
    vector<int> {3, 5},
    vector<int> {3, 4},
    vector<int> {7},
    vector<int> {2, 6, 8},
    vector<int> {1, 7}
};
bool visited[101];

// BFS 함수 정
void bfs(int start) {
    queue<int> q;

    // 탐색 시작 노드 큐에 삽입
    q.push(start);
    // 현재 노드를 방문 처리
    visited[start] = true;
    
    // 큐가 빌때까지 반복
    while(!q.empty()) {
        // 큐에서 원소 하나 뽑기
        int now = q.front();
        cout << now << " ";
        q.pop();

        // 해당 원소와 연결된, 아직 방문하지 않은 노 큐에 삽입 후 방문처리
        for (int next : graph[now]){
            if (!visited[next]) {
                q.push(next);
                visited[next] = true;
            }
        }
    }
}
int main(void) {

    bfs(1);
    cout << "\n";

    return 0 ;
}

 

결론

      트리의 깊이를 묻는 문제는 BFS를 사용하자. 둘 다 사용할 수 있어도 BFS를 쓰자.

 

그리디 알고리즘(Greedy algorithm) : 탐욕법, 현재 상황에서 지금 당장 좋은 것만 고르는 방법

 

그리디 알고리즘의 대표적인 예제는 거스름돈이다.

마트에 가서 현금으로 결제하면 직원은 우리에게 '가장 큰 화폐단위'부터 돈을 거슬러 준다.

그것이 가장 적은 화폐수로 거스름돈을 줄 수 있는 방법이기 때문이다.

 

정당성을 어떻게 보장하는가?

 

그리디 알고리즘의 정당성 : 그리디 알고리즘을 이용해도 '최적의 해'를 찾을 수 없는 경우가 많다.

 

거스름돈의 예에서, 어떤 화폐는 그것보다 작은 단위의 배수이므로 작은 단위의 동전들을 종합해 다른 해가 나올 수 없나는 사실이 정당성을 증명한다. 

우리나라 화폐는 10, 50, 100, 500, 1000, 5000, 10000, 50000이다. 즉 배수로 증가함을 확인할 수 있다.

반면에, 600원짜리 화폐가 등장한다면 그리디 알고리즘의 정당성을 충족하지 못한다.

 

이처럼 그리디 알고리즘의 문제에서는 문제풀이 아이디어를 그리디로 접근했다면 이것이 정당한지를 검토할 수 있어야 한다.

'알고리즘' 카테고리의 다른 글

[Algorithm] 정렬  (0) 2022.02.07

지도(그래프)에서 동서남북으로 움직이는 문제는 자주 출제되는 문제유형인 것 같다.

2차원 배열에서 상하좌우로 1칸씩 움직일 수 있고 배열의 범위를 초과하는 것을 다뤄야 할 경우가 많다.

그래서 2차원 배열 동서남북 움직이기를 템플릿으로 만들어 두면 좋을 것 같다.

 

다음은 R, L, U, D를 입력 받아 동서남북으로 움직이는 전형적인 문제의 답안이다.

움직이는 좌표를 설정하고 공간을 벗어나는 경우를 continue로 무시하여 깔끔하게 코드를 작성하는 법이라 생각한다.

#include <iostream>
#include <vector>

using namespace std;

int r = 1, c = 1;
int n;
string plans;

// 동, 서, 남, 북 방향
int dr[4] = {0, 0, 1, -1};
int dc[4] = {1, -1, 0, 0};
char move_types[4] = {'R', 'L', 'D', 'U'};

int main(void) {
    cin >> n;
    cin.ignore();
    getline(cin, plans);

    // 이동 계획을 하나씩 확인
    for (int i = 0; i < plans.size(); i++) {
        char plan = plans[i];

        // 이동 후 좌표 구하기
        int tr = r, tc = c;
        for (int j = 0; j < 4; j++) {
            if (plan == move_types[j]) {
                tr += dr[j];
                tc += dc[j];
            }
        }

        // 공간을 벗어나는 경우 무시
        if (tr < 1 || tr > n || tc < 1 || tc > n) {
            continue;
        }

        r = tr;
        c = tc;
    }

    cout << r << " " << c << "\n";

    return 0;
}

 

C++에서는 순열과 조합을 각각 next_permutation, prev_permutation을 이용해 쉽게 구할 수 있다.

next_permutation

C++의 algorithm 헤더에는 n개의 원소의 순열을 구할 수 있는 next_permutation이라는 함수가 있습니다. 기본적 문법은 다음과 같습니다. 

 

1
2
3
4
5
6
7
// default
bool next_permutation (BidirectionalIterator first,
                         BidirectionalIterator last);
 
// custom
bool next_permutation (BidirectionalIterator first,
                         BidirectionalIterator last, Compare comp);
cs

 

next_permutation은 순열을 구할 컨테이너(쉽게 말해 배열)의 시작과 끝 iterator를 인자로 받습니다. 만약 해당 컨테이너에 다음 순열이 존재하면 그 컨테이너의 원소를 해당 순열 순서로 바꾸고 true를 반환하고, 다음 순열이 없다면 false를 반환합니다. 위 코드의 custom에서 볼 수 있듯이 비교 함수 comp를 인자로 넘기는 것도 가능합니다. 

 

 

 

next_permutation을 사용할 때는 몇 가지 주의할 점이 있습니다. 

  1. 오름차순으로 정렬된 값을 가진 컨테이너로만 사용가능합니다.
  2. default로 operator < 를 사용해 순열을 생성합니다. 즉 오름차순으로 순열을 생성합니다. 
  3. 중복이 있는 원소들은 중복을 제외하고 순열을 만들어줍니다. 

3번의 예시를 들자면 {0, 0, 1}과 같은 배열의 순열을 구한다면 중복을 제외한

{0, 0, 1}, {0, 1, 0}, {1, 0, 0}이 됩니다. 이를 이용해 조합(Combination)을 구할 수 있습니다. 


출처: https://mjmjmj98.tistory.com/38 [Live passionate😎]

 

<순열 - 중복 포함하지 않음> 

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

using namespace std;

vector<int> v{2, 1, 3};

int main(void) {

    // 1. next_permutation 사용을 위한 v 오름차순 정렬
    sort(v.begin(), v.end());

    // 2. 오름차순으로 정렬된 순열 출력
    do {
        for (auto it = v.begin(); it != v.end(); it++) {
            cout << *it << " ";
        }
        cout << endl;
    }
    while (next_permutation(v.begin(), v.end()));

    return 0 ;
}

<조합 - 중복 포함하지 않음>

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

using namespace std;

vector<int> v{2, 1, 3};
vector<int> t{1, 1, 0};

int main(void) {

    // 1. prev_permutation 사용을 위한 t 내림차순 정렬
    sort(t.begin(), t.end(), greater<int>());

    // 2. 내림차순으로 정렬된 데이터를 받아서 순열로 뽑고 그 부분만 v에서 뽑아내 조합으로 만들기
    do {
        for (int i = 0; i < v.size(); i++) {
            if (t[i]) {
                cout << v[i] << " ";
            }
        }
        cout << endl;
    }
    while (prev_permutation(t.begin(), t.end()));

    return 0 ;
}

do-while은 쓰면 안 좋다지만, 위의 경우는 예외로 쓰도록 하자.

참고로, next_permutation, prev_permutation 함수는 터미널에서는 기본 제공되지만, 백준과 같은 사이트에서 사용하려면

#include <algorithm>을 해줘야 한다.

 

시간복잡도

 - next_permutation 자체가 O(n)이고 nPr 자체는 O(n!) 이므로 위 템플릿처럼 순열 조합을 구현하면 O(n*n!)이 된다.

Prefix Sum : 여러 개의 누적합, 구간합을 묻는 쿼리에 대해 효율적인 알고리즘

  • 누적합 : [0, Right] 구간의 합
  • 구간합 : [Left, Right] 구간의 합

하나의 구간합을 구하는 것이 O(N)이고 구간합을 구하라는 M개의 쿼리가 들어온다면 시간복잡도는 O(NM)이다.

N = 1,000,000 이고 M = 1,000,000이라면 시간복잡도가 너무 안좋다.

이런 경우 사용하는 것이 Prefix Sum 이다.

누적합을 구해놓고 누적합 간에 -연산을 하면 구간합이기 때문이다.

 

- 시간복잡도 : O(N+M)

- 동작과정

  1) N개의 수에 대하여 접두사 합(Prefix Sum)을 계산하여 배열 P에 저장한다.

  2) 매 M개의 쿼리 정보 [L, R]을 확인할 때 구간 합은 P[R]-P[L-1]이다.

  ▪️ 이때, P[0] 에 0을 넣고 L >= 1 을 만들면 코드가 간결해진다.

 

#include <iostream>
#include <vector>

using namespace std;

int n = 5;
vector<int> data = {10, 20, 30, 40, 50};
vector<int> prefix_sum;

int main(void) {
    // 첫 배열 0 대입
    prefix_sum.push_back(0);

    int sum_value = 0;
    for (auto d : data) {
        sum_value += d;
        prefix_sum.push_back(sum_value);
    }

    int left = 3;
    int right = 4;
    cout << prefix_sum[right] - prefix_sum[left-1] << endl;


    return 0 ;
}

 

다만 위 코드는 90도 회전할 때마다 vector를 선언해 heap영역에 계속 쌓이게 된다.

여러번 돌리면 메모리 관점에서 좋지 않다.

그렇기 때문에 4번이 넘어가는 회전은 4로 나눈 나머지만 회전하는 등 사용에 주의가 필요하다.

+ Recent posts