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

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

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

아 o(logn) 꼴도 보기싫다 ㅋㅋㅋㅋ.. 지문이 제법 길지만, 뭐 대충  이거는 pivot 에 의해 정렬 이 된 상태의 배열이 주어진다. 위에 예시가 착하게 나와 있으니 예시를 보면 이해하기 쉽다. 음 ? 이해가 왜 미디엄 레벨 이지 ?

좌우 포인터 잡고 하나씩 줄여가기로 결정

class Solution {
  public int search(int[] nums, int target) {
    int left = 0; 
    int right = nums.length - 1;
    int idx = -1; // set default as -1
    while (left <= right) {
      if (nums[left] == target) {
        idx = left;
        break;
      } else {
        left++;
      }
      if (nums[right] == target) {
        idx = right;
        break;
      } else {
        right--;
      }
    }
    return idx;
  }
}

특별할것 하나 없는 코드이다. 말 그대로 -1 을 디폴트로 설정해서 좌우 번갈아 가면서 찾아준다. 주어진 문제에서 숫자는 고유하기 떄문에 이러한 방법이 가능하다. 

다른사람들의 코드를 보자.

public class Solution {
public int search(int[] A, int target) {
    int lo = 0;
    int hi = A.length - 1;
    while (lo < hi) {
        int mid = (lo + hi) / 2; // 미드 값 구하기
        if (A[mid] == target) return mid;
        
        if (A[lo] <= A[mid]) { 
        //현재 미드값 과 레프트 값의 비교 만약 이게 성립이 된다면 현재 범위는 로테이트 된 값 이 있는 부분이다.
            if (target >= A[lo] && target < A[mid]) {
                hi = mid - 1; //로테이된 왼쪽 부분에 위치하는 조건이다. 
            } else {
                lo = mid + 1; // 로테이트된 오른쪽 부분에 위치하는 조건이다.
            }
        } else {
            if (target > A[mid] && target <= A[hi]) {
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
    }
    return A[lo] == target ? lo : -1;
}

이 사람은 이분탐색 그대로 구현했다. 대신 미드 값에 따른 이동 포인터를 좌우 비교를 통해 현재 진행되는 방향이 로테이트 된 방향인지 아닌지를 모두 체크하는 if 문을 넣어 주었는데 비교적 코드가 직관적 이여서 가져왔다.

음  만약 1,2,3,4,5,6,7 이란 코드가 있고 4,5,6,7,0,1,2,3 이렇게 피벗 4 에 변경이 된다면 4 5 6 7  / 0 1 2 3 이렇게 나누어서 생각해 볼수 있다 여기서 찝은 미드의 값과 왼쪽 과 오른쪽 비교를 해주었을때  어느 부분을 찝었는 지 알수 있다. 또한 타겟을 이용해 범위를 명확히 줄여 나가는 것이 참 신기한 아이디어 같다.

왼쪽 비교를 할때는 4 와 미드값 7 그리고 타겟

오른쪽 비교를 할때는 미드값 7 과 마지막 값 3   그리고 타겟

이렇게 현재 잡힌 미드 와 타겟 의 값을 이용해 반대편을 계속 완전히 버려줄수 있다. 

 

정말 고수들은 많다.

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

Easy 문제 풀기 Day1(79/610)  (0) 2022.12.03
BFS 문제들을 풀어보자 1탄  (0) 2022.08.25
34. Find First and Last Position of Element in Sorted Array  (0) 2022.07.03
53. Maximum Subarray  (0) 2022.06.29
242. Valid Anagram  (0) 2022.06.26

주어진 num 범위 안에서 타겟 에 해당하는 범위를 출력하는것이다. 이미 정렬 되어진 상태이니 이분탐색으로 빠르게 해당 값을 찾고 

그 값으로 부터 좌우로 증가하면서 알맞은 값을 찾는것이다.

코드를 보자.

class Solution {
  public int[] searchRange(int[] arr, int target) {
    int left = -1;
    int right = -1;

    int nLeft = 0;
    int nRight = arr.length - 1;
    int idx = -1;
    while (nLeft <= nRight) {
      int mid = nLeft + (nRight - nLeft) / 2;
      if (arr[mid] < target) {
        nLeft = mid + 1;
      } else if (arr[mid] == target) {
        idx = mid;
        break;
      } else {
        nRight=mid-1;
      }
    }
    if (idx == -1)
      return new int[] { left, right };
    left = idx;
    right = idx;
    while (arr[left] == target) {
      if (left-1>=0 &&arr[left - 1] == target) {
        left--;
      } else {
        break;
      }
    }
    while (arr[right] == target) {
        if(right+1 >= arr.length){
            break;
        }
      if (right+1<arr.length && arr[right + 1] == target) {
        right++;
      } else {
        break;
      }
    }
    System.out.println(left + " " + right);
    return new int[] { left, right };
  }
}

left , right 는 -1 로 지정을 해주고 문제에서 못찾으면 -1 리턴 하라고 했으니 이니셜 지정 해주자. 가운데 값을 찾은 후 말그대로 while 돌려가며 왼쪽 끝 오른쪽 끝을 이동하며 찾는데 12 ms ~ 15ms 정도 나온다. 매우 느린 코드다 마음에 들지 않는다. 양쪽으로 이분탐색을 다시해봐도 별반 차이가 없다... ㅎㅎ

고수들의 코드를 봐보자.

public class Solution {
public int[] searchRange(int[] nums, int target) {
    int[] result = new int[2];
    result[0] = findFirst(nums, target);
    result[1] = findLast(nums, target);
    return result;
}

private int findFirst(int[] nums, int target){
    int idx = -1;
    int start = 0;
    int end = nums.length - 1;
    while(start <= end){
        int mid = (start + end) / 2;
        if(nums[mid] >= target){
            end = mid - 1;
        }else{
            start = mid + 1;
        }
        if(nums[mid] == target) idx = mid;
    }
    return idx;
}

private int findLast(int[] nums, int target){
    int idx = -1;
    int start = 0;
    int end = nums.length - 1;
    while(start <= end){
        int mid = (start + end) / 2;
        if(nums[mid] <= target){
            start = mid + 1;
        }else{
            end = mid - 1;
        }
        if(nums[mid] == target) idx = mid;
    }
    return idx;
}

이분탐색 을 이렇게 반반 나눠서 한다. 와 미쳤다.. 생각 하지도 못했다.... 정형화된 이분탐색 에서 break else 에 변화를 주고 점점 가지치기 해나가는데 와... 멋진 코드이다.

public class Solution {
	public int[] searchRange(int[] A, int target) {
		int start = Solution.firstGreaterEqual(A, target);
		if (start == A.length || A[start] != target) {
			return new int[]{-1, -1};
		}
		return new int[]{start, Solution.firstGreaterEqual(A, target + 1) - 1};
	}

	//find the first number that is greater than or equal to target.
	//could return A.length if target is greater than A[A.length-1].
	//actually this is the same as lower_bound in C++ STL.
	private static int firstGreaterEqual(int[] A, int target) {
		int low = 0, high = A.length;
		while (low < high) {
			int mid = low + ((high - low) >> 1);
			//low <= mid < high
			if (A[mid] < target) {
				low = mid + 1;
			} else {
				//should not be mid-1 when A[mid]==target.
				//could be mid even if A[mid]>target because mid<high.
				high = mid;
			}
		}
		return low;
	}
}

와 이건 ㅋㅋㅋㅋㅋㅋ 아이디어의 발상이 참 대단하다. 찾고자 하는수에 +1 을 해서 그수를 찾고 그인덱스 -1 과 찾고자 하는 수 와 정말 멋진코드가 많다. 이걸보고 다시 내 코드보니 왜이리 구려보이 는지..... 내일 다시풀때는 이 코드로 작성해보자.

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

BFS 문제들을 풀어보자 1탄  (0) 2022.08.25
33. Search in Rotated Sorted Array  (0) 2022.07.04
53. Maximum Subarray  (0) 2022.06.29
242. Valid Anagram  (0) 2022.06.26
383. Ransom Note  (0) 2022.06.26

주어진 숫자 배열 안에 연속된 부분 배열 중 가장 큰 값을 리턴하면 되는 심플 한 문제이다. 문제만 심플하지 생각할 것이 꽤 많은 문제였다. 

문제풀이를 하면서 다양하게 접근했다. 저기 주어진 예시들 은 길고 특별한 케이스이니 심플하게 만들어서 생각해보자.

[-3,2,3,-1]

뭐 있나 1번 부터 다 쓰자...

1번 인덱스 : 뭐 할 게 없다 그냥 리턴해줘야 한다...-3 

2번 인덱스 : [-3], [2] / [-3,2]  오 뭔가 느낌이 온다. 

3번 인덱스 : [-3], [2], [3]  / [-3,2], [2,3] / [-3,2,3] 왔다, 약간 dp 스럽게 풀어야 하는 건가 싶었다. 

 

현재 인덱스까지 진행된 것 중 버릴 거 버리면서 진행한다면  나쁘지 않은 효율이 나올 것 같았다. 

뭘 어떻게 버려 주어야 할까 고민하다가, 현재 진행 중인 값에 다음 값을 더해서 그 값이 다음값 보다 작다면? 뒤에 까지 다 더한 것은 쓸모가 없어진다.. -2 1 -3을 예로 들어보자 -2 1  => -1 인 상태에서 -3 을 더하면? -4인데 우리는 연속된 수를 구해야 하니 버리자 대신 저 앞에 까지 더해진 부분을 따로 기록을 해 두어야 할 것이다.

class Solution {
    public int maxSubArray(int[] arr) {
        if(arr.length == 1) return arr[0];
        int p1 = 0;
        int p2 = 1;
        int maxSum = arr[p1];
        int currentSum = arr[p1];
        while(p2<arr.length){
            int nextCur = currentSum + arr[p2];
            if(nextCur < arr[p2]){
                p1 = p2;
                currentSum = arr[p2];
            }else{
                currentSum = nextCur;
            }
            maxSum = Math.max(maxSum,currentSum);
            p2++;
        }
        return maxSum;
    }
}

 

오늘 two pointer 강의를 듣고 와서 그런가 엄청 비슷하게 푼 거 같은데 허점이 존재한다.... p1을 선언해 놓고 쓰지도 않는다......
고수들의 코드를 보자.

 

class Solution {
    public int maxSubArray(int[] nums) {
        int currSum = nums[0], result = nums[0];
        for (int i=1;i<nums.length;i++){
            currSum = Math.max(nums[i], currSum + nums[i]);
            result = Math.max(result, currSum);
        }
        return result;
    }
}

호 올리 몰리......  math.max를 이렇게 선언하면서 깔끔하게 for 반복문으로 해결해 버리는 아름다운 코드이다. 조금 더 디스커스를 둘러보면 Kadane's Algorithm 이란 글로 엄청 아름답게 써놓은 글이 있다. 따로 퍼오기 좀 그러니 링크를 첨부하겠다.

개괄적으로 설명을 하자면 위의 풀이법 그대로가 카 데인 알고리즘이다. 글 설명 10줄보다 그림 이 훨씬 보기 편하니 아래 링크로 이동해서 확인하면 더 도움 이 됩니다.
보러 가기

이 사람이 작성한 글에 의하면 어느 부분이던 합이 - 에 도달한다면, 이걸 계속 가지고 있을 의미가 없기 때문에 합을 0으로 초기화시켜준다고 한다.

-2 1 -3 4 -1 2 1 -5 4 (예시 1번)

i = 0  일 때 합은 -2 가 되고 , 최대 합은 -2 , 합이 0 보다 작기 때문에 현재 합은 0으로 초기화된다.

i = 1  일 때 합은 0+1(현재 합을 0 으로 초기화 시켜서 전 인덱스에서) 이되고, 합이 1 이된다 최대합은 1

i = 2 일 때 합은 1 -3 -2 가 되고 최대합은 1, 현재합은 0으로 초기화된다 왜 ?  0보다 작아서

i = 3 일때 합은 0+4 4 가되고 최대합은 4, 현재합은 4 가된다

이런 식으로 쭉쭉 이어나가면 된다. 위 링크에 더 자세히 설명되어 있으니 참고 바랍니다.!!

 

자세히 다시 보니 브루트 포스 에서 내가 느낀 그 디피 느낌이 이 카데인 알고리즘 이였다 그냥 디피라고 하면 될걸 왜 굳이 이름을 이렇게 붙이는지 다시보니 도둑이 집터는 문제랑 매우 유사하다고 생각된다...... 현재 합과, 현재 인덱스 중 큰 것, 또 맥스 값과 큰 것을 비교하는 법
왜 항상 모든 블로그 혹은 유튜브 비디오에서 항상 브루트 포스를 먼저 하라고 하는지 다시 한번 느끼는 문제였다..

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

33. Search in Rotated Sorted Array  (0) 2022.07.04
34. Find First and Last Position of Element in Sorted Array  (0) 2022.07.03
242. Valid Anagram  (0) 2022.06.26
383. Ransom Note  (0) 2022.06.26
387. First Unique Character in a String  (0) 2022.06.26

+ Recent posts