알고리즘 문제 유형(부분집합)

알고리즘 문제 유형(부분집합)

부분집합의 개수를 구해보자

본 알고리즘 유형은 C++를 이용하여 해결한다.

이 글을 쓰게 된 이유는 간혹 브루트 포스 문제를 풀거나 정렬 문제를 풀 때 부분집합 의 개수를 묻는 문제가 간혹 있었는데 그럴 때마다 항상 어떻게 구해야되지 라는 생각이 들어 아예 정리하기 위해 쓰고 있다.

문제 설명

문제는 이런식으로 주어진다.

아무개는 랜덤값 K 를 받아 배열 A 에서 원소가 K개만큼 들어있는 부분집합의 총 개수를 구하려고 합니다. [1,2] 나 [2,1] 은 같은 부분집합으로 구분됩니다. 구해보세요~

예제

arrayKreturn
[1,2,3]2[1,2], [1,3], [2,3]
[1,8,8,8]3[1,8,8], [8,8,8]

문제 해결

노가다 버전 : for문을 이용한 해결법

시간복잡도 면에서 최악의 해결법이다. 같이 코드를 보자


vector<vector<int>> solution(vector<int>& array, int K) {
    //이 코드에서는 K가 4이라고 가정하겠습니다.

    vector<vector<int>> ret;
    vector<int> temp(4,0);
    int n = array.size();

    for(int i=0;i<n;++i)
        for(int j=i+1;j<<n;++j)
            for(int k=j+1;k<n;++k)
                for(int l=k+1;l<n;++l){
                    temp = {i, j, k, l};
                    ret.push_back(temp);
                }

    return ret;
}

이 코드는 부분집합들을 2차원 배열로 리턴해준다. 다만 이 방법은 리스크를 감당하기에는 너무 비효율적이다.

  • 중첩된 for문
  • 만약 K값이 4가 아니라 5라면 for문을 그에 맞게 늘려야되는 부분

위와 같은 사유로 안 쓰는 게 나은 해결법이다. (다만 K값이 고정되어 있는 경우 다른 해결법이 도무지 생각나지 않는다면 이 방법도 괜찮다. 단 K가 작아야 됨)

똑똑한 방법 : 재귀함수

이 방법은 앞서 소개한 방법에서 for문을 하나를 함수로 떼어낸 것이다. 이 방법은 최악의 해결법에서 2번째 단점을 해결해준다.

vector<int> ret;

void solution(int n, vector<int> &picked, int K) { // n은 원소의 개수

    if(K == 0) return;

    // 고를 수 있는 가장 작은 원소 하나를 고른다.
    int smallest = ret.empty() ? 0 : ret.back() + 1;
    
    //아래 단계에서 원소 하나를 고른다.
    for(int next = smallest; next < n; ++next) {
        ret.push_back(next);
        ret(picked, K-1);
        ret.pop_back();
    }
}

위와 같은 방법으로 첫 번째 방법에서 K값에 따른 그에 비례하는 for문을 줄일 수 있다. 다만 아쉽게 전역변수를 써야하지만 오로지 코테용으로만 쓸 거면 나쁘지 않은 선택이다. 오늘은 간단하게 c++ 을 이용하여 부분집합을 구하는 방법을 알아봤다. 부분집합 문제는 가끔씩 보이는 문제인데 항상 아이디어가 생각나지 않았다. 이 글을 통해 다들 코테 공부를 열심히 하길 바란다.