Letter Combinations of a Phone Number - LeetCode

dfs를 통해서 가능한 문자들을 더해나간다.

번호와 문자열을 매칭해야 하는데 어차피 8개밖에 없으니 직접 넣어주는 방식을 선택하겠다.

class Solution {

    static Map<Integer, String> letterMap = new HashMap<>();

    static {
        letterMap.put(2, "abc");
        letterMap.put(3, "def");
        letterMap.put(4, "ghi");
        letterMap.put(5, "jkl");
        letterMap.put(6, "mno");
        letterMap.put(7, "pqrs");
        letterMap.put(8, "tuv");
        letterMap.put(9, "wxyz");
    }

    public List<String> letterCombinations(String digits) {
        if (digits.equals("")) return List.of();

        List<String> result = new ArrayList<>();
        dfs(0, "", digits, result);
        return result;    
    }

    private void dfs(int depth, String curr, String digits, List<String> result) {
        if (depth == digits.length()) {
            result.add(curr);
            return;
        }

        int key = digits.charAt(depth) - '0';
        for (String s : letterMap.get(key).split("")) {
            dfs(depth + 1, curr + s, digits, result);
        }
    }
}

Binary Tree Right Side View - LeetCode

 

이진트리의 root가 주어졌을 때 자신의 우측 방향에서 바라봤을 때 보이는 노드들의 값을 위에서 아래방향으로 반환하라.

오른쪽에서 봤을 때라는 뜻은 각 레벨마다 오른쪽에 있는 값이라는 뜻이다.

그렇다면 레벨별로 탐색하는 것이 중요하다.

레벨별로 탐색은 어떻게 가능할까? BFS를 사용하면 된다.

다음의 작업을 처리하면 된다.

  1. 현재 레벨의 처리해야 할 노드 수를 기록한다.
  2. 해당 레벨의 모든 노드를 순회한다.
  3. 그 노드의 왼쪽, 오른쪽 자식이 있다면 큐에 추가한다.
  4. 해당 레벨의 마지막 노드이면 결과 리스트에 추가한다.
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) return result;
        
        Queue<TreeNode> q = new LinkedList<>();
        q.add(root);

        while (!q.isEmpty()) {
            int levelSize = q.size();
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = q.poll();
                if (i == levelSize - 1) {
                    result.add(node.val);
                }
                if (node.left != null) q.add(node.left);
                if (node.right != null) q.add(node.right);
            }

        }
        return result;
    }    
}

 

그런데 이 문제는 DFS로도 해결이 가능하다.

트리를 탐색해 나가면서 오른쪽 자식을 먼저 보면 당연히 오른쪽에서 바라보는 노드의 값을 얻을 수 있다.

문제는 이제 왼쪽 서브트리에서 레벨이 더 큰 자식이 나왔을 때이다.

이를 해결하기 위해 레벨과 찾은 오른쪽에서 바라봤을 때의 노드 개수를 연관 지어 처리해 줄 방법이 필요하다.

해당 레벨에는 하나의 정답 노드가 존재할 것이다. 따라서 저장된 노드의 개수와 레벨을 비교하면 된다.

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) return result;
        dfs(0, root, result);
        return result;
    }    

    private void dfs(int level, TreeNode node, List<Integer> result) {
        if (node == null) return;
        
        if (level == result.size()) {
            result.add(node.val);
        }

        dfs(level + 1, node.right, result);
        dfs(level + 1, node.left, result);
    }
}

Minimum Absolute Difference in BST - LeetCode

 

구해야 하는 값이 노드에 있는 값의 차이 중에 절댓값이 제일 작은 것이다.

가장 먼저 떠오르는 방법은 모든 트리를 탐색해서 값들을 저장하고 거기서 모든 값을 순회해 목푯값을 찾는 것이다.

이는 N개의 노드를 탐색하고 이중루프를 사용하기 때문에 결과적으로 O(N^2)의 시간복잡도를 가지게 될 것이다. 아주 비효율적이다.

 

다른 효율적인 방법을 생각해보자.

최소 절대 차이를 어떻게 효율적으로 찾을 수 있을까?

생각해봐야 할 점은 주어진 트리가 그냥 이진트리가 아니라 이진 탐색 트리(BST)라는 것이다.

BST는 왼쪽 노드는 부모 노드보다 작고, 오른쪽 노드는 부모 노드보다 크다는 특징을 가지고 있다.

이 말은 중위순회를 사용하게 되면 노드 값이 정렬된 순서로 나열된다는 뜻이다.

그렇다면 모든 노드를 이중루프를 통해 확인할 필요 없이 인접한 값들끼리만 비교하면 될 것이다.

해야 할 일이 다음으로 정리됐다.

  1. BST를 중위 순회하여 정렬된 노드 값을 얻는다.
  2. 순회하면서 인접한 노드 간의 차이를 계산한다.
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int getMinimumDifference(TreeNode root) {
        List<Integer> values = new ArrayList<>();
        search(root, values);
        int min = Integer.MAX_VALUE;
        int prev = values.get(0);

        for (int i = 1; i < values.size(); i++) {
            int val = values.get(i);
            min = Math.min(min, val - prev);
            prev = val;
        }

        return min;
    }

    private void search(TreeNode node, List<Integer> values) {
        if (node == null) return;

        search(node.left, values);
        values.add(node.val);
        search(node.right, values);
    }
}

Summary Ranges - LeetCode

 

다음 숫자와 차이가 1보다 크면 같은 범위에 속한 숫자가 아니게 된다.

포인터를 두고 확장해 나가며 범위를 구해 정답에 추가하자.

class Solution {
    public List<String> summaryRanges(int[] nums) {
        if (nums.length == 0) {
            return List.of();
        }
        List<String> result = new ArrayList<>();
        int s = 0;
        int prev = nums[s];
        int e;
        for (e = 0; e < nums.length; e++) {
            if (Math.abs(nums[e] - prev) > 1) {
                // 범위를 벗어난 상태 s부터 e-1까지 유효 범위
                if (s != e - 1) {
                    result.add(nums[s] + "->" + nums[e - 1]);
                } else {
                    result.add(String.valueOf(nums[s]));
                }
                s = e;
            }
            prev = nums[e];
        }

        if (s == e - 1) {
            result.add(String.valueOf(nums[s]));
        } else {
            result.add(nums[s] + "->" + nums[e - 1]);
        }

        return result;
    }
}

여기서 주의해야 할 점은 크기 비교 부분이다.

nums[e] - prev > 1 이렇게 판정을 할 시 배열의 값이 int 범위이므로 오버플로우가 발생할 수 있다.

예를 들어 2147483647와 -2147483647를 보면 알 수 있다.

2147483647 - (-2147483647)를 하면 오버플로우로 순환이 발생해 -2가 반환값으로 나오게 된다.

이를 해결하기 위해 거리개념을 도입하기 위해 Math.abs를 사용했다. 아니면 오버플로우를 방지하기 위해 long으로 변환해 계산해도 된다.

Valid Sudoku - LeetCode

9x9 크기의 스도쿠 판이 유효할 수 있는지 판단하라.

빈칸은 다음과 같은 규칙으로 채워진다.

  1. 각각 row는 중복 없는 1~9 사이 숫자로 이루어진다.
  2. 각 col은 중복 없는 1~9 사이 숫자로 이루어진다.
  3. 3x3의 9개의 서브박스는 중복 없는 1~9 사이 숫자로 이루어진다.

숫자를 채워 넣는 스도쿠가 아니라 그냥 현재 스도쿠의 상태를 판정하는 문제이다. 중복 확인만 잘하면 된다.

중복 확인은 어떻게 해야할까? 열과 행은 각각을 HashSet으로 관리하면 될 것 같다.

박스의 경우 어떻게 해야할까? 박스의 대표 인덱스를 하나 표현할 수 있으면 좋을 것 같다.

이거는 (r / 3) * 3 + (c / 3) 이면 각 박스에 속하는 값들이 모두 같은 결과를 내게 된다. 따라서 해당 결괏값을 id로 사용하겠다.

class Solution {
    public boolean isValidSudoku(char[][] board) {
        Map<Integer, Set<Character>> rowMap = new HashMap<>();
        Map<Integer, Set<Character>> colMap = new HashMap<>();
        Map<Integer, Set<Character>> boxMap = new HashMap<>();

        for (int i = 0; i < 9; i++) {
            rowMap.put(i, new HashSet<>());
            colMap.put(i, new HashSet<>());
            boxMap.put(i, new HashSet<>());
        }

        for (int r = 0; r < 9; r++) {
            for (int c = 0; c < 9; c++) {
                if (board[r][c] != '.') {
                    char value = board[r][c];
                    if (rowMap.get(r).contains(value)) return false;
                    if (colMap.get(c).contains(value)) return false;
                    int boxId = (r / 3) * 3 + (c / 3);
                    if (boxMap.get(boxId).contains(value)) return false;

                    rowMap.get(r).add(value);
                    colMap.get(c).add(value);
                    boxMap.get(boxId).add(value);
                }
            }
        }

        return true;
    }
}

https://leetcode.com/problems/substring-with-concatenation-of-all-words

 

문자열 s, 같은 길이의 단어가 들어있는 String [] words

concatenated string이라는 것이 등장한다. 이는 words의 단어들을 순열로 만들어 하나로 합친 것들이다.

예를 들면 words = [”ab”, “cd”, “ef”] 일 때 이 배열의 순열들은 다음과 같다.

[”ab”, “cd”, “ef”], [”ab”, “ef”, “cd”], [”cd”, “ab”, “ef”], [”cd”, “ef”, “ab”], [”ef”, “ab”, “cd”], [”ef”, “cd”, “ab”] 이렇게 총 6가지이다.

따라서 concatenated string은 다음의 6가지가 된다.

“abcdef”, “abefcd”, “cdabef”, “cdefab”, “efabcd”, “efcdab”

문자열 S에서 모든 concatenated string의 substring 위치를 담은 리스트를 반환하라.

 

substring의 위치 같은 경우에는 indexOf를 사용하면 찾을 수 있을 것 같다.

결국 필요한 것은 words의 단어들로 순열을 만들어 concatenated string를 구성하는 것이다.

주의할 점은 words 내에 똑같은 단어가 여러 번 등장할 수 있다는 것이다.

이렇게 되면 순열을 구성시에 중복된 concatenated string이 등장할 수 있다. 따라서 중복처리가 필요하다.

중복처리는 그냥 간단하게 Set을 통해 처리하겠다.

또 다른 문제도 있다. 문자열 s에서 같은 substring이 2번 등장했을 때이다. indexOf는 처음으로 등장하는 substring(부분 문자열)의 index만을 반환한다.

이를 해결하려면 substring의 위치를 판단하는 메서드를 새롭게 작성하던가 indexOf 중 오버로딩 된 시작 위치를 변경해 가며 찾는 메서드를 사용하는 방식이 있다. 나는 후자를 선택했다.

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        Set<Integer> answer = new HashSet<>();
        permutation(0, words, new boolean[words.length], new StringBuilder(), answer, s);
        return answer.stream().collect(Collectors.toList());
    }

    private void permutation(int idx, String[] words, boolean[] visited, StringBuilder sb, Set<Integer> answer, String s) {
        if (idx == words.length) {
            // 마지막 위치까지 확인
            int from = 0;
            while (from < s.length()) {
                int substringIdx = s.indexOf(sb.toString(), from);
                if (substringIdx == -1) {
                    break;
                }
                answer.add(substringIdx);
                from = substringIdx + 1;
            }
            return;
        }

        for (int i = 0; i < words.length; i++) {
            if (visited[i]) continue;
            visited[i] = true;
            permutation(idx + 1, words, visited, new StringBuilder(sb.toString()).append(words[i]), answer, s);
            visited[i] = false;
        }
    }
}    

시간초과가 발생했다.

s = "fffffffffffffffffffffffffffffffff”

words = ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"]

이 경우에서 실패했다.

그냥 위의 생각 자체가 틀렸다. 문제의 조건을 다시 보자. words 배열의 최대 길이는 5000이다.

그렇게 되면 만들어지는 수열은 최대 5000! 이 된다. 이는 절대로 위의 방식으로는 풀 수 없는 수치이다.

또한 indexOf로 substring을 검색하는 방식도 s가 길어질수록 비효율적으로 변한다.

 

다른 방식을 생각해 보자.

사용하지 않은 조건 중에 words의 모든 단어가 같은 길이를 갖는다는 조건이 있다.

여기서 얻을 수 있는 정보는 유효한 substring의 길이가 항상 일정하다는 것이다.

일정한 크기의 슬라이딩 윈도인가? 하는 생각이 든다.

또한 words의 모든 단어가 1번씩은 등장한다는 조건도 있다.

 

이를 통해서 생각을 정리해 보자.

해당 슬라이딩 윈도 크기 내에서 등장하는 words의 단어들의 빈도수를 알면 된다.

예를 들어 words = [”foo”, “bar”, “foo”] 이면 “foo”가 2번, “bar”가 1번 등장해야만 한다.

슬라이딩 윈도를 이동해 가면서 해당 범위 내에서 “foo”가 2번, “bar”가 1번 등장했는지 확인만 하면 된다.

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> answer = new ArrayList<>();
        Map<String, Integer> wordsMap = new HashMap<>();
        int size = words[0].length();
        int windowSize = words.length * size;
        
        for (String word : words) {
            wordsMap.put(word, wordsMap.getOrDefault(word, 0) + 1);
        }

        int left = 0;
        for (int right = windowSize; right <= s.length(); right++) {
            String substring = s.substring(left, right);
            // substring을 size(단어 길이) 단위로 분할
            int start = 0;
            Map<String, Integer> substringWordMap = new HashMap<>();
            while (start < substring.length()) {
                String word = substring.substring(start, start + size);
                substringWordMap.put(word, substringWordMap.getOrDefault(word, 0) + 1);
                start += size;
            }
            if (wordsMap.equals(substringWordMap)) answer.add(left);

            left++;
        } 
        
        return answer;
    }
}

정답은 통과됐다. 그런데 시간 효율이 엄청나게 별로다.

가장 좋은 시간 효율을 가진 사람에 비해 터무니없이 느리다.

 

여기서부터는 당장 개선 방법이 안 떠오르므로 gpt의 도움을 받아 정답 코드를 분석해 보자.

import java.util.*;

public class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> result = new ArrayList<>();
        if(s == null || s.length() == 0 || words == null || words.length == 0) return result;
        
        int wordLength = words[0].length();
        int totalLength = wordLength * words.length;
        
        // 단어 빈도 맵 생성
        Map<String, Integer> wordCount = new HashMap<>();
        for(String word : words){
            wordCount.put(word, wordCount.getOrDefault(word, 0) +1);
        }
        
        // 단어 길이만큼 이동하며 검사
        for(int i = 0; i < wordLength; i++){
            int left = i, right = i, count = 0;
            Map<String, Integer> currentCount = new HashMap<>();
            while(right + wordLength <= s.length()){
                String word = s.substring(right, right + wordLength);
                right += wordLength;
                if(wordCount.containsKey(word)){
                    currentCount.put(word, currentCount.getOrDefault(word, 0) +1);
                    count++;
                    
                    // 빈도 초과시 왼쪽 포인터 이동
                    while(currentCount.get(word) > wordCount.get(word)){
                        String leftWord = s.substring(left, left + wordLength);
                        currentCount.put(leftWord, currentCount.get(leftWord) -1);
                        left += wordLength;
                        count--;
                    }
                    
                    // 모든 단어 매칭시 결과에 추가
                    if(count == words.length){
                        result.add(left);
                    }
                } else {
                    // 일치하지 않는 단어일 경우 초기화
                    currentCount.clear();
                    count = 0;
                    left = right;
                }
            }
        }
        return result;
    }
}

위의 코드는 엄청나게 빠르다. 기존의 방식과 뭐가 차이나길래 이렇게 속도차이가 날까?

 

외부 루프가 한 단어의 길이만큼만 반복하고 있다.

이 내부에서 슬라이딩 윈도를 사용해 문자열 s를 검사하는 방식을 쓴다.

하지만 내 코드 같은 경우에는 외부 루프가 1씩 증가하며 모든 지점을 검사하고 있다.

슬라이딩 윈도가 유의미하게 사용된 곳이 없다.

한 단어의 길이만큼 반복해도 모든 가능한 위치에서 검사할 수 있는 이유는 단어들이 모두 같은 길이를 가지기 때문이다.

예를 들어 한 단어가 “foo” 이면

i = 0 일 때는 0, 3, 6, 7, 12를 시작 인덱스로 검사하고

i = 1 일 때는 1, 4, 7, 10, 13 i = 2 일 때는 2, 5, 8, 11, 14가 된다.

 

솔직히 실제 상황에서 이렇게 풀라 하면 못 풀 것 같다.

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

LeetCode 228 - Summary Ranges  (0) 2024.10.14
LeetCode 36 - Valid Sudoku  (0) 2024.10.14
LeetCode 3 - Longest Substring Without Repeating Characters  (0) 2024.10.07
LeetCode 209 - Minimum Size Subarray Sum  (0) 2024.10.04
LeetCode 15 - 3Sum  (0) 2024.10.04

https://leetcode.com/problems/longest-substring-without-repeating-characters

 

문자열 s가 주어졌을 때 반복되는 문자가 등장하지 않는 가장 긴 substring을 반환하라.

 

무선 문자의 등장 여부를 관리할 필요가 있겠다. 이는 Set을 통해 관리하면 1개만 등장하는지 여부를 빠르게 판단할 수 있을 것 같다.

이제는 substring을 판단해야 한다. 이는 슬라이딩 윈도를 통해 판단하겠다.

이 슬라이딩 윈도우를윈도를 확장하다 더 이상 확장할 수 없을 때 즉, 중복 단어가 등장했을 때 기존에 해당 단어가 위치했던 곳 다음을 시작점으로 다시 윈도를 확장한다.

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if (s.length() == 0) return 0;

        Set<Character> charSet = new HashSet<>();
        int left = 0;
        int maxLength = Integer.MIN_VALUE;

        for (int right = 0; right < s.length(); right++) {
            while (charSet.contains(s.charAt(right))) {
                charSet.remove(s.charAt(left));
                left++;
            }

            charSet.add(s.charAt(right));
            maxLength = Math.max(maxLength, right - left + 1);
        }

        return maxLength;
    }
}

https://leetcode.com/problems/minimum-size-subarray-sum

 

subarray의 합이 target보다 크거나 같은 subarray 중 최소 길이를 구하라.

subarray이기에 정렬이 수행되면 안 된다.

 

연속된 수를 확인할 때는 슬라이딩 윈도 기법이 좋다.

여기서 슬라이딩 윈도의 크기를 조절하며 최소 길이를 갱신하는 방법을 쓰면 될 것 같다.

 

이 슬라이딩 윈도는 두 개의 포인터로 구성이 돼있다.

두 포인터 사이에 있는 값들의 합이 target보다 크면 최소 길이를 갱신한다. 하지만 최소를 구해야 하기 때문에 범위를 줄여가며 체크를 해줘야 한다.

따라서 왼쪽 범위를 하나 줄이고 다시 체크를 하는 동작을 반복한다.

그러다가 target보다 합이 작아져 더 이상 왼쪽을 줄일 수 없을 때 오른쪽 범위를 늘려주도록 한다.

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int minLength = Integer.MAX_VALUE;
        int sum = 0;
        int left = 0;

        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];

            while (sum >= target) {
                minLength = Math.min(minLength, right - left + 1);
                sum -= nums[left];
                left++;
            }
        }

        return minLength == Integer.MAX_VALUE ? 0 : minLength;
    }
}

https://leetcode.com/problems/3sum

 

i ≠ j, i ≠ k, j ≠ k 이면서 nums [i] + nums [j] + nums [k] == 0 인 nums [i], nums [j], nums [j]의 조합을 모두 찾아라.

nums 배열을 보면 중복된 숫자가 존재할 수 있다.

int[ ] nums = [-1, 0, 1, 2, -1, -4]로 주어졌다고 가정하자.

합이 0이 되는 경우는 -1 + 0 + 1, -1 + -1 + 2가 존재한다. 하나씩 전부 확인하는 브루트포스방식을 사용하면 찾을 수는 있지만 아주 비효율적이다. 어떻게 하면 효율적으로 찾을 수 있을까?

보통 다른 문제에서는 두 수의 합이 target이 되는 경우를 찾는다. 그리고 그때 정렬과 투 포인터를 주로 사용한다.

그런 관점에서 보면 이 문제는 target이 변하는 투 포인터 문자라고도 볼 수 있다고 생각한다.

 

우선 정렬을 수행해보자. 정렬을 하면 중복을 뛰어넘기 쉽고 투 포인터 사용에도 용이하다.

nums = [-4, -1, -1, 0, 1, 2]이다. 0이 되기 위해서 하나의 숫자를 고정해 보다. 그 숫자가 -4라고 하자. 그렇다면 두 수의 합이 4가 되는 수를 찾는 것이다. 그다음은 그 숫자가 -1이 되고 합이 1이 되는 숫자를 찾는 동작이다. 이 동작을 끝까지 반복하는 것이다.

 

해야 할 일을 분리해보자.

  1. target을 정한다.
  2. 투 포인터를 사용해 target과 일치하는 조합을 찾는다.
  3. 중복을 피하기 위한 로직을 추가한다.
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> answer = new ArrayList<>();
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 2; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;
                findTriplets(i, nums, answer);
        }

        return answer;
    }

    private void findTriplets(int targetIdx, int[] nums, List<List<Integer>> answer) {
        int left = targetIdx + 1;
        int right = nums.length - 1;

        while (left < right) {
            int sum = nums[left] + nums[right] + nums[targetIdx];

            if (sum == 0) {
                answer.add(Arrays.asList(nums[targetIdx], nums[left], nums[right]));

                while (left < right && nums[left] == nums[left + 1]) left++;
                while (left < right && nums[right] == nums[right - 1]) right--;

                left++;
                right--;
            } else if (sum < 0) {
                left++;
            } else {
                right--;
            }
        }
    }
}

https://leetcode.com/problems/container-with-most-water

 

가장 많은 물을 가두기 위해 어느 두 점을 선택해야 하는가.

너비라는 것이 가로 x 세로이다. 일단 가로를 가장 길게 설정해 둔 다음 가로를 좁혀가며 최댓값을 변경하면 될 것 같다.

이때 가로를 어떻게 좁히느냐가 문제인데 작은 것을 기준으로 세로가 정해지기 때문에 세로값이 작은 것을 변경시키는 방향으로 가는 것이 맞다.

class Solution {
    public int maxArea(int[] height) {
        int max = Integer.MIN_VALUE;

        int left = 0;
        int right = height.length - 1;

        while (left < right) {
            int sum = (right - left) * Math.min(height[left], height[right]);
            max = Math.max(max, sum);

            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }

        return max;
    }
}

+ Recent posts