刷题记录之牛客网剑指Offer66题Java版(详细注释)

目录

01.二维数组中的查找

02.替换空格

03.从尾到头打印链表

04.根据前序和中序重建二叉树

05.用两个栈实现队列

06.旋转数组的最小数字

07.斐波那契数列

08.跳台阶

09.升级版跳台阶

10.矩形覆盖

11.二进制中1的个数

12.数值的整数次方

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

14.链表中倒数第k个节点

15.反转链表

16.合并两个排序的链表

17.树的子结构

18.二叉树的镜像

19.顺时针打印矩阵

20.包含min函数的栈

21.栈的压入、弹出序列

22.从上至下逐层打印二叉树

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

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

25.复杂链表的复制

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

27.字符串的排列

28.数组中出现次数超过一半的数字

29.最小的k个数

30.连续子数组的最大和

31.整数中1出现的次数(从1到n整数中1出现的次数)

32.把数组排成最小的数

33.丑数

34.第一个只出现一次的字符

35.数组中的逆序对

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

37.数字在排序数组中出现的次数

38.二叉树的深度

39.平衡二叉树

40.数组中只出现一次的数字

41.和为S的连续正数序列

42.和为S的两个数字

43.左旋转字符串

44.旋转单词顺序列

45.扑克牌顺子

46.孩子们的游戏(圆圈中最后剩下的数)

47.求1+2+3+...+n

48.不用加减乘除做加法

49.把字符串转换成整数

50.数组中重复的数字

51.构建乘积数组

52.正则表达式匹配

53.表示数值的字符串

54.字符流中第一个不重复的字符

55.链表中环的入口节点

56.删除链表中重复的节点

57.二叉树的下一个节点

58.对称的二叉树

59.按之字形打印二叉树

60.把二叉树打印成多行

61.序列化二叉树

62.二叉搜索树的第k个节点

63.数据流的中位数

64.滑动窗口的最大值

65.矩阵中的路径

66.机器人的运动范围

END


01.二维数组中的查找

题目描述

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

public class Solution {
    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])
                return true;
            //target小于当前值,说明下方和右方整个块全部作废,因此左移1位
            else if(target < array[row][col])
                col--;
            else
                row++;
        }
        return false;
    }
}

02.替换空格

题目描述

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

public class Solution {
    public String replaceSpace(StringBuffer str) {
        String s = str.toString();
        String res = "";
        for(int i = 0; i < s.length(); i++) {
            if(s.charAt(i) == ' ')
                res += "%20";
            else
                res += s.charAt(i);
        }
        return res;
    }
}

03.从尾到头打印链表

题目描述

输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        ListNode head = reverseList(listNode);
        while(head != null) {
            arrayList.add(head.val);
            head = head.next;
        }
        return arrayList;
    }
    //链表转置函数
    public static ListNode reverseList(ListNode listNode) {
        ListNode tail = null;
        while(listNode != null){
            ListNode p = listNode.next;  //储存next
            listNode.next = tail;
            tail = listNode;  //更新tail
            listNode = p;
        }
        return tail;
    }
}

04.根据前序和中序重建二叉树

题目描述

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

public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        return reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1);
    }
    //用下标获取每个子树的前序和中序序列
    public TreeNode reConstructBinaryTree(int [] pre, int preStart, int preEnd, 
                                          int [] in, int inStart, int inEnd) {
        if(preStart > preEnd || inStart > inEnd)  //序列为空,返回null
            return null;
        TreeNode res = new TreeNode(pre[preStart]);  //前序第1个值是根节点
        if(preStart == preEnd)  //只有一个节点
            return res;
        int leftCount = 0;  //左子树的大小
        for(int i = inStart; in[i] != pre[preStart]; i++)
            leftCount++;
        res.left = reConstructBinaryTree(pre, preStart + 1, preStart + leftCount,
                                         in, inStart, inStart + leftCount - 1);
        res.right = reConstructBinaryTree(pre, preStart + leftCount + 1, preEnd,
                                          in, inStart + leftCount + 1, inEnd);
        return res;
    }
}

05.用两个栈实现队列

题目描述

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

import java.util.Stack;
public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
        if(stack2.isEmpty()) {  //stack2为空时,stack1中要全部移入stack2保证顺序
            while(!stack1.isEmpty())
                stack2.push(stack1.pop());
        }
        return stack2.pop();    
    }
}

06.旋转数组的最小数字

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length == 0)
            return 0;
        int start = 0, end = array.length - 1;
        //二分查找
        while(start < end) {
            int mid = (start + end) / 2;
            if(array[mid] < array[start])  //最小值在左半边(要包括mid)
                end = mid;
            else if(array[mid] > array[end])  //最小值在右半边(不包括mid)
                start = mid + 1;
            else  //即 array[start] <= array[mid] <= array[end]
                return array[start];
                
        }
        return array[start];
    }
}

07.斐波那契数列

题目描述

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

n<=39

public class Solution {
    public int Fibonacci(int n) {
        int a = 0, b = 1;
        for(int i = 0; i < n / 2; i++) {
            a += b;
            b += a;
        }
        return n % 2 == 0 ? a : b;
    }
}

08.跳台阶

题目描述

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

public class Solution {
    public int JumpFloor(int target) {
        //还是斐波那契数列
        int a = 1, b = 2;
        for(int i = 0; i < (target - 1) / 2; i++) {
            a += b;
            b += a;
        }
        return target % 2 == 1 ? a : b;
    }
}

09.升级版跳台阶

题目描述

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

public class Solution {
    public int JumpFloorII(int target) {
        /*
        a[1] = 1, a[2] = 2
        根据最后1步跳的距离区分,是1到target-1时,跳法数分别对应a[target-1]到a[1]
        最后再加1(就是最后1步跳整个target长)
        a[n] = 1+a[n-1]+a[n-2]+...+a[1] = 1+S[n-1]
        S[n] = a[n]+S[n-1] = 2*S[n-1]+1
        S[n]+1 为等比数列
        最后得通项公式为 a[n] = 2^(n-1)
        */
        return (int)Math.pow(2.0, (double)(target - 1));
    }
}

10.矩形覆盖

题目描述

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

public class Solution {
    public int RectCover(int target) {
        //又是斐波那契数列
        if(target == 0)
            return 0;
        int a = 1, b = 2;
        for(int i = 0; i < (target - 1) / 2; i++) {
            a += b;
            b += a;
        }
        return target % 2 == 1 ? a : b;
    }
}

11.二进制中1的个数

题目描述

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

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while(n != 0){
            count++;
            // n&(n-1)可以消掉最右边的一个1
            n = n & (n - 1);
         }
        return count;
    }
}

12.数值的整数次方

题目描述

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

public class Solution {
    public double Power(double base, int exponent) {
        if(exponent == 0)
            return 1.0;
        boolean isNegExponent = false;
        //注意判断负指数
        if(exponent < 0) {
            isNegExponent = true;
            exponent = -exponent;
        }
        //直接递归求根号值就ok
        double sqrtRes = Power(base, exponent / 2);
        double res = 1.0;
        if(exponent % 2 == 0)
            res = sqrtRes * sqrtRes;
        else
            res = sqrtRes * sqrtRes * base;
        if(isNegExponent)
            return 1 / res;
        else
            return res;
    }
}

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

题目描述

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

public class Solution {
    public void reOrderArray(int [] array) {
        // 和插入排序相同
        int p = 0;  // 插入位置
        for (int i = 0; i < array.length; i++) {
            // 只需对奇数向前插入,偶数不用操作
            if (array[i] % 2 != 0) {
                int temp = array[i];
                for (int j = i - 1; j >= p; j--) {
                    array[j + 1] = array[j];
                }
                array[p] = temp;
                p++;
            }
        }
    }
}

14.链表中倒数第k个节点

题目描述

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

public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if (head == null || k <= 0) {
            return null;
        }
        ListNode p1 = head, p2 = head;
        int i = 0;
        while (p2 != null && i < k) {
            p2 = p2.next;
            i++;
        }
        // 长度不够返回null
        if (i < k) {
            return null;
        }
        while (p2 != null) {
            p1 = p1.next;
            p2 = p2.next;
        }
        return p1;
    }
}

15.反转链表

题目描述

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

public class Solution {
    //链表转置函数
    public static ListNode ReverseList(ListNode head) {
        ListNode tail = null;
        while(head != null){
            ListNode p = head.next;  //储存next
            head.next = tail;
            tail = head;  //更新tail
            head = p;
        }
        return tail;
    }
}

16.合并两个排序的链表

题目描述

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

先上非递归:

public class Solution {
    public ListNode Merge(ListNode list1, ListNode list2) {
        // 为了节约空间复杂度,这里不用递归
        if (list1 == null) {
            return list2;
        } else if (list2 == null) {
            return list1;
        }
        // 让list1指向更小表头,然后把list2插入到list1
        if (list2.val < list1.val) {
            ListNode temp = list1;
            list1 = list2;
            list2 = temp;
        }
        // head记录更小表头,作为最后返回值
        ListNode head = list1;
        // list1最后还要用到,所以指向最后一个节点为止,不要到null
        while (list1.next != null && list2 != null) {
            // 这里保证始终有 list1.val <= list2.val
            if (list1.next.val <= list2.val) {
                list1 = list1.next;
                continue;
            }
            // 否则把当前list2节点插入
            ListNode temp = list2.next;
            list2.next = list1.next;
            list1.next = list2;
            list2 = temp;
        }
        // list2剩余
        if (list2 != null) {
            list1.next = list2;
        }
        return head;
    }

}

递归比较简单: 

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null)
            return list2;
        if(list2 == null)
            return list1;
        if(list1.val <= list2.val) {  //递归即可
            list1.next = Merge(list1.next, list2);
            return list1;
        }
        else {
            list2.next = Merge(list2.next, list1);
            return list2;
        }
    }
}

17.树的子结构

题目描述

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

public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        //前序遍历
        //这道题的意思是值相同,不是指同一节点,且可以只是中间部分的子结构
        if(root2 == null || root1 == null)  //由题意都不为null才行
            return false;
        if(HasSubtreeFromRoot(root1, root2))  //先判断根节点
            return true;
        else  //递归左右
            return HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
    }
    public boolean HasSubtreeFromRoot(TreeNode root1,TreeNode root2) {
        //root1可能包含root2,从根节点开始比较,只要root2能遍历完即可
        //以下保证输入root2非空
        if(root1 == null)
            return false;
        else if(root1.val != root2.val)
            return false;
        boolean res = true;
        if(root2.left != null)  //非空就要递归判断
            res = res && HasSubtreeFromRoot(root1.left, root2.left);
        if(root2.right != null)
            res = res && HasSubtreeFromRoot(root1.right, root2.right);
        return res;
    }
}

18.二叉树的镜像

题目描述

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

输入描述

二叉树的镜像定义:源二叉树 
            8
           /  \
          6   10
         / \     / \
        5  7 9 11
        镜像二叉树
            8
           /  \
          10   6
          / \     / \
        11 9 7  5

public class Solution {
    public void Mirror(TreeNode root) {
        if(root != null) {
            //先交换左右子树,再分别置镜像
            TreeNode temp = root.left;
            root.left = root.right;
            root.right = temp;
            Mirror(root.left);
            Mirror(root.right);
        }
    }
}

19.顺时针打印矩阵

题目描述

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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.

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> res = new ArrayList<Integer>();
        if(matrix.length == 0)
            return res;
        int step = 0;  //第几圈
        int i = step, j = step;
        //总共的圈数,由matrix的长和宽中较小的那个决定
        int totalLoops = (Math.min(matrix.length, matrix[0].length) + 1) / 2;
        while(step < totalLoops) {
            while(j < matrix[0].length - step)  //向右到最右,否则一条线构不成圈时会漏掉
                res.add(matrix[i][j++]);
            j--;  //第一个方向一定会执行,j回位
            i++;  //行数下移1
            while(i < matrix.length - step)  //向下,同样要到最下
                res.add(matrix[i++][j]);
            //前两个方向结束就可以判断退出
            if(i == step + 1 || j == step)  //分别是一行和一列的情况,退出
                break;
            i--;  //i回位
            j--;  //j左移1
            while(j > step)  //向左
                res.add(matrix[i][j--]);
            while(i > step)  //向上
                res.add(matrix[i--][j]);
            step++;
            i = step;
            j = step;
        }
        return res;
    }
}

20.包含min函数的栈

题目描述

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

import java.util.Stack;
public class Solution {
    long min;
    Stack<Long> stack = new Stack<Long>();
    public void push(int node) {
        if(stack.isEmpty()) {
            stack.push((long)node);
            min = (long)node;
        }
        else {
            stack.push((long)node - min);  //入栈的是node减去(旧的)min
            if((long)node < min)  //更新min
                min = (long)(node);
        }
    }
    
    public void pop() {
        if(stack.isEmpty())
            return;
        long pop = stack.pop();
        if(pop < 0)  //出栈的是更新了min的node,min要还原
            min = min - pop;  //右边min是node真实值,而pop是node真实值减去(旧的)min
    }
    
    public int top() {
        long top = stack.peek();
        if(top >= 0)
            return (int)(top + min);
        else  //top小于0时,node减去的是旧的min,当前min是node真实值
            return (int)min;
    }
    
    public int min() {
        return (int)min;
    }
}

21.栈的压入、弹出序列

题目描述

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

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if(pushA.length != popA.length)
            return false;
        Stack<Integer> stack = new Stack<Integer>();
        int i = 0;
        //对每个出栈序列元素执行检测,匹配上则pop(),否则返回false
        for(int j = 0; j < popA.length; j++) {
            //一直入栈直到匹配成功
            while(stack.empty() || stack.peek() != popA[j]) {
                if(i < pushA.length)  //i合法,继续入栈
                    stack.push(pushA[i++]);
                else  //i越界,匹配失败
                    return false;
            }
            stack.pop();  //匹配成功后出栈
        }
        return true;     
    }
}

22.从上至下逐层打印二叉树

题目描述

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

public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        //层次遍历即可
        ArrayList<Integer> res = new ArrayList<Integer>();
        if(root == null)
            return res;
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            res.add(cur.val);
            if(cur.left != null)
                queue.offer(cur.left);
            if(cur.right != null)
                queue.offer(cur.right);
        }
        return res;
    }
}

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

题目描述

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

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        //后序遍历是左,右,中
        //起始就是空数组时,不算是搜索树,返回false
        if(sequence.length == 0)
            return false;
        return VerifySquenceOfBST(sequence, 0, sequence.length - 1);
    }
    public boolean VerifySquenceOfBST(int [] sequence, int start, int end) {
        //对于子树,start > end视作空子树,返回true
        if(start >= end)
            return true;
        int i = start;
        int leftEnd = start;
        //左子树全部比根节点小
        while(sequence[i] < sequence[end])
            i++;
        leftEnd = i - 1;
        //右子树全部比根节点大
        while(sequence[i] > sequence[end])
            i++;
        //到达根节点
        if(i == end)
            return VerifySquenceOfBST(sequence, start, leftEnd) && VerifySquenceOfBST(sequence, leftEnd + 1, end - 1);
        //没有到达,返回false
        else
            return false;
    }
}

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

题目描述

输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

public class Solution {
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
        ArrayList<Integer> list = new ArrayList<Integer>();
        preOrder(root, target, res, list);
        return res;
    }
    //前序遍历
    public void preOrder(TreeNode cur, int target, ArrayList<ArrayList<Integer>> res, ArrayList<Integer> list) {
        if(cur != null) {
            list.add(cur.val);  //加入list
            if(cur.left == null && cur.right == null && cur.val == target)  //满足条件的叶节点
                res.add(new ArrayList(list));  //要先复制list再加入res才对
            else {
                preOrder(cur.left, target - cur.val, res, list);
                preOrder(cur.right, target - cur.val, res, list);
            }
            list.remove(list.size() - 1);  //回退时需要移除最后一个元素
        }
    }
}

25.复杂链表的复制

题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
    public RandomListNode Clone(RandomListNode head) {
        if(head == null) {
            return null;
        }
        //头节点非空,先复制头节点为res
        RandomListNode res = new RandomListNode(head.label);
        res.next = head.next;
        head.next = res;
        //第一轮复制,先不考虑随机指针,每个复制的节点插入到原节点之后
        for(RandomListNode p = res.next; p != null;) {
            RandomListNode p1 = new RandomListNode(p.label);
            p1.next = p.next;
            p.next = p1;
            p = p1.next;
        }
        //第二轮,连接随机指针,p1.random = p.random.next;
        for(RandomListNode p = head; p != null;) {
            RandomListNode p1 = p.next;
            if(p.random != null) {
                p1.random = p.random.next;
            }
            p = p1.next;
        }
        //最后取出复制的链表,并将原链表还原
        head.next = res.next;
        for(RandomListNode p = res.next, p1 = res; p != null;) {
            p1.next = p.next;
            p1 = p1.next;
            p.next = p1.next;
            p = p.next;
        }
        return res;
    }
}

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

题目描述

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

public class Solution {
    //全局变量保存链表头和尾
    TreeNode head = null;
    TreeNode tail = null;

    public TreeNode Convert(TreeNode pRootOfTree) {
        inOrder(pRootOfTree);
        return head;
    }
    //中序遍历
    public void inOrder(TreeNode root) {
        if(root != null) {
            inOrder(root.left);
            if(head == null) {
                head = root;
                tail = root;
            }
            else {
                tail.right = root;
                root.left = tail;
                tail = root;
            }
            inOrder(root.right);
        }
    }
}

27.字符串的排列

题目描述

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

输入描述:

输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

import java.util.ArrayList;
import java.util.Collections;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> res = new ArrayList<String>();
        if(str.length() == 0)
            return res;
        permute(str.toCharArray(), 0, res);  //生成res
        Collections.sort(res);
        return res;
    }
    public void permute(char[] ch, int i, ArrayList<String> list) {
        if(i == ch.length - 1) {  //递归出口
            String cur = new String(ch);
            if(!list.contains(cur))  //如果没有重复就存入当前字符串
                list.add(cur);
        }
        else {
            for(int j = i; j < ch.length; j++) {  //逐个交换字母,递归,再换回
                swap(ch, i, j);
                permute(ch, i + 1, list);
                swap(ch, i, j);
            }
        }
    }
    public void swap(char[] ch, int i, int j) {  //交换两个字母顺序
        char temp = ch[i];
        ch[i] = ch[j];
        ch[j] = temp;
    }
}

28.数组中出现次数超过一半的数字

题目描述

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

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        //这道题要利用数组的特殊性质,不需要HashMap
        if(array.length == 0)
            return 0;
        int res = array[0];
        int count = 1;
        //经过第一次循环假如有超过一半的元素一定会留下来
        for(int i = 1; i < array.length; i++) {
            if(count == 0) {  //count为0,更新res
                res = array[i];
                count = 1;
            }
            else if(array[i] == res)
                count++;
            else
                count--;
        }
        //第二次循环验证留下来的是否是超过一半次的元素
        count = 0;
        for(int i = 0; i < array.length; i++) {
            if(array[i] == res)
                count++;
            if(count > array.length / 2)
                return res;
        }
        return 0;
    }
}

29.最小的k个数

题目描述

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

import java.util.ArrayList;
import java.util.Random;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if (input.length == 0 || k > input.length) {
            return res;
        }
        GetLeastNumbers_Solution(input, k, 0, input.length - 1, res);
        return res;
    }
    private void GetLeastNumbers_Solution(int [] nums, int k, int start, int end,
                                                       ArrayList<Integer> res) {
        // 快排法求topK
        if (start > end || k == 0) {
            return;
        }
        if (k == end - start + 1) {
            for (int i = start; i <= end; i++) {
                res.add(nums[i]);
            }
            return;
        }
        int p = partition(nums, start, end);
        // p之前多于k个时无法确定,k不变,end左移
        if (p - start > k) {
            GetLeastNumbers_Solution(nums, k, start, p - 1, res);
        } else {
            // 否则p之前都可以加入res
            for (int i = start; i < p; i++) {
                res.add(nums[i]);
            }
            GetLeastNumbers_Solution(nums, k - (p - start), p, end, res);
        }
    }
    // 二路快排
    private int partition(int[] nums, int i, int j) {
        // 随机洗牌
        int rand = new Random().nextInt(j - i + 1);
        swap(nums, i, i + rand);

        int pivot = nums[i];
        while (i < j) {
            while (i < j && nums[j] >= pivot) {
                j--;
            }
            nums[i] = nums[j];
            while (i < j && nums[i] <= pivot) {
                i++;
            }
            nums[j] = nums[i];
        }
        nums[i] = pivot;
        return i;
    }
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

30.连续子数组的最大和

题目描述

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int curSum = array[0];  //curSum的定义是包含当位的值
        int maxSum = curSum;
        for(int i = 1; i < array.length; i++){
            if(curSum <= 0)  //旧的和非正数,则抛弃
                curSum = array[i];
            else
                curSum = curSum + array[i];
            maxSum = Math.max(maxSum, curSum);  //更新maxSum
        }
        return maxSum;
    }
}

31.整数中1出现的次数(从1到n整数中1出现的次数)

题目描述

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

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        //计算逐个位的1的次数并求和
        int count = 0;
        int weight = 1;
        int r = 0;  //余数,用来计算当位是1时这个1带来的个数
        while(weight <= n) {
            r = n % weight;
            if((n / weight) % 10 > 1)  //当位大于1时增加weight个(如百位含100~199共100个1)
                count += weight;
            else if((n / weight) % 10 == 1)  //当位是1时增加r+1个(如百位含100~109共10个1)
                count += r + 1;
            count += (n / weight / 10) * weight;  //高位带来的当位1的个数,
                                       //如00100到23199,除去额外的199,百位增加共23*100个1
                                       //千万不能化简算式,否则最高位时结果不同!
            weight *= 10;
        }
        return count;
    }
}

32.把数组排成最小的数

题目描述

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

public class Solution {
    public String PrintMinNumber(int [] numbers) {
        //普通排序即可,如果交换位置后拼成的数更小,就交换
        for(int i = 0; i < numbers.length - 1; i++){
            for(int j = i + 1; j < numbers.length; j++){
                int a = Integer.valueOf(numbers[i]+""+numbers[j]);
                int b = Integer.valueOf(numbers[j]+""+numbers[i]);
                if(a > b){
                    int temp = numbers[i];
                    numbers[i] = numbers[j];
                    numbers[j] = temp;
                }
            }
        }
        String str = "";
        for(int i = 0; i < numbers.length; i++)
            str += numbers[i];
        return str;
    }
}

33.丑数

题目描述

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

public class Solution {
    /*丑数可以分为三个序列
    (1) 1×2, 2×2, 3×2, 4×2, 5×2, …
    (2) 1×3, 2×3, 3×3, 4×3, 5×3, …
    (3) 1×5, 2×5, 3×5, 4×5, 5×5, …
    每个序列都是丑数序列本身 * 2, 3, 5,每次从这三个序列选最小就可以获取丑数序列;
    之后更新(包括重复值也要更新才行)*/
    public int GetUglyNumber_Solution(int n) {
        if(n == 0)
            return 0;
        int[] ugly = new int[n];
        ugly[0] = 1;
        int index2 = 0, index3 = 0, index5 = 0;
        int factor2 = 2, factor3 = 3, factor5 = 5;
        for(int i = 1; i < n; i++) {
            int min = Math.min(Math.min(factor2, factor3), factor5);  //选最小
            ugly[i] = min;
            if(factor2 == min)  //因为可能有重复值,一定要3个都判断并更新,不可以用else
                factor2 = 2 * ugly[++index2];
            if(factor3 == min)
                factor3 = 3 * ugly[++index3];
            if(factor5 == min)
                factor5 = 5 * ugly[++index5];
        }
        return ugly[n - 1];
    }
}

34.第一个只出现一次的字符

题目描述

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

import java.util.HashMap;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        //直接上HashMap计数即可
        HashMap<Character, Integer> map = new HashMap<Character, Integer>();
        int res = -1;
        for(int i = 0; i < str.length(); i++)
            map.put(str.charAt(i), map.getOrDefault(str.charAt(i), 0) + 1);
        for(int i = 0; i < str.length(); i++)
            if(map.get(str.charAt(i)) == 1) {
                res = i;
                break;
            }
        return res;
    }
}

35.数组中的逆序对

题目描述

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

输入描述:

题目保证输入的数组中没有的相同的数字

数据范围:

对于%50的数据,size<=10^4

对于%75的数据,size<=10^5

对于%100的数据,size<=2*10^5

public class Solution {
    public int InversePairs(int [] array) {
        //归并排序并且统计逆序数
        long[] count = new long[1];
        mergeSort(array, 0, array.length - 1, count);
        return (int)count[0];
    }
    //归并排序
    public void mergeSort(int[] array, int start, int end, long[] count) {
        if(start >= end)
            return;
        int mid = start + (end - start) / 2;  //预防int溢出的写法
        mergeSort(array, start, mid, count);
        mergeSort(array, mid + 1, end, count);
        merge(array, start, mid, end, count);
    }
    public void merge(int[] array, int start, int mid, int end, long[] count) {
        //对start到mid,以及mid+1到end两部分进行合并
        int[] temp = new int[end - start + 1];
        int i = start, j = mid + 1, k = 0;
        while(i <= mid && j <= end) {
            if(array[i] > array[j]) {  //出现逆序,更新count
                temp[k++] = array[j++];
                count[0] += mid - i + 1;  //右侧array[j]左移越过的左侧个数即增加的逆序数
                count[0] %= 1000000007;  //预防数值过大溢出
            }
            else
                temp[k++] = array[i++];
        }
        while(i <= mid)
            temp[k++] = array[i++];
        while(j <= end)
            temp[k++] = array[j++];
        for (k = 0; k < temp.length; k++)  //将合并后数组移回原数组
            array[start + k] = temp[k];
    }
}

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

题目描述

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

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        int length1 = 0, length2 = 0;
        ListNode l1, l2;
        //先获取两个链表长度
        for(l1 = pHead1; l1 != null; l1 = l1.next)
            length1++;
        for(l2 = pHead2; l2 != null; l2 = l2.next)
            length2++;
        //让l1指向较长一个链表
        if(length1 < length2) {
            l1 = pHead2;
            l2 = pHead1;
        }
        else {
            l1 = pHead1;
            l2 = pHead2;
        }
        for(int i = 0; i < Math.abs(length1 - length2); i++)  //l1先移动长度差
            l1 = l1.next;
        while(l1 != l2) {
            l1 = l1.next;
            l2 = l2.next;
        }
        return l1;
    }
}

37.数字在排序数组中出现的次数

题目描述

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

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        // 利用二分查找的变种方法,找到第1个k和最后1个k,计算位置跨度
        int first = binarySearchFirst(array, k);
        if (first == -1) {
            return 0;
        }
        return binarySearchLast(array, k) - first + 1;
    }
    // 二分查找第1个k
    public int binarySearchFirst(int [] array , int k) {
        int start = 0, end = array.length - 1;
        while(start <= end) {
            int mid = (start + end) / 2;
            if(array[mid] >= k)
                end = mid - 1;
            else
                start = mid + 1;
        }
        if (start < array.length && array[start] == k) {
            return start;
        }
        return -1;
    }
    // 二分查找最后1个k
    public int binarySearchLast(int [] array , int k) {
        int start = 0, end = array.length - 1;
        while(start <= end) {
            int mid = (start + end) / 2;
            if(array[mid] <= k)
                start = mid + 1;
            else
                end = mid - 1;
        }
        if (end >= 0 && array[end] == k) {
            return end;
        }
        return -1;
    }
}

38.二叉树的深度

题目描述

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

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

39.平衡二叉树

题目描述

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        if(root == null)
            return true;
        //比较左右子树深度即可
        int leftDepth = getDepth(root.left);
        int rightDepth = getDepth(root.right);
        if(Math.abs(leftDepth - rightDepth) > 1)
            return false;
        else
            return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
    }
    public int getDepth(TreeNode root) {
        if(root == null)
            return 0;
        else
            return 1 + Math.max(getDepth(root.left), getDepth(root.right));
    }
}

40.数组中只出现一次的数字

题目描述

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

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int bitResult = 0;
        //异或消去重复的数,最后得到bitResult是这两个数的异或
        for(int i = 0; i < array.length; i++) {
            bitResult ^= array[i];
        }
        int index = 0;
        //从低位到高位找到这两个数第一个异或后为1,也就是第一个不同的数位
        while(index < 32 && ((bitResult & 1) == 0)){
            index++;
            bitResult >>= 1;
        }
        //最后求一遍异或,根据不同位为1还是0分开,即可分别得到两个数
        for(int i = 0; i < array.length; i++) {
            if(((array[i] >> index) & 1) == 1)
                num1[0] ^= array[i];
            else
                num2[0] ^= array[i];
        }
    }
}

41.和为S的连续正数序列

题目描述

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

输出描述:

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer> > res = new ArrayList<ArrayList<Integer> >();
        if(sum <= 2)
            return res;
        int n = 2;  //连续正数的个数为n
        int mid = sum / n;
        int first = mid + 1 - n / 2;  //n为偶数的first计算公式,奇数没有+1
        boolean nIsOdd = false;
        while(first > 0) {  
            //n为奇数或偶数分别处理
            if(nIsOdd && sum % n == 0) {  //n为奇数时要能除开
                //求和之后就是mid*n,一定是sum,不必再判断
                ArrayList<Integer> list = new ArrayList<Integer>();
                for(int i = 0; i < n; i++)
                    list.add(first + i);
                res.add(0, list);  //按题目要求顺序需要头部插入
            }
            if(!nIsOdd && sum % n != 0) {  //n为偶数时不可以除开
                //求和之后结果是mid*n+(last-mid),即mid*n+n/2
                if(mid * n + n / 2 == sum) {
                    ArrayList<Integer> list = new ArrayList<Integer>();
                    for(int i = 0; i < n; i++)
                        list.add(first + i);
                    res.add(0, list);
                }
            }
            //更新各个参数
            n++;
            nIsOdd = !nIsOdd;
            mid = sum / n;
            first = mid + 1 - n / 2;
            if(nIsOdd)  //n为奇数,first去掉+1
                first--;
        }
        return res;
    }
}

42.和为S的两个数字

题目描述

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

输出描述:

对应每个测试案例,输出两个数,小的先输出。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        //乘积最小是最靠近两侧的结果
        ArrayList<Integer> res = new ArrayList<Integer>();
        if(array.length == 0)
            return res;
        int i = 0, j = array.length - 1;  //从两侧向中间遍历
        while(i < j) {
            if(array[i] + array[j] == sum) {
                res.add(array[i]);
                res.add(array[j]);
                break;
            }
            else if(array[i] + array[j] < sum)
                i++;
            else
                j--;
        }
        return res;
    }
}

43.左旋转字符串

题目描述

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

public class Solution {
    public String LeftRotateString(String str,int n) {
        //先分两部分,再分别转置的解法
        if(str == null || n > str.length())
            return "";
        String a = str.substring(0, n);
        String b = str.substring(n);
        return reverse(reverse(a) + reverse(b));
    }
    public String reverse(String str) {
        String res = "";
        for(int i = 0; i < str.length(); i++)
            res = str.charAt(i) + res;
        return res;
    }
}

44.旋转单词顺序列

题目描述

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

public class Solution {
    public String ReverseSentence(String str) {
        String[] strArr = str.split(" ");
        if(strArr.length == 0)
            return str;  //原样返回
        String res = "";
        int i = 0, j = strArr.length - 1;
        while(i < j) {  //reverse
            String temp = strArr[i];
            strArr[i] = strArr[j];
            strArr[j] = temp;
            i++;
            j--;
        }
        for(int k = 0; k < strArr.length - 1; k++)
            res += strArr[k] + " ";
        res += strArr[strArr.length - 1];
        return res;
    }
}

45.扑克牌顺子

题目描述

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 class Solution {
    public boolean isContinuous(int [] numbers) {
        if(numbers.length != 5)
            return false;
        int[] pokers = new int[14];
        int max = 1, min = 13;
        for(int num : numbers) {
            if(num == 0)
                pokers[0]++;
            else{
                if(pokers[num] > 0)  //出现重复
                    return false;
                pokers[num]++;
                if(num < min)
                    min = num;
                if(num > max)
                    max = num;
            }
        }
        if(pokers[0] == 4)  //4个0一定可以
            return true;
        //否则只要没有重复,且最大最小值差在4以内即可
        else if(max - min > 4)
            return false;
        return true;
    }
}

46.孩子们的游戏(圆圈中最后剩下的数)

题目描述

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

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        //就是约瑟夫环
        if(n == 0) 
            return -1;
        int s = 0;  //1人环中,就是0
        for(int i = 2; i <= n; i++) {  //2人环到n人环迭代
            s = (s + m) % i;
        }
        return s;
    }
}

47.求1+2+3+...+n

题目描述

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

public class Solution {
    public int Sum_Solution(int n) {
        int res = n;
        //利用&&的短路,前面为假后面不计算,构造递归出口
        boolean a = (res > 0) && ((res += Sum_Solution(res - 1)) > 0);
        return res;
    }
}

48.不用加减乘除做加法

题目描述

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

public class Solution {
    public int Add(int num1,int num2) {
        //直接加就可以
        while(num2 != 0) {
            int temp = num1 ^ num2;  //求和不算进位
            num2 = (num1 & num2) << 1;  //进位
            num1 = temp;
        }
        return num1;
    }
}

49.把字符串转换成整数

题目描述

将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。

输入描述:

输入一个字符串,包括数字字母符号,可以为空

输出描述:

如果是合法的数值表达则返回该数字,否则返回0

public class Solution {
    public int StrToInt(String str) {
        if(str == null || str.length() == 0)
            return 0;
        int res = 0;
        int weight = 1;
        if(str.charAt(0) == '-')  //负数
            weight = -1;
        for(int i = str.length() - 1; i >= 0; i--) {  //从右向左遍历
            char c = str.charAt(i);
            if(c >= '0' && c <= '9') {
                int temp = res + (c - '0') * weight;
                if(temp - res != (c - '0') * weight)  //溢出
                    return 0;
                else
                    res = temp;
            }
            else if((c == '+' || c == '-') && i == 0)  //不是0-9只能是第1位+-号,否则非法
                break;
            else
                return 0;
            weight *= 10;
        }
        return res;
    }
}

50.数组中重复的数字

题目描述

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

public class Solution {
    //    这里要特别注意~返回任意重复的一个,赋值duplication[0]
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        //这个题应该利用下标
        if(length <= 1)
            return false;
        int j = 0;  //下标为j,初始不能是值和下标相同的,否则跳不出去会产生错误结果
        while(j < length && numbers[j] == j)  //找到一个符合要求的初始点
            j++;
        if (j == length)  //全部不符,说明无重复
            return false;
        j = numbers[j];  //这一步至关重要,要知道初始下标j不是跳到的,不能置-1
                         //所以要改为跳到的第一个下标
        //计算值和下标相同的个数,循环次数要减掉才行
        int count = 0;
        for(int i = 0; i < length; i++)
            if(numbers[i] == i)
                count++;
        for(int i = 0; i < length - count; i++) {  //此处i只是次数,与下标无关
            if(numbers[j] == -1 || numbers[j] == j) {  //第二个条件重要!跳到陷阱处也说明是重复
                duplication[0] = j;
                return true;
            }
            else {
                int temp = j;
                j = numbers[j];
                numbers[temp] = -1;  //跳到过的置-1
            }
        }
        return false;
    }
}

51.构建乘积数组

题目描述

给定一个数组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]。不能使用除法。

public class Solution {
    public int[] multiply(int[] A) {
        //遍历一个来回,逐个填充乘进结果数组,用一个变量储存乘积即可
        int[] res = new int[A.length];
        int left = 1;
        int right = 1;
        for(int i = 0; i < A.length; i++) {
            res[i] = left;
            left *= A[i];
        }
        for(int i = A.length - 1; i >= 0; i--) {
            res[i] *= right;
            right *= A[i];
        }
        return res;
    }
}

52.正则表达式匹配

题目描述

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

public class Solution {
    public boolean match(char[] str, char[] pattern) {
        return match(str, 0, pattern, 0);
    }
    //加上开始下标参数,以便递归处理'*'的复杂情况
    public boolean match(char[] str, int i1, char[] pattern, int i2) {
        if(i1 == str.length && i2 == pattern.length)  //都遍历完,返回true
            return true;
        else if(i2 == pattern.length)  //pattern结束,str还没结束当然不行
            return false;
        else if(i1 == str.length) {  //str结束,pattern剩下的只能是字符+'*'的组合
            while(i2 + 1 < pattern.length && pattern[i2 + 1] == '*')
                i2 += 2;
            return i2 == pattern.length;
        }
        //两个都没结束的情况
        //pattern后面是'*'时
        if(i2 + 1 < pattern.length && pattern[i2 + 1] == '*') {
            if(str[i1] == pattern[i2] || pattern[i2] == '.')  //'*'代表1次或多次,或0次
                 //0次不要丢掉!因为pattern后面可能有普通字符需要匹配str中字符
                 //如果丢了0次'.'把str中的匹配掉了,pattern后面就没处匹配了,如"bbbba"和".*a*a"
                return match(str, i1 + 1, pattern, i2 + 2) || 
                       match(str, i1 + 1, pattern, i2) || 
                       match(str, i1, pattern, i2 + 2);
            else  //'*'代表0次
                return match(str, i1, pattern, i2 + 2);
        }
        //pattern后面不是'*'时
        else {
            if(str[i1] == pattern[i2] || pattern[i2] == '.')
                return match(str, i1 + 1, pattern, i2 + 1);
            else
                return false;
        }
    }
}

53.表示数值的字符串

题目描述

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

public class Solution {
    public boolean isNumeric(char[] str) {
        boolean noPoint = true;  //是否没有'.'
        boolean noE = true;  //是否没有'E'或'e'
        int pointPos = 0;
        int ePos = 0;
        for(int i = 0; i < str.length; i++) {
            if(str[i] == '.') {
                if(noPoint) {
                    noPoint = false;
                    pointPos = i;
                }
                else  //两个.
                    return false;
            }
            if(str[i] == 'e' || str[i] == 'E') {
                if(noE) {
                    noE = false;
                    ePos = i;
                }
                else  //两个e
                    return false;
            }
        }
        if(!noE) {  //有e
            if(pointPos > ePos)
                return false;
            else
                return isIntegerOrFloat(str, ePos + 1, str.length - 1, false) 
                        && isIntegerOrFloat(str, 0, ePos - 1, true);
        }
        else
            return isIntegerOrFloat(str, 0, str.length - 1, true);
    }

    public boolean isNum(char c) {
        return c >= '0' && c <= '9';
    }

    public boolean isIntegerOrFloat(char[] str, int start, int end, boolean canBeFloat) {
        if(start > end)
            return false;
        if(start == end)
            return isNum(str[start]);
        if(str[start] == '+' || str[start] == '-')
            start++;
        while(start <= end) {
            // 不用担心‘.’超过一个因为主函数判断过了
            if (isNum(str[start]) || canBeFloat && str[start] == '.') { 
                start++;
            } else {
                return false;
            }
        }
        return start == end + 1;
    }
}

54.字符流中第一个不重复的字符

题目描述

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

输出描述:

如果当前字符流没有存在出现一次的字符,返回#字符。

public class Solution {
    String s = "";
    int[] charNums = new int[256];
    //Insert one char from stringstream
    public void Insert(char ch) {
        s += ch;
        charNums[ch]++;
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce() {
        //从字符串s开始遍历
        for(int i = 0; i < s.length(); i++)
            if(charNums[s.charAt(i)] == 1)
                return s.charAt(i);
        return '#';
    }
}

55.链表中环的入口节点

题目描述

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

public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead) {
        if(pHead == null)
            return null;
        else if(pHead.next == null)
            return null;
        ListNode fast = pHead, slow = pHead, start = pHead;
        while(fast != null) {
            if(fast.next != null)
                fast = fast.next.next;
            else
                fast = fast.next;  //此时fast已经为null
            slow = slow.next;
            if(fast == slow) {  //相等处fast多走了整数个环长,也是slow走的长度
                while(start != slow) {  //再走1个环外长度就刚好会遇到
                    start = start.next;
                    slow = slow.next;
                }
                break;
            }
        }
        if(fast == null)  //无环
            return null;
        else
            return start;
    }
}

56.删除链表中重复的节点

题目描述

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

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if(pHead == null)
            return null;
        ListNode res = new ListNode(0);  //pHead是可能被删掉的,所以要保存一个头节点
        res.next = pHead;
        ListNode pre = res;  //前置节点,删除时会用到
        ListNode l = pHead;
        int cur = pHead.val;
        while(l != null) {
            int count = 0;
            while(l != null && l.val == cur) {  //统计相同元素个数
                l = l.next;
                count++;
            }
            if(count > 1)
                pre.next = l;  //越过重复值的节点,注意pre不动
            else
                pre = pre.next;  //后移
            if(l != null)
                cur = l.val;  //更新cur
        }
        return res.next;
    }
}

57.二叉树的下一个节点

题目描述

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

/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        if(pNode == null)
            return null;
        if(pNode.right != null)
            return inOrderFirstNode(pNode.right);
        //pNode右为空,则当前子树已经遍历完
        TreeLinkNode father = pNode.next;
        //要用循环才行
        while(father != null) {
            if(father.left == pNode)
                return father;
            //否则就是father为根的子树也遍历完
            else {
                pNode = father;
                father = father.next;
            }
        }
        return null;
    }
    //求中序遍历的第一个节点
    public TreeLinkNode inOrderFirstNode(TreeLinkNode root) {
        //调用时让这个函数输入不为空,不包含返回空的情况,便于理解
        if(root.left == null)
            return root;
        else
            return inOrderFirstNode(root.left);
    }
}

58.对称的二叉树

题目描述

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

public class Solution {
    boolean isSymmetrical(TreeNode pRoot) {
        if(pRoot == null)
            return true;
        else  //左右子树互为镜像即对称
            return isMirror(pRoot.left, pRoot.right);
    }
    //判断是否是镜像
    boolean isMirror(TreeNode root1, TreeNode root2) {
        if(root1 == null && root2 == null)
            return true;
        else if(root1 == null || root2 == null)
            return false;
        if(root1.val != root2.val)
            return false;
        else
            return isMirror(root1.left, root2.right) && isMirror(root1.right, root2.left);
    }
}

59.按之字形打印二叉树

题目描述

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        //还是层次遍历
        //和逐行打印相同,只是新增加一个变量标记,偶数行要逆序
        ArrayList<ArrayList<Integer> > res = new ArrayList<>();
        if(pRoot == null)
            return res;
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(pRoot);
        TreeNode mark = new TreeNode(0);
        queue.offer(mark);
        ArrayList<Integer> list = new ArrayList<>();
        boolean reverse = false;  //逆序标记,偶数行为true
        while(!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            if (cur == mark) {  //检测到标记
                if (reverse) {
                    Collections.reverse(list);
                }
                res.add(list);
                list = new ArrayList<>();
                reverse = !reverse;
                if (!queue.isEmpty()) {  //队列还有元素时,标记塞回队列
                    queue.offer(cur);
                }
            } else {
                list.add(cur.val);
                if (cur.left != null)
                    queue.offer(cur.left);
                if (cur.right != null)
                    queue.offer(cur.right);
            }
        }
        return res;
    }
}

60.把二叉树打印成多行

题目描述

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

import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        //同上题,去掉reverse即可
        //层次遍历,标记每行结束
        ArrayList<ArrayList<Integer> > res = new ArrayList<>();
        if(pRoot == null)
            return res;
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(pRoot);
        TreeNode mark = new TreeNode(0);
        queue.offer(mark);
        ArrayList<Integer> list = new ArrayList<>();
        while(!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            if (cur == mark) {  //检测到标记
                res.add(list);
                list = new ArrayList<>();
                if (!queue.isEmpty()) {  //队列还有元素时,标记塞回队列
                    queue.offer(cur);
                }
            } else {
                list.add(cur.val);
                if (cur.left != null)
                    queue.offer(cur.left);
                if (cur.right != null)
                    queue.offer(cur.right);
            }
        }
        return res;
    }
}

61.序列化二叉树

题目描述

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

public class Solution {
    String Serialize(TreeNode root) {
        //前序遍历序列化,","分隔节点,"#"表示null
        StringBuilder res = new StringBuilder();
        if(root == null) {
            res.append("#,");
            return res.toString();
        }
        res.append(root.val);
        res.append(",");
        res.append(Serialize(root.left));
        res.append(Serialize(root.right));
        return res.toString();
    }
    TreeNode Deserialize(String str) {
        if(str == null)
            return null;
        String[] strArr = str.split(",");  //获取节点String数组
        int[] index = new int[]{-1};
        return DeserializeStrArr(strArr, index);
    }
    TreeNode DeserializeStrArr(String[] strArr, int[] index) {
        //一定要最先修改index,因为递归1次就检查了1个节点,index要后移
        //这样可以保证每次递归的index都是不同的
        index[0]++;
        TreeNode root = null;
        if(!strArr[index[0]].equals("#")) {  //非null节点
            root = new TreeNode(Integer.valueOf(strArr[index[0]]));
            root.left = DeserializeStrArr(strArr, index);
            root.right = DeserializeStrArr(strArr, index);
        }
        return root;
    }
}

62.二叉搜索树的第k个节点

题目描述

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)    中,按结点数值大小顺序第三小结点的值为4。

import java.util.ArrayList;
public class Solution {
    TreeNode KthNode(TreeNode pRoot, int k) {
        //中序遍历,数到k即可
        if(k <= 0)
            return null;
        int[] count = new int[1];
        count[0] = k;
        return inOrder(pRoot, count);
    }
    TreeNode inOrder(TreeNode pRoot, int[] count) {
        if (pRoot != null) {
            TreeNode leftRes = inOrder(pRoot.left, count);
            if (leftRes != null) {  //左子树中找到第k小
                return leftRes;
            }
            if (count[0] == 1) {  //当前节点为第k小
                return pRoot;
            }
            count[0]--;
            return inOrder(pRoot.right, count);
        }
        return null;
    }
}

63.数据流的中位数

题目描述

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

import java.util.ArrayList;
public class Solution {
    ArrayList<Integer> list = new ArrayList<>();  // 有序列表

    public void Insert(Integer num) {
        if(list.size() == 0) {
            list.add(num);
            return;
        }
        if(num >= list.get(list.size() - 1))  // 尾插单独判断下
            list.add(num);
        else {
            list.add(binarySearchIndex(num), num);
        }
    }

    // 二分查找插入位置
    public int binarySearchIndex(int num) {
        int start = 0, end = list.size() - 1;
        while (start < end) {
            int mid = (start + end) / 2;  
            int midVal = list.get(mid);
            if (midVal == num || 
                    midVal >= num && mid >= 1 && list.get(mid - 1) < num)
                return mid;
            else if (midVal < num)
                start = mid + 1;  // 较小值不会是插入位置,因此跳过
            else
                end = mid;  // 较大值有可能是插入位置
        }
        return start;
    }

    public Double GetMedian() {
        int size = list.size();
        if(size == 0)
            return null;
        else if(size % 2 == 1)
            return list.get((size - 1) / 2) * 1.0;  //注意下标和size的关系,别忘了-1
        else
            return (list.get((size - 1) / 2)
                    + list.get((size - 1) / 2 + 1)) / 2.0;
    }
}

64.滑动窗口的最大值

题目描述

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。

import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        ArrayList<Integer> res = new ArrayList<>();
        if(size <= 0 || size > num.length)
            return res;
        Queue<Integer> queue = new LinkedList<Integer>();  //保持队列大小为size,更新max
        int maxIndex = 0, max = Integer.MIN_VALUE;
        for(int i = 0; i < num.length; i++) {
            if(queue.size() < size) {  //初始队列没满的时候
                queue.offer(num[i]);
                if(num[i] >= max) {  //要加上等于,以便maxIndex更新到右边第一个max位置
                    max = num[i];
                    maxIndex = i;
                }
            }
            else {
                res.add(max);  //先储存max
                //看出队的是不是当前max,用下标判断
                if(maxIndex == i - size) {  //出队的是当前max
                    max = num[i - size + 1];
                    maxIndex = i - size + 1;
                    for(int j = i - size + 2; j <= i; j++)  //循环取得新max
                        if(num[j] >= max) {
                            max = num[j];
                            maxIndex = j;
                        }
                }
                //如果不是,就不用管出队的值,只有新值num[i]>=max时更新max即可
                else if(num[i] >= max) {  //当前为最大值,更新max和maxIndex
                    max = num[i];
                    maxIndex = i;
                }
                queue.poll();  //无论前面如何都要出队和入队
                queue.offer(num[i]);
            }
        }
        res.add(max);
        return res;
    }
}

65.矩阵中的路径

题目描述

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

public class Solution {
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
        for(int i = 0; i < matrix.length; i++) {  //不同的起始点
            boolean[] flag = new boolean[matrix.length];  //每个起始点新建标记数组
            if(backtracking(i, matrix, rows, cols, str, 0, flag))
                return true;
        }
        return false;
    }
    //回溯算法
    public boolean backtracking(int start, char[] matrix, int rows, int cols, 
                                char[] str, int strIndex, boolean[] flag) {
        if(strIndex == str.length)  //字符串比较完成
            return true;
        if(start < 0 || start >= matrix.length || flag[start] || matrix[start] != str[strIndex])
            return false;
        else {  //求上下左右的坐标
            flag[start] = true;
            int left = start % cols == 0 ? -1 : start - 1;  //是否是每行开始
            int right = (start + 1) % cols == 0 ? -1 : start + 1;  //是否是每行结束
            int up = start - cols;
            int down = start + cols;
            return backtracking(left, matrix, rows, cols, str, strIndex + 1, flag)
                || backtracking(right, matrix, rows, cols, str, strIndex + 1, flag)
                || backtracking(up, matrix, rows, cols, str, strIndex + 1, flag)
                || backtracking(down, matrix, rows, cols, str, strIndex + 1, flag);
        }
    }
}

66.机器人的运动范围

题目描述

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

public class Solution {
    public int movingCount(int threshold, int rows, int cols) {
        boolean[][] flag = new boolean[rows][cols];  //标记是否走过
        return backtracking(0, 0, threshold, rows, cols, flag);
    }
    //回溯算法
    public int backtracking(int i, int j, int threshold, int rows, int cols, boolean[][] flag) {
        if(i < 0 || i >= rows || j < 0 || j >= cols || digitAdd(i, j) > threshold || flag[i][j])
            return 0;
        else {
            flag[i][j] = true;
            return 1 + backtracking(i - 1, j, threshold, rows, cols, flag)
                + backtracking(i + 1, j, threshold, rows, cols, flag)
                + backtracking(i, j - 1, threshold, rows, cols, flag)
                + backtracking(i, j + 1, threshold, rows, cols, flag);
        }
    }
    //数位求和
    public int digitAdd(int a, int b) {
        int sum = 0;
        while(a > 0) {
            sum += a % 10;
            a /= 10;
        }
        while(b > 0) {
            sum += b % 10;
            b /= 10;
        }
        return sum;
    }
}

END

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值