반응형

오늘의 학습 키워드

이진탐색


알고리즘 문제


공부한 내용

이진탐색 여러 풀이


오늘의 회고

처음 시도

  • 여러 개의 데이터가 있고, 주어진 days 안에 모든 데이터를 탐색해서 범위 최솟값을 찾는 문제였기 때문에 이진 탐색으로 범위를 반으로 줄여가며 최솟값을 찾는 알고리즘을 생각했다.
  • 먼저 생각한 건 return 값, 초기 범위 최소값(최소 capa), 초기 범위 최댓값(최대 capa)이다.
    • return은 모든 짐(weights)를 정해진 days 안에 모두 나를 수 있는 배 용량의 최솟값이다.
    • 이진 탐색 범위 초기 최솟값 min은 weights 중 가장 큰 수로 최소 capa를 의미한다. 
    • 만약 min이 weights 중 다른 값으로 초기화된다면, weights의 큰 짐을 아예 나를 수 없기 때문에 가장 큰 짐을 최소 capa로 정했다.
    • 이진 탐색 범위 초기 최댓값 max는 모든 짐을 한번에 나를 수 있는 capa로 모든 weights 합으로 초기화했다.
  • 여기까진 잘 정했는데 이진 탐색 과정에서 문제가 있었다. 
    • 처음에 문제를 좀 잘 못 이해해서 중간값 mid로 주어진 days와 같은 날짜만큼 모든 짐을 나를 수 있는지에 대해 비교하면서 오류를 범했다. => Case 3번에서 걸림.
    • 또한 if 문으로 비교 로직이 많아지면서 로직이 복잡해졌고, 반복문 안에서 continue;를 남발하게 되었다.

해결

  • 탐색 범위를 정했다면, 이제 생각해야 할 것은 중간 값으로 탐색 범위에서 어떻게 범위를 좁힐 수 있을지 고민한다.
  • 문제에서 탐색 범위 중간값 mid로 해당 capa가 days 안에 모든 짐을 나를 수 있는지 가능 여부를 판단해야 한다.
    • 해당 capa (mid)로 모든 짐을 나르는 데 걸리는 최소 날짜를 구해서 days보다 크다면 불가능
    • 해당 capa (mid)로 모든 짐을 나르는 데 걸리는 최소 날짜가 days보다 작거나 같다면 가능하다고 판단
  • 그 다음으로 가능 여부가 나오면 범위를 어떻게 좁힐 지 결정한다.
    • 해당 capa (mid)로 가능하다면, max = mid - 1로 범위를 좁히면, mid 값은 더 작은 capa으로 설정된다.
    • 해당 capa (mid)로 불가능하다면, min = mid + 1로 범위를 좁히면, mid 값은 더 큰 capa로 설정된다.
    • 이렇게 반복해서 min과 max를 중앙값의 뒤, 앞으로 움직여 중앙값으로 최적 값을 찾아가는 게임이다. 
    • 즉, max를 움직이는 이유는 가능하지만, 더 최적의 값이 있는지를 찾는 것. min을 움직이는 이유는 불가능하기 때문에 가능한 값을 찾는 것이다.

문제 풀이

내 풀이

class Solution {
    //return: 모든 짐을 정해진 days 안에 모두 나를 수 있는 배 용량의 최솟값
    int[] weights;
    int days;
    int max;
    int min;
    public void findWeight() {
        int sum = 0;
        int big = weights[0];
        for (int w : weights) {
            sum += w;
            if (w > big) big = w;
        }
        max = sum;
        min = big;
    }

    public boolean canShip(int capa) {
        int sum = 0;
        int count = 1;
        for (int w : weights) {
            sum += w;
            if (sum > capa) {
                count++;
                sum = w;
            }
            if (count > days) return false; 
        }
        return true;
    }

    public int shipWithinDays(int[] weights, int days) {
        this.weights = weights;
        this.days = days;
        findWeight();

        while (min <= max) {
            int mid = (min+max)/2;
            if(canShip(mid)) {
                max = mid - 1;
            } else {
                min = mid + 1;
            }
        }

        return min;
    }
}

 

다른 사람 풀이

class Solution {
    public int shipWithinDays(int[] weights, int days) {


        int left = 0, right = 0;

        for(int w :  weights)
        {
            left = Math.max(left, w);
            right += w;
        }

        if (days ==1) return right;
        int ans = 0;
       //  System.out.println(left +" left, right" + right);
        while (left < right)
        {
            int mid = (left+right)/2;

            if(canShipPackage(weights, mid, days)){
                right = mid;
                ans = mid;
            } else {
                left = mid+1;
            }
        }
        return ans;
    }

    boolean canShipPackage(int [] weights,int mid,int days)
    {
        int count = 1;
        int sum = 0;
        for(int w: weights)
        {
            if(sum+w > mid)
            {
                sum = 0;
                count++;
            }
            sum += w;

            //System.out.println(count +"," + sum + "," +w);
        }
       // System.out.println(count +"," + mid);


        return count <= days ? true : false;
    }

}
반응형
반응형

오늘의 학습 키워드

이진탐색


알고리즘 문제

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 


공부한 내용

이진 탐색 (이분 탐색)

  • 정렬된 데이터 범위에서 탐색 범위를 반(1/2)만큼 좁혀나가면서 원하는 값을 찾는 알고리즘이다.
  • 데이터를 미리 정렬시킨 후 탐색을 해야 한다.
  • 시간복잡도: O(logN)

오늘의 회고

처음 시도

  • 사실 이진 탐색 알고리즘에 대해 잘 몰라서 찾아보면서 해결해야 했다.
  • 처음에 완탐으로 시간을 +1 하면서 검사된 사람 카운트를 해서 n이 되면 반환하는 식으로 풀려다가 조건에 n의 범위가 1 ~ 1000000000이었기 때문에 
    • 먼저 오름차순으로 times 배열을 정렬했다고 가정한다. (times 배열: 각 심사관이 검사하는데 걸리는 시간)
    • n이 1이라면, 심사하는 데 걸리는 시간은 times[0] (최소시간) * 1이 된다.
    • n이 1000000000이라면, 심사하는 데 걸리는 시간은 최악(심사관이 1명)인 경우 times[0] * 1000000000이다. => 백억
  • 완탐 시간 복잡도 O(N)
    • 시간복잡도를 볼 때,  보통 1초에 1억(10^8)번 연산 가능하다고 본다.
    • 앞서 완탐으로 생각해본 시간복잡도는 심사관 수 * times[0](최소 검사시간) * n(심사할 사람 수)이다.
    • 심사관이 1명이고 검사시간이 1일 때, 1초에 1억(10^8)번 연산 가능 기준으로 보면 10초 정도 걸릴 것으로 예상된다. 하지만 이것은 최소 기준!
    • 심사관 수는 1~100000 범위인데 심사관과 심사시간이 늘어날수록 연산에 필요한 시간은 엄청나게 늘어나게 된다. => 불가능

해결

  • 이진 탐색 시간 복잡도 O(logN)
    • 탐색 범위가 한 번 탐색할 때마다 절반으로 줄어들게 된다.
    • 이 문제가 요구하는 것은 모든 사람(n)이 심사를 받는데 걸리는 최소 시간이다.
    • 이 문제에서 탐색 범위는 가장 빠른 심사관의 심사시간 ~ 가장 빠른 심사관이 모든 사람(n)에 대한 심사시간
      • 최소 범위: 가장 빠른 심사관이 1명을 심사하는 데 걸리는 시간 => times[0]
      • 최대 범위: 가장 빠른 심사관이 모든 사람을 심사하는 데 걸리는 시간 => times[0] * n
    • log2(최대 탐색 범위)=log2(최대범위−최소범위)로 최대 이진 탐색 수는 log2 (times[0]*n-times[0])이고, times[0]은 작은 수이기 때문에 log2 (times[0]*n)에 근사하다.
    • 탐색을 반복할 때 중간 값을 계산해서 mid(중간값) 시간 내에 심사할 수 있는 사람 수를 계산 O(1)하는데 심사관 수만큼 반복하므로 시간복잡도가 O(심사관 수)이다. => O(1) + O(M) => O(M)
    • 최종 시간복잡도: O(M log2(times[0]*N))

문제 풀이

import java.util.*;

class Solution {
    public long solution(int n, int[] times) {
        long answer = 0;
        
        Arrays.sort(times);
        
	long min = times[0];
	long max = times[0] * (long)n;
        while (min <= max) {
            long mid = (min + max) / 2;
            long count = 0L;
            for (int i = 0; i < times.length; i++) {
                count += mid / times[i];
            }
            if (count < n)
                min = mid + 1;
            else {
                max = mid - 1;
                answer = mid;
            }
        }
        
        return answer;
    }
}
반응형
반응형

오늘의 학습 키워드

DP


알고리즘 문제


공부한 내용

DP

 

Math.min(Math.min(A, B), C) 

  • A, B, C 중에 제일 작은 수

오늘의 회고

처음 시도

  • 작은 정사각형부터 검사해서 더 큰 정사각형을 찾기 위해 시도했다. 
  • DP로 풀기 위해 dp[][] 배열을 두고 정사각형을 찾을 때마다 숫자를 더해주는 방식이어야 한다고 생각했다.
  • 내가 생각하지 못했던 부분은 정사각형 크기 결정 기준 지점이다. 나는 처음에 정사각형이 시작하는 지점을 기준으로 정사각형을 정의했기 때문에 DP 배열을 재사용할 수 있는 방법을 찾기 못했다.

해결

  • 해결점은 정사각형이 끝나는 지점을 기준으로 생각해야 했다.
  • 첫번째 행과 첫번째 열은 그대로 dp 배열에 넣는다.
    => 첫 행과 첫 열은 그대로 정사각형을 이루기 때문에 dp에 바로 넣는다.
  • 그 외에 위치들은 현재 위치 값이 0이 아니면, 1X1 보다 큰 정사각형을 만들 수 가능성이 있기 때문에 비교해서 dp 배열을 변경한다.
  • 즉, 현재 위치 중심으로 가장 큰 정사각형 크기를 알아보는 방법은 현재 위치에서 왼쪽, 대각선 왼위쪽, 위쪽 위치에서 가능한 정사각형 크기 중 가장 작은 값을 찾아서 +1 하면 현재 위치가 새로운(현재까지 가장 큰) 정사각형의 오른쪽 아래 모서리가 되는 위치를 나타낸다.
    • 예를 들어, (1,1) 지점의 값이 0이 아니면 왼쪽(1,0), 위쪽(0,1), 왼쪽위대각선(0,0) 값 중에 최솟값을 찾는다.
    • 그 다음으로, 최솟값에 +1을 하면 만들 수 있는 최대 정사각형 크기가 나온다.
      => 위, 왼, 위왼대각선 이 3개의 값들은 정사각형을 만드는 좌표이기 때문이다.
    • dp 배열의 해당 위치에 위치 값이 0인 경우 0을, 1인 경우 최솟값+1을 넣는다.
  • 결과적으로 모든 dp 값을 더하면 총 정사각형 개수가 나온다.

문제 풀이

class Solution {
    public int countSquares(int[][] matrix) {
        int xlen = matrix[0].length;
        int ylen = matrix.length;
        int[][] dp = new int[ylen][xlen];
        int count = 0;

        for (int i = 0; i < ylen; i++) {
            for (int j = 0; j < xlen; j++) {
                if (i == 0 || j == 0) {
                    dp[i][j] = matrix[i][j];
                } else if (matrix[i][j] == 1) {
                    dp[i][j] = Math.min(Math.min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1]) + 1;
                }
                count += dp[i][j];
            }
        }

        return count;
    }
}
반응형
반응형

오늘의 학습 키워드

DFS, 재귀


알고리즘 문제

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


공부한 내용

DFS, 재귀

  • 10일차(소수찾기 문제)에는 재귀를 활용해 순서를 바꿔가며 모든 숫자를 조합해야 했다.
  • 이번 문제는 앞에서부터 하나의 숫자를 픽스 + 픽스한 숫자에 대한 경우는 2가지(+, -)가 있다.
  • 그리고 픽스한 숫자의 인덱스의 바로 다음번째 인덱스의 숫자를 픽하게 되는데 이 때 또한 +, - 하는 경우의 수가 2개.
  • 인덱스마다 순서대로 반복해서 2가지 경우(더하거나 빼거나)를 반복해서 주어진 배열 끝까지 돌아 합을 구하는 것.

오늘의 회고

처음 시도

  • [4, 2, 1, 2] 케이스를 보면서 공책에 끄적여 봤다.

 

해결

  • 앞에서부터 하나의 숫자를 픽스 => 픽스한 숫자에 대한 경우는 각각 2가지(+, -)가 있다.
  • 순서대로 픽스한 숫자의 인덱스의 바로 다음번 인덱스의 숫자를 픽하게 되는데 이 때 또한 +, - 하는 경우의 수가 2개.
  • 인덱스마다 순서대로 반복해서 2가지 경우(더하거나 빼거나)를 반복해서 주어진 배열 끝까지 돌아 합을 구하는 것.

문제 풀이

  • 메모리 상 이득을 위해 자주 사용하게 될 변수는 따로 빼서 클래스 전체에서 사용할 수 있도록 선언해주었다.
    • count : sum이 target과 같은 모든 경우의 수
    • target: 주어진 변수 (target)
    • numbers: 주어진 변수 (numbers)
    • len: numbers.length
  • dfs(0, 0) 은 처음에 재귀 호출할 때 인덱스가 0이고, sum을 0으로 시작한다.
  • 재귀 종료 조건은 인덱스가 마지막까지 모두 돌은 경우(numbers의 길이만큼 돈 경우)
  • 카운트 추가 조건은 인덱스가 마지막까지 모두 돌아서 합한 결과가 target가 같은 경우
  • 재귀 호출 조건은 인덱스+1, 현재까지 결과에 + 다음 숫자 / 현재까지 결과에 - 다음 숫자
class Solution {
    int count;
    int target;
    int[] numbers;
    int len;
    
    public void dfs(int idx, int sum) {
        if (idx == len) {
        	if (sum == target) count++;
            return;
        }
        
        dfs(idx+1, sum + numbers[idx]);
        dfs(idx+1, sum - numbers[idx]); 
    }
    
    public int solution(int[] numbers, int target) {
        this.target = target;
        this.numbers = numbers;
        this.count = 0;
        this.len = numbers.length;
        
        dfs(0, 0);
        
        return count;
    }
}
반응형

+ Recent posts