LeetCode刷题记录(中等题)

LeetCode刷题记录(中等题)

剑指 Offer 32 - I. 从上到下打印二叉树(4.20)

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

例如: 给定二叉树: [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回:

[3,9,20,15,7]

提示:

  1. 节点总数 <= 1000

1ms(BFS广度搜索,用队列)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] levelOrder(TreeNode root) {
        if(root == null) return new int[0];
        List<Integer> ans = new ArrayList();//存储答案
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            ans.add(node.val);
            if(node.left!=null) queue.add(node.left);
            if(node.right!=null) queue.add(node.right);
        }
        int[] res = new int[ans.size()];
        for(int i = 0;i<ans.size();i++){
            res[i] = ans.get(i);
        }
        return res;
    }
}

剑指 Offer 33. 二叉搜索树的后序遍历序列(4.20)

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

参考以下这颗二叉搜索树:

    5
    / \
   2   6
  / \
 1   3

示例 1:

输入: [1,6,3,2,5]
输出: false

示例 2:

输入: [1,3,2,6,5]
输出: true

提示:

  1. 数组长度 <= 1000

0ms

class Solution {
    // 要点:二叉搜索树中根节点的值大于左子树中的任何一个节点的值,小于右子树中任何一个节点的值,子树也是
    public boolean verifyPostorder(int[] postorder) {
        if (postorder.length < 2) return true;
        return verify(postorder, 0, postorder.length - 1); 
    }

    // 递归实现
    private boolean verify(int[] postorder, int left, int right){
        if (left >= right) return true; // 当前区域不合法的时候直接返回true就好

        int rootValue = postorder[right]; // 当前树的根节点的值

        int k = left;
        while (k < right && postorder[k] < rootValue){ // 从当前区域找到第一个大于根节点的,说明后续区域数值都在右子树中
            k++;
        }

        for (int i = k; i < right; i++){ // 进行判断后续的区域是否所有的值都是大于当前的根节点,如果出现小于的值就直接返回false
            if (postorder[i] < rootValue) return false;
        }

        // 当前树没问题就检查左右子树
        if (!verify(postorder, left, k - 1)) return false; // 检查左子树

        if (!verify(postorder, k, right - 1)) return false; // 检查右子树

        return true; // 最终都没问题就返回true
    }
}

剑指 Offer 64. 求1+2+…+n(4.21)

1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

示例 1:

输入: n = 3
输出: 6

示例 2:

输入: n = 9
输出: 45

限制:

  • 1 <= n <= 10000

1ms

class Solution {
    public int sumNums(int n) {
        try{
            int i=1%n;
        }catch (Exception e){
            return 0;
        }
        return n+sumNums(n-1);
    }
}

0ms

class Solution {
    public int sumNums(int n) {
       int sum = n;
        boolean flag = n > 0 && (sum += sumNums(n - 1)) > 0;
        return sum;
    }
}

剑指 Offer 56 - II. 数组中数字出现的次数 II(4.21)

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

示例 1:

输入:nums = [3,4,3,3]
输出:4

示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1

限制:

  • 1 <= nums.length <= 10000
  • 1 <= nums[i] < 2^31

13ms

class Solution {
    public int singleNumber(int[] nums) {
        Map<Integer,Integer> map=new HashMap<>();
        for (int num : nums) {
            if(map.containsKey(num)){
                map.replace(num,map.get(num)+1);
            }
            else {
                map.put(num,1);
            }
        }
        for (Integer integer : map.keySet()) {
            if(map.get(integer)==1){
                return integer;
            }
        }
        return 0;
    }
}

48. 旋转图像(4.22)

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]

示例 2:

输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]

提示:

  • n == matrix.length == matrix[i].length
  • 1 <= n <= 20
  • -1000 <= matrix[i][j] <= 1000

0ms

class Solution {
    //以四个顶点为关注点, 一个点的坐标是matrix[x][y],顺时针旋转就是:左上角y+1、右上角x+1,右下角y-1,左下角x-1。从外圈一圈一圈往里遍历交换。
    public void rotate(int[][] matrix) {
        int n = matrix.length - 1;
        for (int i = 0; i <= matrix.length / 2; i++) {
            for (int j = i; j < n - i; j++) {
                // 获取各顶点的值
                int a = matrix[i][j]; // 左上角
                int b = matrix[j][n-i]; // 右上角
                int c = matrix[n-i][n-j]; // 右下角
                int d = matrix[n-j][i]; // 左下角
                // 交换各顶点的值
                matrix[i][j] = d; // 左上角
                matrix[j][n-i] = a; // 右上角
                matrix[n-i][n-j] = b; // 右下角
                matrix[n-j][i] = c; // 左下角
            }
        }
    }
}

剑指 Offer 35. 复杂链表的复制(4.22)

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:

  • -10000 <= Node.val <= 10000
  • Node.random 为空(null)或指向链表中的节点。
  • 节点数目不超过 1000 。

0ms(哈希表)

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return head;
        }
        //map中存的是(原节点,拷贝节点)的一个映射
        Map<Node, Node> map = new HashMap<>();
        for (Node cur = head; cur != null; cur = cur.next) {
            map.put(cur, new Node(cur.val));
        }
        //将拷贝的新的节点组织成一个链表
        for (Node cur = head; cur != null; cur = cur.next) {
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
        }

        return map.get(head);        
    }
}

0ms(原地复制)

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return head;
        }
        //将拷贝节点放到原节点后面,例如1->2->3这样的链表就变成了这样1->1'->2->2'->3->3'
        for (Node node = head, copy = null; node != null; node = node.next.next) {
            copy = new Node(node.val);
            copy.next = node.next;
            node.next = copy;
        }
        //把拷贝节点的random指针安排上
        for (Node node = head; node != null; node = node.next.next) {
            if (node.random != null) {
                node.next.random = node.random.next; //node.next 是 node 复制出来的节点 ,node.random.next 也是 node.random 复制出来的节点
            }
        }
        //分离拷贝节点和原节点,变成1->2->3和1'->2'->3'两个链表,后者就是答案
        Node newHead = head.next;
        for (Node node = head, temp = null; node != null && node.next != null;) {
            // 要修改原来节点和copy节点各自的next
            temp = node.next;
            node.next = temp.next; 
            node = temp;
        }

        return newHead;  
    }
}

剑指 Offer 07. 重建二叉树(4.23)

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

示例 1:

Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]

示例 2:

Input: preorder = [-1], inorder = [-1]
Output: [-1]

限制:

0 <= 节点个数 <= 5000

2ms

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
        /**
     * 思路:通过先序遍历数组确定根节点,通过中序遍历确定左右孩子节点
     * 1.使用HashMap存储中序遍历的节点和其索引
     * 2.第一个节点就是根节点,通过map找到其在中序数组的位置,该节点左边的数是左子树,右边的数是右子树
     * 3.递归寻找左子树的根节点、右子树的根节点
     * @param preorder 先序遍历结果 array
     * @param inorder  中序遍历结果 array
     * @return 一棵二叉树
     */
    HashMap<Integer, Integer> hm = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder == null || inorder == null || preorder.length < 1 || inorder.length < 1){
            return null;
        }
        for(int i = 0; i < inorder.length; i++){
            hm.put(inorder[i], i);
        }

        return build(preorder, 0, 0, preorder.length - 1);
    }

    /**
     * 
     * @param preorder 先序遍历数组
     * @param index 根节点索引(先序遍历数组)
     * @param l 子树的左边界 (中序遍历数组)
     * @param r 子树的右边界(中序遍历数组)
     * @return 二叉树节点
     */
    public TreeNode build(int[] preorder, int index, int l, int r){
        if(l > r){
            return null;
        }
        TreeNode node = new TreeNode(preorder[index]);
        Integer next = hm.get(preorder[index]);
        node.left = build(preorder, index + 1, l, next - 1);
        // 右子树的根节点可能不好理解 但其实就是把根节点的左子树的节点全部减去,就是右子树的根节点
        node.right = build(preorder, index + (next - l + 1), next + 1, r);

        return node;
    }
}

剑指 Offer II 004. 只出现一次的数字 (4.23)

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。

示例 1:

输入:nums = [2,2,3,2]
输出:3

示例 2:

输入:nums = [0,1,0,1,0,1,100]
输出:100

提示:

  • 1 <= nums.length <= 3 * 10^4
  • -2^31 <= nums[i] <= 2^31 - 1
  • nums 中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次

5ms

class Solution {
    public int singleNumber(int[] nums) {
        Map<Integer, Integer> res = new HashMap<Integer, Integer>();
        for (int num : nums) {
            freq.put(num, res.getOrDefault(num, 0) + 1);
        }
        int ans = 0;
        for (Map.Entry<Integer, Integer> entry : res.entrySet()) {
            int num = entry.getKey(), occ = entry.getValue();
            if (occ == 1) {
                ans = num;
                break;
            }
        }
        return ans;
    }
}

剑指 Offer II 055. 二叉搜索树迭代器(4.24)

实现一个二叉搜索树迭代器类BSTIterator ,表示一个按中序遍历二叉搜索树(BST)的迭代器:

  • BSTIterator(TreeNode root) 初始化 BSTIterator 类的一个对象。BST 的根节点 root 会作为构造函数的一部分给出。指针应初始化为一个不存在于 BST 中的数字,且该数字小于 BST 中的任何元素。

  • boolean hasNext() 如果向指针右侧遍历存在数字,则返回 true ;否则返回 false 。

  • int next()将指针向右移动,然后返回指针处的数字。

注意,指针初始化为一个不存在于 BST 中的数字,所以对 next() 的首次调用将返回 BST 中的最小元素。

可以假设 next() 调用总是有效的,也就是说,当调用 next() 时,BST 的中序遍历中至少存在一个下一个数字。

示例:

输入
inputs = ["BSTIterator", "next", "next", "hasNext", "next", "hasNext", "next", "hasNext", "next", "hasNext"]
inputs = [[[7, 3, 15, null, null, 9, 20]], [], [], [], [], [], [], [], [], []]
输出
[null, 3, 7, true, 9, true, 15, true, 20, false]

解释
BSTIterator bSTIterator = new BSTIterator([7, 3, 15, null, null, 9, 20]);
bSTIterator.next();    // 返回 3
bSTIterator.next();    // 返回 7
bSTIterator.hasNext(); // 返回 True
bSTIterator.next();    // 返回 9
bSTIterator.hasNext(); // 返回 True
bSTIterator.next();    // 返回 15
bSTIterator.hasNext(); // 返回 True
bSTIterator.next();    // 返回 20
bSTIterator.hasNext(); // 返回 False

提示:

  • 树中节点的数目在范围 [1, 105]
  • 0 <= Node.val <= 106
  • 最多调用 105hasNextnext 操作

15ms

/**
 * 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 BSTIterator {
    List<Integer> list;
    int idx, n;

    public BSTIterator(TreeNode root) {
        list = new ArrayList<>();
        LNR(root);
        this.n = list.size();
        idx = 0;
    }
    
    public int next() {
        return list.get(idx++);
    }
    
    public boolean hasNext() {
        if(idx<n) return true;
        return false;
    }

    public void LNR(TreeNode node){
        if(node == null) return ;
        LNR(node.left);
        list.add(node.val);
        LNR(node.right);
    }
}

/**
 * Your BSTIterator object will be instantiated and called as such:
 * BSTIterator obj = new BSTIterator(root);
 * int param_1 = obj.next();
 * boolean param_2 = obj.hasNext();
 */

*剑指 Offer II 079. 所有子集(4.24)

给定一个整数数组 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 中的所有元素 互不相同

0ms

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> res = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        dfs(nums,0);
        return result;
    }
    public void dfs(int[] nums,int start) {
        result.add(new ArrayList<>(res));
        for (int i=start;i<nums.length;++i) {
            res.add(nums[i]);
            dfs(nums,i+1);
            res.remove(res.size()-1);
        }
    }
}

剑指 Offer II 092. 翻转字符(4.25)

如果一个由 '0' 和 '1' 组成的字符串,是以一些 '0'(可能没有 '0')后面跟着一些 '1'(也可能没有 '1')的形式组成的,那么该字符串是 单调递增 的。

我们给出一个由字符 '0' 和 '1' 组成的字符串 s,我们可以将任何 '0' 翻转为 '1' 或者将 '1' 翻转为 '0'。

返回使 s 单调递增 的最小翻转次数。

示例 1:

输入:s = "00110"
输出:1
解释:我们翻转最后一位得到 00111.

示例 2:

输入:s = "010110"
输出:2
解释:我们翻转得到 011111,或者是 000111。

示例 3:

输入:s = "00011000"
输出:2
解释:我们翻转得到 00000000。

提示:

  • 1 <= s.length <= 20000
  • s 中只包含字符 '0''1'

2ms(DP)

class Solution {
    public int minFlipsMonoIncr(String s) {
        int zero =0, one = 0;
        for(char ch:s.toCharArray()){
            if(ch=='1') one++;
            else zero = Math.min(zero+1,one);
        }
        return zero;        
    }
}

剑指 Offer 36. 二叉搜索树与双向链表(4.26)

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

为了让您更好地理解问题,以下面的二叉搜索树为例:

我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。

特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

0ms

/*
// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val,Node _left,Node _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
    // 1. 中序,递归,来自解题大佬
    Node pre, head;
    public Node treeToDoublyList(Node root) {
        // 边界值
        if(root == null) return null;
        dfs(root);

        // 题目要求头尾连接
        head.left = pre;
        pre.right = head;
        // 返回头节点
        return head;
    }
    void dfs(Node cur) {
        // 递归结束条件
        if(cur == null) return;
        dfs(cur.left);
        // 如果pre为空,就说明是第一个节点,头结点,然后用head保存头结点,用于之后的返回
        if (pre == null) head = cur;
        // 如果不为空,那就说明是中间的节点。并且pre保存的是上一个节点,
        // 让上一个节点的右指针指向当前节点
        else if (pre != null) pre.right = cur;
        // 再让当前节点的左指针指向父节点,也就连成了双向链表
        cur.left = pre;
        // 保存当前节点,用于下层递归创建
        pre = cur;
        dfs(cur.right);
    }
}

剑指 Offer II 070. 排序数组中只出现一次的数字(4.27)

给定一个只包含整数的有序数组 nums ,每个元素都会出现两次,唯有一个数只会出现一次,请找出这个唯一的数字。

示例 1:

输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2

示例 2:

输入: nums =  [3,3,7,7,10,11,11]
输出: 10

提示:

  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] <= 10^5

0ms

class Solution {
    public int singleNonDuplicate(int[] nums) {
        if(nums.length ==1){
            return nums[0];
        }
        int l =0,r = nums.length-1;
        while (l <= r){
            if (nums[l+1] == nums[l]){
                l = l+2;
            }else {
                return nums[l];
            }
            if (nums[r] == nums[r-1]){
                r=r -2;
            }else {
                return nums[r];
            }
        }
        return -1;
    }
}

面试题 16.01. 交换数字(4.28)

编写一个函数,不用临时变量,直接交换numbers = [a, b]ab的值。

示例:

输入: numbers = [1,2]
输出: [2,1]

提示:

  • numbers.length == 2
  • -2147483647 <= numbers[i] <= 2147483647

0ms

a=a^b;            
b=a^b;         // b= (a^b) ^ b   也就是    b= a^ (b ^ b  )  即 b=a ^ 0 , 即 b=a;  
a=a^b;        // 这个自己推, 注意 a 和b 都已经被修改过了.

 假如给定 [1,3],也就是[01,11]  异或规则是同值为0,异值为1
 numbers[0] = numbers[0]^numbers[1];  //按位异或运算赋值之后[10,11]
 numbers[1] = numbers[0]^numbers[1];  //[10,01]
 numbers[0] = numbers[0]^numbers[1];  //[11,01]也就是[3,1]
class Solution {
    public int[] swapNumbers(int[] numbers) {
        numbers[0] ^= numbers[1];
        numbers[1] ^= numbers[0];
        numbers[0] ^= numbers[1];
        return numbers;        
    }
}

1325. 删除给定值的叶子节点(4.29)

给你一棵以 root 为根的二叉树和一个整数 target ,请你删除所有值为 target 的 叶子节点 。

注意,一旦删除值为 target 的叶子节点,它的父节点就可能变成叶子节点;如果新叶子节点的值恰好也是 target ,那么这个节点也应该被删除。

也就是说,你需要重复此过程直到不能继续删除。

示例 1:

输入:root = [1,2,3,2,null,2,4], target = 2
输出:[1,null,3,null,4]
解释:
上面左边的图中,绿色节点为叶子节点,且它们的值与 target 相同(同为 2 ),它们会被删除,得到中间的图。
有一个新的节点变成了叶子节点且它的值与 target 相同,所以将再次进行删除,从而得到最右边的图。

示例 2:

输入:root = [1,3,3,3,2], target = 3
输出:[1,3,null,null,2]

示例 3:

输入:root = [1,2,null,2,null,2], target = 2
输出:[1]
解释:每一步都删除一个绿色的叶子节点(值为 2)。

示例 4:

输入:root = [1,1,1], target = 1
输出:[]

示例 5:

输入:root = [1,2,3], target = 1
输出:[1,2,3]

提示:

  • 1 <= target <= 1000
  • 每一棵树最多有 3000 个节点。
  • 每一个节点值的范围是 [1, 1000]

0ms

后序遍历,因为需要根据子节点的结果,判断父节点是否需要删除

/**
 * 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 removeLeafNodes(TreeNode root, int target) {
        if (root == null) return null;
        
        root.left = removeLeafNodes(root.left, target);
        root.right = removeLeafNodes(root.right, target);
        
        if (root.val == target && root.left == null && root.right == null) return null;
        return root;       
    }
}

109. 有序链表转换二叉搜索树(4.30)

给定一个单链表的头节点 head ,其中的元素 按升序排序 ,将其转换为高度平衡的二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差不超过 1。

示例 1:

输入: head = [-10,-3,0,5,9]
输出: [0,-3,9,-10,null,5]
解释: 一个可能的答案是[0,-3,9,-10,null,5],它表示所示的高度平衡的二叉搜索树。

示例 2:

输入: head = []
输出: []

0ms

递归 - 快慢指针找中点

class Solution {
    public TreeNode sortedListToBST(ListNode head) {
        if(head == null){
            return null;
        }
        return helper(head,null);
    }
    private TreeNode helper(ListNode head,ListNode tail){
        if(head == tail){
            return null;
        }
        //找出当前链表中间位置(偶数个节点则取中间靠后那个节点), slow节点最终的位置即为中间节点
        ListNode fast = head;
        ListNode slow = head;
        while(fast != tail && fast.next != tail){
            fast = fast.next.next;
            slow = slow.next;
        }
        TreeNode root = new TreeNode(slow.val);
        root.left = helper(head,slow);
        root.right = helper(slow.next,tail);
        return root;
    }
}
/**
 * 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; }
 * }
 */
/**
 * 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 sortedListToBST(ListNode head) {
        if(head == null) return null;
        else if(head.next == null) return new TreeNode(head.val);
        ListNode pre = head;
        ListNode p = pre.next;
        ListNode q = p.next;
        //找到链表的中点p
        while(q!=null && q.next!=null){
            pre = pre.next;
            p = pre.next;
            q = q.next.next;
        }
        //将中点左边的链表分开
        pre.next = null;
        TreeNode root = new TreeNode(p.val);
        root.left = sortedListToBST(head);
        root.right = sortedListToBST(p.next);
        return root;
    }
}

92. 反转链表 II(5.1)

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

示例 1:

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]

示例 2:

输入:head = [5], left = 1, right = 1
输出:[5]

提示:

  • 链表中节点数目为 n
  • 1 <= n <= 500
  • -500 <= Node.val <= 500
  • 1 <= left <= right <= n

0ms

实现思路 :以1->2->3->4->5, m = 2, n=4 为例:

  • 定位到要反转部分的头节点 2,head = 2;前驱结点 1,pre = 1;
  • 当前节点的下一个节点3调整为前驱节点的下一个节点 1->3->2->4->5,
  • 当前结点仍为2, 前驱结点依然是1,重复上一步操作。。。
  • 1->4->3->2->5.

思路其实就是:我们需要反转n-m次,每一次我们将head的next节点移动到需要反转链表部分的首部,需要反转链表部分剩余节点依旧保持相对顺序即可

/**
 * 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 ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pre = dummy;
        for(int i = 1; i < left; i++){
            pre = pre.next;
        }
        head = pre.next;
        for(int i = left; i < right; i++){
            ListNode nex = head.next;
            head.next = nex.next;
            nex.next = pre.next;
            pre.next = nex;
        }
        return dummy.next;
    }
}

剑指 Offer II 038. 每日温度(5.2)

请根据每日 气温 列表 temperatures ,重新生成一个列表,要求其对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 1:

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

示例 2:

输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]

示例 3:

输入: temperatures = [30,60,90]
输出: [1,1,0]

提示:

  • 1 <= temperatures.length <= 105
  • 30 <= temperatures[i] <= 100

7ms

从后向前

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int[] arr = new int [temperatures.length];
        for(int i = temperatures.length-1;i >= 0;i--){
            int j = i + 1;
            while(j < temperatures.length){
                if(temperatures[j] > temperatures[i]){
                    arr[i] = j - i; // 计算几天后气温更高
                    break;
                }else if(arr[j] == 0){ // 最后一天必为0,因为没有明天了
                    break;
                }else{ 
                    j += arr[j]; // 如果此时天气温度没有大于前一天的,那么就找比此时温度高的那一天的下标偏移量arr[j],加到j上,就找出来比今天温度更高的那一天,进入下一次while循环,拿它来比较...
                }
            }
        } 
        return arr;
    }
}

148. 排序链表(5.3)

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

示例 1:

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目在范围 [0, 5 * 10^4]
  • -10^5 <= Node.val <= 10^5

7ms

/**
 * 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 {
    /**
     * 参考:Sort List——经典(链表中的归并排序) https://www.cnblogs.com/qiaozhoulin/p/4585401.html
     * 
     * 归并排序法:在动手之前一直觉得空间复杂度为常量不太可能,因为原来使用归并时,都是 O(N)的,
     * 需要复制出相等的空间来进行赋值归并。对于链表,实际上是可以实现常数空间占用的(链表的归并
     * 排序不需要额外的空间)。利用归并的思想,递归地将当前链表分为两段,然后merge,分两段的方
     * 法是使用 fast-slow 法,用两个指针,一个每次走两步,一个走一步,知道快的走到了末尾,然后
     * 慢的所在位置就是中间位置,这样就分成了两段。merge时,把两段头部节点值比较,用一个 p 指向
     * 较小的,且记录第一个节点,然后 两段的头一步一步向后走,p也一直向后走,总是指向较小节点,
     * 直至其中一个头为NULL,处理剩下的元素。最后返回记录的头即可。
     * 
     * 主要考察3个知识点,
     * 知识点1:归并排序的整体思想
     * 知识点2:找到一个链表的中间节点的方法
     * 知识点3:合并两个已排好序的链表为一个新的有序链表
     */
    public ListNode sortList(ListNode head) {
        return head == null ? null : mergeSort(head);
    }

    private ListNode mergeSort(ListNode head) {
        if (head.next == null) {
            return head;
        }
        ListNode p = head, q = head, pre = null;
        while (q != null && q.next != null) {
            pre = p;
            p = p.next;
            q = q.next.next;
        }
        pre.next = null;
        ListNode l = mergeSort(head);
        ListNode r = mergeSort(p);
        return merge(l, r);
    }

    ListNode merge(ListNode l, ListNode r) {
        ListNode dummyHead = new ListNode(0);
        ListNode cur = dummyHead;
        while (l != null && r != null) {
            if (l.val <= r.val) {
                cur.next = l;
                cur = cur.next;
                l = l.next;
            } else {
                cur.next = r;
                cur = cur.next;
                r = r.next;
            }
        }
        if (l != null) {
            cur.next = l;
        }
        if (r != null) {
            cur.next = r;
        }
        return dummyHead.next;
    }
}

面试题 02.04. 分割链表(5.4)

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你不需要 保留 每个分区中各节点的初始相对位置。

示例 1:

输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]

示例 2:

输入:head = [2,1], x = 2
输出:[1,2]

提示:

  • 链表中节点的数目在范围 [0, 200]
  • -100 <= Node.val <= 100
  • -200 <= x <= 200

题目要求是只要把小于x的数移动到所有x的左方就行,没有顺序要求,而且等于x,大于x这些元素没有要求!

所以运用双指针,第一个指针落在不小于x的节点上,第二个向前移动。如果第二个指针遇到小于x的节点,交换二者的值,然后第一个指针指向next。第二个指针肯定走得更快,所以第一个指针一直处于不小于x的节点的位置,或者就是两个指针重叠,这时候还是礼貌性地交换一下罢了。

0ms

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
 // [1, 4, 3, 2, 5, 2]. prev = 1, cur = 1
 // [1, 4, 3, 2, 5, 2]. prev = 1, cur = 1 小于,交换二者,没有变化
 // [1, 4, 3, 2, 5, 2]. prev = 4, cur = 4 继续往后next
 // [1, 4, 3, 2, 5, 2]. prev = 4, cur = 3 无序交换, 只修改cur指针
 // [1, 4, 3, 2, 5, 2]. prev = 4, cur = 2 同上
 // [1, 2, 3, 4, 5, 2]. prev = 2, cur = 4 2 < 3,交换二者
 // [1, 2, 3, 4, 5, 2]. prev = 3, cur = 5 二者同时往后next
 // [1, 2, 3, 4, 5, 2]. prev = 3, cur = 2
 // [1, 2, 2, 4, 5, 3] over
class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode cur = head, prev = head;
        while (cur != null) {
            if (cur.val < x) {
                int tmp = prev.val;
                prev.val = cur.val;
                cur.val = tmp;
                prev = prev.next;
            }
            cur = cur.next;
        }
        return head;
        
    }
}

剑指 Offer II 045. 二叉树最底层最左边的值(5.5)

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

假设二叉树中至少有一个节点。

示例 1:

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

示例 2:

输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7

提示:

  • 二叉树的节点个数的范围是 [1,10^4]
  • -2^31 <= Node.val <= 2^31 - 1

1ms

保存每层第一个值就行了

/**
 * 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 findBottomLeftValue(TreeNode root) {
        Deque<TreeNode> deque = new LinkedList<>();
        deque.offer(root);
        int res = 0;
        while (!deque.isEmpty()) {
            int size = deque.size();
            for (int i = 0; i < size; ++i) {
                TreeNode node = deque.poll();
                if (i == 0) {
                    res = node.val;
                }
                if (node.left != null) {
                    deque.offer(node.left);
                }
                if (node.right != null) {
                    deque.offer(node.right);
                }
            }
        }
        return res;        
    }
}

222. 完全二叉树的节点个数(5.6)

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。

示例 1:

输入:root = [1,2,3,4,5,6]
输出:6

示例 2:

输入:root = []
输出:0

示例 3:

输入:root = [1]
输出:1

提示:

  • 树中节点的数目范围是[0, 5 * 10^4]
  • 0 <= Node.val <= 5 * 10^4
  • 题目数据保证输入的树是 完全二叉树

0ms

/**
 * 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 countNodes(TreeNode root) {
        /**
        完全二叉树的高度可以直接通过不断地访问左子树就可以获取
        判断左右子树的高度: 
        如果相等说明左子树是满二叉树, 然后进一步判断右子树的节点数(最后一层最后出现的节点必然在右子树中)
        如果不等说明右子树是深度小于左子树的满二叉树, 然后进一步判断左子树的节点数(最后一层最后出现的节点必然在左子树中)
        **/
        if (root==null) return 0;
        int ld = getDepth(root.left);
        int rd = getDepth(root.right);
        // (1 << ld)说明 
        // 1<<30为例,首先把1转为二进制数字 0000 0000 0000 0000 0000 0000 0000 0001
		// 然后将上面的二进制数字向左移动30位后面补0得到 0010 0000 0000 0000 0000 0000 0000 0000
        // (1 << ld)相当于 1 * 2^ld 次方, 满二叉树的节点个数计算公式为2^n-1(n为树的高度),即(1 << n) - 1
        if(ld == rd) return (1 << ld) + countNodes(root.right); // 1(根节点) + (1 << ld)-1(左完全左子树节点数) + 右子树节点数量
        else return (1 << rd) + countNodes(root.left);  // 1(根节点) + (1 << rd)-1(右完全右子树节点数) + 左子树节点数量
        
    }

    private int getDepth(TreeNode r) {
        int depth = 0;
        while(r != null) {
            depth++;
            r = r.left;
        }
        return depth;
    }
}

剑指 Offer II 085. 生成匹配的括号(5.7)

正整数 n 代表生成括号的对数,请设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]

提示:

  • 1 <= n <= 8

1ms

class Solution {
    List<String> result = new ArrayList<>();
    public List<String> generateParenthesis(int n) {
        String str = "";
        dfs(n,n,str);
        return result;
    }

    public void dfs(int left,int right,String s) {
        if (right == 0 && left == 0) {
            result.add(new String(s));
            return;
        }
        // 保证不能超过括号对的数量,同时右括号的使用次数不能超过左括号的次数
        if (left < 0 || right < 0 || right < left) {
            return;
        }
        dfs(left-1,right,s+"(");
        dfs(left,right-1,s+")");
    }
}

260. 只出现一次的数字 III(5.7)

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?

示例 1:

输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案。

示例 2:

输入:nums = [-1,0]
输出:[-1,0]

示例 3:

输入:nums = [0,1]
输出:[1,0]

提示:

  • 2 <= nums.length <= 3 * 10^4
  • -2^31 <= nums[i] <= 2^31 - 1
  • 除两个只出现一次的整数外,nums 中的其他数字都出现两次

6ms (HashMap)

class Solution {
    public int[] singleNumber(int[] nums) {
        Map<Integer, Integer> freq = new HashMap<Integer, Integer>();
        for (int num : nums) {
            freq.put(num, freq.getOrDefault(num, 0) + 1);
        }
        int[] ans = new int[2];
        int index = 0;
        for (Map.Entry<Integer, Integer> entry : freq.entrySet()) {
            if (entry.getValue() == 1) {
                ans[index++] = entry.getKey();
            }
        }
        return ans;
    }
}

剑指 Offer II 060. 出现频率最高的 k 个数字(5.8)

给定一个整数数组 nums 和一个整数 k ,请返回其中出现频率前 k 高的元素。可以按 任意顺序 返回答案。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

提示:

  • 1 <= nums.length <= 10^5
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

14ms

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        int[] ans=new int[k];
        Map<Integer,Integer> map=new HashMap<>();
        for(int num:nums){
            map.put(num,map.getOrDefault(num,0)+1);
        }
        List<Integer> list=new ArrayList<Integer>(map.keySet());
        Collections.sort(list,(a,b)->map.get(b)-map.get(a)); // 倒序排序
        for(int i=0;i<k;i++){
            ans[i]=list.get(i);
        }
        return ans;
    }
}


class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        int res[] = new int[k];

        for (int num: nums){
            map.put(num,map.getOrDefault(num,0)+1);
        }
        ArrayList<Map.Entry<Integer, Integer>> list = new ArrayList<>(map.entrySet());

        Collections.sort(list,(a,b)->b.getValue() - a.getValue()); // 倒序排序

        for (int i =0 ; i < k ; i++){
            res[i] = list.get(0).getKey();
            list.remove(0);
        }
    return res;
    }
}

剑指 Offer 56 - I. 数组中数字出现的次数(5.9)

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]

示例 2:

输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]

限制:

  • 2 <= nums.length <= 10000

nums = [1,2,10,4,1,4,3,3]

  • a^a=0
  • a^0=a
  • a^b^c=a^c^b
  • a&(-a)=最低位为1的二进制(从右到左)
  • 所有的异或结果得到sum=2^10=8
  • flag=-8&8=8
  • 可分为两组,一组为与flag相与等于1的[10],另一组为0的[1,2,4,1,4,3,3]
  • 组内异或分别得到【10】【2】

1ms

class Solution {
    public int[] singleNumbers(int[] nums) {
        int sum=0;
        //得到异或结果,即为不相同两个数的异或结果sum
        for(int num:nums)
            sum^=num;
        //得到sum的二进制的1的最低位
        int flag=(-sum)&sum;
        int result[]=new int[2];
        //分成两个组进行异或,每组异或后的结果就是不相同两个数的其中之一
        for(int num:nums){
            if((flag&num)==flag)
                result[0]^=num;
            else{
                result[1]^=num;
            }
        }
        return result;
    }
}

剑指 Offer 31. 栈的压入、弹出序列(5.10)

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。

示例 1:

输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

示例 2:

输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。

提示:

  • 0 <= pushed.length == popped.length <= 1000

  • 0 <= pushed[i], popped[i] < 1000

  • pushed 是 popped 的排列。

判断合不合法,用个栈试一试: 把压栈的元素按顺序压入,当栈顶元素和出栈的第一个元素相同,则将该元素弹出,出栈列表指针后移并继续判断。最后判断出栈列表指针是否指向出栈列表的末尾即可。

1ms

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Deque<Integer> stack = new ArrayDeque<>();
        int j = 0;
        for (int elem : pushed){
            stack.push(elem);
            while(j < popped.length && !stack.isEmpty() && stack.peek() == popped[j]){
                stack.pop();
                j++;
            }
        }
        return j == popped.length;
    }
}
end
  • 作者:AWhiteElephant(联系作者)
  • 发表时间:2022-05-10 11:19
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 评论