剑指Offer题目练习

穷举

1.和为S的连续正数序列
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。
但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。
没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。
现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

public static ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
    
    
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        if(sum == 0){
    
    
            return res;
        }
        int s = 0;
        for (int i = 1; i <= sum / 2 + 1; i++){
    
    
            s = 0;
            ArrayList<Integer> list = new ArrayList<>();
            for (int j = i; j <= sum / 2 + 1; j++){
    
    
                s += j;
                list.add(j);
                if (s == sum && list.size() > 1){
    
    
                    res.add(list);
                    break;
                }else if (s > sum){
    
    
                    list.clear();
                    break;
                }
            }
        }
        return res;
    }

数组

1.二维数组中的查找
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

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

2.裴波那契数列
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。

public static int Fibonacci(int n) {
    
    
        if (n <= 0){
    
    
            return 0;
        }else if (n == 1 || n == 2){
    
    
            return 1;
        }else{
    
    
            /*
            动态规划
             */
            int []dp = new int[n];
            dp[0] = dp[1] = 1;
            for (int i = 2 ; i < n ; i++){
    
    
                dp[i] = dp[i-1] + dp[i-2];
            }
            return dp[n-1];
        }
    }

3.调整数组顺序使奇数位于偶数面前
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,
使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

public static void reOrderArray(int [] array) {
    
    
        // odd存储奇数,even存放偶数
        List<Integer> odd = new ArrayList<>();
        List<Integer> even = new ArrayList<>();
        for (int val : array){
    
    
            if (val % 2 == 0){
    
    
                even.add(val);
            }else{
    
    
                odd.add(val);
            }
        }
        int i = 0;
        for (; i < odd.size(); i++){
    
    
            array[i] = odd.get(i);
        }
        for (int j = 0; j < even.size(); j++){
    
    
            array[i++] = even.get(j);
        }
    }

4.顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,
例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

public ArrayList<Integer> printMatrix(int [][] matrix) {
    
    
        ArrayList<Integer> res = new ArrayList<Integer>();
        if(matrix .length == 0)	return res;
        int down = matrix.length - 1;
        int right = matrix[0].length - 1;
        int count = (down + 1) * (right+ 1) , up = 0 ,left = 0 ;
        while(count > 0) {
    
    
            for(int i = left ; i <= right && count > 0; i++) {
    
    
                res.add(matrix[up][i]);
                count--;
            }
            up++;
            for(int i = up ; i <= down  && count > 0; i++) {
    
    
                res.add(matrix[i][right]);
                count--;
            }
            right--;
            for(int i = right ; i >= left  && count > 0 ;i--) {
    
    
                res.add(matrix[down][i]);
                count--;
            }
            down--;
            for(int i = down ; i >= up  && count > 0; i--) {
    
    
                res.add(matrix[i][left]);
                count--;
            }
            left++;
        }
        return res;
    }

5.数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。
由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

public static int MoreThanHalfNum_Solution(int [] array) {
    
    
        if (array == null || array.length == 0){
    
    
            return 0;
        }else if (array.length == 1){
    
    
            return array[0];
        }
        int n = array.length;
        int []count = new int[n];
        for (int val : array){
    
    
            count[val]++;
        }
        for (int i = 0 ; i < n ; i++){
    
    
            if (count[i] > n / 2){
    
    
                return i;
            }
        }
        return 0;
    }

6.把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,
打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

/*
    先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。
     */
    public static String PrintMinNumber(int [] numbers) {
    
    
        List<Integer> list=new ArrayList<Integer>();
        for (int i : numbers) {
    
    
            list.add(i);
        }
        Collections.sort(list, new Comparator<Integer>() {
    
    

            @Override
            public int compare(Integer o1, Integer o2) {
    
    
                String str1=o1+""+o2;
                String str2=o2+""+o1;
                return str1.compareTo(str2);
            }
        });
        StringBuilder res = new StringBuilder();
        for (Integer integer : list) {
    
    
            res.append(integer);
        }
        return res.toString();
    }

7.数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

public static int InversePairs(int [] array) {
    
    
        if (array == null || array.length == 0){
    
    
            return 0;
        }
        // 创建辅助数组
        int length = array.length;
        int[] copy = new int[length];
        return InversePairsCore(array, copy, 0, length - 1);
    }

    //构造递归函数
    private static int InversePairsCore(int[] array, int[] copy, int low, int high) {
    
    
        //递归结束条件
        if(low == high){
    
    
            return 0;
        }

        int mid = (low + high) >> 1; //除以2

        //分治算法,将数组分为两部分
        int leftCount = InversePairsCore(array, copy, low, mid) % 1000000007;
        int rightCount = InversePairsCore(array, copy, mid+1, high) % 1000000007;

        int count = 0;
        int i = mid;
        int j = high;
        int copyIndex = high;

        //将子数组合并、排序、计算逆序数
        while(i >= low && j > mid){
    
    

            //合并时前半部分的array[i]>array[j],
            //即array[j]前面的数字都会比array[i]小,以此来计算逆序数
            if(array[i] > array[j]){
    
    
                count = count + j-mid;
                copy[copyIndex--] = array[i--];

                //如果count过大,即对count进行取余处理
                if(count >= 1000000007)
                    count = count % 1000000007;
            }else{
    
    
                copy[copyIndex--] = array[j--];
            }
        }
        //将数组中剩余元素复制到copy数组中,排好序
        for(; i >= low; i--){
    
    
            copy[copyIndex--] = array[i];
        }
        for(; j > mid; j--){
    
    
            copy[copyIndex--] = array[j];
        }
        //将排好序的数组服回给原数组,进行下一步的合并
        for(int s = low; s <= high; s++){
    
    
            array[s] = copy[s];
        }
        return (count + leftCount + rightCount) % 1000000007;
    }

8.数字在升序数组中出现的次数
统计一个数字在升序数组中出现的次数。

public static int GetNumberOfK(int [] array , int k) {
    
    
        if (array == null || array.length == 0){
    
    
            return 0;
        }
        int n = array.length;
        int left = 0, right = n-1, count = 0;
        while (left <= right){
    
    
            int mid = (right + left) / 2;
            if (array[mid] == k){
    
    
                count++;
                left = right = mid;
                while (left > 0 && array[--left] == k){
    
    
                    count++;
                }
                while (right < n-1 && array[++right] == k){
    
    
                    count++;
                }
                break;
            }else if (array[mid] > k){
    
    
                right = mid - 1;
            }else{
    
    
                left = mid + 1;
            }
        }
        return count;
    }

9.和为S的两个数字
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

public static ArrayList<Integer> FindNumbersWithSum(int [] array, int sum) {
    
    
        if (array == null || array.length < 2){
    
    
            return new ArrayList<>();
        }
        int min = Integer.MAX_VALUE;
        ArrayList<Integer> res = new ArrayList<>();
        int left = 0, right = array.length - 1;
        while (left < right){
    
    
            if (array[left] + array[right] == sum && array[left] * array[right] < min){
    
    
                res.clear();
                min = array[left] * array[right];
                res.add(array[left]);
                res.add(array[right]);
                left++;
            }else if (array[left] + array[right] < sum){
    
    
                left++;
            }else{
    
    
                right--;
            }
        }
        return res;
    }

10.数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。
数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。
请找出数组中第一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
返回描述:
如果数组中有重复的数字,函数返回true,否则返回false。
如果数组中有重复的数字,把重复的数字放到参数duplication[0]中。(ps:duplication已经初始化,可以直接赋值使用。)

/*
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers==null||numbers.length==0){
            return false;
        }
        for(int i = 0 ; i < length ; i++) {
            int target = numbers[i];
            for(int j = i + 1 ; j < length ; j++) {
                if(target == numbers[j]) {
                    duplication[0] = target;
                    return true;
                }
            }
        }
        return false;
    }
    */
    public boolean duplicate(int numbers[],int length,int [] duplication) {
    
    
        if(numbers==null||numbers.length==0){
    
    
            return false;
        }
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < numbers.length; i++) {
    
    
            if (set.contains(numbers[i])){
    
    
                duplication[0]=numbers[i];
                return true;
            }else {
    
    
                set.add(numbers[i]);
            }
        }
        if (set.size()==length){
    
    
            return false;
        }
        return true;
    }

11.构建乘积数组
给定一个数组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]。
不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)
对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。

/*
    效率较低
     */
    /*
    public static int[] multiply(int[] A) {
        if(A==null || A.length<2){
            return new int[0];
        }
        int n = A.length;
        int []B = new int[n];
        for (int i = 0; i < n; i++){
            B[i] = 1;
            for (int j = 0 ; j < n; j++){
                if (i == j){
                    continue;
                }
                B[i] *= A[j];
            }
        }
        return B;
    }
*/
    /*
    可以发现:
        B[i]的左半部分和B[i-1]有关(将B[i]的左半部分乘积看成C[i],有C[i]=C[i-1]*A[i-1]),
        B[i]的右半部分与B[i+1]有关(将B[i]的右半部分乘积看成D[i],有D[i]=D[i+1]*A[i+1]),
  因此我们先从0到n-1遍历,计算每个B[i]的左半部分,
    然后定义一个变量temp代表右半部分的乘积,
    从n-1到0遍历,令B[i]*=temp,而每次的temp与上次的temp关系即为temp*=A[i+1]。


     */
    public static int[] multiply(int[] A) {
    
    
        if(A == null || A.length < 2) {
    
    
            return null;
        }
        int n = A.length;
        int[] B = new int[n];
        B[0] = 1;
        for(int i = 1; i < n; i++)
            B[i] = B[i-1] * A[i-1];
        int temp = 1;
        for(int i = A.length-2; i >= 0; i--){
    
    
            temp *= A[i+1];
            B[i] *= temp;
        }
        return B;
    }

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

public static int movingCount(int threshold, int rows, int cols){
    
    
        if (threshold == 0){
    
    
            return 1;
        }
        boolean [][]vistied = new boolean[rows][cols];
        return dfs(0,0,rows,cols,threshold,vistied);
    }

    public static int dfs(int i, int j, int rows, int cols, int threshold, boolean [][]vistied){
    
    
        if (i < 0 || i >= rows || j < 0 || j >= cols || getNumBitSum(i) + getNumBitSum(j) > threshold || vistied[i][j]){
    
    
            return 0;
        }
        vistied[i][j] = true;
        return 1 + dfs(i+1,j,rows,cols,threshold,vistied)
                + dfs(i-1,j,rows,cols,threshold,vistied)
                + dfs(i,j+1,rows,cols,threshold,vistied)
                + dfs(i,j-1,rows,cols,threshold,vistied);
    }

    /*
    数的数位之和
     */
    public static int getNumBitSum(int num){
    
    
        int res = 0;
        while (num != 0){
    
    
            res += num % 10;
            num /= 10;
        }
        return res;
    }

排序

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

public static ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
    
    
        ArrayList<Integer> res = new ArrayList<>();
        if(input.length < k){
    
    
            return res;
        }
        for (int i = 0 ; i < k ; i++){
    
    
            for (int j = i + 1 ; j < input.length ; j++){
    
    
                if (input[i] > input[j]){
    
    
                    int tmp = input[i];
                    input[i] = input[j];
                    input[j] = tmp;
                }
            }
            res.add(input[i]);
        }
        return res;
    }

2.数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,
那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,
那么中位数就是所有数值排序之后中间两个数的平均值。
我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

private ArrayList<Integer> arrayList = new ArrayList<>();
    private int len = 0;
    private int mid = 0;

    public void Insert(Integer num) {
    
    
        if (len == 0) {
    
    
            arrayList.add(num);
        } else {
    
    
            int i = mid;
            if (num > arrayList.get(mid)) {
    
    
                while (i < len && num > arrayList.get(i)) {
    
    
                    i++;
                }
                arrayList.add(i, num);
                if (len % 2 == 0) {
    
    
                    mid++;
                }
            } else {
    
    
                while (i >= 0 && num < arrayList.get(i)) {
    
    
                    i--;
                }
                arrayList.add(i + 1, num);
                if (len % 2 == 0) {
    
    
                    mid++;
                }
            }
        }
        len++;

    }

    public Double GetMedian() {
    
    
        if (len % 2 == 1) {
    
    
            return arrayList.get(mid) / 1.0;
        } else {
    
    
            return (arrayList.get(mid) + arrayList.get(mid + 1)) / 2.0;
        }
    }

dfs

1.输入一个字符串,按字典序打印出该字符串中字符的所有排列。
例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

public static ArrayList<String> Permutation(String str){
    
    
        ArrayList<String> res = new ArrayList<>();
        if(str == null || str.length() == 0)
            return res;
        dfs(str.toCharArray(),res,0);
        Collections.sort(res);
        return res;

    }

    public static void dfs(char[] chars, ArrayList<String> list,int start){
    
    
        if(start == chars.length - 1){
    
    
            list.add(new String(chars));
        }

        for(int j = start; j < chars.length; j++){
    
    
            if(j != start && chars[j] == chars[start]){
    
    
                continue;
            }else{
    
    
                swap(chars,start,j);
                dfs(chars,list,start + 1);
                swap(chars,start,j);
            }
        }
    }

    public static void swap(char[] chars, int i, int j){
    
    
        char t = chars[j];
        chars[j] = chars[i];
        chars[i] = t;
    }

2.矩阵中的路径
题目描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。

public static boolean hasPath(String matrix, int rows, int cols, String  str) {
    
    
        char []mart = matrix.toCharArray();
        boolean []flag = new boolean[mart.length];
        for (int i = 0; i < rows; i++){
    
    
            for (int j = 0; j < cols; j++){
    
    
                if (dfs(mart,rows,cols,i,j,str.toCharArray(),0,flag)){
    
    
                    return true;
                }
            }
        }
        return false;
    }
    public static boolean dfs(char[] matrix, int rows, int cols, int i, int j, char[] str, int k, boolean[] flag){
    
    
        int idx = i * cols + j;
        // 各种不成立的情况
        if (i < 0 || i>= rows || j <0 || j >= cols || str[k] != matrix[idx] || flag[idx]){
    
    
            return false;
        }
        // 判断完所有单词即存在
        if (k == str.length-1){
    
    
            return true;
        }
        flag[idx] = true;
        if (dfs(matrix,rows,cols, i+1,j,str,k+1,flag) ||
            dfs(matrix,rows,cols, i-1,j,str,k+1,flag) ||
            dfs(matrix,rows,cols, i,j+1,str,k+1,flag) ||
            dfs(matrix,rows,cols, i,j-1,str,k+1,flag)
        ){
    
    
            return true;
        }
        flag[idx] = false;
        return false;
    }

递归 / 动态规划

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

/*
    标记方法,双重循环
     */
    /*
    public static int FindGreatestSumOfSubArray(int[] array) {
        if (array.length == 0){
            return 0;
        }
        int max = array[0];
        for (int i = 0; i < array.length; i++){
            int cur = 0;
            for (int j = i; j < array.length ; j++){
                cur += array[j];
                if (cur > max){
                    max = cur;
                }
            }
        }
        return max;
    }
    */

    /*
    动态规划
     */
    public static int FindGreatestSumOfSubArray(int[] array) {
    
    
        if (array.length == 0){
    
    
            return 0;
        }
        int n = array.length;
        int []dp = new int[n];
        int max = dp[0] = array[0];
        for (int i = 1; i < n; i++){
    
    
            dp[i] = array[i] + dp[i-1];
            if (dp[i] < array[i]){
    
    
                dp[i] = array[i];
            }
            if (dp[i] > max){
    
    
                max = dp[i];
            }
        }
        return max;
    }

2.跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

 /*
    动态规划
     */
    /*
    public static int JumpFloor(int target) {
        if (target == 1 || target == 2){
            return target;
        }else{
            int []dp = new int[target];
            dp[0] = 1;
            dp[1] = 2;
            for (int i = 2; i < target; i++){
                dp[i] = dp[i-1] + dp[i-2];
            }
            return dp[target-1];
        }
    }
     */
    /*
    递归
     */
    public static int JumpFloor(int target) {
    
    
        if (target == 1 || target == 2){
    
    
            return target;
        }
        return JumpFloor(target-1) + JumpFloor(target-2);
    }

3.矩形覆盖
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

/*
    动态规划
     */
    /*
    public static int RectCover(int target) {
        if(target <= 2){
            return target;
        }else{
            int []dp = new int[target];
            dp[0] = 1;
            dp[1] = 2;
            for(int i = 2 ; i < target ; i++){
                dp[i] = dp[i-1] + dp[i-2];
            }
            return dp[target-1];
        }
    }
    */
    /*
    递归
     */
    public static int RectCover(int target) {
    
    
        if (target <= 2){
    
    
            return target;
        }
        return RectCover(target-1) + RectCover(target-2);
    }

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

/*
    动态规划
     */

    public static int JumpFloorII(int target) {
    
    
        if (target == 0){
    
    
            return target;
        }
        int []dp = new int[target + 1];
        dp[0] = 0;
        dp[1] = 1;
        for (int i = 2; i <= target; i++){
    
    
            dp[i] = 0;
            for (int j = 1; j < i; j++){
    
    
                dp[i] += dp[j];
            }
            dp[i] += 1;
        }
        return dp[target];
    }

1.二叉搜索树与双向链表

Node head, pre;
    public Node treeToDoublyList(Node root) {
    
    
        if(root==null) return null;
        dfs(root);

        pre.right = head;
        head.left =pre;//进行头节点和尾节点的相互指向,这两句的顺序也是可以颠倒的

        return head;

    }

    public void dfs(Node cur){
    
    
        if(cur==null) return;
        dfs(cur.left);

        //pre用于记录双向链表中位于cur左侧的节点,即上一次迭代中的cur,当pre==null时,cur左侧没有节点,即此时cur为双向链表中的头节点
        if(pre==null) head = cur;
        //反之,pre!=null时,cur左侧存在节点pre,需要进行pre.right=cur的操作。
        else pre.right = cur;
       
        cur.left = pre;//pre是否为null对这句没有影响,且这句放在上面两句if else之前也是可以的。

        pre = cur;//pre指向当前的cur
        dfs(cur.right);//全部迭代完成后,pre指向双向链表中的尾节点
    }

2.重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

/*
    dfs根据前序和中序构建二叉树
     */

    public static TreeNode reConstructBinaryTree(int [] pre,int [] in) {
    
    
        if (pre == null || in == null){
    
    
            return null;
        }
        return ContructBinaryTree(pre,in,0,0,pre.length-1,in.length-1);
    }



    public static TreeNode ContructBinaryTree(int []pre , int []in , int startPre , int startIn , int endPre , int endIn){
    
    
        if(startPre > endPre || startIn > endIn){
    
    
            return null;
        }
        //前序遍历的一个数字是根结点的值
        TreeNode root  = new TreeNode(pre[startPre]);
        //遍历中序数组,找到根结点的位置,创建左右节点
        for(int i = startIn ; i <= endIn ; i++){
    
    
            if(in[i] == pre[startPre]){
    
    
                root.left = ContructBinaryTree(pre,in,startPre+1,startIn,startPre+i-startIn,i-1);
                root.right = ContructBinaryTree(pre,in,startPre+i+1-startIn,i+1,endPre,endIn);
            }
        }
        return root;
    }

3.平衡二叉树
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树
平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

 public boolean IsBalanced_Solution(TreeNode root) {
    
    
        return backTrack(root) >= 0;
    }

    public int backTrack(TreeNode root){
    
    
        if (root == null){
    
    
            return 0;
        }
        int left_height = backTrack(root.left);
        int right_height = backTrack(root.right);
        if (left_height == -1 || right_height == -1 || Math.abs(left_height - right_height) > 1){
    
    
            return -1;
        }else{
    
    
            return Math.max(left_height, right_height) + 1;
        }
    }

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

public static boolean HasSubtree(TreeNode root1,TreeNode root2) {
    
    
    if (root1 == null || root2 == null){
    
    
        return false;
    }
    boolean flag = false;
    if (root1.val == root2.val){
    
    
        flag = isValid(root1,root2);
    }
    if (!flag){
    
    
        flag = HasSubtree(root1.left,root2);
    }
    if (!flag){
    
    
        flag = HasSubtree(root1.right,root2);
    }
    return flag;
}


public static boolean isValid(TreeNode root1,TreeNode root2){
    
    
    if (root2 == null){
    
    
        return true;
    }else if (root1 == null){
    
    
        return false;
    }else if (root1.val != root2.val){
    
    
        return false;
    }
    return isValid(root1.left, root1.left) && isValid(root1.right,root2.right);
}

5.二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像。

public TreeNode Mirror (TreeNode pRoot) {
    
    
        // write code here
        if(pRoot == null){
    
    
            return null;
        }
        TreeNode tmp = pRoot.left;
        pRoot.left = pRoot.right;
        pRoot.right = tmp;
        Mirror(pRoot.left);
        Mirror(pRoot.right);
        return pRoot;
    }

6.层序遍历二叉树
从上往下打印出二叉树的每个节点,同层节点从左至右打印。

public static ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
    
    
        ArrayList<Integer> res = new ArrayList<>();
        if (root == null) {
    
    
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
    
    
            int size = queue.size();
            for (int i = 0; i < size; i++){
    
    
                TreeNode cur = queue.poll();
                if (cur == null){
    
    
                    continue;
                }
                res.add(cur.val);
                if (cur.left != null){
    
    
                    queue.offer(cur.left);
                }
                if (cur.right != null){
    
    
                    queue.offer(cur.right);
                }
            }
        }
        return res;
    }

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

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

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

 /*
    后序遍历的序列中,最后一个数字是树的根节点, 数组中前面的数字可以分为两部分:
    第一部分是左子树节点 的值,都比根节点的值小;
    第二部分 是右子树 节点的值,都比 根 节点 的值大,
    后面用递归分别判断前后两部分 是否 符合以上原则
     */

    public static boolean VerifySquenceOfBST(int [] sequence) {
    
    
        if(sequence == null || sequence.length == 0){
    
    
            return false;
        }
        return helper(sequence,0,sequence.length-1);
    }


    public static boolean helper(int [] sequence, int start, int end){
    
    
        // 递归终止条件,即当后序遍历序列中无元素或只存在一个元素,则是合法后续序列
        if(start >= end){
    
    
            return true;
        }
        // 每一个子树的在最后一个节点都是其根节点
        int root = sequence[end];
        // 顺序查找出第一个大于当前子树根节点
        int i = start;
        while(sequence[i] < root){
    
    
            i++;
        }
        // 判断之后的节点是否都大于当前根节点,小于则返回false
        int j = i;
        for(; j < end; j++){
    
    
            if(sequence[j] <= root){
    
    
                return false;
            }
        }
        return helper(sequence,start,i-1)&&helper(sequence, i,end-1);
    }

9.二叉树中和为某一值的路径
输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。
路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
    
    
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        if (root == null){
    
    
            return res;
        }
        dfs(root, target, new ArrayList<Integer>(), res);
        return res;
    }

    public static void dfs(TreeNode root, int target, List<Integer> list, List<ArrayList<Integer>> res){
    
    
        if (root == null){
    
    
            return;
        }
        int val = root.val;
        target -= val;
        list.add(val);
        if (target == 0 && root.left == null && root.right == null){
    
    
            res.add(new ArrayList<>(list));
        }else{
    
    
            dfs(root.left, target, list, res);
            dfs(root.right, target, list, res);
        }
        list.remove(list.size()-1);
    }

10.对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的

boolean isSymmetrical(TreeNode pRoot) {
    
    
        if (pRoot == null){
    
    
            return true;
        }
        return isSymmetrical(pRoot.left,pRoot.right);
    }

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

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

public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
    
    
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        if (pRoot == null){
    
    
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        boolean flag = false;
        queue.offer(pRoot);
        while (!queue.isEmpty()){
    
    
            int size = queue.size();
            Deque<Integer> list = new LinkedList<>();
            for (int i = 0; i < size; i++){
    
    
                TreeNode cur = queue.poll();
                if (cur == null){
    
    
                    continue;
                }
                if (!flag){
    
    
                    list.offerLast(cur.val);
                }else{
    
    
                    list.offerFirst(cur.val);
                }
                queue.offer(cur.left);
                queue.offer(cur.right);
            }
            if (!list.isEmpty()){
    
    
                res.add(new ArrayList<>(list));
            }
            flag = !flag;
        }
        return res;
    }

12.把二叉树打印成多行
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

 ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
    
    
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        if (pRoot == null){
    
    
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(pRoot);
        while (!queue.isEmpty()){
    
    
            int size = queue.size();
            ArrayList<Integer> list = new ArrayList<>();
            for (int i = 0; i < size; i++){
    
    
                TreeNode cur = queue.poll();
                if (cur == null){
    
    
                    continue;
                }
                list.add(cur.val);
                queue.offer(cur.left);
                queue.offer(cur.right);
            }
            if (!list.isEmpty()){
    
    
                res.add(list);
            }
        }
        return res;
    }

13.二叉搜索树的第k个结点
给定一棵二叉搜索树,请找出其中的第k小的TreeNode结点。

ArrayList<TreeNode> res = new ArrayList<>();

    TreeNode KthNode(TreeNode pRoot, int k) {
    
    
        helper(pRoot);
        if (k == 0 || k > res.size()){
    
    
            return null;
        }else{
    
    
            return res.get(k-1);
        }
    }

    public  void helper(TreeNode root){
    
    
        if (root == null){
    
    
            return;
        }
        helper(root.left);
        res.add(root);
        helper(root.right);
    }

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

二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,
从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,
序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树

String Serialize(TreeNode root) {
    
    
        StringBuilder sb = new StringBuilder();
        Queue<TreeNode> queue = new LinkedList<>();
        if (root != null){
    
    
            queue.offer(root);
        }
        while (!queue.isEmpty()){
    
    
            TreeNode node = queue.poll();
            if (node != null){
    
    
                sb.append(node.val + ",");
                queue.offer(node.left);
                queue.offer(node.right);
            }else{
    
    
                sb.append("#" + ",");
            }
        }
        if (sb.length() > 0){
    
    
            // 删除最后的逗号
            sb.deleteCharAt(sb.length()-1);
        }
        return sb.toString();
    }
    TreeNode Deserialize(String str) {
    
    
        TreeNode root = null;
        if (str == null || str.length() < 1){
    
    
            return root;
        }
        String []nodes = str.split(",");
        TreeNode[] treeNodes = new TreeNode[nodes.length];
        for (int i = 0; i < nodes.length; i++){
    
    
            if (!nodes[i].equals("#")){
    
    
                treeNodes[i] = new TreeNode(Integer.parseInt(nodes[i]));
            }
        }
        for (int i = 0, j = 1; j < treeNodes.length; i++){
    
    
            if (treeNodes[i] != null){
    
    
                treeNodes[i].left = treeNodes[j++];
                treeNodes[i].right = treeNodes[j++];
            }
        }
        return treeNodes[0];
    }

15.二叉树的下一个结点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

/*
思路:
首先这道题给出的是中序遍历这个二叉树,那么就是左根右。我们在求一个结点的下一个结点,那么这个时候我们需要分情况讨论:
1、如果该结点有右子树,则该结点的下一个结点为该结点的右子树的最左结点。
2、如果该结点没有右子树,则又分两种情况讨论:
情况一:如果该节点为父节点的左子节点,则下一个节点为其父节点。
情况二:如果该节点为父节点的右子节点,则沿着父节点向上遍历,直到找到一个节点的父节点的左子节点为该节点,则该节点的父节点下一个节点
*/
    public TreeLinkNode GetNext(TreeLinkNode pNode){
    
    
        if (pNode == null){
    
    
            return null;
        }
        if (pNode.right != null){
    
    
            pNode = pNode.right;
            while (pNode.left != null){
    
    
                pNode = pNode.left;
            }
            return pNode;
        }
        while (pNode.next != null){
    
    
            if (pNode.next.left == pNode){
    
    
                return pNode.next;
            }
            pNode = pNode.next;
        }
        return null;
    }

数学

1.二进制中1的个数
输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。

public static int NumberOf1(int n) {
    
    
        /*没考虑到负数
        StringBuilder s = new StringBuilder();
        while(n != 0){
            s.append(n%2);
            n = n / 2;
        }
        String res = s.reverse().toString();
        int count = 0;
        for(int i = 0 ; i < res.length() ; i++){
            if(s.charAt(i) == '1'){
                count++;
            }
        }
        return count;
        */
        // 运用了Integer的方法
        int count = 0;
        String s = Integer.toBinaryString(n);
        for(int i = 0 ; i < s.length() ; i++){
    
    
            if(s.charAt(i) == '1'){
    
    
                count++;
            }
        }
        return count;
    }

2.数值的整数次方
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0

public static double Power(double base, int exponent) {
    
    
        if (exponent < 0){
    
    
            exponent = -exponent;
            base = 1/base;
        }
        return calPower(base, exponent);
    }

    public static double calPower(double base, int exponent) {
    
    
        if (exponent == 0){
    
    
            return 1.0;
        }
        double res = calPower(base, exponent / 2);
        if (exponent % 2 == 1){
    
    
            return res * res * base;
        }else{
    
    
            return res * res;
        }
    }

3.整数中出现1的次数
求出1-13的整数中1出现的次数,并算出100-1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

public int NumberOf1Between1AndN_Solution(int n) {
    
    
        int count = 0 ;
         for(int i = 1 ; i <= n ; i++){
    
    
             String s = String.valueOf(i);
             for(int j = 0 ; j < s.length() ; j++){
    
    
                 if(s.charAt(j) == '1'){
    
    
                     count++;
                 }
             }
             
         }
        return count;
    }

4.丑数

把只包含质因子2、3和5的数称作丑数(Ugly Number)。
例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

public static int GetUglyNumber_Solution(int index) {
    
    
        if (index <= 0){
    
    
            return 0;
        }
        int []res = new int[index];
        res[0] = 1;
        int p2 = 0, p3 = 0, p5 = 0, count = 1;
        while (count < index){
    
    
            int val = Math.min(res[p2] * 2, Math.min(res[p3] * 3, res[p5] * 5));
            if (val == res[p2] * 2){
    
    
                p2++;
            }
            if (val == res[p3] * 3){
    
    
                p3++;
            }
            if (val == res[p5] * 5){
    
    
                p5++;
            }
            res[count++] = val;
        }
        return res[index-1];
    }

5.孩子们的游戏(约瑟夫问题)
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。
其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,
然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…
直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。
请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

如果没有小朋友,请返回-1


/*
通过对约瑟夫环的推导,我们可以得出一个公式x`=(x+m%n)%n=(x+m)%n,利用这个公式我们可以更快的得出解。
*/

public static int LastRemaining_Solution(int n, int m) {
    
    
        if (n == 0){
    
    
            return -1;
        }
        int idx = 0;
        for (int i = 2; i <= n; i++){
    
    
            idx = (idx + m) % i;
            //System.out.println("i= " + i + "  idx=" + idx);
        }
        return idx;
    }

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

public static int Sum_Solution(int n) {
    
    
        int sum = 0;
        for (int i = 1; i <= n; i++){
    
    
            sum += i;
        }
        return sum;
    }

7.不用加减乘除做加法
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号

/*
通过位运算,模拟加法
 */
    public static int Add(int num1,int num2) {
    
    
        int sum = 0;
        int carry = 0;
        while (num2 != 0){
    
    
            // 按位异或运算符(^)
            sum = num1 ^ num2;
            carry = (num1 & num2) << 1;
            num1 = sum;
            num2 = carry;
        }
        return sum;
    }

8.把字符串转换成整数
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0

public static int StrToInt(String str) {
    
    
        if(str == null || str == "0" || str.equals(""))
            return 0;
        boolean flag = str.charAt(0) == '-'?  false:true;
        int res = 0 , k = 1;
        for(int i = str.length() - 1 ; i >= 0  ; i--){
    
    
            if(str.charAt(i) >= '0' && str.charAt(i) <= '9' ){
    
    
                res = res + (str.charAt(i) - '0') * k;
                k = k * 10;
            }else if(str.charAt(i) == '-' || str.charAt(i) == '+'){
    
    
                continue;
            }else{
    
    
                return 0;
            }
        }
        if(flag) {
    
    
            return res;
        }else {
    
    
            return -res;
        }
    }

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

/*
    思路分析:
        长度为n的绳子,请把绳子剪成整数长度的m段,长度为3的段数越多(切割越接近自然底数(约等于2.7)乘积越大!),最后的乘积越大。
        所以尽可能的划分更多的3,最多能够划分长度为3的块数:n/3。剩下的长度根据n%3划分为三种情况:
        0:全部切割为长度为三。
        1:减少一块3,和剩下的1凑成4,然后分成两块各为2。
        2:最后一块就是2。

     */

    public static int cutRope(int target) {
    
    
        if(target == 2) {
    
    
            return 1;
        }else if(target == 3) {
    
    
            return 2;
        }
        int num = target / 3;
        int flag = target % 3;
        if (flag == 0){
    
    
            return (int)Math.pow(3,num);
        }else if (flag == 1){
    
    
            return (int)Math.pow(3,num-1) * 4;
        }else{
    
    
            return (int)Math.pow(3,num) * 2;
        }
    }

10.数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。
数组中某些数字是重复的,但不知道有几个数字是重复的。
也不知道每个数字重复几次。请找出数组中第一个重复的数字。
例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是第一个重复的数字2。没有重复的数字返回-1。

public static int duplicate (int[] numbers) {
    
    
        // write code here
        ArrayList<Integer> list = new ArrayList<>();
        for (int val : numbers){
    
    
            if (list.contains(val)){
    
    
                return val;
            }else{
    
    
                list.add(val);
            }
        }
        return -1;
    }

双指针

1.滑动窗口的最大值
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
窗口大于数组长度的时候,返回空

public static ArrayList<Integer> maxInWindows(int[] num, int size) {
    
    
        ArrayList<Integer> res = new ArrayList<>();
        if (size <= 0 || num == null || size > num.length) {
    
    
            return res;
        }
        for (int i = 0; i <= num.length - size; i++) {
    
    
            int max = 0;
            for (int j = i; j < i + size; j++) {
    
    
                if (max <= num[j]) {
    
    
                    max = num[j];
                }
            }
            res.add(max);
        }
        return res;
    }

二分

1.旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0public static int minNumberInRotateArray(int [] array) {
    
    
        if (array == null || array.length == 0){
    
    
            return 0;
        }
        int left = 0, mid = 0, right = array.length - 1;
        while (array[left] >= array[right]){
    
    
            if (right - left == 1){
    
    
                mid = right;
                break;
            }
            mid = (right - left) / 2 + left;
            if (array[left] <= array[mid]){
    
    
                left = mid;
            }
            if (array[mid] <= array[right]){
    
    
                right = mid;
            }
        }
        return array[mid];
    }

位运算

1.数组中只出现一次的两个数字
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

/*
    利用了HashSet
     */
    /*
    public int[] FindNumsAppearOnce (int[] array) {
        int []res = new int[2];
        HashSet<Integer> set = new HashSet<>();
        for (int val : array){
            if (set.contains(val)){
                set.remove(val);
            }else{
                set.add(val);
            }
        }
        int i = 0;
        for (int val : set){
            res[i++] = val;
        }
        return res;
    }
    */

    /*
    位方法
    把两个只出现一次的数记为a、b
    1、将数组中所有元素进行异或操作,因为相同的数异或为0,这样得到的结果就是a异或b的值。
    2、因为a和b肯定不相等,所以第一步得到的结果肯定不为0.也就是说此结果写成二进制至少有一位是1,
        找到这个为1的下标。用这一位我们可以把数组中的数分成两部分,一部分是这一位为1的数,
        一部分是这一位为0的数。a和b肯定不在同一个部分。数组中原来相同的数肯定在同一个部分。
    3、将这两部分数分别进行异或运算。最后每部分异或的结果就是a和b。
     */

    public int[] FindNumsAppearOnce (int[] array) {
    
    
        int xor = 0;
        for (int num : array) {
    
    
            xor ^= num;
        }
        int i;
        for (i = 0; i < 32; i++) {
    
    
            if ((xor & (1 << i)) != 0) break;
        }
        int a = 0;
        int b = 0;
        for (int num : array) {
    
    
            if ((num & (1 << i)) == 0) {
    
    
                a ^= num;
            } else {
    
    
                b ^= num;
            }
        }
        return new int[]{
    
    Math.min(a, b), Math.max(a, b)};
    }

字符串

1.替换空格
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

/*
    利用方法替换
     */
    /*
    public static String replaceSpace (String s) {
        return s.replaceAll(" ","%20");
    }
    */
    public static String replaceSpace (String s) {
    
    
        if (s == null){
    
    
            return null;
        }
        String res = "";
        for (int i = 0; i < s.length(); i++){
    
    
            if (s.charAt(i) == ' '){
    
    
                res += "%20";
            }else{
    
    
                res += s.charAt(i);
            }
        }
        return res;
    }

2.第一个只出现一次的字符
在一个字符串(0<=字符串长度<=10000,全部由字母组成)
中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)

/*
    利用map解法
     */
    /*
    public static int FirstNotRepeatingChar(String str) {
        if(str == null || str.equals("")){
            return -1;
        }
        String []arr = str.split("");
        HashMap<String,Integer> mark = new HashMap<String,Integer>();
        for(String s : arr){
                mark.put(s , mark.getOrDefault(s ,0)+1);
        }
        for(int i = 0 ; i < arr.length ; i++) {
            if(mark.get(arr[i]) == 1) {
                return i;
            }
        }
        return -1;
    }
    */

    public static int FirstNotRepeatingChar(String str) {
    
    
        if(str == null || str.equals("")){
    
    
            return -1;
        }
        // 大小写字母共26+26=52,加上中间的6个其他字符,共58个
        int []mark = new int[58];
        for (int i = 0; i < str.length(); i++){
    
    
            mark[str.charAt(i)-65] += 1;
        }
        for (int i = 0; i < str.length(); i++){
    
    
            if (mark[str.charAt(i)-65] == 1){
    
    
                return i;
            }
        }
        return -1;
    }

3.左旋转字符串
汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。
对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。
例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

 /*
    public static String LeftRotateString(String str,int n) {
        if(str == null || str.equals(""))
			return "";
		String s1 = str.substring(n,str.length());
		String s2 = str.replace(s1, "");
		return new StringBuilder(s1).append(s2).toString();
    }
    */
    public static String LeftRotateString(String str,int n) {
    
    
        if (str == null || str.equals(""))
            return "";
        String res = "";
        for (int i = n; i < str.length(); i++){
    
    
            res += str.charAt(i);
        }
        res += str.substring(0,n);
        return res;
    }

4.翻转单词顺序列
牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。
同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。
例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了
,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

public static String ReverseSentence(String str) {
    
    
        if(str == null || str.trim().equals("")){
    
    
            return str;
        }
        String []arrs = str.split(" ");
        StringBuilder res = new StringBuilder();
        for(int i = arrs.length-1 ; i >= 0 ; i--){
    
    
            res.append(arrs[i]);
            if(i != 0){
    
    
                res.append(" ");
            }
        }
        return res.toString();
    }

5.扑克牌顺子
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…
他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!
“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,
他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。
上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。
现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何,
如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

/*
    public static boolean isContinuous(int [] numbers) {
        if(numbers == null || numbers.length == 0) {
			return false;
		}
		Arrays.sort(numbers);
		int k = 0;
		for(int i = 0 ; i < numbers.length-1 ; i++) {
			if(numbers[i] == 0) {
				k++;	//0个数
			}else {
				if(numbers[i+1] - numbers[i] == 1) {
					continue;
				}else if(numbers[i+1] - numbers[i] > 1 && k > 0){
					k = k - (numbers[i+1] - numbers[i]) + 1;
					//System.out.println(k);
					if(k < 0)	return false;
				}else if(numbers[i+1] == numbers[i]){	//对子
					return false;
				}else {
					return false;
				}
			}
		}
        return true;
    }
     */


    public static boolean isContinuous(int [] numbers) {
    
    
        if(numbers == null || numbers.length == 0) {
    
    
            return false;
        }
        // 数组排序
        Arrays.sort(numbers);
        int count = 0;  //记录大小王的个数
        int flag;   //记录前一个数和后一个数中间缺几个数
        for (int i = 0; i < numbers.length - 1; i++){
    
    
            if (numbers[i] == 0){
    
    
                count++;
            }else{
    
    
                flag = numbers[i+1] - numbers[i] - 1;
                // flag > count:缺的数大于大王和小王数,凑不成顺子
                // flag < 0:表明这两张牌师对子
                if (flag > count || flag < 0){
    
    
                    return false;
                }else{
    
    
                    // 减去替代的几个数
                    count -= flag;
                }
            }
        }
        return true;
    }

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

public static boolean match (String str, String pattern) {
    
    
        if (pattern.isEmpty()){
    
    
            return str.isEmpty();
        }
        //判断两个首字符是否相等或第一个字符是否为.,不相等或不为.,结果false
        boolean first_char = !str.isEmpty() && (str.charAt(0) == pattern.charAt(0) || pattern.charAt(0) == '.');
        if (pattern.length() >= 2 && pattern.charAt(1) == '*'){
    
    
            //Match(str, p.substring(2)) 这种情况是判断0个的情况,因为a*,可以代表0个,即空
            //(first_char && Match(str.substring(1), p))这种情况是判断重复1个或多个的情况,因此需要把S给截取掉,然后一直跟*比较
            return match(str,pattern.substring(2)) || (first_char && match(str.substring(1),pattern));
        }else{
    
    
            // 没有 * 的情况
            return first_char && match(str.substring(1),pattern.substring(1));
        }
    }

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

public static boolean isNumeric (String str) {
    
    
        if (str == null || str.equals("")){
    
    
            return false;
        }
        boolean e = false, point = false, num = false;
        char[] arr = str.toCharArray();
        for (int i = 0; i < arr.length; i++){
    
    
            if (arr[i] >= '0' && arr[i] <= '9'){
    
    
                num = true;
            }else if (arr[i] == '.'){
    
    
                if (e || point){
    
    
                    return false;
                }
                point = true;
            }else if (arr[i] == 'e' || arr[i] == 'E'){
    
    
                if (!num || e){
    
    
                    return false;
                }
                num = false;
                e = true;
            }else if (arr[i] == '+' || arr[i] == '-'){
    
    
                if (i != 0 && arr[i-1] != 'e' && arr[i-1] != 'E'){
    
    
                    return false;
                }
            }else{
    
    
                return false;
            }
        }
        return num;
    }

8.字符流中第一个不重复的字符
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

ArrayList<Character> list = new ArrayList<>();
    public void Insert(char ch)
    {
    
    
        if(list.contains(ch)){
    
    
            list.remove((Character)ch);
        }else 
            list.add(ch);
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
    
    
        if(list.isEmpty()) return '#';
        else return list.get(0);
    }

链表

1.从尾到头打印链表
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    
    
        ArrayList<Integer> list = new  ArrayList<>();
        while(listNode != null){
    
    
            list.add(listNode.val);
            listNode = listNode.next;
        }
        ArrayList<Integer> res = new  ArrayList<>();
        for(int i = list.size()-1 ; i >= 0 ; i--){
    
    
            res.add(list.get(i));
        }
        return res;
    }

2.链表中倒数第k个结点
输入一个链表,输出该链表中倒数第k个结点。

public static ListNode FindKthToTail (ListNode pHead, int k) {
    
    
        int len = ListNodeLength(pHead);
        if (k > len){
    
    
            return null;
        }
        for (int i = 0; i < len - k; i++){
    
    
            pHead = pHead.next;
        }
        return pHead;
    }

    public static int ListNodeLength(ListNode head){
    
    
        int count = 0;
        while (head != null){
    
    
            count++;
            head = head.next;
        }
        return count;
    }

3.反转链表
输入一个链表,反转链表后,输出新链表的表头。

public static ListNode ReverseList(ListNode head) {
    
    
        ListNode pre = null, cur = head;
        while (cur != null){
    
    
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }

4.合并两个排序的链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

public static ListNode Merge(ListNode list1,ListNode list2) {
    
    
        if (list1 == null && list1 == null){
    
    
            return null;
        }else if (list1 == null){
    
    
            return list2;
        }else if (list2 == null){
    
    
            return list1;
        }
        ListNode head = new ListNode(-1);
        ListNode cur = head;
        while (list1 != null && list2 != null){
    
    
            if (list1.val < list2.val){
    
    
                cur.next = list1;
                list1 = list1.next;
            }else{
    
    
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        if (list1 == null){
    
    
            cur.next = list2;
        }else{
    
    
            cur.next = list1;
        }
        return head.next;
    }

5.复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

复杂结点结构:

public class RandomListNode {
    
    

    int label;
    RandomListNode next = null, random = null;
    public RandomListNode(int label){
    
    
        this.label = label;
    }

}
 /*
    利用map存储原先结点,再进行复制
     */

    public RandomListNode Clone(RandomListNode pHead) {
    
    
        if (pHead == null){
    
    
            return null;
        }
        // head 为返回的头
        RandomListNode head = new RandomListNode(pHead.label);
        // 获取当前链表头
        RandomListNode cur = pHead;
        // 获取新的链表头
        RandomListNode p = head;
        HashMap<RandomListNode, RandomListNode> map = new HashMap<>();
        // 原链表存入map
        while (pHead != null){
    
    
            map.put(pHead, new RandomListNode(pHead.label));
            pHead = pHead.next;
        }
        // head 作为新链表头,由cur,p移动复制链表
        while (cur != null){
    
    
            p.next = map.get(cur.next);
            p.random = map.get(cur.random);
            cur = cur.next;
            p = p.next;
        }
        return head;
    }

6.两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

public static ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
    
    
        if (pHead1 == null || pHead2 == null){
    
    
            return null;
        }else if (pHead1.val == pHead2.val){
    
    
            return pHead1;
        }
        ListNode p1 = pHead1, p2 = pHead2;
        int len1 = 0, len2 = 0;
        while (p1 != null){
    
    
            len1++;
            p1 = p1.next;
        }
        while (p2 != null){
    
    
            len2++;
            p2 = p2.next;
        }
        p1 = pHead1;
        p2 = pHead2;
        if (len1 > len2){
    
    
            for (int i = 0; i < len1 - len2; i++){
    
    
                p1 = p1.next;
            }
        }else{
    
    
            for (int i = 0; i < len2 - len1; i++){
    
    
                p2 = p2.next;
            }
        }
        while (p1 != null && p2 != null){
    
    
            if (p1.val == p2.val){
    
    
                break;
            }
            p1 = p1.next;
            p2 = p2.next;
        }
        return p1;
    }

7.链表中环的入口结点
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

/*
    利用hashset的方法
     */
    /*
    public ListNode EntryNodeOfLoop(ListNode pHead) {
        HashSet<ListNode> set = new HashSet<>();
        while (pHead != null){
            if (set.contains(pHead)){
                return pHead;
            }
            set.add(pHead);
            pHead = pHead.next;
        }
        return null;
    }
     */
    /*
    利用快慢指针
     */
    public ListNode EntryNodeOfLoop(ListNode pHead) {
    
    
        if (pHead == null || pHead.next == null){
    
    
            return null;
        }
        ListNode low = pHead, fast = pHead.next;
        while (low != fast){
    
    
            low = low.next;
            fast = fast.next.next;
        }
        low = pHead;
        fast = fast.next;
        while (low != fast){
    
    
            low = low.next;
            fast = fast.next;
        }
        return low;
    }

8.删除链表中重复的结点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,
重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

public ListNode deleteDuplication(ListNode pHead){
    
    
        if (pHead == null || pHead.next == null){
    
    
            return pHead;
        }
        ListNode head = new ListNode(-1);
        head.next = pHead;
        ListNode p1 = head, p2 = head.next;
        while (p2 != null){
    
    
            if (p2.next != null && p2.val == p2.next.val){
    
    
                while (p2.next != null && p2.val == p2.next.val){
    
    
                    p2 = p2.next;
                }
                p1.next = p2.next;
                p2 = p2.next;
            }else{
    
    
                p1 = p1.next;
                p2 = p2.next;
            }
        }
        return head.next;
    }

1.用两个栈实现队列
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

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

    public void push(int node) {
    
    
        s1.push(node);
    }

    public int pop() {
    
    
        while (!s1.isEmpty()){
    
    
            s2.push(s1.pop());
        }
        int res = s2.pop();
        while (!s2.isEmpty()){
    
    
            s1.push(s2.pop());
        }
        return res;
    }

2.包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

Stack<Integer> stack = new Stack<>();
    public void push(int node) {
    
    
        stack.push(node);
    }

    public void pop() {
    
    
        stack.pop();
    }

    public int top() {
    
    
        return stack.peek();
    }

    public int min() {
    
    
        int min = stack.peek();
        int tmp = 0;
        Iterator<Integer> iterator = stack.iterator();
        while (iterator.hasNext()){
    
    
            tmp = iterator.next();
            if (tmp < min){
    
    
                min = tmp;
            }
        }
        return min;
    }

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

 /*
    借助栈,通过循环和消除进行操作,最终栈为空说明匹配消除了
     */

    public static boolean IsPopOrder(int [] pushA,int [] popA) {
    
    
        if (pushA == null || popA == null || popA.length != pushA.length){
    
    
            return false;
        }
        Stack<Integer> stack = new Stack<>();
        int i = 0, j = 0;
        for(; i < pushA.length ; i++){
    
    
            stack.push(pushA[i]); //入栈
            System.out.println(stack.peek());
            for(; j < popA.length && !stack.isEmpty() && stack.peek() == popA[j] ; j++){
    
     //找到对应就出栈
                stack.pop();
            }
        }
        return stack.isEmpty();
    }

猜你喜欢

转载自blog.csdn.net/qq_42748009/article/details/113841770
今日推荐