LeetCode热门100题算法和思路(day4)

LeetCode70 爬楼梯

题目详情

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶

示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。

  1. 1 阶 + 1 阶 + 1 阶
  2. 1 阶 + 2 阶
  3. 2 阶 + 1 阶

提示:
1 <= n <= 45

代码
public class LeetCode70 {
    public static void main(String[] args) {
        System.out.println(new Solution().climbStairs(30));
        System.out.println(new Solution().climbStairs2(30));
        System.out.println(new Solution().climbStairs3(30));
    }

    static class Solution {
        /*
        动态规划-空间换时间
         */
        public int climbStairs(int n) {
            if (n == 1) {
                return 1;
            }
            //长度为n会越界
            int[] dp = new int[n + 1];
            dp[1] = 1;
            dp[2] = 2;
            for (int i = 3; i <= n; i++) {
                dp[i] = dp[i - 1] + dp[i - 2];
            }
            return dp[n];
        }

        /*
         递归算法-超时
         */
        public int climbStairs2(int n) {
            if (n == 1) return 1;
            if (n == 2) return 2;
            return climbStairs(n - 2) + climbStairs2(n - 1);
        }

        /*
        循环的方式
         */
        public int climbStairs3(int n) {
            int p = 0, q = 0, r = 1;
            for (int i = 1; i <= n; ++i) {
                p = q;
                q = r;
                r = p + q;
            }
            return r;
        }
    }
}

LeetCode72 编辑距离

题目详情

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

提示:

0 <= word1.length, word2.length <= 500
word1 和 word2 由小写英文字母组成

代码
public class LeetCode72 {
    public static void main(String[] args) {
        System.out.println(new Solution().minDistance("horse", "ros"));
    }

    static class Solution {
        /*
        方法一:动态规划
        思路和算法:
        我们可以对任意一个单词进行三种操作:
        插入一个字符;
        删除一个字符;
        替换一个字符。
         */
        public int minDistance(String word1, String word2) {
            int n = word1.length();
            int m = word2.length();
            // 有一个字符串为空串
            if (n * m == 0)
                return n + m;
            // DP 数组
            int[][] D = new int[n + 1][m + 1];
            // 边界状态初始化
            for (int i = 0; i < n + 1; i++) {
                D[i][0] = i;
            }
            for (int j = 0; j < m + 1; j++) {
                D[0][j] = j;
            }
            // 计算所有 DP 值
            for (int i = 1; i < n + 1; i++) {
                for (int j = 1; j < m + 1; j++) {
                    int left = D[i - 1][j] + 1;
                    int down = D[i][j - 1] + 1;
                    int left_down = D[i - 1][j - 1];
                    if (word1.charAt(i - 1) != word2.charAt(j - 1))
                        left_down += 1;
                    D[i][j] = Math.min(left, Math.min(down, left_down));
                }
            }
            return D[n][m];
        }
    }
}

LeetCode75 颜色分类

题目详情

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库的sort函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2
进阶:
你可以不使用代码库中的排序函数来解决这道题吗?
你能想出一个仅使用常数空间的一趟扫描算法吗?

代码
class LeetCode75 {
    public static void main(String[] args) {
        int[] nums1 = {2, 0, 2, 1, 1, 0};
        new Solution().sortColors(nums1);
        System.out.println(Arrays.toString(nums1));

        int[] nums2 = {2, 0, 2, 1, 1, 0};
        new Solution().sortColors2(nums2);
        System.out.println(Arrays.toString(nums2));
    }

    /*
    本问题被称为 荷兰国旗问题,最初由 Edsger W. Dijkstra提出。
    其主要思想是给每个数字设定一种颜色,并按照荷兰国旗颜色的顺序进行调整。
    我们用三个指针(p0, p2 和curr)来分别追踪0的最右边界,2的最左边界和当前考虑的元素。
     */
    static class Solution {
        /*
        荷兰三色旗问题解
        双指针算法
        */
        public void sortColors(int[] nums) {
            // 对于所有 idx < i : nums[idx < i] = 0
            // j是当前考虑元素的下标
            int p0 = 0, curr = 0;
            // 对于所有 idx > k : nums[idx > k] = 2
            int p2 = nums.length - 1;

            int tmp;
            while (curr <= p2) {
                if (nums[curr] == 0) {
                    // 交换第 p0个和第curr个元素
                    // i++,j++
                    tmp = nums[p0];
                    nums[p0++] = nums[curr];
                    nums[curr++] = tmp;
                } else if (nums[curr] == 2) {
                    // 交换第k个和第curr个元素
                    // p2--
                    tmp = nums[curr];
                    nums[curr] = nums[p2];
                    nums[p2--] = tmp;
                } else curr++;
            }
        }

        /*
        快速排序算法
        思路:借助快速排序partition过程的一趟扫描法
        -回顾快速排序partition过程:随机选择一个元素作为切分元素(pivot),然后经过一次扫描,通过交换不同位置的元素使得数组按照数值大小分成以下3个部分:
         pivot

         循环不变量 分区定义:
         [0,p0) ==0
         [p0,i) ==1
         (p2,len01] ==2

         */
        public void sortColors2(int[] nums) {
            int len = nums.length;
            if (len < 2) {
                return;
            }

            int p0 = 0;
            int i = 0;
            int p2 = len - 1;
            while (i <= p2) {//(p2,len01] ==2
                if (nums[i] == 0) {
                    swap(nums, i, p0);
                    p0++;
                    i++;
                } else if (nums[i] == 1) {
                    i++;
                } else {
                    //nums[i] ==2
                    swap(nums, i, p2);
                    p2--;
                }
            }
        }
        private void swap(int[] nums, int i, int j) {
            int temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
        }
    }
}

LeetCode76 最小覆盖子串

题目详情

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
示例 2:
输入:s = "a", t = "a"
输出:"a"
示例 3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
提示:
1 <= s.length, t.length <= 105
s 和 t 由英文字母组成
进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?

代码
public class LeetCode76 {
    public static void main(String[] args) {
        System.out.println(new Solution().minWindow("ADOBECODEBANC", "ABC"));
        System.out.println(new Solution2().minWindow("ADOBECODEBANC", "ABC"));
    }

    /*
    滑动窗口
    本问题要求我们返回字符串s中包含字符串t的全部字符的最小窗口。
    我们称包含t的全部字母的窗口为"可行"窗口。

    我们可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针,一个用于"延伸"现有窗口的r指针,和一个用于"收缩"窗口的l指针。
    在任意时刻,只有一个指针运动,而另一个保持静止。
    我们在s上滑动窗口,通过移动r指针不断扩张窗口。
    当窗口包含t全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。
     */
    static class Solution {
        Map ori = new HashMap();
        Map cnt = new HashMap();

        public String minWindow(String s, String t) {
            int tLen = t.length();
            for (int i = 0; i < tLen; i++) {
                char c = t.charAt(i);
                ori.put(c, ori.getOrDefault(c, 0) + 1);
            }
            int l = 0, r = -1;
            int len = Integer.MAX_VALUE, ansL = -1, ansR = -1;
            int sLen = s.length();
            while (r < sLen) {
                ++r;
                if (r < sLen && ori.containsKey(s.charAt(r))) {
                    cnt.put(s.charAt(r), cnt.getOrDefault(s.charAt(r), 0) + 1);
                }
                while (check() && l <= r) {
                    if (r - l + 1 < len) {
                        len = r - l + 1;
                        ansL = l;
                        ansR = l + len;
                    }
                    if (ori.containsKey(s.charAt(l))) {
                        cnt.put(s.charAt(l), cnt.getOrDefault(s.charAt(l), 0) - 1);
                    }
                    ++l;
                }
            }
            return ansL == -1 ? "" : s.substring(ansL, ansR);
        }

        public boolean check() {
            Iterator iter = ori.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry) iter.next();
                Character key = (Character) entry.getKey();
                Integer val = (Integer) entry.getValue();
                if (cnt.getOrDefault(key, 0) < val) {
                    return false;
                }
            }
            return true;
        }
    }

    static class Solution2 {
        public String minWindow(String s, String t) {
            if (s == null || s == "" || t == null || t == "" || s.length() < t.length()) {
                return "";
            }
            //维护两个数组,记录已有字符串指定字符的出现次数,和目标字符串指定字符的出现次数
            //ASCII表总长128
            int[] need = new int[128];
            int[] have = new int[128];

            //将目标字符串指定字符的出现次数记录
            for (int i = 0; i < t.length(); i++) {
                need[t.charAt(i)]++;
            }

            //分别为左指针,右指针,最小长度(初始值为一定不可达到的长度)
            //已有字符串中目标字符串指定字符的出现总频次以及最小覆盖子串在原字符串中的起始位置
            int left = 0, right = 0, min = s.length() + 1, count = 0, start = 0;
            while (right < s.length()) {
                char r = s.charAt(right);
                //说明该字符不被目标字符串需要,此时有两种情况
                // 1.循环刚开始,那么直接移动右指针即可,不需要做多余判断
                // 2.循环已经开始一段时间,此处又有两种情况
                //  2.1 上一次条件不满足,已有字符串指定字符出现次数不满足目标字符串指定字符出现次数,那么此时
                //      如果该字符还不被目标字符串需要,就不需要进行多余判断,右指针移动即可
                //  2.2 左指针已经移动完毕,那么此时就相当于循环刚开始,同理直接移动右指针
                if (need[r] == 0) {
                    right++;
                    continue;
                }
                //当且仅当已有字符串目标字符出现的次数小于目标字符串字符的出现次数时,count才会+1
                //是为了后续能直接判断已有字符串是否已经包含了目标字符串的所有字符,不需要挨个比对字符出现的次数
                if (have[r] < need[r]) {
                    count++;
                }
                //已有字符串中目标字符出现的次数+1
                have[r]++;
                //移动右指针
                right++;
                //当且仅当已有字符串已经包含了所有目标字符串的字符,且出现频次一定大于或等于指定频次
                while (count == t.length()) {
                    //挡窗口的长度比已有的最短值小时,更改最小值,并记录起始位置
                    if (right - left < min) {
                        min = right - left;
                        start = left;
                    }
                    char l = s.charAt(left);
                    //如果左边即将要去掉的字符不被目标字符串需要,那么不需要多余判断,直接可以移动左指针
                    if (need[l] == 0) {
                        left++;
                        continue;
                    }
                    //如果左边即将要去掉的字符被目标字符串需要,且出现的频次正好等于指定频次,那么如果去掉了这个字符,
                    //就不满足覆盖子串的条件,此时要破坏循环条件跳出循环,即控制目标字符串指定字符的出现总频次(count)-1
                    if (have[l] == need[l]) {
                        count--;
                    }
                    //已有字符串中目标字符出现的次数-1
                    have[l]--;
                    //移动左指针
                    left++;
                }
            }
            //如果最小长度还为初始值,说明没有符合条件的子串
            if (min == s.length() + 1) {
                return "";
            }
            //返回的为以记录的起始位置为起点,记录的最短长度为距离的指定字符串中截取的子串
            return s.substring(start, start + min);
        }
    }
}

LeetCode78 子集

题目详情

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums 中的所有元素 互不相同

代码
public class LeetCode78 {
    public static void main(String[] args) {
        System.out.println(new Solution().subsets(new int[]{1, 2, 3, 4}));
        System.out.println(new Solution2().subsets(new int[]{1, 2, 3, 4}));
        System.out.println(new Solution3().subsets(new int[]{1, 2, 3, 4}));
    }

    /*
    迭代法实现子集枚举
     */
    static class Solution {
        List t = new ArrayList();
        List> ans = new ArrayList>();

        public List> subsets(int[] nums) {
            int n = nums.length;
            for (int mask = 0; mask < (1 << n); ++mask) {
                t.clear();
                for (int i = 0; i < n; ++i) {
                    if ((mask & (1 << i)) != 0) {
                        t.add(nums[i]);
                    }
                }
                ans.add(new ArrayList(t));
            }
            return ans;
        }
    }

    /*
    递归法实现子集枚举
     */
    static class Solution2 {
        List t = new ArrayList();
        List> ans = new ArrayList>();

        public List> subsets(int[] nums) {
            dfs(0, nums);
            return ans;
        }

        public void dfs(int cur, int[] nums) {
            if (cur == nums.length) {
                ans.add(new ArrayList(t));
                return;
            }
            t.add(nums[cur]);
            dfs(cur + 1, nums);
            t.remove(t.size() - 1);
            dfs(cur + 1, nums);
        }
    }

    /*
    本质是动态规划思想,属于较简单的线性动规。

可以这么表示,dp[i]表示前i个数的解集,dp[i] = dp[i - 1] + collections(i)。其中,collections(i)表示把dp[i-1]的所有子集都加上第i个数形成的子集。

【具体操作】

因为nums大小不为0,故解集中一定有空集。令解集一开始只有空集,然后遍历nums,每遍历一个数字,拷贝解集中的所有子集,将该数字与这些拷贝组成新的子集再放入解集中即可。时间复杂度为O(n^2)。

例如[1,2,3],一开始解集为[[]],表示只有一个空集。
遍历到1时,依次拷贝解集中所有子集,只有[],把1加入拷贝的子集中得到[1],然后加回解集中。此时解集为[[], [1]]。
遍历到2时,依次拷贝解集中所有子集,有[], [1],把2加入拷贝的子集得到[2], [1, 2],然后加回解集中。此时解集为[[], [1], [2], [1, 2]]。
遍历到3时,依次拷贝解集中所有子集,有[], [1], [2], [1, 2],把3加入拷贝的子集得到[3], [1, 3], [2, 3], [1, 2, 3],然后加回解集中。此时解集为[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]。
     */
    static class Solution3 {
        public List> subsets(int[] nums) {
            List> lists = new ArrayList<>(); // 解集
            lists.add(new ArrayList()); // 首先将空集加入解集中
            for (int i = 0; i < nums.length; i++) {
                int size = lists.size(); // 当前子集数
                for (int j = 0; j < size; j++) {
                    List newList = new ArrayList<>(lists.get(j));// 拷贝所有子集
                    newList.add(nums[i]); // 向拷贝的子集中加入当前数形成新的子集
                    lists.add(newList); // 向lists中加入新子集
                }
            }
            return lists;
        }
    }
}

LeetCode79 单词搜索

题目详情

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
输出:true
示例 3:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"
输出:false
提示:
m == board.length
n = board[i].length
1 <= m, n <= 6
1 <= word.length <= 15
board 和 word 仅由大小写英文字母组成
进阶:你可以使用搜索剪枝的技术来优化解决方案,使其在 board 更大的情况下可以更快解决问题?

代码
public class LeetCode79 {
    public static void main(String[] args) {
        char[][] board = {{'a', 'b'}};
        String word = "ba";
        Solution solution = new Solution();
        boolean exist = solution.exist(board, word);
        System.out.println(exist);
    }

    /*
    这是一个使用回溯算法解决的问题,涉及的知识点有 DFS 和状态重置。
     */

    static class Solution {
        private boolean[][] marked;

        //        x-1,y
        // x,y-1  x,y    x,y+1
        //        x+1,y
        private int[][] direction = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};
        // 盘面上有多少行
        private int m;
        // 盘面上有多少列
        private int n;
        private String word;
        private char[][] board;

        public boolean exist(char[][] board, String word) {
            m = board.length;
            if (m == 0) {
                return false;
            }
            n = board[0].length;
            marked = new boolean[m][n];
            this.word = word;
            this.board = board;

            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if (dfs(i, j, 0)) {
                        return true;
                    }
                }
            }
            return false;
        }

        private boolean dfs(int i, int j, int start) {
            if (start == word.length() - 1) {
                return board[i][j] == word.charAt(start);
            }
            if (board[i][j] == word.charAt(start)) {
                marked[i][j] = true;
                for (int k = 0; k < 4; k++) {
                    int newX = i + direction[k][0];
                    int newY = j + direction[k][1];
                    if (inArea(newX, newY) && !marked[newX][newY]) {
                        if (dfs(newX, newY, start + 1)) {
                            return true;
                        }
                    }
                }
                marked[i][j] = false;
            }
            return false;
        }
        private boolean inArea(int x, int y) {
            return x >= 0 && x < m && y >= 0 && y < n;
        }
    }
}

LeetCode

题目详情

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:


输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10
示例 2:

输入: heights = [2,4]
输出: 4
提示:
1 <= heights.length <=105
0 <= heights[i] <= 104

代码
public class LeetCode84 {
    public static void main(String[] args) {
        System.out.println(new Solution().largestRectangleArea(new int[]{2, 1, 5, 6, 2, 3}));
        System.out.println(new Solution().largestRectangleArea2(new int[]{2, 1, 5, 6, 2, 3}));
    }

    static class Solution {
        /*
        单调栈
         */
        public int largestRectangleArea(int[] heights) {
            int n = heights.length;
            int[] left = new int[n];
            int[] right = new int[n];

            Deque mono_stack = new ArrayDeque();
            for (int i = 0; i < n; ++i) {
                while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                    mono_stack.pop();
                }
                left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
                mono_stack.push(i);
            }

            mono_stack.clear();
            for (int i = n - 1; i >= 0; --i) {
                while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                    mono_stack.pop();
                }
                right[i] = (mono_stack.isEmpty() ? n : mono_stack.peek());
                mono_stack.push(i);
            }

            int ans = 0;
            for (int i = 0; i < n; ++i) {
                ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
            }
            return ans;
        }

        /*
        单调栈+常数优化
         */
        public int largestRectangleArea2(int[] heights) {
            int n = heights.length;
            int[] left = new int[n];
            int[] right = new int[n];
            Arrays.fill(right, n);

            Deque mono_stack = new ArrayDeque();
            for (int i = 0; i < n; ++i) {
                while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                    right[mono_stack.peek()] = i;
                    mono_stack.pop();
                }
                left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
                mono_stack.push(i);
            }

            int ans = 0;
            for (int i = 0; i < n; ++i) {
                ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
            }
            return ans;
        }
    }
}

LeetCode85 最大矩形

题目详情

给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
示例 1:


输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]
输出:6
解释:最大矩形如上图所示。
示例 2:
输入:matrix = []
输出:0
示例 3:
输入:matrix = [["0"]]
输出:0
示例 4:
输入:matrix = [["1"]]
输出:1
示例 5:
输入:matrix = [["0","0"]]
输出:0
提示:
rows == matrix.length
cols == matrix[0].length
1 <= row, cols <= 200
matrix[i][j] 为 '0' 或 '1'

代码
public class LeetCode85 {
    public static void main(String[] args) {
        System.out.println(new Solution().maximalRectangle(
                new char[][]{
                        {'1', '0', '1', '0', '0'},
                        {'1', '0', '1', '1', '1'},
                        {'1', '1', '1', '1', '1'},
                        {'1', '0', '0', '1', '0'}}));
        System.out.println(new Solution().maximalRectangle2(
                new char[][]{
                        {'1', '0', '1', '0', '0'},
                        {'1', '0', '1', '1', '1'},
                        {'1', '1', '1', '1', '1'},
                        {'1', '0', '0', '1', '0'}}));

    }

    static class Solution {
        /*
        暴力解法
         */
        public int maximalRectangle(char[][] matrix) {
            int m = matrix.length;
            if (m == 0) {
                return 0;
            }
            int n = matrix[0].length;
            int[][] left = new int[m][n];

            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if (matrix[i][j] == '1') {
                        left[i][j] = (j == 0 ? 0 : left[i][j - 1]) + 1;
                    }
                }
            }

            int ret = 0;
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if (matrix[i][j] == '0') {
                        continue;
                    }
                    int width = left[i][j];
                    int area = width;
                    for (int k = i - 1; k >= 0; k--) {
                        width = Math.min(width, left[k][j]);
                        area = Math.max(area, (i - k + 1) * width);
                    }
                    ret = Math.max(ret, area);
                }
            }
            return ret;
        }

        /*
        单调栈
         */
        public int maximalRectangle2(char[][] matrix) {
            int m = matrix.length;
            if (m == 0) {
                return 0;
            }
            int n = matrix[0].length;
            int[][] left = new int[m][n];

            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if (matrix[i][j] == '1') {
                        left[i][j] = (j == 0 ? 0 : left[i][j - 1]) + 1;
                    }
                }
            }

            int ret = 0;
            for (int j = 0; j < n; j++) { // 对于每一列,使用基于柱状图的方法
                int[] up = new int[m];
                int[] down = new int[m];

                Deque stack = new LinkedList();
                for (int i = 0; i < m; i++) {
                    while (!stack.isEmpty() && left[stack.peek()][j] >= left[i][j]) {
                        stack.pop();
                    }
                    up[i] = stack.isEmpty() ? -1 : stack.peek();
                    stack.push(i);
                }
                stack.clear();
                for (int i = m - 1; i >= 0; i--) {
                    while (!stack.isEmpty() && left[stack.peek()][j] >= left[i][j]) {
                        stack.pop();
                    }
                    down[i] = stack.isEmpty() ? m : stack.peek();
                    stack.push(i);
                }

                for (int i = 0; i < m; i++) {
                    int height = down[i] - up[i] - 1;
                    int area = height * left[i][j];
                    ret = Math.max(ret, area);
                }
            }
            return ret;
        }
    }
}

LeetCode94 二叉树的中序遍历

题目详情

给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3]
1
\
2
/
3
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
提示:
树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100
**进阶: **递归算法很简单,你可以通过迭代算法完成吗?

代码
class LeetCode94 {
    public static void main(String[] args) {
        TreeNode node1 = TreeNode.buildBinaryTree(new Integer[]{6, 4, 9, 2, 5, 7, 10, 1, 3, null, null, null, 8});

        TreeNode.prettyPrintTree(node1);

        System.out.println(new Solution1().inOrderTraversal(node1));

        System.out.println(new Solution2().inOrderTraversal(node1));
        System.out.println(new Solution2().inOrderTraversal(node1));

        System.out.println(new Solution1().inOrderTraversal(node1));

        System.out.println(new Solution3().inOrderTraversal(node1));

        //System.out.println(TreeNode.binaryTree2List(node1));
    }

    /*
    第一种解决方法是使用递归。这是经典的方法,直截了当。我们可以定义一个辅助函数来实现递归。

     */
    static class Solution1 {
        public List inOrderTraversal(TreeNode root) {
            List res = new ArrayList<>();
            helper(root, res);
            return res;
        }

        public void helper(TreeNode root, List res) {
            if (root != null) {
                //先左子树
                if (root.left != null) {
                    helper(root.left, res);
                }
                res.add(root.val);
                //后右子树
                if (root.right != null) {
                    helper(root.right, res);
                }
            }
        }
    }

    /*
    方法二:基于栈的遍历
     */
    static class Solution2 {
        public List inOrderTraversal(TreeNode root) {
            List res = new ArrayList<>();
            Stack stack = new Stack<>();
            TreeNode curr = root;
            while (curr != null || !stack.isEmpty()) {
                while (curr != null) {//一直取左子树知道叶子节点
                    stack.push(curr);//从根节点到最左叶节点依次入栈
                    curr = curr.left;//一直移动指针到左子树
                }
                curr = stack.pop();//子节点先出栈
                res.add(curr.val);//值加入结果列表
                curr = curr.right;//移动指针到右子树
            }
            return res;
        }
    }

    // 莫里斯中序遍历 不破坏树结构
    static class Solution3 {
        public List inOrderTraversal(TreeNode root) {
            List ldr = new ArrayList();
            TreeNode cur = root;
            TreeNode pre = null;
            while (cur != null) {
                if (cur.left == null) {//左子树为空,输出当前节点,将其右孩子作为当前节点
                    ldr.add(cur.val);
                    cur = cur.right;
                } else {
                    pre = cur.left;//左子树
                    while (pre.right != null && pre.right != cur) {//找到前驱节点,即左子树中的最右节点
                        pre = pre.right;
                    }
                    //如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。
                    if (pre.right == null) {
                        pre.right = cur;
                        cur = cur.left;
                    }
                    //如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状)。输出当前节点。当前节点更新为当前节点的右孩子。
                    if (pre.right == cur) {
                        pre.right = null;
                        ldr.add(cur.val);
                        cur = cur.right;
                    }
                }
            }
            return ldr;
        }
    }
}

LeetCode96 不同的二叉搜索树

题目详情

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例 1:


输入:n = 3
输出:5
示例 2:
输入:n = 1
输出:1
提示:
1 <= n <= 19

代码
class LeetCode96 {

    public static void main(String[] args) {
        System.out.println(new Solution().numTrees(3));
        System.out.println(new Solution1().numTrees(3));
        System.out.println(new Solution2().numTrees(3));
    }


    /*
    直觉

本问题可以用动态规划求解。

给定一个有序序列 1 ... n,为了根据序列构建一棵二叉搜索树。我们可以遍历每个数字 i,将该数字作为树根,1 ... (i-1) 序列将成为左子树,(i+1) ... n 序列将成为右子树。
于是,我们可以递归地从子序列构建子树。
在上述方法中,由于根各自不同,每棵二叉树都保证是独特的。

可见,问题可以分解成规模较小的子问题。因此,我们可以存储并复用子问题的解,而不是递归的(也重复的)解决这些子问题,这就是动态规划法。

算法:

问题是计算不同二叉搜索树的个数。为此,我们可以定义两个函数:

G(n): 长度为n的序列的不同二叉搜索树个数。

F(i,n): 以i为根的不同二叉搜索树个数(1 <= i<=n)。

G(n)= ∑G(i−1)⋅G(n−i)
     */
    static class Solution {
        public int numTrees(int n) {
            /*长度为n的序列的不同二叉搜索树的个数*/
            int[] G = new int[n + 1];
            G[0] = 1;
            G[1] = 1;

            for (int k = 2; k <= n; ++k) {
                for (int i = 1; i <= k; ++i) {
                    G[k] += G[i - 1] * G[k - i];
                }
            }
            return G[n];
        }
    }

    /*
    本问题可以用动态规划求解。
给定一个有序序列 1 ... n,为了根据序列构建一棵二叉搜索树。我们可以遍历每个数字 i,将该数字作为树根,1 ... (i-1) 序列将成为左子树,(i+1) ... n 序列将成为右子树。于是,我们可以递归地从子序列构建子树。
在上述方法中,由于根各自不同,每棵二叉树都保证是独特的。

可见,问题可以分解成规模较小的子问题。因此,我们可以存储并复用子问题的解,而不是递归的(也重复的)解决这些子问题,这就是动态规划法。

     */
    static class Solution1 {
        public int numTrees(int n) {
            int[] G = new int[n + 1];
            G[0] = 1;
            G[1] = 1;

            for (int i = 2; i <= n; ++i) {
                for (int j = 1; j <= i; ++j) {
                    G[i] += G[j - 1] * G[i - j];
                }
            }
            return G[n];
        }
    }

    /*
    事实上G(n)函数的值被称为 卡塔兰数
 。卡塔兰数更便于计算的定义如下:

证明过程可以参考上述文献,此处略去。

     */
    static class Solution2 {
        public int numTrees(int n) {
            //注意:这里应该使用long而不是int,否则溢出
            long C = 1;
            for (int i = 0; i < n; ++i) {
                C = C * 2 * (2 * i + 1) / (i + 2);
            }
            return (int) C;
        }
    }
}

LeetCode98 验证二叉搜索树

题目详情

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:


输入:root = [2,1,3]
输出:true
示例 2:


输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
树中节点数目范围在[1, 104] 内
-231 <= Node.val <= 231 - 1

代码
class LeetCode98 {
    public static void main(String[] args) {
        System.out.println(new Solution().isValidBST(TreeNode.buildBinaryTree(new Integer[]{2, 1, 5, null, 5})));
        System.out.println(new Solution1().isValidBST(TreeNode.buildBinaryTree(new Integer[]{2, 1, 5, null, 5})));
    }

    /*
    要解决这道题首先我们要了解二叉搜索树有什么性质可以给我们利用,
    由题目给出的信息我们可以知道:如果该二叉树的左子树不为空,则左子树上所有节点的值均小于它的根节点的值;
     若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;它的左右子树也为二叉搜索树。
     */
    static class Solution {
        public boolean helper(TreeNode node, Integer lower, Integer upper) {
            if (node == null) return true;

            int val = node.val;
            if (lower != null && val <= lower) return false;
            if (upper != null && val >= upper) return false;

            if (!helper(node.right, val, upper)) return false;
            if (!helper(node.left, lower, val)) return false;
            return true;
        }

        public boolean isValidBST(TreeNode root) {
            return helper(root, null, null);
        }
    }

    /*
    方法二:中序遍历
 思路和算法

 基于方法一中提及的性质,我们可以进一步知道二叉搜索树「中序遍历」得到的值构成的序列一定是升序的,这启示我们在中序遍历的时候实时检查当前节点的值是否大于前一个中序遍历到的节点的值即可。如果均大于说明这个序列是升序的,整棵树是二叉搜索树,
 否则不是,下面的代码我们使用栈来模拟中序遍历的过程。

 可能由读者不知道中序遍历是什么,我们这里简单提及一下,
 中序遍历是二叉树的一种遍历方式,它先遍历左子树,再遍历根节点,最后遍历右子树。而我们二叉搜索树保证了左子树的节点的值均小于根节点的值,根节点的值均小于右子树的值,因此中序遍历以后得到的序列一定是升序序列。

     */
    static class Solution1 {
        public boolean isValidBST(TreeNode root) {
            Stack stack = new Stack();
            double inorder = -Double.MAX_VALUE;

            while (!stack.isEmpty() || root != null) {
                while (root != null) {
                    stack.push(root);
                    root = root.left;
                }
                root = stack.pop();
                // 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
                if (root.val <= inorder) return false;
                inorder = root.val;
                root = root.right;
            }
            return true;
        }
    }
}

你可能感兴趣的:(LeetCode热门100题算法和思路(day4))