剑指offer

2020/02/18 Prolems

目录

面试题03:数组中重复的数字 (简单)

  • 题目描述

    找出数组中重复的数字。

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

    示例 1:

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

    限制:

    2 <= n <= 100000

  • 解法

    思路1:使用 HashSet 来进行处理,因为 HashSet 本身不允许出现重复元素,所以当添加元素失败或已经包含该数字时,则表示出现了重复元素,将其返回即可。时间复杂度:O(n),空间复杂度:O(n)

    思路2:从题目描述中我们可以看出,因为所有数字都在 0 ~ n-1 的范围内,其实完全可以省掉额外的空间开辟,将每个位置的数交换映射到其对应的数组下标下面,当出现新的元素与其对应的下标中的数字相等时,即为重复数字。时间复杂度:O(n),空间复杂度:O(1)

  • 代码

class Solution {
    public int findRepeatNumber(int[] nums) {
        Set<Integer> numsSet = new HashSet<>();
        for(int num: nums) {
            if(!numsSet.add(num)) {
                return num;
            }
        }
        return -1;
    }
}
class Solution {
    public int findRepeatNumber(int[] nums) {
        for(int i=0;i<nums.length;i++){
            while(i!=nums[i]){
                if(nums[i]==nums[nums[i]]){
                    return nums[i];
                }
                int tmp = nums[i];
                nums[i] = nums[tmp];//注意这里不能写成nums[i]=nums[nums[i]],会死循环
                nums[tmp] = tmp;
            }
        }
        return -1;
    }
}

面试题04. 二维数组中的查找(简单)

  • 题目描述

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

    示例 1:

    现有矩阵 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 = 5,返回 true。
    给定 target = 20,返回 false。

  • 解法

    从二维数组的右上角开始查找。如果当前元素等于目标值,则返回 true。如果当前元素大于目标值,则移到左边一列。如果当前元素小于目标值,则移到下边一行。

  • 代码

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

面试题05. 替换空格(简单)

  • 题目描述

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

    示例 1:

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

    限制:

    0 <= s 的长度 <= 10000

  • 解法

    由于每次替换从 1 个字符变成 3 个字符,使用字符数组可方便地进行替换。建立字符数组地长度为 s 的长度的 3 倍,这样可保证字符数组可以容纳所有替换后的字符。

  • 代码

class Solution {
    public String replaceSpace(String s) {
        int length = s.length();
        char[] array = new char[length * 3];
        int size = 0;
        for (int i = 0; i < length; i++) {
            char c = s.charAt(i);
            if (c == ' ') {
                array[size++] = '%';
                array[size++] = '2';
                array[size++] = '0';
            } else {
                array[size++] = c;
            }
        }
        String newStr = new String(array, 0, size);
        return newStr;
    }
}
class Solution {
    public String replaceSpace(String s) {
        s=s.replaceAll(" ","%20");
        return s;
    }
}

面试题06. 从尾到头打印链表(简单)

  • 题目描述

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

    示例 1:

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

    限制:

    0 <= 链表长度 <= 10000

  • 解法

    这题本身没难度,栈或者递归,第二个解法试了下stream,效率极低

  • 代码

class Solution {
    public int[] reversePrint(ListNode head) {
        if(head==null) return new int[0];
        ListNode node = head;
        Stack<ListNode> stack = new Stack<>();
        while(node!=null){
            stack.push(node);
            node = node.next;
        }
        int[] result = new int[stack.size()];
        int i=0;
        while(!stack.isEmpty()){
            result[i++] = stack.pop().val;
        }
        return result;
    }
}
class Solution {
    ArrayList<Integer> list = new ArrayList<>();
    public int[] reversePrint(ListNode head) {
        if(head==null) return new int[0];
        recur(head);
        int[] result = list.stream().mapToInt(Integer::valueOf).toArray();
        return result;
    }

    public void recur(ListNode tmp){
        if(tmp.next!=null){
            reversePrint(tmp.next);
        }
        list.add(tmp.val);
    }
}
class Solution {
    ArrayList<Integer> tmp = new ArrayList<Integer>();
    public int[] reversePrint(ListNode head) {
        recur(head);
        int[] res = new int[tmp.size()];
        for(int i = 0; i < res.length; i++)
            res[i] = tmp.get(i);
        return res;
    }
    void recur(ListNode head) {
        if(head == null) return;
        recur(head.next);
        tmp.add(head.val);
    }
}

非递归方式反转链表

public static Node reverseList(Node node) {
	Node pre = null;
	Node next = null;
	while (node != null) {
		next = node.next;
		node.next = pre;
		pre = node;
		node = next;
	}
	return pre;
}

面试题07. 重建二叉树(中等)

  • 题目描述

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

    示例 1:

    前序遍历 preorder = [3,9,20,15,7]
    中序遍历 inorder = [9,3,15,20,7]

    限制:

    0 <= 节点个数 <= 5000

  • 解法

    前序得到根节点,去中序那得到根节点下标,左边是中序的左子树,右边是右子树,递归创建

  • 代码

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder==null||preorder.length==0){
            return null;
        }
        int rootNum = preorder[0];
        TreeNode tmpNode = new TreeNode(rootNum);
        int rootIndex = 0;
        for(int i=0;i<inorder.length;i++){
            if(rootNum==inorder[i]){
                rootIndex = i;
            }
        }
        int[] preorderLeft = Arrays.copyOfRange(preorder,1,1+rootIndex);
        int[] inorderLeft = Arrays.copyOfRange(inorder,0,rootIndex);
        int[] preorderRight = Arrays.copyOfRange(preorder,1+rootIndex,preorder.length);
        int[] inorderRight = Arrays.copyOfRange(inorder,1+rootIndex,inorder.length);
        tmpNode.left = buildTree(preorderLeft,inorderLeft);
        tmpNode.right = buildTree(preorderRight,inorderRight);
        return tmpNode;
    }
}

面试题09. 用两个栈实现队列(简单)

  • 题目描述

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

    示例 1:

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

    限制:

    1 <= values <= 10000
    最多会对 appendTail、deleteHead 进行 10000 次调用

  • 解法

    出栈前,判断一下,把一个栈扔到另外一个里

  • 代码

class CQueue {
    public Stack<Integer> stack1;
    public Stack<Integer> stack2;
    public CQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    
    public void appendTail(int value) {
        stack1.push(value);
    }
    
    public int deleteHead() {
        if(!stack2.isEmpty()){
            return stack2.pop();
        }else if(stack1.isEmpty()){
            return -1;
        }else{
            while(!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
            return stack2.pop();
        }
    }
}

面试题10- I. 斐波那契数(简单)

  • 题目描述

    写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:F(0) = 0, F(1) = 1 F(N) = F(N - 1) + F(N - 2), 其中 N > 1.答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

    示例 1:

    输入:n = 5
    输出:5

    限制:

    0 <= n <= 100

  • 解法

    注意斐波那契变种题即可

  • 代码

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

面试题10- II. 青蛙跳台阶问题(简单)

  • 题目描述

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

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

    示例 1:

    输入:n = 2
    输出:2

    限制:

    0 <= n <= 100

  • 解法

    斐波那契变种

  • 代码

class Solution {
    public int numWays(int n) {
        if(n==0) return 1;
        if(n==1) return 1;
        if(n==2) return 2;
        int a=1,b=2;
        int result=0;
        for(int i=3;i<n+1;i++){
            result=(a+b)%1000000007;
            a=b;
            b=result;
        }
        return result;
    }
}

面试题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

  • 解法

    二分法,重点!减治思想

    用左边位置 left 和中间位置 mid 的值进行比较是否可以?
    举例:[3, 4, 5, 1, 2] 与 [1, 2, 3, 4, 5] ,此时,中间位置的值都比左边大,但最小值一个在后面,一个在前面,因此这种做法不能有效地减治。

    用右边位置 right 和中间位置 mid 的值进行比较是否可以?
    举例:[1, 2, 3, 4, 5]、[3, 4, 5, 1, 2]、[2, 3, 4, 5 ,1],用右边位置和中间位置的元素比较,可以进一步缩小搜索的范围。

    补充说明:遇到 nums[mid] == nums[right] 的时候,不能草率地下定结论最小数字在哪一边,但是可以确定的是,把 right 舍弃掉,并不影响结果。

  • 代码

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

面试题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

    限制:

    1 <= board.length <= 200
    1 <= board[i].length <= 200

  • 解法

    解法1:路径问题一般需要visit来存储是否访问过,由于每个点都可以是起点,所以在两个for循环中使用回溯。在回溯内部,上下左右走是递归的入口,如果能走返回true

    解法2:深度优先搜索(DFS)+ 剪枝 解决。

    • 深度优先搜索: 可以理解为暴力法遍历矩阵中所有字符串可能性。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
    • 剪枝: 在搜索中,遇到 这条路不可能和目标字符串匹配成功 的情况(例如:此矩阵元素值和目标字符值不同、路径已访问此元素),则应立即返回,称之为 可行性剪枝 。
  • 代码

class Solution {
    boolean[] visited;
    public boolean exist(char[][] board, String word) {
        int row=board.length;
        int col=board[0].length;
        boolean result=false;
        visited = new boolean[row*col];
        for(int i=0;i<row;i++){
            for(int j=0;j<col;j++){
                result = result||existCore(board,i,j,word,0);
            }
        }
        return result;
    }

    public boolean existCore(char[][] board,int row,int col,String word,int index){
        int rowLen=board.length;
        int colLen=board[0].length;
        if(board[row][col]!=word.charAt(index)||visited[row*colLen+col]){
            return false;
        }
        if(board[row][col]==word.charAt(index)&&index==word.length()-1){
            return true;
        }
        visited[row*colLen+col]=true;
        if(row>0&&existCore(board,row-1,col,word,index+1)) return true;
        if(row<rowLen-1&&existCore(board,row+1,col,word,index+1)) return true;
        if(col>0&&existCore(board,row,col-1,word,index+1)) return true;
        if(col<colLen-1&&existCore(board,row,col+1,word,index+1)) return true;
        visited[row*colLen+col]=false;
        return false;
    }
}
class Solution {
    public boolean exist(char[][] board, String word) {
        char[] words = word.toCharArray();
        for(int i = 0; i < board.length; i++) {
            for(int j = 0; j < board[0].length; j++) {
                if(dfs(board, words, i, j, 0)) return true;
            }
        }
        return false;
    }
    boolean dfs(char[][] board, char[] word, int i, int j, int k) {
        if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;
        if(k == word.length - 1) return true;
        char tmp = board[i][j];
        board[i][j] = '/';
        boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || 
                      dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
        board[i][j] = tmp;
        return res;
    }
}

面试题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

    限制:

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

  • 解法

    使用深度优先搜索(Depth First Search,DFS)方法进行求解。回溯是深度优先搜索的一种特例,它在一次搜索过程中需要设置一些本次搜索过程的局部状态,并在本次搜索结束之后清除状态。而普通的深度优先搜索并不需要使用这些局部状态,虽然还是有可能设置一些全局状态。

  • 代码

public class Solution {
     
    int result=0;
    boolean[] visited = null;
    public int movingCount(int threshold, int rows, int cols)
    {
        visited = new boolean[rows*cols];
        countCore(threshold,rows,cols,0,0);
        return result;
    }
     
    public void countCore(int threshold,int rows,int cols,int row,int col){
        boolean judgeD=judgeDigit(threshold,row,col);
        if(!judgeD||visited[row*cols+col]){
            return;
        }
        result++;
        visited[row*cols+col]=true;
        if(row>0) countCore(threshold,rows,cols,row-1,col);
        if(row<rows-1) countCore(threshold,rows,cols,row+1,col);
        if(col>0) countCore(threshold,rows,cols,row,col-1);
        if(col<cols-1) countCore(threshold,rows,cols,row,col+1);
    }
     
    public boolean judgeDigit(int threshold,int row,int col){
        int count=0;
        while(row>0){
            count+=row%10;
            row/=10;
        }
        while(col>0){
            count+=col%10;
            col/=10;
        }
        if(count>threshold){
            return false;
        }else{
            return true;
        }
    }
}

面试题14- I. 剪绳子(中等)

  • 题目描述

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

    示例 1:

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

    限制:

    2 <= n <= 58

  • 解法

    贪心规则:

    • 最高优先级: 3 。把绳子尽可能切为多个长度为 3 的片段,留下的最后一段绳子的长度可能为 0,1,2 三种情况。
    • 次高优先级: 2 。若最后一段绳子长度为 2,则保留,不再拆为 1+1 。
    • 最低优先级: 1; 若最后一段绳子长度为 1;则应把最后的 3 + 1 替换为 2 + 2,因为 2×2>3×1

    如果需要对结果取模运算,需要对每一步取模,不然会溢出

  • 代码

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 (int)Math.pow(3,a);
        if(b==1) return (int)Math.pow(3,a-1)*4;
        return (int)Math.pow(3,a)*2;
    }
}
class Solution {
    public int cuttingRope(int n) {
        if(n == 2) {
            return 1;
        }
        if(n == 3){
            return 2;
        }
        int mod = (int)1e9 + 7;
        long res = 1;
        while(n > 4) {
            res *= 3;
            res %= mod;
            n -= 3;
        }
        return (int)(res * n % mod);
    }
}

面试题15. 二进制中1的个数(简单)

  • 题目描述

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

    示例 1:

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

    示例 2:

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

  • 解法

    依次判断整数的每一位是否为 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=n>>>1;//无符号右移,忽略符号位,空位都以0补齐
        }
        return count;
    }
}

面试题16. 数值的整数次方(中等)

  • 题目描述

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

    示例 1:

    输入: 2.00000, 10
    输出: 1024.00000

    限制:

    -100.0 < x < 100.0
    n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。

  • 解法

    要注意的一点是,虽然题目中告诉我们不需要考虑大数问题,但是给出的 n 可以取到 -2147483648−2147483648(整型负数的最小值),因此,在编码的时候,需要将 n 转换成 long 类型。因为abs(INT_MIN) 比 INT_MAX 大1, 所以不能直接用 -n

    写法一:递归写法(分治思想)

    写法二:非递归写法(将指数看成二进制数)把指数 n 做“二进制分解”,在底数不断自身乘以自身的过程中,将最终结果需要的部分保存下来。

    offer16

  • 代码

class Solution {
    public double myPow(double x, int n) {
        long N = n;
        if(n<0){
            x=1/x;
            return myPow(x,-N);
        }
        return myPow(x,N);
    }
    public double myPow(double x,long N){
        if(N==0||x==1) return 1.0;
        // 根据指数是奇数还是偶数进行分类讨论
        // 使用位运算的 与 运算符代替了求余数运算
        if((N&1)==0){
            double square = myPow(x,N>>>1);// 分治思想:分
            return square*square;// 分治思想:合
        }else{
            double square = myPow(x,(N-1)>>>1);
            return square*square*x;
        }
    }
}
class Solution {
    public double myPow(double x, int n) {
        long N = n;
        if(n<0){
            x=1/x;
            N*=-1;
        }
        double result=1;
        while(N>0){
            if((N&1)==1){
                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 {
    public int[] printNumbers(int n) {
        int max=(int)Math.pow(10,n);
        int[] result = new int[max-1];
        for(int i=1;i<max;i++){
            result[i-1]=i;
        }
        return result;
    }
}

面试题18. 删除链表的节点(简单)

  • 题目描述

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

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

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

    示例 1:

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

    说明:

    题目保证链表中节点的值互不相同
    若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点

  • 代码

/**
 * 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) {
        ListNode tmp = head;
        if(head.val==val) return head.next;
        while(tmp!=null){
            if(tmp.next.val==val){
                tmp.next=tmp.next.next;
                break;
            }
            tmp=tmp.next;
        }
        return head;
    }
}

面试题20. 表示数值的字符串(中等)

  • 题目描述

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

  • 解法

    1)A[.[B]][e EC] 2).B[e EC]; 其中,A、C是整数,B是正整数
  • 代码

/*
核心: 有效数字的模式有两种:1)A[.[B]][e|EC]  2).B[e|EC]; 其中,A、C是整数,B是正整数
有A的话,有没有B都可以
没有A的话, 必须有B
*/
class Solution {
    //扫描字符串时的索引
    int i=0;
    public boolean isNumber(String s) {
        //input check
        if(s==null || s.length()==0)
            return false;
        //去掉首尾的空字符
        s = s.trim();
        boolean A = scanInteger(s), B=false, C=false;
        //判断是否有B; 使用索引时要确保索引不越界
        if(i<s.length() && s.charAt(i)=='.'){
            i++;
            B = scanUnsignedInteger(s);
        }
        //判断是否有C
        if(i<s.length() && (s.charAt(i)=='e' || s.charAt(i)=='E')){
            i++;
            C = scanInteger(s);
            //如果存在e|E, 但是没有C, 说明不是数字
            if(C==false)
                return false;
        }
        //here, 说明C是合格的, 只需判断A和B的情况
        //i必须扫描完整个字符串 && (A合格则B合不合格都可以, A不合格则B必须合格)
        return i==s.length() && (A || B);
        
    }
    private boolean scanInteger(String s){
        if(i<s.length() && (s.charAt(i)=='+' || s.charAt(i)=='-'))
            i++;
        return scanUnsignedInteger(s);
    }
    private boolean scanUnsignedInteger(String s){
        //起始索引
        int start = i;
        while(i<s.length() && s.charAt(i)>='0' && s.charAt(i)<='9'){
            i++;
        }
        //i>start说明扫描到了数字; 
        //i<=start说明没有扫描到数字, 此种情况说明要么start越界, 要么s.charAt(start)不是数字
        return i > start;
    }
}

面试题21. 调整数组顺序使奇数位于偶数前面(简单)

  • 题目描述

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

    示例 1:

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

    限制:

    1 <= nums.length <= 50000
    1 <= nums[i] <= 10000

  • 解法

    头尾指针,指向排好的奇数和偶数

  • 代码

class Solution {
    public int[] exchange(int[] nums) {
        int oddIndex=0;
        int evenIndex=nums.length-1;
        while(oddIndex<evenIndex){
            if((nums[oddIndex]&1)==1){
                oddIndex++;
            }else{
                swap(nums,oddIndex,evenIndex);
                evenIndex--;
            }
        }
        return nums;
    }

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

面试题22. 链表中倒数第k个节点(简单)

  • 题目描述

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

    示例 1:

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

  • 解法

    快慢指针,快的先走k步

  • 代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode preNode = head;
        for(int i=0;i<k;i++){
            preNode = preNode.next;
        }
        ListNode resNode = head;
        while(preNode!=null){
            preNode = preNode.next;
            resNode = resNode.next;
        }
        return resNode;
    }
}

面试题24. 反转链表(简单)

  • 题目描述

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

    示例 1:

    输入: 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) {
        ListNode pre = null;
        ListNode cur = head;
        while(cur!=null){
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

面试题25. 合并两个排序的链表(简单)

  • 题目描述

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

    示例 1:

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

    限制:

    0 <= 链表长度 <= 1000

  • 解法

    递归,值小的链表的下一个递归,并且往后走一个

    循环,利用一下dummyNode即可

  • 代码

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1==null) return l2;
        if(l2==null) return l1;
        if(l1.val<l2.val){
            l1.next = mergeTwoLists(l1.next,l2);
            return l1;
        }else{
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }
}
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode dummy = new ListNode(0), p = dummy;

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

    if (l1 != null) p.next = l1;
    if (l2 != null) p.next = l2;
    return dummy.next;
    }
}

面试题26. 树的子结构(中等)

  • 题目描述

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

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

    示例 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) {
        boolean result = false;
        if(A!=null&&B!=null){
            if(A.val==B.val){
                result = equalTree(A,B);
            }
            if(!result){
                result = isSubStructure(A.left,B);
            }
            if(!result){
                result = isSubStructure(A.right,B);
            }
        }
        return result;
    }

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

面试题27. 二叉树的镜像(简单)

  • 题目描述

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

    示例 1:

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

    限制:

    0 <= 节点个数 <= 1000

  • 解法

    交换左右子树,然后递归进左右子树

  • 代码

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

面试题28. 对称的二叉树(简单)

  • 题目描述

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

    例如,二叉树 [1,2,2,3,4,4,3] 是对称的。但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的。

    示例 1:

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

    限制:

    0 <= 节点个数 <= 1000

  • 解法

    先判断空值的情况,如果不空判断值是否相等,递归进左右

  • 代码

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root==null){
            return true;
        }
        return judge(root.left,root.right);
    }

    public boolean judge(TreeNode node1,TreeNode node2){
        if(node1==null&&node2==null) return true;
        if(node1==null||node2==null) return false;
        if(node1.val!=node2.val){
            return false;
        }else{
            return judge(node1.left,node2.right)&&judge(node1.right,node2.left);
        }
    }
}

面试题29. 顺时针打印矩阵(简单)

  • 题目描述

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

    示例 1:

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

    限制:

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

  • 解法

    算出来圈数之后按圈从左到右,从上到下,从右到左,从下到上打印

  • 代码

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if(matrix==null||matrix.length==0) return new int[0];
        int row = matrix.length;
        int col = matrix[0].length;
        int[] result = new int[row*col];
        int resultIndex = 0;
        int circle = Math.min(row,col)/2+1;
        for(int i=0;i<circle;i++){
            //从左到右打印
            for(int j=i;j<col-i;j++){
                result[resultIndex++] = matrix[i][j];
            }
            if(resultIndex==row*col) break;
            //从上到下打印
            for(int j=i+1;j<row-i;j++){
                result[resultIndex++] = matrix[j][col-i-1];
            }
            if(resultIndex==row*col) break;
            //从右到左打印
            for(int j=i+1;j<col-i;j++){
                result[resultIndex++] = matrix[row-i-1][col-j-1];
            }
            if(resultIndex==row*col) break;
            //从下到上打印
            for(int j=i+1;j<row-i-1;j++){
                result[resultIndex++] = matrix[row-j-1][i];
            }
            if(resultIndex==row*col) break;
        }
        return result;
    }
}

面试题30. 包含min函数的栈(简单)

  • 题目描述

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

    示例 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> stack;
    Stack<Integer> minStack;
    /** initialize your data structure here. */
    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }
    
    public void push(int x) {
        stack.push(x);
        if(minStack.isEmpty()){
            minStack.push(x);
        }else if(x<=minStack.peek()){
            minStack.push(x);
        }else if(x>minStack.peek()){
            minStack.push(minStack.peek());
        }
    }
    
    public void pop() {
        stack.pop();
        minStack.pop();
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int min() {
        return minStack.peek();
    }
}

面试题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

    限制:

    0 <= pushed.length == popped.length <= 1000
    0 <= pushed[i], popped[i] < 1000
    pushed 是 popped 的排列。

  • 解法

    for循环将push压栈,每次压入后看是否能pop

  • 代码

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Stack<Integer> stack = new Stack<>();
        int j=0;
        for(int i=0;i<pushed.length;i++){
            stack.push(pushed[i]);
            while(!stack.isEmpty()&&stack.peek()==popped[j]){
                stack.pop();
                j++;
            }
        }
        if(stack.isEmpty()){
            return true;
        }else{
            return false;
        }
    }
}

面试题32 - I. 从上到下打印二叉树(简单)

  • 题目描述

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

    示例 1:

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

    限制:

    节点总数 <= 1000

  • 解法

    简单的层序遍历,队列即可

  • 代码

class Solution {
    public int[] levelOrder(TreeNode root) {
        if(root==null) return new int[]{};
        LinkedList<TreeNode> que = new LinkedList<>();
        List<Integer> resultList = new ArrayList<>();
        que.add(root);
        while(!que.isEmpty()){
            TreeNode tmp = que.pop();
            resultList.add(tmp.val);
            if(tmp.left!=null) que.add(tmp.left);
            if(tmp.right!=null) que.add(tmp.right);
        }
        int[] result = new int[resultList.size()];
        for(int i=0;i<resultList.size();i++){
            result[i] = resultList.get(i);
        }
        return result;
    }
}

面试题32 - II. 从上到下打印二叉树 II(简单)

  • 题目描述

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

  • 解法

    层序遍历,while中多一层for循环,每一层添加完后,记录一下队列的size,为for循环的次数

    方法二,利用递归

  • 代码

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root==null) return new ArrayList<>();
        LinkedList<TreeNode> que = new LinkedList<>();
        List<List<Integer>> result = new ArrayList<>();
        que.add(root);
        int levelSize=0;       
        while(!que.isEmpty()){
            List<Integer> tmp = new ArrayList();
            levelSize = que.size();
            for(int i=0;i<levelSize;i++){
                TreeNode tmpNode = que.pop();
                tmp.add(tmpNode.val);
                if(tmpNode.left!=null) que.add(tmpNode.left);
                if(tmpNode.right!=null) que.add(tmpNode.right);
            }
            result.add(tmp);
        }
        return result;
    }
}

面试题32 - III. 从上到下打印二叉树 III(中等)

  • 题目描述

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

  • 解法

    方法一:利用两个栈

    方法二:递归,最后按层反转

  • 代码

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root==null) return new ArrayList<>();
        List<List<Integer>> result = new ArrayList<>();
        Stack<TreeNode> stack1 = new Stack<>();
        Stack<TreeNode> stack2 = new Stack<>();
        stack1.add(root);
        List<Integer> tmpResult = new ArrayList<>();
        while(!stack1.isEmpty()||!stack2.isEmpty()){
            while(!stack1.isEmpty()){
                TreeNode tmpNode = stack1.pop();
                tmpResult.add(tmpNode.val);
                if(tmpNode.left!=null) stack2.add(tmpNode.left);
                if(tmpNode.right!=null) stack2.add(tmpNode.right);
            }
            result.add(new ArrayList<>(tmpResult));
            tmpResult.clear();
            while(!stack2.isEmpty()){
                TreeNode tmpNode = stack2.pop();
                tmpResult.add(tmpNode.val);
                if(tmpNode.right!=null) stack1.add(tmpNode.right);
                if(tmpNode.left!=null) stack1.add(tmpNode.left);
            }
            if(tmpResult.size()!=0) result.add(new ArrayList<>(tmpResult));
            tmpResult.clear();
        }
        return result;
    }
}
class Solution {
    List<List<Integer>> ref;
    public List<List<Integer>> levelOrder(TreeNode root) {
        ref = new ArrayList<>();
        if(root == null) {
            return ref;
        }
        helper(root, 0);
        for(int i = 1; i < ref.size(); i += 2) {
            Collections.reverse(ref.get(i));
        }
        return ref;
    }   
    private void helper(TreeNode root, int cnt) {
        if(root == null) {
            return;
        }
        if(ref.size() <= cnt) {
            ref.add(new ArrayList<>());
        }
        ref.get(cnt).add(root.val);
        helper(root.left, cnt + 1);
        helper(root.right, cnt + 1);
    }
}

面试题33. 二叉搜索树的后序遍历序列(中等)

  • 题目描述

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

    示例 1:

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

    示例 2:

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

  • 解法

    后序遍历的特点,最后一个数是根节点,左子树都比根节点小,右子树都比根节点大。利用二叉搜索树的这个特点,先找到根节点的左右子树分界点,之后判断右子树是否都大于根节点,不满足返回false。满足的话最后递归左右子树。

  • 代码

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        if(postorder==null||postorder.length==0) return true;
        return verifyCore(postorder,0,postorder.length-1);
    }
    public boolean verifyCore(int[] postorder,int start,int root){
        if(start>=root) return true;
        int i=0;
        for(i=start;i<root;i++){
            if(postorder[i]>postorder[root]){
                break;
            }
        }
        for(int j=i;j<root;j++){
            if(postorder[root]>postorder[j]){
                return false;
            }
        }
        return verifyCore(postorder,start,i-1)&&verifyCore(postorder,i,root-1);
    }
}

面试题34. 二叉树中和为某一值的路径(中等)

  • 题目描述

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

    示例 1:

    offer34

    限制:

    节点总数 <= 10000

  • 解法

    递归+回溯,list存入节点,满足则加入结果,递归进左右,递归后剪枝。

  • 代码

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> tmpRes = new ArrayList<>();
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        if(root==null) return res;
        tmpRes.add(root.val);
        if(root.left==null&&root.right==null&&root.val==sum){
            res.add(new ArrayList<>(tmpRes));
        }
        pathSum(root.left,sum-root.val);
        pathSum(root.right,sum-root.val);
        tmpRes.remove(tmpRes.size()-1);
        return res;
    }
}

面试题35. 复杂链表的复制(中等)

  • 题目描述

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

    示例 1:

    offer35

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

    限制:

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

  • 解法

    第一遍,在原链表基础上每个节点后面复制一个节点。第二遍,连接上random。第三遍,把两个链表拆开。

  • 代码

class Solution {
    public Node copyRandomList(Node head) {
        if(head==null) return null;
        Node cur=head;
        while(cur!=null){
            Node copyNode = new Node(cur.val);
            copyNode.next = cur.next;
            cur.next = copyNode;
            cur = cur.next.next;
        }
        cur=head;
        while(cur!=null){
            if(cur.random!=null){//注意这里的判空
                cur.next.random = cur.random.next;
            }
            cur = cur.next.next;
        }
        cur=head;
        Node res=cur.next;
        while(cur!=null){
            Node tmpNext = cur.next;
            cur.next = cur.next.next;
            if(tmpNext.next!=null){//注意这里的判空
                tmpNext.next = tmpNext.next.next;
            }
            cur = cur.next;
        }
        return res;
    }
}

面试题36. 二叉搜索树与双向链表(中等)

  • 题目描述

    输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

    示例 1:

    offer36

    offer36

  • 解法

    方法一,先中序遍历一遍,然后改前后链接关系

  • 代码

class Solution {
    public Node treeToDoublyList(Node root) {
        if(root==null) return null;
        Stack<Node> stack = new Stack<>();
        List<Node> order = new ArrayList<>();
        Node cur = root;
        while(cur!=null||!stack.isEmpty()){
            if(cur!=null){
                stack.push(cur);
                cur=cur.left;
            }else{
                Node tmp=stack.pop();
                order.add(tmp);
                cur=tmp.right;
            }
        }
        for(int i=1;i<order.size()-1;i++){
            Node tmp=order.get(i);
            tmp.left=order.get(i-1);
            tmp.right=order.get(i+1);
        }
        order.get(0).left=order.get(order.size()-1);
        if(order.size()>1){
            order.get(0).right=order.get(1);
            order.get(order.size()-1).left=order.get(order.size()-2);
        } 
        order.get(order.size()-1).right=order.get(0);
        return order.get(0);
    }
}

如果不需要头尾相连的话:

public class Solution {
    TreeNode head,pre=null;
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null) return null;
        inOrder(pRootOfTree);
        return head;
    }
    public void inOrder(TreeNode node){
        if(node==null) return;
        inOrder(node.left);
        if(pre==null){
            head=node;
        }else{
            pre.right=node;
        }
        node.left=pre;
        pre=node;
        inOrder(node.right);
    }
}

头尾相连的遍历写法:(其实也可以不记录尾巴,和上面一样,只是这么看好看点罢了)

class Solution {
    Node head=null,pre=null,tail=null;
    public Node treeToDoublyList(Node root) {
        if(root==null) return root;
        //中序遍历访问节点并连接
        inorder(root);
        //连接头尾节点
        head.left=tail;
        tail.right=head;
        return head;
    }
    private void inorder(Node root){
        //递归出口
        if(root==null) return ;
        //访问左子树
        inorder(root.left);
        //将当前节点和上一个节点连接
        if(pre==null) head=root;
        else pre.right=root;
        root.left=pre;
        pre=root;
        tail=root;
        //访问右子树
        inorder(root.right);
        return ;
    }
}

面试题37. 序列化二叉树(困难)

  • 题目描述

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

  • 解法

    自己写的解法,序列化成string时,遇到空记录‘,#’,反序列化先按逗号分隔,之后判定#

  • 代码

public class Codec {

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

    public void preOrder(TreeNode node){
        if(node==null){
            ser+=",#";
            return;
        } 
        if(ser.length()==0||ser==null){
            ser+=node.val;
        }else{
            ser+=","+node.val;
        }
        preOrder(node.left);
        preOrder(node.right);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if(data==null||data.length()==0) return null;
        String[] node = data.split(",");
        TreeNode root = dePreOrder(node);
        return root;
    }

    int index=0;
    public TreeNode dePreOrder(String[] node){
        if(index>=node.length) return null;
        if(node[index].equals("#")) return null;
        int val = Integer.valueOf(node[index]);
        TreeNode root = new TreeNode(val);
        index++;
        root.left = dePreOrder(node);
        index++;
        root.right = dePreOrder(node);
        return root;
    }
}
public class Codec {

    // Encodes a tree to a single string.
    int index=-1;
    String serialize(TreeNode root) {
        StringBuilder builder = new StringBuilder();
        serializeHelper(root,builder);
        return builder.toString();
    }
     
    void serializeHelper(TreeNode node,StringBuilder s){
        if(node==null){
            s.append("#,");
            return;
        }
        s.append(node.val+",");
        serializeHelper(node.left,s);
        serializeHelper(node.right,s);
    }
     
    TreeNode deserialize(String str) {
        if(str==null||str.equals("")){
            return null;
        }
       String[] value = str.split(",");
       return deserializeHelper(value);
    }
     
    TreeNode deserializeHelper(String[] value){
        index++;
        if(value[index].equals("#")){
            return null;
        }
        TreeNode tmp = new TreeNode(Integer.parseInt(value[index]));
        tmp.left = deserializeHelper(value);
        tmp.right = deserializeHelper(value);
        return tmp;
    }
}

层序遍历方式:

public class Codec {
    //很明显是一个层序遍历,
    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if(root == null)return "[]";
        String res = "[";
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()){
            TreeNode cur = queue.poll();
            if(cur!=null){
                res+=cur.val+",";
                queue.offer(cur.left);
                queue.offer(cur.right);
            }else{
                res+="null,";
            }
        }
        //去除最后的一个,
        res = res.substring(0,res.length()-1);
        return res+="]";
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if(data == null || "[]".equals(data))return null;
        String res = data.substring(1,data.length()-1);
        String[] values = res.split(",");
        int index = 0;
        TreeNode head = generateTreeNode(values[index++]);
        Queue<TreeNode> queue = new LinkedList<>();
        TreeNode node = head;
        queue.offer(head);
        while(!queue.isEmpty()){
            node = queue.poll();
            node.left = generateTreeNode(values[index++]);
            node.right = generateTreeNode(values[index++]);
            if(node.left!=null){
                queue.offer(node.left);
            }
            if(node.right!=null){
                queue.offer(node.right);
            }
        }
        return head;
    }
    public TreeNode generateTreeNode(String value){
        if("null".equals(value))return null;
        return new TreeNode(Integer.valueOf(value));
    }
}

面试题38. 字符串的排列(中等)

  • 题目描述

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

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

    示例 1:

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

    限制:

    1 <= s 的长度 <= 8

  • 解法

    经典全排列问题,回溯思想

    题目类似于全排列。
    这种题目基本上都是采用回溯的套路。
    主要有几点需要注意:
    1:for循环的起始位置。如果需要乱序,那么就是从0开始,如果每次都是顺序查找(不需要往前找),那么就需要有个start
    2:标记数组。通过标记才能让递归后的代码知道之前用过哪些市局,就可以舍去这些数字。

  • 代码

class Solution {
    HashSet<String> set = new HashSet<>();
    boolean[] visited;
    StringBuilder sb = new StringBuilder();
    public String[] permutation(String s) {
        char[] ss = s.toCharArray();
        Arrays.sort(ss);//可有可无
        visited = new boolean[ss.length];
        generate(ss,0);
        String[] res = set.toArray(new String[0]);
        return res;
    }

    public void generate(char[] ss,int index){
        if(index==ss.length){
            set.add(sb.toString());
        }
        for(int i=0;i<ss.length;i++){
            if(!visited[i]){
                sb.append(ss[i]);
                visited[i]=true;
                generate(ss,index+1);
                visited[i]=false;
                sb.deleteCharAt(index);
            }
        }
    }
}
class Solution {
     Set<String> result = new HashSet<>();
     public String[] permutation(String s) {
         if(s == null) return new String[]{};
         boolean[] visited = new boolean[s.length()];
         process(s, "", visited);
         return result.toArray(new String[result.size()]);
     }
 
     private void process(String s, String letter, boolean[] visted){
         if(s.length() == letter.length()){
             result.add(letter);
             return;
         }
         for(int i = 0; i < s.length(); i++){
             char temp = s.charAt(i);
             if(visted[i]) continue;
             visted[i] = true;
             process(s, letter + String.valueOf(temp), visted);
             visted[i] = false;
         }
     }
}

面试题39. 数组中出现次数超过一半的数字(简单)

  • 题目描述

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

    示例 1:

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

    限制:

    1 <= 数组长度 <= 50000

  • 解法

    记录当前最多的那个数,一样+1,不一样-1,到0换数

  • 代码

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

面试题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]

    限制:

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

  • 解法

    方法一:快排思想,找到下标就可以停止了

    方法二:优先队列往里面加

    方法三:先创建一个大小为10001的数组line(随便起的名字),默认值是0不用管。遍历arr,每次将arr[i]的值作为line的下标,然后line[arr[i]]++。这样遍历完后,line这个数组存放的就是arr中每个值存放的次数。如arr=[3,2,1]。那么最后line=[0,1,1,1,0.,0..],又如arr=[0,1,2,1],则line=[1,2,1,0….]这样再遍历一次arr就可以了

  • 代码

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        quickSort(arr,0,arr.length-1,k);
        int[] res = Arrays.copyOfRange(arr,0,k);
        return res;
    }

    public void quickSort(int[] arr,int low,int high,int k){
        if(low>high) return;
        int index = getIndex(arr,low,high);
        if(index==k-1){
            return;
        }else{
            quickSort(arr,low,index-1,k);
            quickSort(arr,index+1,high,k);
        }
    }
    public  int getIndex(int[] arr,int low,int high){
        int tmp=arr[low];
        while(low<high){
            while(low<high&&arr[high]>=tmp){
                high--;
            }
            arr[low]=arr[high];
            while(low<high&&arr[low]<=tmp){
                low++;
            }
            arr[high]=arr[low];
        }
        arr[low]=tmp;
        return low;
    }
}
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if(k == 0) {
            return new int[0];
        }
        Queue<Integer> queue = new PriorityQueue<>((a, b) -> (b - a));
        for(int i: arr) {
            if(queue.size() < k) {
                queue.add(i);
            } else {
                if(queue.peek() > i) {
                    queue.remove();
                    queue.add(i);
                }
            }
        }
        int[] ref = new int[k];
        int cnt = 0;
        while(queue.size() > 0) {
            ref[cnt++] = queue.remove();
        }
        return ref;
    }
}

面试题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]

    限制:

    最多会对 addNum、findMedia进行 50000 次调用。

  • 解法

    分成两半,一个大顶堆,一个小顶堆

  • 代码

class MedianFinder {

    PriorityQueue<Integer> minHeap = null;
    PriorityQueue<Integer> maxHeap = null;
    /** initialize your data structure here. */
    public MedianFinder() {
        minHeap = new PriorityQueue<Integer>((a, b)->(a-b));
        maxHeap = new PriorityQueue<Integer>((a,b)->(b-a));
    }
    int message=0;
    public void addNum(int num) {
        if((message&1)==1){
            maxHeap.add(num);
            minHeap.add(maxHeap.poll());
        }else{
            minHeap.add(num);
            maxHeap.add(minHeap.poll());
        }
        message++;
    }

    public double findMedian() {
        if(minHeap.size()>maxHeap.size()){
            return minHeap.peek();
        }else if(minHeap.size()==maxHeap.size()){
            return (double)(maxHeap.peek()+minHeap.peek())/2;
        }else{
            return maxHeap.peek();
        }
    }
}
class MedianFinder {

    private PriorityQueue<Integer> maxHeap, minHeap;

    public MedianFinder() {
        maxHeap = new PriorityQueue<>(Collections.reverseOrder());
        minHeap = new PriorityQueue<>();
    }

    public void addNum(int num) {
        maxHeap.offer(num);
        minHeap.offer(maxHeap.poll());
        //如果不平衡则调整
        if (minHeap.size() > maxHeap.size()) {
            maxHeap.offer(minHeap.poll());
        }
    }

    public double findMedian() {
        if (maxHeap.size() == minHeap.size()) {
            return (maxHeap.peek() + minHeap.peek()) * 0.5;
        }
        return maxHeap.peek();
    }
}

面试题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

  • 解法

    累加值,如果小于当前的被加数,则中间结果设置为被加数

  • 代码

class Solution {
    public int maxSubArray(int[] nums) {
        int tmp=nums[0];
        int result=nums[0];
        for(int i=1;i<nums.length;i++){
            if(tmp+nums[i]<nums[i]){
                tmp=nums[i];
            }else{
                tmp+=nums[i];
            }
            result=Math.max(result,tmp);
        }
        return result;
    }
}
class Solution {
    public int maxSubArray(int[] nums) {
        int ans = Integer.MIN_VALUE, sum = 0;
        for(int num : nums) {
            sum = Math.max(sum + num, num);
            ans = Math.max(ans, sum);
        }
        return ans;
    }
}

面试题43. 1~n整数中1出现的次数(中等)

  • 题目描述

    输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

    例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

    示例 1:

    输入:n = 12
    输出:5

    限制:

    1 <= n < 2^31

  • 解法

    暴力解法会超时,需要判断最高位是否为1,然后递归,找到递推公式(规律)很重要

  • 代码

class Solution {
    public int countDigitOne(int n) {
        return dfs(n);
    }
    private int dfs(int n) {
        if (n <= 0) {
            return 0;
        }

        String numStr = String.valueOf(n);
        int high = numStr.charAt(0) - '0';
        int pow = (int) Math.pow(10, numStr.length() - 1);
        int last = n - high * pow;

        if (high == 1) {
            // 最高位是1,如1234, 此时pow = 1000,那么结果由以下三部分构成:
            // (1) dfs(pow - 1)代表[0,999]中1的个数;
            // (2) dfs(last)代表234中1出现的个数;
            // (3) last+1代表固定高位1有多少种情况。
            return dfs(pow - 1) + dfs(last) + last + 1;
        } else {
            // 最高位不为1,如2234,那么结果也分成以下三部分构成:
            // (1) pow代表固定高位1,有多少种情况;
            // (2) high * dfs(pow - 1)代表999以内和1999以内低三位1出现的个数;
            // (3) dfs(last)同上。
            return pow + high * dfs(pow - 1) + dfs(last);
        }
    }
}

()

  • 题目描述

    示例 1:

    限制:

  • 解法

  • 代码


Search

    Table of Contents