https://leetcode.com/problems/maximum-subarray/

 

Maximum Subarray - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

 

연속 부분 수열의 최대합(한국어로 맞는지 모르겠습니다)을 구하는 정말 유명한 DP 문제입니다.

문제를 볼 때마다 매번 10초 정도 LCS와 헷갈립니다. 완전히 다른 알고리즘이라 금방 깨닫긴 하지만, 아마 입력의 형태가 비슷해서 그런것 같습니다. 

저만 그럴수도 있구요.

 

아이디어는 이렇습니다. 배열 nums가 주어졌다고 할때, 

어떤 위치 i 에서 끝나는 부분수열의 최대합은 두가지 경우입니다.

i-1에서 끝나는 부분수열의 최대합이 양수라서 그 값을 더하거나, 버리고 자기 자신만 취하거나

 

배열을 만들어서 각 위치마다 해당 위치에서 끝나는 최대합을 기록하며 풀이해도 되는데 이번 문제에선 중간 내용을 활용하지 않기 때문에 그냥 변수에 바로 저장해서 풀이했습니다.

if-else구문을 max(i, sum+i)로 줄이면 코드라인은 줄어들지만 실행시간이 조금 느려집니다.

조건문을 (0 > sum)으로 작성해도 같은 코드입니다.

class Solution:
    def maxSubArray(self, nums: list) -> int:
        sum = -100_000
        maxSum = -100_000
        for i in nums:
            if(i > i + sum):
                sum = i
            else:
                sum += i
                
            maxSum = max(sum, maxSum)
                
        return maxSum

input = [[-2,1,-3,4,-1,2,1,-5,4],[1],[5,4,-1,7,8]]
for i in input:
    print(Solution().maxSubArray(i))
728x90

https://leetcode.com/problems/jump-game-ii/

 

Jump Game II - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

점프게임의 시리즈 문제입니다.

1번 문제와 달리 도달할 수 있는지의 가부가 아니라 도달하는 최소의 step을 리턴해야 합니다. (마지막 위치에 도달하는것은 문제에서 보장함.)

처음에는 각 index별로 도달하는 최소의 step을 갱신하는 기본적인 DP의 풀이방법으로 풀었는데, 채점에 통과는 되었지만 실행시간이 좋지 않았습니다.

제공되는 testcase의 내용을 확인해 보니 각 index에서 매 번 목적지에 도달이 가능하다면 1차 제출한 코드는 N^2의 시간복잡도를 갖게 된다는 사실을 알게되었습니다.

(예를 들어 100 길이의 배열에 들어있는 숫자가 99,98,97,96...0 이라면 제 코드는 index를 한 칸 진행할 때마다 뒤의 모든 배열을 순회해야 합니다.)

 

더보기

처음 제출했던 비효율적인 코드

class Solution:
    def jump(self, nums: list) -> int:
        dp = [20000]*len(nums)
        dp[0] = 0
        
        for i in range(0, len(nums)-1):
            for j in range(i+1, min(len(nums), i + nums[i] + 1)):
                dp[j] = min(dp[i]+1, dp[j])
        return dp[-1]
    

input = [[2,3,1,1,4],[2,3,0,1,4]]

for i in input:
    print(Solution().jump(i))

 

토론 창의 도움을 얻어 BFS와 유사한, 매 step마다 내가 갈수있는 최대의 범위를 갱신하는 코드를 작성했습니다.

class Solution:
    def jump(self, nums: list) -> int:
        step = 0
        position = 0
        canMove = 0
        
        while(position < len(nums)-1):
            step += 1
            maxMove = 0
            
            while(position <= canMove):
                maxMove = max(maxMove, position+nums[position])
                position += 1
                if(maxMove >= len(nums)-1):
                    return step
                
            canMove = maxMove
        
        return step

input = [[0],[2,3,1,1,4],[2,3,0,1,4]]

for i in input:
    print(Solution().jump(i))

 

확실히 실행시간이 어마어마하게 차이가 나네요.

728x90

https://leetcode.com/problems/jump-game/

 

Jump Game - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

배열의 마지막에 도달할 수 있는지 묻는 문제입니다.

저는 거꾸로 배열의 마지막에서 시작해 어떤 위치까지 갈 수 있는지의 여부를 갱신하고 만약 시작점에서도 가능하다면 True를 리턴하는 식으로 풀이했습니다.

 

class Solution:
    def canJump(self, nums: list) -> bool:
        idx = len(nums)-2
        reach = len(nums)-1
        
        while(idx >= 0):
            if(nums[idx] + idx >= reach):
                reach = idx
            idx -= 1
            
        return reach == 0
    

input = [[2,3,1,1,4],[3,2,1,0,4],[2,0,0]]

for i in input:
    print(Solution().canJump(i))

 

728x90

https://www.acmicpc.net/problem/17472

 

17472번: 다리 만들기 2

첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. 둘째 줄부터 N개의 줄에 지도의 정보가 주어진다. 각 줄은 M개의 수로 이루어져 있으며, 수는 0 또는 1이다. 0은 바다, 1은 땅을 의미한다.

www.acmicpc.net

 

다리만들기 1과는 비슷한듯 전혀 다른 문제입니다.

각 섬들을 다리로 연결하는 부분은 같지만, 다리만들기1 문제와 다르게 다리는 직선으로만 가능하며 모든 섬이 연결되도록 완성해야 하고 이때 다리의 길이가 2이상인 최소한의 다리를 활용해야 합니다.

이 문제는 크게 3가지 과정으로 나눌 수 있습니다.

 

1. 주어진 지도에서 섬의 영역을 확인하고 이름 붙이기

2. 각각의 섬에서 다른 섬으로 잇는 최단 다리를 모두 만들기

3. 다리를 선택해 모든 섬들이 연결되게 만들기.

 

1,2번 과정은 다리만들기 1번 문제와 비슷하고 3번 과정은 MST(최소 스패닝 트리)를 구하는 과정으로 만들 수 있습니다.

즉, 각각의 섬들이 노드가 되고 각 섬들에서 다른섬으로 연결되는 최소거리의 다리가 엣지가 되는 식입니다.

MST를 만드는 알고리즘은 여러 종류가 있는데 우선순위 큐와 Union만을 이용해 간단하게 구현할 수 있는 크루스칼(Kruskal) 알고리즘을 사용했습니다.

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;

public class q17472__BOJ_다리만들기2 {
	static StringTokenizer st;
	static int[][] delta = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };
	static int[] parent;
	static int N, M;

	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		st = new StringTokenizer(br.readLine());
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());

		int[][] map = new int[N][M];

		for (int n = 0; n < N; n++) {
			st = new StringTokenizer(br.readLine());
			for (int m = 0; m < M; m++) {
				map[n][m] = Integer.parseInt(st.nextToken()) * -1;
			}
		}

		int island = 1;
		Queue<int[]> queue = new LinkedList<>();

		for (int n = 0; n < N; n++) {
			for (int m = 0; m < M; m++) {
				if (map[n][m] == -1) {
					queue.offer(new int[] { n, m });
					map[n][m] = island;

					while (!queue.isEmpty()) {
						int[] now = queue.poll();
						int x = now[0];
						int y = now[1];

						for (int i = 0; i < 4; i++) {
							int nX = x + delta[i][0];
							int nY = y + delta[i][1];
							if (isIn(nX, nY) && map[nX][nY] == -1) {
								queue.offer(new int[] { nX, nY });
								map[nX][nY] = island;
							}
						}
					}
					island++;
				}
			}
		}

		int[][] graph = new int[island][island];

		for (int is = 1; is <= island; is++) {
			boolean[][] visit = new boolean[N][M];

			for (int n = 0; n < N; n++) {
				for (int m = 0; m < M; m++) {
					if (map[n][m] == is) {
						queue.offer(new int[] { n, m, 0, 5 });
						visit[n][m] = true;
					}
				}
			}

			while (!queue.isEmpty()) {
				int[] now = queue.poll();
				int x = now[0];
				int y = now[1];
				int l = now[2];
				int dir = now[3];

				if (dir == 5) {
					for (int i = 0; i < 4; i++) {
						int nX = x + delta[i][0];
						int nY = y + delta[i][1];
						if (isIn(nX, nY) && !visit[nX][nY] && map[nX][nY] == 0) {
							queue.offer(new int[] { nX, nY, l + 1, i });
							visit[nX][nY] = true;
						}
					}
				}

				else {
					int nX = x + delta[dir][0];
					int nY = y + delta[dir][1];
					if (isIn(nX, nY) && !visit[nX][nY]) {

						if (map[nX][nY] == 0) {
							queue.offer(new int[] { nX, nY, l + 1, dir });
							visit[nX][nY] = true;
						} else {
							if (graph[is][map[nX][nY]] == 0 && 1 < l) {
								graph[is][map[nX][nY]] = l;
							}
							visit[nX][nY] = true;
						}
					}
				}
			}
		}

		int answer = 0;
		parent = new int[island];
		for (int n = 1; n < island; n++) {
			parent[n] = n;
		}
		PriorityQueue<int[]> pqueue = new PriorityQueue<>((e1, e2) -> {
			return e1[0] - e2[0];
		});

		for (int from = 1; from < island; from++) {
			for (int to = 1; to < island; to++) {
				if (graph[from][to] != 0) {
					pqueue.offer(new int[] { graph[from][to], from, to });
				}
			}
		}

		while (!pqueue.isEmpty()) {
			int[] now = pqueue.poll();
			int l = now[0];
			int from = now[1];
			int to = now[2];

			if (find(from) != find(to)) {
				answer += l;
				union(from, to);
			}
		}

		boolean flag = false;
		int parent = find(1);
		for (int n = 1; n < island; n++) {
			if (find(n) != parent) {
				flag = true;
				break;
			}
		}

		if (flag) {
			System.out.println(-1);
		} else {
			System.out.println(answer);
		}
	}

	static boolean isIn(int x, int y) {
		if (0 <= x && x < N && 0 <= y && y < M) {
			return true;
		}
		return false;
	}

	static int find(int i) {
		if (i == parent[i])
			return i;
		return parent[i] = find(parent[i]);
	}

	static void union(int x, int y) {
		x = find(x);
		y = find(y);

		if (x > y)
			parent[x] = y;
		else
			parent[y] = x;
	}
}
728x90

https://www.acmicpc.net/problem/2146

 

2146번: 다리 만들기

여러 섬으로 이루어진 나라가 있다. 이 나라의 대통령은 섬을 잇는 다리를 만들겠다는 공약으로 인기몰이를 해 당선될 수 있었다. 하지만 막상 대통령에 취임하자, 다리를 놓는다는 것이 아깝다

www.acmicpc.net

구현을 신경써야 하는 문제입니다.

문제를 2개의 과정으로 나눌 수 있습니다.

 

1. 주어진 지도에서 섬의 영역을 확인하고 이름 붙이기

2. 각각의 섬에서 다른 섬으로 잇는 다리 만들기

 

1번은 BFS DFS아무것이나 원하는 방법으로 구현하면 됩니다.

2번의 경우는 BFS를 이용하는것이 구현과 이해가 간편합니다.

시간 단축을 위해서는 모든 경우에 대해 다리를 구하고 다리의 길이를 비교하는 것 보다는 최소값을 다리가 완성될 때 마다 갱신하고, 이미 구해진 최솟값을 넘는 경우에는 Queue를 비워버리고 스킵하는 것이 훨씬 빠릅니다.

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class q2146_BOJ_다리만들기 {
	static StringTokenizer st;
	static int[][] delta = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };
	static int[][] map;
	static int[] parent;
	static int N;

	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		N = Integer.parseInt(br.readLine());

		map = new int[N][N];

		for (int n = 0; n < N; n++) {
			st = new StringTokenizer(br.readLine());
			for (int m = 0; m < N; m++) {
				map[n][m] = Integer.parseInt(st.nextToken()) * -1;
			}
		}

		int island = 1;

		for (int n = 0; n < N; n++) {
			for (int m = 0; m < N; m++) {
				if (map[n][m] == -1) {
					dfs(n, m, island);
					island++;
				}
			}
		}

		int answer = Integer.MAX_VALUE;
		Queue<int[]> queue = new LinkedList<>();
		for (int is = 1; is <= island; is++) {
			boolean[][] visit = new boolean[N][N];

			for (int n = 0; n < N; n++) {
				for (int m = 0; m < N; m++) {
					if (map[n][m] == is) {
						queue.offer(new int[] { n, m });
						visit[n][m] = true;
					}
				}
			}

			int l = 0;

			while (!queue.isEmpty()) {
				int size = queue.size();

				for (int s = 0; s < size; s++) {

					int[] now = queue.poll();
					int x = now[0];
					int y = now[1];

					for (int i = 0; i < 4; i++) {
						int nX = x + delta[i][0];
						int nY = y + delta[i][1];
						if (isIn(nX, nY) && !visit[nX][nY]) {

							if (map[nX][nY] == 0) {
								queue.offer(new int[] { nX, nY });
								visit[nX][nY] = true;
							} else {
								answer = Math.min(answer, l);
								visit[nX][nY] = true;
							}
						}
					}
				}
				l++;
				if (l > answer) {
					queue.clear();
				}
			}
		}
		System.out.println(answer);

	}

	static void dfs(int x, int y, int name) {
		map[x][y] = name;

		for (int i = 0; i < 4; i++) {
			int nX = x + delta[i][0];
			int nY = y + delta[i][1];
			if (isIn(nX, nY) && map[nX][nY] == -1) {
				dfs(nX, nY, name);
			}
		}

	}

	static boolean isIn(int x, int y) {
		if (0 <= x && x < N && 0 <= y && y < N) {
			return true;
		}
		return false;
	}
}

 

 

 

728x90

https://leetcode.com/problems/delete-and-earn/

 

Delete and Earn - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

 

 

주어진 조건에서 어떤 숫자를 선택하면 앞 뒤로 붙은 숫자들은 선택할 수 없습니다.

또 어떤 숫자를 선택하면 그 숫자는 계속 선택해도 됩니다.

 

문제의 설명만 보면 숨겨진 의도를 바로 찾기 힘들지만 생각해 보면 숫자를 연달아 선택할 수 없다는건 이전의 House Robber문제와 같은 유형이란것을 알 수 있습니다.

또 어떤 숫자를 선택하면 그 숫자에 걸린 point는 모두 획득 할 수 있기 때문에 숫자의 범위와 같은 크기의 List로 값을 압축 해 쉽게 구현 할 수 있습니다.

 

class Solution:
    def deleteAndEarn(self, nums: list) -> int:
        expectValue = [0] * 10001
        for i in nums:
            expectValue[i] += i
        
        dp = [0] * 10001
        dp[1] = expectValue[1]
        for i in range(2,10001):
            dp[i] = max(dp[i-1], dp[i-2]+expectValue[i])
        
        return dp[-1]
    

input = [[3,4,2], [2,2,3,3,3,4]]
for i in input:
    print(Solution().deleteAndEarn(i))
728x90

 

https://leetcode.com/problems/house-robber-ii/

 

House Robber II - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

 

https://nodingco.tistory.com/101?category=570522 

 

[Python] Leetcode 198. House Robber (Medium)

https://leetcode.com/problems/house-robber/ House Robber - LeetCode Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next..

nodingco.tistory.com

위 문제의 시리즈로 마찬가지로 DP문제입니다.

집들의 위치가 원형으로 연결되어 첫번째 집과 마지막 집이 붙어있는 형태가 되었다는 조건이 추가되었습니다.

아이디어를 떠올리기가 힘들었는데요. 

간단하게 생각하면 첫번째 집과 마지막 집은 동시에 도둑질을 할 수 없습니다.

집들을 의미하는 입력 리스트를 [첫번째 집~마지막 앞의 집]과 [두번째 집~마지막 집]으로 나누어 각각의 최댓값을 1번 문제와 같은 방식으로 구하면 둘 중에 더 큰 값이 정답이 됩니다.

 

생각해보면, [첫번째 집~마지막 앞의 집]에서 나오는 정답의 경우는 두가지로 생각해 볼 수 있습니다.

1. 첫번째 집을 도둑질 함 -> 이 경우엔 마지막 집은 당연히 털 수 없었습니다. 하지만 마지막 집을 턴 경우의 값이 더 컸을수도 있겠죠? 그 경우는 [두번째 집~마지막 집]으로 나뉜 범위에서 구한 정답이 됩니다.

2. 첫번째 집을 도둑질 하지 않음 - > 이 경우는 [두번째 집~마지막 집]으로 나눈 것에서 얻어낸 정답과 같습니다. (마지막집의 도둑질여부는 관계없습니다.)

[두번째 집~마지막 집]의 경우도 같은 방식으로 생각됩니다.

따라서 두 범위에서 문제를 풀이하면 둘 중 하나는 무조건 정답이 되게 됩니다.

 

class Solution:
    def rob(self, nums: list) -> int:
        if(len(nums) == 1):
            return nums[0]
        
        return max(self.sliceRob(nums[1:]), self.sliceRob(nums[:-1]))

    def sliceRob(self, nums: list) -> int:
        dp = [0] * (len(nums) + 1)
        dp[0] = 0
        dp[1] = nums[0]

        for i in range(2, len(nums)+1):
            dp[i] = max(dp[i-1],dp[i-2]+ nums[i-1])

        return dp[-1]


input = [[2,3,2],[1,2,3,1],[1,2,3],[1]]
for i in input:
    print(Solution().rob(i))
728x90

https://leetcode.com/problems/house-robber/

 

House Robber - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

 

DP의 대표적인 유형 입문 문제입니다.

집을 연속으로 털지 못한다는 것에서 N번째 집을 털때의 최대값은 N-2번째 집을 털고 N번째 집을 터는 것과 N번째 집을 털지 않는 경우 (즉 N-1번째 집에서의 최댓값) 두 값중 큰 값을 고르게 됩니다.

 

class Solution:
    def rob(self, nums: list) -> int:
        
        dp = [0] * (len(nums) + 1)
        dp[0] = 0
        dp[1] = nums[0]

        for i in range(2, len(nums)+1):
            dp[i] = max(dp[i-1],dp[i-2]+ nums[i-1])

        return dp[-1]


input = [[1,2,3,1],[2,7,9,3,1],[1]]
for i in input:
    print(Solution().rob(i))
728x90

+ Recent posts