刷题代码

LeetCode代码记录

总结各种算法模板

循环求数位和

int sum(int x) {
    int s = 0;
    
    while (x != 0) {
        s += x % 10;
        x /= 10;
    }
    
    return s;
}

快速幂

// 快速幂(取模):注意这里用long
long fastExponentiation(long x, long y) {
    long result = 1;

    while (y != 0) {
        if (y % 2 != 0) {	// (y & 1) != 0
            result *= x;
            result %= 1000000007;
        }

        x *= x;
        x %= 1000000007;
        y /= 2;		// y >>>= 1;
    }

    return result;
}

二分查找

归并排序

快速排序

双指针

深度优先遍历

剑指 Offer

3 、数组中重复的数字

找出数组中重复的数字。

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

示例 1:

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

/*
 * 方法一:原地哈希,时间复杂度O(n),空间复杂度O(1).
 * 因为数组中的元素都是在0-n-1之间,所以我们让位置i的地方存储元素i。
 * 如果位置i的元素不是i是j的话,我们就把j放到其应该在的位置j。如果
 * 归位时发现位置j处的元素nums[j]和j相同,则说明重复了
 */
class Solution {
    public int findRepeatNumber(int[] nums) {

        for (int i = 0; i < nums.length; i++) {
            // 一直交换,直至该位置归位
            while (nums[i] != i) {
                int m = nums[i];

                if (nums[m] != m) {
                    nums[i] = nums[m];
                    nums[m] = m;
                } else {
                    return m;
                }
            }
        }

        return -1;
    }
}

4、二维数组的查找

在一个 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。

/*
 * 每次比较target和剩余矩阵右上角的元素大小
 * 右上角元素等于target,true
 * 右上角元素大于target,右上角这一列都大于target,去除这一列
 * 右上角元素小于target,右上角这一行都小于target,去除这一行
 */
class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix.length == 0) {
            return false;
        }
        
        int m = 0;  // m表示右上角元素的行号,此处初始化
        int n = matrix[0].length - 1;  // n表示右上角元素的列号

        while (m <= matrix.length - 1 && n >= 0) {
            int current = matrix[m][n];

            if (current == target) {
                return true;
            } else if (current > target) {
                n--;
            } else {
                m++;
            }
        }

        return false;   // 没有找到返回false
    }
}

5、替换空格

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

示例 1:

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

// 使用字符数组存储
class Solution {
    public String replaceSpace(String s) {
        int len = s.length();
        char[] arr = new char[3 * len]; // 保证能存储所以字符,不会溢出
        int curLen = 0; // curLen存储当前字符数组的大小

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            if (c == ' ') {
                arr[curLen++] = '%';
                arr[curLen++] = '2';
                arr[curLen++] = '0';
            } else {
                arr[curLen++] = c;
            }
        }

        String str = new String(arr, 0, curLen);

        return str;
    }
}
// StringBuilder
class Solution {
    public String replaceSpace(String s) {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            if (c == ' ') {
                sb.append("%20");
            } else {
                sb.append(c);
            }
        }

        return sb.toString();
    }
}

6、从尾到头打印链表

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

示例 1:

输入:head = [1,3,2]
输出:[2,3,1]
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        int count = 0;
        ListNode curNode = head;

        while (curNode != null) {
            curNode = curNode.next;
            count++;
        }

        int[] result = new int[count];
        curNode = head;

        for (int i = count - 1; i >= 0; i--) {
            result[i] = curNode.val;
            curNode = curNode.next;
        }

        return result;
    }
}

7、重建二叉树

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

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:

3

/
9 20
/
15 7

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private Map<Integer, Integer> indexMap;

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if (preorder.length == 0 || inorder.length == 0) {
            return null;
        }

        indexMap = new HashMap<Integer, Integer>();
        for (int i = 0; i < inorder.length; i++) {
            indexMap.put(inorder[i], i);
        }

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

    TreeNode build(int[] preorder, int[] inorder, int startPre, int endPre, int startIn, int endIn) {
        if (startPre > endPre) {
            return null;
        }

        int num = preorder[startPre];   // 作为根节点
        TreeNode root = new TreeNode(num);
        
        // 在中序序列中寻找根节点
        int i = indexMap.get(num);  // i存放中序序列根节点位置
        int lenOfLeft = i - startIn;  // 左子树的元素个数
        // for (i = startIn; i <= endIn; i++) {
        //     if (inorder[i] == num) {
        //         lenOfLeft = i - startIn; 	// 这里要break啊,傻子
        //     }
        // }

        root.left = build(preorder, inorder, startPre + 1, startPre + lenOfLeft, startIn, i - 1);
        root.right = build(preorder, inorder, startPre + lenOfLeft + 1, endPre, i + 1, endIn);

        return root;
    }
}

9、用两个栈实现队列

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

示例 1:

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

/*
 * s1做插入,s2做头部删除整数功能,每当s2为空时就要从s1中导入元素。
 */
class CQueue {
    Stack<Integer> s1;
    Stack<Integer> s2;

    public CQueue() {
        s1 = new Stack<>();
        s2 = new Stack<>();
    }
    
    public void appendTail(int value) {
        s1.push(value);
    }

    public int deleteHead() {
        if (s2.empty()) {
            if (s1.empty()) {
                return -1;
            } else {
                while (!s1.empty()) {
                    s2.push(s1.pop());
                }
            }
        }

        return s2.pop();
    }
}

10-1、裴波那契数列

写一个函数,输入 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

class Solution {
    public int fib(int n) {
        if (n == 0 || n == 1) {
            return n;
        }

        int x = 0;
        int y = 1;
        for (int i = 2; i <= n; i++) {
            y = x + y;
            x = y - x;
            y %= 1000000007;
            x %= 1000000007;
        }

        return y;
    }
}

10-2、青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

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

示例 1:

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

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

输入:n = 0
输出:1

/*
 * 动态规划f(n) = f(n-1) + f(n-2)
 */
class Solution {
    public int numWays(int n) {
        if (n == 0 || n == 1) {
            return 1;
        }

        int x = 1; 
        int y = 1;

        for (int i = 2; i <= n; i++) {
            y = y + x;
            x = y - x;
            y %= 1000000007;
            x %= 1000000007;
        }

        return y;
    }
}

11、旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [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

/*
 * 二分法
 * 旋转数组可分为左递增数组和右递增数组,本题目的为找到右排序数组的首个元素
 * 利用二分法,设i和j分别表示待搜索数组的左右两侧序号
 * 1、当numbers[mid] > numbers[j]时,numbers[mid]在左排序数组内,则最小元素一定在不包括右侧,即i = mid + 1
 * 2、当numbers[mid] < numbers[j]时,numbers[mid]在右排序数组内,则最小元素在包括自己的左侧,即j = mid
 * 3、当相等时,缩小j范围,不知道是在左侧还是右侧,j = j - 1
 */
class Solution {
    public int minArray(int[] numbers) {
        int i = 0; 
        int j = numbers.length - 1;
        int mid = 0;

        while (i < j) {
            mid = (i + j) / 2;

            if (numbers[mid] > numbers[j]) {
                i = mid + 1;
            } else if (numbers[mid] < numbers[j]) {
                j = mid;
            } else {
                j = j - 1;
            }
        }

        return numbers[i];
    }
}

12、矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。

[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]

但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

示例 1:

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

// 深度优先遍历+剪枝 回溯法(递归)
class Solution {
    public boolean exist(char[][] board, String word) {
        int rows = board.length;
        int cols = board[0].length;
        boolean[][] visited = new boolean[rows][cols];
        int pathLength = 0;

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (board[i][j] == word.charAt(0)) {
                    if (Search(board, word, i, j, visited, pathLength)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    boolean Search(char[][] board, String word, int i, int j, boolean[][] visited, int pathLength) {
        if (pathLength == word.length()) {
            return true;
        }
		
        // 剪枝(i,j不能超出矩阵范围,该位置不能被访问过,该位置不能不等于字符串中相应处的字符)
        if (i >= board.length || i < 0 || j < 0 || j >= board[0].length || visited[i][j] || board[i][j] != word.charAt(pathLength)) {
            return false;
        }

        visited[i][j] = true;
        boolean hasPath = false;

        hasPath = Search(board, word, i + 1, j, visited, pathLength + 1)
               || Search(board, word, i - 1, j, visited, pathLength + 1)
               || Search(board, word, i, j + 1, visited, pathLength + 1)
               || Search(board, word, i, j - 1, visited, pathLength + 1);

        visited[i][j] = false;

        return hasPath;
    }
}

13、机器人的运动范围(深度优先搜索)

地上有一个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
提示:

1 <= n,m <= 100
0 <= k <= 20

class Solution {
    public int movingCount(int m, int n, int k) {
        boolean[][] visited = new boolean[m][n];

        return moving(m, n, k, 0, 0, visited);
    }

   int moving(int m, int n, int k, int i, int j, boolean[][] visited) {
        if (i < 0 || i >= m || j < 0 || j >= n || visited[i][j] || getSum(i, j) > k) {
            return 0;
        }

        visited[i][j] = true;

        return moving(m, n, k, i - 1, j, visited) + moving(m, n, k, i + 1, j, visited)
                + moving(m, n, k, i, j - 1, visited) + moving(m, n, k, i, j + 1, visited)
                + 1;

        // visited[i][j] = false; 不用再设置回去,因为是要求统计所有能进入的格子,访问过的就不要再访问了;
    }

    // 求i,j的数位之和
    int getSum(int i, int j) {
        int sum_i = 0;
        int sum_j = 0;

        while (i != 0) {
            sum_i = sum_i + i % 10;
            i /= 10;
        }

        while (j != 0) {
            sum_j = sum_j + j % 10;
            j /= 10;
        }

        return sum_i + sum_j;
    }
}

14-1 剪绳子

给你一根长度为 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
提示:

2 <= n <= 58

/*
 * 当n <= 3时,因为m > 1,所以只好切出一米,最大乘积为n - 1
 * 当n > 3时,尽可能将绳子分成3m一段,设共有a段3m的段,剩有bm
   当b = 0时,最大乘积为3^a
   当b = 1时,因为4 > 3,所以将一段3m的和1m合并即最大乘积为3^(a-1) * 4
   当b = 2时,最大乘积为3^a * 2
 */

class Solution {

    public int cuttingRope(int n) {
        if (n <= 3) {
            return n - 1;
        }

        int a = n / 3;
        int b = n % 3;

        if (b == 0) {
            return getMul(a);
        } else if (b == 1){
            return getMul(a - 1) * 4;
        } else {
            return getMul(a) * 2;
        }
    }

    int getMul(int x) {
        int result = 1;

        while (x > 0) {
            result *= 3;
            x--;
        }

        return result;
    }
}


/*深度优先遍历:超时
class Solution {
    int maxMul = 0;
    public int cuttingRope(int n) {

        getMul(n, n, 1);

        return maxMul;
    }

    void getMul(int n, int k, int result) {
        if (k == 0) {   // 递归结束条件
            if (result > maxMul) {
                maxMul = result;
            }

            return;
        }

        if (k == n) {   // 初始时,不能一下就剪掉自身的长度
            for (int i = 1; i < k; i++) {
                getMul(n, k - i, result * i);
            }
        } else {
            for (int i = 1; i <= k; i++) {
                getMul(n, k - i, result * i);
            }
        }
    }
}
 */

14-2、剪绳子(快速幂 取模 类型强转)

给你一根长度为 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。

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

示例 1:

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

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

// 快速幂
class Solution {
    public int cuttingRope(int n) {
        if (n <= 3) {
            return n - 1;
        }

        int a = n / 3;
        int b = n % 3;

        // 注意类型强转啊,(int)后面整体也要包括在一个括号内,注意啊
        if (b == 0) {
            return (int)fastExponentiation(3, a);
        } else if (b == 1) {
            return (int)((fastExponentiation(3, a - 1) * 4) % 1000000007);
        } else {
            return (int)((fastExponentiation(3, a) * 2) % 1000000007);
        }
    }

    // 快速幂(取模):注意这里用long
    long fastExponentiation(long x, long y) {
        long result = 1;

        while (y != 0) {
            if (y % 2 != 0) {
                result *= x;
                result %= 1000000007;
            }

            x *= x;
            x %= 1000000007;
            y /= 2;
        }

        return result;
    }
}

15、二进制中1的个数(&位运算符)

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

示例 1:

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

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int count = 0;

        // while (n != 0) {
        //     count += n & 1;
        //     n >>>= 1;
        // }
        while (n != 0) {
            if ((n & 1) == 1) { // n & 1 == 1出错得加括号
                count++;
            }

            n >>>= 1;	// 无符号右移运算符,其他都是两个箭头
        }

        return count;
    }
}

16、数值的整数次方(快速幂 负整数转换)

实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

示例 1:

输入: 2.00000, 10
输出: 1024.00000
示例 2:

输入: 2.10000, 3
输出: 9.26100
示例 3:

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

class Solution {
    public double myPow(double x, int n) {
        if (x == 1 || n == 0) {
            return 1;
        }

        long b = n;     // 这一步很重要,因为n是大数

        if (b < 0) {
            x = 1 / x;
            b = -b;
        }

        return fastExponentiation(x, b);
    }

    // 快速幂(n为正数)
    double fastExponentiation(double x, long n) {
        double result = 1;

        while (n != 0) {
            if ((n & 1) != 0) {
                result *= x;
            }

            x *= x;
            n >>= 1;
        }

        return result;
    }
}

17、打印从1到最大的n位数(全排列)

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

示例 1:

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

说明:

用返回一个整数列表来代替打印
n 为正整数

// 分治思想:全排列(返回字符串)
class Solution {
    ArrayList<String> arr;

    public String[] printNumbers(int n) {
        arr = new ArrayList<>();
        String[] str = new String[n];

        for (int i = 0; i < n; i++) {
            str[i] = "0";
        }

        print(str, 0);

        return (String[])arr.toArray();
    }

    void print(String[] str, int index) {
        if (index == str.length) {  // 递归出口
            // 此时一个排列形成
            String newStr = "";
            
            for (int i = 0; i < str.length; i++) {
                newStr = newStr + str[i];
            }

            arr.add(newStr);

            return;
        }

        for (int i = 0; i <= 9; i++) {
            str[index] = "" + i;
            print(str, index + 1);
        }
    }
}
// 分治思想:全排列
class Solution {
    ArrayList<Integer> arr;

    public int[] printNumbers(int n) {
        arr = new ArrayList<>();
        String[] str = new String[n];

        for (int i = 0; i < n; i++) {
            str[i] = "0";
        }

        print(str, 0);
        int[] result = new int[arr.size() - 1];

        for (int i = 0; i < arr.size() - 1; i++) {
            result[i] = arr.get(i + 1); // 0不需要
        }

        return result;
    }

    void print(String[] str, int index) {
        if (index == str.length) {  // 递归出口
            // 此时一个排列形成
            String newStr = "";
            
            for (int i = 0; i < str.length; i++) {
                newStr = newStr + str[i];
            }

            int num = Integer.parseInt(newStr);
            arr.add(num);

            return;
        }

        for (int i = 0; i <= 9; i++) {
            str[index] = "" + i;
            print(str, index + 1);
        }
    }
}

18、删除链表的节点(新建虚拟节点指向头结点)

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

注意:此题对比原题有改动

示例 1:

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

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if (head == null) {
            return null;
        }

        ListNode temp = new ListNode(-1);   // 新建一个虚拟节点指向头结点
        temp.next = head;
        ListNode pre = temp;
        ListNode p = head;

        while (p != null) {
            if (p.val == val) {
                pre.next = p.next;
                break;
            }

            pre = p;
            p = p.next;
        }

        return temp.next;
    }
}

19、正则表达式匹配

请实现一个函数用来匹配包含’. ‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(含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 = “cab”
输出: true
解释: 因为 ‘*’ 表示零个或多个,这里 ‘c’ 为 0 个, ‘a’ 被重复一次。因此可以匹配字符串 “aab”。

class Solution {
    public boolean isMatch(String s, String p) {
        if (s.length() == 0) {  // 当模式串为空时
            if (p.length() % 2 == 1) {  // 当正则串为奇数时,一定不匹配
                return false;
            }

            // 当正则串为偶数时,只有奇数位都为*才能匹配
            int i = 1;
            while (i < p.length()) {
                if (p.charAt(i) != '*') {
                    return false;
                }

                i += 2;
            }

            return true;
        }

        // 当模式串不为空时
        if (p.length() == 0) {  // 如果此时正则串为空,则不匹配
            return false;
        }

        // 两个串都不为空时,设c1,c2,c3为模式串当前位、正则串当前位、正则串当前后一位
        char c1 = s.charAt(0);
        char c2 = p.charAt(0);
        char c3 = 'a';
        
        if (p.length() > 1) {
            c3 = p.charAt(1);
        }

        // c3是不是*分为两种情况
        if (c3 != '*') {
            if (c1 == c2 || c2 == '.') {    // 此时可以往后走一位
                return isMatch(s.substring(1), p.substring(1));
            } else {
                return false;
            }
        } else {
            if (c1 == c2 || c2 == '.') {
                return isMatch(s.substring(1), p) || isMatch(s, p.substring(2));
            } else {
                return isMatch(s, p.substring(2));
            }
        }
    }
}

21、调整数组顺序使奇数位于偶数前面(双指针)

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

示例:

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

// 双指针
class Solution {
    public int[] exchange(int[] nums) {
        int i = 0;
        int j = nums.length - 1;
        int temp = 0;

        while (i < j) {
            while (nums[i] % 2 != 0 && i < j) {	// i < j不能丢
                i++;
            }

            while (nums[j] % 2 == 0 && i < j) {
                j--;
            }
            
            temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
        }

        return nums;
    }
}

22、链表中倒数第k个节点(快慢指针)

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

示例:

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

返回链表 4->5.

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
// 快慢指针:设置两个指针p和q,p指向头和q指向第k个节点,然后再一起运动,当q到尾部时,p即为倒数第k个节点
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        if (head == null) {
            return null;
        }

        ListNode p = head;
        ListNode q = head;

        while (q.next != null && --k > 0) {
            q = q.next;
        }



        while (q.next != null) {
            p = p.next;
            q = q.next;
        }

        return p;
    }
}

24、反转链表

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

示例:

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

限制:

0 <= 节点个数 <= 5000

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
// 递归
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;
    }
}
/* 迭代法
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null) {
            return null;
        }

        if (head.next == null) {
            return head;
        }

        ListNode pre = null;
        ListNode p = head;
        ListNode tail = p.next;

        while (p != null) {
            p.next = pre;
            pre = p;
            p = tail;

            if (tail != null) {     // 防止空指针异常
                tail = tail.next;
            }
        }

        return pre;
    }
}
*/

/* 借助栈
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null) {
            return null;
        }

        Stack<ListNode> s = new Stack<>();
        ListNode p = head;

        while (p != null) {
            s.push(p);
            p = p.next;
        }

        ListNode cur = s.pop();     // 先出栈一个
        ListNode pre = cur;

        while (!s.empty()) {
            p = s.pop();
            pre.next = p;
            pre = p;
        }

        pre.next = null;    // 这个一定不能忘,而且p.next = null错误

        return cur;
    }
}
*/

25、合并两个排序的链表(双指针)

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

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:

0 <= 链表长度 <= 1000

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
// 双指针
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummyHead = new ListNode(-1);
        ListNode pre = dummyHead;

        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                pre.next = l1;
                l1 = l1.next;
                pre = pre.next;
            } else {
                pre.next = l2;
                l2 = l2.next;
                pre = pre.next;
            }
        }

        if (l1 != null) {
            pre.next = l1;
        }

        if (l2 != null) {
            pre.next = l2;
        }

        return dummyHead.next;
    }
}

26、树的子结构

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

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

例如:
给定的树 A:

 3
/ \

4 5
/
1 2
给定的树 B:

4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

示例 1:

输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:

输入:A = [3,4,5,1,2], B = [4,1]
输出:true

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
// 子结构和子树不同
class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        if (A == null || B == null) {
            return false;
        }

        boolean result = false;

        if (A.val == B.val) {       // 当A中节点和B的根节点值相同时,查找该节点是否有B子结构
            result = subStructure(A, B);
        }

        if (!result) {      // 当前节点值与B根节点值不同时,或者当前节点中不包含子结构时,遍历左右节点
            result = isSubStructure(A.left, B);
        }

        if (!result) {
            result = isSubStructure(A.right, B);
        }

        return result;
    }

    boolean subStructure(TreeNode root1, TreeNode root2) {
        if (root2 == null) {    // 这两个判断很重要
            return true;
        }

        if (root1 == null) {
            return false;
        }

        if (root1.val != root2.val) {
            return false;
        }

        return subStructure(root1.left, root2.left) && subStructure(root1.right, root2.right);
    }
}

27、二叉树的镜像

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

例如输入:

 4

/
2 7
/ \ /
1 3 6 9
镜像输出:

 4

/
7 2
/ \ /
9 6 3 1

示例 1:

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

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
// 前序遍历交换左右子节点
class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if (root == null) {
            return null;
        }

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

        mirrorTree(root.left);
        mirrorTree(root.right);

        return root;
    }
}

28、对称的二叉树

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

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

1

/
2 2
/ \ /
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

1

/
2 2
\
3 3

示例 1:

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

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

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        return symmetric(root, root);
    }

    boolean symmetric(TreeNode root1, TreeNode root2) {
        if (root1 == null && root2 == null) {
            return true;
        }

        if (root1 == null || root2 == null) {
            return false;
        }

        if (root1.val != root2.val) {
            return false;
        }

        return symmetric(root1.left, root2.right) && symmetric(root1.right, root2.left);
    }
}

29、顺时针打印矩阵

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

示例 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]

限制:

0 <= matrix.length <= 100
0 <= matrix[i].length <= 100

// 外围分成四部分(上右下左)
class Solution {
    ArrayList<Integer> array;
    public int[] spiralOrder(int[][] matrix) {
        if (matrix.length == 0) {
            return new int[0];
        }
        
        array = new ArrayList<>();

        print(matrix, 0, matrix.length - 1, 0, matrix[0].length - 1);
        
        int[] result = new int[array.size()];
        for (int i = 0; i < array.size(); i++) {
            result[i] = array.get(i);
        }

        return result;
    }

    void print(int[][] matrix, int m, int n, int p, int q) {
        if (m > n || p > q) {
            return;
        }

        for (int j = p; j <= q; j++) {
            array.add(matrix[m][j]);
        }

        if (n > m) {    // 至少两行一列
            for (int i = m + 1; i <= n; i++) {
                array.add(matrix[i][q]);
            }
        }

        if (n > m && q > p) {   // 至少两行两列
            for (int j = q - 1; j >= p; j--) {
                array.add(matrix[n][j]);
            }
        }

        if (n > m + 1 && q > p) {   // 至少三行两列
            for (int i = n - 1; i >= m + 1; i--) {
                array.add(matrix[i][m]);
            }
        }

        print(matrix, m + 1, n - 1, p + 1, q - 1);      // 递归
    }
}

30、包含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.

提示:

各函数的调用总次数不超过 20000 次

// 使用一个辅助栈
class MinStack {
    Stack<Integer> s;
    Stack<Integer> sMin;

    /** initialize your data structure here. */
    public MinStack() {
        s = new Stack<>();
        sMin = new Stack<>();
    }
    
    public void push(int x) {
        s.push(x);
        
        if (sMin.empty() || x < sMin.peek()) {      // 每次最小值都入辅助栈
            sMin.push(x);
        } else {
            sMin.push(sMin.peek());
        }
    }
    
    public void pop() {
        if (s.empty()) {
            return;
        }

        s.pop();        // 直接两个栈都出栈(因为之前每次最小值都入了辅助栈)
        sMin.pop();
    }
    
    public int top() {
        return s.peek();
    }
    
    public int min() {
        return sMin.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.min();
 */

31、栈的压入、弹出序列

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

// 将pushed依次压入栈,当栈顶数字等于出栈序列当前数字时,出栈,否则继续入栈。
class Solution {
    Stack<Integer> s;

    public boolean validateStackSequences(int[] pushed, int[] popped) {
        if (pushed.length == 0) {
            return true;
        }

        int i = 0;
        int n = pushed.length;
        s = new Stack<>();

        for (int elem: pushed) {
            s.push(elem);

            while (!s.empty() && i < n && s.peek() == popped[i]) {
                s.pop();
                i++;
            }
        }

        return s.empty();   // 栈空说明true
    }
}
// 设置一个压入序列栈,和一个弹出序列辅助栈
class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        if (pushed.length == 0 && popped.length == 0) {
            return true;
        }
        
        if (pushed.length == 0 || popped.length == 0) {
            return false;
        }

        Stack<Integer> s1 = new Stack<>();
        Stack<Integer> s2 = new Stack<>();

        // 逆序压入s1
        for (int i = pushed.length - 1; i >= 0; i--) {
            s1.push(pushed[i]);
        }

        // 遍历popped数组,找能不能以此顺序输出
        boolean flag = true;
        for (int i = 0; i < popped.length; i++) {
            flag = flag && Search(s1, s2, popped[i]);
        }

        return flag;
    }

    // 搜索
    boolean Search(Stack s1, Stack s2, int x) {
        
        // 若辅助栈栈顶是要输出的数字,则说明找到了
        if (!s2.empty()) {
            if (s2.peek().equals(x)) {
                s2.pop();
                return true;
            }
        }

        // 若在压入序列栈栈顶是要输出的数字,说明找到了,不需要再压入辅助栈了,直接弹出
        if (!s1.empty()) {
            if (s1.peek().equals(x)) {
                s1.pop();
                return true;
            }
        }

        // 如果两个栈的栈顶都没有找到,则将压入序列的元素压入到辅助栈中,直到找到该元素(序列栈为空或者没找到则返回false)
        while (!s1.empty() && !s1.peek().equals(x)) {
            s2.push(s1.pop());
        }

        // 如果没有找到
        if (s1.empty()) {
            return false;
        } else {    // 找到了
            s1.pop();
            return true;
        }
    }
}

32-1、从上到下打印二叉树

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

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

3

/
9 20
/
15 7
返回:

[3,9,20,15,7]

/**
 * 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];
        }

        Queue<TreeNode> q = new LinkedList<>();     // Queue是抽象的,LinkedList实现了Queue接口
        ArrayList<Integer> arr = new ArrayList<>();
        q.offer(root);

        while (!q.isEmpty()) {
            TreeNode cur = q.poll();
            arr.add(cur.val);

            if (cur.left != null) {
                q.offer(cur.left);
            }

            if (cur.right != null) {
                q.offer(cur.right);
            }
        }

        int[] result = new int[arr.size()];
        for (int i = 0; i < arr.size(); i++) {
            result[i] = arr.get(i);
        }

        return result;
    }
}

32-2、从上到下打印二叉树

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

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

3

/
9 20
/
15 7
返回其层次遍历结果:

[
[3],
[9,20],
[15,7]
]

提示:

节点总数 <= 1000

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> list = new ArrayList<>();

        if (root == null) {
            return list;
        }

        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);   

        while (!q.isEmpty()) {
            int len = q.size(); // 获取当前队列中节点数量
            List<Integer> list1 = new ArrayList<>();

            while (len-- > 0) {
                TreeNode cur = q.poll();
                list1.add(cur.val);

                if (cur.left != null) {
                    q.offer(cur.left);
                }

                if (cur.right != null) {
                    q.offer(cur.right);
                }
            }

            list.add(list1);
        }

        return list;
    }
}

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

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

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

 5
/ \

2 6
/
1 3
示例 1:

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

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

// 递归分治
class Solution {
    public boolean verifyPostorder(int[] postorder) {
        if (postorder.length == 0) {
            return true;
        }

        return verify(postorder, 0, postorder.length - 1);
    }

    boolean verify(int[] postorder, int p, int q) {
        if (p >= q) {
            return true;
        }

        int rootVal = postorder[q];     // 后序遍历序列最后的数字为根节点

        // 接下来找左子树和右子树的范围
        int s = p;
        for (; s < q; s++) {
            if (postorder[s] > rootVal) {
                break;
            }
        }

        int t = s;  // 此时p~s-1为左子树
        for (; t < q; t++) {       // 找一下有没有节点值小于根节点值的不符合二叉搜索树的情况
            if (postorder[t] < rootVal) {
                return false;
            }
        }

        boolean left = true; // 判断左右子树
        boolean right = true;

        if (s - 1 >= p) {       // 判断左子树的范围是否合法
            left = verify(postorder, p, s - 1);
        }

        if (s <= q - 1) {
            right = verify(postorder, s, q - 1);
        }

        return left && right;
    }
}

34、二叉树中和为某一值的路径(前序遍历)

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

示例:
给定如下二叉树,以及目标和 sum = 22,

          5
         / \
        4   8
       /   / \
      11  13  4
     /  \    / \
    7    2  5   1

返回:

[
[5,4,11,2],
[5,8,4,5]
]

提示:

节点总数 <= 10000

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
// 前序遍历
class Solution {
    List<List<Integer>> res;
    List<Integer> list;

    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        res = new ArrayList<>();

        if (root == null) {
            return res;
        }

        list = new ArrayList<>();
        preOrder(root, sum, 0);

        return res;
    }

     void preOrder(TreeNode root, int sum, int num) {
        if (root == null) {

            if (num == sum) {
                res.add(new ArrayList<>(list));
            }

            return;
        }

        num += root.val;
        list.add(root.val);

        if (num == sum && root.left == null && root.right == null) {
            res.add(new ArrayList<>(list));
        } else {
            preOrder(root.left, sum, num);
            preOrder(root.right, sum, num);
        }

        list.remove(list.size() - 1);       // 回溯法,回到上一个节点
    } 

    // void preOrder(TreeNode root, int target) {
    //     if (root == null) {
    //         return;
    //     }

    //     target -= root.val;
    //     list.add(root.val);

    //     if (target == 0 && root.left == null && root.right == null) {
    //         res.add(new ArrayList<>(list));
    //     } else {
    //         preOrder(root.left, target);
    //         preOrder(root.right, target);
    //     }

    //     list.remove(list.size() - 1);       // 回溯法,回到上一个节点
    // } 
}

35、复杂链表的复制

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

示例 1:

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

/*
// 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 null;
        }

        // 第一步,复制节点,复制节点链接在节点后面
        Node pre = head;
        Node p = head;

        while (p != null) {
            Node temp = new Node(p.val);
            p = p.next;
            pre.next = temp;
            temp.next = p;
            pre = p;
        }

        // 复制节点的random节点就是节点的random节点的后一个节点
        p = head;
        Node q = head.next;

        while (p != null) {
            if (p.random != null) {
                q.random = p.random.next;
            }

            if (q.next == null) {
                break;
            }
            
            p = q.next;
            q = p.next;
        }

        // 拆分链表
        p = head;
        Node markFirst = head.next; // 标记复制链表的首节点
        q = markFirst;

        while (p != null) {
            if (q.next == null) {       // 最后一个节点时,设置末尾指向过过过null,不然报NullPointerException异常
                p.next = null;
                q.next = null;
                break;
            }

            p.next = q.next;
            p = p.next;
            q.next = p.next;
            q = q.next;
        }
        
        return markFirst;
    }
}

36、二叉搜索树与双向链表

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

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

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

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

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

/*
// 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 {
    Node lastNodeInList = null;

    public Node treeToDoublyList(Node root) {
        if (root == null) {
            return null;
        }

        toDoublyList(root);
        Node last = lastNodeInList;

        while (lastNodeInList.left != null) {
            lastNodeInList = lastNodeInList.left;
        }

        Node first = lastNodeInList;
        lastNodeInList.left = last;
        last.right = lastNodeInList;

        return first;
    }

    void toDoublyList(Node root) {
        if (root == null) {
            return;
        }
        
        if (root.left != null) {
            toDoublyList(root.left);
        }

        root.left = lastNodeInList;

        if (lastNodeInList != null) {
            lastNodeInList.right = root;
        }

        lastNodeInList = root;      // 此时序列中最后一个节点改变为当前节点

        if (root.right != null) {
            toDoublyList(root.right);
        }

        
    }
}

37、序列化二叉树

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

示例:

你可以将以下二叉树:

1

/
2 3
/
4 5

序列化为 “[1,2,3,null,null,4,5]”

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    String NULL = "#";	// 代表空null
    String SEP = ",";	// 分隔符,

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if (root == null) {
            return NULL;
        }

        StringBuilder res = new StringBuilder();

        newSerialize(root, res);

        return res.toString();
    }

    public void newSerialize(TreeNode root, StringBuilder res) {
        if (root == null) {
            res.append(NULL).append(SEP);
            return;
        }

        res.append(root.val).append(SEP);

        newSerialize(root.left, res);
        newSerialize(root.right, res);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if (data.length() == 0) {
            return null;
        }

        LinkedList<String> cur = new LinkedList<>();

        // 除去,
        for (String e: data.split(SEP)) {
            cur.add(e);
        }

        return newDeserialize(cur);
    }

    public TreeNode newDeserialize(LinkedList<String> cur) {
        if (cur.isEmpty()) {
            return null;
        }

        String temp = cur.removeFirst();

        if (temp.equals(NULL)) {
            return null;
        }
        
        TreeNode root = new TreeNode(Integer.parseInt(temp));

        root.left = newDeserialize(cur);
        root.right = newDeserialize(cur);

        return root;
    }
}

// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));

38、字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

示例:

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

class Solution {
public:

    vector<string> res;
    vector<bool> visited;
    string path;

    void dfs(string& s, int u) {
        if (u == s.size()) {
            res.push_back(path);
            return;
        }

        for (int i = 0; i < s.size(); i++) {
            if (i > 0 && s[i - 1] == s[i] && visited[i - 1]) continue;

            if (!visited[i]) {
                path.push_back(s[i]);
                visited[i] = true;
                dfs(s, u + 1);
                path.pop_back();
                visited[i] = false;
            }
        }
    }

    vector<string> permutation(string s) {
        visited = vector<bool>(s.size(), false);
        sort(s.begin(), s.end());

        dfs(s, 0);

        return res;
    }
};


// class Solution {
// public:

//     vector<string> res;

//     // 分别固定0 ~ n - 1位
//     void dfs(string s, int x) {
//         if (x == s.size()) {
//             res.push_back(s);

//             return;
//         }

//         set<int> st;

//         // 分别用x ~ n - 1位与x位置交换
//         for (int i = x; i < s.size(); i++) {
//             if (st.find(s[i]) != st.end()) continue;    // 保证每种字符只在此处出现一次,剪枝

//             st.insert(s[i]);    // 将s[i]而不是s[x]插入,因为s[i]会替换s[x]
//             swap(s[i], s[x]);   // 交换
//             dfs(s, x + 1);
//             swap(s[i], s[x]);   // 还原
//         }
//     }

//     vector<string> permutation(string s) {
//         dfs(s, 0);

//         return res;
//     }
// };

39、数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

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

示例 1:

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

限制:

1 <= 数组长度 <= 50000

/* 
 * 设置mark标记多数元素,并为其设置count计算次数
 * 初始时,mark为nums[0],count为1.
 * 遍历数组,
        当前元素与mark相同时,count + 1
 *      当前元素与mark不同时,count - 1。当count为0时,mark改变为当前元素,count重新设置为1.
 * 最后,mark即为多数元素
 */
class Solution {
    public int majorityElement(int[] nums) {
        int mark = nums[0];
        int count = 1;

        for (int i = 1; i < nums.length; i++) {
            if (nums[i] == mark) {
                count++;
            } else {
                count--;

                if (count == 0) {
                    mark = nums[i];
                    count = 1;
                }
            }
        }

        return mark;
    }
}

40、最小的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]

限制:

0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000

// 优先队列/最大堆,队里存最小的四个数字降序,队头是四个数字中最大值
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }

        Queue<Integer> q = new PriorityQueue<>((x, y) -> y - x);

        for (int i = 0; i < arr.length; i++) {
            if (q.size() < k) {
                q.offer(arr[i]);
            } else {
                if (arr[i] < q.peek()) {
                    q.poll();
                    q.offer(arr[i]);
                }
            }
        }

        int[] result = new int[k];
        for (int i = 0; i < k; i++) {
            result[i] = q.poll();
        }

        return result;
    }
}

41、数据流中的中位数(最大堆,最小堆两个辅助的优先队列)

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

例如,

[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]

/* 最大堆和最小堆
 * 使用优先队列建立最大堆和最小堆
 * 设两个优先队列为q1和q2,q1队头开始降序q2队头开始升序
 * 1、优先往q1存储元素,并保证q1元素个数等于q2元素个数或多1个元素
 * 2、最后,q1 q2个数相同时,两个队列的队头元素的平均数即为中位数;个数不同时,q1队头元素即为中位数
 */
class MedianFinder {
public:

    priority_queue<int, vector<int>, less<int> > q1;     // priority_queue默认大顶堆,降序
    priority_queue<int, vector<int>, greater<int> > q2;
    /** initialize your data structure here. */
    MedianFinder() {

    }
    
    void addNum(int num) {
        q2.push(num);       // 从右边过滤一下,然后加入左边
        q1.push(q2.top());
        q2.pop();

        if (q1.size() > q2.size() + 1) {        // 当左边个数比右边个数多了不止1个时,左侧出队一个加入右边
            q2.push(q1.top());
            q1.pop();
        }
    }
    
    double findMedian() {
        if (q1.empty()) return -1;

        if (q1.size() == q2.size()) {
            return (double)(q1.top() + q2.top()) / 2;       // (double)((q1.top() + q2.top()) / 2)是错的,因为里面已经取整了
        }

        return (double)q1.top();
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

42 连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

示例1:

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

提示:

1 <= arr.length <= 10^5
-100 <= arr[i] <= 100

// 动态规划:f(i)表示以第i个数字结尾的子数组的和的最大值
class Solution {
    public int maxSubArray(int[] nums) {
        int maxNum = nums[0];   // 设置maxNum为所有子数组的和的最大值,初始时为nums[0]
        int[] f = new int[nums.length]; // f[]数组用来记录以第i个数字结尾的子数组的和的最大值
        //f[0] = nums[0];

        for (int i = 0; i < nums.length; i++) {

            // 当i = 0或者f[i - 1] < 0时,f[i - 1] + nums[i] <= nums[i]
            if (i == 0 || f[i - 1] < 0) {
                f[i] = nums[i];
            } else {
                f[i] = f[i - 1] + nums[i];
            }

            if (maxNum < f[i]) {
                maxNum = f[i];
            }
        }

        return maxNum;
    }
}

43 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

限制:

1 <= n < 2^31

// 利用递归(以21345为例,分为1-1345 1346-21345)
class Solution {
    public int countDigitOne(int n) {

        int first = n;  // 设置first为最高位的数字
        int lenOfN = 1; // lenOfN记录n的位数
        while (first > 9) {
            first /= 10;
            lenOfN++;
        }

        if (lenOfN == 1 && first == 0) {
            return 0;
        }

        if (lenOfN == 1 && first > 0) {
            return 1;
        }

        // 最高位大于1时,10000-19999中万位的1总数为10^4
        int numFirstDigits = 0;  //记录最高位1的总数
        if (first > 1) {
            numFirstDigits = powerBase10(lenOfN - 1);
        } else if (first == 1) {
            numFirstDigits = n - powerBase10(lenOfN - 1) + 1; // 假如最高位为1,例如12345(万位的1的个数为2345 + 1)
        }

        // 接下来求除最高位1之外的位为1的总数
        int numOtherDigits = first * (lenOfN - 1) * powerBase10(lenOfN - 2);
        // 递归求解除去最高位的n(即1345)
        int numRecursive = countDigitOne(n - first * powerBase10(lenOfN - 1));

        return numFirstDigits + numOtherDigits + numRecursive;
    }

    int powerBase10(int n) {
        int result = 1;
        while (n > 0) {
            result *= 10;
            n--;
        }

        return result;
    }
}

44 数字序列中某一位的数字

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

请写一个函数,求任意第n位对应的数字。

示例 1:

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

输入:n = 11
输出:0

限制:0 <= n < 2^31

class Solution {
    public int findNthDigit(int n) {
        if (n >= 0 && n <= 9) {
            return n;
        }

        int k = 1;
        int countOfK = 10;  // 利用countofK记录k位数中总共的数字总数,初始时从1位开始
        while (n > countOfK) {
            n -= countOfK;
            k++;
            countOfK = sumOfK(k);
        }

        // 这样第n个数就在k位数中
        // 接下来,在k位数中寻找
        int r = n % k;
        int temp = n / k;

        // k(k > 1)位数起始的数为10 ^ (k - 1)
        int first = (int)Math.pow(10, k - 1);
        int last = first + temp;    // r是0 ~ k - 1

        int result = last / (int)(Math.pow(10, k - r - 1)) % 10;

        return result;
    }

    // k(k >= 2)位数总共的数字总数
    int sumOfK(int k) {
        return (int)(9 * Math.pow(10, k - 1) * k);
    }
}

45 把数组排成最小的数

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

示例 1:

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

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

提示:

0 < nums.length <= 100
说明:

输出结果可能非常大,所以你需要返回一个字符串而不是整数
拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0

// 利用排序函数,排序规则(字符串的排序规则)
class Solution {
    public String minNumber(int[] nums) {
        List<String> str = new ArrayList<>();

        for (int num: nums) {
            str.add(String.valueOf(num));
        }

        str.sort((a, b) -> (a + b).compareTo(b + a));

        // StringBuilder线程不安全且性能略高,StringBuffer安全
        StringBuilder result = new StringBuilder();
        for (String s: str) {
            result.append(s);
        }

        return result.toString();
    }
}

46 把数字翻译成字符串

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

示例 1:

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

// 递归表达式设f[i]为以i为开始的数字的翻译数目
// f[i] = f[i + 1] + get(i, i + 1) * f[i + 2]
// get(i, i + 1),如果第i i + 1拼接的元素在10-25之间返回1,否则返回0
// 递归从顶向下会有很多重复子问题,所以本题从小向顶
class Solution {
    public int translateNum(int num) {
        if (num >= 0 && num <= 9) {
            return 1;
        }
        
        // 首先将num的各位传入数组中
        ArrayList<Integer> arr = new ArrayList<>();

        while (num != 0) {
            arr.add(num % 10);
            num /= 10;
        }

        Collections.reverse(arr);  // arr中是逆序的,翻转一下

        int[] result= new int[arr.size()];

        result[arr.size() - 1] = 1;
        result[arr.size() - 2] = 1 + getTwo(arr.get(arr.size() - 2), arr.get(arr.size() - 1));

        for (int i = arr.size()- 3; i >= 0; i--) {
            result[i] = result[i + 1] + getTwo(arr.get(i), arr.get(i + 1)) * result[i + 2];
        }

        return result[0];
    }

    int getTwo(int a, int b) {
        if (a * 10 + b <= 25 && a * 10 + b >= 10) {
            return 1;
        }

        return 0;
    }
}

47 礼物的最大价值

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

示例 1:

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

提示:

0 < grid.length <= 200
0 < grid[0].length <= 200

// 动态规划(dp[i][j]表示以这个元素为终点的最多价值)
// 利用dp存储[i][j]的左边一行0~j-1元素和上面的j~cols的元素
class Solution {

    public int maxValue(int[][] grid) {

        int[] dp = new int[grid[0].length];

        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                int left = 0;
                int up = 0;

                if (j > 0) {
                    left = dp[j - 1];
                }

                if (i > 0) {
                    up = dp[j];
                }
                dp[j] = Math.max(left, up) + grid[i][j];

            }
        }

        return dp[grid[0].length - 1];
    }
}
// 深度优先遍历:这题会超时
class Solution {
    private int maxResult = 0;
    public int maxValue(int[][] grid) {
        int rows = grid.length;
        int cols = grid[0].length;
        boolean[][] visited = new boolean[rows][cols];

        getMaxValue(grid, visited, 0, 0, 0);

        return maxResult;
    }

    void getMaxValue(int[][] grid, boolean[][] visited, int i, int j, int maxNum) {
        // 结束条件
        if (i >= grid.length || j >= grid[0].length) {
            if (maxNum > maxResult) {
                maxResult = maxNum;
            }

            return;
        }

        maxNum += grid[i][j];
        visited[i][j] = true;

        getMaxValue(grid, visited, i + 1, j, maxNum);
        getMaxValue(grid, visited, i, j + 1, maxNum);

        // 结束后,visited[i][j] = false;
        visited[i][j] = false;
    }
}

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

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

示例 1:

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

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

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

提示:

s.length <= 40000

// 动态规划:设f(i)为以i为下标的元素为结尾的最长的不包含重复字符的字符串长度
/* 一、当前i的字符没有出现过:f(i) = f(i - 1) + 1
 * 二、当前i的字符出现过:
    设上一次出现过的字符到现在的距离为d
    1、若d <= f(i - 1),即重复的字符在i-1的最长串上时,则f(i) = d
    2、若d > f(i - 1),则f(i) = f(i - 1) + 1
 */

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if (s.length() == 0) {
            return 0;
        }
        
        HashMap<Character, Integer> map = new HashMap<>();    // 利用HashMap存储,字符-下标    

        int[] dp = new int[s.length()]; // 记录f(i)
        dp[0] = 1;
        map.put(s.charAt(0), 0);

        for (int i = 1; i < s.length(); i++) {
            // 判断该字符有没有出现过
            if (!map.containsKey(s.charAt(i))) {
                dp[i] = dp[i - 1] + 1;
                map.put(s.charAt(i), i);
                continue;
            }

            int index = map.get(s.charAt(i));  // 得到重复字符上一个的坐标
            map.put(s.charAt(i), i);    // 必须放上一行后面,否则就被更新了取错了
            int d = i - index;  // 得到两者的距离

            if (d <= dp[i - 1]) {
                dp[i] = d;
            } else {
                dp[i] = dp[i - 1] + 1;
            }
        }

        int maxResult = 0;
        for (int i = 0; i < dp.length; i++) {
            if (dp[i] > maxResult) {
                maxResult = dp[i];
            }
        }

        return maxResult;
    }
}

49 丑数

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

示例:

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

1 是丑数。
n 不超过1690。

// 空间换时间(建立一个数组存放所有的丑数)
// 每个丑数都是一个丑数*2 *3 *5(2 3 5可多次乘)得来的
// 求一个丑数*2 *3 *5得到的数中第一个大于M(数组中最大的丑数)的最小值
// 可选一个数作为x*2 左边小于M 右边大于M,这样就不用了一个个遍历数组中的丑数了(找到一个*2一次就大于M的数)
class Solution {
    public int nthUglyNumber(int n) {
        ArrayList<Integer> arr = new ArrayList<>();
        arr.add(1);
        int M2 = 0;
        int M3 = 0;
        int M5 = 0;

        while (arr.size() < n) {
            int newM = Math.min(Math.min(arr.get(M2) * 2, arr.get(M3) * 3), arr.get(M5) * 5);
            arr.add(newM);

            // 利用三指针,第一个丑数是1,以后的丑数都是基于前面的小丑数分别乘2 3 5构成的,我们每次添加进去一个当前计算出来的
            // 三个丑数的最小的一个,并且是谁计算的,谁指针就后移一位

            if (arr.get(M2) * 2 == newM) {
                M2++;
            }

            if (arr.get(M3) * 3 == newM) {
                M3++;
            }

            if (arr.get(M5) * 5 == newM) {
                M5++;
            }
        }

        return arr.get(n - 1);
    }
}

50 第一个只出现一次的字符

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

示例:

s = “abaccdeff”
返回 “b”

s = “”
返回 " "

限制:

0 <= s 的长度 <= 50000

class Solution {
    public char firstUniqChar(String s) {
        HashMap<Character, Integer> map = new HashMap<>();

        for (int i = 0; i < s.length(); i++) {
            if (!map.containsKey(s.charAt(i))) {
                map.put(s.charAt(i), 1);
                continue;   // 老是这里忘了加
            }

            map.put(s.charAt(i), map.get(s.charAt(i)) + 1);
        }

        for (int i = 0; i < s.length(); i++) {
            if (map.get(s.charAt(i)) == 1) {
                return s.charAt(i);
            }
        }

        return ' ';
    }
}

51 数组中的逆序对

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

示例 1:

输入: [7,5,6,4]
输出: 5

限制:

0 <= 数组长度 <= 50000

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

        int[] copy = new int[nums.length];
        int[] temp = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            copy[i] = nums[i];
        }

        return getReversePairs(copy, 0, nums.length - 1, temp);
    }

    int getReversePairs(int[] nums, int start, int end, int[] temp) {
        if (start == end) {
            return 0;
        }

        int mid = start + (end - start) / 2;
        int leftPairs = getReversePairs(nums, start, mid, temp);
        int rightPairs = getReversePairs(nums, mid + 1, end, temp);

        // 如果左边最大值小于等于右边最小值,就不用排序找逆序对了
        if (nums[mid] <= nums[mid + 1]) {
            return leftPairs + rightPairs;
        }

        int curPairs = mergeAndCount(nums, start, mid, end, temp);

        return leftPairs + rightPairs + curPairs;
    }

    int mergeAndCount(int[] nums, int start, int mid, int end, int[] temp) {
        int count = 0;
        int i = start;
        int j = mid + 1;
        int k = 0;

        while (i <= mid && j <= end) {

            if (nums[i] <= nums[j]) {
                temp[k++] = nums[i];
                i++;
            } else {
                count = count + mid - i + 1;    // 这个得想清楚
                temp[k++] = nums[j];
                j++;
            }
        }

        while (i <= mid) {
            temp[k++] = nums[i++];
            //count = count + end - mid;
        }

        while (j <= end) {
            temp[k++] = nums[j++];
        }

        // 这一步很重要
        for (int t = 0; t < k; t++) {
            nums[start + t] = temp[t];
        }

        return count;
    }
}
// 利用归并排序,不断分成子数组,子数组进行排序
class Solution {

    public int reversePairs(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }

        int[] copy = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            copy[i] = nums[i];
        }
        return getReversePairs(nums, copy, 0, nums.length - 1);
    }

    int getReversePairs(int[] nums, int[] copy, int start, int end) {
        if (start == end) {
            copy[start] = nums[start];
            return 0;
        }

        int len = (end - start) / 2;
        // 这里copy nums位置互换就是因为copy中之前已经排序好的,要复制到nums中,这样往上一级排序时,这一级才是有序的
        // 之前只是把排序的结果存在了tmp临时数据里,现在要把这部分复制到原来的arr数组对应的位置,这样在下一次用到arr的时候才          // 我们这次排好序的
        // 而copy处放nums是因为需要初始化辅助数组为原始数组,只是起到辅助作用
        int left = getReversePairs(copy, nums, start, start + len);
        int right = getReversePairs(copy, nums, start + len + 1, end);

        // 令i j分别指向两个数组的末尾
        int i = start + len;
        int indexCopy = end;    // 这个很重要
        int j = end;
        int count = 0;

        while (i >= start && j >= start + len + 1) {
            if (nums[i] > nums[j]) {
                count += j - start - len; // 若左边的数大于右边的数,则start + len + 1 ~ j都与其构成逆序
                copy[indexCopy--] = nums[i--];
            } else {
                copy[indexCopy--] = nums[j--];
            }
        }

        while (i >= start) {
            copy[indexCopy--] = nums[i--];
        }

        while (j >= start + len + 1) {
            copy[indexCopy--] = nums[j--];
        }

        return left + right + count;
    }
}

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

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

如下面的两个链表:

在节点 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。

注意:

如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }

        int lenA = 1;
        int lenB = 1;
        ListNode p = headA;
        ListNode q = headB;

        while (p.next != null) {
            p = p.next;
            lenA++;
        }

        while (q.next != null) {
            q = q.next;
            lenB++;
        }

        int d = lenA - lenB;
        
        if (d >= 0) {
            return getNode(headA, headB, d);
        } else {
            return getNode(headB, headA, -d);
        }
        
    }

    // 默认A至少B一样长
    ListNode getNode(ListNode A, ListNode B, int d) {
        while (d-- > 0) {
            A = A.next;
        }

        while (A != null && B != null) {
            if (A == B) {
                return A;
            }
            A = A.next;
            B = B.next;
        }

        return null;
    }
}

53-1 在排序数组中查找数字

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

示例 1:

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

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

限制:

0 <= 数组长度 <= 50000

// 二分查找(找到第一个t)
/*
 * 1、nums[mid] > t,t在左侧数组
 * 2、nums[mid] < t,t在右侧数组
 * 3、nums[mid] == t,
 * 1)nums[mid - 1] < t,在nums[mid]就是第一个t
 * 2)nums[mid - 1] = t,继续在左侧数组找第一个t
 */
class Solution {
    private int count = 0;

    public int search(int[] nums, int target) {
        if (nums.length == 0) {
            return 0;
        }

        searchTarget(nums, target, 0, nums.length - 1);

        return count;
    }

    void searchTarget(int[] nums, int target, int start, int end) {
        if (start > end) {
            return;
        }

        int mid = (start + end) / 2;

        if (nums[mid] > target) {
            searchTarget(nums, target, start, mid - 1);
        } else if (nums[mid] < target) {
            searchTarget(nums, target, mid + 1, end);
        } else {
            if (mid - 1 >= start) {
                // 此时就是第一个t
                if (nums[mid - 1] < target) {
                    // 以此下标遍历计算
                    count = getTimes(nums, target, mid);
                } else{
                    // 第一个t在左侧数组
                    searchTarget(nums, target, start, mid - 1);
                }
            } else {
                count = getTimes(nums, target, mid);         
            }
            
        }
    }

    int getTimes(int[] nums, int target, int start) {
        int result = 1;

        for (int i = start + 1; i <= nums.length - 1; i++) {
            if (nums[i] != target) {
                break;
            }
            result++;
        }

        return result;
    }
}

53-2 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

限制:

1 <= 数组长度 <= 10000

// 二分查找
class Solution {
    private int result = 0;

    public int missingNumber(int[] nums) {

        if (nums.length == 1 && nums[0] == 0) {
            return 1;
        }

        if (nums.length == 1 && nums[0] == 1) {
            return 0;
        }

        if (nums[nums.length - 1] == nums.length - 1) {
            return nums.length;
        }

        getMissingNumber(nums, 0, nums.length - 1);

        return result;
    }

    void getMissingNumber(int[] nums, int start, int end) {
        if (start > end) {
            return;
        }

        int mid = start + (end - start) / 2;

        // 数组中只有一个数字的情况需要考虑(否则下面的nums[mid - 1]就会越界)
        if (mid == start && nums[mid] != mid) {
            result = mid;
            return;
        }

        if (nums[mid] == mid) {
            getMissingNumber(nums, mid + 1, end);
        } else if (nums[mid] > mid && nums[mid - 1] != mid - 1) {
            getMissingNumber(nums, start, mid - 1);
        } else {
            result = mid;
        }
    }
}

54 二叉搜索树的第k大节点

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

示例 1:

输入: root = [3,1,4,null,2], k = 1
3
/
1 4

2
输出: 4
示例 2:

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

限制:

1 ≤ k ≤ 二叉搜索树元素个数

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    int count = 0;
    int result = 0;

    public int kthLargest(TreeNode root, int k) {

        if (root == null) {
            return -1;
        }

        getkthLargest(root, k);

        return result;
    }

    void getkthLargest(TreeNode root, int k) {
        if (root == null) {
            return;
        }

        kthLargest(root.right, k);

        count++;
        if (count == k) {
            result = root.val;
            return;
        }

        kthLargest(root.left, k);
    }
}

55-1 二叉树的深度

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

例如:

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

    3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

提示:

节点总数 <= 10000

class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }

        int left = maxDepth(root.left);
        int right = maxDepth(root.right);

        return Math.max(left, right) + 1;
    }
}

55-2 平衡二叉树

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

示例 1:

给定二叉树 [3,9,20,null,null,15,7]
返回 true 。

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]
返回 false 。

限制:

1 <= 树的结点个数 <= 10000

// 从底向上:通过利用求每个节点深度的方式,判断节点是否平衡,然后递推得到整棵树是否平衡
// 如果先序遍历每个节点,然后再调用求节点深度的函数,会导致重复遍历的问题,效率变低
class Solution {
    boolean flag = true;
    public boolean isBalanced(TreeNode root) {
        getDepth(root);

        return flag;
    }

    int getDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }

        int left = getDepth(root.left);
        int right = getDepth(root.right);

        if (left - right > 1 || left - right < -1) {
            flag = flag && false;
        }

        return Math.max(left, right) + 1;
    }
}

56-1 数组中数字出现的次数

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

class Solution {
    public int[] singleNumbers(int[] nums) {
        int x = 0;  // 用于记录A B的异或结果

        /*
         * 得到A^B的结果
         * 基于异或运算的以下几个性质
         * 1、交换律
         * 2、结合律
         * 3、对于任何数x,都有x^x=0和x^0=x
         */
 
        for (int val: nums) {
            x ^= val;
        }

        int flag = x & (-x);    // 得到最低位的1
        // 而我们所需要的做到的是:利用这个1来进行分组,也就是做到将A和B区分开
        // 前面已经知道,x是我们需要的结果数A和B异或的结果,也就是说,x的二进制串上的任何一个1,都能成为区分A和B的条件
        // 因此我们只需要得到x上的任意一个1,就可以做到将A和B区分开

        int res = 0;    // 用于记录两个只出现一次的数A B的其中一个

        // 分组操作
        for (int val: nums) {
            /* 根据二进制位上的那个1进行分组
             * 需要注意的是,分组的结果必然是相同的数在相同的组,且还有一个结果数
             * 因此每组的数再与res一路异或下去,最终会得到那个结果数A或B
             * 且由于异或运算具有自反性,因此只需要得到其中一个数即可
             */
            if ((flag & val) != 0) {
                res ^= val;
            }
        }

        // 利用先前的x进行异或运算得到另一个,即利用自反性
        return new int[]{res, x ^ res};
    }
}

56-2 数组中出现的次数

在一个数组 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

class Solution {
    public int singleNumber(int[] nums) {
        int ones = 0;
        int twos = 0;

        for (int num: nums) {
            ones = ones ^ num & ~twos;
            twos = twos ^ num & ~ones;
        }

        return ones;
    }
}

57 和为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]

/*
 * 双指针,分别指向头尾
 * 1、若指向的两个数字等于s,则找到
 * 2、若小于s,则左指针向右移动一个位置
 * 3、若大于s,则右指针向左移动一个位置
 */
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int p = 0;
        int q = nums.length - 1;

        while (p < q) {

            if (nums[p] + nums[q] == target) {
                return new int[]{nums[p], nums[q]};
            } else if (nums[p] + nums[q] < target) {
                p++;
            } else {
                q--;
            }
        }

        return new int[]{};
    }
}

57-2 和为s的连续正数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

示例 1:

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

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

限制:

1 <= target <= 10^5

/*
 * 双指针,设置一个指针为序列头部,初始化p = 1,另一个指针为序列尾部,初始化为q = 2
 * 若p-q序列和大于target,则去掉序列的头部元素(p++)
 * 若p-q序列和小于target,则尾部元素再增加一个(q++)
 */
class Solution {
    public int[][] findContinuousSequence(int target) {
        int p = 1, q = 2;
        List<List<Integer>> list = new ArrayList<>();

        while (p <= (target + 1) / 2) {
            int result = 0;

            for (int i = p; i <= q; i++) {
                result += i;
            }

            if (result < target) {
                q++;
                continue;
            }

            if (result > target){
                p++;
                continue;
            }

            List<Integer> arr = new ArrayList<>();
            for (int i = p; i <= q; i++) {
                arr.add(i);
            }

            list.add(arr);

            p++;    // 找到一个序列之后,害得继续找下一个,所以p++
        }

        // int[][] array = new int[list.size()][list.get(0).size()];
        int[][] array = new int[list.size()][];

        for (int i = 0; i < list.size(); i++) {
            array[i] = new int[list.get(i).size()];
            for (int j = 0; j < list.get(i).size(); j++) {
                array[i][j] = list.get(i).get(j);
            } 
        }

        return array;

    }
}

58-1 翻转单词顺序

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"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) {
        String[] str = s.trim().split(" "); // 将传进来的字符串以空格拆分
        StringBuilder sb = new StringBuilder();


        // 遍历字符串,寻找其中的单词进行翻转,然后放置到sb的末尾
        for (int i = str.length - 1; i >= 0; i--) {
            if (str[i].equals("")){
                continue;
            }

            sb.append(str[i]);
            sb.append(" ");
        }

        return sb.toString().trim();
    }
}

58-2 左旋转字符串

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

示例 1:

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

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

限制:

1 <= k < s.length <= 10000

class Solution {
    public String reverseLeftWords(String s, int n) {
        // return new StringBuilder(s.substring(n)).reverse().toString() + new StringBuilder(s.substring(0, n)).reverse().toString();
        return new StringBuilder(s.substring(n)).append(new StringBuilder(s.substring(0, n))).toString();
    }
}

Hot100

1、两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

// 快速排序后双指针解法错误,因为数组下标会变化
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> s;
        vector<int> result;

        for (int i = 0; i < nums.size(); i++) {
            auto it = s.find(target - nums[i]);

            if (it != s.end()) {
                result.push_back(i);
                result.push_back(it->second);
            }

            s[nums[i]] = i;
        }

        return result;
    }

    // vector<int> twoSum(vector<int>& nums, int target) {
    //     unordered_map<int, int> hashtable;
    //     for (int i = 0; i < nums.size(); ++i) {
    //         auto it = hashtable.find(target - nums[i]);
    //         if (it != hashtable.end()) {
    //             return[{it->second, i};
    //         }
    //         hashtable[nums[i]] = i;
    //     }
    //     return {};
    // }


    // void quick_sort(vector<int> nums, int l, int r) {
    //     if (l >= r) return;

    //     int x = nums[l];
    //     int i = l - 1;
    //     int j = r + 1;

    //     while (i < j) {
    //         while (nums[++i] < x);
    //         while (nums[--j] > x);

    //         if (i < j) swap(nums[i], nums[j]);
    //     }

    //     quick_sort(nums, l, j);
    //     quick_sort(nums, j + 1, r);
    // }
};

2、两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:

输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

提示:

每个链表中的节点数在范围 [1, 100] 内
0 <= Node.val <= 9
题目数据保证列表表示的数字不含前导0

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        if (l1 == nullptr) return l2;
        if (l2 == nullptr) return l1;

        int t = 0;  // 进位

        ListNode* p = l1;
        ListNode* q = l2;

        ListNode* First = new ListNode(-1);
        ListNode* temp = First; 

        while (p != nullptr || q != nullptr || t > 0) {
            if (p != nullptr)  {
                t += p->val;
                p = p->next;
            }

            if (q != nullptr) {
                t += q->val;
                q = q->next;
            }

            temp->next = new ListNode(t % 10);
            temp = temp->next;

            t /= 10;
        }

        return First->next;
    }
};

3、无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

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

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

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

输入: s = “”
输出: 0

提示:

0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成

/*  
 * 利用hashmap存储j - i范围内的元素的次数,将次数多于1的元素剔除范围
 * j ~ i - 1是不重复的,但加入i导致重复,所以需要去掉上一个s[i]
 * 出现前的所有元素
 */

class Solution {
public:
    unordered_map<char, int> mp;

    int lengthOfLongestSubstring(string s) {
        int res = 0;

        for (int i = 0, j = 0; i < s.length(); i++) {
            mp[s[i]]++;

            while (mp[s[i]] > 1) {
                mp[s[j]]--;
                j++;
            }

            res = max(res, i - j + 1);
        }

        return res;
    }
};

4、寻找正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
示例 3:

输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例 4:

输入:nums1 = [], nums2 = [1]
输出:1.00000
示例 5:

输入:nums1 = [2], nums2 = []
输出:2.00000

提示:

nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106

进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?

class Solution {
public:
    /* 二分法 */
    int getKthNum(vector<int>& q1, int l1, int r1, vector<int>& q2, int l2, int r2, int k) {
        int len1 = r1 - l1 + 1;
        int len2 = r2 - l2 + 1;

        if (len1 > len2) {      // 可能有个数组长度不够,那么空的话就让q1为空
            return getKthNum(q2, l2, r2, q1, l1, r1, k);
        }

        if (len1 == 0) {
            return q2[l2 + k - 1];
        }

        if (k == 1) {
            return min(q1[l1], q2[l2]);
        }

        int i = l1 + min(len1, k / 2) - 1;
        int j = l2 + min(len2, k / 2) - 1;

        if (q1[i] < q2[j]) {
            return getKthNum(q1, i + 1, r1, q2, l2, r2, k - (i - l1 + 1));
        } else {
            return getKthNum(q1, l1, r1, q2, j + 1, r2, k - (j - l2 + 1));
        }
    }
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size();
        int n = nums2.size();

        int left = (m + n + 1) / 2;
        int right = (m + n + 2) / 2;

        double result = (double)(getKthNum(nums1, 0, m - 1, nums2, 0, n - 1, left) 
                        + getKthNum(nums1, 0, m - 1, nums2, 0, n - 1, right)) / 2;

        return result;
    }
};

5、最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:

输入:s = “cbbd”
输出:“bb”
示例 3:

输入:s = “a”
输出:“a”
示例 4:

输入:s = “ac”
输出:“a”

提示:

1 <= s.length <= 1000
s 仅由数字和英文字母(大写和/或小写)组成

/* 中心扩展:分奇偶 */
class Solution {
public:
     pair<int, int> find(string& s, int x, int y) {

        while (x >= 0 && y < s.size() && s[x] == s[y]) {
            x--;
            y++;
        }

        return {x + 1, y - 1};
    }

    string longestPalindrome(string s) {
        int n = s.size();
        int l = 0, r = 0;
        
        for (int i = 0; i < n; i++) {
            auto [l1, r1] = find(s, i, i);
            auto [l2, r2] = find(s, i, i + 1);

            if (r1 - l1 > r - l) {
                r = r1;
                l = l1;
            }

            if (r2 - l2 > r - l) {
                r = r2;
                l = l2;
            }
        }

        return s.substr(l, r - l + 1);
    }
};

/* 动态规划 */
class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        string ans;

        vector<vector<int>> dp(n, vector<int>(n));

        for (int l = 1; l <= n; l++) {
            for (int i = 0; i + l - 1 < n; i++) {
                int j = i + l - 1;  // i子串的起始位置,j子串的结束位置

                /* 
                 * 外循环是子串长度,从1~n,内循环是子串的起始位置
                 * 1、当前串长度为1时,必定是回文串
                 * 2、当前串长度为2时,判断s[i] == s[j]
                 * 3、否则,dp[i][j] = (dp[i + 1][j - 1] && (s[i] == s[j]))
                 */
                if (l == 1) {
                    dp[i][j] = 1;
                } else if (l == 2) {
                    dp[i][j] = (s[i] == s[j]);
                } else {
                    dp[i][j] = (dp[i + 1][j - 1] && (s[i] == s[j]));
                }

                if (dp[i][j] && l > ans.size()) {
                    ans = s.substr(i, l);
                }
            }
        }

        return ans;
    }
};

6、z字形变换

将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:

P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);

示例 1:

输入:s = “PAYPALISHIRING”, numRows = 3
输出:“PAHNAPLSIIGYIR”
示例 2:
输入:s = “PAYPALISHIRING”, numRows = 4
输出:“PINALSIGYAHRPI”
解释:
P I N
A L S I G
Y A H R
P I
示例 3:

输入:s = “A”, numRows = 1
输出:“A”

提示:

1 <= s.length <= 1000
s 由英文字母(小写和大写)、’,’ 和 ‘.’ 组成
1 <= numRows <= 1000

class Solution {
public:
    string convert(string s, int numRows) {
        if (numRows == 1) {
            return s;
        }      

        int len = s.size();
        bool flag = true;

        int temp = -1;

        vector<string> res(numRows);

        for (int i = 0; i < len; i++) {
            if (flag) {
                temp++;
                res[temp].push_back(s[i]);

                if (temp == numRows - 1) {
                    flag = false;
                }
            } else {
                temp--;
                res[temp].push_back(s[i]);

                if (temp == 0) {
                    flag = true;
                }
            }
        }

        string str;

        for (int i = 0; i < numRows; i++) {
            str += res[i];
        }

        return str;
    }
};

7、整数反转

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。

假设环境不允许存储 64 位整数(有符号或无符号)。

示例 1:

输入:x = 123
输出:321
示例 2:

输入:x = -123
输出:-321
示例 3:

输入:x = 120
输出:21
示例 4:

输入:x = 0
输出:0

提示:

-231 <= x <= 231 - 1

class Solution {
public:
    // 正整数翻转
    long long reverse_num(long long x) {
        long long res = 0;

        while (x != 0) {
            res = res * 10 + x % 10;
            x /= 10;
        }

        return res;
    }

    // 判断数字是否在32位有符号整数范围内
    bool judge(long long x) {
        if (x < 0 && x < (-1 * pow(2, 31))) return false;
        if (x > 0 && x > (pow(2, 31) - 1)) return false;

        return true;
    }

    int reverse(int x) {
        if (x > 0) {
            long long res = reverse_num(x);

            if (judge(res)) {
                return (int)res;
            } else {
                return 0;
            }
        } else {
            x = -1 * (long long)x;      // -2^31转成2^31会溢出,所以提前考虑
            long long res = reverse_num(x);
            res = -1 * res;

            if (judge(res)) {
                return (int)res;
            } else {
                return 0;
            }
        }
    }
};

8、字符串转换整数(atoi)

请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。

函数 myAtoi(string s) 的算法如下:

读入字符串并丢弃无用的前导空格
检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
如果整数数超过 32 位有符号整数范围 [−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。
返回整数作为最终结果。
注意:

本题中的空白字符只包括空格字符 ’ ’ 。
除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。

示例 1:

输入:s = “42”
输出:42
解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。
第 1 步:“42”(当前没有读入字符,因为没有前导空格)
^
第 2 步:“42”(当前没有读入字符,因为这里不存在 ‘-’ 或者 ‘+’)
^
第 3 步:“42”(读入 “42”)
^
解析得到整数 42 。
由于 “42” 在范围 [-231, 231 - 1] 内,最终结果为 42 。
示例 2:

输入:s = " -42"
输出:-42
解释:
第 1 步:" -42"(读入前导空格,但忽视掉)
^
第 2 步:" -42"(读入 ‘-’ 字符,所以结果应该是负数)
^
第 3 步:" -42"(读入 “42”)
^
解析得到整数 -42 。
由于 “-42” 在范围 [-231, 231 - 1] 内,最终结果为 -42 。
示例 3:

输入:s = “4193 with words”
输出:4193
解释:
第 1 步:“4193 with words”(当前没有读入字符,因为没有前导空格)
^
第 2 步:“4193 with words”(当前没有读入字符,因为这里不存在 ‘-’ 或者 ‘+’)
^
第 3 步:“4193 with words”(读入 “4193”;由于下一个字符不是一个数字,所以读入停止)
^
解析得到整数 4193 。
由于 “4193” 在范围 [-231, 231 - 1] 内,最终结果为 4193 。
示例 4:

输入:s = “words and 987”
输出:0
解释:
第 1 步:“words and 987”(当前没有读入字符,因为没有前导空格)
^
第 2 步:“words and 987”(当前没有读入字符,因为这里不存在 ‘-’ 或者 ‘+’)
^
第 3 步:“words and 987”(由于当前字符 ‘w’ 不是一个数字,所以读入停止)
^
解析得到整数 0 ,因为没有读入任何数字。
由于 0 在范围 [-231, 231 - 1] 内,最终结果为 0 。
示例 5:

输入:s = “-91283472332”
输出:-2147483648
解释:
第 1 步:"-91283472332"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"-91283472332"(读入 ‘-’ 字符,所以结果应该是负数)
^
第 3 步:"-91283472332"(读入 “91283472332”)
^
解析得到整数 -91283472332 。
由于 -91283472332 小于范围 [-231, 231 - 1] 的下界,最终结果被截断为 -231 = -2147483648 。

提示:

0 <= s.length <= 200
s 由英文字母(大写和小写)、数字(0-9)、’ ‘、’+’、’-’ 和 ‘.’ 组成

class Solution {
public:
    int myAtoi(string s) {
        if (s.size() == 0) {
            return 0;
        }

        /* 判断并丢弃前导空格 */
        int idx = 0;
        while (idx < s.size() && s[idx] == ' ') {
            idx++;

            // 判断空格是否到末尾
            if (idx == s.size()) {
                return 0;
            }
        }

        // s = s.substr(idx, s.size() - idx);

        // 判断 + - 其他符号
        bool flag = false;

        if (s[idx] == '+') {
            idx++;
        } else if (s[idx] == '-') {
            flag = true;
            idx++;
        } else if (s[idx] - '0' < 0 || s[idx] - '0' > 9) {
            return 0;
        }

        // 判断是否为数字
        // int ans = 0;

        // while (idx < s.size() && s[idx] - '0' >= 0 && s[idx] - '0' <= 9) {
        //     int x = s[idx] - '0';

        // if (ans > (INT_MAX - x) / 10) {
        //     return negative ? INT_MIN : INT_MAX;
        // }

        //     ans = ans * 10 + x;
        //     idx++;
        // }

        long long ans = 0;

        while (idx < s.size() && s[idx] - '0' >= 0 && s[idx] - '0' <= 9) {
            ans = ans * 10 + (s[idx] - '0');
            idx++;

            if (!flag && ans > INT_MAX) {
                return INT_MAX;
            } else if (flag && -ans < INT_MIN) {
                return INT_MIN;
            }
        }

        return flag ? (int)-ans : (int)ans;
    }
};

9、回文数

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。

回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。

示例 1:

输入:x = 121
输出:true
示例 2:

输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:

输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。
示例 4:

输入:x = -101
输出:false

提示:

-231 <= x <= 231 - 1

class Solution {
public:
    /* 只翻转一半的数字 */
    bool isPalindrome(int x) {
        // x < 0 或者末尾为0,前面不为0的话,不是回文
        if (x < 0 || (x % 10 == 0 && x != 0)) return false;

        int rev_half_nums = 0;

        while (x > rev_half_nums) {
            rev_half_nums = rev_half_nums * 10 + x % 10;
            x /= 10;
        }  

        return x == rev_half_nums || x == (rev_half_nums) / 10;
    }

    // bool isPalindrome(int x) {
    //     if (x < 0) return false;

    //     long long ans = 0;
    //     long long y = x;

    //     while (y != 0) {
    //         ans = ans * 10 + y % 10;
    //         y /= 10;
    //     }

    //     return ans == x;
    // }
};

10、正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

示例 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 = “cab”
输出:true
解释:因为 ‘*’ 表示零个或多个,这里 ‘c’ 为 0 个, ‘a’ 被重复一次。因此可以匹配字符串 “aab”。
示例 5:

输入:s = “mississippi” p = “misisp*.”
输出:false

提示:

0 <= s.length <= 20
0 <= p.length <= 30
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
保证每次出现字符 * 时,前面都匹配到有效的字符

class Solution {
public:
    bool isMatch(string s, string p) {
        /* 当s为空时 */
        if (s.size() == 0) {
            /* p长度为奇数时 */
            if (p.size() % 2 == 1) return false;

            /* p长度为偶数时,包含0 */
            int i = 1;

            while (i < p.size()) {
                if (p[i] != '*') return false;

                i += 2;
            }

            return true;
        }

        /* 当s不为空时 */
        if (p.size() == 0) return false;    // 当p也为空时

        char c1 = s[0];
        char c2 = p[0];
        char c3 = 'a';

        if (p.size() > 1) c3 = p[1];

        if (c3 != '*') {
            if (c1 == c2 || c2 == '.') {
                return isMatch(s.substr(1), p.substr(1));
            } else {
                return false;
            }
        } else {
            if (c1 == c2 || c2 == '.') {
                return isMatch(s, p.substr(2)) || isMatch(s.substr(1), p);
            } else {
                return isMatch(s, p.substr(2));
            }
        }
    }
};

11、盛最多水的容器(双指针)

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器。

示例 1:

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:

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

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

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

提示:

n = height.length
2 <= n <= 3 * 104
0 <= height[i] <= 3 * 104

/* 双指针:一头一尾数组中两元素a[i] a[j]的水量为min(a[i], a[j]) * (j - i) */
class Solution {
public:
    int maxArea(vector<int>& height) {
        int i = 0, j = height.size() - 1;
        int res = 0;

        while (i < j) {
            int temp = min(height[i], height[j]) * (j - i);

            if (temp > res) {
                res = temp;
            }

            /* 如果保持左指针不动,右指针向内移动,则min(a[i], a[y]) <= min(a[i], a[j]),
             * 又底减小了,所以 i++*/
            if (height[i] < height[j]) {  // 指向较小元素的指针向内移动,查找是否能有构成更大面积的元素
                i++;
            } else {
                j--;
            }
        }

        return res;
    }
};

15、三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:

输入:nums = []
输出:[]
示例 3:

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

提示:

0 <= nums.length <= 3000
-105 <= nums[i] <= 105

/*
 * 1、排序
 * 2、外循环遍历数组nums,每次固定nums[i]
 * 3、对nums[i]后面的数使用双指针p q指向首尾
 * nums[i] + nums[p] + nums[q] = sum
 * 当sum > 0时,后面不可能有和为0,break
 * 当sum = 0时,是三元组,但要判断是否重复
 * 当sum = 0时,如果nums[i] = nums[i - 1],跳过i
 * 当sum = 0时,如果nums[p] = nums[p + 1],跳过p + 1
 * 当sum = 0时,如果nums[q] = nums[q - 1],跳过q - 1
 */
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();

        vector<vector<int>> res;

        if (n < 3) {
            return res;
        }

        sort(nums.begin(), nums.end());

        for (int i = 0; i < n - 2; i++) {
            if (nums[i] > 0) break;

            if (i > 0 && nums[i] == nums[i - 1]) continue;

            int p = i + 1, q = n - 1;

            while (p < q) {
                int sum = nums[i] + nums[p] + nums[q];

                if (sum == 0) {
                    vector<int> temp;

                    temp.push_back(nums[i]);
                    temp.push_back(nums[p]);
                    temp.push_back(nums[q]);

                    res.push_back(temp);

                    // if (nums[i] == nums[i - 1]) i++;
                    while (p < q && nums[p] == nums[p + 1]) p++;    // 可能有多个重复数字,用while
                    while (q > p && nums[q] == nums[q - 1]) q--;

                    p++;
                    q--;

                } else if (sum < 0) {
                    p++;
                } else {
                    q--;
                }
            }
        }

        return res;
    }
};

17、电话号码的字母组合(回溯法)

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
示例 2:

输入:digits = “”
输出:[]
示例 3:

输入:digits = “2”
输出:[“a”,“b”,“c”]

提示:

0 <= digits.length <= 4
digits[i] 是范围 [‘2’, ‘9’] 的一个数字。

class Solution {
public:
    vector<string> res;
    unordered_map<char, string> mp;

    void dfs(string& digits, int index, int n, string s) {
        if (index == n) {
            res.push_back(s);
            return;
        } else {
            char c1 = digits[index];
            string str = mp[c1];

            for (char c2: str) {
                s += c2;
                dfs(digits, index + 1, n, s);
                s.pop_back();
            }
        }
    }

    vector<string> letterCombinations(string digits) {
        if (digits.size() == 0) return res;

        mp = {
            {'2', "abc"},
            {'3', "def"},
            {'4', "ghi"},
            {'5', "jkl"},
            {'6', "mno"},
            {'7', "pqrs"},
            {'8', "tuv"},
            {'9', "wxyz"}
        };

        int n = digits.size();

        dfs(digits, 0, n, "");
        
        return res;
    }
};

19、删除链表的倒数第N个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

进阶:你能尝试使用一趟扫描实现吗?

示例 1:

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

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

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

提示:

链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* q = head;

        while (--n) {
            q = q->next;
        }

        ListNode* p = head;
        ListNode* virfirst = new ListNode(-1, p);
        ListNode* pre = virfirst;    // 作为p的前驱

        while (q->next != nullptr) {
            q = q->next;
            p = p->next;
            pre = pre->next;
        }

        pre->next = p->next;    // 删除节点

        return virfirst->next;
    }
};

20、有效的括号

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

示例 1:

输入:s = “()”
输出:true
示例 2:

输入:s = “()[]{}”
输出:true
示例 3:

输入:s = “(]”
输出:false
示例 4:

输入:s = “([)]”
输出:false
示例 5:

输入:s = “{[]}”
输出:true

提示:

1 <= s.length <= 104
s 仅由括号 ‘()[]{}’ 组成

/* 栈 */
class Solution {
public:
    bool judge(char c1, char c2) {
        if (c1 == ')' && c2 == '(') {
            return true;
        } else if (c1 == '}' && c2 == '{') {
            return true;
        } else if (c1 == ']' && c2 == '[') {
            return true;
        }

        return false;
    }

    bool isValid(string s) {
        /* 字符串长度奇数时不有效 */
        if (s.size() % 2 != 0) {
            return false;
        }

        /* 字符串长度偶数时,左半边与右半边判断是否对应 */
        stack<char> stack1;

        for (int i = 0; i < s.size(); i++) {
            /* 左括号就入栈,右括号就要先判断 */
            if (s[i] == '(' || s[i] == '{' || s[i] == '[') {
                stack1.push(s[i]);
                continue;
            } 

            if (stack1.empty()) {
                return false;
            } else {

                if (judge(s[i], stack1.top())) {
                    stack1.pop();
                    continue;
                } else {
                    return false;
                }

            }
        }

        if (!stack1.empty()) {
            return false;
        }

        return true;
    }
};

21、合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1:

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

输入:l1 = [], l2 = []
输出:[]
示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

提示:

两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    // 递归
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (l1 == nullptr) {
            return l2;
        }

        if (l2 == nullptr) {
            return l1;
        }

        if (l1->val <= l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        } else {
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
    
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (l1 == nullptr) {
            return l2;
        }

        if (l2 == nullptr) {
            return l1;
        }

        ListNode* virfirst = new ListNode(-1);
        ListNode* pre = virfirst;


        while (l1 != nullptr && l2 != nullptr) {
            if (l1->val <= l2->val) {
                pre->next = l1;
                l1 = l1->next;
                pre = pre->next;
            } else {
                pre->next = l2;
                l2 = l2->next;
                pre = pre->next;
            }
        }

        // while (l1 != nullptr) {
        //     pre->next = l1;
        //     break;  // 不加break就死循环了
        // }

        // while (l2 != nullptr) {
        //     pre->next = l2;
        //     break;
        // }

        pre->next = l1 ? l1 : l2;

        return virfirst->next;
    }
};

22、括号生成(DFS)

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

示例 1:

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

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

提示:

1 <= n <= 8

class Solution {
public:
    vector<string> res;

    void dfs(string s, int left, int right, int n) {
        if (left == n && right == n) {
            res.push_back(s);   // 因为每一次尝试都是使用新的字符串变量,所以s无需回溯
            return;
        } 

        if (left < right) return;   // 剪枝,因为右括号比左括号还多

        /* dfs要找到分支,这里就分为加入(和加入) */
        if (left < n) {
            dfs(s + '(', left + 1, right, n);
        }

        if (right < n) {
            dfs(s + ')', left, right + 1, n);
        }
    }

    vector<string> generateParenthesis(int n) {
        dfs("", 0, 0, n);

        return res;
    }
};

23、合并K个升序链表(归并)

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:

输入:lists = []
输出:[]
示例 3:

输入:lists = [[]]
输出:[]

提示:

k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i] 按 升序 排列
lists[i].length 的总和不超过 10^4

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */

/* 归并排序 */
class Solution {
public:
    ListNode* merge_sort(ListNode* l1, ListNode* l2) {
        if (l1 == nullptr) return l2;
        if (l2 == nullptr) return l1;

        ListNode* virfisrt = new ListNode(-1);
        ListNode* pre = virfisrt;

        while (l1 != nullptr && l2 != nullptr) {
            if (l1->val < l2->val) {
                pre->next = l1;
                l1 = l1->next;
                pre = pre->next;
            } else {
                pre->next = l2;
                l2 = l2->next;
                pre = pre->next;
            }
        }

        pre->next = l1 ? l1 : l2;

        return virfisrt->next;
    }

    // vector<ListNode*> merge(vector<ListNode*> temp) {
    //     vector<ListNode*> res;

    //     for (int i = 0; i < temp.size(); i += 2) {
    //         if (i + 1 < temp.size()) {
    //             res.push_back(merge_sort(temp[i], temp[i + 1]));
    //         } else {
    //             res.push_back(temp[i]);
    //         }
    //     }

    //     return res;
    // }

    ListNode* merge(vector<ListNode*>& lists, int l, int r) {
        if (l == r) return lists[l];

        int mid = (l + r) >> 1;

        ListNode* left_node = merge(lists, l, mid);
        ListNode* right_node = merge(lists, mid + 1, r);

        return merge_sort(left_node, right_node);
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int n = lists.size();

        if (n == 0) return nullptr;

        return merge(lists, 0, lists.size() - 1);
    }
};

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */

/* 归并排序 */
class Solution {
public:
    ListNode* merge_sort(ListNode* l1, ListNode* l2) {
        if (l1 == nullptr) return l2;
        if (l2 == nullptr) return l1;

        ListNode* virfisrt = new ListNode(-1);
        ListNode* pre = virfisrt;

        while (l1 != nullptr && l2 != nullptr) {
            if (l1->val < l2->val) {
                pre->next = l1;
                l1 = l1->next;
                pre = pre->next;
            } else {
                pre->next = l2;
                l2 = l2->next;
                pre = pre->next;
            }
        }

        pre->next = l1 ? l1 : l2;

        return virfisrt->next;
    }

    vector<ListNode*> merge(vector<ListNode*> temp) {
        vector<ListNode*> res;

        for (int i = 0; i < temp.size(); i += 2) {
            if (i + 1 < temp.size()) {
                res.push_back(merge_sort(temp[i], temp[i + 1]));
            } else {
                res.push_back(temp[i]);
            }
        }

        return res;
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int n = lists.size();

        if (n == 0) return nullptr;

        while (lists.size() > 1) {
            lists = merge(lists);
        }

        return lists[0];
    }
};

31、下一个排列

实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须 原地 修改,只允许使用额外常数空间。

示例 1:

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

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

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

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

提示:

1 <= nums.length <= 100
0 <= nums[i] <= 100

class Solution {
public:
    /* 思想:从后面找一个尽可能小的大数,与一个距其最近的小数(坐标i),交换,然后i后面是姜旭
     * 1、从后往前找一对相邻的升序对A[i] A[j]
     * 2、从后往前在j~size-1之间找一个大于A[i]的数(包括A[j]),A[k]
     * 3、交换A[i]与A[k],这是j~size-1也是降序,然后j~size-1逆置
     * 4、找不到升序对时,下一个更大的排列就是最小的排列,逆置
     */

    void nextPermutation(vector<int>& nums) {
        if (nums.size() == 1) return;

        int i, j;
        bool flag = false;
        
        for (i = nums.size() - 2; i >= 0; i--) {
            if (nums[i] < nums[i + 1]) {
                j = i + 1;
                flag = true;
                break;
            }
        }

        /* 不存在升序对时,下一个更大的排列就是最小的排列,逆置 */
        if (!flag) {
            reverse(nums.begin(), nums.end());
            return;
        }

        for (int t = nums.size() - 1; t >= j; t--) {
            if (nums[t] > nums[i]) {
                swap(nums[t], nums[i]);
                reverse(nums.begin() + j, nums.end());
                break;
            }
        }
    }
};

32、最长有效括号(动态规划/栈)

给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例 1:

输入:s = “(()”
输出:2
解释:最长有效括号子串是 “()”
示例 2:

输入:s = “)()())”
输出:4
解释:最长有效括号子串是 “()()”
示例 3:

输入:s = “”
输出:0

提示:

0 <= s.length <= 3 * 104
s[i] 为 ‘(’ 或 ‘)’

class Solution {
public:
    /* 栈 */
    int longestValidParentheses(string s) {
        int res = 0;
        stack<int> stack1;

        stack1.push(-1);        // 先入栈一个),其下标为-1

        for (int i = 0; i < s.size(); i++) {
            if (s[i] == '(') {
                stack1.push(i);
                continue;
            } 

            /* s[i]为右括号时,判断栈顶 */
            stack1.pop();   // 先弹出栈顶

            /* 弹出栈顶后,栈顶为空时,表示没有与)匹配的(,将)入栈 */
            if (stack1.empty()) {
                stack1.push(i);
            } else {
                /* 栈不空时 */
                res = max(res, i - stack1.top());

            }
        }

        return res;
    }


    // /* 动态规划:dp[i]表示以i结尾的最长有效括号子串的长度 */
    // int longestValidParentheses(string s) {
    //     int res = 0;
    //     vector<int> dp(s.size(), 0);

    //     for (int i = 1; i < s.size(); i++) {
    //         /* 当前为左括号时不会有有效串,设置为0 */

    //         /* 当前为右括号时,可能有有效串 */
    //         if (s[i] == ')') {

    //             if (s[i - 1] == '(') {
    //                 dp[i] = (i - 2 >= 0 ? dp[i - 2] : 0) + 2;
    //             } else {
    //                 if (i - dp[i - 1] - 1 >= 0 && s[i - dp[i - 1] - 1] == '(') {
    //                     dp[i] = (i - dp[i - 1] - 2 >= 0 ? dp[i - dp[i - 1] - 2] : 0)
    //                             + dp[i - 1] + 2;
    //                 }
    //             }

    //         }

    //         res = max(res, dp[i]);

    //         // /* 如果前一位时左括号,那么当前有效串数量将 + 2 */
    //         // if (i - 1 >= 0 && s[i - 1] == '(') {
    //         //     dp[i] = (i - 2 >= 0 ? dp[i - 2] : 0) + 2;
    //         // } else if (i > 0 && s[i - 1] == ')'){
    //         //     /* 前一位也为右括号时,dp[i - 1]是前面的有效串长度
    //         //         * dp[i - dp[i - 1] - 2]是可能的前面有效串的长度
    //         //         */
    //         //     if (i - dp[i - 1] > 0 && s[i - dp[i - 1] - 1] == '(') {
    //         //         dp[i] = (i - dp[i - 1] - 2 >= 0 ? dp[i - dp[i - 1] - 2] : 0) + dp[i - 1] + 2;
    //         //     } else {
    //         //         dp[i] = 0;
    //         //     }
    //         // }

    //     }

    //     return res;
    // }
};

33、搜索旋转排序数组(二分法)

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

示例 1:

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

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:

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

提示:

1 <= nums.length <= 5000
-10^4 <= nums[i] <= 10^4
nums 中的每个值都 独一无二
题目数据保证 nums 在预先未知的某个下标上进行了旋转
-10^4 <= target <= 10^4

进阶:你可以设计一个时间复杂度为 O(log n) 的解决方案吗?

class Solution {
public:
    /*  
     * 二分法:
     * 切成两半,至少有一半时有序的,根据有序的那一半,确定target在不在里面
     * 在有序一半里面就往有序一半查找,否则往另一半查找
     */
    int search(vector<int>& nums, int target) {

        int l = 0, r = nums.size() - 1;
        int mid;

        while (l <= r) {
            mid = l + r >> 1;

            if (nums[mid] == target) return mid;

            /* 当左侧有序时 */
            if (nums[l] <= nums[mid]) {     // 注意边界:mid = 0, l = 0,这边=加上,案例1, 3, target=2

                /* 当target在有序一侧时, 不在有序一侧时,就在另一侧 */
                if (target <= nums[mid] && target >= nums[l]) {
                    r = mid;
                } else {
                    l = mid + 1;
                }

            } else {

                if (target >= nums[mid] && target <= nums[r]) {
                    l = mid;
                } else {
                    r = mid - 1;
                }
            }
        }

        return -1;
    }
};

34、在排序数组中查找元素的第一个和最后一个位置(二分法)

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:

你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例 1:

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

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

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

提示:

0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109

class Solution {
public:
    /* 二分法 */
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> res(2, -1);   // 存储开始位置和结束位置

        if (nums.size() == 0) return res;

        int l = 0, r = nums.size() - 1;
        int mid;

        /* 找开始位置 */
        while (l < r) {
            mid = l + r >> 1;

            if (nums[mid] >= target) {  
                r = mid;    // mid包含其中,因为可能是开始位置可能不是
            } else {
                l = mid + 1;
            }
        }

        if (nums[l] == target) res[0] = l;    // 找到第一个target

        /* 找结束位置 */
        l = 0, r = nums.size() - 1;

        while (l < r) {
            mid = l + r + 1 >> 1;

            if (nums[mid] <= target) {
                l = mid;
            } else {
                r = mid - 1;
            }
        }

        if (nums[l] == target) res[1] = l;

        return res;
    }
};

56、合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

提示:

1 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104

// 区间合并
class Solution {
public:
    /* 定排序规则
    static bool cmp1(vector<int>& x, vector<int>& y)
    {
	    return x[0] < y[0];
    }
    */
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        if (intervals.size() == 0) {
            return {};
        }

        vector<vector<int>> merged;

        sort(intervals.begin(), intervals.end());
        // sort(intervals.begin(), intervals.end(), cmp1);

        int start = -1, end = -1;
        for (auto seg: intervals) {
            if (end < seg[0]) {
                if (start != -1) {
                    vector<int> res;
                    res.push_back(start);
                    res.push_back(end);
                    merged.push_back(res);
                }

                start = seg[0];
                end = seg[1];
            } else {
                end = max(end, seg[1]);
            }
        }

        if (start != -1) {
            vector<int> res;
            res.push_back(start);
            res.push_back(end);
            merged.push_back(res);
        }

        return merged;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值