작년 나름 릿코드 열심히 들어가서 문제를 풀었다고 생각했는데 올해 처음 들어가니 100 일 짜리 뱃지 를 받았다 ㅎㅎ

내년 에는 200 일 짜리 뱃지 를 받아보도록 노력해보자.

 

944. Delete Columns to Make Sorted

주어진 문자 를 나열하고 세로로 읽었을때 사전 순이 되지 않는 경우를 판별하는 문제이다. 모든 문자열의 길이가 똑같다는 전제조건 이 있어서 이지 난이도라고 생각된다.

 

더보기
class Solution {
    public int minDeletionSize(String[] strs) {
        int answer = 0;
        int len = strs[0].length();
        for(int i=0;i<len;i++){
            for(int j=1;j<strs.length;j++){
                char a = strs[j].charAt(i);
                char b = strs[j-1].charAt(i);
                if( a < b){
                    answer++;
                    break;
                }
            }
        }
        return answer;
    }
}

배열의 최대 크기 1000, 문자의 최대길이 100 => 2중 포문 돌려도 문제없다고 생각해서 바로 작성했다. 

최초 작성시 에 i, j 를 이상하게 지정을 해버리는 바람에 indexOutOfRange 에러가 났다. 언제쯤 이런 잔 실수를 없앨수 있는지...

그래도 제출하니 바로 통과 가 되긴한다.

 

대부분 의 solution code 들도 이중포문 을 이용해서 작성 되어있다.

 

 

520. Detect Capital

 

주어지는 문제 의 조건 에 맞춰서 구현만 해주면 된다. 

더보기
class Solution {
    public boolean detectCapitalUse(String word) {
        char first = word.charAt(word.length()-1);
        if('A' <= first && first <= 'Z'){
            for(char c : word.toCharArray()){
                if(!('A' <= c && c <= 'Z')) return false;
            }
        }else{
            for(int i=word.length()-1;i>=0;i--){
                char c = word.charAt(i);
                if(!('a' <= c && c <= 'z') && (i!= 0)) return false;
            }
        }
        return true;
    }
}

그냥 문제 에서 주어진 그대로 구현했다. 

이 방법 외에는 regex 가 있다. 

class Solution {
    public boolean detectCapitalUse(String word) {
        return word.matches("[A-Z]*|[A-Z]?[a-z]*");
    }
}

대문자 이거나 혹은 대문자가 있거나 없거나 그리고 나머지 소문자 로 채우면 된다.

이것 외에 괜찮다고 생각하는 코드가 있어서 들고왔다.

더보기
public boolean detectCapitalUse(String word) {
        if (word.length() < 2) return true;
        if (word.toUpperCase().equals(word)) return true;
        if (word.substring(1).toLowerCase().equals(word.substring(1))) return true;
        return false;
}

반대로 true 가 되는 조건만 을 subString 과 toUpperCase 를 이용해 깔끔하게 코드 를 구사했다. 

그러나 toUpperCase 의 작동하는 방식을 보면 내부적으로 forLoop 를 돌려서 내부로직 수행후 String 을 리턴해준다.

하물며 subString 은 어떤가 String 은 불변 이다 따라서 클래스 내부 의 value 바이트 코드 배열을 arrayCopy 를 수행해 새로운 스트링을 만들어 뱉어준다. 즉 for 를 한번더 돈다는 의미이다. eqaul 또한 하나씩 비교하기 위해 forLoop 을 돈다 

이렇게 수행 로직 만 보고 생각한다면 느릴수 밖에없다. 다만 이해하기 쉽다. 

실제 업무에 투입되어 작성한코드 라면 사실 나는 이 코드 처럼 작성하고 싶다. 성능상의 문제가 있는 것 이 아니라면 코드 자체를 이렇게 두고 유지할것이다. 왜 ? 읽기 쉬운 코드가 최고니깐.

 

290. Word Pattern

주어진 패턴에 맞는 스트링 값을 찾아 반환 해주면 되는 문제이다.

 

더보기
class Solution {
    public boolean wordPattern(String pattern, String s) {
        Map<Character,String> map = new HashMap();
        Set<String> set = new HashSet();

        int len = pattern.length();
        String[] arr = s.split(" ");

        if(len != arr.length) return false;
        for(int i=0;i<len;i++){
            char p = pattern.charAt(i);
            if(!map.containsKey(p) && set.add(arr[i])){
                map.put(p,arr[i]);
            }
            if(!map.getOrDefault(p,"ㄱ").equals(arr[i])) return false;
        }

        return true;
    }
}

쫌 과하다 싶을 만큼 자료구조 를 선언해서 사용했다. Set 없이 그냥 map.containsValue 를 사용해도 됬었다. 다만 containsValue 를 사용해서 forLoop 를 두번돌게 하고 싶지 않았다. 

Map 을 두번써서 좌우로 맵핑 한 사람도 있고, 다양한 풀이방법 이 있다.

본인이 선호하는 방법으로 작성하면 된다고 생각한다. 

 

이렇게 3문제를 풀어도 2,3 번 같은경우는 2~3번씩 제출을 더 했다. 로직 의 오류, 예외케이스 핸들링 생략 등이 그 이유이다.

언제쯤 풀어야 이런 이지 난이도 를 한번에 클리어 할수 있을까.... 

'PS > LeetcCode' 카테고리의 다른 글

LeetCode Daily 문제풀기  (0) 2023.01.05
1월4일 문제 풀기  (2) 2023.01.04
Binary Tree Maximum Path Sum  (0) 2022.12.11
1339. Maximum Product of Splitted Binary Tree  (0) 2022.12.10
Easy 문제 풀기 Day1(79/610)  (0) 2022.12.03

트리 내에 존재하는 엣지 를 타고 가장 합이 높은 부분을 구하는 문제이다. 어제와 유사한 문제라고 생각해서 비슷하게 접근했으나 머리가 대차게 깨져버렸다.

어제 와 유사한 코드로 접근하였으나 - 값의 여부와 한가지 패스 안에서는 길을 타고 다시 거슬러 올라오면 안 된다는 부분에서 신경이 쓰여 preorder를 사용했다. 중복을 처리해줄 방법이 없어 생각보다 애를 많이 먹었다... 

if를 이용해서 틀릴 때마다 처리를 해주었으나. 99개 테스트 케이스에서 56 이 한계였다. preOrder, postOrder 전부 실패했다. prefix를 이용하기 위해 리스트 넘기는 방법, max를 이용해 현재 비교대상을  3번 비교하는 방법 전부 실패했다.

뭔가 잡힐듯 말듯한데 자꾸 실패해서 분하다...

 

약 1시간 정도 풀다가 실패해서 discuss를 보고 오니 내가 구해야 할 명확한 목적을 알고 나니 풀이가 쉬워졌다.

 

머리 깨진 풀이

class Solution {
    int max;
    public int maxPathSum(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        max = Integer.MIN_VALUE;
        helper(root, list);
        int total = list.get(list.size()-1);
        for (int i = 0; i < list.size()-1; i++) {
            max = Math.max(Math.max(total,total-list.get(i)),max);
        }
        return max;
    }

    public void helper(TreeNode node, List<Integer> list){
        if(node == null) return;
        helper(node.left, list);
        int previous = list.size() == 0 ? 0 : list.get(list.size()-1);;
        list.add(previous+node.val);
        max = Math.max(max,node.val);
        helper(node.right,list);
    }
}

트리 순회를 위해 helper를 선언해 중위 순회를 선택했다. 이유는 후위 순회를 하게 된다면 리프부터 타고 올라가야 하는데 이 기준점을 정해주기 가 생각보다 어려웠다. prefix를 사용해야 한다는 압박감 때문 인지. 후위 순회를 하게 되니 오류가 많이 생겼다. 

디스커스에서 읽고 바로 풀이 방법을 바꾼 힌트 를 보자.

우리가 구해야 할 4가지 경우의 수가 있다.

1. 왼쪽 패스 + 현재 값

2. 오른쪽 패스 + 현재 값

3. 왼쪽 패스 + 오른쪽 패스 + 현재 값

4. 현재 값 자체로 위 값보다 큰 경우

이 4 가지를 명확히 하고 갔어야 했는데 이게 안되니 null 이 여러 번 들어간 깊이 있는 트리인 경우 오답이 나게 되는 것이다. ㅠ

 

위 4가지 힌트를 얻어서 작성한 풀이법이다. 

class Solution{
	int max = Integer.MIN_VALUE;
    
	public int maxPathSum(TreeNode root) {
        helper(root);
        return max;
    }
    
    public int helper(TreeNode node){
    	if(node == null) return Integer.MIN_VALUE;
        int left = helper(node.left);
        int right = helper(node.right);
        
        max = Math.max(max,Math.max(left,right));
        max = Math.max(max,node.val);
        max = Math.max(max,node.val+Math.max(left,0)+Math.max(right,0));
        return node.val + Math.max(0,Math.max(left,right));
    }
}

max 값을 비교할 Integer를 선언해주고 , 현재 값이 - 값이라면 무조건 0으로 초기화시켜주었다. 

left 패스, right 패스를 각각 구하고 현 max와 패스의 합 +현재 값

그리고 현재 값에 패스 값 중 높은 값을 다음으로 넘겨주는 재귀를 작성하였다. 

 

class Solution{
	int value = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
        int temp = process(root);
        return Math.max(temp, value);
    }

    public int process(TreeNode head){
        if(head == null){
            return 0;
        }
        int leftTree = Math.max(0, process(head.left));
        int rightTree = Math.max(0, process(head.right));
        value = Math.max(value, leftTree + rightTree + head.val);
        return Math.max(leftTree, rightTree) + head.val;
    }
}

이 사람도 보면 참 깔끔하게 작성한 거 같다. 왼쪽 패스, 오른쪽 패스를 나누고 현재 값의 맥스를 왼쪽 오른쪽의 합과 현재 값을 이용하고 다음 재귀로 왼쪽 오른쪽 중 큰 값을 현재 값에 더해 넘겨준다.

 

maxValue = Math.max(maxValue, left + right + node.val);

작성할 때 난감 했던 부분이다. maxVaule를 현재 와 왼쪽 오른쪽 그리고 노드의 값을 합친 값으로 업데이트를 하는 것이 아닌가.

문제의 포인트를 잡지 못해서 이상한 풀이법으로 나가 결국 맞지 못한 문제였다.

'PS > LeetcCode' 카테고리의 다른 글

1월4일 문제 풀기  (2) 2023.01.04
Easy 문제 풀기 Day2(83/618)  (0) 2023.01.03
1339. Maximum Product of Splitted Binary Tree  (0) 2022.12.10
Easy 문제 풀기 Day1(79/610)  (0) 2022.12.03
BFS 문제들을 풀어보자 1탄  (0) 2022.08.25

주어진 이진트리에서 하나의 엣지 를 잘라서 각각의 서브 트리를 만들 때 두 서브 트리 합의 곱이 가장 큰 값을 리턴하는 문제이다.

10**4 까지 노드 가 주어진다고 가정한다면 9999개 의 엣지가 생길 수 있고 완탐 때려도 충분히 가능하다고 생각해 규칙을 찾기 시작했다. 

주어지는 트리가 이진트리 라고 가정한다면 최초 만나는 이중 노드에서 각각의 서브 트리 연산을 진행해 최소 최대 값을 구해 곱해주면 된다고 생각했다.

첫 번째 코드를 보자.

class Solution {
    int total = 0;
    int module = (int) Math.pow(10,9)+7;
    public int maxProduct(TreeNode root) {
        if(root == null) return 0;
        TreeNode hasSubNodes = getTotalBeforeSplit(root);
        
        int subTotalLeft = getSubTotal(hasSubNodes.left);
        int subTotalRight = getSubTotal(hasSubNodes.right);
        total += Math.min(subTotalLeft, subTotalRight);
        return (total * Math.max(subTotalLeft,subTotalRight))%module;
    }
    public int getSubTotal(TreeNode node){
        int subTotal = 0;
        if(node == null) return subTotal;
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(node);
        while(!q.isEmpty()){
            TreeNode cur = q.poll();
            subTotal += cur.val;
            if(cur.left != null) q.offer(cur.left);
            if(cur.right != null) q.offer(cur.right);
        }
        return subTotal%module;
    }

    public TreeNode getTotalBeforeSplit(TreeNode root){
        total = (total+root.val)%module;
        if(root.left != null && root.right == null){
            return getTotalBeforeSplit(root.left);
        }
        if(root.right != null && root.left == null)
            return getTotalBeforeSplit(root.right);
        return root;
    }
}

getTotalBefoeSplit을 이용해 최초 만나는 이중 노드까지 의 합과 현재 노드의 위치를 기록했다. 

getSubTotal을 이용해 좌우 값을 모두 연산해 주었다. 

테스트 코드는 잘통과 된다. 바로 제출했다. 실패 ㅠㅠ

내 로직의 문제점 이 발생했다. 편향 트리인경우 내 로직이 작동하지 않는다... 노드를 끝까지 탐색해 결국 0이라는 답이 나온다. 

if를 넣어 편향 트리인 경우를 핸들링하려고 했으나 깊이를 알 수 없기 때문에 불가능하다는 결론에 도달.

 

만약 배열에서 내가 이걸 계산한다면 어떻게 할까 고민 하다 보니 prefix를 이용한 배열을 이용해 구한다는 생각에 도달했다.

 1. 모든 노드 의 합을 구한다.

2. 모든 노드 의 합을 구하는 히스토리를 입력해 prefix를 작성한다.

3. prefix 기준으로 각 서브트리의 합은 prefix , 모든 노드의 합 - prefix이다.

 

로직 이 보다 명확해졌다.

 

사실 long 타입을 쓰기 싫어서 모듈을 각각 연산마다 해주려고 했더니 prefix 본질에 어긋나 버린다. 만약 1e9 + 7의 값이 넘어가 버린다면 모듈 된 값이 prefix에 기입되어 명확한 토털 값 계산이 되질 않는다. 

 

이렇게 두개의 값을 이용해 차이 값을 구할때 prefix 합을 염두해 두고 풀이법 을 생각해보는것도 좋은것 같다.

 

 

'PS > LeetcCode' 카테고리의 다른 글

Easy 문제 풀기 Day2(83/618)  (0) 2023.01.03
Binary Tree Maximum Path Sum  (0) 2022.12.11
Easy 문제 풀기 Day1(79/610)  (0) 2022.12.03
BFS 문제들을 풀어보자 1탄  (0) 2022.08.25
33. Search in Rotated Sorted Array  (0) 2022.07.04

27. Remove Element

주어진 배열 과 값이 나오고 배열 내에서 해당 값을 지워서 리턴해주면 되는 간단한 문제이다. 

다만 문제의 조건상 에 추가적인 메모리 가 없기 떄문에 배열 자체를 수정해서 리턴 해야한다. 

class Solution {
    public int removeElement(int[] nums, int val) {
        int left =0;
        for(int i=0;i<nums.length;i++){
            if(nums[i] != val){
                int tmp = nums[left];
                nums[left++] = nums[i];
                nums[i] = tmp;
            }
        }
        return left;
    }
}

left 값 하나 를 선정해서 투포인터 느낌으로 스왑 해가면서 풀었다.  for loop 자동으로 증가하는 값을 right 로 생각하면 보다 쉽게 풀듯하다.

leetcode post 

 

58. Length of Last Word

문자 가 주어지면 그 스트링 내에서 가장 마지막에 있는 문자 의 개수를 반환하는 문제이다.

class Solution {
    public int lengthOfLastWord(String s) {
        int len = s.length()-1;
        int cnt =0;
        for(int i =len;i>=0;i--){
            if(!Character.isDigit(s.charAt(i)) && s.charAt(i) != ' '){
                cnt++;
            }
            if(cnt != 0 && s.charAt(i) == ' '){
                return cnt;
            }
        }
        return cnt;
    }
}

 

 

뒷방향 부터 탐색해 나가면서 카운트 가 시작되어있는 상태에서 공백을 만나면 반환을 하는 함수를 작성했다. 

사실 트림을 이용해서 하면 아래 추가적인 if 문 없이 작성할수 있으나 trim 내부적으로 ' ' 이걸 만나지 않을때 까지 와일 문을 앞뒤로 돌린다. 그게 싫어서 저렇게 작성했다.

leetcode post

66. Plus One

나름 난감했던 문제 였다. Stack 을 이용하지 않고 구현을 해보려고 노력하다 보니 그런거 같다.

class Solution {
    public int[] plusOne(int[] digits) {
        int len = digits.length-1;
        StringBuilder answer = new StringBuilder();
        int willAdd = 1;

        for (int i = len; i >=0 ; i--) {
          int next = digits[i] + willAdd;
          answer.append(next%10);
          if(next > 9) willAdd = 1;
          else willAdd = 0;
        }
        if(willAdd == 1) answer.append(1);

        int[] ans = new int[answer.length()];
        for (int i = 0; i < ans.length; i++) {
          ans[i] = answer.charAt(ans.length-1-i)-'0';
        }

        return ans;
    }
}

1ms 가 나왔다 스택을 이용해서 속도적인 차이가 궁금해서 바로 구현해 봤다.

class Solution {
    public int[] plusOne(int[] digits) {
        Stack<Integer> stack = new Stack<>();
        int len = digits.length-1;
        int willAdd = 1;

        for (int i = len; i >= 0; i--) {
            int val = digits[i]+willAdd;
            stack.push(val%10);

            if(val > 9) willAdd = 1;
            else willAdd = 0;
        }

        if(willAdd == 1) stack.push(1);

        int[] answer = new int[stack.size()];

        for (int i = 0; i < answer.length; i++) {
            answer[i] = stack.pop();
        }
        
        return answer;
    }
}

2ms 가 나온다 더 느리다 .
스택은 백터 를 상속받아서 구현 된 클래스다. 따라서 스레드 세이프 하다는 의미이다. 대부분의 함수에 syncronize 가 걸려있다.

동기화가 제거된 버전인 Deque 를 사용해보자 보통 queue , stack 에서 deque 를 사용하면 성능적인 측면에서 좋은 효과가 있다 왜 ?

스레드 세이프 하지 않으니깐. 1ms 가 나온다. 

 

다른 사람의 풀이를 한번 확인해보자.

class Solution {
    public int[] plusOne(int[] digits) {
        
    int n = digits.length;
    for(int i=n-1; i>=0; i--) {
        if(digits[i] < 9) {
            digits[i]++;
            return digits;
        }
        
        digits[i] = 0;
    }
    
    int[] newNumber = new int [n+1];
    newNumber[0] = 1;
    
    return newNumber;
}
}

와.... 생각지도 못했던 부분이다. 물론 9 이하인경우 +1 해서 리턴 생각을 했지만 구현의 구체적인 방향성이 떠오르지 않아 위와같이 작성했는데.... 0ms 나온다. 저 9이하 리턴 부분이 최적화 가 완성된 부분이기떄문에 2배이상 빠른것 같다.

leetcode post

 

갑자기 내 첫번쨰 로직에 포함된 StringBuilder 와 StringBuffer 의 차이가 궁금해졌다.

라이브 코딩을 하게 되면 인터뷰어를 설득해야한다.

저런 클래스에 사용에 있어 명확한 이유가 있어야 한다. 이번기회 에 한번 찾아보고자 한다.

 

기본적으로 String 은 불변객체이다.

스트링은 + 연산자가 되던데 ?  라고 생각하면 착각이다. 새로운 메모리가 할당되어 포인팅 된다.

즉 기존에 가지고 있던 포인터 부분은 가비지컬렉터 제거 대상이 되어진다. 이렇게 되면 메모리 어뷰징이 발생되고 빈번한 문자 변경에 성능상 이슈가 야기될수 있기 때문에

자바에서 StringBuffer 를 처음 만들게 된다.(Java 1.0 에서 등장한다.)

최초 만들때 즉 가변성 있는 객체를 만들고자 buffer 를 만들고 쓰레드 세이프 한 방식으로 구현되어진다.

(실제 클래스 내부에 들어가보면 대부분의 함수들이 syncronize 가 달려있다)

스택 과 동일하게 동기화 함수가 걸려있다면 성능적으로 느려진다.

대부분의 스트링을 이용한 케이스를 고려해본다면 멀티쓰레드 환경에서 사용되어 지지 않는다. 이부분을 고려해 java 1.5 에서 StringBuilder 가 등장하게 된다. 당연히 동기화 함수 는 제거된 상태로 구현되어 있다. 

 

StringBuffer,Builder 두종류 모두 comparable 을 구현한다. 따라서 priorityQueue 에 넣어서 사용할수 있는데 String 의 정렬과 동일한 방식으로 된다. 즉 사전시으로 정렬된다.

  public static void main(String[] args) {
    PriorityQueue<StringBuilder> a = new PriorityQueue<>();
    a.add(new StringBuilder("c"));
    a.add(new StringBuilder("a"));
    a.add(new StringBuilder("b"));
    a.add(new StringBuilder("132"));
  }

이렇게 넣게되면 ? [132, a, b, c] 이런식의 정렬된 값이 들어가게 된다.

 

이지 문제였지만 나름 혼자 이것저것 고민해보면서 재밌는 시간을 보낸거 같다.

나는 Bfs를 못한다. 따로 bfs의 문제의 풀이 흐름을 이해하기 위해 bfs101을 하려고 한다.

나중에 안 사실인데 title 순으로 정렬하다 보니 tree 문제만 풀었다 ㅎㅎㅎ... 

아름다운 코드들은 해당 문제 discuss 를 확인하시면 멋진 아이디어들이 다양하게 있으니 한번 찾아보자.

 

100Same Tree

풀러 가기

트리노드 두 개가 주어지고 두 개의 노드가 동일한 노드인지 를 체크하는 문제이다. 바로 루트 노드부터 bfs로 트리를 계층으로 타고 내려 갈 생각 하면서 비교를 할 생각을 하였다.

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
       if(p == null && q == null) return true;
        if(p == null || q == null) return false;
        Deque<TreeNode> q1 = new ArrayDeque<>();
        Deque<TreeNode> q2 = new ArrayDeque<>();
        q1.add(p);
        q2.add(q);
        
        while(!q1.isEmpty() && !q2.isEmpty()){
            TreeNode curOne = q1.poll();
            TreeNode curTwo = q2.poll();
            
            if(curOne ==null && curTwo != null) return false;
            if(curOne != null && curTwo == null) return false;
            if(curOne.val != curTwo.val) return false;
            
            //left null check
            if(curOne.left != null){
                q1.add(curOne.left);
                if(curTwo.left == null){return false;}
            }
            if(curTwo.left != null){
                q2.add(curTwo.left);
                if(curOne.left == null){return false;}
            }
            //right null check
            if(curOne.right != null){
                q1.add(curOne.right);
                if(curTwo.right == null){return false;}
            }
            if(curTwo.right != null){
                q2.add(curTwo.right);
                if(curOne.right == null){return false;}
            }
        }
        return true;
    }
}

릿코드 답게 예상하지 못한 예외 케이스가 존재한다 ㅎㅎ, 풀이 의 과정은 2개의 2 덱을 선언해서 각각의 트리를 bfs로 순환해서 타 줄 것이다. 대신 그 중간에 null 값은 들어가지 못하게 예외처리를 분명하게 해주어야 한다. 이렇게 해도 통과되는 이유는 양쪽의 노드가 동일한 노드인지를 확인해주는 것이기 때문에 null 값은 체크 안 하고 넘겨주면 된다. if 처리가 복잡해서 그렇지 시간적 여유를 가지고 천천히 작성하면 한 번에 통과될 수준의 문제이다.

이 코드가 이해가 안 된다면 뒤에 문제들 은 전부 손볼 수가 없다. 확실히 이해하고 넘어가자. 

참고로 모든 트리 순회 문제는 dfs 풀이가 2000배 섹시하다. 내가 좋아하는 풀이법이니 첨부하자.
Dfs풀이

더보기
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        return isSameNode(p, q);
    }
    public boolean isSameNode(TreeNode p, TreeNode q){
        if(p == null && q == null){
            return true;
        }else if(p == null || q == null){
            return false;
        }
        if((p.val != q.val) || !isSameNode(p.left, q.left) || !isSameNode(p.right, q.right)){
            return false;
        }
        return true;
    }
}

101Symmetric Tree

풀러 가기

음? ㅋㅋㅋ 위의 문제가 정확히 일치하는 트리노드를 찾는 것이라면 이건 대칭되는 트리 노드를 찾는 것이다. 즉 위의 코드에서 q에 들어가 탐색할 대상이 deque 1 은 기존과 동일하게 그렇지만 deque 2는 기존과 반대로 넣어주면 해결되는 문제이다. 이해가 안 된다면 4층 레이어도 그려 본다면 느낌이 올 것이다. 풀이를 보자 위의 풀이와 다를게 크게 없다.

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root.left == null && root.right == null) return true;
        if(root.left == null || root.right == null) return false;
        
        Deque<TreeNode> left = new ArrayDeque<>();
        Deque<TreeNode> right = new ArrayDeque<>();
        
        left.add(root.left);
        right.add(root.right);
        
        while(!left.isEmpty() && !right.isEmpty()){
            TreeNode leftPoll = left.poll();
            TreeNode rightPoll = right.poll();
            
            if(leftPoll.val != rightPoll.val) return false;
            //left check
            if(leftPoll.left != null && rightPoll.right == null) return false;
            if(leftPoll.left == null && rightPoll.right != null) return false;
            if(leftPoll.left != null && rightPoll.right != null){
                left.add(leftPoll.left);
                right.add(rightPoll.right);
            }
            //right check
            if(leftPoll.right != null && rightPoll.left == null) return false;
            if(leftPoll.right == null && rightPoll.left != null) return false;
            if(leftPoll.right != null && rightPoll.left != null){
                left.add(leftPoll.right);
                right.add(rightPoll.left);
            }
        }
        if(left.size() > 0 || right.size() > 0) return false;
        return true;
    }
}

처음 주어지는 노드의 null 체크를 진행한 후 bfs를 진행한다. 비교대상이 될 왼쪽의 왼쪽 자식, 오른쪽의 오른쪽 자식을 null 및 추가해주는 과정을 진행하고 반대로 왼쪽의 오른쪽 자식, 오른쪽의 왼쪽 자식을  비교해주면 된다. 

while의 과정을 각 덱의 사이즈 0 전까지만 실행한 이후에도 덱 안에 노드가 남아있다면 그건 비대칭 트리이다 한쪽이 더 길다는 소리이니깐..

Dfs풀이

더보기
public boolean isSymmetric(TreeNode root) {
    return root==null || isSymmetricHelp(root.left, root.right);
}

private boolean isSymmetricHelp(TreeNode left, TreeNode right){
    if(left==null || right==null)
        return left==right;
    if(left.val!=right.val)
        return false;
    return isSymmetricHelp(left.left, right.right) && isSymmetricHelp(left.right, right.left);
}

102Binary Tree Level Order Traversal

풀러 가기

오 계층별로 트리를 탐색하는 문제이다. 이 문제를 보자마자 백준의 토마토 완숙이랑 리코드 썩은 오렌지가 생각난다.

미디엄 난이도인데 위에 트리 발리데이션 체크하는 거보다 쉽게 풀었다. 각 층 별로 탐색한 후 list에 담아 준다고 생각하면 된다. 이건 말보다 코드를 보는 것이 2000 배 이해하기 쉽다.

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if(root == null) return result;
        Deque<TreeNode> q = new ArrayDeque<>();
        q.add(root);
        
        while(!q.isEmpty()){
            int n = q.size();
            List<Integer> attach = new ArrayList<>();
            
            for(int i=0;i<n;i++){
                TreeNode cur = q.poll();
                attach.add(cur.val);
                
                if(cur.left != null){
                    q.add(cur.left);
                }
                if(cur.right != null){
                    q.add(cur.right);
                }
            }
            result.add(attach);
        }
        return result;
    }
}

최초 정답을 배열 List를 생성하고 만약 주어지는 노드가 null이라면 그냥 비어있는 list를 반환해준다.

기본적인 틀은 위와 동일하게 현재 노드들을 담을 덱을 만들고 bfs이다

단 while 안에서는 현재 덱 안에 들어있는 사이즈만큼 돌게 된다면? 그것이 바로 현재 진행 중인 깊이이다. 따라서 이 깊이에 따라 새로운 리스트를 만들고 그 리스트를 정답에 추가해주면 되는 것이다.

Dfs풀이

더보기

 

public List<List<Integer>> levelOrder(TreeNode root) {
		List<List<Integer>> res = new ArrayList<>();
		if (root == null)
			return res;
		levelOrderHelper(res, root, 0);
		return res;
	}
	
	public void levelOrderHelper(List<List<Integer>> res, TreeNode root, int level) {
		if (root == null)
			return;
		List<Integer> curr;
		if (level >= res.size()) {
			curr = new ArrayList<>();
			curr.add(root.val);
			res.add(curr);
		} else {
			curr = res.get(level); 
			curr.add(root.val); 
			//res.add(curr); // No need to add the curr into the res, because the res.get(index) method does not remove the index element
		}
		levelOrderHelper(res, root.left, level + 1);
		levelOrderHelper(res, root.right, level + 1);
	}

 

103Binary Tree Zigzag Level Order Traversal

풀러 가기

응...? 이번에는 계층 간의 출력이긴 하지만 중간에 계속 방향을 바꾸어 주어야 한다. 홀수 층 일 때는 정방향 짝수층일 때는 역방향이니 홀수 짝수 층 구분할 불리언 변수로 주고 , 컬렉션 리버스를 이용해 정답에 계속 더해주자.

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if(root == null) return result;
        
        Deque<TreeNode> q = new ArrayDeque<>();
        q.add(root);
        boolean isOdd = true;
        while(!q.isEmpty()){
            int n = q.size();
            List<Integer> attach = new ArrayList<>();
            for(int i=0;i<n;i++){
                TreeNode cur = q.poll();
                attach.add(cur.val);
                
                if(cur.left != null){
                    q.add(cur.left);
                }
                if(cur.right != null){
                    q.add(cur.right);
                }
                
            }
            
            if(isOdd){
                result.add(attach);
            }else{
                Collections.reverse(attach);
                result.add(attach);
            }
            isOdd = !isOdd;
        }
        return result;
    }
}

정확하게 위위 코드와 같고 중간에 컬렉션 리버스와 odd라는 불리언을 변경해준다.

Dfs풀이

더보기
 public List<List<Integer>> zigzagLevelOrder2(TreeNode root) {
     List<List<Integer>> ret = new ArrayList<>();
     dfs(root, 0, ret);
     return ret;
 }
 
 private void dfs(TreeNode node, int l, List<List<Integer>> ret) {
     if (node != null) {
         if (l == ret.size()) {
             List<Integer> level = new ArrayList<>();
             ret.add(level);
         }
         if (l % 2 == 1) {
            ret.get(l).add(0, node.val);  // insert at the beginning
         } else {
            ret.get(l).add(node.val);
         }
         dfs(node.left, l+1, ret);
         dfs(node.right, l+1, ret);
     }
 }

 

104Maximum Depth of Binary Tree

풀러 가기

노드의 최대 깊이를 찾아주는 문제이다. bfs를 돌려 q 안에 값이 없을 때 까지 돌린다면 최고 층을 손쉽게 찾을 수 있다.

class Solution {
    public int maxDepth(TreeNode root) {
        int result = 0;
        if(root == null) return result;
        Deque<TreeNode> q = new ArrayDeque<>();
        q.add(root);
        while(!q.isEmpty()){
            int n = q.size();
            for(int i=0;i<n;i++){
                TreeNode cur = q.poll();
                if(cur.left != null){
                    q.add(cur.left);
                }
                
                if(cur.right != null){
                    q.add(cur.right);
                }
            }
            result++;
        }
        return result;
    }
}

계층 별로 탐색하기 위해 사이즈만큼 폴을 진행하고 더해준다. 그러한 일련의 과정 중 깊이를 측정해 리턴해준다.

Dfs풀이

더보기
public int maxDepth(TreeNode root) {
        if(root==null){
            return 0;
        }
        return 1+Math.max(maxDepth(root.left),maxDepth(root.right));
    }

107Binary Tree Level Order Traversal II

풀러 가기

트리 레벨 순회인데 역순이다. 위에서 진행했던 트리 오더를 리벌스 해서 제출해주자.

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if(root == null) return result;
        Deque<TreeNode> q = new ArrayDeque<>();
        q.add(root);
        while(!q.isEmpty()){
            int n = q.size();
            List<Integer> attach = new ArrayList<>();
            for(int i=0;i<n;i++){
                TreeNode cur = q.poll();
                attach.add(cur.val);
                if(cur.left != null) q.add(cur.left);
                if(cur.right != null) q.add(cur.right);
            }
            result.add(attach);
        }
        Collections.reverse(result);
        return result;
    }
}

이렇게 말고도 정답을 linkedList로 생성해서 result.add(0, attach); 이런 식으로 하는 사람도 있었다.  확실히 리버스 보다 링크드 리스트 앞으로 삽입 하느것이 빠르다 속도적인 측면에서의 차이는 없었으나 제출자 들 사이에서 의 속도 차이는 많이 유의미했다.

Dfs풀이

더보기
public class Solution {
        public List<List<Integer>> levelOrderBottom(TreeNode root) {
            List<List<Integer>> wrapList = new LinkedList<List<Integer>>();
            levelMaker(wrapList, root, 0);
            return wrapList;
        }
        
        public void levelMaker(List<List<Integer>> list, TreeNode root, int level) {
            if(root == null) return;
            if(level >= list.size()) {
                list.add(0, new LinkedList<Integer>());
            }
            levelMaker(list, root.left, level+1);
            levelMaker(list, root.right, level+1);
            list.get(list.size()-level-1).add(root.val);
        }
    }

 

111Minimum Depth of Binary Tree

풀러 가기

음? 이번에는 트리 깊이의 최솟값을 구하면 된다 bfs 돌다가 처음 자식들이 null 인 경우의 깊이를 반환해주면 된다.

class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) return 0;
        int d = 1;
        Deque<TreeNode> q = new ArrayDeque<>();
        q.offer(root);
        while(!q.isEmpty()){
            int n = q.size();
            // for each level
            for(int i=0;i<n;i++){
                TreeNode node = q.poll();
                if(node.left == null && node.right == null){
                    return d;
                }
                if(node.left != null){
                    q.offer(node.left);
                }
                if(node.right != null){
                    q.offer(node.right);
                }
            }
            d++;
        }
        return d;
	}
}

 

 

특별할 것 없는 코드이다. 위에 설명한 대로 최초 만난 자식 없는 노드는 리프임으로 현재 깊이를 반환해준다.

이 문제는 dfs 보다는 bfs 가 더 괜찮다고 생각한다. 왼쪽에는 노드 한 개가 있지만 오른쪽은 노드가 500 층까지 있다고 생각하면 bfs의 경우 왼쪽 노드 한 개를 탐 새하고 리턴 하지만, dfs의 경우 반대쪾 500 개 모두를 탐색해야 한다. 그래도 dfs 코드는 섹시하니깐 첨부한다.

Dfs풀이

더보기
public class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) return 0;
        int left = minDepth(root.left);
        int right = minDepth(root.right);
        return (left == 0 || right == 0) ? left + right + 1: Math.min(left,right) + 1;
       
    }
}

112. Path Sum

풀러 가기

이 문제는 트리 가 주어지고 타깃 값이 주어지면 현재 시작점부터 리프까지 그중에 타겟값이 존재하냐는 문제이다. 처음 - 값의 제한상을 보지 못해서 트리의 bfs를 진행함과 동시에 가중치를 넘겨주었다 이 가중치가 타깃보다 크다면 q에 추가를 하지를 않았는데 제한사항을 보니 - 값이 있기에 위의 최적화 부분을 제외해 주었다.

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if(root == null) return false;
        Deque<TreeNode> q = new ArrayDeque<>();
        q.add(root);
        while(!q.isEmpty()){
            TreeNode cur = q.poll();
            if(cur.val == targetSum && cur.left ==null && cur.right ==null) return true;
            if(cur.left != null ){
                cur.left.val += cur.val;
                q.add(cur.left);
            }
            if(cur.right != null){
                cur.right.val += cur.val;
                q.add(cur.right);
            }
        }
        return false;
    }
}

val와 같은 값임과 동시에 리프 여야 하는 조건이 있기에 리턴 값을 위와 같이 지정해 주었다.

Dfs풀이

더보기
public boolean hasPathSum(TreeNode root, int sum) {
    // recursion method
    if (root == null) return false;
    if (root.left == null && root.right == null && root.val == sum) return true;
    return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}

트리라고 써진 건 다 푼 줄 알았는데 밑에 문제가 더 있다... 101 2탄을 조만간 날 잡고 진행할 생각이다.. 

긴 글 읽어주셔서 감사합니다.

최단 경로를 구하는 알고리즘 중 하나이다. 지난번 cs 네트워크 라우터 관련 검색하다가 잠깐 살펴본 내용인데 이번 기회에 흐름을 익혀보고자 글을 작성한다.

위의 그래프 에서 각 노드 간 의 최단거리를 구하는 방법 중 하나이다. 

distance 의 배열이 존재하고 모두 최대 값 혹은 엄청 큰수로 초기화를 진행한다. 배열 의 수는 노드의 갯수 만큼 구성해서 계속 저장해 가며 값을 기록한다.

0번 에서 출발한다고 할시 갈수 있는경우 0-1, 0-2 이 가존재하며 배열에는 아래와 같이 기록한다.

distance =  0 | 5 | 1 | INF | INF | INF  => 최초 시작점을 0 으로 대입해 시작 하며 이후 다음 노드는 거리가 가장 짧은 2번 부터 진행한다. 이렇게 진행하며 현재 진행 중인 간선의 수가 노드의 갯수-1 이된다면 진행을 멈추고 마지막 값을 리턴하면 그것 이 최단 거리가 된다.

코드로 구현해보자.

class Node{
    int to;
    int distnace;

    public Node(int to, int distance) {
        this.to = to;
        this.distnace = distance;
    }
}

각각 의 노드 에 대한 정보를 기록하기 위해 이너클래스 로 노드를 하나 구성해준다.

class Dijkstra{
	public void dijkstra(int[][] locateInfo, int edge, int start){
        ArrayList<ArrayList<Node>> graph = new ArrayList();
        for(int i=0;i<edge;i++){
            graph.add(new ArrayList());
        }
        for(int i=0;i< locateInfo.length;i++){
            int from = locateInfo[i][0];
            int to = locateInfo[i][1];
            int distance = locateInfo[i][2];
            graph.get(from).add(new Node(to,distance));
        }
    }
}

노드 를 담기 위한 2중 리스트를 생성 해준다. 각 첫번째 리스트 는 현재 엣지 를 의미하고 그안에 담긴 리스트 들은 갈수 있는 경로 를 생성해 더해준다.

public void dijkstra(int[][] locateInfo, int edge, int start){
        ArrayList<ArrayList<Node>> graph = new ArrayList();
        for(int i=0;i<edge;i++){
            graph.add(new ArrayList());
        }
        for(int i=0;i< locateInfo.length;i++){
            int from = locateInfo[i][0];
            int to = locateInfo[i][1];
            int distance = locateInfo[i][2];
            graph.get(from).add(new Node(to,distance));
        }

        int[] shortest_dist = new int[edge];
        Arrays.fill(shortest_dist,1,shortest_dist.length,1<<30);
        PriorityQueue<Node> q = new PriorityQueue<>((x,y)->x.distnace - y.distnace);
        boolean[] visit = new boolean[edge+1];
        q.offer(new Node(start,0));
    }

거리 들을 기록할 배열을 하나 생성해주고 , 2^31-1 이 int 의 최대값이니 그전 까지 업데이트 해주자 전에 한번 overflow 나고 나서부터는 이렇게 초기화를 진행하고 있다.

우선순위 큐를 거리가 짧은 순으로 해서 생성해주고, 마지막까지 가는데 있어 중복 계산을 피할 방문 배열을 생성해준다.

처음 시작점을 변수로 제공받기 떄문에 큐안에 시작점을 넣어주자

public void dijkstra(int[][] locateInfo, int edge, int start){
        ArrayList<ArrayList<Node>> graph = new ArrayList();
        for(int i=0;i<edge;i++){
            graph.add(new ArrayList());
        }
        for(int i=0;i< locateInfo.length;i++){
            int from = locateInfo[i][0];
            int to = locateInfo[i][1];
            int distance = locateInfo[i][2];
            graph.get(from).add(new Node(to,distance));
        }

        int[] shortest_dist = new int[edge];
        Arrays.fill(shortest_dist,1,shortest_dist.length,1<<30);
        PriorityQueue<Node> q = new PriorityQueue<>((x,y)->x.distnace - y.distnace);
        boolean[] visit = new boolean[edge+1];
        q.offer(new Node(start,0));

        while(!q.isEmpty()){
            Node cur = q.poll();
            if(visit[cur.to]) continue;
            visit[cur.to] = true;
            for (int i = 0; i < graph.get(cur.to).size(); i++) {
                Node next = graph.get(cur.to).get(i);
                if(visit[next.to]) continue;
                if(shortest_dist[next.to] > cur.distnace+next.distnace){
                    shortest_dist[next.to] = cur.distnace+next.distnace;
                    q.offer(new Node(next.to,shortest_dist[next.to]));
                }
            }
        }
        System.out.println(Arrays.toString(shortest_dist));
    }

큐 안이 비어있기 전까지 계속 반복문 을 돌려준다. 대신 모든 곳을 방문 했다면 큐 를 계속해서 뽑아오기 때문에 반복문 아래부분 까지 내려가지 않고 계속 뽑아주어 함수를 종료해준다.

 

현재 가려는 방향의 엣지를 받아와 그안에 들어있는 갈수있는 방향들에 대한 정보들을 뽑아와 기록 되어있는 거리와 현재 거리+다음 노드의 거리 를 이용해 최소값을 계속 업데이트 하는 방식이다. 만약 그렇게 업데이트 가 되었다면 큐에 다시 넣어주어 새로운 지점에서 시작하는 노드를 넣어준다. 2~3 번 이상 계속 노트에 손코딩 하다보니 외워버렸지만 미래에 찾고있을 나를 위해 이렇게 정리해 보았다.

'PS > Algorithm' 카테고리의 다른 글

[5번째 알고리즘 모임] 4문제  (0) 2022.07.11
[4번째 알고리즘 모임] 4문제  (0) 2022.07.03
[3번째 알고리즘 모임] 4문제  (0) 2022.06.25
순열 을 다양하게 돌아보자.  (0) 2022.06.20

+ Recent posts