728x90

이번 문제 또한 매우 간단한 문제입니다.

N명의 학생이 존재하고, 3번째 줄부터 N개 나오는 학생들 이름을 가지고 인기도를 측정하여 출력하면 되는 문제입니다.

 

우선 여기서 봐야 할 점은 인기도 측정 방식정렬인데요.

인기도 측정 방식은 그냥 나오는 학생 이름들을 전부 인기도에 반영하면 되는 간단한 문제이고,

정렬은 주어진 조건에 맞춰 정렬하면 됩니다.

 

정렬의 경우 저는 Stream의 sorted함수를 사용하였는데요. 이때, sort조건을 커스텀함으로써 간단하게 정렬하였습니다.

이후 reduce를 활용하여 StringBuilder에 값들을 담게 했는데요, 이는 String과 달리 StringBuilder는 불변성이 아니어서 메모리 활용 측면에서 훨씬 효율적이라 판단하여 사용하였습니다.

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        Map<String, Integer> map = new HashMap<>();
        int N = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());

        for(int i = 0; i < N; i++) {
            map.put(st.nextToken(), 0);
        }

        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            while (st.hasMoreTokens()) {
                String to = st.nextToken();
                map.put(to, map.get(to) + 1);
            }
        }

        StringBuilder answer = map.entrySet().stream().sorted((prev, now) -> {
            if(prev.getValue() != now.getValue()) return now.getValue() - prev.getValue();
            return prev.getKey().compareTo(now.getKey());
        }).reduce(new StringBuilder(),(sb, entry) -> sb.append(entry.getKey()).append(" ").append(entry.getValue()).append("\n"),StringBuilder::append);
        System.out.println(answer);
    }
}
  • 시간복잡도: O(n)
  •  공간복잡도: O(n)
728x90

'알고리즘 문제풀이' 카테고리의 다른 글

[ 백준 - 11725 ] 트리의 부모 찾기  (0) 2024.11.12
[ 백준 - 29701 ] 모스 부호  (0) 2024.11.11
[ 백준 - 2358 ] 평행선  (2) 2024.11.09
[테코테코] 2주차 Stack  (0) 2024.09.23
Hackerrank 사이트 소개  (2) 2024.06.13
728x90

이번 문제는 고민 했던 것보다 꽤 쉽게 풀어져 가져와보았습니다.

 

위와 같이 문제가 주어졌습니다.

 

쉽게 생각해보면 점이 n개 주워졌을 때 x나 y에 평행이 되는 선의 갯수를 구하라는 뜻인데,

여기서 살짝 생각해보면 좋은 점은 x와 y에 평행이 되려면 어떤 조건을 가져야하는가 입니다.

 

x에 평행이 되려면 두 점의 y좌표는 동일해야할 것이고, y에 평행이 되려면 x좌표가 동일해야할 것 입니다.

 

즉, x점을 기준으로 묶은 자료와 y점을 기준으로 묶은 자료만 있다면 쉽게 문제를 풀 수 있는 것입니다.

public class Main {

    static Map<Integer, Integer> xLineMap = new HashMap<>();
    static Map<Integer, Integer> yLineMap = new HashMap<>();

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        for(int i = 0; i < n; i++) {
            String[] strs = br.readLine().split(" ");
            int x = Integer.parseInt(strs[0]);
            int y = Integer.parseInt(strs[1]);
            xLineMap.put(x, xLineMap.getOrDefault(x, 0) + 1);
            yLineMap.put(y, yLineMap.getOrDefault(y, 0) + 1);
        }
        int answer = 0;
        for (int count : xLineMap.values()) {
            if (count > 1) {
                answer++;
            }
        }
        for (int count : yLineMap.values()) {
            if (count > 1) {
                answer++;
            }
        }
        System.out.println(answer);
    }
}

 

 

위 문제를 확인해보면 2초의 시간초가 주어지고, n은 최대 100,000이라고 합니다.

이를 통해보면 알고리즘은 O(n)의 성능을 가지고 있도록 작성하는 것이 합리적이라 생각합니다.

 

위 알고리즘을 보면 n을 받아 n번 반복함으로 O(n)의 시간복잡도를 가지고 있다고 볼 수 있습니다.

또한, n을 받아 사용하는 데이터들을 보면 n개의 데이터들만을 활용하는 것을 볼 수 있어 공간복잡도 또한 O(n) 혹은 O(2n)의 복잡도를 가지고 있습니다.

  • 시간 복잡도: O(n)
  • 공간 복잡도: O(n)

 

728x90
728x90

들어가기에 앞서

해당 글을 작성하기에 앞서 이진트리는 어떤 것인지에 대한 사전 지식이 있어야 이해할 수 있는 글입니다.

 

만약 이진트리가 무엇인지 모른다면 이 글을 읽고 오시기 바랍니다.


이진 트리 순회

이진 트리를 순회하는 방법에는 어디서부터 확인하느냐로 4가지로 나눌 수 있습니다.

 

순회 방법에 따라 사용처가 다르게 나오게 되고, 이를 학습함으로써 좀 더 본인이 사용하고자 하는 목적에 알맞은 방법으로 알고리즘을 작성할 수 있습니다.

 

아래 보이는 이진 트리를 통해 각각의 순회 결과에 대해 알려드리겠습니다.

아래 이진 트리는 이 곳에서 코드로 확인해 보실 수 있습니다.


전위 순회 ( Pre-order Traversal )

전위 순회는 부모 노드 -> 왼쪽 노드 -> 오른쪽 노드 순으로 재귀하며 출력하는 방법입니다.

즉 가장 왼쪽부터 라인부터 탐색하며, 가장 깊은 Level의 오른쪽 노드부터 차근차근 탐색할 수 있게 해줍니다.

public void traversePreOrder(TreeNode<Integer> node) {
    if (node != null) {
        System.out.print(" " + node.getValue());
        traversePreOrder(node.getLeft());
        traversePreOrder(node.getRight());
    }
}

 

결과

 

사용처

  • DFS 탐색
  • 트리 복사

중위 순회 ( In-order Traversal )

중위 순회는 왼쪽 자식 노드 -> 부모 노드 -> 오른쪽 자식노드 순으로 순회하는 방법입니다.

public void traverseInOrder(TreeNode<Integer> node) {
    if (node != null) {
        traverseInOrder(node.getLeft());
        System.out.print(" " + node.getValue());
        traverseInOrder(node.getRight());
    }
}

 

결과

 

사용처

  • 오름차순 정렬
  • 정렬된 데이터 탐색

후위 순회 ( Post-order Traversal )

후위 순회는 왼쪽 자식노드 -> 오른쪽 자식노드 -> 부모 노드 순으로 순회하는 방법입니다.

public void traversePostOrder(TreeNode node) {
    if (node != null) {
        traversePostOrder(node.getLeft());
        traversePostOrder(node.getRight());
        System.out.print(" " + node.getValue());
    }
}

 

결과

 

사용처

  • 메모리 해제
  • 파일 구조 삭제
  • 후위 표기법을 통한 연산

레벨 순회 ( Level-order Traveral )

레벨 순회는 root 부터 각 레벨마다 순차적으로 왼쪽부터 오른쪽으로 순회하는 방법입니다.

public void traverseLevelOrder(TreeNode root) {
    if (root == null) return;

    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);

    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        System.out.print(" " + node.getValue());

        if (node.getLeft() != null) queue.add(node.getLeft());
        if (node.getRight() != null) queue.add(node.getRight());
    }
}

 

결과

 

사용처

  • 최단 경로 탐색
  • Bfs 탐색

Reference

https://www.w3schools.com/dsa/dsa_data_binarysearchtrees.php

 

W3Schools.com

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

728x90
728x90

이진 트리란?

이진 트리는 트리 자료구조의 한 종류로, 각 노드가 최대 2개의 자식노드를 가질 수 있는 구조입니다.

 

이진 트리를 구성하는 노드들은 3가지로 분리할 수 있습니다.

  • 루트 노드 ( Root Node ): 트리의 시작점으로, 부모 노드가 존재하지 않고 이진트리에서 하나만 존재합니다.
  • 자식 노드 ( Child Node ): 부모 노드에 속한 노드로써 Left Child와 Right Child로 구분.
  • 잎 노드 (Leaf Node): 트리의 마지막에 존재하는 노드로써 자식이 존재하지 않는 노드를 의미합니다.

위와 같이 3가지로 분류할 수 있지만 때에 따라 하나의 노드가 의미하는 바가 겹칠 수 있습니다.


이진 트리 유형

이진 트리는 속한 노드들의 상태에 따라 아래와 같은 유형들로 분류할 수 있습니다.

균형 이진 트리 ( Balanced Binary Tree )

모든 자식 노드들의 Level차이가 최대 1 이하 차이나는 트리입니다.

 

즉, 한 노드에 쏠림 현상이 발생하지 않고 아래와 같이 규형 잡힌 상태를 의미합니다.

균형 이진 트리

포화 이진 트리 ( Full Binary Tree )

모든 자식 노드가 0개 혹은 2개씩 채워진 상태를 의미합니다. 

포화 이진 트리

완전 이진 트리 ( Complete Binary Tree )

완전 이진트리는 마지막 레벨을 제외 모든 노드들이 채워져 있는 상태를 의미합니다.

이때 자식 노드는 왼쪽부터 차 있어야 합니다.

완전 이진트리


이진 트리 표현

이진 트리는 배열과 링크드 리스트를 통해 표현할 수 있습니다.

Array 표현

배열을 통해 이진트리를 표현할 경우 아래와 같은 규칙을 통해 표현할 수 있습니다.

  • 루트는 배열의 0번째 인덱스 혹은 1번째 인덱스에 존재한다.
  • 왼쪽 자식인덱스 = 루트 인덱스 == 0 ? 부모 인덱스 * 2 + 1 : 부모 인덱스 * 2;
  • 오른쪽 자식 인덱스 = 루트 인덱스 == 0 ? 부모인덱스 * 2 + 2; 부모 인덱스 * 2 + 1;

위와 같은 규칙을 가지고 이진트리를 표현하였을 때 단점이 존재하는데,

이는 배열로 구현하다 보니 초기 이진트리의 크기가 특정된다는 점이고, 이는 이진트리 내 데이터가 골고루 분포되지 않다면 메모리 자원을 비효율 적으로 사용한다는 단점이 존재합니다.

배열로 표현한 이진트리

public static void main(String[] args) {
        int[] arr = new int[7];
        // Root
        arr[0] = 1;
        // Level 1
        arr[1] = 2;
        arr[2] = 3;
        // Level 2
        arr[3] = 4;
        // Level 3
        arr[5] = 6;
        arr[6] = 7;
        printTree(arr);
}

코드로 작성하면 위와 같이 작성을 할 수 있고, Terminal에 출력할 경우 위와 같이 출력해 볼 수 있습니다.

더보기

Termial 출력 코드

public static void printTree(int[] arr) {
        int level = 0;
        int index = 0;
        while (index < arr.length) {
            int nodesInLevel = (int) Math.pow(2, level); // 해당 레벨의 노드 개수
            for (int i = 0; i < nodesInLevel && index < arr.length; i++) {
                System.out.print(arr[index] + " ");
                index++;
            }
            System.out.println(); // 각 레벨 출력 후 줄바꿈
            level++;
        }
 }

Linked List 표현

Linked List로 표현할 경우 좀더 효율적으로 생성이 가능하다는 장점이 있지만, 균형 이진 트리인 경우 배열로 구현하는 것이 더욱 효율적이라 생각합니다.

 

우선 우리가 알고 있는 java.util.LinkedList 구현체로 구현하는 것은 아닙니다.

LinkedList의 구현체 중 데이터 저장 클래스인 Node를 확인해보면 Node안은 아래 필드들로 구현되어있습니다.

  • E item
  • Node<E> next
  • Node<E> prev

위와 같이 이전과 이후에 대한 주소를 얻을 수 있지만 우리가 원하는 이진트리를 구현하기 위해서는 next가 leftNext와 rightNext로 나눠져야합니다.

 

그리하여 직접 구현한다면 아래와 같이 구현할 수 있습니다.

public static class TreeNode<E> {
        private E item;
        private TreeNode<E> left;
        private TreeNode<E> right;

        public TreeNode(E item) {
            this.item = item;
        }

    }

    public static class BinaryTree {
        TreeNode<Integer> root;

        public void add(Integer item) {
            root = addRecursive(root, item);
        }
        private TreeNode<Integer> addRecursive(TreeNode<Integer> current, Integer item) {
            if (current == null) {
                return new TreeNode<>(item);
            }

            if(current.item > item) {
                current.left = addRecursive(current.left, item);
            } else {
                current.right = addRecursive(current.right, item);
            }

            return current;
        }
}

root를 기준으로 왼쪽은 작은값, 오른쪽은 큰값으로 셋팅되게 구현하였습니다.

 

검색의 성능을 높이기 위해 최소 힙, 최대 힙, 탐색 이진 트리 등등 다양한 방법으로 구성될 수 있으며,

해당 내용들은 다음 글에 작성하도록 하겠습니다.

Reference

https://www.w3schools.com/dsa/dsa_data_binarysearchtrees.php

 

W3Schools.com

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

728x90

'알고리즘 문제풀이 > 자료구조' 카테고리의 다른 글

[ Tree ] 이진 탐색 트리 삽입, 삭제  (0) 2024.11.16
[Tree] 이진 트리 순회  (1) 2024.11.08
[자료구조] 스택(Stack)  (0) 2024.10.21
728x90

Stack이란?

Stack은 한쪽 끝에서만 자료를 넣고 뺄 수 있는 LIFO( Last In First Out ) 자료구조입니다.

추상자료형( abstract data type, ADT )이며, 이는 논리적인 기능을 명시해 놓은 것이라 볼 수 있고, 이는 구현체가 이를 상속받아 구현하는 방식으로 자료형은 Stack이지만 구현은 여러 방법으로 구현할 수 있음을 의미합니다.

 

Stack은 Vector를 상속받아 구현을 하고 있는데 이는 Vector를 구현되 LIFO방식으로 데이터를 다룰 수 있게 다루기 위해 Stack이란 자료형이 존재한다 생각해 볼 수 있습니다.

 

우선 기본적인 기능을 확인해 보겠습니다.

Stack은 Vector를 상속한 ADT이기 때문에 기본적으로 Object []인 elementData로 데이터를 관리하고 있습니다.

 

배열의 크기는  기본적으로 10의 크기로 생성하고 있으며, resizing을 통해 많은 데이터가 들어오더라도 수용할 수 있습니다. 이렇듯 대부분의 기능은 동일하지만, 외부에서 접근할 수 있는 pop메서드를 확인해 보면 elemtnData의 Size를 확인하여 맨 마지막 데이터만을 추출할 수 있도록 구현되어 있는 것을 확인할 수 있습니다.

public synchronized E pop() {
    E       obj;
    int     len = size();

    obj = peek();
    removeElementAt(len - 1);

    return obj;
}

 

제공하는 기능

Method 기능
push item을 추가합니다.
pop 최근 들어온 Element를 추출합니다.
peek 최근 들어온 Element를 확인합니다.
search element를 조회합니다. ( Index를 반환합니다. )

사용처

그럼 Stack은 어떤 곳에서 사용하게 될까요??

웹브라우저 History 즉 뒤로가기 앞으로가기 와 같은 기능처럼 history를 쌓아두고 이를 최신 것부터 접근하는 상황이 있다면 이럴때는 Stack을 사용함으로써 편하게 관리할 수 있습니다.

 

또한 재귀함수와 같이 연산이 루프를 도는 방향의 알고리즘을 구현을 한다면,

최근 접근 한 것들을 접근 할 수 있는 Stack을 통해 쉽게 최근에 넣은 데이터를 꺼내 연산을 하면서 손쉽게 구현할 수 있습니다. 백준 코딩 테스트

References

https://docs.oracle.com/javase/7/docs/api/java/util/Stack.html

728x90
728x90

주제

금주 테코테코의 주제는 Stack입니다.

 

Stack 자료구조를 활용하거나 Stack자료구조의 작동원리를 이해하여 아래 문제를 해결하였습니다.

 

문제

1.  올바른 괄호

'(' 또는 ')' 로만 이루어진 문자열 s가 주어졌을 때, 문자열 s가 올바른 괄호이면 true를 return 하고, 올바르지 않은 괄호이면 false를 return 하는 solution 함수를 완성해 주세요.

 

코드

public class 올바른_괄호 {

    boolean solution(String s) {

        Node first = null;
        Node last = null;

        int len = s.length();
        for(int i = 0; i < len; i++) {
            char c = s.charAt(i);
            if(c == '(') {
                if(first == null) {
                    first = new Node();
                    last = first;
                    continue;
                }
                last = last.newNextNode();
                continue;
            }
            if(first == null) return false;
            Node prev = last.removeNowNode();
            if(prev == null) {
                first = null;
            }
            last = prev;
        }

        return last == null;
    }

    public static class Node {
        public Node prevNode;
        public Node nextNode;

        public Node() {
        }

        public Node(Node prevNode) {
            this.prevNode = prevNode;
        }

        public static Node from (Node prev) {
            return new Node(prev);
        }

        public Node newNextNode() {
            this.nextNode = Node.from(this);
            return this.nextNode;
        }

        public Node removeNowNode() {
            Node prev = this.prevNode;
            if(prev == null) return null;
            prev.nextNode = null;
            return prev;
        }
    }
}

 

2.  괄호_회전하기

대괄호, 중괄호, 그리고 소괄호로 이루어진 문자열 s가 매개변수로 주어집니다. 이 s를 왼쪽으로 x (0 ≤ x < (s의 길이)) 칸만큼 회전시켰을 때 s가 올바른 괄호 문자열이 되게 하는 x의 개수를 return 하도록 solution 함수를 완성해주세요.

 

코드

package com.codingTest.programers.level2;

import java.util.*;

public class 괄호_회전하기 {
    Map<Character, Integer> map;

    public 괄호_회전하기() {
        map = new HashMap<>();
        map.put('(', 0);
        map.put('[', 1);
        map.put('{', 2);
        map.put(')', 3);
        map.put(']', 4);
        map.put('}', 5);
    }

    public int solution(String s) {
        int len = s.length();

        int answer = 0;

        boolean flag;
        Stack<Character> checkPoint;

        int point = 0;
        for(int i = 0; i < len; i++){
            flag = false;
            checkPoint = new Stack<>();
            for(int j = 0; j < len; j++){
                char c = s.charAt((j + point)%len);
                int index = map.get(c);
                if(index < 3) {
                    checkPoint.push(c);
                } else {
                    if(checkPoint.isEmpty()) {
                        flag = true;
                        break;
                    }
                    char check = checkPoint.pop();
                    int prevIndex = map.get(check);
                    if(prevIndex != index%3) {
                        flag = true;
                        break;
                    }
                }
            }
            if(checkPoint.isEmpty() && !flag) {
                answer++;
            }
            point++;
        }

        return answer;
    }
}
728x90
728x90

오늘은 코딩 테스트 사이트인 Hackerrank라는 사이트에 대해 소개를 해볼까 합니다.

 

Hackerrank는 최근 모 기업 코딩테스트를 보면서 처음 만나게 된 사이트인데,

코딩테스트, 기술 체크 등 포괄적으로 본인이 가지고 있는 기술을 체크할 수 있는 사이트인 거 같습니다.

 

코딩테스트의 경우 Hackerrank는 모든 문제가 영어로 작성되어있는 만큼 문제 해석에 시간이 오래 걸릴 수 있는데요,

이러한 점 때문에 프로그래머스, 백준 보다 문제 자체를 이해하는데 살짝 어렵다고 개인적으로 느꼈습니다.

 

코딩 문제의 경우 백준 기준 브론즈부터 골드 1문제까지의 난이도를 만나보았으며, 문제의 난이도와 종류 그리고 언어에 따른 문제들도 다양하게 존재하고 있습니다.

 

코딩 테스트를 연습하고 싶다!! 라고 한다면 30 Days of code 라는 챌린지 형태의 튜토리얼도 지원해 주어 코딩 테스트 준비가 어색하다 하면 한 번쯤 해보는 것을 추천드립니다. 난이도는 매우 쉬움부터 날이 갈수록 어려워지는 구조로 코딩 테스트라는 것에 익숙해지는데 도움을 많이 받았습니다.

 

일반 코딩테스트 문제들만 존재한다면 굳이 소개를 할 필요는 없겠죠 제가 소개하게된 큰 이유는 기술 검증 메뉴에 있습니다.

 

이 기술 검증이란 주어진 시간내에 해당 주제에 대해 여러 문제들을 주고, 이를 통과하였을 때 인증서를 제공하는 서비스인데요,

Java와 Sql등 여러 가지의 언어들과 테스트할 수 있는 것들이 존재하는 만큼 본인이 어느 정도 자신이 있다 생각하면 한번 응시하여 기술 검증을 받아보는 것이 좋을 것 같습니다.

 

기술 검증은 계정당 1회 가능하며 만약 통과를 하지 못할 경우 재응시가 불가 하니 주의 바랍니다.

추후 업데이트를 통해 재응시가 가능하도록 하겠다 하지만 언제 업데이트가 이뤄질지 모르니 공부하고 응시해 보시기를 권장합니다.

 

기술 검증에 통과하게 되면 아래와 같이 이쁜 인증서도 지급해주니 인증서를 모아보는 것도 하나의 취미로 삼아도 괜찮을 것 같습니다.

 

HackerRank Skill Certificate

Join over 23 million developers in solving code challenges on HackerRank, one of the best ways to prepare for programming interviews.

www.hackerrank.com

 

 

 

Dashboard | HackerRank

Join over 23 million developers in solving code challenges on HackerRank, one of the best ways to prepare for programming interviews.

www.hackerrank.com

 

728x90
728x90

이번에 소개할 문제는 문자열 압축이라는 알고리즘 문제이다.

 

해당 문제는 데이터를 처리할 때 최소한의 메모리를 사용하여 문자열을 저장하고 싶은 게 핵심인 문제인 거 같다.

 

예를 들어보면 "가나다라마바사아"라는 문자열이 존재한다고 가정해보자.

이때 해당 문자열을 그대로 저장하게 되면 8글자를 Byte로 저장하게 된다.

위와 같이 중복된 글자가 없으면 그대로 저장하는 것이 데이터의 정확도상 당연한 것이라 생각한다.

 

하지만 "가가가나나나나나나다다다다다라라라마마바"라는 문자열이 있다고 생각해보자.

위와 같은 문자열이 있을 때 해당 문자열을 그대로 저장하는 것은 20개의 글자를 Byte로 저장하는 것이다.

이럴 때 동일한 문자가 연달아 존재할 때 몇 개인지를 앞에 명시하여 보다 짧은 문자열로 저장하는 것이 훨씬 적은 데이터를 가지고 저장을 할 수 있다.

 

또한 문자열을 줄일 때 1 단어 단위로 줄이면 "3가6나5다3라2마바"와 같은 형태로 저장되는 것이고,

2 단위로 줄이면 "가가가나2나나나다2다다라라라마마바"라는 문자열로 저장되는 식으로 몇 개 단위로 잘라 저장하느냐에 따라 저장되는 데이터 양에 차이를 주게 된다.

 

다시 문제로 돌아가서 해당 문제는 데이터를 최소한 짧은 문자열로 저장하고 싶은데 n단위로 잘라 저장했을 때 가장 적을 경우 가장 짧은 문자열일 경우 해당 문자열의 length를 구하여라로 생각하면 될 거 같다.

 

우선 default를 지정하도록 하자.

int answer = s.length();

해당 default로 지정한 경우는 어떤 방법으로 문자열을 압축한다 하더라도 기본 문자열보다 길어질 수 없기 때문에 기본 문자열의 length를 default로 지정하였다.

 

이후 먼저 중요하게 생각한 것은 문자열을 자를 건데 몇 개 단위로 자를지에 관해 먼저 생각하기로 하였다.

for(int i = 1; i< s.length() /2 +1; i++) {

}

문자열을 자를 거지만 절반을 넘어갈 경우 어차피 default와 길이는 같을 테니 적절하게 max를 지정해 주었다.

 

이후 잘라 만들어진 문자열과 이전 값을 비교하기 위한 변수와 몇 번째 압축인지를 알기 위한 변수를 선언해 주었다.

// i 는 자르는 갯수
for(int i = 1; i< s.length() /2 +1; i++) {
    StringBuffer sb = new StringBuffer();

    //이전값
    String bef = "";

    // 압축 count default 1
    int zipNum = 1;
}

이후부터는 subString으로 문자열을 자르기 위해 반복문을 돌려 문자열을 자르는 startPoint와 endPoint를 지정해 주도록 하자.

for (int j = 0; j < s.length(); j++) {

    //마지막 index
    int endNum = i+j > s.length() ? s.length() : i+j;

    //현재 
    String sub = s.substring(j, endNum);

}

위에서 나온 sub의 값은 현재 잘려 나온 문자열의 값이다.

 

현재 잘려나온 값을 알았으니 이제 해당 값이 압축이 가능한 문자열인지 check를 해볼 차례이다.

for(int i = 1; i< s.length() /2 +1; i++) {

    StringBuffer sb = new StringBuffer();
    String bef = "";
    int zipNum = 1;

	for (int j = 0; j < s.length(); j++) {
				
        //마지막 index
        int endNum = i+j > s.length() ? s.length() : i+j;

        //현재 
        String sub = s.substring(j, endNum);
    
        if (!sub.equals(bef)) {
        
            bef = sub;
            zipNum = 1;
        }
        else {
            zipNum++;
        }
    
    }
}

bef = 이전 값. 

sub = 현재 값.

위 두 값을 비교하여 같을 경우 압축 가능한 문자열이니 zipNum을 올려주며 끝나고,

문자열이 같지 않을 경우 압축할 수 없는 값이니 다음 단계를 진행해 주었다.

 

if (!sub.equals(bef)) {

    if(zipNum > 1) {
        sb.append(zipNum);
    }

    if (!bef.equals("")) {

        if (endNum != s.length()) {
            sb.append(bef);
        }
        else {
            sb.append(bef).append(sub);
        }	
    }

    bef = sub;
    zipNum = 1;
}
else {
    zipNum++;

    if (endNum == s.length()) {
        sb.append(zipNum).append(bef);
    }
}

자른 문자열이 이전과 이후가 같을 확률은 다를 경우보다 높아 if문 앞 조건에서 처리를 할 수 있도록 하였다.

 

zipNum이 1보다 클 경우 해당 문자열은 이미 앞에서 문자열 압축이 이뤄진 것이니 압축이 된 문자열 숫자를 붙여주고,

이전 문자를 붙여줌과 동시에 현재 잘린 문자열이 기존 문자열의 마지막 단계가 아닐 경우 bef에 값을 저장하여 이후 들어올 잘린 문자열이 현재 값과 비교를 할 수 있게 조절해준다.

 

문자열이 서로 같을 경우 zipNum을 1올려줌과 동시에 현재 문자열이 마지막 문자열인지 체크하여 값을 추가해주도록 하여 값이 빠지는 경우가 생기지 않게 해 주었다.

 

이대로 끝났으면 좋겠지만 아직 2가지를 더 처리를 해 주어야 한다.

 

우선 현재 문자열을 자를 때 j의 값을 사용하게 되는데 해당 값은 무조건 1씩 오르기 때문에 2개 이상의 단어를 자르게 될 시 값이 중복되어 나오는 경우가 생기게 된다.

 

설명을 하자면 본인이 원하는 경우는 1, 3, 5, 7, 9와 같이 2단계씩 건너뛰려 하는데 이 코드를 지금 상태로 사용하게 될 시 1, 2, 3, 4, 5, 6, 7, 8, 9와 같이 1단계씩 건너 2 단어씩 잘라 앞 문자열의 뒷 단어와 뒷 문자열의 앞 단어가 곂치는 현상이 발생하는 것이다.

 

이를 해결하기 위해선 

for(int i = 1; i< s.length() /2 +1; i++) {

    StringBuffer sb = new StringBuffer();
    String bef = "";
    int zipNum = 1;

	for (int j = 0; j < s.length(); j++) {
			
        int endNum = i+j > s.length() ? s.length() : i+j;

        if (!sub.equals(bef)) {
					
            if(zipNum > 1) {
                sb.append(zipNum);
            }

            if (!bef.equals("")) {

                if (endNum != s.length()) {
                    sb.append(bef);
                }
                else {
                    sb.append(bef).append(sub);
                }	
            }

            bef = sub;
            zipNum = 1;
        }
        else {
            zipNum++;

            if (endNum == s.length()) {
                sb.append(zipNum).append(bef);
            }
        }
    
    	//단계를 조절하기 위해 값을 현재 end값 -1로 명시해준다.
        j = endNum - 1;
    }
}

j = 문자열 자르는 단계 (index)

 

j를 마지막 잘랐던 index번호로 변경해주면 반복문이 작동하며 j++로 인해 의도했던 번호보다 1이 많아지게 되어 -1을 해주면 정상적으로 작동하게 된다.

 

subString의 용도상 startIndex부터 ~ endIndex전까지 잘라주게 되는데 위와 같이 해주면 이전 문자열의 index 다음 index부터 문자열을 잘라주게 되어 값의 중복 없이 정상적으로 작동하게 된다.

 

마지막으로

answer = answer > sb.toString().length() ? sb.toString().length() : answer;

잘랐던 문자열을 toString()으로 하나의 문자열로 뽑아 보았을 때 해당 문자열의 길이가 이전에 잘라서 합친 문자열보다 짧을 경우 해당 값을 정답으로 제출하기 위해 비교하며 저장한다.

 


제출 코드

class Solution {
    public int solution(String s) {
    
        int answer = s.length();
	
		for(int i = 1; i< s.length() /2 +1; i++) {
        
			StringBuffer sb = new StringBuffer();			
			String bef = "";
			int zipNum = 1;
            
			for (int j = 0; j < s.length(); j++) {
				
				int endNum = i+j > s.length() ? s.length() : i+j;				
				String sub = s.substring(j, endNum);
                
				if (!sub.equals(bef)) {
					
					if(zipNum > 1) {
						sb.append(zipNum);
					}
					
					if (!bef.equals("")) {

						if (endNum != s.length()) {
							sb.append(bef);
						}
						else {
							sb.append(bef).append(sub);
						}	
					}
					
					bef = sub;
					zipNum = 1;
				}
				else {
                
					zipNum++;
					
					if (endNum == s.length()) {
						sb.append(zipNum).append(bef);
					}
				}
				j = endNum - 1;
			}
			answer = answer > sb.toString().length() ? sb.toString().length() : answer;
		}
		
        return answer;
    }
}

 


해당 문제를 풀면서 stream을 사용하며 풀거나 재귀 함수를 사용할 시 훨씬 코드가 줄어들게 되지만 아직 실력이 부족하기도 하고 외부 라이브러리를 사용하지  않고 직접 돌아가는 상황을 작성해보고 싶어 위와 같이 작성하게 되었다.

 

다음에 stream이나 재귀함수를 공부하며 Refactoring 해 보아야겠다.

728x90

+ Recent posts