Loading

🔥 LeetCode 热题 HOT 100(61-70)

207. 课程表

思路:根据题意可知:当课程之间不存在 环状 循环依赖时,便能完成所有课程的学习,反之则不能。因此可以将问题转换成: 判断有向图中是否存在环。使用 拓扑排序法 :

  • 构建 入度表:记录每个结点的入度数;
  • 构建 邻接表:记录每个结点的后继结点;
  • 将入度为0的结点加入队列,开始遍历;
  • 当一个结点出队时,将其后继结点的入度数减1,若减1后入度为0则将后继结点也加入队列;
  • 当所有入度为0的结点都已经出队时,检查是否还有入度不为0的结点,存在则说明不可能完成所有课程的学习。
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        Map<Integer, Integer> indegreeMap = new HashMap<>();
        Map<Integer, List<Integer>> postsMap = new HashMap<>();

        // 初始化 入度表, 邻接表
        for (int i = 0; i < numCourses; i++) {
            indegreeMap.put(i, 0);
            postsMap.put(i, new LinkedList<>());
        }

        for (int[] temp : prerequisites) {
            int cur = temp[1];
            int post = temp[0];

            // 统计后继结点入度
            indegreeMap.put(post, indegreeMap.getOrDefault(post, 0) + 1);
            
            // 建立 邻接表
            postsMap.get(cur).add(post);

        }

        Deque<Integer> queue = new LinkedList<>();
        // 向队列中加入入度为0的结点
        for (Integer key : indegreeMap.keySet()) {
            if (indegreeMap.get(key) == 0) {
                queue.offer(key);
            }
        }

        // 出队
        while (!queue.isEmpty()) {
            Integer cur = queue.poll();
            List<Integer> posts = postsMap.get(cur);

            for (Integer post : posts) {
                // 将后继结点入度减1
                indegreeMap.put(post, indegreeMap.get(post) - 1);
                // 将减1后入度为0,且存在后继的后继结点加入队列
                if (indegreeMap.get(post) == 0) {
                    queue.offer(post);
                }
            }
        }

        for (Integer key : indegreeMap.keySet()) {
            if (indegreeMap.get(key) > 0) {
                return false;
            }
        }

        return true;
    }
}

208. 实现 Trie (前缀树)

推荐题解:Trie Tree 的实现 (适合初学者)🌳

class Trie {
    private Node root;

    /** Initialize your data structure here. */
    public Trie() {
        root = new Node();
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        Node temp = root;
        for (int i = 0; i < word.length(); i++) {
            if (temp.children[word.charAt(i) - 'a'] == null) {
                temp.children[word.charAt(i) - 'a'] = new Node();
            }
            
            temp = temp.children[word.charAt(i) - 'a'];
        }

        temp.isEnd = true;
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        Node temp = root;
        for (int i = 0; i < word.length(); i++) {
            if (temp.children[word.charAt(i) - 'a'] == null) {
                return false;
            }
            
            temp = temp.children[word.charAt(i) - 'a'];
        }

        return temp.isEnd;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        Node temp = root;
        for (int i = 0; i < prefix.length(); i++) {
            if (temp.children[prefix.charAt(i) - 'a'] == null) {
                return false;
            }
            
            temp = temp.children[prefix.charAt(i) - 'a'];
        }

        return true;
    }
}

class Node {
    boolean isEnd = false;
    Node[] children = new Node[26];
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */

215. 数组中的第K个最大元素

思路:基于快速排序的选择方法。每次partition即可确定一个元素的位置 mid,如果当前mid的位置小于len - k,则下一次的partition区间就为[mid + 1, right],反之则为[left, mid + 1],直到mid = len - k

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        int left = 0;
        int right = len - 1;

        while (true) {
            int mid = partition(nums, left, right);

            if (mid == (len - k)) {
                return nums[mid];
            } else if (mid < (len - k)) {
                left = mid + 1;
            } else if (mid > (len - k)) {
                right = mid - 1;
            }
        }
    }

    private Random random = new Random(47);

    // 将 大于等于 pivot 的值放在 pivot 右边,小于 pivot 的放在 pivot 左边
    private int partition(int[] nums, int left, int right) {
        int randIndex = left + random.nextInt(right - left + 1);
        swap(nums, left, randIndex);
        int pivot = nums[left];

        while (left < right) {
            // 在右边找一个小于 pivot 的值填至 left
            while (left < right && nums[right] >= pivot) {
                right--;
            }
            if (left < right) {
                swap(nums, left, right);
                left++;
            }

            // 在左边找一个大于等于 pivot 的值填至 right
            while (left < right && nums[left] < pivot) {
                left++;
            }
            if (left < right) {
                swap(nums, left, right);
                right--;
            }
        }

        nums[left] = pivot;
        return left;
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

221. 最大正方形

class Solution {
    public int maximalSquare(char[][] matrix) {
        int row = matrix.length;
        int col = matrix[0].length;

        // 以 matrix[i][j] 为右下角的正方形的最大边长
        int[][] dp = new int[row][col];

        int maxLen = 0;
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                //base case
                if (i == 0 || j == 0) {
                    dp[i][j] = matrix[i][j] - '0';

                // 转移方程:取 左、上、左上 三个dp值的最小值加1
                } else if (matrix[i][j] == '1') {
                    dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
                }

                maxLen = Math.max(maxLen, dp[i][j]);
            }
        }

        return maxLen * maxLen;
    }
}

推荐题解:理解 三者取最小+1

226. 翻转二叉树

/**
 * 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 TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return root;
        }

        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;

        invertTree(root.left);
        invertTree(root.right);

        return root;
    }
}

234. 回文链表

思路:快慢指针找到中点,反转后半部分,然后依次比较,最后要记得还原链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        //没有或只有一个结点
        if (head == null || head.next == null) {
            return true;
        }

        ListNode mid = getMidNode(head);
        ListNode rHead = reverse(mid.next);
        //切断
        mid.next = null;

        ListNode lTemp = head;
        ListNode rTemp = rHead;

        //右半部分链表长度小于等于左半部分链表长度
        while (rTemp != null) {
            if (lTemp.val != rTemp.val) {
                return false;
            }
            
            lTemp = lTemp.next;
            rTemp = rTemp.next;
        }

        //还原
        mid.next = reverse(rHead);
        
        return true;
    }

    //对于偶数个结点和奇数个结点,slow都位于右部分的前一个结点
    private ListNode getMidNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head.next;

        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        return slow;
    }

    //反转链表
    private ListNode reverse(ListNode head) {
        ListNode pre = null, cur = head, next = head;

        while (cur != null) {
            next = cur.next;
            cur.next = pre;

            pre = cur;
            cur = next;
        }

        return pre;
    }
}
  • 推荐使用slow = head;fast = head.next;的方式使用快慢指针
  • 该方式下,结点个数为奇数,slow指向中点;结点个数为偶数,slow指向左边中点

236. 二叉树的最近公共祖先

思路:后序遍历演变而来,若找到其中一个结点就自底向上返回。若p、q互不为对方子树中的结点,p、q最终会在某个结点相遇,该节点就为最近公共祖先;否则p或q即为最近公共结点。

class Solution {
    //重要已知:p != q
    //     p 和 q 均存在于给定的二叉树中。
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) {
            return root;
        }

        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);

        if (left != null && right != null) {
            return root;
        }

        return left != null ? left : right;
    }
}

238. 除自身以外数组的乘积

如果元素中不含 0 就很简单:先遍历一遍数组求出所有元素的 总乘积,第二次遍历数组只需要用 总乘积 除以当前元素即可得到其他各元素的乘积。
思路:由于这里元素是可能为0的,那么可以通过两个备忘录,分别记录当前元素 左侧所有元素乘积 和 右侧所有元素乘积,然后两者对应坐标元素相乘即可。

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int len = nums.length;
        int[] l_product = new int[len];
        int[] r_product = new int[len];

        // 左侧所有元素乘积 
        l_product[0] = 1;
        for (int i = 1; i < len; i++) {
            l_product[i] = nums[i - 1] * l_product[i - 1];
        }
		
        // 左侧所有元素乘积 
        r_product[len - 1] = 1;
        for (int i = len - 2; i >= 0; i--) {
            r_product[i] = nums[i + 1] * r_product[i + 1];
        }

        int[] res = new int[len];
        for (int i = 0; i < len; i++) {
            res[i] = l_product[i] * r_product[i];
        }

        return res;
    }
}

由于题目进阶要求 常数空间复杂度(输出数组不被视为额外空间,也就是上面代码中的res)。

思路:我们其实可以先只存储 左侧所有元素乘积:

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int len = nums.length;
        int[] res = new int[len];

        res[0] = 1;
        for (int i = 1; i < len; i++) {
            res[i] = nums[i - 1] * res[i - 1];
        }

        int r_product = 1;
        for (int i = len - 1; i >= 0; i--) {
            res[i] = res[i] * r_product;
            r_product = nums[i] * r_product;
        }

        return res;
    }
}

239. 滑动窗口最大值

求窗口中的最大值肯定很简单,但是如何在窗口向右滑动时(最左边元素出窗,最右边有新元素进窗)快速( O(n) )求出新的最大值?

思路:使用单调队列(由大到小)记录当前窗口中的最大值以及候选最大值

class Solution {
    
    public int[] maxSlidingWindow(int[] nums, int k) {
        int len = nums.length;
        int[] maxs = new int[len - k + 1];

        int j = 0;
        for (int i = 0 ; i < len; i++) {
            if (i < k - 1) {
                offer(nums[i]);
            } else {
                offer(nums[i]);

                maxs[j++] = max();

                poll(nums[i - k + 1]);
            }
        }

        return maxs;
    }

    private Deque<Integer> queue = new LinkedList<>();

    //保证队列头始终是当前窗口中的最大值,后面是次大值(如果存在)
    private void offer(int e) {
        //将队列尾部小于新元素的候选值全部出队
        while (!queue.isEmpty() && e > queue.peekLast()) {
            queue.pollLast();
        }
        queue.offer(e);
    }

    //当 出窗口的元素是当前最大值时更新队列
    private void poll(int e) {
        if (e == queue.peek()) {
            queue.poll();
        }
    }

    private int max() {
        return queue.peek();
    }
}

推荐题解:单调队列解题详解

240. 搜索二维矩阵 II

思路:从右上角开始,target小于当前值则该列可以被排除,target大于当前值则该行可以被排除

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int row = matrix.length;
        int col = matrix[0].length;

        int i = 0;
        int j = col - 1;

        while (i < row && j >= 0) {
            if (target < matrix[i][j]) {
                j--;
            } else if (target > matrix[i][j]) {
                i++;
            } else if (target == matrix[i][j]) {
                return true;
            }
        }

        return false;
    }
}
posted @ 2021-07-17 04:32  WINLSR  阅读(47)  评论(0编辑  收藏  举报