6월 28, 2024

[백준] 17141번 연구소2 문제 BFS로 풀어보기

1. 문제

 www.acmicpc.net/problem/17141


자세한 문제의 사항은 위의 링크를 클릭하여 백준 사이트에서 확인해보자.


2. 풀이

먼저 이 문제에서는 바이러스를 최대 m개 놓을 수 있기 때문에 어떤 위치에 바이러스 m개를 배치할지를 결정해주어야 한다. 그러기 위해서 먼저 문제에서 2라고 표시된 부분에 바이러스를 놓을 수 있다고 했기 때문에 가능한 바이러스의 위치를 ArrayList에다 넣어주도록 하겠다 (나는 virus라는 이름의 ArrayList를 만들어주었다). 그런 다음에 2라고 표시된 부분은 벽이 아니므로 자유롭게 움직일 수 있기에, 다시 0으로 바꾸어준다. 이러면 바이러스 자리인지 빈칸 자리인지 구분이 되지 않지만 우리는 ArrayList에다 넣어주었기 때문에 문제가 없다. 

 

다음으로 m개의 바이러스 자리를 결정하기 위해서 재귀함수를 사용해준다.

static void recur(int index, int cnt) {
        if (index == virus.size()) { 
            if (cnt == m) {//m개를 다 결정한 경우 
                bfs();
            }
        } else {
            int x = virus.get(index).x;
            int y = virus.get(index).y;
            a[x][y] = 3;
            recur(index+1, cnt+1); //해당 배열의 위치에 바이러스를 놓기로 결정한 경우
            a[x][y] = 0;
            recur(index+1, cnt);//해당 배열의 위치에 바이러스를 놓지 않기로 결정한 경우
        }
    }

위와 같은 재귀함수를 만들어주었다. 이런 식으로 재귀함수로 바이러스 m개의 위치를 결정해준다. 만약 마지막 바이러스의 위치에 도착했는데 cnt가 m개라면 m개의 바이러스 위치를 모두 결정해준 것이니 bfs 함수를 호출해주면 된다. 

 

여기서 바이러스의 진짜 위치를 표시해주기 위해서 만약 해당 좌표에 바이러스가 놓인 것이라면 그 값을 3으로 바꾸어주는 추가 작업도 실시해준다.


이제 그러면 bfs 함수가 어떻게 구성되어있는지 보도록 하겠다. (variable이 헷갈린다면 먼저 이 글의 가장 하단의 전체 코드를 보고 오는 것이 더 이해가 빠를 수 있다 )

static void bfs() {
        for (int i=0; i<n; i++) {
            for(int j=0; j<n; j++){
                d[i][j]=-1;
            }
        }
        Queue<Pair> q = new LinkedList<>();
        for (int i=0; i<n; i++) {
            for (int j=0; j<n; j++) {
                if (a[i][j] == 3) {
                    q.add(new Pair(i,j));
                    d[i][j] = 0;
                }
            }
        }
        while (!q.isEmpty()) {
            Pair p = q.remove();
            int x = p.x;
            int y = p.y;
            for (int k=0; k<4; k++) {
                int nx = x+dx[k];
                int ny = y+dy[k];
                if (0 <= nx && nx < n && 0 <= ny && ny < n) {
                    if (a[nx][ny] != 1 && d[nx][ny] == -1) {
                        d[nx][ny] = d[x][y] + 1;
                        q.add(new Pair(nx, ny));
                    }
                }
            }
        }
        int cur = 0;
        for (int i=0; i<n; i++) {
            for (int j=0; j<n; j++) {
                if (a[i][j] != 1) {
                    if (d[i][j] == -1) return;
                    if (cur < d[i][j]) cur = d[i][j];
                }
            }
        }
        if (ans == -1 || ans > cur) {
            ans = cur;
        }
    }

먼저 d라는 배열을 모두 -1로 초기화해준다. 그런 다음 바이러스의 위치를 모두 queue에다가 넣어준다. 만약 벽이 아니고 방문하지 않은 칸이라면 이를 다시 queue에 넣어두는 등 일반적으로 우리가 BFS를 구할 때 하는 행동을 해준다. 

 

그런 다음에 d의 최댓값을 찾는 것이 사실상 이 문제에서 구하고자 하는 값이다. 따라서 모든 배열의 칸을 돌면서 현재의 ans가 iteration에서 돌아서 나온 최댓값보다 작다면 이를 새로 update시키는 과정을 거친다. 

 

이렇게 모든 과정을 거치게 되면 우리는 문제에서 구하고자 하는 값을 얻을 수 있다. 아래는 Java로 구현한 전체 코드이다.


3. 코드



import java.util.*;
class Pair {
    int x, y;
    Pair(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
public class Main {
    static int[][] a;
    static int[][] d;
    static final int[] dx = {0,0,1,-1};
    static final int[] dy = {1,-1,0,0};
    static int n, m;
    static ArrayList<Pair> virus = new ArrayList<>();
    static int ans = -1;
    static void bfs() {
        for (int i=0; i<n; i++) {
            for(int j=0; j<n; j++){
                d[i][j]=-1;
            }
        }
        Queue<Pair> q = new LinkedList<>();
        for (int i=0; i<n; i++) {
            for (int j=0; j<n; j++) {
                if (a[i][j] == 3) {
                    q.add(new Pair(i,j));
                    d[i][j] = 0;
                }
            }
        }
        while (!q.isEmpty()) {
            Pair p = q.remove();
            int x = p.x;
            int y = p.y;
            for (int k=0; k<4; k++) {
                int nx = x+dx[k];
                int ny = y+dy[k];
                if (0 <= nx && nx < n && 0 <= ny && ny < n) {
                    if (a[nx][ny] != 1 && d[nx][ny] == -1) {
                        d[nx][ny] = d[x][y] + 1;
                        q.add(new Pair(nx, ny));
                    }
                }
            }
        }
        int cur = 0;
        for (int i=0; i<n; i++) {
            for (int j=0; j<n; j++) {
                if (a[i][j] != 1) {
                    if (d[i][j] == -1) return;
                    if (cur < d[i][j]) cur = d[i][j];
                }
            }
        }
        if (ans == -1 || ans > cur) {
            ans = cur;
        }
    }
    static void recur(int index, int cnt) {
        if (index == virus.size()) {
            if (cnt == m) {
                bfs();
            }
        } else {
            int x = virus.get(index).x;
            int y = virus.get(index).y;
            a[x][y] = 3;
            recur(index+1, cnt+1);
            a[x][y] = 0;
            recur(index+1, cnt);
        }
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        a = new int[n][n];
        d = new int[n][n];
        for (int i=0; i<n; i++) {
            for (int j=0; j<n; j++) {
                a[i][j] = sc.nextInt();
                if (a[i][j] == 2) {
                    a[i][j] = 0;
                    virus.add(new Pair(i,j));
                }
            }
        }
        recur(0,0);
        System.out.println(ans);
    }
}

3월 25, 2024

[백준] 11728번 배열 합치기 Merge Sort 풀이법

1. 문제

www.acmicpc.net/problem/11728

문제는 위의 링크에 들어가면 확인할 수 있다.


2. merge sort

merge sort 중에서 합치는 부분을 코드로 구현해야 하는 문제이다.

Merge Sort에 관한 내용은 이전 포스팅에서 정말 자세하게 다루었다.

https://www.programmingstory.com/2024/02/merge-sort.html

위의 포스팅에서 합치는 코드 또한 다루었기 때문에 한번 읽어보면 이 문제를 푸는데에도 큰 도움이 될 것이다.


3. 풀이

다른 점은 위 포스팅에서는 start, end 라고 해서 두 그룹을 합쳐서 처음과 끝 index를 따로 표시해주었는데 이번에는 두 배열을 서로 다른 배열로 입력받아놓았다는 점이다. 

그래도 알고리즘 자체는 완전히 같다.

 

합치는 코드 부분만 우선 살펴보겠다.

 //합치는 코드
        int i=0; //첫번째 그룹 index
        int j=0; //두번째 그룹 index
        int k=0; //현재 저장하는 c index
        while(i<n && j<m){
            if (a[i]<=b[j]){
                c[k++]=a[i++];
            }
            else {
                c[k++]=b[j++];
            }
        }
        while(i<n){
            c[k++]=a[i++];
        }
        while(j<m){
            c[k++]=b[j++];
        }

 

여기서 c라는 배열에다가 정렬된 것을 저장해놓을 것이고, 두 그룹의 index별로 대소관계를 비교해 먼저 c 배열에 들어와야 할 요소를 판단한다. 

마지막에 while문은 만약 하나의 그룹의 원소들은 모두 다 c 배열에 저장이 되었는데 다른 그룹의 원소들이 남아있을 경우를 대비하여 c 배열에 순서대로 저장하는 코드를 추가한 것이다.


4. 전체 코드

입력받고 출력하는 것까지 전체 코드는 아래와 같다.

import java.util.*;
import java.io.*;

public class Main{
    public static void main(String[] args) throws IOException{
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        String[] line = br.readLine().split(" ");
        int n = Integer.valueOf(line[0]);
        int m = Integer.valueOf(line[1]);
        int[] a = new int[n];
        line = br.readLine().split(" ");
        for (int i=0; i<n; i++) {
            a[i] = Integer.valueOf(line[i]);
        }
        int[] b = new int[m];
        line = br.readLine().split(" ");
        for (int i=0; i<m; i++) {
            b[i] = Integer.valueOf(line[i]);
        }
        int[] c = new int[n+m]; //저장할 공간
        
        //합치는 코드
        int i=0; //첫번째 그룹 index
        int j=0; //두번째 그룹 index
        int k=0; //현재 저장하는 c index
        while(i<n && j<m){
            if (a[i]<=b[j]){
                c[k++]=a[i++];
            }
            else {
                c[k++]=b[j++];
            }
        }
        while(i<n){
            c[k++]=a[i++];
        }
        while(j<m){
            c[k++]=b[j++];
        }
        StringBuilder sb = new StringBuilder();
        for (int in=0; in<n+m; in++) {
            sb.append(c[in] + " ");
        }
        System.out.println(sb);
    }
}

 

입력이 많기 때문에 Scanner 를 사용하는 대신 BufferedReader를 사용했고, 출력또한 매번 System.out.print()를 사용하는 것이 아닌, StringBuilder를 사용해서 한꺼번에 출력하여 시간을 줄였다. 


3월 19, 2024

[백준] 2138번: 전구와 스위치 문제

1. 문제

1) 링크

www.acmicpc.net/problem/2138

2) 문제

N개의 스위치와 N개의 전구가 있다. 각각의 전구는 켜져 있는(1) 상태와 꺼져 있는 (0) 상태 중 하나의 상태를 가진다. i(1<i<N)번 스위치를 누르면 i-1, i, i+1의 세 개의 전구의 상태가 바뀐다. 즉, 꺼져 있는 전구는 켜지고, 켜져 있는 전구는 꺼지게 된다. 1번 스위치를 눌렀을 경우에는 1, 2번 전구의 상태가 바뀌고, N번 스위치를 눌렀을 경우에는 N-1, N번 전구의 상태가 바뀐다.

N개의 전구들의 현재 상태와 우리가 만들고자 하는 상태가 주어졌을 때, 그 상태를 만들기 위해 스위치를 최소 몇 번 누르면 되는지 알아내는 프로그램을 작성하시오.

3) 입력

첫째 줄에 자연수 N(2≤N≤100,000)이 주어진다. 다음 줄에는 전구들의 현재 상태를 나타내는 숫자 N개가 공백 없이 주어진다. 그 다음 줄에는 우리가 만들고자 하는 전구들의 상태를 나타내는 숫자 N개가 공백 없이 주어진다.

4) 출력

첫째 줄에 답을 출력한다. 불가능한 경우에는 -1을 출력한다.

 

더 자세한 문제의 제한과 입출력을 보기 위해서는 위의 링크로 들어가보자!


2. 풀이

이 문제는 두 가지 경우로 나누어서 풀어야 한다.

첫 번째 0번 스위치를 눌렀을 경우, 두 번째 0번 스위치를 누르지 않았을 경우로 나누어서 문제를 해결해준다.

 

왜냐하면 첫 번째 스위치만 결정해주게 되면, 자신의 왼쪽에 있는 칸을 바꿀 수 있는 것은 자신 스위치밖에 더 이상 존재하지 않기 때문이다. 

다시 말해서, 왼쪽에서 오른쪽으로 가면서 한 칸씩 스위치를 검사하기 때문에 첫번째 스위치만 켜짐/꺼짐을 결정하면 1번 index의 칸 값을 바꿀 수 있는 스위치는 2번 스위치가 유일하다. 

 

따라서 첫 번째 스위치가 켜졌을 경우와 꺼졌을 경우로 나누어서 문제를 해결해준다.

먼저 첫 번째 스위치가 켜졌을 경우에 시행을 한 뒤에 꺼졌을 경우에도 다음에 시행을 해준다. 주의할 점은 이렇게 순차적으로 코드를 써주게 되면, 두번째 경우인, 첫번째 스위치가 꺼졌을 경우를 할 때 첫번째 경우의 스위치 변화에 영향을 받기 때문에 입력배열을 똑같은 곳에다가 하나 더 저장해두는 것이 필요하다. 


3. 코드

이것을 코드화한 것은 아래와 같다.

import java.util.*;

public class Main{
   
    static int n;
    static void change(int a[], int i){
        a[i]=1-a[i];
        a[i+1]=1-a[i+1];
        if (i+2<=n-1){
            a[i+2]=1-a[i+2];
        }
    }
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        n=sc.nextInt();
        int a[]=new int[n];
        int b[]=new int[n];
        String s=sc.next();
        for(int i=0; i<n; i++){
            a[i]=s.charAt(i)-'0';
        }
        s=sc.next();
        for(int i=0; i<n;i++){
            b[i]=s.charAt(i)-'0';
        }
        int c[]=new int[n];
        for(int i=0; i<n;i++){
            c[i]=a[i];
        }
        int count1=1;
        //0번 스위치를 눌렀을 경우
        a[0]=1-a[0];
        a[1]=1-a[1];
        for(int i=1; i<n; i++){
            if (a[i-1]!=b[i-1]){
                count1++;
                change(a, i-1);
            }
        }
        boolean first=true;
        for(int i=0; i<n; i++){
            if (a[i]!=b[i]){
                first=false;
            }
        }
        int count2=0; 
        //안눌렀을 경우
       
         for(int i=1; i<n; i++){
            if (c[i-1]!=b[i-1]){
                count2++;
                change(c, i-1);
            }
        }
         boolean second=true;
        for(int i=0; i<n; i++){
            if (c[i]!=b[i]){
                second=false;
            }
        }
        
        if (!first && !second){
            System.out.println(-1);
        }
        else if (!first){
            System.out.println(count2);
        }
        else if (!second){
            System.out.println(count1);
        }
        else{
            System.out.println(Math.min(count1, count2));
        }
        
    }
}

 

두 번다 시행을 해준 뒤 해당 값이 일치하는지를 한 번 더 검사한 뒤 first/ second라는 boolean 자료형을 하나 만들었다. 만약 둘다 true라면 두 번 시행 모두 가능하다는 뜻이므로 둘 중에서 작은 값을 출력해주면 되고, 하나만 가능하다면 가능한 count 값을 출력해주면 된다. 만약 둘다 가능하지 않다면 문제의 요구사항대로 -1을 출력해준다. 


3월 12, 2024

[백준] 10844번 쉬운 계단수 어떻게 풀 수 있을까?

1. 문제

1) 링크

www.acmicpc.net/problem/10844

2) 문제

45656이란 수를 보자.

이 수는 인접한 모든 자리수의 차이가 1이 난다. 이런 수를 계단 수라고 한다.

세준이는 수의 길이가 N인 계단 수가 몇 개 있는지 궁금해졌다.

N이 주어질 때, 길이가 N인 계단 수가 총 몇 개 있는지 구하는 프로그램을 작성하시오. (0으로 시작하는 수는 없다.)

3) 입력

첫째 줄에 N이 주어진다. N은 1보다 크거나 같고, 100보다 작거나 같은 자연수이다.

4) 출력

첫째 줄에 정답을 1,000,000,000으로 나눈 나머지를 출력한다.

 

더 자세한 문제의 제한은 위의 링크에 들어가서 확인해보자.


2. 풀이

이 문제는 자릿수를 하나씩 줄여서 풀어볼 수 있다.

만약 자릿수가 n이었고 수가 연속해야 하기 때문에 마지막 숫자가 a였다면 그 전 수는 자릿수 n-1에 마지막 수가 a-1 또는 a+1이었을 것이다. 즉 이처럼 역으로 생각해서 하나씩 자리수를 줄여본다.

 

다시 말해 이 문제는 자리수가 큰 문제에서 작은 문제로 나누어서 푸는 다이나믹 프로그래밍 관련 문제이다.


 

즉 우리는 여기서 2차원 배열을 정의하고 앞의 배열은 자리수, 뒤의 배열은 마지막 자리 수를 나타내는 용도로 사용하기로 하자.

 

ans[n][i] 가 있다면 n은 n자리 자릿수를 나타내는 것이고 i는 마지막에 사용된 숫자 (1~9까지) 나타내는 것이다.

 

그럴 때 ans[n][i]= ans[n-1][i-1] + ans[n-1][i+1]이라고 할 수 있다.

 

하지만 여기서 한 가지 주의해야 할 점은 마지막 자리의 숫자가 0이거나 9인 경우 그것보다 더 작고, 큰 숫자가 없으므로 이 경우는 예외처리를 해 주어야 한다는 점이다. 

 

그리고 이러한 문제는 항상 초기값 starting point가 필요한데 이 경우 자릿수의 최소 자리는 1자리 임으로 n의 starting point는 1이라고 할 수 있다. n이 1일 경우 마지막 자리의 수는 곧 가장 높은 자리를 의미하기 때문에 0이 들어갈 수 없다는 것을 조심해야 한다. 

 

이 경우에는 i가 1부터 9까지인 경우에만 1이라고 처리해주면 된다.

 


3. 코드 

전체코드를 살펴보면 아래와 같다.

 

import java.util.*;

public class Main{
    public static long mod = 1000000000L;
    static long[][] ans;
    public static void main(String[] args){
        Scanner sc= new Scanner(System.in);
        int n=sc.nextInt();
        ans=new long[n+1][10]; //계단의 길이, 마지막 숫자
        for(int i=1; i<=9; i++){
            ans[1][i]=1; //초기값 설정. 계단의 길이가 1이고 마지막 자리가 i인것은 다 한개
        }
        for(int i=2; i<=n; i++){
            for(int j=0; j<=9; j++){
                if (j>=1){
                    ans[i][j]+=ans[i-1][j-1]; //하나 작은 것과 인접
                }
                if (j<=8){
                    ans[i][j]+=ans[i-1][j+1]; //하나 큰 것과 인접
                }
                ans[i][j]%=mod;
            }
        }
        long fin_ans=0;
        for(int i=0; i<=9; i++){
            fin_ans+=ans[n][i];
        }
        System.out.println(fin_ans%mod);
        
    }
}

여기서 숫자가 너무 커질 것을 염려해 mod로 매번 나머지 연산을 해주어야 된다는 점을 까먹지 말자. mod를 여러번 사용해야 하니 계속 숫자를 사용하기 보다는 static 변수로 만들어 이를 여러번 활용하자 


3월 07, 2024

[백준] 2250번 트리의 높이와 너비 구하기 (In-Order 사용)

1. 문제

1) 링크

www.acmicpc.net/problem/2250

2) 문제

이진트리를 다음의 규칙에 따라 행과 열에 번호가 붙어있는 격자 모양의 틀 속에 그리려고 한다. 이때 다음의 규칙에 따라 그리려고 한다.

  1. 이진트리에서 같은 레벨(level)에 있는 노드는 같은 행에 위치한다.
  2. 한 열에는 한 노드만 존재한다.
  3. 임의의 노드의 왼쪽 부트리(left subtree)에 있는 노드들은 해당 노드보다 왼쪽의 열에 위치하고, 오른쪽 부트리(right subtree)에 있는 노드들은 해당 노드보다 오른쪽의 열에 위치한다.
  4. 노드가 배치된 가장 왼쪽 열과 오른쪽 열 사이엔 아무 노드도 없이 비어있는 열은 없다.

이와 같은 규칙에 따라 이진트리를 그릴 때 각 레벨의 너비는 그 레벨에 할당된 노드 중 가장 오른쪽에 위치한 노드의 열 번호에서 가장 왼쪽에 위치한 노드의 열 번호를 뺀 값 더하기 1로 정의한다. 트리의 레벨은 가장 위쪽에 있는 루트 노드가 1이고 아래로 1씩 증가한다.

아래 그림은 어떤 이진트리를 위의 규칙에 따라 그려 본 것이다. 첫 번째 레벨의 너비는 1, 두 번째 레벨의 너비는 13, 3번째, 4번째 레벨의 너비는 각각 18이고, 5번째 레벨의 너비는 13이며, 그리고 6번째 레벨의 너비는 12이다.

우리는 주어진 이진트리를 위의 규칙에 따라 그릴 때에 너비가 가장 넓은 레벨과 그 레벨의 너비를 계산하려고 한다. 위의 그림의 예에서 너비가 가장 넓은 레벨은 3번째와 4번째로 그 너비는 18이다. 너비가 가장 넓은 레벨이 두 개 이상 있을 때는 번호가 작은 레벨을 답으로 한다. 그러므로 이 예에 대한 답은 레벨은 3이고, 너비는 18이다.

임의의 이진트리가 입력으로 주어질 때 너비가 가장 넓은 레벨과 그 레벨의 너비를 출력하는 프로그램을 작성하시오

3) 입력

첫째 줄에 노드의 개수를 나타내는 정수 N(1 ≤ N ≤ 10,000)이 주어진다. 다음 N개의 줄에는 각 줄마다 노드 번호와 해당 노드의 왼쪽 자식 노드와 오른쪽 자식 노드의 번호가 순서대로 주어진다. 노드들의 번호는 1부터 N까지이며, 자식이 없는 경우에는 자식 노드의 번호에 -1이 주어진다.

4) 출력

첫째 줄에 너비가 가장 넓은 레벨과 그 레벨의 너비를 순서대로 출력한다. 너비가 가장 넓은 레벨이 두 개 이상 있을 때에는 번호가 작은 레벨을 출력한다.

 

더 자세한 문제의 내용과 제한사항을 보기 위해서는 위의 백준 링크에 들어가서 확인해보자.


2. 풀이

우선 이 문제를 풀기 위해서 필요한 class를 하나 만들어주자

class Node{
    int left, right;
    public int width, depth;
    Node(int left, int right){
        this.left=left;
        this.right=right;
    }
}

Node라는 클래스를 위와 같이 만들어주었고 자식노드를 가리키는 left, right 이외에도 몇 행, 몇 열에 있는지를 나타내는 width와 depth를 추가하였다. width는 가로 몇 번째 칸에 있는지를 나타내는 것이고 depth는 세로, 즉 트리의 높이를 나타내는 것이다. 

 


이 문제는 width를 나타내주기 위해서 무조건 트리의 순회 방법 중 In-Order 방식을 사용해야 한다. 왜냐하면 순회를 할 때 왼쪽 자식 노드부터 순회를 하고 그 다음에 현재 노드, 이후 오른쪽 자식 노드를 순회하기 때문에 width를 기록하기에 가장 좋은 방법이다. 

따라서 In-Order 방식으로 구현을 하고 해당 방식을 코드화한 것은 아래와 같다. 

static void inorder(int node, int depth){
        if (node==-1) return;
        inorder(arr[node].left, depth+1);
        arr[node].width=++width;
        arr[node].depth=depth;
        inorder(arr[node].right, depth+1);
    }

마찬가지로 -1의 값을 가지고 있으면 return을 하고 왼쪽 자식 노드 -> 현재 노드 -> 오른쪽 자식 노드의 순서로 순회를 한다. 현재 노드 차례에서는 width를 하나씩 늘려가면서 가로 몇번째 칸에 노드가 위치해있는지를 저장하고 depth는 함수의 매개값으로 전달해주어 저장을 하게 된다. 이런식으로 저장하면 모든 노드에 필요한 width와 depth값을 저장할 수 있다.


다음으로는 우리가 트리의 너비를 구해야 하기 때문에 각 depth마다 가장 왼쪽에 위치한 노드와 가장 오른쪽에 위치한 노드의 위치를 알아야한다. 그 두 위치의 차에 +1을 한 것이 너비의 값이라고 한다고 문제에서 주어졌다.

따라서, 각 depth마다 가장 왼쪽과 오른쪽 노드의 위치를 저장해야 하기 때문에

int[] leftmost = new int[10001]; //해당 깊이의 가장 왼쪽 노드
int[] rightmost = new int[10001]; //해당 깊이의 가장 오른쪽 노드

위와 같은 배열을 만들어주고,

int finaldepth=0;
 for(int i=1; i<=n; i++){
   int width=arr[i].width;
   int depth=arr[i].depth;
  finaldepth=Math.max(depth, finaldepth);
 rightmost[depth]=Math.max(rightmost[depth], width);
   if (leftmost[depth]==0){
    leftmost[depth]=width;
}
   else{
  leftmost[depth]=Math.min(leftmost[depth],width);
    }
 }

위와 같이 각 높이마다 가장 왼쪽 노드의 위치와 오른쪽 노드의 위치를 저장해준다. 여기서 가장 깊은 depth가 얼마인지도 추가적으로 알면 최종 계산에서 편하기 때문에 finaldepth라는 수를 만들어서 가장 깊은 높이 또한 구해준다. 

 


3. 코드

이 모든 것을 구현한 코드는 아래와 같다.

import java.util.*;

class Node{
    int left, right;
    public int width, depth;
    Node(int left, int right){
        this.left=left;
        this.right=right;
    }
}
public class Main{
    static int width=0;
    static int n;
    static Node[] arr=new Node[10001];
    static int[] parent = new int[10001];
    static void inorder(int node, int depth){
        if (node==-1) return;
        inorder(arr[node].left, depth+1);
        arr[node].width=++width;
        arr[node].depth=depth;
        inorder(arr[node].right, depth+1);
    }
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        n=sc.nextInt();
        for(int i=0; i<n; i++){
            int x=sc.nextInt();
            int y=sc.nextInt();
            int z=sc.nextInt();
            arr[x]=new Node(y,z);
            if (y!=-1){
                parent[y]+=1; //루트를 찾기 위해
            }
            if (z!=-1){
                parent[z]+=1;//루트를 찾기 위해
            }
        }
        int root=0;
        for(int i=1; i<=n; i++){   //루트 노드 구해주는 부분
            if (parent[i]!=1){
                root=i;
            }
        }
        inorder(root, 1);
       
         int[] leftmost = new int[10001]; //해당 깊이의 가장 왼쪽 노드
         int[] rightmost = new int[10001]; //해당 깊이의 가장 오른쪽 노드
        int finaldepth=0;
        for(int i=1; i<=n; i++){
            int width=arr[i].width;
            int depth=arr[i].depth;
            finaldepth=Math.max(depth, finaldepth);
            rightmost[depth]=Math.max(rightmost[depth], width);
            if (leftmost[depth]==0){
                leftmost[depth]=width;
            }
            else{
                leftmost[depth]=Math.min(leftmost[depth],width);
            }
        }
        int ans=0;
        int depthans=0;
        for(int i=1; i<=finaldepth; i++){
            int tmp_ans=rightmost[i]-leftmost[i]+1;
            if (ans<tmp_ans){
                ans=tmp_ans;
                depthans=i;
            }
        }
        System.out.println(depthans+" "+ans);
        
        
    }
}

여기서 하나 중요한 것은 문제에서 root 노드가 1이라고 정확하게 명시되지 않았기 때문에 우리가 루트 노드를 한 번 구해주는 과정이 필요하다는 것이다. 

따라서 parent라는 배열을 만들고 문제에서 입력을 받을 때 만약 해당 노드가 자식노드라면 해당 parent 배열의 숫자를 1을 늘린다. 

만약 루트 노드가 아니라면 모든 노드는 부모가 1개씩 있을 수밖에 없다. 따라서 전체 노드를 다시 순회하면서 해당 배열의 값이 1이 아닌 것을 찾으면 그것이 루트 노드일 것이다.

 

문제를 잘 읽고 문제에서 루트 노드가 1로 주어졌는지 아닌지도 잘 살펴보아야 한다. 


2월 29, 2024

[백준] 1991번 트리순회 출력문제 코드

1. 문제

1) 링크

www.acmicpc.net/problem/1991

2) 문제

이진 트리를 입력받아 전위 순회(preorder traversal), 중위 순회(inorder traversal), 후위 순회(postorder traversal)한 결과를 출력하는 프로그램을 작성하시오.

예를 들어 위와 같은 이진 트리가 입력되면,

  • 전위 순회한 결과 : ABDCEFG // (루트) (왼쪽 자식) (오른쪽 자식)
  • 중위 순회한 결과 : DBAECFG // (왼쪽 자식) (루트) (오른쪽 자식)
  • 후위 순회한 결과 : DBEGFCA // (왼쪽 자식) (오른쪽 자식) (루트)

가 된다.

3) 입력

첫째 줄에는 이진 트리의 노드의 개수 N(1≤N≤26)이 주어진다. 둘째 줄부터 N개의 줄에 걸쳐 각 노드와 그의 왼쪽 자식 노드, 오른쪽 자식 노드가 주어진다. 노드의 이름은 A부터 차례대로 영문자 대문자로 매겨지며, 항상 A가 루트 노드가 된다. 자식 노드가 없는 경우에는 .으로 표현된다.

4) 출력

첫째 줄에 전위 순회, 둘째 줄에 중위 순회, 셋째 줄에 후위 순회한 결과를 출력한다. 각 줄에 N개의 알파벳을 공백 없이 출력하면 된다.

 

더 자세한 문제의 제한을 보기 위해서는 위의 백준 링크에 들어가 보자


2. 트리 자료 구조

이 문제는 트리의 구조와 트리의 순화방법을 알지 못한다면 절대로 풀 수 없는 문제이다.

만약 트리 자료구조를 알지 못하면 

https://www.programmingstory.com/2024/02/blog-post_11.html

이전 포스팅을 먼저 보고 오자.


3. 풀이

우선 트리 구조를 구현하기 위해서 Node class를 사용했다. 자바에서는 구조체와 같은 역할을 하는 것이 class이기 때문에 클래스를 하나 더 만들어주었다. 

 

필요한 요소는 왼쪽 자식 노드와 오른쪽 자식 노드이기 때문에

다음과 같이 코드를 작성할 수 있다.


class Node {
    int left, right;
    Node(int left, int right) {
        this.left = left;
        this.right = right;
    }
}

그리고 만약 자식 노드가 없다면 해당 노드의 값은 -1로 표시하여 leaf node인지 아닌지를 판단할 수 있어야 한다. 

 

그래서 post-order, in-order, pre-order를 할 때도 -1의 값이 나오면 값을 return 해주면서 끝을 알릴 수 있어야 한다. 

여기서 각각의 함수를 static으로 만들고 호출해줄 것인데, 사실 코드는 거의 유사하다. 현재 노드를 출력하는 출력 라인의 순서만 달라지는 것이다. 

 


1] preorder 함수

 static void preorder( int x) {
        if (x == -1) return;
        System.out.print((char)(x+'A'));
        preorder(a[x].left);
        preorder(a[x].right);
    }

preorder 함수는 먼저 현재 노드를 출력해주고, 좌측 자식노드와 우측 자식노드를 차례대로 출력해준다.

노드의 값이 -1이라면 더 이상 자식 노드가 없다는 뜻이므로 return 해준다.

 

2] inorder 함수

static void inorder(int x) {
        if (x == -1) return;
        inorder(a[x].left);
        System.out.print((char)(x+'A'));
        inorder(a[x].right);
    }

inorder는 좌측 자식 노드를 출력, 현재 노드 출력, 우측 자식 노드를 출력하는 식으로 이루어진다.

노드의 값이 -1이라면 preorder와 마찬가지로 자식 노드가 없다는 뜻이므로 return 해준다.

 

3] postorder 함수

 static void postorder(int x) {
        if (x == -1) return;
        postorder(a[x].left);
        postorder(a[x].right);
        System.out.print((char)(x+'A'));
    }

postorder는 현재 노드를 가장 마지막에 출력해주는 것이다. 즉, 좌측 자식 노드와 우측 자식 노드 부분을 먼저 다 출력한 다음에 출력해준다. 노드의 값이 -1이라면 preorder와 마찬가지로 자식 노드가 없다는 뜻이므로 return 해준다.

 


4. 코드

이런식으로 비슷하게 세 가지 함수를 비슷하게 코드 작성해줄 수 있다.

전체 코드를 살펴보면, 다음과 같다.

import java.util.*;
class Node {
    int left, right;
    Node(int left, int right) {
        this.left = left;
        this.right = right;
    }
}
public class Main{
    static Node[] a;
    static void preorder( int x) {
        if (x == -1) return;
        System.out.print((char)(x+'A'));
        preorder(a[x].left);
        preorder(a[x].right);
    }
    static void inorder(int x) {
        if (x == -1) return;
        inorder(a[x].left);
        System.out.print((char)(x+'A'));
        inorder(a[x].right);
    }
    static void postorder(int x) {
        if (x == -1) return;
        postorder(a[x].left);
        postorder(a[x].right);
        System.out.print((char)(x+'A'));
    }
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        a=new Node[26];
        for(int i=0; i<n; i++){
            int current=sc.next().charAt(0)-'A';
            char le=sc.next().charAt(0);
            char ri=sc.next().charAt(0);
            
            int left = -1;
            int right = -1;
            if (le != '.') {
                left = le-'A';
            }
            if (ri != '.') {
                right = ri-'A';
            }
            a[current] = new Node(left, right);
        }
        preorder(0);
        System.out.println();
        inorder(0);
        System.out.println();
        postorder(0);
        System.out.println();
    }
}


2월 28, 2024

[백준] 14226번 이모티콘 문제 BFS의 변형

1. 문제

1) 링크

www.acmicpc.net/problem/14226

2) 문제

영선이는 매우 기쁘기 때문에, 효빈이에게 스마일 이모티콘을 S개 보내려고 한다.

영선이는 이미 화면에 이모티콘 1개를 입력했다. 이제, 다음과 같은 3가지 연산만 사용해서 이모티콘을 S개 만들어 보려고 한다.

  1. 화면에 있는 이모티콘을 모두 복사해서 클립보드에 저장한다.
  2. 클립보드에 있는 모든 이모티콘을 화면에 붙여넣기 한다.
  3. 화면에 있는 이모티콘 중 하나를 삭제한다.

모든 연산은 1초가 걸린다. 또, 클립보드에 이모티콘을 복사하면 이전에 클립보드에 있던 내용은 덮어쓰기가 된다. 클립보드가 비어있는 상태에는 붙여넣기를 할 수 없으며, 일부만 클립보드에 복사할 수는 없다. 또한, 클립보드에 있는 이모티콘 중 일부를 삭제할 수 없다. 화면에 이모티콘을 붙여넣기 하면, 클립보드에 있는 이모티콘의 개수가 화면에 추가된다.

영선이가 S개의 이모티콘을 화면에 만드는데 걸리는 시간의 최솟값을 구하는 프로그램을 작성하시오.

3) 입력

첫째 줄에 S (2 ≤ S ≤ 1000) 가 주어진다.

4) 출력

첫째 줄에 이모티콘을 S개 만들기 위해 필요한 시간의 최솟값을 출력한다.

 

문제의 세부조건을 더 알아보기 위해서는 위의 링크에 들어가서 확인해보자.


 2. 풀이

이 문제는 중요한 변수가 크게 두 가지가 있다. 첫번째는 화면에 있는 이모티콘의 개수, 두번째는 클립보드에 있는 이모티콘의 개수이다. 할 수 있는 연산 또한 클립보드의 개수에 따라서 결과가 달라지기 때문에 이 문제는 화면이모티콘의 개수와 클립보드에 있는 이모티콘의 개수를 함께 queue에 넣어주어야 한다. while문을 돌때도 두 개를 함께 pop 해주어야 한다는 것을 알 수 있다. 

 

그렇다면, 할 수 있는 시행을 하나씩 보면서 화면이모티콘의 개수와 클립보드 이모티콘의 개수가 어떻게 변화하는지 알아보자

  1. 화면에 있는 이모티콘을 모두 복사해서 클립보드에 저장한다. : (화면, 클립) --> (화면, 화면)
  2. 클립보드에 있는 모든 이모티콘을 화면에 붙여넣기 한다.: (화면, 클립)--> (화면+클립, 클립)
  3. 화면에 있는 이모티콘 중 하나를 삭제한다. : (화면, 클립)--> (화면-1, 클립)

위와 같이 변한다는 것을 알 수 있다. 

시작은 화면이모티콘 1개와 클립보드 이모티콘 0개로 시작한다. 

따라서 queue 에 1과 0을 각각 push해준 채로 시작하면 된다.

 

그리고 가능한 3가지의 경우를 모두 다 queue로 처리해준다.

 while(!q.isEmpty()){
  int s=q.remove();
    int c=q.remove();
    if (time[s][s]==-1){ //첫번째 복사
   time[s][s]=time[s][c]+1;
  q.add(s);q.add(s);
            }
   if (s+c<=goal&&time[s+c][c]==-1){ //두번째 붙여넣기
     time[s+c][c]=time[s][c]+1;
     q.add(s+c); q.add(c);
            }
    if (s>=1 && time[s-1][c]==-1){ //세번째 삭제
  time[s-1][c]=time[s][c]+1;
q.add(s-1); q.add(c);
            }
        }

세가지를 모두 다 처리하면 위와 같다.

대부분의 bfs 문제는 한 가지 변수만 queue로 처리하지만 이 경우는 답이 두 가지 변수에 의해 영향을 받기 때문에 두 가지를 queue에 push, pop해준다는 점이 특이하다.

 

3. 코드 

전체 코드를 살펴보면 아래와 같다. 

import java.util.*;

public class Main{
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int goal=sc.nextInt();
        int time[][]=new int[goal+1][goal+1]; // 화면이모티콘과 클립보드 이모티콘 개수 저장
        for(int i=0; i<=goal; i++){
            for(int j=0; j<=goal; j++){
                time[i][j]=-1; //처음 시간은 -1로 초기화
            }
        }
        Queue<Integer> q= new LinkedList<>();
        q.add(1);
        q.add(0);
        time[1][0]=0;
        while(!q.isEmpty()){
            int s=q.remove();
            int c=q.remove();
            if (time[s][s]==-1){
                time[s][s]=time[s][c]+1;
                q.add(s);q.add(s);
            }
            if (s+c<=goal&&time[s+c][c]==-1){
                time[s+c][c]=time[s][c]+1;
                q.add(s+c); q.add(c);
            }
            if (s>=1 && time[s-1][c]==-1){
                time[s-1][c]=time[s][c]+1;
                q.add(s-1); q.add(c);
            }
        }
        int ans=-1;
        for(int i=0; i<=goal; i++){
            if (time[goal][i]!=-1){
                if ((ans==-1)|| (ans> time[goal][i])){
                ans=time[goal][i];
            }
            }
            
        }
        System.out.println(ans);
    }
}

2월 28, 2024

[백준] 16940번 BFS 스페셜 저지 문제 풀어보기 (Collections.sort())

1. 문제

1) 링크

www.acmicpc.net/problem/16940

2) 문제

BOJ에서 정답이 여러가지인 경우에는 스페셜 저지를 사용한다. 스페셜 저지는 유저가 출력한 답을 검증하는 코드를 통해서 정답 유무를 결정하는 방식이다. 오늘은 스페셜 저지 코드를 하나 만들어보려고 한다.

정점의 개수가 N이고, 정점에 1부터 N까지 번호가 매겨져있는 양방향 그래프가 있을 때, BFS 알고리즘은 다음과 같은 형태로 이루어져 있다.

  1. 큐에 시작 정점을 넣는다. 이 문제에서 시작 정점은 1이다. 1을 방문했다고 처리한다.
  2. 큐가 비어 있지 않은 동안 다음을 반복한다.
    1. 큐에 들어있는 첫 정점을 큐에서 꺼낸다. 이 정점을 x라고 하자.
    2. x와 연결되어 있으면, 아직 방문하지 않은 정점 y를 모두 큐에 넣는다. 모든 y를 방문했다고 처리한다.

2-2 단계에서 방문하지 않은 정점을 방문하는 순서는 중요하지 않다. 따라서, BFS의 결과는 여러가지가 나올 수 있다.

트리가 주어졌을 때, 올바른 BFS 방문 순서인지 구해보자.

3) 입력

첫째 줄에 정점의 수 N(2 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N-1개의 줄에는 트리의 간선 정보가 주어진다. 마지막 줄에는 BFS 방문 순서가 주어진다. BFS 방문 순서는 항상 N개의 정수로 이루어져 있으며, 1부터 N까지 자연수가 한 번씩 등장한다.

4) 출력

입력으로 주어진 BFS 방문 순서가 올바른 순서면 1, 아니면 0을 출력한다.

 

더 자세한 문제의 조건을 보려면 위의 링크에 방문해보자.


2. 풀이

이 문제는 BFS의 가능한 경우의 수가 여러 개 있다는 아이디어에서 출발한다. 

BFS는 한 번만 모든 경우를 다 순회하면 되기 때문에 가능한 답이 여러개이다.

문제에서 주어진 배열을 보고 이 경우가 가능한 경우인지를 판단하면 된다. 

 

먼저 인접리스트를 사용하여서 이 문제를 풀 것인데, 

예를 들어 1과 인접해있는 숫자가 4와 6이라고 해보자. 

그런데 문제에서 4보다 6이 마지막줄에 먼저 나왔다면 1에 해당하는 ArrayList의 정렬을 6, 4로 해주는 것이다. 나는 ord 라는 배열을 만들어서 입력 마지막 줄의 각 숫자에 대한 순서를 저장했다. 즉 ord[a]가 ord[b]보다 작다면 각 노드의 ArrayList에서 a를 b보다 앞으로 정렬하는 것이다. 이는 Collections.sort에서 public int compare(Integer a, Integer b)를 사용해주면 된다. 

sorting하는 코드부터 보면, 

  
      for(int i=0; i<n; i++){
          Collections.sort(arr[i], new Comparator<Integer>(){
             public int compare(Integer a, Integer b){
                 if (ord[a]<ord[b]){
                     return -1;
                 }
                 else return 1;
             } 
          });
      }

위와 같이 구할 수 있다. 

이렇게 인접리스트를 모두 다 정렬하면 이제 1에서 시작하는 것은 동일하기 때문에 인접리스트로 BFS를 구한 답과 문제에서 주어진 답을 일일히 비교하면 된다. 


예전 BFS문제에서는 가능한 BFS의 순서를 출력하는 문제였기 때문에 단순히 queue 하나가 필요하고 그것을 remove할 때마다 출력만 해주면 되었다. 하지만 이 문제에서는 배열 두개를 비교해야 하기 때문에 실제로 하나씩 pop할 것을 담는 ArrayList

ArrayList<Integer> comparedList=new ArrayList<>();

을 하나 더 만들었다. 이제 queue에서 pop한 것을 하나씩 comparedList에서 담으면서 문제에서 주어진 배열과 같은지만 확인하면 된다. 

문제의 입력을 받는 배열은 내 코드에서는 inp[] 이다. (input을 줄여쓴것)


전체 코드를 살펴보면

import java.util.*;

public class Main{

    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
         int n=sc.nextInt();
        ArrayList<Integer> arr[]=new ArrayList[n];
        for(int i=0; i<n; i++){
            arr[i]=new ArrayList<>();
        }
        for(int i=0; i<n-1; i++){
            int from=sc.nextInt()-1;
            int to=sc.nextInt()-1;
            arr[from].add(to);
            arr[to].add(from);
        }
        
        int inp[]=new int[n];
        int ord[]=new int[n];
        for(int i=0; i<n; i++){
            inp[i]=sc.nextInt()-1;
            ord[inp[i]]=i;
        }
       
      for(int i=0; i<n; i++){
          Collections.sort(arr[i], new Comparator<Integer>(){
             public int compare(Integer a, Integer b){
                 if (ord[a]<ord[b]){
                     return -1;
                 }
                 else return 1;
             } 
          });
      }
        boolean check[]=new boolean[n];
        Queue<Integer> q= new LinkedList<>();
        ArrayList<Integer> comparedList=new ArrayList<>();
        q.add(0);
        check[0]=true;
        while(!q.isEmpty()){
            int x=q.remove();
            comparedList.add(x);
            for(int y: arr[x]){
                if (!check[y]){
                    q.add(y);
                    check[y]=true;
                }
            }
        }
        boolean isTrue=true;
        for(int i=0; i<n; i++){
            if (comparedList.get(i)!=inp[i]){
                isTrue=false;
                break;
            }
        }
        if (isTrue){
            System.out.println(1);
        }
        else{
            System.out.println(0);
        }
    }
}

위와 같이 구할 수 있다. 

 

즉 이 문제는 BFS가 여러가지 순서로 나올 수 있지만 인접리스트를 나타내는 ArrayList를 문제의 order에 따라서 정렬하고 난 후에는 가능한 경우가 1가지라는 것을 고려하여 코드를 작성할 수 있다. 


2월 20, 2024

[백준] 14391번 종이조각 비트마스크로 풀어보기

 www.acmicpc.net/problem/14391

문제

영선이는 숫자가 쓰여 있는 직사각형 종이를 가지고 있다. 종이는 1×1 크기의 정사각형 칸으로 나누어져 있고, 숫자는 각 칸에 하나씩 쓰여 있다. 행은 위에서부터 아래까지 번호가 매겨져 있고, 열은 왼쪽부터 오른쪽까지 번호가 매겨져 있다.

영선이는 직사각형을 겹치지 않는 조각으로 자르려고 한다. 각 조각은 크기가 세로나 가로 크기가 1인 직사각형 모양이다. 길이가 N인 조각은 N자리 수로 나타낼 수 있다. 가로 조각은 왼쪽부터 오른쪽까지 수를 이어 붙인 것이고, 세로 조각은 위에서부터 아래까지 수를 이어붙인 것이다.

 

입력

첫째 줄에 종이 조각의 세로 크기 N과 가로 크기 M이 주어진다. (1 ≤ N, M ≤ 4)

둘째 줄부터 종이 조각이 주어진다. 각 칸에 쓰여 있는 숫자는 0부터 9까지 중 하나이다.

출력

영선이가 얻을 수 있는 점수의 최댓값을 출력한다.

 

예제와 제한이 궁금하다면 위의 링크를 클릭해 자세한 사항을 알아보자. 

 


이 문제는 비트마스크로 풀 수 있다. 

비트마스크란 비트연산을 사용하여 정수로 집합을 나타내는 것이다.

 

예를 들어 {1,3,4,5,9} 가 사용이 되었다면 우리는 이를 정수로 01000111010 이라고 표현할 수 있다. (binary digit이 0자리부터 시작한다고 생각했을 때, 1번째 자리, 3번째자리, 4번째 자리, 5번째 자리, 9 번째 자리를 1로 표시하는 것이다)

 

이 경우에는 n*m 의 칸이 있으니 각각을 자릿수로 하는 n*m-1자리 정수를 만들 수 있는 것이다.

 

각각의 칸에 대해서 가로로 묶을 것인지, 세로로 묶을 것인지 정하면 되는데 양자택일의 문제이므로

가로로 묶을 경우 해당 자릿수의 숫자를 0으로, 세로로 묶을 경우 해당 자릿수의 숫자를 1로 정했다고 가정하겠다.

 


 int sum=0;
 //가로 찾기
            for(int i=0; i<n; i++){
                int current=0;
                for(int j=0; j<m; j++){
                    int k=i*m+j;
                    if ((s&(1<<k))==0){ //해당 칸이 가로일 경우
                        current=current*10+a[i][j];
                    }
                    else{ //해당 칸이 세로일 경우: current를 0으로
                        sum+=current;
                        current=0;
                    }
                }
                sum+=current;
            }

위 링크는 가로로 묶은 숫자들을 다 더하는 경우이다. 

i 번째 열과 j 번째 행에 대해서 이중 for문을 설계하였고, 가로의 경우 하나의 열에 대해서 쭉 이어지는 식으로 구해야 하기 때문에 열을 나타내는 i가 바깥 for문이 되는 것이다. 

 

해당 칸이 가로일 경우 current 숫자 뒤에 해당 칸에 있는 숫자를 더하고, 그렇지 않을 경우 세로를 나타내는 것이기 때문에 sum에다가 지금까지의 수를 더해준 뒤 current는 초기화해준다.

 


세로의 경우도 마찬가지로 하되, 열과 행의 순서만 바꾸면 된다.

 //세로
            for(int j=0;j<m; j++ ){
                int current=0;
                for(int i=0; i<n; i++){
                    int k=i*m+j;
                    if ((s&(1<<k))!=0){
                        current=current*10+a[i][j];
                    }
                    else{
                        sum+=current;
                        current=0;
                    }
                }
                sum+=current;
            }

위의 내용을 종합해보았을 때 전체 코드는

import java.util.*;

public class Main{
    public static void main(String[] args){
        Scanner sc= new Scanner (System.in);
        int n=sc.nextInt();
        int m=sc.nextInt();
        int [][]a=new int [n][m];
        for(int i=0; i<n; i++){
            String s=sc.next();
            for(int j=0; j<m; j++){
                a[i][j]=s.charAt(j)-'0';
            }
        }
        int ans=0;
        //가로: 0, 세로: 1
        for(int s=0; s<(1<<(n*m)); s++){
            int sum=0;
            //가로 찾기
            for(int i=0; i<n; i++){
                int current=0;
                for(int j=0; j<m; j++){
                    int k=i*m+j;
                    if ((s&(1<<k))==0){
                        current=current*10+a[i][j];
                    }
                    else{
                        sum+=current;
                        current=0;
                    }
                }
                sum+=current;
            }
            //세로
            for(int j=0;j<m; j++ ){
                int current=0;
                for(int i=0; i<n; i++){
                    int k=i*m+j;
                    if ((s&(1<<k))!=0){
                        current=current*10+a[i][j];
                    }
                    else{
                        sum+=current;
                        current=0;
                    }
                }
                sum+=current;
            }
            ans=Math.max(sum, ans);
        }
        System.out.println(ans);
    }
}

이렇게 작성할 수 있다.