아래 글은 오늘 강의를 들은 자료구조/알고리즘 수업의 내용과 내가 푼 방식, 강사님이 푼 방식이다. 문제의 저작권 때문에 간략하게 정리해서 올리는 점 참고해 주시기 바란다.

[재귀함수, DFS, BFS]

재귀함수를 이용한 이진수 출력

10진수 N이 입력되었을 시, 재귀를 이용하여 2진수로 변환하여라

📌 내가 푼 방법

function solution(N) {
    if(N === 0) {
        return '';
    }
    else {
        return solution(parseInt(N/2), 10) + String(N%2);
    }
}

console.log(solution(11));

재귀로 리턴을 해가면서 풂.

📌 강사님 방법

function solution(n) {
    let answer = 0, tmp = [];
    function DFS(n) {
        if(n===0) return;
        else {
            DFS(parseInt(n/2));
            tmp.push(n%2);
        }
    }
    DFS(n);
    for(let i =0; i<tmp.length; i++) {
        answer=answer*10+tmp[i];
    }
    return answer
}

console.log(solution(11));

재귀를 통해 나눈 값을 tmp값에 넣고 다시 for문을 통해 answer에 곱하기 10을 통해 숫자 자리를 맞추면서 값을 넣어줌


이진트리 순회(깊이우선탐색: DFS)

이진트리의 전위, 중위, 후위 순회를 하여라

📌 내가 푼 방법

function solution(n) {
    let answer = "";
    function DFS(v) {
        if(v > 7) return;
        else {
            // answer+=(v+'');
            DFS(v*2);
            // answer+=(v+'');
            DFS(v*2+1);
            answer+=(v+'');
        }
    }
    DFS(n);
    return answer;
}
console.log(solution(1));

📌 강사님 방법

코드 같음


부분집합 구하기(DFS)

자연수 N이 주어지면 1부터 N까지의 원소를 갖는 집합의 부분집합을 모두 출력하여라

📌 내가 푼 방법

📌 강사님 방법

function solution(n){
    let answer=[];
    let part=[];
    function DFS(L){
        if(L===n+1){
            if(part.length!==0) answer.push(part.slice());
        }
        else{
            part.push(L);
            DFS(L+1);
            part.pop();
            DFS(L+1);
        }
    }
    DFS(1);
    return answer;
}

console.log(solution(3));

함수가 실행되는 순서를 스택과 트리로 그려보면 왼쪽으로 가면 그 숫자를 포함하는 숫자이고 오른쪽으로 가면 그 숫자를 포함하지 않는것! (트리로 보면 그 부분에 있는 값을 사용하겠다, 사용하지 않겠다라고 생각해서 풀면됨)
재귀 안에선 if~else문 무조건!!!


합이 같은 부분집합

N개의 원소로 구성된 자연수 집합이 주어지고, 두 개의 부분집합으로 나눌 때, 원소의 합이 서로 같은 경우가 존재하면 "YES"아니면 "NO"를 출력하여라

📌 내가 푼 방법

function solution(nums) {
    let answer = "NO";
    let n = nums.length-1;
    let part = []

    function DFS(N) {
        if(N > n) {
            if(part.length !== 0) {
                let comp1 = part.slice();
                let comp2 = nums.filter(x => !comp1.includes(x));
                let sum1 = 0;
                let sum2 = 0;
                for(let c1 of comp1) {
                    sum1 += c1;
                }
                for(let c2 of comp2) {
                    sum2 += c2;
                }
                if(sum1 === sum2) {
                    answer =  "YES";
                }
            }
        }
        else {
            part.push(nums[N]);
            DFS(N+1);
            part.pop();
            DFS(N+1);
        }
    }
    DFS(0);

    return answer;
}

console.log(solution([3, 9, 11, 13]));

위의 부분집합 구하기와 같이 만들 수 있는 집합을 모두 세어서 반대되는 집합을 만들어 그 집합과 만든 집합의 sum을 비교하였다.

📌 강사님 방법

function solution(nums) {
    let answer = "NO";
    flag = false;
    let total = nums.reduce((a, b) => a + b, 0); // 넘어온 nums의 총합 순회하면서 이 total값의 반이되는 sum이 있으면 정답!
    let N = nums.length;

    function DFS(L, sum) {
        if (flag) return; // flag가 true되면 찾았다는 의미이므로 나머지 스택에 있는 함수들을 다 처음에 종료시켜버림

        if (L === N) {
            if ((total - sum) === sum) {
                answer = "YES";
                flag = true;
            }
        } else {
            DFS(L + 1, sum + nums[L]); // 매개변수활용을 하여 시간복잡도를 줄이기
            DFS(L + 1, sum);
        }
    }
    DFS(0, 0);
    return answer;
}

console.log(solution([3, 9, 11, 13]));

그 원소를 사용할 것 인지, 사용하지 않을 것인지 트리로 생각을 해보면 된다. 그리고 sum을 활용하여 전체합을 같이 더하면서 내려가기!!


바둑이 승차(DFS)

바둑이의 무게가 배열로 주어지고 최대로 버틸 수 있는 무게 C가 주어졌을 때, 트럭에 태울 수 있는 가장 무거운 무게를 구하여라

📌 내가 푼 방법

📌 강사님 방법

function solution(nums, c) {
    let answer = 0;
    let total = nums.reduce((a, b) => a+b, 0);
    let N = nums.length;

    function DFS(L, sum, tsum) {
        if(sum > c) return;
        if(sum+(total-tsum) < answer) return;
        if(L === N) {
            answer = Math.max(answer, sum);
        } else {
            DFS(L+1, sum + nums[L], tsum+nums[L]);
            DFS(L+1, sum, tsum+nums[L]);
        }
    }
    DFS(0, 0, 0);
    return answer; 
}

console.log(solution([34, 56, 55, 67, 33, 76, 63, 43], 379));

위의 문제와 마찬가지로 DFS로 원소들을 돌면서 이 원소를 포함시킬 것인지 포함시키지 않을 것인지를 생각하면서 돌고, 그 중 무게 C를 넘지 않는 최대값을 찾아내면 된다.
sum > c로 그 아래 행을 실행시키지 않고 커트시켜주며, tsum을 이용해 더해주지 않은 것들 것 까지 생각을 해서 미리 커트를 생각해준다.
(total-tsum) <-- 앞으로 검사를 받아야할 나머지 바둑이들의 값들을 sum에다가 더해주었는데 불구하고 answer보다 작으면 굳이 해줄 필요없으므로 return으로 커팅!


최대점수 구하기(DFS)

풀었을 때 얻는 점수와 걸리는 시간이 nums 배열로 주어진다. 제한시간안에 M안에 얻을 수 있는 최대점수를 반환하여라

📌 내가 푼 방법

📌 강사님 방법

function solution(nums, m) {
    let answer = 0;
    let N = nums.length;

    function DFS(L, time, sum) {
        if(time > m) return;
        if(L === N) {
            answer = Math.max(answer, sum);
        } else {
            DFS(L+1, time + nums[L][1], sum+nums[L][0]);
            DFS(L+1, time, sum);
        }
    }
    DFS(0, 0, 0);
    return answer; 
}

console.log(solution([[10, 5], [25, 12], [15, 8], [6, 3], [7, 4]], 20));

마찬가지로 문제를 푼다 or 풀지 않는다 를 정해서 모든 경우의 수를 생각해서 풀면됨
또한 tsum을 통해서 위의 문제와 같이 커팅을 할 수 있음.
if(sum+(total-tsum) < answer) return; // 매개변수 4개해서 tsum추가하고 total을 위에 계산해두어서 사용하면 됨


중복순열 구하기

1부터 N까지 번호가 적힌 구슬을 중복을 허락해서 M번을 뽑아 만들 수 있는 경우의수를 출력하여라

📌 내가 푼 방법

📌 강사님 방법

function solution(n, m) {
    let answer = [], tmp = [];
    function DFS(L) {
        if (L === m) {
            answer.push(tmp.slice());
        } else {
            for(let i = 1; i<=n; i++) { // i가 뽑는 숫자, n번을 돌음
                tmp.push(i);
                DFS(L+1); // 종료지점을 위해
                tmp.pop();
            }
        }
    }
    DFS(0);
    return answer; 
}

console.log(solution(3, 2));

중복 순열을 구하기 위해 i를 1부터 n까지 돌려서 뽑고 DFS로 순회를 한다.

중복순열이 아닌 경우

function solution(n, m) {
    let answer = [], tmp = [];
    let ch = Array.from({length: n+1}, () => 0);
    function DFS(L) {
        if (L === m) {
            answer.push(tmp.slice());
        } else {
            for(let i = 1; i<=n; i++) { // i가 뽑는 숫자, n번을 돌음
                if(ch[i] === 0) {
                    ch[i] = 1;
                    tmp.push(i);
                    DFS(L+1); // 종료지점을 위해
                    tmp.pop();
                    ch[i] = 0;
                }
            }
        }
    }
    DFS(0);
    return answer; 
}

console.log(solution(3, 2));

다음과 같이 중복인지 체크를 해주는 변수 배열을 만들어 체크하면서 돌면 됨


순열 구하기

10 이하의 N개의 자연수가 주어지면 이 중 M개를 뽑아 일렬로 나열하여라.

📌 내가 푼 방법

📌 강사님 방법

function solution(nums, m) {
    let answer = [], tmp = [];
    let ch = Array.from({length: nums.length}, () => 0);
    function DFS(L) {
        if (L === m) {
            answer.push(tmp.slice());
        } else {
            for(let i = 0; i<nums.length; i++) { // i가 뽑는 숫자, n번을 돌음
                if(ch[i] === 0) {
                    ch[i] = 1;
                    tmp.push(nums[i]);
                    DFS(L+1); // 종료지점을 위해
                    tmp.pop();
                    ch[i] = 0;
                }
            }
        }
    }
    DFS(0);
    return answer; 
}

console.log(solution([3, 6, 9], 2));

중복 순열을 방지하기 위해, ch배열을 만들어서 체킹을 해주고, 돌면서 nums[i] 값들을 가져와 순열을 만든다.


조합의 경우수(메모이제이션)

조합의 경우의 수를 재귀를 이용해 조합수를 구하여라

nCr = n-1Cr-1 + n-1Cr

이 의미는 {1, 2, 3, 4, 5}가 있을 때, 5의 입장에서는 자기가 참여한 조합의 수와 자기가 참여하지 않은 조합의 수로 나눌 수 있음. 그 둘의 합은 5C3의 조합의 수와 같음

📌 내가 푼 방법

📌 강사님 방법

function solution(n, r) {
    let answer = 0;
    
    function DFS(N, R) {
        if(N===R || R===0) return 1;
        else {
            return DFS(N-1, R-1) + DFS(N-1, R);
        }
    }

    answer = DFS(n, r);
    return answer; 
}

console.log(solution(33, 19));

분류를 잘해야함! 종료지점은 n과 r이 같아지는 지점 n===r, r이 0이되는 지점 r===0
이렇게 코드를 짜면 33, 19를 넣었을 때 시간이 오래걸림
이것을 해결하기 위해 메모이제이션 사용!

메모이제이션 코드

function solution(n, r) {
    let answer = 0;
    let dy = Array.from(Array(35), () => Array(35).fill(0));
    
    function DFS(N, R) {
        if(dy[N][R] > 0) return dy[N][R];
        if(N===R || R===0) return 1;
        else {
            dy[N][R] = DFS(N-1, R-1) + DFS(N-1, R);
            return dy[N][R];
        }
    }

    answer = DFS(n, r);
    return answer; 
}

let start = new Date();
console.log(solution(33, 19));
let end = new Date();

console.log(end-start);

약 5밀리초 밖에 안걸림. dy라는 2차원 배열을 선언해서 그 값들을 안에 저장시켜두고 다시 돌 때 그 값이 존재하면 바로 return dy[N][R]을 해주어서 시간을 단축시킴


수열 추측하기

📌 내가 푼 방법

📌 강사님 방법

가장 윗줄에 1부터 N까지의 숫자가 한 개씩 적혀있고, 아래로 파스칼의 삼각형처럼 진행되며 가장 밑에있는 줄의 수가 주어진다. 삼각형에서 가장 위에 들어갈 N개의 숫자를 반환하여라.

function solution(n, f) {
    let answer = 0, flag = false;
    let dy = Array.from(Array(11), () => Array(11).fill(0));
    let ch = Array.from({length: n+1}, () => 0);
    let p = [];
    let b = []; // 이항 계수
    function combi(n, r) {
        if(dy[n][r] > 0) return dy[n][r];
        if(n===r || r===0) return 1;
        else return dy[n][r] = combi(n-1, r-1) + combi(n-1, r);
    }

    function DFS(L, sum) {
        if(flag) return;
        if(L === n) {
            if(sum === f) {
                answer = p.slice();
                flag = true;
            }
        }
        else {
            for(let i = 1; i<=n; i++) {
                if(ch[i] === 0) {
                    ch[i] = 1;
                    p.push(i);
                    DFS(L+1, sum+(p[p.length-1] * b[L]));
                    ch[i] = 0;
                    p.pop();
                }
            }
        }
    }

    for(let i=0; i<n; i++) {
        b.push(combi(n-1,i));
    }

    DFS(0, 0)
    return answer; 
}

console.log(solution(4, 16));

이항계수로 컴비네이션을 이용해서 푸는 방법 중요! 다시해보기!!!

조합 안쓰고 구하는 방법

function solution(n, f) {
    let answer = 0, flag = false;
    // let dy = Array.from(Array(11), () => Array(11).fill(0));
    let ch = Array.from({length: n+1}, () => 0);
    let p = [];
    let b = []; // 이항 계수
    // function combi(n, r) {
    //     if(dy[n][r] > 0) return dy[n][r];
    //     if(n===r || r===0) return 1;
    //     else return dy[n][r] = combi(n-1, r-1) + combi(n-1, r);
    // }

    function DFS(L, sum) {
        if(flag) return;
        if(L === n) {
            if(sum === f) {
                answer = p.slice();
                flag = true;
            }
        }
        else {
            for(let i = 1; i<=n; i++) {
                if(ch[i] === 0) {
                    ch[i] = 1;
                    p.push(i);
                    DFS(L+1, sum+(p[p.length-1] * b[L]));
                    ch[i] = 0;
                    p.pop();
                }
            }
        }
    }

    b.push(1);
    for(let i=1; i<n; i++) {
        b.push((b[i-1] * (n-i))/i);
    }
    DFS(0, 0)
    return answer; 
}

console.log(solution(4, 16));

조합 구하기

1부터 N까지 번호가 적힌 구슬이 있다. 이 중 M개를 뽑는 방법의 수를 출력하여라

📌 내가 푼 방법

📌 강사님 방법

function solution(n, m) {
    let answer = [], tmp=[];

    function DFS(L, s) { 
        if(L === m) {
            answer.push(tmp.slice());
        } else {
            for(let i = s; i<=n; i++) {
                tmp.push(i);
                DFS(L+1, i+1);
                tmp.pop();
            }
        }
    }

    DFS(0, 1);
    return answer; 
}

console.log(solution(4, 2));

조합의 기본!, 부분집합을 구할 때 이 코드를 이용해서 풀 수 있다!
여기서 포인트는 for문의 i의 값을 넘어온 s를 이용해 초기화를 시켜주어 자신의 위에 숫자만 포함되도록 하는 것!

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글