【leetcode】剑指Offer

文章目录

数组中的重复的数字

找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3

原地置换

class Solution {
    public int findRepeatNumber(int[] nums) {
        int len = nums.length;
        for (int i = 0; i < len; i++) {
            if (nums[i] != i) {
                if (nums[nums[i]] == nums[i])
                    return nums[i];
                int tmp = nums[nums[i]];
                nums[nums[i]] = nums[i];
                nums[i] = tmp;
            }
        }
        return -1;
    }
}

二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

示例:
现有矩阵 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 findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix.length == 0)
            return false;
        int rows = matrix.length, cols = matrix[0].length;
        int i = rows - 1, j = 0;
        while (i >= 0 && j < cols) {
            if (matrix[i][j] == target)
                return true;
            else if (matrix[i][j] > target)
                i--;
            else
                j++;
        }
        return false;
    }
}

替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1:
输入:s = “We are happy.”
输出:“We%20are%20happy.”

遍历替换

class Solution {
    public String replaceSpace(String s) {
        StringBuilder sb = new StringBuilder();
        int len = s.length();
        for (int i = 0; i < len; i++) {
            if (s.charAt(i) == ' ')
                sb.append("%20");
            else
                sb.append(s.charAt(i));
        }
        return sb.toString();
    }
}

从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

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

迭代

class Solution {
    public int[] reversePrint(ListNode head) {
        ListNode cur = head;
        int len = 0;
        while (cur != null) {
            cur = cur.next;
            len++;
        }

        int[] result = new int[len];
        int i = len - 1;
        while (head != null) {
            result[i--] = head.val;
            head = head.next;
        }
        return result;
    }
}

重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
在这里插入图片描述

递归

class Solution {
    private int[] preorder;
    private int[] inorder;
    private int len;
    private Map<Integer, Integer> map;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        this.inorder = inorder;
        len = preorder.length;
        map = new HashMap<>();
        for (int i = 0; i < len; i++) 
            map.put(inorder[i], i);
        return buildTree(0, len - 1, 0, len - 1);
    }

    private TreeNode buildTree(int preLeft, int preRight, int inLeft, int inRight) {
        if (preLeft > preRight)
            return null;
        
        TreeNode root = new TreeNode(preorder[preLeft]);
        int inRoot = map.get(preorder[preLeft]);
        int leftNum = inRoot - inLeft;

        root.left = buildTree(preLeft + 1, preLeft + leftNum, inLeft, inRoot - 1);
        root.right = buildTree(preLeft + leftNum + 1, preRight, inRoot + 1, inRight);
        return root;
    }
}

用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

示例 1:
输入:
[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[3],[],[]]
输出:[null,null,3,-1]

示例 2:
输入:
[“CQueue”,“deleteHead”,“appendTail”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]

入栈和出栈

class CQueue {
    private Deque<Integer> inStack;
    private Deque<Integer> outStack;

    public CQueue() {
        inStack = new LinkedList<>();
        outStack = new LinkedList<>();
    }
    
    public void appendTail(int value) {
        inStack.push(value);
    }
    
    public int deleteHead() {
        if (outStack.isEmpty()) {
            if (inStack.isEmpty())
                return -1;
            while (!inStack.isEmpty())
                outStack.push(inStack.pop());
        }
        return outStack.pop();
    }
}

斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:
输入:n = 2
输出:1

示例 2:
输入:n = 5
输出:5

迭代

递归法会有许多重复计算。

class Solution {
    public int fib(int n) {
        if (n == 0)
            return 0;
        int[] dp = new int[n + 1];
        dp[0] = 0; dp[1] = 1;
        for (int i = 2; i <= n; i++) 
            dp[i] = (dp[i - 2] + dp[i - 1]) % 1000000007;
        return dp[n];
    }
}

青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:
输入:n = 2
输出:2

示例 2:
输入:n = 7
输出:21

示例 3:
输入:n = 0
输出:1

动态规划

class Solution {
    public int numWays(int n) {
        if (n < 2)
            return 1;
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i <= n; i++)
            dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007;
        return dp[n];
    }
}

旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

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

示例 2:
输入:[2,2,2,0,1]
输出:0

二分查找

class Solution {
    public int minArray(int[] numbers) {
        int low = 0, high = numbers.length - 1;
        while (low < high) {
            int mid = (low + high) / 2;
            if (numbers[mid] == numbers[high])
                high--;
            else if (numbers[mid] > numbers[high])
                low = mid + 1;
            else
                high = mid;
        }
        return numbers[low];
    }
}

矩阵中的路径

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

例如,在下面的 3×4 的矩阵中包含单词 “ABCCED”(单词中的字母已标出)。
在这里插入图片描述

示例 1:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true

示例 2:
输入:board = [[“a”,“b”],[“c”,“d”]], word = “abcd”
输出:false

DFS

class Solution {
    private int[][] DIRECTION = new int[][]{{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
    private int len;
    private int rows, cols;
    private boolean[][] visit;

    public boolean exist(char[][] board, String word) {
        rows = board.length; cols = board[0].length;
        len = word.length();
        visit = new boolean[rows][cols];

        char[] charArray = word.toCharArray();
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (dfs(board, i, j, charArray, 0))
                    return true;
            }
        }
        return false;
    }

    private boolean dfs(char[][] board, int x, int y, char[] charArray, int i) {
        if (board[x][y] != charArray[i])
            return false;
        
        if (i == charArray.length - 1)
            return true;

        visit[x][y] = true;
        for (int[] direction : DIRECTION) {
            int newX = x + direction[0];
            int newY = y + direction[1];
            if (newX < 0 || newX >= rows || newY < 0 || newY >= cols || visit[newX][newY])
                continue;
            if (dfs(board, newX, newY, charArray, i + 1))
                return true;
        }
        visit[x][y] = false;
        return false;
    }
}

机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例 1:
输入:m = 2, n = 3, k = 1
输出:3

示例 2:
输入:m = 3, n = 1, k = 0
输出:1

递归

class Solution {
    public int movingCount(int m, int n, int k) {
        boolean[][] visit = new boolean[m][n];
        return move(visit, 0, 0, k);
    }

    public int move(boolean[][] visit, int i, int j, int k){
        if (i >= visit.length || j >= visit[i].length || count(i) + count(j) > k || visit[i][j])
            return 0;
        visit[i][j] = true;
        return 1 + move(visit, i + 1, j , k) + move(visit, i, j + 1, k) ;
    }

    private int count(int value){
        return value % 10 + value / 10;
    }
}

迭代

class Solution {
    public int movingCount(int m, int n, int k) {
        if (k == 0) 
            return 1;
        boolean[][] visit = new boolean[m][n];
        int result = 1;
        visit[0][0] = true;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (count(i) + count(j) > k || (i == 0 && j == 0)) 
                    continue;
                if (i - 1 >= 0) 
                    visit[i][j] |= visit[i - 1][j];
                if (j - 1 >= 0) 
                    visit[i][j] |= visit[i][j - 1];
                if (visit[i][j])
                    result++;
            }
        }
        return result;
    }

    private int count(int value){
        return value % 10 + value / 10;
    }
}

剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

数学推导

根据均值不等式 n 1 + n 2 + … + n a a ≥ n 1 n 2 … n a a \frac{n_1+n_2+\ldots+n_a}{a} \geq \sqrt[a]{n_1 n_2 \ldots n_a} an1+n2++naan1n2na ,等号当且仅当 n 1 = n 2 = . . . = n a n_1 = n_2 = ... = n_a n1=n2=...=na时成立。

  • 设将绳子按照 x x x 长度等分为 a a a 段,即 n = a x n = ax n=ax ,则乘积为 x a x^a xa x a = x n x = ( x 1 x ) n x^{a}=x^{\frac{n}{x}}=\left(x^{\frac{1}{x}}\right)^{n} xa=xxn=(xx1)n,由于 n n n 为常数,因此当 x 1 x x^{\frac{1}{x}} xx1取最大值时, 乘积达到最大值。
  • 可将问题转化为求 y = x 1 x y=x \frac{1}{x} y=xx1的极大值,因此对 x x x 求导数。 y ˙ = 1 − ln ⁡ x x 2 x 1 x \dot{y}=\frac{1-\ln x}{x^{2}} x^{\frac{1}{x}} y˙=x21lnxxx1
  • y ˙ = 0 \dot{y}=0 y˙=0,则 1 − ln ⁡ x = 0 1 - \ln x = 0 1lnx=0,易得驻点为 x 0 = e ≈ 2.7 x_0 = e \approx 2.7 x0=e2.7 x 0 x_0 x0为极大值点。代入 x = 2 x = 2 x=2 x = 3 x = 3 x=3 y ( 3 ) = 3 1 / 3 ≈ 1.44 y(3)=3^{1 / 3} \approx 1.44 y(3)=31/31.44 y ( 2 ) = 2 1 / 2 ≈ 1.41 y(2)=2^{1 / 2} \approx 1.41 y(2)=21/21.41。所以 x = 3 x=3 x=3时乘积最大。
  • 最后一段长度可能为0、1、2。当最后一段为1时,将一个 3 + 1 3+1 3+1转化为 2 + 2 2+2 2+2乘积更大。
class Solution {
    public int cuttingRope(int n) {
        if(n <= 3) 
            return n - 1;
        int num = n / 3, len = n % 3;
        int result;
        if(len == 0) 
            result = (int)Math.pow(3, num);
        else if(len == 1) 
            result = (int)Math.pow(3, num - 1) * 4;
        else 
        	result = (int)Math.pow(3, num) * 2;
        return result;
    }
}

动态规划

class Solution {
    public int cuttingRope(int n) {
        int[] dp = new int[n + 1];
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
            for (int j = 2; j < i; j++) 
                dp[i] = Math.max(dp[i], j * Math.max(i - j, dp[i - j]));
        }
        return dp[n];
    }
}

二进制中1的个数

请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。

示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 ‘1’。

示例 2:
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 ‘1’。

示例 3:
输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 ‘1’。

逐位判断

public class Solution {
    public int hammingWeight(int n) {
        int count = 0;
        while (n != 0) {
            count += n & 1;
            n >>>= 1;
        }
        return count;
    }
}

去除最右侧的1

public class Solution {
    public int hammingWeight(int n) {
        int count = 0;
        while (n != 0) {
            count++;
            n &= n - 1;
        }
        return count;
    }
}

数值的整数次方

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即, x n x^n xn)。不得使用库函数,同时不需要考虑大数问题。

示例 1:
输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:
输入:x = 2.10000, n = 3
输出:9.26100

示例 3:
输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

迭代

class Solution {
    public double myPow(double x, int n) {
        if(x == 0) 
            return 0;
        long m = n;
        double result = 1.0;
        if(m < 0) {
            x = 1 / x;
            m = -m;
        }
        while(m > 0) {
            if((m & 1) == 1) 
                result *= x;
            x *= x;
            m >>= 1;
        }
        return result;
    }
}

递归

class Solution {
    public double myPow(double x, int n) {
        if (n == 0)
            return 1;
        else if (n < 0)
            return 1 / (x * myPow(x, - n - 1));
        else if ((n & 1) == 1)
            return x * myPow(x, n - 1);
        else
            return myPow(x * x, n >> 1);    
    }
}

打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

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

循环

class Solution {
    public int[] printNumbers(int n) {
        int high = (int)Math.pow(10, n);
        int[] result = new int[high - 1];
        for (int i = 1; i < high; i++) 
            result[i - 1] = i;
        return result;
    }
}

删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动

示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

示例 2:
输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

遍历

class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode cur = dummy;
        while (cur.next != null) {
            if (cur.next.val == val) {
                cur.next = cur.next.next;
                break;
            }
            cur = cur.next;
        }
        return dummy.next;
    }
}

正则表达式匹配

请实现一个函数用来匹配包含’. ‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但与"aa.a"和"ab*a"均不匹配。

示例 1:
输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。

示例 2:
输入:
s = “aa”
p = “a*”
输出: true
解释: 因为 ‘*’ 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 ‘a’。因此,字符串 “aa” 可被视为 ‘a’ 重复了一次。

示例 3:
输入:
s = “ab”
p = “.*”
输出: true
解释: “.*” 表示可匹配零个或多个(’*’)任意字符(’.’)。

示例 4:
输入:
s = “aab”
p = “c*a*b”
输出: true
解释: 因为 ‘*’ 表示零个或多个,这里 ‘c’ 为 0 个, ‘a’ 被重复一次。因此可以匹配字符串 “aab”。

示例 5:
输入:
s = “mississippi”
p = “mis*is*p*.”
输出: false

动态规划

c∗ 可分为两种情况:

  • 匹配0个, d p [ i ] [ j ] = d p [ i ] [ j − 2 ] dp[i][j] = dp[i][j-2] dp[i][j]=dp[i][j2]
  • 匹配大于0个, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i1][j]
class Solution {
    public boolean isMatch(String s, String p) {
        int sLen = s.length(), pLen = p.length();
        boolean[][] dp = new boolean[sLen + 1][pLen + 1];

        char[] sChar = s.toCharArray(), pChar = p.toCharArray();
        for (int i = 0; i <= sLen; i++) {
            for (int j = 0; j <= pLen; j++) {
                if (j == 0)
                    dp[i][j] = i == 0;
                else if (pChar[j - 1] != '*') {
                    if (i != 0 && match(sChar[i - 1], pChar[j - 1]))
                        dp[i][j] = dp[i - 1][j - 1];
                } else if (j >= 2) {
                    dp[i][j] |= dp[i][j - 2];
                    if (i != 0 && match(sChar[i - 1], pChar[j - 2]))
                        dp[i][j] |= dp[i - 1][j];
                }
            }
        }
        return dp[sLen][pLen];
    }

    private boolean match(char c1, char c2) {
        return c1 == c2 || c2 == '.';
    }
}

调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。

遍历+双指针

class Solution {
    public int[] exchange(int[] nums) {
        int len = nums.length;
        int[] result = new int[len];
        int p1 = 0, p2 = len - 1;
        for (int num : nums) {
            if (num % 2 == 0)
                result[p2--] = num;
            else
                result[p1++] = num;
        }
        return result;
    }
}

原地替换

class Solution {
    public int[] exchange(int[] nums) {
        int len = nums.length;
        int left = 0, right = len - 1;
        while (left < right ) {
            if (nums[left] % 2 == 0)
                swap(nums, left, right--);
            else
                left++;
        }
        return nums;
    }

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

链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.

双指针

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode slow = head, fast = head;
        while(k-- > 0) 
            fast = fast.next;
        while (fast != null) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

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

迭代

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null, curr = head, next = head;
        while (curr != null) {
            next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}

递归

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null)
            return head;
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

合并两个排序的链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

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

迭代

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode cur = dummy;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                cur.next = l1;
                l1 = l1.next;
            } else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        cur.next = (l1 != null) ? l1 : l2;
        return dummy.next;
    }
}

递归

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null || l2 == null)
            return l1 != null ? l1 : l2;

        if(l1.val <= l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        } 
    }
}

表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。

分治法

class Solution {
    public boolean isNumber(String s) {
        s = s.trim();
        int ePos = Math.max(s.indexOf("e"), s.indexOf("E"));
        if (ePos == -1)
            return isSignedFloat(s);
        return isSignedFloat(s.substring(0, ePos)) && isSignedInteger(s.substring(ePos + 1));
    }

    private boolean isSignedFloat(String s) {
        if (s.length() == 0)
            return false;
        if (s.charAt(0) == '-' || s.charAt(0) == '+')
            return isUnsignedFloat(s.substring(1));
        return isUnsignedFloat(s);
    }

    private boolean isUnsignedFloat(String s) {
        int dotPos = s.indexOf(".");
        if (dotPos == -1)
            return isUnsignedInteger(s);
        else if (dotPos == 0)
            return isUnsignedInteger(s.substring(1));
        else if (dotPos == s.length() - 1)
            return isUnsignedInteger(s.substring(0, dotPos));
        return isUnsignedInteger(s.substring(0, dotPos)) && isUnsignedInteger(s.substring(dotPos + 1));
    }

    private boolean isSignedInteger(String s) {
        if (s.length() == 0)
            return false;
        if (s.charAt(0) == '-' || s.charAt(0) == '+')
            return isUnsignedInteger(s.substring(1));
        return isUnsignedInteger(s);
    }
    
    private boolean isUnsignedInteger(String s) {
        int len = s.length();
        if (len == 0)
            return false;
        for (int i = 0; i < len; i++)
            if (s.charAt(i) < '0' || s.charAt(i) > '9')
                return false;
        return true;
    }
}

树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

例如:
给定的树 A:
在这里插入图片描述
给定的树 B:
在这里插入图片描述
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

递归

class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        if (A == null || B == null)
            return false;
        return isContain(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
    }

    boolean isContain(TreeNode A, TreeNode B) {
        if (B == null) 
            return true;
        if (A == null || A.val != B.val) 
            return false;
        return isContain(A.left, B.left) && isContain(A.right, B.right);
    }
}

二叉树的镜像

请完成一个函数,输入一个二叉树,该函数输出它的镜像。

例如输入:
在这里插入图片描述
镜像输出:
在这里插入图片描述

递归

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if (root == null)
            return null;
        TreeNode left = root.left;
        root.left = mirrorTree(root.right);
        root.right = mirrorTree(left);
        return root;
    }
}

对称的二叉树

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
在这里插入图片描述
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
在这里插入图片描述

递归

class Solution {
    public boolean isSymmetric(TreeNode root) {
        return dfs(root, root);
    }

    private boolean dfs(TreeNode root1, TreeNode root2) {
        if (root1 == null && root2 == null)
            return true;
        if (root1 == null || root2 == null || root1.val != root2.val)
            return false;
        return dfs(root1.left, root2.right) && dfs(root1.right, root2.left);
    }
}

顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

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

示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

模拟

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        int rows = matrix.length;
        if (rows == 0)
            return new int[0];
        int cols = matrix[0].length;

        int total = rows * cols;
        int row = 0, col = 0;
        int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        boolean[][] visit = new boolean[rows][cols];
        int dIndex = 0;
        int[] result = new int[total];
        for (int i = 0; i < total; i++) {
            result[i] = matrix[row][col];
            visit[row][col] = true;
            int nextRow = row + directions[dIndex][0], nextCol = col + directions[dIndex][1];
            if (nextRow >= rows || nextCol < 0 || nextCol >= cols || visit[nextRow][nextCol])
                dIndex = (dIndex + 1) % 4;
            
            row += directions[dIndex][0];
            col += directions[dIndex][1];
        }
        return result;
    }
}

按层模拟

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        int rows = matrix.length;
        if (rows == 0)
            return new int[0];
        int cols = matrix[0].length;
        int total = rows * cols;
        int left = 0, right = cols - 1, top = 0, bottom = rows - 1;
        int[] result = new int[total];
        int k = 0;
        while (k < total) {
            for (int i = left; i <= right; i++)
                result[k++] = matrix[top][i];
            for (int i = top + 1; i <= bottom; i++)
                result[k++] = matrix[i][right];
            if (k >= total) 
                break;
            for (int i = right - 1; i >= left; i--)
            result[k++] = matrix[bottom][i];
            for (int i = bottom - 1; i > top; i--)
                result[k++] = matrix[i][left];

            left++; right--;
            top++; bottom--;
        }
        return result;
    }
}

包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.

数值存储

class MinStack {
    private int capacity;
    private int index;
    private int[] data_stack;
    private int[] min_stack;

    public MinStack() {
        index = -1;
        capacity = 100;
        data_stack = new int[capacity];
        min_stack = new int[capacity];
    }
    
    public void push(int x) {
        index++;
        if (index == capacity) {
            capacity *= 10;
            int[] new_data_stack = new int[capacity];
            int[] new_min_stack = new int[capacity];
            for (int i = 0; i < index; i++) {
                new_data_stack[i] = data_stack[i];
                new_min_stack[i] = min_stack[i];
            }
            data_stack = new_data_stack;
            min_stack = new_min_stack;
        }
        data_stack[index] = x;
        if (index == 0 || min_stack[index - 1] > x)
            min_stack[index] = x;
        else
            min_stack[index] = min_stack[index - 1];
    }
    
    public void pop() {
        index--;
    }
    
    public int top() {
        return data_stack[index];
    }
    
    public int min() {
        return min_stack[index];
    }
}

辅助栈

class MinStack {
    private Deque<Integer> data_stack;
    private Deque<Integer> min_stack;

    public MinStack() {
        data_stack = new LinkedList<>();
        min_stack = new LinkedList<>();
        min_stack.add(Integer.MAX_VALUE);
    }
    
    public void push(int x) {
        data_stack.push(x);
        if (x <= min_stack.peek())
            min_stack.push(x);
    }
    
    public void pop() {
        if (data_stack.pop().equals(min_stack.peek()))
            min_stack.pop();
    }
    
    public int top() {
        return data_stack.peek();
    }
    
    public int min() {
        return min_stack.peek();
    }
}

栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {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 之前弹出。

辅助栈

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Deque<Integer> stack = new LinkedList<>();
        int i = 0;
        for(int num : pushed) {
            stack.push(num);
            while (!stack.isEmpty() && stack.peek() == popped[i]) {
                stack.pop();
                i++;
            }
        }
        return stack.isEmpty();
    }
}

从上到下打印二叉树1

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

例如:
给定二叉树:
在这里插入图片描述
返回:
[3,9,20,15,7]

层次遍历

class Solution {
    public int[] levelOrder(TreeNode root) {
        if (root == null)
            return new int[0];
        List<Integer> list = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            list.add(node.val);
            if (node.left != null)
                queue.offer(node.left);
            if (node.right != null)
                queue.offer(node.right);
        }
        int len = list.size();
        int[] result = new int[len];
        for (int i = 0; i < len; i++)
            result[i] = list.get(i);
        return result;
    }
}

从上到下打印二叉树2

返回二叉树层次遍历结果:
[
[3],
[9,20],
[15,7]
]

层次遍历

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null)
            return result;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> level = new ArrayList<>();
            while (size-- > 0) {
                TreeNode node = queue.poll();
                level.add(node.val);
                if (node.left != null)
                    queue.offer(node.left);
                if (node.right != null)
                    queue.offer(node.right);
            }
            result.add(level);
        }
        return result;
    }
}

从上到下打印二叉树3

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

返回其层次遍历结果:
[
[3],
[20,9],
[15,7]
]

层次遍历

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null)
            return result;
        boolean flag = true;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> level = new ArrayList<>();
            while (size-- > 0) {
                TreeNode node = queue.poll();
                if (flag)
                    level.add(node.val);
                else
                    level.add(0, node.val);
                if (node.left != null)
                    queue.offer(node.left);
                if (node.right != null)
                    queue.offer(node.right);
            }
            flag = !flag;
            result.add(level);
        }
        return result;
    }
}

二叉搜索树的后序遍历序列

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

参考以下这颗二叉搜索树:
在这里插入图片描述

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

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

递归分治

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        return recursion(postorder, 0, postorder.length - 1);
    }

    private boolean recursion(int[] postorder, int left, int root) {
        if (left >= root)
            return true;
        int i = left;
        while (postorder[i] < postorder[root])
            i++;
        int right = i;
        while (postorder[i] > postorder[root])
            i++;
        return i == root && recursion(postorder, left, right - 1) && recursion(postorder, right, root - 1);
    }
}

辅助栈

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        Deque<Integer> stack = new LinkedList<>();
        int root = Integer.MAX_VALUE;
        for(int i = postorder.length - 1; i >= 0; i--) {
            if(postorder[i] > root) 
                return false;
            while(!stack.isEmpty() && stack.peek() > postorder[i])
            	root = stack.pop();
            stack.push(postorder[i]);
        }
        return true;
    }
}

二叉树中和为某一值的路径

输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。

示例:
给定如下二叉树,以及目标和 target = 22,
在这里插入图片描述
返回:
[
[5,4,11,2],
[5,8,4,5]
]

回溯

class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int target) {
        List<List<Integer>> result = new ArrayList<>();
        backtrack(result, new ArrayList<Integer>(), root, target);
        return result;
    }

    private void backtrack(List<List<Integer>> result, List<Integer> path, TreeNode root, int target) {
        if (root == null)
            return;
        
        target -= root.val;
        path.add(root.val);
        if (target == 0 && root.left == null && root.right == null)
            result.add(new ArrayList<>(path));
        backtrack(result, path, root.left, target);
        backtrack(result, path, root.right, target);
        path.remove(path.size() - 1);
    }
}

复杂链表的复制

请实现 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 = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

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

哈希表存储

class Solution {
    public Node copyRandomList(Node head) {
        Map<Node, Node> map = new HashMap<>();
        Node dummy = new Node(0), cur = dummy;
        while (head != null) {
            if (!map.containsKey(head)) {
                Node node = new Node(head.val);
                map.put(head, node);
            }
            cur.next = map.get(head);
            cur = cur.next;
            if (head.random != null) {
                if (!map.containsKey(head.random)) {
                    Node node = new Node(head.random.val);
                    map.put(head.random, node);
                }
                cur.random = map.get(head.random);
            }
            head = head.next;
        }
        return dummy.next;
    }
}

拼接 + 拆分

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) 
            return null;
        Node cur = head;
        while (cur != null) {
            Node node = new Node(cur.val);
            node.next = cur.next;
            cur.next = node;
            cur = node.next;
        }
        cur = head;
        while (cur != null) {
            if (cur.random != null)
                cur.next.random = cur.random.next;
            cur = cur.next.next;
        }
        Node newHead = head.next, pre = head;
        cur = newHead;
        while (cur.next != null) {
            pre.next = cur.next;
            pre = cur.next;
            cur.next = pre.next;
            cur = pre.next;
        }
        pre.next = null;
        return newHead;
    }
}

二叉搜索树与双向链表

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

为了让您更好地理解问题,以下面的二叉搜索树为例:
在这里插入图片描述
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
在这里插入图片描述
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

中序遍历

class Solution {
    private Node head = null, prev = null;
    public Node treeToDoublyList(Node root) {
        if (root == null)
            return null;
        dfs(root);
        head.left = prev;
        prev.right = head;
        return head;
    }

    private void dfs(Node root) {
        if (root == null)
            return;
        
        dfs(root.left);
        if (prev == null)
            head = root;
        else
            prev.right = root;
        root.left = prev;
        prev = root;
        dfs(root.right);
    }
}

序列化二叉树

示例:
你可以将以下二叉树:
在这里插入图片描述
序列化为 “[1,2,3,null,null,4,5]”

摩尔投票法

class Solution {
    public int majorityElement(int[] nums) {
        int len = nums.length;
        int candidate = nums[0];
        int count = 1;
        for (int i = 1; i < len; i++) {
            if(count == 0) 
                candidate = nums[i];
            count += (nums[i] != candidate) ? -1 : 1;
        }
        return candidate;
    }
}

第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

示例:
s = “abaccdeff”
返回 “b”
s = “”
返回 " "

indexOf

class Solution {
    public char firstUniqChar(String s) {
        int index = s.length();
        int first = 0;
        for (char c = 'a'; c <= 'z'; c++) {
            first = s.indexOf(c);
            if (first != -1 && first == s.lastIndexOf(c)) {
                if (index > first) 
                    index = first;
            }
        }
        return index < s.length() ? s.charAt(index) : ' ';
    }
}

数组存储

class Solution {
    public char firstUniqChar(String s) {
        int len = s.length();
        if (len == 0)
            return ' ';
        int[] num = new int[26];
        char[] charArray = s.toCharArray();
        for (int i = 0; i < len; i++) {
            if (num[charArray[i] - 'a'] == 0)
                num[charArray[i] - 'a'] = i + 1;
            else if (num[charArray[i] - 'a'] > 0)
                num[charArray[i] - 'a'] = -1;
        }
        
        int min = Integer.MAX_VALUE;
        char c = ' ';
        for (int i = 0; i < 26; i++) {
            if (num[i] > 0 && min > num[i]) {
                min = num[i];
                c = (char)(i + 'a');
            }
        }
        return c;
    }
}

最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]

大顶堆 优先队列

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) 
            return new int[0];

        Queue<Integer> queue = new PriorityQueue<>((i1, i2) -> i2 - i1);
        for (int num : arr) {
            if (queue.size() < k) {
                queue.add(num);
            } else if (num < queue.peek()) {
                queue.poll();
                queue.add(num);
            }
        }

        int[] result = new int[k];
        for (int i = k - 1; i >= 0; i--)
            result[i] = queue.poll();
        return result;
    }
}

快排

class Solution {
    private static Random random = new Random(System.currentTimeMillis());
    public int[] getLeastNumbers(int[] arr, int k) {
        int len = arr.length;
        if (k == 0 || len == 0) 
            return new int[0];
        
        int left = 0, right = len - 1;
        while (true) {
            int index = partition(arr, left, right);
            if (index == k - 1)
                return Arrays.copyOf(arr, k);
            else if (index < k)
                left = index + 1;
            else
                right = index - 1;
        }
    }

    private int partition(int[] arr, int left, int right) {
        if (right > left) {
            int randomIndex = left + 1 + random.nextInt(right - left);
            swap(arr, left, randomIndex);
        }
        int pivot = arr[left];
        int l = left + 1, r = right;
        while (true) {
            while (l <= r && arr[l] < pivot)
                l++;
            while (l <= r && arr[r] > pivot)
                r--;
            if (l > r)
                break;
            swap(arr, l++, r--);
        }
        swap(arr, left, r);
        return r;
    }

    private void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。

示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

动态规划

class Solution {
    public int maxSubArray(int[] nums) {
        int len = nums.length;
        int result = nums[0];
        for (int i = 1; i < len; i++) {
            nums[i] += Math.max(0, nums[i - 1]);
            result = Math.max(result, nums[i]);
        }
        return result;
    }
}

字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

示例:
输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]

回溯法

class Solution {
    private char[] charArray;
    private int len;
    public String[] permutation(String s) {
        charArray = s.toCharArray();
        len = charArray.length;
        
        List<String> list = new ArrayList<>();
        backtrack(list, 0);
        return list.toArray(new String[0]);
    }

    private void backtrack(List<String> list, int start) {
        if (start == len) {
            list.add(String.valueOf(charArray));
            return;
        }

        boolean[] flag = new boolean[26];
        for (int i = start; i < len; i++) {
            if(flag[charArray[i] - 'a'])
                continue;
            flag[charArray[i] - 'a'] = true;
            swap(i, start);
            backtrack(list, start + 1);
            swap(i, start);
        }
    }

    private void swap(int a, int b) {
        char tmp = charArray[a];
        charArray[a] = charArray[b];
        charArray[b] = tmp;
    }
}

序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。

示例:
你可以将以下二叉树:
在这里插入图片描述
序列化为 “[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 "[]";

        StringBuilder sb = new StringBuilder("[");
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (node == null) {
                sb.append("null,");
            } else {
                sb.append(node.val).append(',');
                queue.offer(node.left);
                queue.offer(node.right);
            }
        }
        sb.deleteCharAt(sb.length() - 1).append(']');
        return sb.toString();
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if (data.equals("[]"))
            return null;
        
        String[] vals = data.substring(1, data.length() - 1).split(",");
        int len = vals.length;
        
        TreeNode root = new TreeNode(Integer.parseInt(vals[0]));
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        for (int i = 1; i < len; i+=2) {
            TreeNode node = queue.poll();
            if (!vals[i].equals("null")) {
                node.left = new TreeNode(Integer.parseInt(vals[i]));
                queue.offer(node.left);
            }
            if (!vals[i + 1].equals("null")) {
                node.right = new TreeNode(Integer.parseInt(vals[i + 1]));
                queue.offer(node.right);
            }
        }
        return root;
    }
}

数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

  • [2,3,4] 的中位数是 3
  • [2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

  • void addNum(int num) - 从数据流中添加一个整数到数据结构中。
  • double findMedian() - 返回目前所有元素的中位数。

示例 1:
输入:
[“MedianFinder”,“addNum”,“addNum”,“findMedian”,“addNum”,“findMedian”]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]

示例 2:
输入:
[“MedianFinder”,“addNum”,“findMedian”,“addNum”,“findMedian”]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]

优先队列

class MedianFinder {
    private PriorityQueue<Integer> small; 
    private PriorityQueue<Integer> large;
    public MedianFinder() {
        small = new PriorityQueue<Integer>((i1, i2) -> i2 - i1);
        large = new PriorityQueue<Integer>();
    }
    
    public void addNum(int num) {
        if (large.isEmpty()) {
            large.offer(num);
        } else if (small.size() == large.size()) {
            if (num < small.peek()) {
                small.offer(num);
                num = small.poll();
            }
            large.offer(num);
        } else {
            if (num > large.peek()) {
                large.offer(num);
                num = large.poll();
            }
            small.offer(num);
        }
    }
    
    public double findMedian() {
        if (small.size() < large.size())
            return large.peek();
        else
            return (large.peek() + small.peek()) / 2.0;
    }
}

1~n整数中 1 出现的次数

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

示例 1:
输入:n = 12
输出:5

示例 2:
输入:n = 13
输出:6

数学归纳法

class Solution {
    public int countDigitOne(int n) {
        int count = 0;
        for (int k = 1; k <= n; k *= 10) {
            int high = n / k;
            int cur = high % 10;
            high /= 10;
            int low = n % k;

            count += high * k;
            if (cur > 1) {
                count += k;
            } else if (cur == 1) {
                count += low + 1;
            }
            if (high == 0)
                break;
        }
        return count;
    }
}

数字序列中的某一位数字

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。

示例 1:
输入:n = 3
输出:3

示例 2:
输入:n = 11
输出:0

数学归纳法

在这里插入图片描述

class Solution {
    public int findNthDigit(int n) {
        if (n == 0)
            return 0;
            
        long start = 1;
        long count = 9;
        int digit_num = 1;
        while (n > count) {
            n -= count;
            start *= 10;
            count = start * 9 * ++digit_num;
        }
        int num = (int)(start + (n - 1) / digit_num);
        int pos = (n - 1) % digit_num;
        char c = String.valueOf(num).charAt(pos);
        return c - '0';
    }
}

把数组排成最小的数

输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

示例 1:
输入: [10,2]
输出: “102”

示例 2:
输入: [3,30,34,5,9]
输出: “3033459”

排序

class Solution {
    public String minNumber(int[] nums) {
        int len = nums.length;
        Integer[] numArr = new Integer[len];
        for (int i = 0; i < len; i++)
            numArr[i] = nums[i];
        Arrays.sort(numArr, new Comparator<Integer>() {
            public int compare(Integer a, Integer b) {
                long k = 10;
                long a1 = 0, b1 = 0;
                while (a1 * b1 == 0) {
                    if (a1 == 0 && k > a)
                        a1 = k;
                    if (b1 == 0 && k > b)
                        b1 = k;
                    k *= 10;
                }
                long newA = a * b1 + b, newB = b * a1 + a;
                return (int)(newA - newB);
            }
        });
        
        StringBuilder sb = new StringBuilder();
        for (Integer num : numArr) 
            sb.append(num);
        return sb.toString();
    }
}

把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", “bwfi”, “bczi”, “mcfi"和"mzi”

动态规划

class Solution {
    public int translateNum(int num) {
        List<Integer> list = new ArrayList<>();
        do {
            list.add(0, num % 10);
            num /= 10;
        } while (num != 0);

        int len = list.size();
        int[] dp = new int[len + 1];
        dp[0] = 1; dp[1] = 1;
        for (int i = 1; i < len; i++) {
            if (list.get(i - 1) != 0 && list.get(i - 1) * 10 + list.get(i) < 26)
                dp[i + 1] = dp[i] + dp[i - 1];
            else
                dp[i + 1] = dp[i];
        }
        return dp[len];
    }
}

礼物的最大价值

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

示例 1:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物

动态规划

class Solution {
    public int maxValue(int[][] grid) {
        int rows = grid.length, cols = grid[0].length;
        for(int i = 0; i < rows; i++) {
            for(int j = 0; j < cols; j++) {
                if(i == 0 && j == 0) 
                    continue;
                if(i == 0) 
                    grid[i][j] += grid[i][j - 1] ;
                else if(j == 0) 
                    grid[i][j] += grid[i - 1][j];
                else 
                    grid[i][j] += Math.max(grid[i][j - 1], grid[i - 1][j]);
            }
        }
        return grid[rows - 1][cols - 1];
    }
}

最长不含重复字符的子字符串

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

双指针

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int[] index = new int[128];
        int len = s.length();
        int start = 0, end = 0;
        int max_length = 0;
        for (int i = 1; i <= len; i++) {
            char c = s.charAt(i - 1);
            if (index[c] != 0) 
                start = Math.max(start, index[c]);
            end = i;
            index[c] = i;
            max_length = Math.max(max_length, end - start);
        }
        return max_length;
    }
}

队列

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int len = s.length();
        Queue<Character> queue = new LinkedList<>();
        int max_length = 0;
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i);
            while (queue.contains(c))
                queue.poll();
            queue.offer(c);
            max_length = Math.max(max_length, queue.size());
        }
        return max_length;
    }
}

丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

动态规划

class Solution {
    public int nthUglyNumber(int n) {
        int[] ugly_num = new int[n];
        ugly_num[0] = 1;
        int p2 = 0, p3 = 0, p5 = 0;
        for (int i = 1; i < n; i++) {
            int n2 = 2 * ugly_num[p2], n3 = 3 * ugly_num[p3], n5 = 5 * ugly_num[p5];
            ugly_num[i] = Math.min(Math.min(n2, n3), n5);
            if (ugly_num[i] == n2) 
                p2++;
            if (ugly_num[i] == n3) 
                p3++;
            if (ugly_num[i] == n5) 
                p5++;
        }
        return ugly_num[n - 1];
    }
}

两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共节点。

如下面的两个链表:
在这里插入图片描述
在节点 c1 开始相交。

示例 1:
在这里插入图片描述
输入: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 个节点。

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

示例 3:
在这里插入图片描述
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

双指针

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode n1 = headA, n2 = headB;
        while (n1 != n2) {
            n1 = n1 == null ? headB : n1.next;
            n2 = n2 == null ? headA : n2.next;
        }
        return n1;
    }
}

在排序数组中查找数字1

统计一个数字在排序数组中出现的次数。

示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2

示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0

二分查找

class Solution {
    public int search(int[] nums, int target) {
        return binarySearch(nums, target) - binarySearch(nums, target - 1);
    }

    private int binarySearch(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] > target) 
                right = mid - 1;
            else
                left = mid + 1;
        }
        return left;
    }
}

0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

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

示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8

二分法

class Solution {
    public int missingNumber(int[] nums) {
        int len = nums.length;
        int left = 0, right = len - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] == mid)
                left = mid + 1;
            else
                right = mid - 1;
        }
        return left;
    }
}

二叉搜索树的第k大节点

给定一棵二叉搜索树,请找出其中第k大的节点。

示例 1:
输入: root = [3,1,4,null,2], k = 1
在这里插入图片描述
输出: 4

示例 2:
输入: root = [5,3,6,2,4,null,null,1], k = 3
在这里插入图片描述
输出: 4

中序倒序遍历

class Solution {
    private int result, k;
    public int kthLargest(TreeNode root, int k) {
        this.k = k;
        dfs(root);
        return result;
    }

    private void dfs(TreeNode root) {
        if(root == null) 
            return;
        dfs(root.right);
        if(k == 0) 
            return;
        if(--k == 0) 
            result = root.val;
        dfs(root.left);
    }
}

二叉树的深度

输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。

例如:
给定二叉树 [3,9,20,null,null,15,7]
在这里插入图片描述

递归

class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null)
            return 0;
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }
}

平衡二叉树

输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。

示例 1:
在这里插入图片描述
给定二叉树 [3,9,20,null,null,15,7]
返回 true 。

示例 2:
在这里插入图片描述
给定二叉树 [1,2,2,3,3,null,null,4,4]
返回 false 。

递归

class Solution {
    public boolean isBalanced(TreeNode root) {
        return maxDepth(root) != -1;
    }

    private int maxDepth(TreeNode root) {
        if (root == null)
            return 0;
        
        int left_height = maxDepth(root.left);
        if (left_height == -1)
            return -1;
        int right_height = maxDepth(root.right);
        if (right_height == -1)
            return -1;

        return Math.abs(left_height - right_height) < 2 ? Math.max(left_height, right_height) + 1 : -1;
    }
}

和为s的两个数字

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]

示例 2:
输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]

双指针

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int len = nums.length;
        int left = 0, right = len - 1;
        while (left < right) {
            int sum = nums[left] + nums[right];
            if (sum == target)
                return new int[]{nums[left], nums[right]};
            else if (sum < target)
                left++;
            else
                right--;
        }
        return new int[0];
    }
}

和为s的连续正数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

示例 1:
输入:target = 9
输出:[[2,3,4],[4,5]]

示例 2:
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]

双指针

class Solution {
    public int[][] findContinuousSequence(int target) {
        List<int[]> result = new ArrayList<>();
        int start = 1, end = 2, sum = 3;
        while (start < end) {
            if (sum == target) {
                int[] res = new int[end - start + 1];
                for (int i = start; i <= end; i++)
                    res[i - start] = i;
                result.add(res);
                sum -= start++;
            } else if (sum < target) {
                sum += ++end;
            } else {
                sum -= start++;
            }
        }
        return result.toArray(new int[result.size()][]);
    }
}

翻转单词顺序

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。

示例 1:
输入: “the sky is blue”
输出: “blue is sky the”

示例 2:
输入: " hello world! "
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

示例 3:
输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

双指针

class Solution {
    public String reverseWords(String s) {
        int len = s.length();
        StringBuilder sb = new StringBuilder();
        int start = 0, end = 0;
        for (int i = len - 1; i >= 0; i--) {
            if (s.charAt(i) == ' ')
                continue;
            end = i + 1; start = i;
            while (start > 0 && s.charAt(start - 1) != ' ')
                start--;
            sb.append(s.substring(start, end) + " ");
            i = start;
        }
        return sb.toString().trim();
    }
}

库函数

class Solution {
    public String reverseWords(String s) {
        String[] words = s.trim().split(" ");
        int num = words.length;
        StringBuilder sb = new StringBuilder();
        for (int i = num - 1; i >= 0; i--) {
            if (!words[i].equals(""))
                sb.append(words[i]).append(" ");
        }
        return sb.toString().trim();
    }
}

左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

示例 1:
输入: s = “abcdefg”, k = 2
输出: “cdefgab”

示例 2:
输入: s = “lrloseumgh”, k = 6
输出: “umghlrlose”

切片+拼接

class Solution {
    public String reverseLeftWords(String s, int n) {
        return s.substring(n) + s.substring(0, n);
    }
}

扑克牌中的顺子

从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。

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

示例 2:
输入: [0,0,1,2,5]
输出: True

排序

class Solution {
    public boolean isStraight(int[] nums) {
        Arrays.sort(nums);
        int joker = 0;
        for (int i = 0; i < 4; i++) {
            if (nums[i] == 0) 
                joker++;
            else if (nums[i] == nums[i + 1])
                return false;
        }
        return nums[4] - nums[joker] < 5;
    }
}

圆圈中最后剩下的数字

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

示例 1:
输入: n = 5, m = 3
输出: 3

示例 2:
输入: n = 10, m = 17
输出: 2

约瑟夫环问题

要得到最后剩下的数字,可以转换思路,即找到该数字在开始时的下标。在最后只剩一个数时,其坐标为0,可以逆推上一轮其位置。
在这里插入图片描述

class Solution {
    public int lastRemaining(int n, int m) {
        int cur = 0;
        for (int i = 2; i <= n; i++) 
            cur = (cur + m) % i;
        return cur;
    }
}

不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。

示例:
输入: a = 1, b = 1
输出: 2

位运算

在这里插入图片描述

class Solution {
    public int add(int a, int b) {
        while (b != 0) {
            int c = (a & b) << 1;
            a ^= b; // 非进位和
            b = c;  // 进位
        }
        return a;
    }
}

二叉搜索树的最近公共祖先

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

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
在这里插入图片描述

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

示例 2:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

递归

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root.val > p.val && root.val > q.val)
            return lowestCommonAncestor(root.left, p, q);
        else if (root.val < p.val && root.val < q.val)
            return lowestCommonAncestor(root.right, p, q);
        return root;
    }
}

迭代

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        TreeNode ancestor = root;
        while (true) {
            if (ancestor.val > p.val && ancestor.val > q.val)
                ancestor = ancestor.left;
            else if (ancestor.val < p.val && ancestor.val < q.val)
                ancestor = ancestor.right;
            else
                break;
        }
        return ancestor;
    }
}

二叉树的最近公共祖先

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

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
在这里插入图片描述

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

示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

DFS

class Solution {
    private TreeNode result;
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        dfs(root, p, q);
        return result;
    }

    private boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null)
            return false;

        boolean current = root.val == p.val || root.val == q.val;
        boolean lson = dfs(root.left, p, q);
        boolean rson = dfs(root.right, p, q);
        if ((lson && rson) || (current && (lson || rson)))
            result = root;
        return lson || rson || current;
    }
}

求1+2+…+n

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

示例 1:
输入: n = 3
输出: 6

示例 2:
输入: n = 9
输出: 45

逻辑符短路

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

位运算乘法

class Solution {
    public int sumNums(int n) {
        return multiply(n, n + 1) >> 1;
    }  

    private int multiply(int x, int y) {
        int result = 0;
        boolean flag;

        flag = (y != 0) &&
               (flag = (y & 1) == 1 
               && ((result += x + multiply(x<<=1, y>>=1)) > 0) 
               || ((result += multiply(x<<=1, y>>=1)) > 0) );
        
        return result;
    }
}

异常捕获

耗时长

class Solution {
    private int[] nums = new int[] {1};
    public int sumNums(int n) {
        try {
            return nums[n - 1];
        } catch (Exception e) {
            return n + sumNums(n - 1);
        }
    }  
}

空间换时间

class Solution {
    private int[] nums = new int[10000];
    public int sumNums(int n) {
        try {
            return nums[n - 1] + n + sumNums(n - 1);
        } catch (Exception e) {
            return 0;
        }
    }  
}

数组中数字出现的次数

一个整型数组 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]

分组异或

只有一个数时可以使用异或,要求两个数则需要将这两个数分到不同的组中分别求。

class Solution {
    public int[] singleNumbers(int[] nums) {
        int xor = 0;
        for (int num : nums)
            xor ^= num;
        
        int div = xor & (xor - 1) ^ xor;  // 得到最右侧的1
        // int div = -xor & xor;  // 最低位的1所对应的值
        int result1 = 0, result2 = 0;
        for (int num : nums) {
            if ((div & num) != 0)
                result1 ^= num;
            else
                result2 ^= num;
        }
        return new int[]{result1, result2};
    }
}

数组中数字出现的次数2

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

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

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

统计各比特出现的次数

class Solution {
    public int singleNumber(int[] nums) {
        int[] bits = new int[32];
        for (int num : nums) {
            for (int i = 0; i < 32; i++) {
                if ((num & 1) == 1)
                    bits[i]++;
                num >>>= 1;
            }
        }
        int result = 0;
        for (int i = 0; i < 32; i++) {
            if (bits[i] % 3 == 1)
                result |= 1 << i;
        }
        return result;
    }
}

有限状态机

在这里插入图片描述

  • 计算 one
if two == 0:
	if n == 0:
    	one = one
  	if n == 1:
    	one = ~one
if two == 1:
	one = 0

可简化为:one = one ^ n & ~two

  • 计算two

在这里插入图片描述

class Solution {
    public int singleNumber(int[] nums) {
        int ones = 0, twos = 0;
        for (int num : nums) {
            ones = ones ^ num & ~twos;
            twos = twos ^ num & ~ones;
        }
        return ones;
    }
}

把字符串转换成整数

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

注意:
假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [ − 2 31 , 2 31 − 1 ] [−2^{31}, 2^{31} − 1] [231,2311]。如果数值超过这个范围,请返回 INT_MAX ( 2 31 − 1 2^{31} − 1 2311) 或 INT_MIN ( − 2 31 -2^{31} 231) 。

示例 1:
输入: “42”
输出: 42

示例 2:
输入: " -42"
输出: -42
解释: 第一个非空白字符为 ‘-’, 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。

示例 3:
输入: “4193 with words”
输出: 4193
解释: 转换截止于数字 ‘3’ ,因为它的下一个字符不为数字。

示例 4:
输入: “words and 987”
输出: 0
解释: 第一个非空字符是 ‘w’, 但它不是数字或正、负号。
因此无法执行有效的转换。

示例 5:
输入: “-91283472332”
输出: -2147483648
解释: 数字 “-91283472332” 超过 32 位有符号整数范围。
因此返回 INT_MIN ( − 2 31 -2^{31} 231) 。

分步处理,越界判断

class Solution {
    public int strToInt(String str) {
        char[] charArray = str.trim().toCharArray();
        int len = charArray.length;
        if (len == 0)
            return 0;

        int result = 0, sign = 1;
        int i = 1;
        if (charArray[0] == '-')
            sign = -1;
        else if (charArray[0] != '+') 
            i = 0;
        for (; i < len; i++) {
            if (charArray[i] < '0' || charArray[i] > '9')
                break;
            int tmp = 10 * result + charArray[i] - '0';
            if (tmp / 10 != result) 
                return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            result = tmp;
        }
        return sign * result;
    }
}

数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:
输入: [7,5,6,4]
输出: 5

归并排序

class Solution {
    private int count = 0;
    public int reversePairs(int[] nums) {
        mergeSort(nums, 0, nums.length - 1);
        return count;
    }

    private void mergeSort(int[] nums, int start, int end) {
        if (start >= end) 
            return;
        
        int mid = (end + start - 1) / 2;
        mergeSort(nums, start, mid);
        mergeSort(nums, mid + 1, end);
        if (nums[mid] <= nums[mid + 1])
            return;

        int[] tmp = Arrays.copyOfRange(nums, start, end + 1);
        int i = 0, len1 = mid + 1 - start, j = len1, len2 = end + 1 - start;
        for (int k = start; k <= end; k++) {
            if (i == len1) {
                nums[k] = tmp[j++];
            } else if (j == len2) {
                nums[k] = tmp[i++];
            } else if (tmp[i] <= tmp[j]) {
                nums[k] = tmp[i++];
            } else {
                nums[k] = tmp[j++];
                count += len1 - i;
            }
        }
    }
}

树状数组

public class Solution {
    public int reversePairs(int[] nums) {
        int len = nums.length;
        if (len < 2) 
            return 0;

        // 离散化:使得数字更紧凑,节约树状数组的空间
        // 1、使用二分搜索树是为了去掉重复元素
        Set<Integer> treeSet = new TreeSet<>();
        for (int num : nums) 
            treeSet.add(num);

        // 2、把排名存在哈希表里方便查询
        Map<Integer, Integer> rankMap = new HashMap<>();
        int rankIndex = 1;
        for (Integer num : treeSet) 
            rankMap.put(num, rankIndex++);

        int count = 0;
        // 在树状数组内部完成前缀和的计算
        // 规则是:从后向前,先给对应的排名 + 1,再查询前缀和
        FenwickTree fenwickTree = new FenwickTree(rankMap.size());

        for (int i = len - 1; i >= 0; i--) {
            int rank = rankMap.get(nums[i]);
            fenwickTree.update(rank, 1);
            count += fenwickTree.query(rank - 1);
        }
        return count;
    }

    private class FenwickTree {
        private int[] tree;
        private int len;

        public FenwickTree(int n) {
            this.len = n;
            tree = new int[n + 1];
        }

        // 单点更新:将 index 这个位置 + delta
        public void update(int i, int delta) {
            // 从下到上,最多到 size,可以等于 size
            while (i <= this.len) {
                tree[i] += delta;
                i += lowbit(i);
            }
        }

        // 区间查询:查询小于等于 tree[index] 的元素个数
        // 查询的语义是「前缀和」
        public int query(int i) {
            // 从右到左查询
            int sum = 0;
            while (i > 0) {
                sum += tree[i];
                i -= lowbit(i);
            }
            return sum;
        }

        public int lowbit(int x) {
            return x & (-x);
        }
    }
}

滑动窗口的最大值

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
在这里插入图片描述

单调队列

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int len = nums.length;
        if (len == 0)
            return new int[0];
        Deque<Integer> queue = new LinkedList<>();
        int[] result = new int[len - k + 1];
        for (int i = 0; i < len; i++) {
            while (!queue.isEmpty() && nums[queue.peekLast()] <= nums[i])
                queue.pollLast();
            queue.addLast(i);
            if (i - k >= queue.peek())
                queue.poll();
            if (i >= k - 1)
                result[i - k + 1] = nums[queue.peek()];
        }
        return result;
    }
}

队列的最大值

单调队列

class MaxQueue {
    private Queue<Integer> data;
    private Deque<Integer> max;
    public MaxQueue() {
        data = new LinkedList<>();
        max = new LinkedList<>();
    }
    
    public int max_value() {
        return max.isEmpty() ? -1 : max.peekFirst();
    }
    
    public void push_back(int value) {
        data.offer(value);
        while (!max.isEmpty() && max.peekLast() < value)
            max.pollLast();
        max.addLast(value);
    }
    
    public int pop_front() {
        if (data.isEmpty())
            return -1;
        if (max.peekFirst().equals(data.peek()))
            max.pollFirst();
        return data.poll();
    }
}

股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

动态规划

buy当前花费最少买入股票,sell当前收益最高卖出股票。

class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        int buy = Integer.MIN_VALUE, sell = 0;
        for (int i = 0; i < len; i++) {
            buy = Math.max(buy, -prices[i]); 
            sell = Math.max(sell, buy + prices[i]);
        }
        return sell;
    }
}

n个骰子的点数

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

示例 1:
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]

示例 2:
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

动态规划

在这里插入图片描述

class Solution {
    public double[] dicesProbability(int n) {
        double[] dp = new double[] {1.0};
        for (int i = 1; i <= n; i++) {
            double[] tmp = new double[5 * i + 1];
            for (int j = 0; j < dp.length; j++) {
                for (int k = 0; k < 6; k++) 
                    tmp[j + k] += dp[j] / 6.0; 
            }
            dp = tmp;
        }
        return dp;
    }
}

构建乘积数组

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。。

示例:
输入: [1,2,3,4,5]
输出: [120,60,40,30,24]

两次遍历,左乘积*右乘积

class Solution {
    public int[] constructArr(int[] a) {
        int len = a.length;
        if (len == 0)
            return new int[0];
        int[] result = new int[len];
        result[0] = 1;
        for (int i = 1; i < len; i++)
            result[i] = result[i - 1] * a[i - 1];

        int rightProduct = 1;
        for (int i = len - 2; i >= 0; i--) {
            rightProduct *= a[i + 1];
            result[i] *= rightProduct;
        }
            
        return result;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值