나는 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탄을 조만간 날 잡고 진행할 생각이다.. 

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

+ Recent posts