剑指offer

剑指offer

20200224

题目 :找出数组中重复的数字。

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

示例 1:

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

思路 :利用集合元素的唯一性,找出相同的元素。

它考察的是程序员的沟通能力,先问面试官要时间/空间需求!!!
只是时间优先就用字典,
还有空间要求,就用指针+原地排序数组,
如果面试官要求空间O(1)并且不能修改原数组,还得写成二分法!!!

code

public int findRepeatNumber(int[] nums) {
     Set<Integer> set = new HashSet<>();
     for(int i=0;i<nums.length;i++){
         if(!set.add(nums[i])){           
              return nums[i];
         }
      }
      return -1;
    }

题目 :二维数组中的查找

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

示例:

现有矩阵 matrix 如下:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。

给定 target = 20,返回 false

思路 :以左下角的值为基点,判断与target的大小,当较小的时候,行数减一。反着,列数加一。

code

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

题目 :替换空格

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

思路 :找到空格,重新分配空间。

code

class Solution {
    public String replaceSpace(String s) {
        int count=0;
        int j=0;
        char[] c1 = s.toCharArray();
        for(int i=0;i<c1.length;i++){
            if(c1[i] == ' '){
                count++;
            }
        }
        int lengthNew = 2 * count + c1.length;
        char[] newArr = new char[lengthNew];



        for(int i=0;i<c1.length;i++){
            if(c1[i] == ' '){
                newArr[j++] = '%';
                newArr[j++] = '2';
                newArr[j++] = '0';
            }else{
                newArr[j++]=c1[i];
            }
        }
        return new String(newArr);
    }
}

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

示例 1:

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

思路 :利用栈,反向存储。

code

class Solution {
    public int[] reversePrint(ListNode head) {
        Stack<Integer> stack = new Stack<>();
        while(head != null){
            stack.push(head.val);
            head = head.next;
        }
        int[] res = new int[stack.size()];
        int cnt = 0;
        while(!stack.isEmpty()){
            res[cnt++] = stack.pop();
        }
        return res;
    }
}

题目 :重建二叉树

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

例如,给出

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

    3
   / \
  9  20
    /  \
   15   7

思路 :通过前序遍历找到根节点。中序遍历通过根节点可以确定左子树和右子树。

code

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if (preorder == null || preorder.length == 0) {
            return null;
        }
        Map<Integer, Integer> indexMap = new HashMap<Integer, Integer>();
        int length = preorder.length;
        for (int i = 0; i < length; i++) {
            indexMap.put(inorder[i], i);
        }
        TreeNode root = buildTree(preorder, 0, length - 1, inorder, 0, length - 1, indexMap);
        return root;
    }

    public TreeNode buildTree(int[] preorder, int preorderStart, int preorderEnd, int[] inorder, int inorderStart, int inorderEnd, Map<Integer, Integer> indexMap) {
        if (preorderStart > preorderEnd) {
            return null;
        }
        int rootVal = preorder[preorderStart];
        TreeNode root = new TreeNode(rootVal);
        if (preorderStart == preorderEnd) {
            return root;
        } else {
            int rootIndex = indexMap.get(rootVal);
            int leftNodes = rootIndex - inorderStart, rightNodes = inorderEnd - rootIndex;
            TreeNode leftSubtree = buildTree(preorder, preorderStart + 1, preorderStart + leftNodes, inorder, inorderStart, rootIndex - 1, indexMap);
            TreeNode rightSubtree = buildTree(preorder, preorderEnd - rightNodes + 1, preorderEnd, inorder, rootIndex + 1, inorderEnd, indexMap);
            root.left = leftSubtree;
            root.right = rightSubtree;
            return root;
        }
    }
}

20200225

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

示例 1:

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

思路 : 需要注意的是,栈2每次要保证为空,才能将栈1中的元素存储。

code

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

题目 :斐波那契数列

写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项。斐波那契数列的定义如下:

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

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

思路 :建立一个数组,动态规划。

code:

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

题目 :青蛙跳台阶问题

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

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

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

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

思路 :和斐波那契数列相似。

code

class Solution {
    public int numWays(int n) {
        if(n == 0) return 1;
        if(n == 1) return 1;
        int[] dp = new int[n+1];
        dp[0] = 1;
        dp[1] = 1;

        for(int i=2;i<=n;i++ ){
            dp[i]= (dp[i-1]+dp[i-2]) % 1000000007;
        }
        return dp[n];
    }
}

20200226

题目 :旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [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
算法流程:
循环二分: 设置 ii, jj 指针分别指向 numbers 数组左右两端,m = (i + j) // 2m=(i+j)//2 为每次二分的中点( "//" 代表向下取整除法,因此恒有 i \leq m < ji≤m<j ),可分为以下三种情况:
1. 当 numbers[m] > numbers[j]时: mm 一定在 左排序数组 中,即旋转点 xx 一定在 [m + 1, j][m+1,j] 闭区间内,因此执行 i = m + 1i=m+12. 当 numbers[m] < numbers[j] 时: mm 一定在 右排序数组 中,即旋转点 xx 一定在[i, m][i,m] 闭区间内,因此执行 j = mj=m;
3. 当 numbers[m] == numbers[j] 时: 无法判断 mm 在哪个排序数组中,即无法判断旋转点 xx 在 [i, m][i,m] 还是 [m + 1, j][m+1,j] 区间中。解决方案: 执行 j = j - 1j=j−1 缩小判断范围;
返回值: 当 i = ji=j 时跳出二分循环,并返回 numbers[i] 即可。

code

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

题目 :矩阵中的路径

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

[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]

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

思路深度优先搜索(DFS) + 剪枝

code

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;
    }
    
    private 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;
    }
}

题目 :机器人的运动范围

地上有一个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。请问该机器人能够到达多少个格子?

思路 :对四个方位深度优先搜索。

code :

class Solution{
    int count;
    boolean[][] getVisited;
    public int movingCount(int m, int n, int k){
        if(m <= 0 || n <= 0 || k < 0) return 0;
        getVisited = new boolean[m][n];
        dfs(0,0,m,n,k);
        return count;
    }
    
    public void dfs(int i, int j, int m, int n, int k){
        if(i < 0 || j <0 || i >= m || j >= n || getVisited[i][j] == true) return;
        getVisited[i][j] = true;
        if(!canK(i,j,k)) return ;
        count++;
        dfs(i+1,j,m,n,k);
        dfs(i-1,j,m,n,k);
        dfs(i,j-1,m,n,k);
        dfs(i,j+1,m,n,k);
    }
    
    public boolean canK(int i, int j,int k){
        int sum = 0;
        while(i!=0){
            sum += i % 10;
            i /= 10;
        }
        while(j!=0){
            sum += j % 10;
            j /= 10;
        }
        return sum <= k;
    }
}

20200227

题目 :剪绳子

给你一根长度为 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. 最高优先级:3;把绳子尽可能切为多个长度为3的片段,留下的最后一段绳子的长度可能为0,1,2三种情况。
				2. 次高优先级;2;若最后一段绳子长度为2,则保留,不再拆为1+1.
                    					3. 最低优先级;1;若最后一段绳子长度为1,则应把最后的3+1替换为2+2。

code

class Solution{
    public int cuttingRope(int n){
        if(n < 3) return n - 1;
        int a = n / 3, 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;
    }
}

复杂度分析

  • 时间复杂度 O(1): 仅有求整、求余、次方运算。
    • 求整和求余运算:资料提到不超过机器数的整数可以看作是 O(1);
    • 次方运算:资料提到不超过机器数的整数可以看作是 O(1)。
  • 空间复杂度 O(1): 变量 ab 使用常数大小额外空间。

题目:剪绳子||

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

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

思路 :利用二分求余法。

class Solution{
    public int cuttingRope(int n){
        if(n <= 3) return n - 1;
        int b = n % 3, p = 1000000007;
        long rem = 1, x = 3;
        for(int a = n / 3 - 1; a > 0;a /= 2){
            if(a % 2 == 1) rem = (rem * x) % p;
            x = (x * x) % p;
        }
        if(b == 0) return (int)(rem * 3 % p);
        if(b == 1) return (int)(rem * 4 % p);
        return (int)(rem * 6 % p);
    }
}

20200228

题目 :二进制中1的个数

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

思路 :与二进制有关的题目,通常应用位运算解决。

  • 根据 与运算 定义,设二进制数字 n,则有:
    • n& 1 = 0n&1=0 ,则 n二进制 最右一位 为 0 ;
    • 若 n & 1 = 1n&1=1 ,则 n 二进制 最右一位 为 1 。
  • 循环判断:
    • 判断n最右一位是否为1,根据结果计数。
    • 将n右移位。

code

public class Solution{
    public int hammingWeight(int n){
        int res = 0;
        while(n != 0){
            res += n & 1;
            n >>>= 1;//向右移动一位。
        }
        return res;
    }
}

复杂度分析

  • 时间复杂度$ O(log_2n) : 此 算 法 循 环 内 部 仅 有 移 位 、 与 、 加 等 基 本 运 算 , 占 用 :此算法循环内部仅有移位、与、加等基本运算,占用 O(1) ; 逐 位 判 断 需 要 循 环 ;逐位判断需要循环 log_2n 次 , 其 中 次,其中 log_2n 代 表 数 字 代表数字 n$最高位1的所在位数。
  • 空间复杂度$ O(1) : 变 量 :变量 res$使用常数大小额外空间。

题目 :数值的整数次方

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

示例 1:

输入: 2.00000, 10
输出: 1024.00000

思路 :快速幂解析(二进制角度):

  • 对于任何二进制正整数,设其二进制为“$ b_m…b_3b_2b_1 ” ( ”( ( b_i 为 二 进 制 某 位 值 , 为二进制某位值, i\in[1,m]$),则有:
    • 二进制转十进制:$ n=1b_1 + 2b_2 + 4b_3 +…+ 2^{m-1}b_m$
    • 幂的二进制展开:$ x^n = x^{1b_1 + 2b_2 + 4b_3 +…+ 2^{m-1}b_m} = x{1b_1}x{2b_2}x{4b_3}…x{m-1b_m}$;
  • 根据以上推到,可把计算$ x^n$转化为解决以下两个问题:
    • 计算$ x1,x2,x4,…,x{2{m-1}}$的值:循环执行$x=x2$以下操作即可;
    • 获取二进制各位$ b_1,b_2,b_3,…,b_m$的值:循环执行以下操作即可。
      • $ n&1 ( 与 操 作 ) : 判 断 (与操作):判断 n$二进制最右一位是否是1;
      • $ n >>= 1 ( 移 位 操 作 ) : (移位操作): () n$右移一位。

code

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

复杂度分析:

  • 时间复杂度 $ O(log_2 n)$: 二分的时间复杂度为对数级别。
  • 空间复杂度 $ O(1)$: resres, bb 等变量占用常数大小额外空间

20200229

题目 :打印从1到最大的n位数。

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

思路 :利用快速幂解析

code

public int[] printNumbers(int n){
    if(n <= 0) return new int[0];
    int result = 1;
    int x = 10;
    
    while(n > 0){
        if((n&1) == 1) result *= x;
        n >>= 1;
        x *= x;
    }
    int len = result - 1;
    int[] array = new int[len];
    for(int i=0;i<len;i++){
        array[i] = i + 1;
    }
    return array;
}

题目 :删除链表的节点

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

思路 :双指针

  1. 定位值为val的节点:遍历链表head,直到head.val == val时跳出,即定位到目标节点
  2. 删除链表中某节点:设cur的上个节点为pre,下个节点为cur.next;执行pre.next = cur.next,即可实现删除cur节点。

code

class Solution{
    public ListNode deleteNode(ListNode head, int val){
        if(head.val == val) return head.next;
        ListNode pre = head, cur = head.next;
        while(cur != null && cur.val != val){
            pre = cur;
            cur = cur.next;
        }
        pre.next = cur.next;
        return head;
    }
}

题目正则表达式匹配

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

code

class Solution{
    public boolean isMath(String s,String p){
        if(s == null || p == null){
            return false;
        }
        int rows = s.length();
        int columns = p.length();
        boolean[][] dp = new boolean[rows+1][columns+1];
        
        dp[0][0] = true;
        for(int j=1;j<=columns;j++){
            if(p.charAt(j-1) == '*' &&dp[0][j-2]){
                dp[0][j] = true;
            }
        }
        
        for(int i=1,i<=rows;i++){
            for(int j=1;j<=columns;j++){
                char nows = s.charAt(i-1);
                char nowp = p.charAt(j-1);
                if(nows == nowp){
                    dp[i][j] = dp[i-1][j-1];
                }else{
                    if(j >= 2){
                        char nowpLast = p.charAt(j-2);
                        if(nowpLast == nows || nowpLast == '.'){
                            
                        }
                    }
                }
            }
        }
        
    }
}

20200301

题目 表示数值的字符串

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

思路

1.有效数字的模式只有两种:

  • A[.[B]][e|EC] 比如:+100 29. 3.15E5
  • .B[e|EC] 比如:.3 .4E6
  • 其中,A, C 是整数,B是正整数
  • 其中:有A的话,有没有B都可以;没有A的话,必须有B

code

class Solution{
    int i=0;
    public boolean isNumber(String s){
        if(s == null || s.length() == 0){
            return false;
        }
        //去掉首尾空字符
        s = s.trim();
        //判断是否有B;使用索引将B,C初始化为false
        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;
            }
        }
        //说明C是合格的,只需判断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说明没有扫描到数字
        return i > start;
    }
}

题目 调整数字顺序使奇数位于偶数前面

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

思路 快速排序

code

class Solution{
    public int[] exchange(int[] nums){
        int left = 0;
        int right = nums.length - 1;
        while(left < right){
            while(left < right && nums[left] % 2 != 0){ // 等价于 x & 1
           		left++;
        	}
        	while(left < right && nums[right] % 2 == 0){
            	right--;
        	}
        	if(left < right){
            	int temp = nums[left];
            	nums[left] = nums[right];
            	nums[right] = temp;
        	}
        }
	 return nums;
    }
}

题目 链表中的倒数第k个节点

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

思路 :双指针,建立两个指针fast,slow,fast先向前移动k次,之后一起移动,直到fast为null停止。

code

/**
 * 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 fast = head;
        ListNode slow = head;
        while(k>0){
            fast = fast.next;
            k--;
        }
        while(fast!=null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

2020020302

题目 反转链表

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

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

思路 通过迭代不断交换相邻的值。双指针。

class Solution{
    public ListNode reverseList(ListNode head){
        ListNode pre = null, cur = head, next = null;
        while(cur != null){
            next = cur.next;//保存原先链表的节点的下一个节点
            cur.next = pre;//改变链表的指向
            pre = cur; //指针向前移动
            cur = next;
        }
        return pre;
    }
}

题目 合并两个排序的链表

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

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

code

/**
 * 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 head = new ListNode(0);
        ListNode cur = head;
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                cur.next = l1;
                l1 = l1.next;
            }else{
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        while(l1 != null){
            cur.next = l1;
            cur = cur.next;
            l1 = l1.next;
        }

        while(l2 != null){
            cur.next = l2;
            cur = cur.next;
            l2 = l2.next;
        }
        return head.next;
    }
}

题目 数的子结构

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

B是A的子结构, 即 A中有出现和B相同的结构和节点值。(前序遍历)

例如:
给定的树 A:

     3
    / \
   4   5
  / \
 1   2
给定的树 B:

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

code 记住吧!

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

题目 二叉树的镜像

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

例如输入:

     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]

思路 :递归地交换左右子树。

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

20200303

题目 :对称的二叉树

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

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

    1
   / \
  2   2
 / \ / \
3  4 4  3

code

class Solution{
    public boolean isSymmetric(TreeNode root){
        if(root == null){
            return true;
        }
        return helper(root.left,root.right);
    }
    
    public boolean helper(TreeNode t1,TreeNode t2){
        if(t1 == null && t2 == null) return true;
        if(t1 == null || t2 == null) return false;
        
        return(t1.val == t2.val) && helper(t1.left,t2.right)&&helper(t1.right,t2.left);
    }
}

题目 :顺时针打印矩阵

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

示例 1:

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

思路 :定义边界,然后按照往右、往下、往左、往上的顺序遍历数组的最外层。缩小边界,进行迭代;注意边界。

code

class Solution{
    public int[] spiralOrder(int[][] matrix){
        if(matrix==null||matrix.length==0||matrix[0].length==0){
            return new int[0];
        }
        
        int[] answer = new int[matrix.length * matrix[0].length];
        int rl=0,rh=matrix.length-1;//row
        int cl=0,ch=matrix[0].length-1;//column
        int cur=0;//index
        while(rl<=rh&&cl<=ch){
            for(int j=cl;j<=ch;j++){
                answer[cur++]=matrix[rl][j];
            }
            for(int i=rl+1;i<=rh;i++){
                answer[cur++] = matrix[i][ch];
            }
            if(rl<rh&&cl<ch){
                for(int j=ch-1;j>=cl;j--){
                    answer[cur++]=matrix[rh][j];
                }
                for(int i=rh-1;i>=rl+1;i--){
                    answer[cur++]=matrix[i][cl];
                }
            }
            rl++;
            cl++;
            rh--;
            ch--;
        }
        return answer;
    }
}

题目:包含min函数的栈

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

思路 :用两个栈来实现,s1保存数据,s2同步保存当前最小值。

class MinStack{
    private Stack<Integer> s1;
    private Stack<Integer> s2;
    
    public MinStack(){
        s1 = new Stack<>();
        s2 = new Stack<>();
    }
    
    public void push(int x){
        s1.add(x);
        if(s2.isEmpty() || s2.peek()>x){
            s2.add(x);
        }else{
            s2.add(s2.peek());
        }
    }
    
    public void pop(){
        s1.pop();
        s2.pop();
    }
    
    public int top(){
        return s1.peek();
    }
    
    public int min(){
        return s2.peek();
    }
}

20200304

题目 :栈的压入、弹出序列

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

思路 :新建一个栈,首先把压栈的元素按顺序压入,当栈顶元素和出栈的第一个元素相同时,将该压栈的元素弹出,出栈列表指针向后移动并继续判断;最后判断出栈列表指针是否指向出栈列表的末尾。

code

public boolean validateStackSequences(int[] pushed, int[] popped){
    Stack<Integer> stack = new Stack<>();
    int j = 0;
    for(int elem : pushed){
        stack.push(elem);
        while(j < popped.length && !stack.isEmpty() && stack.peek == popped[j]){
            stack.pop();
            j++;
        }
    }
    return j == popped.length;
}

题目 :从上到下打印二叉树

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

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

	3
   / \
  9  20
    /  \
   15   7
返回:

[3,9,20,15,7]

思路 :层次遍历

code

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

题目 :从上到下打印二叉树||

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

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

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

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

code

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

20200305

题目 :从上到下打印二叉树 |||

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

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

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

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

思路 :利用一个数记录是多少行,偶数行对列表进行翻转。

class Solution{
    public List<List<Integer>> levelOrder(TreeNode root){
        List<List<Integer>> resList = new ArrayList<>();
        if(root == null) return resList;
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        int num=0;
        while(!queue.isEmpty()){
            num++;
            int size = queue.size();
            List<Integer> list = new ArrayList<>();
            for(int i=0;i<size;i++){
                TreeNode curNode = queue.poll();
                list.add(curNode.val);
                if(curNode.left != null) queue.add(curNode.left);
                if(curNode.right != null) queue.add(curNode.right);
            }
            if(num%2==0) Collections.reverse(list);
            resList.add(list);
        }
        return resList;
    }
}

题目 :二叉搜索树的后续遍历

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

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

     5
    / \
   2   6
  / \
 1   3
示例 1:

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

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

思路 :找到根节点,递归方式。

code

class Solution{
    public boolean verifyPostorder(int[] postorder){
        if(postorder.length == 1){
            return true;
        }
        if(postorder.length == 0){
            return true;
        }
        
        boolean flag = verify(postorder,0,postorder.length-1);
        return flag;
    }
    
    public boolean verify(int[] postorder, int left, int right){
        
        if(right <= left){
            return true;
        }
        int temp = left;
        //找到第一个大于根节点的值
        while((temp < right)&&(postorder[temp]<postorder[right])){
            temp++;
        }
        //判断后面是否都大于根节点
        for(int i=temp;i<right;i++){
            if(postorder[i] < postorder[right]){
                return false;
            }
        }
        //递归
        return verify(postorder,left,temp-1) && verify(postorder,temp,right-1);
    }
}

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

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

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

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \    / \
        7    2  5   1
返回:

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

code :

class Solution{
    private List<List<Integer>> ans;
    public List<List<Integer>> pathSum(TreeNode root,int sum){
        ans = new ArrayList<>();
        if(root == null) return ans;
        pathSumHelper(new ArrayList<Integer>(),root,sum,0);
        return ans;
    }
    private void pathSumHelper(List<Integer> path, TreeNode root,int sum,int cur){
        cur += root.val;
        path.add(root.val);
        if(sum == cur && root.left == null && root.right == null){
            ans.add(new ArrayList(path));
        }
        if(root.left != null){
            pathSumHelper(path,root.left,sum,cur);
            path.remove(path.size() - 1);//将入栈,已经遍历的节点取出。
        }
        if(root.right != null){
            pathSumHelper(path,root.right,sum,cur);
            path.remove(path.size() - 1);
        }
    }
}

20200306

题目 :复杂链表的复制

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

思路 :利用哈希表

code

/*
// 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){
        //hashMap<原表节点,新表节点>
        Map<Node,Node> map = new HashMap<>();
        Node p = head;
        while(p!=null){
            map.put(p,new Node(p.val));//只是复制了节点,但并没有连接好
            p=p.next;
        }
        p=head;
        while(p!=null){
            map.get(p).next = map.get(p.next);
            map.get(p).random = map.get(p.random);
            p=p.next;
        }
        return map.get(head);
    }
}

思路 :原地修改

  1. 复制一个新的节点在原有节点之后,如 1 -> 2 -> 3 -> null 复制完就是 1 -> 1 -> 2 -> 2 -> 3 - > 3 -> null
  2. 从头开始遍历链表,通过 cur.next.random = cur.random.next 可以将复制节点的随机指针串起来,当然需要判断 cur.random 是否存在
  3. 将复制完的链表一分为二 根据以上信息,我们不难写出以下代码
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return head;
        }
        // 完成链表节点的复制
        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) { // 注意判断原来的节点有没有random指针
                cur.next.random = cur.random.next;
            }
            cur = cur.next.next;
        }

        // 将链表一分为二
        Node copyHead = head.next;
        cur = head;
        Node curCopy = head.next;
        while (cur != null) {
            cur.next = cur.next.next;
            cur = cur.next;
            if (curCopy.next != null) {
                curCopy.next = curCopy.next.next;
                curCopy = curCopy.next;
            }
        }
        return copyHead;
    }
}

题目 二叉搜索树与双向链表

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

img

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

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

img

code :中序遍历

class Solution{
    Node pre = null;
    Node head = null;
    public Node treeToDoublyList(Node root){
        if(root == null) return null;
        order(root);
        head.left = pre;
        pre.right = head;
        return head;
    }
    
    public void order(Node node){
        if(node == null) return ;
        order(node.left);
        node.left = pre;
        if(pre!=null) pre.right = node;
        pre = node;
        if(head == null) head = node;
        order(node.right);
    }
}

20200307

题目 :序列化二叉树

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

你可以将以下二叉树:

    1
   / \
  2   3
     / \
    4   5

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

思路 :层次遍历

code

public class Codec{
    public String serialize(TreeNode root){
        if(null == root){
            return "";
        }
        LinkedList<TreeNode> queue = new LinkedList<>();
        StringBuilder builder = new StringBuilder("[");
        queue.add(root);
        while(!queue.isEmpty()){
            TreeNode node = queue.removeFirst();
            if(node != null){
                builder.append(node.val);
                queue.addLast(node.left);
                queue.addLast(node.right);
            }else{
                builder.append("null");
            }
            builder.append(",");
        }
        builder.deleteCharAt(builder.length() - 1);
        builder.append("]");
        return builder.toString();
    }
    
    public TreeNode deserialize(String data){
        if(data == null || data.isEmpty()){
            return null;
        }
        String str = data.substring(1,data.length() - 1);
        String[] nodes = str.split(",");
        int index = 0;
        TreeNode root = buildNode(nodes[index++]);
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while(!queue.isEmpty()){
            TreeNode node = queue.removeFirst();
            node.left = buildNode(nodes[index++]);
            node.right = buildNode(nodes[index++]);
            if(node.left != null){
                queue.addLast(node.left);
            }
            if(node.right != null){
                queue.addLast(node.right);
            }
        }
        return root;
    }
    
    private TreeNode buildNode(String node){
        if("null".equals(node)){
            return null;
        }
        return new TreeNode(Integer.valueOf(node));
    }
}

题目 字符串的排列

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

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

code

class Solution{
    ArrayList<String> res = new ArrayList<>();
    public String[] permutation(String s){
        char[] ch = s.toCharArray();
        dfs(ch,0);
        String[] str = new String[res.size()];
        if(s == null || s.length() == 0) return str;
        for(int i=0;i < res.size();i++){
            str[i] = res.get(i);
        }
        return str;
    }
    
    private void dfs(char[] ch, int start){
        if(start == ch.length){
            res.add(new String(ch));
            return;
        }
        boolean[] used = new boolean[256];
        for(int i = start;i < ch.length;i++){
            if(used[ch[i]]) continue;
            used[ch[i]] = true;
            swap(ch,start,i);
            dfs(ch,start+1);
            swap(ch,start,i);
        }
    }
    
    private void swap(char[] ch, int i, int j){
        char tmp = ch[i];
        ch[i] = ch[j];
        ch[j] = tmp;
    }
}

20200308

题目 :数组中出现次数超过一半的数字

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

思路 :排完序,取中值;也可以用hashMap。

code

class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        int left = 0;
        int right = nums.length - 1;
        int middle = (left + right) / 2;
        return nums[middle];
    }
}

题目:最小的k个数

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

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

思路 排序之后,输出来即可。

class Solution{
    public int[] getLeastNumbers(int[] arr, int k){
        if(k == 0) {
            return new int[0];
        }
        if(k == arr.length)
            return arr;
        int[] res = new int[k];
        for(int i=0;i<k;i++){
            res[i] = arr[i];
        }
        return res;
    }
}

思路 大顶堆

class Solution{
    public int[] getLeastNumbers(int[] arr, int k){
        if(k == 0)
            return new int[0];
        int len = arr.length;
        if(k == len){
            return arr;
        }
        //对arr数组的前k个数建堆
        int[] heap = new int[k];
        for(int i=0;i<k;i++){
            heap[i] = arr[i];
        }
        buildHeap(heap);
        
        //遍历arr数组剩余的数,与堆顶元素进行比较大小,小进行替换堆顶,之后保持大顶堆。
        for(int i=k;i<len;i++){
            if(arr[i] < heap[0]){
                heap[0] = arr[i];
                heapify(heap,0);
            }
        }
        //返回堆
        return heap;
    }
    
    //建堆
    //主要 parent = (i-1)/2;childleft = 2*i + 1;childright = 2 * i+ 2;
    private void buildHeap(int[] nums){
        //最后一个结点
        int lastNode = nums.length - 1;
        //最后一个节点的父节点
        int startHeapify = (lastNode - 1) / 2;
        //开始heapfiy直到最后
        while(startHeapify >= 0){
            heapify(nums,startHeapify--);
        }
    }
    
    //维护堆
    private void heapify(int[] nums,int i){
        int len = nums.length;
        if(i >= len){
            return ;
        }
        int c1 = ((i << 1) + 1), c2 = ((i << 1)+2);
        //假定节点i是最大节点。
        int max = i;
        if(c1 < len && nums[c1] > nums[max])
            max = c1;
        if(c2 < len && nums[c2] > nums[max])
            max = c2;
        if(max != i){
            swap(nums,max,i);
            heapify(nums,max);
        }
    }
    private void swap(int[] nums,int i, intj){
        nums[i] = nums[i] + nums[j] - (nums[j] = nums[i]);
    }
}

20200309

题目:数据流中的中位数

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

思路 :将输入的数分成两部分

  • lowpart:利用最大堆维护这一部分,可以比highpart多1;
  • highpart:利用最小堆进行维护;
  • 如果size为奇数,那么中位数就是lowpart的堆顶。否则就是lowparthightpart堆顶的平均值。
  • 每进入一个数,下加入lowpart,然后将lowpart的堆顶移到hightpart。如果这时size是奇数,此时highpart将最小值移出至lowpart

code

class MedianFinder{
     PriorityQueue<Integer> lowPart;
     PriorityQueue<Integer> highPart;
     int size = 0;
	public MedianFinder(){
    	lowPart = new PriorityQueue<Integer>((x,y) -> y-x);//最大堆
    	highPart = new PriorityQueue<Integer>();
    	size = 0;
	}

	public void addNum(int num){
    	size++;
    	lowPart.offer(num);//先插入到最大堆
    	highPart.offer(lowPart.poll());//再插入到最小堆。
    	if((size & 1) == 1){//当时奇数时,保证中值在lowpart中。
        	lowPart.offer(highPart.poll());
    	}
	}

	public double findMedian(){
    	if((size & 1) == 1){
        	return (double)lowPart.peek();
    	}else{
        	return (double)(lowPart.peek() + highPart.peek()) / 2;
    	}
	}
}

题目 : 连续子数组的最大和

输入一个整形数组,数组里有正数也有负数。数组中的一个或者连续多个整数构成一个子数组。求所有子数组的和的最大值。

思路 :动态规划

code

public class Solution{
    public static int maxSubArray(int[] nums){
        int max = nums[0];
        for(int i=1;i<nums.length;i++){
            if(nums[i-1] > 0){
                nums[i] += nums[i-1];
            }
            if(nums[i] > max)
                max = nums[i];
        }
    }
}

20200310

题目 :1~n整数中1出现的次数。

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

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

思路

f(n)函数的意思是1~nn个整数的十进制表示中1出现的次数,将n拆分为两部分,最高一位的数字high和其他位的数字last,分别判断情况后将结果相加,看例子更加简单。

例子如n=1234high=1, pow=1000, last=234

可以将数字范围分成两部分1~9991000~1234

1~999这个范围1的个数是f(pow-1)
1000~1234这个范围1的个数需要分为两部分:
千分位是1的个数:千分位为1的个数刚好就是234+1(last+1),注意,这儿只看千分位,不看其他位
其他位是1的个数:即是234中出现1的个数,为f(last)
所以全部加起来是f(pow-1) + last + 1 + f(last);

例子如3234high=3, pow=1000,last=234

可以将数字范围分成两部分1~9991000~19992000~29993000~3234

1~999这个范围1的个数是f(pow-1)
1000~1999这个范围1的个数需要分为两部分:
千分位是1的个数:千分位为1的个数刚好就是pow,注意,这儿只看千分位,不看其他位
其他位是1的个数:即是999中出现1的个数,为f(pow-1)
2000~2999这个范围1的个数是f(pow-1)
3000~3234这个范围1的个数是f(last)
所以全部加起来是pow + high*f(pow-1) + f(last);

code

class Solution{
    public int countDigitOne(int n){
        return dfs(n);
    }
    private int dfs(int n){
        if(n <= 0){
            return 0;
        }
        String s = String.valueOf(n);
        int high = s.charAt(0) - '0';
        int pow = (int)Math.pow(10,s.length()-1);
        int last = n - high*pow;
        if(high == 1){
            return dfs(pow-1) + last + 1 + dfs(last);
        }else{
            return pow + high*dfs(pow-1) + dfs(last);
        }
    }
}

题目 :数字序列中某一位的数字

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

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

思路 :找到序列第n位所在的是哪个数,然后是数的哪一位。

统计每位数有几个,如1位数有10,2位数有90*2…依次来找出所在的数

code :

class Solution{
    public int findNthDigit(int n){
        long[] count = new long[18];
        count[1]=10;
        long base = 90;
        for(int i=2;i<18;i++){
            count[i] = base*i;
            base*=10;
        }
        int j=1;
        while(n>count[j]){
            n -= count[j];
            j++;
        }
        if(n == 10) return 1;
        if(j == 1) return n;
        //他的j位数当中的第n/j个中的第n%j位(从左往右)
        int jbase = 1;
        for(int k=2;k<=j;k++){
            jbase *=10;//j位数的第0个数
        }
        int jnum = jbase + n/j;//找到了所在的数。
        int m=0;
        for(int k=0;k<=n%j;k++){//从左往右找到n%j位
            m=jnum/jbase;
            jnum%=jbase;
            jbase /= 10;
        }
        return m;
    }
}
class Solution {
    public int findNthDigit(int n) {
        if (n < 10) {
            return n;
        }
        int base = 1;
        long count = 0;  //计算有多少位,测试的时候发现有个1e9的用例,这个用例会导致count越界
        while (true) {
            count = helper(base);
            if (n < count) break;
            n -= count;
            base++;
        }
        //得到新的n和count了,算出第n位对应什么数字
        int num = (int) (n / base + Math.pow(10, base - 1));
        return String.valueOf(num).charAt(n % base) - '0';
    }

    // 计算当前有多少位 1位数10种,10位;2位数 90个数字 180位;3位数 900个 2700位
    private long helper(int base) {
        if (base == 1) {
            return 10;
        }
        return (long) (Math.pow(10, base - 1) * 9 * base);
    }
}

20200311

题目 :把数组排列成最小的数

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

code

class Solution {
    public String minNumber(int[] nums) {
         ArrayList<String> list = new ArrayList<String>();
    for(int i:nums){
        list.add(String.valueOf(i));
    }
    StringBuffer sb = new StringBuffer();
    list.sort((o1,o2) -> (o1+o2).compareTo(o2+o1));
    
    for(String s:list){
        sb.append(s);
    }
    return sb.toString();
    }
}

题目 :把数字翻译成字符串

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

示例:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
//回溯
class Solution{
    private int count = 0;
    public int translateNum(int num){
        String s = String.valueOf(num);
        backtrack(0,s);
        return count;
    }
    public void backtrack(int start, String s){
        if(start == s.length()){
            count++;
            return;
        }
        if(s.charAt(start) >= '0' && s.charAt(start) <= '9'){
            backtrack(start + 1,s);
        }
        if(start < s.length() - 1){
            String sub = s.substring(start,start+2);
            if(sub.compareTo("1/") > 0 && sub.compareTo("26")<0){
                backtrack(start + 2,s);
            }
        }
    }
}

20200312

题目 :礼物的最大价值

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

思路 : dp 转移方程:$ f(i,j) = max[f(i-1,j),f(i,j-1)] + grid[i][j]$

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

题目 :最长不含重复字符的子字符串

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

示例 1:

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

思路 :滑动窗口,用set维护一个不重复的窗口

class Solution{
    public int lengthOfLongestSubstring(String s){
        int res = 0;
        Set<Character> set = new HashSet<>();
        for(int l = 0, r = 0;r < s.length(); r++){
            char c = s.charAt(r);
            while(set.contains(c)){
                set.remove(s.charAt(l++));
            }
            set.add(c);
            res = Math.max(res,r-l+1);
        }
        return res;
    }
}

20200313

题目 :丑数

我们把只包含因子2、3和5的数称为丑数。按从小到大的顺序的第n个丑数。

示例:

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

思路 :动态规划;需将前面的求得的丑数记录下来。

code

class Solution{
    public int nthUglyNumber(int n){
        int p2 = 0, p3 = 0, p5 = 0;
        int[] dp = new int[n];
        dp[0] = 1;//初始值
        for(int i=1;i<n;i++){
            //确保丑数从小到大排序。
            dp[i] = Math.min(dp[p2]*2,Math.min(dp[p3]*3,dp[p5]*5));
            if(dp[i]==dp[p2]*2) p2++;
            if(dp[i]==dp[p3]*3) p3++;
            if(dp[i]==dp[p5]*5) p5++;
        }
        return dp[n-1];
    }
}

题目 :第一个只出现一次的字符。如果没有,返回一个单空格。

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

示例:

s = "abaccdeff"
返回 "b"

s = "" 
返回 " "

思路:利用哈希表,首先遍历,得到每个字符出现的次数,之后再次遍历,找出最先出现一次的字符。

class Solution{
    public char firstUniqChar(String s){
        char ans = ' ';
        //hashMap<Character,Integer>
        Map<Character,Integer> map = new HashMap<>();
        for(int i=0;i<s.length();i++){
            if(map.containsKey(s.charAt(i))){
                int count = map.get(s.charAt(i)) + 1;
                map.put(s.charAt(i),count);
            }else{
                map.put(s.charAt(i),1)
            }
        }
        //再次遍历字符串,找到第一个出现次数为1的返回
        for(int i=0;i<s.length();i++){
            if(map.get(s.charAt(i)) == 1){
                return s.charAt(i);
            }
        }
        return ans;
    }
}
20200314

题目 :数组中的逆序对

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

思路 :逆序对是只要当前数字大于之后数字即可,以7564为例:

逆序对:75,76,74,54,64

利用归并排序

class Solution{
    int count = 0;
    public int reversePairs(int[] nums){
        mergeSort(nums,0,nums.length-1);
        return count;
    }
    public void mergeSort(int[] nums, int start, int end){
        if(start < end){
            int mid = start + ((end - start) >> 1);
            mergeSort(nums,start,mid);
            mergeSort(nums,mid+1,end);
            merge(nums,start,mid,end);
        }
    }
    
    public void merge(int[] nums, int start, int mid,int end){
        int[] temp = new int[end-start+1];
        int p1 = start;
        int p2 = mid + 1;
        int p = 0;
        while(p1<=mid && p2<=end){
            if(nums[p1] <= nums[p2]){
                temp[p++] = nums[p1++];
            }else{
                count = count + mid-p1+1;
                temp[p++] = nums[p2++];
            }
        }
        while(p1 <= mid){
            temp[p++] = nums[p1++];
        }
        while(p2 <= end){
            temp[p++] = nums[p2++];
        }
        for(int i=0;i<temp.length;i++){
            nums[i+start] = temp[i];
        }
    }
}

题目 :两个链表的第一个公共节点

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

例如:

img

在节点c1开始相交。

思路

设交集链表长c,链表1除交集的长度为a,链表2除交集的长度为b,有:

  • a + c + b = b + c + a
  • 若无交集,则a + b = b + a
public ListNode getIntersectionNode(ListNode headA, ListNode headB){
    if(headA == null || headB == null){
        return null;
    }
    ListNode h1 = headA, h2 = headB;
    while(h1 != h2){
        h1 = h1 == null ? headB : h1.next;
        h2 = h2 == null ? headA : h2.next;        
    }
    return h1;
}

20200315

题目 在排序数组中查找数字|

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

思路 :第一眼看去一个hashmap搞定,但是给定的是一个已经排序后的数组,所有采用二分法。

code

class Solution{
    public int search(int[] nums, int target){
        int l = 0;
        int r = nums.length - 1;
        int ans = 0;
        while(l <= r){
            int temp = (l + r) >> 1;
            if(nums[temp] < target){
                l = temp + 1;
            }else if(nums[temp] > target){
                r = temp - 1;
            }else{
                for(int i = temp; i >= 0 && nums[i] == target;i--, ans++);
                for(int i = temp + 1; i < nums.length && nums[i] == target;i++,ans++);
                break;
            }
        }
        return ans;
    }
}

题目 :0~n-1中缺失的数字

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

思路 :利用二分查找,如果当前数字与当前位置相等,则说明前面的数字都没有缺失,接着对后半部分进行查找;

code

class Solution{
    public int missingNumber(int[] nums){
        int low = 0;
        int high = nums.length;
        while(low < high){
            int mid = (low + high) >> 1;
            if(nums[mid] != mid) high = mid;
            else low mid + 1;
        }
        return low;
    }
}

题目 :二叉搜索树的第k大节点

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

示例

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

思路 :二叉树的一个特性:通过中序遍历得到的序列,是有序的。

当遍历到第K大数的时,停止遍历。

code

class Solution{
    private int ans = 0, count = 0;
    public int kthLargest(TreeNode root, int k){
        helper(root,k);
        return ans;
    }
    
    private void helper(TreeNode root, int k){
        if(root.right != null) helper(root.right, k);
        
        if(++count == k){
            ans = root.val;
            return ;
        }
        if(root.left != null) helper(root.left,k);
    }
}

20200316

题目 :二叉树的深度

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

给定二叉树 [3,9,20,null,null,15,7],
	3
   / \
  9  20
    /  \
   15   7
返回它的最大深度 3 。

思路 :进行层次遍历即可知树的深度。

code

public int maxDepth(TreeNode root){
    if(root == null){
        return 0;
    }
    int count = 0;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    while(!queue.isEmpty()){
        count++;
        int size = queue.size();
        for(int i=0;i < size;i++){
            TreeNode cur = queue.poll();
            if(cur.left != null){
                queue.offer(cur.left);
            }
            if(cur.right != null){
                queue.offer(cur.right);
            }
        }
    }
    return count;
}
//双100%
class Solution{
    public int maxDepth(TreeNode root){
        if(root == null){
            return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;
        }
    }
}

题目 :平衡二叉树

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

思路 :从左下角的最小二叉树开始比较,如果左边二叉树和右边二叉树不满足题意时,判断该二叉树不是二叉树。

class Solution{
    boolean flag = true;
    public boolean isBalanced(TreeNode root){
        if(root == null){
            return true;
        }
        helper(root);
        return flag;
    }
    
    public int helper(TreeNode root){
        if(root == null){
            return 0;
        }
        int left = helper(root.left);
        int right = helper(root.right);
        if(Math.abs(left - right) > 1){
            flag = false;
        }
        return Math.max(left,right) + 1;
    }
}

题目 :数组中数字出现的次数

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

思路 :对整个数组求异或,之后进行分组

class Solution {
    public int[] singleNumbers(int[] nums) {
        int temp = 0;
        for (int num : nums) {
            temp ^= num;
        }
        int mask = temp & (-temp);
        int[] res = new int[2];
        for (int num : nums) {
            if ((num & mask) == 0) {
                res[0] ^= num;
            } else {
                res[1] ^= num;
            } 
        }
        return res;
    }
}

20200317

题目 数组中数字出现的次数||

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

示例:

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

思路 :hashmap遍历

code

class Solution {
    Map<Integer, Integer> map = new HashMap<>();

    public int singleNumber(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
        }

        for (Map.Entry entry : map.entrySet()) {
            if ((int) entry.getValue() == 1) {
                return (int) entry.getKey();
            }
        }
        return -1;
    }
}

题目 :和为s的两个数

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

示例:

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

示例 :hashmap

code

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map = new HashMap<>();
        int[] res = new int[2];
        for(int i=0;i<nums.length;i++){
            int find = target - nums[i];
            if(map.containsKey(find)){
                res[0] = nums[i];
                res[1] = find;
                break;
            }
            map.put(nums[i],i);
        }
        return res;
    }
}

双指针

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

20200318

题目 :和为s的连续正数序列

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

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

思路 :利用一个滑动窗口

code

class Solution{
    public int[][] findContinuousSequence(int target){
        List<int[]> list = new ArrayList<>();
        for(int l=1,r=1;sum=0,r < target;r++){
            sum += r;
            while(sum > target){
                sum -= l++;
            }
            if(sum == target){
                int[] temp = new int[r - l + 1];
                for(int i=0;i<temp.length;i++){
                    temp[i] = l + i;
                }
                list.add(temp);
            }
        }
        
        int[][] res = new int[list.size()][];
        for(int i=0;i<res.length;i++){
            res[i] = list.get(i);
        }
        return res;
    }
}

题目 :反转单词顺序

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

code

public String reverseWords(String s){
    //将传进来的字符串以空格拆分
    String[] strings = s.trim().split(" ");
    StringBuffer stringBuffer = new StringBuffer();
    //从尾部开始遍历
    for(int i=strings.length - 1;i >= 0;i--){
        if(strings[i].equals("")){
            continue;
        }
        if(i==0){
            stringBuffer.append(strings[i].trim());
        }else{
            stringBuffer.append(strings[i].trim()).append(" ");
        }
    }
    return stringBuffer.toString();
}

20200318

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

输入: s = "abcdefg", k = 2
输出: "cdefgab"

code

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

题目:滑动窗口的最大值

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

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

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

code

class Solution{
    public int[] maxSlidingWindow(int[] nums,int x){
        if(nums == null || nums.length == 0){
            return new int[0];
        }
        int[] res = new int[nums.length-k+1];
        Deque<Integer> queue = new ArrayDeque<>();
        for(int i=0,j=0;i < nums.length;i++){
            if(!queue.isEmpty() && i - queue.peek() >= k){
                queue.poll();
            }
            while(!queue.isEmpty() && nums[i] > nums[queue.peekLast()]){
                queue.pollLast();
            }
            queue.offer(i);
            if(i >= k - 1){
                res[j++] = nums[queue.peek];
            }
            
        }
        return res;
    }
}

20200320

题目 :队列的最大值

请定义一个队列并实现函数max_value得到队列里的最大值,要求函数max_valuepush_backpop_front的均摊时间复杂度都是$ O(1)$。

若队列为空,pop_frontmax_value 需要返回 -1

code

class MaxQueue {
	Queue<Integer> que;
    Deque<Integer> deq;
    
    public MaxQueue() {
		que = new LinkedList<>();
        deq = new LinkedList<>();
    }
    
    public int max_value() {
		return deq.size() > 0?deq.peek():-1;
    }
    
    public void push_back(int value) {
		que.offer(value);
        while(deq.size()>0 && deq.peekLast()<value){
            deq.pollLast();
        }
        deq.offerLast(value);//将value插入到deq的队尾
    }
    
    public int pop_front() {
		int tmp = que.size() > 0 ? que.poll():-1;
        if(deq.size()>0 && tmp == deq.peek()){
            deq.poll();//如果出队的元素是当前最大值,将deq的对手出队
        }
        return tmp;
    }
}

题目 :n个骰子的点数

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

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

例如:

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

思路

F(n,s)=F(n−1,s−1)+F(n−1,s−2)+F(n−1,s−3)+F(n−1,s−4)+F(n−1,s−5)+F(n−1,s−6)

class Solution{
    public double[] twoSum(int n){
        int[][] dp = new int[n+1][6*n+1];
        //边界
        for(int s = 1;s <= 6;s++) dp[1][s] = 1;
        for(int i = 2;i <= n;i++){
            for(int s=i;s<=6*i;s++){
                for(int d=1;d<=6;d++){
                    if(s-d<i-1) break;//为0了
                    dp[i][s] += dp[i-1][s-d];
                }
            }
        }
        double total = Math.pow((double)6,(double)n);
        double[] ans = new double[5*n+1];
        for(int i=n;i<=6*n;i++){
            ans[i-n]=(double)dp[n][i]/total;
        }
        return ans;
    }
}

20200321

题目 :扑克牌中的顺子

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

示例:

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

思路 :利用桶排序找出给定数组的最大、最小值。

code

class Solution{
    public boolean isStraight(int[] nums){
        int[] arr = new int[14];
        
        //把每个数放入桶里,以便找出最大、小值
        for(int i=0;i<5;i++){
            arr[nums[i]]++;
            //如果有非零的重复值则false
            if(nums[i]!=0&&arr[nums[i]]>1){
                return false;
            }
        }
        int min = -1;
        int max = 14;
        
        //找出最小数,从1开始
        for(int i=1;i<14;i++){
            if(arr[i] == 1){
                min = i;
                break;
            }
        }
        
        for(int i=13;i>0;i--){
            if(arr[i] == 1){
                max = i;
                break;
            }
        }
        return max - min <= 4;
    }
}

题目 :圆圈中最后剩下的数字

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

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

思路 :通过求余可以形成环

code

class Solution{
    public int lastRemaining(int n, int m){
        if(n==0 || m==0){
            return -1;
        }
        List<Integer> list = new ArrayList<>();
        for(int i=0;i<n;i++){
            list.add(i);
        }
        int c=(m-1)%n;
        while(list.size()!=1){
            list.remove(c);
            c = (c + m-1)%list.size();
        }
        return list.get(0);
    }
}

20200322

题目 :股票的最大利润

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

示例:

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

思路 :设置一个变量来存储最小值,遍历整个数组,利用当前值 - 最小值找到最大值,复杂度$ O(n)$

code

class Solution{
    public int maxProfit(int[] prices){
        int min = Integer.MAX_VALUE;
        int max = 0;
        for(int i=0;i<prices.length;i++){
            if(min >= prices[i]){
                min = prices[i];
            }
            max = Math.max(prices[i] - min,max)
        }
        return max;
    }
}

code

使用某天i的最低历史价格,状态表示为dp[i],状态转移方程为dp[i]=min(dp[i-1],prices[i-1])

public int maxProfit(int[] prices){
    int res = 0;
    for(int i=1;i<prices.length;i++){
        res = max(res,prices[i] - prices[i-1]);
        prices[i] = min(prices[i-1],prices[i]);
    }
    return res;
}

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

示例

输入: n = 3
输出: 6
class Solution{
    public int sumNums(int n){
		boolean b = (n==0) || ((n+=sumNums(n-1))>0);
    }
    return n;
}

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

20200323

题目 :不用加减法做加法

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

思路 :使用按位异或和按位与运算:计算a+b等价于(a^b) + ((a & b) << 1)((a & b) << 1)表示进位。

code

class Solution{
    public int add(int a,int b){
        while(a != 0){
            int temp = a ^ b;
            a = (a & b) << 1;
            b = temp;
        }
        return b;
    }
}

题目 :构建乘积数组

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B 中的元素 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

思路 :时间复杂度O(n)
把B[i] = A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]看成A[0]A[1]A[i-1]和A[i+1]…*A[n-2]*A[n-1]两部分的乘积,B可以表示成矩阵。
定义C[i] = A[0]A[1]A[i-1],
D[i] = A[i+1]
…*A[n-2]*A[n-1],
C是左下三角,D是右上三角(倒三角);
他们各自的推算公式是:
C[i] = C[i-1]*A[i-1]
D[i] = D[i+1]*A[i+1]

code

class Solution{
    public int[] constructArr(int[] a){
        if(a == null || a.length == 0){
            return a;
        }
        int length = a.length;
        int[] c = new int[length];
        c[0] = 1;
        //把c排查一列可以形成一个下三角矩阵
        for(int i=1;i<length;i++){
            c[i] = c[i-1]*a[i-1];
        }
        double temp = 1;
        //这里也可以形成上三角
        for(int i = length - 2;i >= 0;i--){
            temp *= a[i+1];
            c[i] *= temp;
        }
        return c;
    }
}

20200324

题目 :把字符串转为整数

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。

当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:

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

示例 1:

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

示例2:

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

思路 :使用正则表达式匹配,^(\s*)([-|]?)(\d+)

code1 :

public int strToInt(String str){
    String regex = "^(\\s*)([-|+]?)(\\d+)";
    Pattern r = Pattern.compile(regrex);
    Matcher matcher = r.matcher(str);
    if(matcher.find()){
        try{
            if(matcher.group(2).equals("-")){
                return -Integer.valueOf(matcher.group(3));
            }else{
                return Integer.valueOf(matcher.group(3));
            }
        }catch(NumberFormatException e){
            if(matcher.group(2).equals("-")){
                return Integer.MIN_VALUE;
            }else{
                return Integer.MAX_VALUE;
            }
        }
    }
    return 0;
}

code2 :

class Solution{
    public int strToInt(String str){
        if(str == null||str.length == 0) return 0;
        int i = 0;
        long ans = 0;
        boolean if_Fu = false;
        while(i<str.length()&&str.charAt(i) == ' ') i++;
        if(i<str.length()&&(str.charAt(i)=='+'||str.charAt(i)=='-')){
            if(str.charAt(i)=='-')isFu = true;
            i++;
        }
        for(;i<str.length()&&Character.isDigit(str.charAt(i));i++){
            ans = ans*10 + str.charAt(i)-'0';
            
            long temp_ans = is_Fu?-ans:ans;
            
            if(temp_ans<=Integer.MIN_VALUE) return Integer.MIN_VALUE;
            if(temp_ans>=Integer.MAX_VALUE) return Integer.MAX_VALUE;
        }
        return (int)(isFu? -ans:ans);
    }
}

题目 :二叉树的最近公共祖先

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

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

img

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

思路 :最近公共祖先(最近公共节点)

  • 以下几种情况:

    • 二叉树本身为空,root==null,return root

    • p.val==q.val,祖先可以是节点自己

    • p.val和q.val都小于root.val

      (只能到左子树寻找)

    • p.val和q.val都大于root.val

      (只能到右子树寻找)

    • 如果上述情况都不满足,返回root

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

20200325

题目 :二叉树的最近公共祖先(||)这个二叉树不是二叉搜索树,没有排序

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

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

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

img

示例1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
class Solution{
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q){
 		if(root == null || root == p || root == q){
            return root;
        }
        TreeNode leftNode = lowestCommonAncestor(root.left,p,q);
        TreeNode rightNode = lowestCommonAncestor(root.right,p,q);
        
        if(leftNode == null){
            return rightNode;
        }
        if(rightNode == null){
            return leftNode;
        }
        
        return root;
    }
}
     if(matcher.group(2).equals("-")){
            return Integer.MIN_VALUE;
        }else{
            return Integer.MAX_VALUE;
        }
    }
}
return 0;

}


**code2** :

```java
class Solution{
    public int strToInt(String str){
        if(str == null||str.length == 0) return 0;
        int i = 0;
        long ans = 0;
        boolean if_Fu = false;
        while(i<str.length()&&str.charAt(i) == ' ') i++;
        if(i<str.length()&&(str.charAt(i)=='+'||str.charAt(i)=='-')){
            if(str.charAt(i)=='-')isFu = true;
            i++;
        }
        for(;i<str.length()&&Character.isDigit(str.charAt(i));i++){
            ans = ans*10 + str.charAt(i)-'0';
            
            long temp_ans = is_Fu?-ans:ans;
            
            if(temp_ans<=Integer.MIN_VALUE) return Integer.MIN_VALUE;
            if(temp_ans>=Integer.MAX_VALUE) return Integer.MAX_VALUE;
        }
        return (int)(isFu? -ans:ans);
    }
}

题目 :二叉树的最近公共祖先

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

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

[外链图片转存中…(img-ehv5RKNg-1585097485019)]

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

思路 :最近公共祖先(最近公共节点)

  • 以下几种情况:

    • 二叉树本身为空,root==null,return root

    • p.val==q.val,祖先可以是节点自己

    • p.val和q.val都小于root.val

      (只能到左子树寻找)

    • p.val和q.val都大于root.val

      (只能到右子树寻找)

    • 如果上述情况都不满足,返回root

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

20200325

题目 :二叉树的最近公共祖先(||)这个二叉树不是二叉搜索树,没有排序

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

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

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

[外链图片转存中…(img-hKFK5BQh-1585097485020)]

示例1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
class Solution{
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q){
 		if(root == null || root == p || root == q){
            return root;
        }
        TreeNode leftNode = lowestCommonAncestor(root.left,p,q);
        TreeNode rightNode = lowestCommonAncestor(root.right,p,q);
        
        if(leftNode == null){
            return rightNode;
        }
        if(rightNode == null){
            return leftNode;
        }
        
        return root;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值