LeetCode——100热题对应方法一览(142~300)

242 阅读24分钟

142. 环形链表 II ^_^

快慢指针,定在环内相遇,相遇后分别从head和相遇点继续走,再次相遇的即是环入口

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos-1,则在该链表中没有环。 

说明:不允许修改给定的链表。

示例:
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。


public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) break;
        }
        if (fast == null || fast.next == null) return null;
        fast = head;
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}


146. LRU缓存机制

LRU缓存算法需要一个数据存储结构同时具有“查找快、插入快、删除快、有顺序“的特性;哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢;所以结合形成一种新的数据结构:哈希链表。


运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。

写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。 

进阶: 你是否可以在 O(1) 时间复杂度内完成这两种操作?

示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得密钥 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得密钥 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4

class LRUCache {
    class Node {
        int key, val;
        Node pre, next;
        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }

    class DoubleLinked {
        Node head, tail;
        int size;
        public DoubleLinked() {
            head = new Node(0, 0);
            tail = new Node(0, 0);
            head.next = tail;
            tail.pre = head;
            size = 0;
        }
        public void AddFirst(Node x) {
            x.next = head.next;
            x.pre = head;
            head.next.pre = x;
            head.next = x;
            size++;
        }
        public void remove(Node x) {
            x.next.pre = x.pre;
            x.pre.next = x.next;
            size--;
        }
        public Node removeLast() {
            if (tail.pre == head) return null;
            Node last = tail.pre;
            remove(last);
            return last;
        }
        public int size() {
            return size;
        }
    }

    Map<Integer, Node> map;
    DoubleLinked cache;
    int capacity;
    public LRUCache(int capacity) {
        map = new HashMap<Integer, Node>();
        cache = new DoubleLinked();
        this.capacity = capacity;
    }
    public int get(int key) {
        if (!map.containsKey(key)) return -1;
        int value = map.get(key).val;
        put(key, value);
        return value;
    }
    public void put(int key, int value) {
        Node x = new Node(key, value);
        if (map.containsKey(key)) {
            cache.remove(map.get(key));
        } else {
            if (cache.size() == capacity) {
                Node last = cache.removeLast();
                map.remove(last.key);
            }
        }
        cache.AddFirst(x);
        map.put(key, x);
    }
}
/** * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */


148. 排序链表

  • 递归归并法,不断拆分为两半,通过双指针找到中间点并断掉中点后的指针,得到返回的前后两个链表,问题转化为合并两个有序链表
  • 非递归,设置一个intv变量从1通过乘2变化到链表length,每次合并intv长的两小段,h1和h2指针分别指向这两段,注意不能正好以intv分为整数份的情况

O(nlogn) 时间复杂度和常数级空间复杂度下,对链表进行排序。

示例:
输入: 4->2->1->3
输出: 1->2->3->4

// 递归
class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null)
             return head;
        ListNode fast = head.next;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        ListNode tmp = slow.next;
        slow.next = null;
        ListNode left = sortList(head);
        ListNode right = sortList(tmp);
        ListNode h = new ListNode(0);
        ListNode res = h;
        while (left != null && right != null) {
            if (left.val < right.val) {
                h.next = left;
                left = left.next;
            } else {
                h.next = right;
                right = right.next;
            }
            h = h.next;
        }
        h.next = left == null ? right : left;
        return res.next;
    }
}

// 非递归
class Solution {
    public ListNode sortList(ListNode head) {
        ListNode h, res, pre, h1, h2;
        int length = 0, intv = 1;
        h = head;
        while (h != null) {
            h = h.next;
            length++;
        }
        res = new ListNode(0);
        res.next = head;
        while (intv < length) {
            pre = res;
            h = res.next;
            while (h != null) {
                int i = intv;
                h1 = h;
                while (i > 0 && h != null) {
                    h = h.next;
                    i--;
                }
                if (i > 0) break;
                h2 = h;
                i = intv;
                while (i > 0 && h != null) {
                    h = h.next;
                    i--;
                }
                int c1 = intv, c2 = intv - i;
                while (c1 > 0 && c2 > 0) {
                    if (h1.val < h2.val) {
                        pre.next = h1;
                        h1 = h1.next;
                        c1--;
                    } else {
                        pre.next = h2;
                        h2 = h2.next;
                        c2--;
                    }
                    pre = pre.next;
                }
                pre.next = c1 == 0 ? h2 : h1;
                while (c1 > 0 || c2 > 0) {
                    pre = pre.next;
                    c1--;
                    c2--;
                }
                pre.next = h;
            }
            intv *= 2;
        }
        return res.next;
    }
}


152. 乘积最大子序列

每一步保存当前最大值和最小值,因为当num为负时,乘最大值会变为最小值,乘最小值会变成最大值

给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。

示例:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

class Solution {
    public int maxProduct(int[] nums) {
        int res = Integer.MIN_VALUE;
        int currmin = 1, currmax = 1;
        for (int num : nums) {
            if (num < 0) {
                int x = currmax;
                currmax = currmin;
                currmin = x;
            }
            currmax = Math.max(currmax, currmax * num);
            currmin = Math.min(currmin, currmin * num);
            res = Math.max(res, currmax);
        }
        return res;
    }
}


155. 最小栈 ^_^

同步或异步辅助栈

设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。

  • push(x) -- 将元素 x 推入栈中。
  • pop() -- 删除栈顶的元素。
  • top() -- 获取栈顶元素。
  • getMin() -- 检索栈中的最小元素。

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2. 

// 同步辅助栈
class MinStack {
    Stack<Integer> stack;
    Stack<Integer> min_stack;
    /** initialize your data structure here. */
    public MinStack() {
        stack = new Stack<>();
        min_stack = new Stack<>();
    }
    
    public void push(int x) {
        if (!stack.empty() && min_stack.peek() < x) {
            min_stack.push(min_stack.peek());
        } else {
            min_stack.push(x);
        }
        stack.push(x);
    }
    
    public void pop() {
        min_stack.pop();
        stack.pop();
    }

    public int top() {
        return stack.peek();
    }

    public int getMin() {
        return min_stack.peek();
    }
}
/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */


160. 相交链表

长短链表拼接,双指针,两指针走过的路程相同,注意headA或headB为空的特例

编写一个程序,找到两个单链表相交的起始节点。

如下面的两个链表


在节点 c1 开始相交。

输入:intersectVal = 8, listA = [4,1,8,4,5],
     listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。
        从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
        在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) return null;
        ListNode pA = headA;
        ListNode pB = headB;
        while (pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        if (pA == null || pB == null) return null;
        return pA;
    }
}


169. 多数元素 ^_^

认为每遇到的第一个数就是多数元素,往后继续寻找,若发现这个元素的数量小于当前元素的一半时,则遗忘当前所有元素,重新开始统计

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。 

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例:
输入: [3,2,3]
输出: 3

class Solution {
    public int majorityElement(int[] nums) {
        int count = 1;
        int major = nums[0];
        for (int i = 1; i < nums.length; i++) {
            if (count == 0)
                 major = nums[i];
            if (nums[i] == major) {
                count++;
            } else {
                count--;
            }
        }
        return major;
    }
}


198. 打家劫舍 ^_^

动态规划

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

class Solution {
    public int rob(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        int steal = nums[0];
        int not_steal = 0;
        for (int i = 1; i < nums.length; i++) {
            int x = steal;
            steal = not_steal + nums[i];
            not_steal = Math.max(x, not_steal);
        }
        return Math.max(steal, not_steal);
    }
}


200. 岛屿数量 ^_^

深度优先遍历,需要建立方向数组,遍历矩阵遇到‘1’就开始深度搜索其周围的陆地,搜索过的陆地置‘0’标记避免重复搜索,注意不要搜索出界

给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。

示例:
输入:
11110
11010
11000
00000

输出: 1

class Solution {
    char[][] grid;
    int m, n;
    int[][] direction = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) return 0;
        int res = 0;
        this.grid = grid;
        m = grid.length;
        n = grid[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '1') {
                    dfs(i, j);
                    res++;
                }
            }
        }
        return res;
    }
    private void dfs(int i, int j) {
        grid[i][j] = '0';
        for (int k = 0; k < 4; k++) {
            int row = i + direction[k][0];
            int col = j + direction[k][1];
            if (inArea(row, col) && grid[row][col] == '1')
                dfs(row, col);
        }
    }
    private boolean inArea(int i, int j) {
        return i >= 0 && j >= 0 && i < m && j < n;
    }
}


206. 反转链表

  • 递归,返回的应是反转后的首节点,每个节点都将其后一节点指向自己,再将自己的指针置空
  • 迭代,三指针,从head开始走,需要记录当前节点及前后节点

反转一个单链表。

示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

// 递归
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode p = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return p;
    }
}

// 迭代
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode nextNode = curr.next;
            curr.next = pre;
            pre = curr;
            curr = nextNode;
        }
        return pre;
    }
}


207. 课程表

一个有向图中是否有环问题,首先统计所有节点的入度,通过队列储存入度为0(即可直接学习的课程)的节点,每次去掉一个入度为0的节点并将其指向的相邻节点的入度减1,若出现了新的入度为0节点则入队,最后统计是否所有节点都被去除

现在你总共有 n 门课需要选,记为 0n-1。 

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]  

给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] indegrees = new int[numCourses];
        for (int i = 0; i < prerequisites.length; i++) {
            indegrees[prerequisites[i][0]]++;
        }
        Queue<Integer> q = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) {
            if (indegrees[i] == 0) q.add(i);
        }
        while (!q.isEmpty()) {
            int curr = q.poll();
            numCourses--;
            for (int i = 0; i < prerequisites.length; i++) {
                if (prerequisites[i][1] == curr) {
                    indegrees[prerequisites[i][0]]--;
                    if (indegrees[prerequisites[i][0]] == 0)
                        q.add(prerequisites[i][0]);
                }
            }
        }
        return numCourses == 0;
    }
}


208. 实现Trie(前缀树)

 背下来!建立节点类,前缀树的每个节点都具有一个指针数组和判断是否为某单词结尾的flag

(下图存了“sea”,“sells”,“she”三个词)


实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。

Trie trie = new Trie();

trie.insert("apple");
trie.search("apple");   // 返回 true
trie.search("app");     // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");   
trie.search("app");     // 返回 true 

说明:

  • 你可以假设所有的输入都是由小写字母 a-z 构成的。
  • 保证所有输入均为非空字符串。

class Trie {
    class TrieNode {
        private TrieNode[] links;
        private boolean isEnd;
        public TrieNode() {
            links = new TrieNode[26];
        }
        public boolean containsKey(char c) {
            return links[c - 'a'] != null;
        }
        public void put(char c, TrieNode node) {
            links[c - 'a'] = node;
        }
        public TrieNode get(char c) {
            return links[c - 'a'];
        }
        public void setEnd() {
            isEnd = true;
        }
        public boolean isEnd() {
            return isEnd;
        }
    }
    
    private TrieNode root;
    
    /** Initialize your data structure here. */
    public Trie() {
       root = new TrieNode();
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        TrieNode node = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (!node.containsKey(c)) {
                node.put(c, new TrieNode());
            }
            node = node.get(c);
        }
        node.setEnd();
    }

    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        TrieNode node = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (!node.containsKey(c)) return false;
            node = node.get(c);
        }
        return node.isEnd();
    }

    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        TrieNode node = root;
        for (int i = 0; i < prefix.length(); i++) {
            char c = prefix.charAt(i);
            if (!node.containsKey(c)) return false;
            node = node.get(c);
        }
        return true;
    }
}
/**
 * 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个最大元素

  • 排序法,排序后返回nums[length-k],时间复杂度 O(N logN)
  • 小顶堆,维护一个大小为k的小顶堆,所有元素入堆后,堆顶元素即第K大,时间复杂度 O(N logk)
  • 快速选择,选定一个元素将其放在正确位置,即其左边元素均小于它,右边元素均大于它,确定第k大的元素应在其左还是右边,继续寻找,时间复杂度 平均O(N) 最坏O(N^2)

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

// 排序
class Solution {
    public int findKthLargest(int[] nums, int k) {
        Arrays.sort(nums);
        return nums[nums.length - k];
    }
}

// 小顶堆
class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> q = new PriorityQueue<>();
        for (int i = 0; i < nums.length; i++) {
            q.add(nums[i]);
            if (q.size() > k) {
                q.poll();
            }
        }
        return q.poll();
    }
}

// 快速选择
class Solution {
    public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        int target = len - k;
        int left = 0, right = len - 1;
        while (true) {
            int index = partition(nums, left, right);
            if (index == target)
                 return nums[index];
            else if (index > target)
                right = index - 1;
            else
                left = index + 1;
        }
    }
    private int partition(int[] nums, int left, int right) {
        int pivot = nums[left];
        int j = left;
        for (int i = left + 1; i <= right; i++) {
            if (nums[i] < pivot) {
                j++;
                swap(nums, i, j);
            }
        }
        swap(nums, j, left);
        return j;
    }
    private void swap(int[] nums, int i, int j) {
        int x = nums[i];
        nums[i] = nums[j];
        nums[j] = x;
    }
}


221. 最大正方形

动态规划,dp[i][j]表示以matrix[i-1][j-1]为右下角的最大正方形的边长,可先建立二维的dp再进行空间复杂度优化

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。

示例:
输入: 
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
输出: 4

class Solution {
    public int maximalSquare(char[][] matrix) {
        if (matrix == null || matrix.length == 0) return 0;
        int maxlen = 0;
        int predp = 0;
        int row = matrix.length;
        int col = matrix[0].length;
        int[] dp = new int[col + 1];
        for (int i = 1; i <= row; i++) {
            for (int j = 1; j <= col; j++) {
                int x = dp[j];
                if (matrix[i - 1][j - 1] == '1') {
                    dp[j] = Math.min(Math.min(dp[j], dp[j - 1]), predp) + 1;
                } else {
                    dp[j] = 0;
                }
                maxlen = Math.max(maxlen, dp[j]);
                predp = x;
            }
        }
        return maxlen * maxlen;
    }
}


226. 翻转二叉树

  • 递归,建立helper函数交换左右子树
  • 非递归,建立队列层序遍历,交换节点的左右子节点

翻转一棵二叉树。

示例:

输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9

输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1 

// 递归
class Solution {
    public TreeNode invertTree(TreeNode root) {
        helper(root);
        return root;
    }
    private void helper(TreeNode root) {
        if (root == null) return;
        TreeNode l = root.left;
        root.left = root.right;
        root.right = l;
        helper(root.left);
        helper(root.right);
    }
}

// 非递归
class Solution {
    public TreeNode invertTree(TreeNode root) {
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        while (!q.isEmpty()) {
            TreeNode node = q.poll();
            if (node == null) continue;
            TreeNode l = node.left;
            node.left = node.right;
            node.right = l;
            q.offer(node.left);
            q.offer(node.right);
        }
        return root;
    }
}


234. 回文链表 ^_^

  • 将链表存储到数组中再双指针判断数组是否回文,空间复杂度O(n)
  • 将后半部分的链表反转,再利用双指针确定是否为回文,最后还原链表,空间复杂度O(1)

请判断一个链表是否为回文链表。

示例:
输入: 1->2
输出: false

输入: 1->2->2->1
输出: true

进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null) return true;
        boolean result = true;
        ListNode fast = head, slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        slow = reverseList(slow);
        fast = head;
        ListNode end = slow;
        while (slow != null) {
            if (fast.val != slow.val) {
                result = false;
                break;
            }
            fast = fast.next;
            slow = slow.next;
        }
        reverseList(end);
        return result;
    }
    private ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode nextNode;
        while (head != null) {
            nextNode = head.next;
            head.next = pre;
            pre = head;
            head = nextNode;
        }
        return pre;
    }
}


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

  • 递归,建立一个递归函数返回boolean,若左子树/右子树/当前节点三者中出现两次成功则可认为当前节点为最近公共祖先,只要该节点或其子节点找到一个目标则可返回true
  • 使用父子针迭代,遍历所有节点并建立Map储存每个节点对应的父节点,后利用Set统计p及其所有祖先,而后从q开始不断搜寻公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。  

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 

示例:给定如下二叉树 root = [3,5,1,6,2,0,8,null,null,7,4]


输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

// 递归
class Solution {
    TreeNode ans = null;
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        backtrack(root, p, q);
        return ans;
    }
    private boolean backtrack(TreeNode curr, TreeNode p, TreeNode q) {
        if (curr == null) return false;
        int left_num = backtrack(curr.left, p, q) ? 1 : 0;
        int right_num = backtrack(curr.right, p, q) ? 1 : 0;
        int curr_num = (curr == p || curr == q) ? 1 : 0;
        if (curr_num + left_num + right_num >= 2) 
            ans = curr;
        return (curr_num + left_num + right_num >= 1);
    }
}

// 使用父子针迭代
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) return null;
        Stack<TreeNode> stack = new Stack<>();
        Map<TreeNode, TreeNode> parents = new HashMap<TreeNode, TreeNode>();
        stack.push(root);
        parents.put(root, null);
        while (!stack.empty()) {
            TreeNode node = stack.pop();
            if (node.right != null) {
                parents.put(node.right, node);
                stack.push(node.right);
            }
            if (node.left != null) {
                parents.put(node.left, node);
                stack.push(node.left);
            }
        }
        Set<TreeNode> pParents = new HashSet<TreeNode>();
        while (p != null) {
            pParents.add(p);
            p = parents.get(p);
        }
        while (!pParents.contains(q))
             q = parents.get(q);
        return q;
    }
}


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

  • 两个数组分别记录从左到右和从右到左的累计乘积,output为两个数组一左一右相乘
  • 空间复杂度优化为O(1),仅使用output数组记录从左到右的累计乘积,而后从右到左更新

给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

示例:
输入: [1,2,3,4]
输出: [24,12,8,6]

说明: 不要使用除法,且在 O(n) 时间复杂度内完成此题。

// 已优化空间复杂度
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int len = nums.length;
        int[] output = new int[len];
        output[0] = 1;
        int R = 1;
        for (int i = 1; i < len; i++) {
            output[i] = output[i - 1] * nums[i - 1];
        }
        for (int i = len - 1; i >= 0; i--) {
            output[i] = output[i] * R;
            R = R * nums[i];
        }
        return output;
    }
}


239. 滑动窗口最大值

动态规划,将nums划分为多个长度为k的小节(最后一节可不满k个),left和right两数组分别求每小节从左到右和从右到左的当前最大值,当窗口跨小节时,可在left和right中分别找到这个窗口左半边和右半边的最大值

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。  

返回滑动窗口中的最大值。

示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7  

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int len = nums.length;
        if (len * k == 0) return new int[0];
        int[] left = new int[len];
        int[] right = new int[len];
        int[] output = new int[len - k + 1];
        left[0] = nums[0];
        right[len - 1] = nums[len - 1];
        for (int i = 1; i < len; i++) {
            if (i % k == 0)
                left[i] = nums[i];
            else
                left[i] = Math.max(nums[i], left[i - 1]);
            if ((len - i - 1) % k == 0)
                right[len - i - 1] = nums[len - i - 1];
            else
                right[len - i - 1] = Math.max(right[len - i], nums[len - i - 1]);
        }
        for (int i = 0; i <= len - k; i++) {
            output[i] = Math.max(left[i + k - 1], right[i]);
        }
        return output;
    }
}


240. 搜索二维矩阵 II

  • 从左下角开始搜索,其右边元素都较当前值大,上面元素都较当前值小 ^_^
  • 将矩阵切分为4块,右下角是某块的max元素,左上角是某块的min元素,搜索中间列若找到target直接true,否则找到target位于该列哪两行之间,以该“虚拟”行为分界左上块均小于target右下块均大于target忽略,继续在剩下两块进行搜索,注意row搜索后在第一行或最后一行的情况

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。 
  • 每列的元素从上到下升序排列。

示例: 

现有矩阵 matrix 如下:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

给定 target = 5,返回 true。 

给定 target = 20,返回 false

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int row = matrix.length - 1;
        int col = 0;
        while (row >= 0 && col < matrix[0].length) {
            if (matrix[row][col] == target) 
               return true;
            else if (matrix[row][col] > target)
                row--;
            else
                col++;
        }
        return false;
    }
}

class Solution {
    int[][] matrix;
    int target;
    public boolean searchMatrix(int[][] matrix, int target) {
        this.matrix = matrix;
        this.target = target;
        if (matrix == null || matrix.length == 0) return false;
        return searchRec(0, matrix[0].length - 1, 0, matrix.length - 1);
    }
    private boolean searchRec(int left, int right, int up, int down) {
        if (left > right || up > down)
             return false;
        else if (matrix[up][left] > target || matrix[down][right] < target)
            return false;
        int mid = (left + right) / 2;
        int row = up;
        while (row <= down && target >= matrix[row][mid]) {
            if (target == matrix[row][mid])
                 return true;
            row++;
        }
        return searchRec(left, mid - 1, row, down) || searchRec(mid + 1, right, up, row - 1);
    }
}


279. 完全平方数

  • 动态规划,建立dp[n+1],dp[i]表示到i的最少平方数个数,dp[i]最大为i,而后进行尝试不同平方数组合的尝试,选取最小的一种
  • 广度优先搜索,建立队列储存搜索的节点(节点重新定义,有当前值与层数),由于是层序遍历第一个出现的0一定是最少的数量直接返回,还需建立一个标记数组,遇到出现过的节点值直接忽略(第二次出现的路径长一定不小于第一次出现),遇到新节点值小于0也直接忽略

给定正整数n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于n。你需要让组成和的完全平方数的个数最少。

示例:
输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4.

// 动态规划
class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            dp[i] = i;
            for (int j = 1; i - j * j >= 0; j++) {
                dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
            }
        }
        return dp[n];
    }
}

// 广度优先搜索
class Solution {
    class Node {
        int val, step;
        public Node(int val, int step) {
            this.val = val;
            this.step = step;
        }
    }
    public int numSquares(int n) {
        Queue<Node> q = new LinkedList<Node>();
        q.offer(new Node(n, 1));
        boolean[] record = new boolean[n];
        while (!q.isEmpty()) {
            Node node = q.poll();
            for (int i = 1; ; i++) {
                int Nextval = node.val - i * i;
                if (Nextval < 0) break;
                else if (Nextval == 0) return node.step;
                else {
                    if (!record[Nextval]) {
                        q.offer(new Node(Nextval, node.step + 1));
                        record[Nextval] = true;
                    }
                }
            }
        }
        return -1;
    }
}


283. 移动零

遍历数组,找到所有不为0的数字逐个填入nums的前k项,而后将剩余的项直接全部置0

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:

  1. 必须在原数组上操作,不能拷贝额外的数组。
  2. 尽量减少操作次数。

class Solution {
    public void moveZeroes(int[] nums) {
        int index = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                nums[index++] = nums[i];
            }
        }
        for (int i = index; i < nums.length; i++) {
            nums[i] = 0;
        }
    }
}


287. 寻找重复数

  • 排序,遍历找到重复数,时间复杂度O(nlgn) 空间复杂度O(1)
  • 集合,逐个放入Set,直到遇见重复的,时间复杂度O(n),空间复杂度O(n)
  • 循环检测,根据数组的值与索引的对应关系可建立一个带环链表,某数字存在两个代表有两个数字指向该重复数,即重复数为环的入口,通过双指针法可找到该入口

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例:
输入: [1,3,4,2,2]
输出: 2

// 循环检测
class Solution {
    public int findDuplicate(int[] nums) {
        int slow = nums[0];
        int fast = nums[0];
         do {
            slow = nums[slow];
            fast = nums[nums[fast]];
        } while (fast != slow);
        fast = nums[0];
        while (fast != slow) {
            fast = nums[fast];
            slow = nums[slow];
        }
        return slow;
    }
}


297. 二叉树的序列化与反序列化

  • 利用queue层序遍历,左孩子/右孩子不为null继续放入队列循环,为null则储存null,即输出序列除了root都是一对一对左右孩子的存在,考虑特殊情况root==null
  • String.valueOf(...) / Integer.valueOf(...) 用于int与string间的互转
  • Arrays.asList(data.split(",")) 以“,”为分隔将data转为list,但这个list不可改变,需要以它初始化新建立的正常的List

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。

请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

示例:
你可以将以下二叉树:
    1
   / \
  2   3
     / \
    4   5
序列化为 "[1,2,3,null,null,4,5]"

public class Codec {
    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if (root == null) return "";
        Queue<String> res = new LinkedList<>();
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        res.offer(String.valueOf(root.val));
        while (!q.isEmpty()) {
            TreeNode node = q.poll();
            if (node.left != null) {
                q.offer(node.left);
                res.offer(String.valueOf(node.left.val));
            } else {
                res.offer("null");
            }
            if (node.right != null) {
                q.offer(node.right);
                res.offer(String.valueOf(node.right.val));
            } else {
                res.offer("null");
            }
        }
        StringBuilder sb = new StringBuilder();
        while (!res.isEmpty()) {
            sb.append(res.poll());
            if (!res.isEmpty())
                sb.append(",");
        }
        return sb.toString();
    }
    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if (data == null || data.length() == 0) return null;
        Queue<String> data_list = new LinkedList(Arrays.asList(data.split(",")));
        Queue<TreeNode> q = new LinkedList<>();
        TreeNode root = null;
        while (!data_list.isEmpty()) {
            String node_val = data_list.poll();
            if (root == null) {
                root = new TreeNode(Integer.valueOf(node_val));
                q.offer(root);
                continue;
            }
            TreeNode parent = q.poll();
            if (!node_val.equals("null")) {
                parent.left = new TreeNode(Integer.valueOf(node_val));
                q.offer(parent.left);
            }
            node_val = data_list.poll();
            if (!node_val.equals("null")) {
                parent.right = new TreeNode(Integer.valueOf(node_val));
                q.offer(parent.right);
            }
        }
        return root;
    }
}
// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));


300. 最长上升子序列

  • 动态规划,dp[i]表示以nums[i]为结尾的最长上升子序列的长度,即寻找前面比它小的元素对应的最大子序列长度+1,时间复杂度 O(n^2)
  • 动态规划+二分查找,建立一个tails数组,tails[i]表示长度为i+1的子序列的尾部元素值,当遇到一个新num时,二分查找其在tails里的位置,要么它大于tails里所有元素则加在最后tails变长,否则tails里一定存在大于它的元素则代替这个元素,因为tails中元素值越小越容易在后续操作中加长,时间复杂度 O(NlogN)

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

// 动态规划
class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        int res = 1;
        int[] dp = new int[nums.length];
        Arrays.fill(dp, 1);
        for (int i = 1; i < nums.length; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j])
                    dp[i] = Math.max(dp[j] + 1, dp[i]);
            }
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

// 动态规划+二分查找
class Solution {
    public int lengthOfLIS(int[] nums) {
        int res = 0;
        int[] tails = new int[nums.length];
        for (int num : nums) {
            int i = 0, j = res;
            while (i < j) {
                int m = (i + j) / 2;
                if (tails[m] < num) i = m + 1;
                else j = m;
            }
            tails[i] = num;
            if (res == j) res++;
        }
        return res;
    }
}