牛客网剑指Offer - java版

剑指Offer - java版

文章目录

  • 剑指Offer - java版
    • JZ01
    • JZ02 替换空格
    • JZ03 从尾到头打印链表
    • JZ04 重建二叉树
    • JZ05 用两个栈实现队列
    • JZ06 旋转数组的最小数字
    • JZ07 斐波那契数列
    • JZ08 跳台阶问题
    • JZ09 青蛙跳台阶
    • JZ10 矩形覆盖
    • JZ11 二进制中1的个数
    • JZ12 数值的整数次方
    • JZ13 调整数组顺序,使奇数位于偶数前面
    • JZ14 链表中倒数第k个结点
    • JZ15 反转链表
    • JZ16 合并两个排序的链表
    • JZ17 树的子结构
    • JZ18 二叉树的镜像
    • JZ19 顺时针打印矩阵
    • JZ20 包含min函数的栈
    • JZ21 栈的压入、弹出序列
    • JZ22 从上往下打印二叉树
    • JZ23 二叉树的后序遍历
    • JZ24 二叉树中和为某值的路径
    • JZ25 复杂链表的复制
    • JZ26 二叉搜索树与双向链表
    • JZ27 字符串的排列
    • JZ28 数组中出现次数超过一半的数字
    • JZ29 最小的k个数
    • JZ30 连续子数组的最大和
    • JZ31 整数中1出现的次数
    • JZ32 把数组排成最小的数
    • JZ33 丑数
    • JZ34 第一个只出现一次的字符
    • JZ35 数组中的逆序对
    • JZ36 两个链表的第一个公共结点
    • JZ37 数字在升序数组中出现的次数
    • JZ38 二叉树的深度
    • JZ39 平衡二叉树
    • JZ40 数组中只出现一次的数字
    • JZ41 和为S的连续正数序列
    • JZ42 和为s的两个数字
    • JZ43 左旋转字符串
    • JZ44 反转单词顺序
    • JZ45 扑克牌顺子
    • JZ46 孩子们的游戏
    • JZ47 求1+2+3+...+n
    • JZ48 不用加减乘除做加法
    • JZ49 把字符串转换成整数
    • JZ50 数组中重复的数字
    • JZ51 构建乘积数组
    • JZ52 正则表达式匹配
    • JZ53 表示数值的字符串
    • JZ54 字符流中第一个不重复的字符
    • JZ55 链表中换的入口结点
    • JZ56 删除链表中的重复结点
    • JZ57 二叉树的下一个结点
    • JZ58 对称的二叉树
    • JZ59 按之字型顺序打印二叉树
    • JZ60 把二叉树打印成多行
    • JZ61 序列化二叉树
    • JZ62 二叉搜索树的第k个结点
    • JZ63 数据流中的中位数
    • JZ64 滑动窗口的最大值
    • JZ65 矩阵中的路径
    • JZ66 机器人的运动范围
    • JZ67 剪绳子

JZ01

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

暴力解法,直接双循环。时间复杂度为: O(n^2)

    public boolean Find(int target, int [][] array) {
        for(int i = 0 ; i < array.length; i++){
            for(int j = 0; j < array[i].length; j++){
                if(target == array[i][j]){
                    return true;
                }
            }
        }
        
        return false;
    }

根据题意,是从小到大递增的数组,所以按照这个规律,先比较每行最末尾的数,若target小于该数,遍历该行即可;若存在,直接返回;不存在,到下一行。

JZ02 替换空格

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

直接遍历,替换空格。

    public String replaceSpace(StringBuffer str) {
        String sb = "";

        for(int i = 0; i < str.length(); i++){
            if( str.charAt(i) != ' '){
                sb = sb + str.charAt(i);
            } else
                sb = sb + "%20";
        }

        return sb;
    }

另外也可以直接调用java的函数.replace()

public String replaceSpace(StringBuffer str) {
        return str.toString().replace(" ", "%20");
    }

JZ03 从尾到头打印链表

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

按部就班,将链表中的值输入到另一个数组中,再将数组中的值,反序输入到ArrayList中。

public ArrayList printListFromTailToHead(ListNode listNode) {
        ArrayList arry = new ArrayList();
        int[] arr = new int[10000];
        int i = 0;
        
        if(listNode == null)
            return arry;
        while(listNode.next != null ){
            arr[i] = listNode.val;
            listNode = listNode.next;
            i++;
        }
        arr[i] = listNode.val;
        
        for( int j = i ; j >= 0 ; j--){
            arry.add(arr[j]);
        }
        return arry;
    }

JZ04 重建二叉树

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

在这里首先要明确二叉树的前序、中序和后序遍历。

  1. 前序:根左右
  2. 中序:左根右
  3. 后序:左右根

那么前序遍历的第一个值,即为根节点;中序遍历根节点前的均为左子树的中序遍历,即可得到左子树的节点数,那么就可以在前序遍历中得到左子树的前序遍历。二者中剩余的变为右子树的,即可带入遍历中。每次遍历返回一个根节点,即为上一次的子节点。另外因为要切割数组,所以用到了java.util.Arrays工具类中的copyOfRange()。牢记左开右闭

public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if( pre.length == 0 || in.length == 0 )
            return null;
        TreeNode root = new TreeNode(pre[0]);
        for(int i = 0 ; i < in.length ; i++){
            if( pre[0] == in[i]) {
                root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(in, 0, i));
                root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(in, i + 1, in.length));
                break;
            }
        }
        
        return root;
    }

JZ05 用两个栈实现队列

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

首先应该明确:栈,先进后出;队列,先进先出。所以考虑直接输出最先出栈的值,其余的值用第二个栈先保存,输出完毕后再输入回第一个栈。有点类似于汉诺塔。

import java.util.Stack;

public class Solution {
    Stack stack1 = new Stack();
    Stack stack2 = new Stack();
    
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
        int lenth = stack1.size();
        for (int i = 0; i < lenth; i++) {
            stack2.push(stack1.pop());
        }

        int res = stack2.pop();
        lenth = stack2.size();
        for (int i = 0; i < lenth ; i++) {
            stack1.push(stack2.pop());
        }

        return res;
    }
}

JZ06 旋转数组的最小数字

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

  1. 直接使用遍历,暴力求解。
    public int minNumberInRotateArray(int [] array) {
        int min = array[0];
        for(int i = 1; i< array.length; i++){
            min = (min < array[i] ? min : array[i]);
        }
        
        return min;
    }
  1. 利用二分法的思想,由于题目中是非递减排序后的数组的旋转,所以可以利用这个特点,但同时也要考虑该特点的特异性(011111或类似相同的数组的一种旋转)。一组非递减排序后的数组,经过旋转之后,可由最小值处分为两段。联想到二分法,mid与last进行比较。有以下情况:
    1. mid > last , 则最小值在mid 和 last之中。
    2. mid < last ,则最小值在first 和 mid之中。
    3. mid == last,分不清,只能一步步缩小last的值。
    public int minNumberInRotateArray(int [] array) {
        if( array.length == 0 )
            return 0;
        int first = 0;
        int last = array.length - 1;
        while (first < last ){
            if( array[first] < array[last] )
                return array[first];
            int mid = ( first + last) >> 1;
            if( array[mid] < array[last]){
                last = mid;
            } else if ( array[mid] > array[last])
                first = mid + 1;
            else
                --last;
        }
        
        return array[first];
    }

JZ07 斐波那契数列

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

经典问题:直接递归。

public int Fibonacci(int n) {
        if(n == 0)
            return 0;
        if(n==1)
            return 1;
        return Fibonacci(n-1) + Fibonacci(n-2);
    }

JZ08 跳台阶问题

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

看到这个题,直接斐波那契数列求解。需要注意的是,在不同的数列中,f0的取值不同。

public int JumpFloor(int target) {
        if( target <= 1)
            return 1;
        return JumpFloor(target - 1) + JumpFloor(target - 2);
    }

当然也可以使用,从下到上的动态规划来解题。

    public int JumpFloor(int target) {
        int[] dp = new int[target + 1];
        if(target == 0)
            dp[0] = 1;
        if(target >= 1)
        {
            dp[0] = 1;
            dp[1] = 1;
        }
            
        for(int i = 2; i < target + 1; i++) {
            dp[i] = dp[i-1] + dp[i-2];
        }
        
        return dp[target];
    }

JZ09 青蛙跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
很明显的递归问题:第一次跳有n种跳法,假设第一次跳了x步,则还剩(n-x)步,将(n-x)又回到原题中了。当剩下的步数(n-x)为0时,就跳到了终点,此时加1即可。

public class Solution {
    public static int COUNT = 0;
    public int JumpFloorII(int target) {
        COUNT = 0;
        jump(target);
        return COUNT;
    }
    
    public void jump(int n){
        for( int i = 1; i <= n; i++){
            if( i== n)
                COUNT ++;
            else
                jump(n-i);
        }
    }
}

通过测试分析可以发现,n = 1时,有1种;n = 2时,有2种;n = 3时,有4种…

发现规律,最终的总数为2的n-1次方。

public class Solution {
    
    public int JumpFloorII(int target) {
        return Math.pow(2, target - 1);
    }
}

JZ10 矩形覆盖

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

比如n=3时,2*3的矩形块有3种覆盖方法。

看似复杂,实则还是斐波那契数列,只是需要注意,此时f(n)序列中的f(0)值为1,但实际上f(0)的值为0。这一点要考虑上。

    public int RectCover(int target) {
        int[] dp = new int[target + 1];
        
        if(target == 0)
            dp[0] = 1;
        
        if( target >= 1 ) {
            dp[0] = 1;
            dp[1] = 1;
            for(int i = 2; i < target + 1; i++)
                dp[i] = dp[i - 1] + dp[i - 2];
        }
        
        if( target == 0 ) return 0;
        return dp[target];
    }

JZ11 二进制中1的个数

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

考察位运算,直接&1即可求得最后一位是否是1,然后依次右移即可。但是这种简单的方法,在java中超出了运算时间2ms。

public int NumberOf1(int n) {
        int count = 0;
        while ( n != 0 ){
            if((n & 1) == 1)
                count++;
            n >>= 1;
        }
        return count;
    }

所以考虑对其进行优化。在上面的方法中每次都要对0进行判断,所以如果能越过0就能节约时间。11001000 - 1后为11000111 再与原数求与,得到11000000即,每次都将最右的一位1去掉了。

    public int NumberOf1(int n) {
        int count = 0;
        while ( n != 0 ){
            ++ count;
            n = n & (n-1);
        }
        return count;
    }

JZ12 数值的整数次方

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

直接用Math包(import java.lang.Math)下的pow()方法即可。

public double Power(double base, int exponent) {
        return Math.pow(base, exponent);
  }

当然,也可以直接一个while循环,exponent--.

JZ13 调整数组顺序,使奇数位于偶数前面

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

暴力求解:直接三次循环。1. 将数组中的奇数放入暂存数组中。2. 将数组中的偶数放入暂存数组中。3. 将暂存数组放回到原来的数组。

    public void reOrderArray(int [] array) {
        int[] b = new int[array.length];
        
        int j = 0;
        for(int i = 0; i < array.length ; i++) {
            if( array[i] % 2 == 1 ){
                b[j] = array[i];
                j ++;
            }
        }
        
        for(int i = 0; i < array.length ; i++) {
            if( array[i] % 2 == 0 ){
                b[j] = array[i];
                j ++;
            }
        }
        
        for(int i = 0; i < array.length ; i++) {
            array[i] = b[i];
        }
    }

JZ14 链表中倒数第k个结点

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

两种思路,一是直接遍历该链表,得到链表的长度length,倒数第k个结点,即为正数第(n+1-k)个。

public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null || k <= 0 )
            return null;
        
        int length = 0;
        ListNode out = head;
        while( out != null){
            out = out.next;
            length ++;
        }
        
        if( k > length)
            return null;
        
        for(int i = 0; i < length - k ; i++) {
            head = head.next;
        }
       
        return head;
    }

二是快慢指针,同时两个指针对链表进行遍历,fast指针先走k步,那么当fast到null时,slow到倒数第k个位置。

public ListNode FindKthToTail(ListNode head,int k) {
        if( head == null || k <= 0 ) return null;
        ListNode slow = head;
        ListNode fast = head;

        while( k > 0 ){
            if( fast == null)
                return null;
            fast = fast.next;
            k--;
        }
        
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        
        return slow;
    }

JZ15 反转链表

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

用三个指针来调整指针的位置。

  1. pre指针指向已经反转好的链表的最后一个节点,最开始没有反转,所以指向nullptr
  2. cur指针指向待反转链表的第一个节点,最开始第一个节点待反转,所以指向head
  3. nex指针指向待反转链表的第二个节点。

首先将指向下一个位置的指针保留到nex中;然后才进行反转操作。

把当前元素的下一个位置的指针指向上一个元素。

然后把当前元素接到pre中。

当前元素指向原始序列的下一个元素中。

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

JZ16 合并两个排序的链表

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

有两个思路,一个是直接依次比较,然后输出整个链表。其二是迭代。

直接比较时,需要两个个指向新链表头部的指针。一个为了保存新链表的头部,作为输出;另一个则指向当前元素,做添加操作。所以在每次的循环结束后,都会将next指向它。

public ListNode Merge(ListNode list1,ListNode list2) {
        ListNode newList = null;
        ListNode head = null;
        if( list1 == null ) return list2;
        if( list2 == null ) return list1;
        if(list1.val < list2.val){
            newList = list1;
            list1 = list1.next;
        }
        else {
            newList = list2;
            list2 = list2.next;
        }

        head = newList;
        while( !( list1 == null && list2 == null ) ){
            if(list1 == null ) {
                newList.next = list2;
                list2 = list2.next;
            } else if (list2 == null ) {
                newList.next = list1;
                list1 = list1.next;
            }else if(list1.val < list2.val) {
                newList.next = list1;
                list1 = list1.next;
            }
            else {
                newList.next = list2;
                list2 = list2.next;
            }
            
            newList = newList.next;
        }
        
        
        return head;
    }

其二是通过迭代的方式去得到最终结果。需要考虑的是迭代的终止条件到底是什么?是输入的两个指针都为空,还是至少一个为空。至少一个为空时,将剩余的接上即可。

public ListNode Merge(ListNode list1,ListNode list2) {
        if (list1 == null) return list2;
        if (list2 == null) return list1;
        
        ListNode head = null;
        if(list1.val <= list2.val){
            head = list1;
            head.next = Merge(list1.next, list2);
        } else {
            head = list2;
            head.next = Merge(list1, list2.next);
        }
            
        return head;
    }

JZ17 树的子结构

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

错误想法:

同样也是两个思路

1. 直接遍历A的所有节点,与B进行比较。

2. 迭代比较,直接比较A B, 若A != B则比较A的左右子树和B是否相同。

子结构:树A和树B的根结点相等,并且树A的左子树和树B的左子树相等,树A的右子树和树B的右子树相等。

  1. 递归函数的功能:判断2个数是否有相同的结构,如果相同,返回true,否则返回false。
  2. 递归终止条件:如果树B为空,返回true,此时,不管树A是否为空,都为true。
    否则,如果树B不为空,但是树A为空,返回false,此时B还没空但A空了,显然false。
  3. 下一步递归参数:如果A的根节点和B的根节点不相等,直接返回false。否则,相等,就继续判断A的左子树和B的左子树,A的右子树和B的右子树

然后就是正确遍历树A中的各个根节点,并对这些根节点进行判断。

    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if( root1 == null || root2 == null ) return false;
        return equals(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
    }
    
    public boolean equals (TreeNode root1,TreeNode root2) {
        if( root2 == null ) return true;
        if( root1 == null ) return false;
        
        return root1.val == root2.val && equals(root1.left, root2.left) && equals(root1.right, root2.right);
    }

错误的迭代方法:

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

JZ18 二叉树的镜像

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

依旧是遍历问题,但是要注意合适的终止情况,当节点为null时,或节点的两个子节点为空时,退出。而其余情况,交换两个节点的位置,然后对该节点继续遍历。

public void Mirror(TreeNode root) {
        TreeNode temp;

        if( root == null );
        else if( root.left == null && root.right == null ) {

        } else {
            temp = root.left;
            root.left = root.right;
            root.right = temp;
            Mirror(root.left);
            Mirror(root.right);
            
        }
    }

JZ19 顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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 printMatrix(int [][] matrix) {
        ArrayList list = new ArrayList<>();
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0)
            return list;
        
        int up = 0;
        int down = matrix.length - 1;
        int left = 0;
        int right = matrix[0].length - 1;
        
        while(true) {
            for(int col = left; col <= right ; col++) 
                list.add(matrix[up][col]);
            up ++;
            if(up > down)
                break;
            
            for(int row = up; row <= down; row++) 
                list.add(matrix[row][right]);
            right --;
            if(right < left)
                break;
            
            for(int col = right; col >= left; col--) 
                list.add(matrix[down][col]);
            down --;
            if( down < up)
                break;
            
            for(int row = down; row >= up; row-- ) 
                list.add(matrix[row][left]);
            left ++;
            if( left > right)
                break;
        }
        
        return list;
        
    }

JZ20 包含min函数的栈

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

使用双栈法,注意:在java中取出栈顶元素的操作为peek()方法。

import java.util.Stack;

public class Solution {

    Stack stack = new Stack<>();
    Stack stackMin = new Stack<>();
    
    public void push(int node) {
        stack.push(node);
        if(stackMin.isEmpty())
            stackMin.push(node);
        else if( node < stackMin.peek())
            stackMin.push(node);
        else
            stackMin.push(stackMin.peek());
    }
    
    public void pop() {
        stack.pop();
        stackMin.pop();
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int min() {
        return stackMin.peek();
    }
}

除了双栈法,也可以使用压缩还原法。每一个进栈的元素都与最小值有关。但是压缩还原法会在INT_MININT_MAX时,由于差值过大,发生溢出。

每次都存储差值。注意出栈时使用的差值,要和入栈时使用的差值计算方式一致。

public void push(int node) {
        
        if(stack.isEmpty()){
            min = node;
            stack.push(0);
        } else {
            if(node < min) {
                stack.push(min - node);
                min = node;
            } else 
                stack.push(min - node);
        }
    }

public void pop() {
        if( stack.peek() > 0 ) {
            min = min + stack.peek();
            stack.pop();
        } else
            stack.pop();
            
    }

JZ21 栈的压入、弹出序列

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

注意,压入顺序不代表是一次性压完。可以压一点、弹一点;再压一点,再弹一些。

使用一个辅助栈来进行模拟。对压入序列中各元素进行入栈操作,当栈顶元素与弹出序列一样时,弹出该元素并移动指针到弹出序列的下一个位置,继续判断栈顶元素与弹出序列是否一致,重复该循环。直到不一致时,继续压栈。

当压栈的for循环完成后,如果该栈能顺利完全弹出,则栈内没有元素isEmpty() == true,所不能则false.

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if( pushA.length == 0 || popA.length == 0 || pushA.length != popA.length )
            return false;
        Stack stack = new Stack<>();
        int j = 0;
        
        for(int i = 0; i < pushA.length; i++){
            stack.push(pushA[i]);
            
            while( !stack.isEmpty() && stack.peek() == popA[j]) {
                stack.pop();
                j++;
            }
                
        }
        
        return stack.isEmpty();
    }
}

JZ22 从上往下打印二叉树

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

遍历所有的节点,但是需要对某些节点暂存。这里用到了队列的思想,先进先出。由于Queue是一个抽象类,不能进行实例化,所以要利用java多态的特性进行实例化。Queue queue = new LinkedList<>();

将遍历到,暂时没有输出的节点存储在队列中。

需要注意,队列的相关操作:

  1. 容量不够或队列为空时不会抛异常:offer(添加队尾元素)、peek(访问队头元素)、poll(访问队头元素并移除)
  2. 容量不够或队列为空时抛异常:add、element(访问队列元素)、remove(访问队头元素并移除)
public class Solution {
    public ArrayList PrintFromTopToBottom(TreeNode root) {
        ArrayList list = new ArrayList<>();
        
        Queue queue = new LinkedList();
        if(root == null) return list;
        queue.offer(root);
        
        while( !queue.isEmpty() ) {
            TreeNode temp = queue.poll();
            list.add(temp.val);
            if(temp.left != null) queue.offer(temp.left);
            if(temp.right != null) queue.offer(temp.right);
        }
        return list;
    }
}

JZ23 二叉树的后序遍历

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

错误思路:

直接后序遍历二叉树,遍历的同时与该数组做比较。

需要注意的是,题干中并未给出二叉树的存在。所以该算法应该具有普适性,需要从二叉搜索树这个数据结构去入手。左子树小于根节点,根节点小于右子树。这才是正确的二叉搜索树存储思想。

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

JZ24 二叉树中和为某值的路径

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

迭代为先,明确三点:

  1. 迭代做什么:遍历所有子结点,FindPath(TreeNode* root,int sum),从root节点出发,找和为sum的路径。
  2. 迭代的终止条件: 该结点为叶子结点,且该路径的和为所求值。
  3. 下一次迭代的参数: 从该结点的左子树和右子树出发,传入的参数为target-val,即还差多少。
public class Solution {
    private ArrayList> result = new ArrayList>();
    private ArrayList list = new ArrayList<>();
    
    public ArrayList> FindPath(TreeNode root,int target) {
        if( root == null )
            return result;
        
        list.add(root.val);
        target -= root.val;
        
        if( target == 0 && root.left == null && root.right == null) 
            result.add( new ArrayList(list));
        
        ArrayList> r1 = FindPath(root.left, target );
        ArrayList> r2 = FindPath(root.right, target );
        
        list.remove(list.size() - 1);
        return result;
    }
}

JZ25 复杂链表的复制

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

本题有以下几个需要注意的点:

  1. 不能出现参数中的节点引用,也就是说需要实例化同样多个数的节点,并对这些节点进行赋值。同时确保这些节点之间的next和random关系一一对应。
  2. 考虑边界情况,即输入的链表为空的情况。

由此得出算法的步骤为:先将链表中的节点进行拷贝,然后再对各节点之间的对应关系进行拷贝。

用到了HashMap来存储复制前后的节点。

import java.util.HashMap;
public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if (pHead == null ) return null; 
        RandomListNode ppHead = pHead;
        HashMap hash = new HashMap<>();
        
        while( pHead != null ) {
            RandomListNode node = new RandomListNode(pHead.label);
            hash.put(pHead, node);
            
            pHead = pHead.next;
        }
        pHead = ppHead;
        while( pHead != null ) {
            RandomListNode node = hash.get(pHead);
            node.next = (pHead.next == null )? null : hash.get(pHead.next);
            node.random = (pHead.random == null) ? null : hash.get(pHead.random);
            pHead = pHead.next;
        }

        return hash.get(ppHead);
    }
}

JZ26 二叉搜索树与双向链表

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

由题意可以,该二叉树是一个二叉搜索树,如果使用中序遍历的话,则得到的结果是有序且从小到大的,再对其进行指针重排即可。

在遍历过程中,判断输入是否为null的时间复杂度较高,为了节约时间,应该对三种情况分别处理,且分别处理可以提前一层处理数据。

import java.util.ArrayList;
import java.util.Iterator;
public class Solution {
    public ArrayList list = new ArrayList<>();
    
    public TreeNode Convert(TreeNode pRootOfTree) {
        if( pRootOfTree == null ) return null;
        DFS(pRootOfTree);
        
        if(list.size() == 1)
            return pRootOfTree;
        
        for(int i = 0 ; i < list.size() - 1; i++) {
            list.get(i).right = list.get(i+1);
            list.get(i+1).left = list.get(i);
        }

        return list.get(0);
        
    }
    
    public void DFS(TreeNode root) {
        if(root.left != null)
            DFS(root.left);
        list.add(root);
        if(root.right != null)
            DFS(root.right);
    }
}

JZ27 字符串的排列

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

将字符串中的字符进行全排列,然后存入中去重后,转换成ArrayList并排序。需要注意

  1. 当传入的参数为数组时,传入的实际上是其引用,使用时会改变原始值。
  2. 当迭代之后,使用过的值,要还原。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
import java.util.Comparator;

public class Solution {
    Set sets = new HashSet<>();
    public ArrayList Permutation(String str) {
        ArrayList list = new ArrayList<>();
        if (str.length() == 0)
            return list;

        char[] chs = str.toCharArray();
        boolean[] used = new boolean[chs.length];
        quanpailie(chs, used, chs.length, new StringBuilder());

        for (String s : sets) {
            list.add(s);
        }

        list.sort(new Comparator() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });

        return list;
    }

    public void quanpailie(char[] chars, boolean[] used, int pos, StringBuilder sb) {
        StringBuilder stringBuilder = new StringBuilder(sb);
        boolean[] uu = new boolean[chars.length];
        for (int i = 0; i < chars.length; i++)
            uu[i] = used[i];
        if (pos != 0) {
            for (int i = 0; i < chars.length; i++) {
                if (!uu[i]) {
                    stringBuilder.append(chars[i]);
                    uu[i] = true;
                    quanpailie(chars, uu, pos - 1, stringBuilder);
                    uu[i] = false;
                    stringBuilder.deleteCharAt(stringBuilder.length() - 1);
                } else
                    continue;;
            }
        } else {
                sets.add(stringBuilder.toString());
        }
    }
}

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

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

较为简单的解法,直接做比较,然后统计是否相同。这里的时间复杂度为O(n^2)

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        int count = 0;
        if(array.length == 1)
            return array[0];
        for(int i = 0 ; i < array.length - 1  ; i++){
            count = 0;
            for(int j = i ; j < array.length ; j++ ) {
                if( array[i] == array[j] ){
                    count ++;
                } 
                if( count > array.length / 2)
                    return array[i];
            }
            
        }
        
        return 0;
    }
}

循环时间太多,考虑优化,将i的终止条件,从i < array.length - 1改为i < (array.length + 1) / 2即做比较的数只用前半段的,因为要求的数出现的个数必须超过数组长度的一半,所以只比较前一半的数就好。

另外还有一种思路,先对数组进行排序,由于出现次数超过数组长度的一半,那么这个数一定是中间那个数,即中间那个数在数组中出现的次数即可,如果大于数组长度的一半则,输出这个数;否则输出0。

import java.util.Arrays;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        Arrays.sort(array);
        int half = array.length/2;
        int count = 0;
        
        for( int i = 0; i < array.length ; i ++)
            if( array[i] == array[half] )
                count++;
        if(count > half) 
            return array[half];
        else
            return 0;
    }
}

用到了排序,那么时间复杂度取决于排序算法的快慢。

import java.util.Arrays;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        Arrays.sort(array);
        int half = array.length/2;
        int count = 0;
        
        for( int i = 0; i < array.length ; i ++)
            if( array[i] == array[half] )
                count++;
        if(count > half) 
            return array[half];
        else
            return 0;
    }
}

JZ29 最小的k个数

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

较为简单的做法:先排序,然后直接输出前k个数字即可。时间复杂度为O(n^2+k)

import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList list = new ArrayList();
        if( k > input.length)
            return list;
        
        Arrays.sort(input);
        
        for(int i = 0 ; i < k ; i++){
            list.add(input[i]);
        }
        
        return list;
    }
}

对其进行优化的话,在排序上入手,考虑到冒泡排序算法,每一轮之后,浮动到最右边的即为最大值,那么可以反其道而行之。使得每一轮之后,浮动到最左边的为最小值,然后将该值输出。此时时间复杂度为O(kn)

import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList list = new ArrayList();
        if( k > input.length)
            return list;
        
        for(int i = 0 ; i < k; i++) {
            for( int j = input.length - 1; j > i ; j-- ){
                if( input[j-1] > input[j]){
                    swap(input, j-1, j);
                }
            }
            list.add(input[i]);
        }
        
        return list;
    }
    
    public void swap(int[] chars, int i, int j) {
        int temp;
        temp = chars[i];
        chars[i] = chars[j];
        chars[j] = temp;
    }
}

JZ30 连续子数组的最大和

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

直接遍历,枚举出所有的子向量和。

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array.length == 0) return 0;
        if(array.length == 1) return array[0];
        int max = array[array.length-1];
        
        for(int i = 0; i < array.length - 1; i++){
            for( int j = i; j  max ? Sum(array, i, j) : max);
            }
        }
        
        return max;
    }
    
    public int Sum(int[] array, int i, int j) {
        int sum = 0;
        for(int k = i; k<= j ; k++)
           sum += array[k];
        return sum;
    }
}

换一种思路,可以使用动态规划。dp[n]代表以当前元素为截止点的连续子序列的最大和,如果dp[n-1]>0dp[n]=dp[n]+dp[n-1],因为当前数字加上一个正数一定会变大;如果dp[n-1]<0dp[n]不变,因为当前数字加上一个负数一定会变小。使用一个变量max记录最大的dp值返回即可。

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int max = array[0];
        for(int i = 1; i < array.length ; i++){
            array[i] += array[i-1] > 0 ? array[i-1] : 0;
            max = max > array[i] ? max : array[i] ;
        }
        return max;
    }
}

JZ31 整数中1出现的次数

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

简单的方法是直接遍历。

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int count = 0;
        for(int i = 1; i <= n; i++){
            int num = i;
            while( num != 0) {
                if( num % 10 == 1 ) {
                    count ++;
                    num = num / 10;
                }
            }
        }
        
        return count;
    }
}

但是该方法空间复杂度过大。所以要对其进行优化。对每一位的情况求解,那么他们的和就是要求的答案。high为高位,digit * cur为当前位,low为低位。
以20X4为例:当digit=10,即在十位上时,有三种情况:

  1. cur == 0, 此时需要计算的数的范围是0010~1910,即为 high*digit.
  2. cur == 1,此时需要计算的数的范围是 0010~2014,即为 high*digit + low + 1
  3. cur == others,此时需要计算的数的范围是0010~2019,即为(high + 1)*digit
public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int res = 0;
        int digit = 1;
        int high = n / 10;
        int cur = n % 10;
        int low = 0;
        
        while( high != 0 || cur != 0 ) {
            if( cur == 0 ) res += high * digit;
            else if ( cur == 1 ) res += high * digit + low + 1;
            else res += (high + 1) * digit;
            
            low += cur*digit;
            cur = high % 10;
            high = high / 10;
            digit = digit * 10;
        }
        
        return res;
    }
}

JZ32 把数组排成最小的数

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

类似于排序,不过不同的是排序的比较方式不一样。不是直接比大小,而是比同一位数的大小,如果相同则比第二位。最后按顺序输出即可。这种排序方式也不对,应该是比较两个串直接连接起来谁大谁小。

import java.util.ArrayList;
import java.lang.String;

public class Solution {
    public String PrintMinNumber(int [] numbers) {
        String[] strs = new String[numbers.length];
        for( int i = 0 ; i < numbers.length ; i ++){
            strs[i] = String.valueOf(numbers[i]);
        }
        
        for( int i = 0 ; i < numbers.length ; i ++){
            for( int j = 0 ; j < numbers.length - 1 -i; j++) {
                if( !Compare(strs[j], strs[j+1]) ) {
                    swap(strs, j, j+1);
                }
            }
        }
        
        StringBuilder sb = new StringBuilder();
        for(int i = 0 ; i < numbers.length ; i++){
            sb.append(strs[i]);
        }
        
        return sb.toString();
    }
    
    public boolean Compare (String str1, String str2) {
        String s1 = str1 + str2;
        String s2 = str2 + str1;
        if( s1.compareTo(s2) < 0)
            return true;
        else
            return false;
    }
    
    public void swap (String[] numbers, int i, int j){
        String temp;
        temp = numbers[i];
        numbers[i] = numbers[j];
        numbers[j] = temp;
    }
}

JZ33 丑数

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

由此可见,每个数都可以因式分解为2^x*3^y*5^z,那么只要能让这些结果有序的排到数组中,即可以得到有序且不重复的丑数序列。

那么如何让其有序且不重复呢。而在2^x,3^y,5^z中,如果x=y=z那么最小丑数一定是乘以2的,但关键是有可能存在x>y>z的情况,所以我们要维持三个指针来记录当前乘以2、乘以3、乘以5的最小值,然后当其被选为新的最小值后,要把相应的指针+1;因为这个指针会逐渐遍历整个数组,因此最终数组中的每一个值都会被乘以2、乘以3、乘以5,也就是实现了我们最开始的想法,只不过不是同时成乘以2、3、5,而是在需要的时候乘以2、3、5.

为了避免其重复,我们在得到最小值后,需要对其进行一次校验,如果能与另一个数*它对应的底那么指针应该+1。

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if (index < 7)
            return index;
        int[] res = new int[index];
        res[0] = 1;
        int p2=0, p3=0, p5=0;
        
        for(int i = 1; i < index; i ++){
            
            res[i] = Math.min(res[p2]*2, Math.min(res[p3]*3, res[p5]*5));
            if(res[i] == res[p2]*2) p2++;
            if(res[i] == res[p3]*3) p3++;
            if(res[i] == res[p5]*5) p5++;
            
        }
        return res[index-1];
    }
}

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

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

首先遍历,统计其出现的个数,并将其存入HashMap中,然后再遍历一次输出第一个只出现一次的字符。

import java.util.HashMap;
import java.util.Set;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        HashMap map = new HashMap<>();
        for(int i = 0 ; i < str.length(); i++){
            if(map.get(str.charAt(i)) != null) {
                int ch = map.get(str.charAt(i));
                ch ++;
                map.put(str.charAt(i), ch);
            } else 
                map.put(str.charAt(i), 1);
        }
        
        for(int i = 0 ; i < str.length(); i++){
            if(map.get(str.charAt(i)) == 1)
                return i;
        }
        return -1;
    }
}

当然,这个hashmap也可以用数组来实现。

除了使用这种方法,也可以直接双循环,进行查找,当大于1时,跳出。注意,为了使得后面的能有对比,第二次循环也应该从0开始。

public class Solution {
    public int FirstNotRepeatingChar(String str) {
        if(str == null ) return -1;
        if(str.length() == 0) return -1;
        
        char[] chars = str.toCharArray();
        int pos = -1;
        int i = 0, j = 0;
        for( i = 0; i < chars.length; i++) {
            for( j = 0 ; j < chars.length; j++) {
                if( (chars[i] == chars[j]) && i != j )
                    break;
            }
            if( j == chars.length) {
                pos = i;
                break;
            }
        }
        return pos;
    }
}

JZ35 数组中的逆序对

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

直接枚举:

public class Solution {
    public int InversePairs(int [] array) {
        int count = 0;
        for(int i = 0;i < array.length ; i++){
            for( int j = i + 1; j < array.length ; j ++){
                if( array[i] > array[j] ){
                    count ++;
                    count %= 1000000007;
                }
            }
        }
        
        return count;
    }
}

归并排序的思想。当左右需要合并的数组为[3, 4], [1, 2]时。可以发现其逆序对分别为3,1 3,2 4,1 4,2

import java.io.*;
public class Main {
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str = br.readLine();
        str = str.substring(1, str.length()-1);
        String[] valueArr = str.split(",");
        int[] array = new int[valueArr.length];
        for (int i = 0; i < valueArr.length; i++) {
            array[i] = Integer.parseInt(valueArr[i]);
        }
        System.out.println(InversePairs(array));
    }
    public static int cons = 1000000007;
    public int InversePairs(int [] array) {
        if(array == null) return 0;
        int[] temp = new int[array.length];
        return mergeSort(array, temp, 0, array.length-1);
    }
    
    public int mergeSort(int[] array, int[] temp, int low, int high) {
        if(low >= high) return 0;
        int res = 0;
        int mid = low + ((high -low)>>1);
        res += mergeSort (array, temp, low, mid);
        res += mergeSort (array, temp, mid + 1, high);
        res += merge(array, temp, low, mid, high);
        
        res %= cons;
        return res;
    }

    public int merge(int[] array, int[] temp, int low, int mid, int high) {
        int i = low, j = mid + 1,k = low;
        int res = 0;
        while(i <= mid && j <= high) {
            if(array[i] > array[j]) {
                res += mid - i + 1;
                res %= cons;
                temp[k++] = array[j++];
            } else
                temp[k++] = array[i++];
        }
        while(i <= mid)
            temp[k++] = array[i++];
        while(j <= high)
            temp[k++] = array[j++];
        for (i = low; i <= high; i++)
            array[i] = temp[i];
        return res;
    }
}

JZ36 两个链表的第一个公共结点

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

同时对两个链表进行遍历。这时候,如果公共结点不在同一长度时,无法正确判断。要么选择双循环,要么就选择让两个公共结点处于同一长度。 由于a + b = b + a,那么如果在第一个链表遍历完时,接上第二个链表;在第二个链表遍历完时,接上第一个链表,那么就可以保证能公共结点处于同一长度。

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1, p2 = pHead2;
        while( p1 != p2) {
            p1 = (p1 != null) ? p1.next : pHead2;
            p2 = (p2 != null) ? p2.next : pHead1;
        }
        
        return p1;
    }
}

JZ37 数字在升序数组中出现的次数

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

直接遍历,当大于k时,支持return即可。

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        int count = 0;
        for(int i = 0; i < array.length; i++){
            if( array[i] == k)
                count++;
            else if (array[i] > k) 
                return count;
        }
        return count;
    }
}

使用二分查找。分别找到连续的k的上下界。

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        int left = 0, right = 0;
        int mid;
        int l = 0, r = array.length;
        while( l < r ) {
            mid = l + (r-l)/2;
            if( k > array[mid])
                l = mid + 1;
            else r = mid;
        }
        left = l;
        l = 0;
        r = array.length;
        while( l < r ) {
            mid = l + (r-l)/2;
            if( k < array[mid])
                r = mid;
            else
                l = mid + 1;
        }
        right = l;
        
        return right - left;
    }
}

注意:位运算的优先级特别低,比加减还要低。

JZ38 二叉树的深度

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

一看题目,就是一道遍历的题。对于数的遍历,有三种方式:前序遍历、中序遍历以及后序遍历。这里暂时使用的是前序遍历。

需要注意的是:对输入了一个空树的考虑,应该直接return 0.同时递归的时候,思路有点不对,应该是分别求左节点是多深,右节点是多深。

public int TreeDepth(TreeNode root) {
            if (root == null) return 0;
            if (root.left == null ){
                if (root.right == null) {
                    return 1;
                }
                return TreeDepth(root.right) + 1;
            } else {
                if (root.right == null)
                    return TreeDepth(root.left) + 1;
                else {
                    return TreeDepth(root.left) > TreeDepth(root.right) ? ( TreeDepth(root.left) + 1 ) : (TreeDepth(root.right) + 1);
                }
            }
        }

// Method 2
public int TreeDepth(TreeNode root) {
    if(root==null){
        return 0;
    }
    int left=TreeDepth(root.left);
    int right=TreeDepth(root.right);
    return (left > right) ? (left+1) : (right+1);
}

JZ39 平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树。

平衡二叉树:任意结点的子树的高度差都小于等于1。

判断左右子树的深度,如果其差值大于1,则非平衡树;如果小于等于1,则是平衡树。

计算左右子树的深度,需要使用递归进行计算。注意,不是遍历的话,其返回值应该是其子树中最深的。

public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        
        if( root == null )
            return true;
        else {
            int leftDeepth = 0;
            int rightDeepth = 0;

            leftDeepth = TreeDeepth(root.left);
            rightDeepth = TreeDeepth(root.right);

            if(leftDeepth - rightDeepth > 1)
                return false;
            else if( rightDeepth - leftDeepth > 1)
                return false;
            else
                return true;
        }
    }
    
    public int TreeDeepth(TreeNode root){
        int count = 1;
        if( root == null)
            return 0;
        else {
            int left = 0;
            int right = 0;
            left = TreeDeepth(root.left);
            right = TreeDeepth(root.right);
            count += Math.max(left, right);
            return count;
        }
    }
}

JZ40 数组中只出现一次的数字

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

先遍历数组,并用hashmap存储每个数字出现的次数。然后遍历该hashmap输出vaule为1的key。

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
import java.util.HashMap;
import java.util.Set;
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        HashMap map = new HashMap();
        boolean first = true;
        for(int i = 0 ; i < array.length ; i ++ ){
            int ch = 0;
            if(map.get(array[i]) == null)
                ch = 0;
            else
                ch = map.get(array[i]);
            map.put(array[i], ++ch);
        }
        Set keys = map.keySet();
        for( Integer i : keys) {
            if(map.get(i) == 1 && first ) {
                num1[0] = i;
                first = false;
            }
            else if (map.get(i) == 1 && !first)
                num2[0] = i;
        }
    }
}

JZ41 和为S的连续正数序列

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

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

直接双循环穷举。

import java.util.ArrayList;
public class Solution {
    public ArrayList > FindContinuousSequence(int sum) {
        ArrayList > list = new ArrayList > ();
        int count = 0;
        for(int i = 1 ; i <= sum / 2 ; i ++ ) {
            int j = i;
            count = 0;
            ArrayList list2 = new ArrayList<>();
            while( true ) {
                count += j;
                if( count < sum ) {
                    list2.add(j);
                    j++;
                } else if ( count == sum ) {
                    list2.add(j);
                    list.add(list2);
                } else 
                    break;
            }
        }
        
        return list;
    }
}
// 观察连续的正序列和为100,有2组`9 10 11 12 13 14 15 16`, `18 19 20 21 22`.可以发现,其中间的数为这组数的平均数,或平均数正负0.5。

JZ42 和为s的两个数字

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

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

注意:这里有一个迷惑人的信息,如果有多对数字的和等于S,输出两个数的乘积最小的。由小学知识可以知道,一根绳子围成的图像,面积最小的是边长差距最大的长方形,然后是正方形稍大,最大是圆形。

所以乘积最小的就是第一次出现的和为S的两个数字。

import java.util.ArrayList;
public class Solution {
    public ArrayList FindNumbersWithSum(int [] array,int sum) {
        ArrayList list = new ArrayList();
        for( int i = 0; i < array.length -1; i++ ) {
            for( int j = i + 1; j < array.length ; j++ ){
                if( array[i] + array[j] == sum ){
                    list.add(array[i]);
                    list.add(array[j]);
                    
                    return list;
                }
            }
        }
        
        return list;
    }
}

JZ43 左旋转字符串

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

直接使用自带的库函数substring(int begin), substring(int begin, int end)

public class Solution {
    public String LeftRotateString(String str,int n) {
        if( n > str.length() ) 
            return str;
        String string = str.substring(n) + str.substring(0, n);
        return string;
    }
}

JZ44 反转单词顺序

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

对原字符串进行拆分,然后将得到的单词放到另一个数组中。

最后在按顺序读取这个数组。

public String ReverseSentence(String str) {
        int count = 0;
        for(int i = 0; i < str.length(); i++){
            if(str.charAt(i) == ' ')
                count++;
        }

        StringBuilder[] strs = new StringBuilder[count+1];
        for(int i = 0 ; i < strs.length ; i++) {
            strs[i] = new StringBuilder("");
        }

        for(int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if( c != ' ' && c != '\0'){
                strs[count].append(c);
            } else {
                count--;
            }
        }
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < strs.length - 1 ; i++)
        {
            sb.append(strs[i].toString());
            sb.append(' ');
        }
        sb.append(strs[strs.length - 1].toString());
        return sb.toString();
    }

需要注意,使用SrtingBuilder[] strs = new SrtingBuilder[]{};时,并没有初始化。,每个元素指向的均为null值,而不是值为null的StringBuilder.

JZ45 扑克牌顺子

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。

拿到题,先进行排序。然后统计大小王的个数,再统计从第一个非0的数起,其后一个数与其的差值。如果他们相等,返回false;如果有差值,若差值-1大于0的个数,则不能用大小王代替;如果小于或等于,则消耗对应数量的零。

import java.util.Arrays;
public class Solution {
    public boolean isContinuous(int [] numbers) {
           if( numbers.length != 5 )
            return false;
        Arrays.sort(numbers);

        int count = 0;
        for (int i = 0 ; i < numbers.length - 1 ; i++ ){
            if(numbers[i] == 0) {
                count ++;
            } else if( numbers[i+1] == numbers[i] && numbers[i+1] != 0) {
                return false;
            } else if( numbers[i+1] - numbers[i] - 1 > count )
                return false;
            else if( numbers[i+1] - numbers[i] - 1 <= count ) {
                count -= (numbers[i+1] - numbers[i] - 1);
            } else
                return true;
        }
        return true;
    }
}

JZ46 孩子们的游戏

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

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

模拟法:使用数组来模拟不好删除,所以使用list来保存。同时用cur指针用来指向开始的元素。m可能会大于n,此时就应该从头开始循环。删除之后,cur指针应该回溯一次。

import java.util.ArrayList;
import java.util.List;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n <= 0)
            return -1;
        else {
            List list = new ArrayList();
            for (int i = 0 ; i < n ; i ++){
                list.add(i);
            }

            int cur = -1;
            while (list.size() > 1) {
                for(int i = 0 ; i < m ; i ++){
                    cur++;
                    if(cur == list.size()) {
                        cur = 0;
                    }
                }
                list.remove(cur);
                cur--;
            }
            return list.get(0);
        }
    }
}

递归的题。每次都删除的是(m % n)当知道了下一次(n-1)删除了第x个元素后,那么本次删除的就是(m%n + x) % n;(x + m ) % n.

import java.util.ArrayList;
import java.util.List;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if ( n <= 0 ) return -1;
        return f(n, m);
    }
    
    public int f(int n, int m ) {
        if ( n == 1 ) return 0;
        int x = f(n-1, m);
        return ( x + m ) % n;
    }
}

JZ47 求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 sum = n;
        boolean stop = (sum != 0 ) && ( ( sum += Sum_Solution( sum - 1 )) != 0);
        
        return sum;
    }
}

JZ48 不用加减乘除做加法

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

本题考查位运算,两个数相的问题即考虑两个数本身 + 进位三个数的和。当没有进位时,和为按位异或;有进位时,和为按位异或之后再与进位按位异或…直到没有进位。

所以先加(按位异或),再计算进位(按位与),再判断是否有进位。

注意:进位,是要在本来的位上再进一位,即需要左移一位。

    public int Add(int num1,int num2) {
        int sum = 0;
        while( num2 != 0 ){
            sum = num1^num2;
            num2 = (num1&num2) << 1;
            num1 = sum;
        }
        return num1;
    }

JZ49 把字符串转换成整数

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

直接从尾到底进行遍历。如果当前是第一位,那么有三种情况:符号位,数字,其它。不是第一位,则只有两种,数字和其它。

当其为数字时的转换为数字的策略。

10的离末尾的距离次方 * 当前位置的数 + 之前的数。

public int StrToInt(String str) {
        int res = 0;
        for( int i = str.length() - 1; i >= 0 ; i-- ){
            if( i == 0 ) {
                if( str.charAt(i) == '+' ) {
                    return res;
                }
                if( str.charAt(i) == '-' ) {
                    return -res;
                }
                if( str.charAt(i) >= '0' && str.charAt(i) <= '9' ) {
                    res = ((int) Math.pow(10, (str.length() - 1 - i)))*(str.charAt(i) - '0') + res;
                    return res;
                }
                return 0;
            } else {
                if( str.charAt(i) >= '0' && str.charAt(i) <= '9' ) {
                    res = ((int) Math.pow(10, (str.length() - 1 - i)))*(str.charAt(i) - '0') + res;
                } else
                    return 0;
            }
        }
        return 0;
    }

JZ50 数组中重复的数字

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

重复问题,使用hashmap来存储重复的个数,最后再遍历一次即可得到结果。

import java.util.HashMap;
public class Solution {
    // Parameters:
    //    numbers:     an array of integers
    //    length:      the length of array numbers
    //    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
    //                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
    //    这里要特别注意~返回任意重复的一个,赋值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) {
        HashMap map = new HashMap();
        for(int i = 0 ; i < length ; i ++ ){
            if( !map.containsKey(numbers[i]) )
                map.put(numbers[i], 1);
            else {
                int num = map.get(numbers[i]);
                num++;
                map.put(numbers[i], num);
            }
        }

        for( int i = 0 ; i < length ; i++) {
            if( map.get(numbers[i]) > 1 ) {
                duplication[0] = numbers[i];
                return true;
            }
        }

        return false;
    }
}

JZ51 构建乘积数组

给定一个数组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无意义,故而无法构建,因此该情况不会存在。

直接for循环暴力算法:

public int[] multiply(int[] A) {
        int[] B = new int[A.length];
        for(int i = 0; i < A.length; i++){
            if( i == 0 ) {
                B[i] = A[1];
                for(int j = 2; j < A.length ; j++ )
                    B[i] = B[i] * A[j];
            } else if( i == A.length - 1 ) {
                B[i] = A[0];
                for(int j = 1; j < A.length - 1 ; j++ )
                    B[i] = B[i] * A[j];
            } else {
                B[i] = A[0];
                for(int j = 1; j < i ; j ++) {
                    B[i] = B[i] * A[j];
                }
                
                for(int j = i+1; j < A.length ; j ++) {
                    B[i] = B[i] * A[j];
                }
            }
            
            
        }
        
        
        return B;
    }

但是,很显然:按照上文中的方法进行计算,时间复杂度特别高:O(n^2)。观察B[]的规律,每次仅仅没有乘到B[i],可以想象为一个矩阵,对角线少了一块。由于不能用除法,所以,可以将该矩阵从对角线分开,左边一部分,右边一部分,先算左再算右。

public int[] multiply(int[] A) {
        int[] B = new int[A.length];
        int[] C = new int[A.length];
        int[] D = new int[A.length];
        
        for(int i = A.length - 1; i >= 0 ; i --){
            if( i == A.length - 1)
                C[i] = 1;
            else{
                C[i] = A[i+1] * C[i+1];
            }
        }
        
        for(int i = 0; i < A.length; i ++){
            if(i == 0)
                D[i] = 1;
            else{
                D[i] = D[i-1] * A[i-1];
            }
        }
        
        for(int i = 0; i < A.length; i ++){
            B[i] = C[i] * D[i];
        }
        

        return B;
    }

时间复杂度变为:O(3n)

JZ52 正则表达式匹配

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

解法一:利用String库函数直接ac

public boolean match(char[] str, char[] pattern){
        return new String(str).matches(new String(pattern));
    }

解法二:迭代每次比较,当然字符,然后再比较接下来的字符。Java中需要传递两个数s和p作为指针,指向当前对比的元素。

  1. 两个指针都指向末尾,匹配完成。
  2. str非空,但pattern已经为空了,匹配失败。
  3. 当前位置的下一个位置非空,且出现了'*',改变匹配规则。若本位置的元素为相同或'.'时,有两种情况1) 重复0次,那么下一次判断的指针位置应该是s, p+2,2)重复1次或多次,下一次的位置为s+1, p。若本位置为不同元素,则只有一种情况,重复0次,下一次判断的指针位置应该是s, p+2。则其返回值,满足二者其一即可。
  4. 当前位置的下一个位置非空,但并未出现'*',继续判断。
  5. 其余情况,匹配失败。
public boolean match(char[] str, char[] pattern){
        if (str == null || pattern == null)
            return false;
        return matchCore(str, 0, pattern, 0);
    }
    private boolean matchCore(char[] str, int s, char[] pattern, int p) {
        //下面4行是递归结束标志,两个指针都指到了最后,才是匹配,否则不匹配
        if (s == str.length && p == pattern.length)
            return true;
        if (s < str.length && p == pattern.length)
            return false;

        //虽然比的是P位置的,但是P后面出现*时,规则需要改变。
        if (p + 1 < pattern.length && pattern[p + 1] == '*') {
            //出现了*,并且s和P指向的相同,3种情况并列
            if ((s < str.length && pattern[p] == '.')
                    || (s < str.length && pattern[p] == str[s])) {
                return matchCore(str, s, pattern, p + 2)
                        || matchCore(str, s + 1, pattern, p);
//                        || matchCore(str, s + 1, pattern, p + 2);
            } else {//出现了*,并且s和p指向的不同,那就把*前面的字符理解出现了0次,p+2
                return matchCore(str, s, pattern, p + 2);
            }
        }
        //说明P后面不是*,那么就进行常规判断。相同就分别给指针+1
        if (s < str.length && (pattern[p] == str[s] || pattern[p] == '.'))
            return matchCore(str, s + 1, pattern, p + 1);
        //p后面又不是*,也没有.给你撑腰,你还敢出现不同,那必然false
        return false;
    }

JZ53 表示数值的字符串

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

正则表达式法。

import java.util.regex.Pattern;
 
public class Solution {
    public static boolean isNumeric(char[] str) {
        String pattern = "^[-+]?\\d*(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?$";
        String s = new String(str);
        return Pattern.matches(pattern,s);
    }
}

按条件判断法:在字符串中一共会出现以下几种字符:

  1. 正负号*/-:只出现在第一位或E/e后面
  2. 小数点.:只有一个,出现在E之前。
  3. 指数E/e:只有一个,出现后,后面跟的必须是整数(若前面没有小数点,占一个小数点位置)。
  4. 正常数字:char >= '0' && char <= '9'
public class Solution {
    public boolean isNumeric(char[] str) {
        boolean point = false, exp = false;
        for (int i = 0 ; i < str.length ; i ++ ) {
            if(str[i] == '+' || str[i] == '-') {
                if(i == 0 || str[i-1] == 'E' || str[i-1] == 'e') {
                    continue;
                } else
                    return false;
            }

            if( str[i] == '.' ){
                if( !point ) {
                    point = true;
                    continue;
                } else
                    return false;
            }

            if( (str[i] == 'E' || str[i] == 'e') ) {
                if( !exp && i != 0 && i != str.length - 1) {
                    exp = true;
                    point = true;
                    continue;
                } else
                    return false;
            }

            if ( str[i] >= '0' && str[i] <= '9' ) {
                continue;
            } else
                return false;

        }

        return true;
    }
}

JZ54 字符流中第一个不重复的字符

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

老方法,用hashmap来存,只是这里需要注意,输入的有顺序,所以这里采用了LinkedHashMap

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Set;

public class Solution {
    HashMap map = new LinkedHashMap();
    
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        if(map.containsKey(ch)) 
            map.put(ch, -1);
        else
            map.put(ch, 1);
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        Set keys = map.keySet();
        for(Object ch : keys ){
            if(map.get(ch) == 1)
                return (char) ch;
        }
        
        return '#';
    }
}

JZ55 链表中换的入口结点

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

拿到题目,直接hash。若表中存在,即该结点为环的入口,若不存在,则将该点加入hashmap中,并指向下一个点。

public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        HashMap list = new HashMap<>();
        while( pHead != null ) {
            if( list.containsKey(pHead) ) {
                return pHead;
            } else {
                list.put(pHead, 1);
                pHead = pHead.next;
            }
        }
        
        return null;
    }

JZ56 删除链表中的重复结点

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

比的是结点的val值,且重复的结点不保留。那么在删除过程中,势必有一个指针指向前一个结点。

首先遍历整个链表,找出其中的重复结点。

然后再遍历一次链表,此时需要注意不应该只用一个指针,而应该是三个。一个指向链表本身进行遍历。第二个指向新的头结点。第三个指向新链表的当前结点。

需要注意:如果遍历结束时,第三个结点并不会自动移动到末尾,需要自己使其结束。

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
import java.util.*;

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if(pHead == null) return pHead;
        ListNode p = pHead;
        HashMap list = new LinkedHashMap<>();
        
        while ( p!= null ) {
            if( list.containsKey(p.val) ) {
                list.put(p.val, -1);
            } else
                list.put(p.val, 1);
            p = p.next;
        }

        p = pHead;
        ListNode head = null;

        while (p != null) {
            if( list.get(p.val) == 1 ) {
                if( head == null ) {
                    head = p;
                    pHead = head;
                }
                else {
                    pHead.next = p;
                    pHead = pHead.next;
                }

            }
            p = p.next;
        }
        pHead.next = null;

        return head;
    }
}

JZ57 二叉树的下一个结点

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

直接模拟。先找出根节点。然后从根节点对整个数进行遍历。最后再遍历一次,输出下一个结点。

import java.util.ArrayList;
public class Solution {
    ArrayList list = new ArrayList<>();
    public TreeLinkNode GetNext(TreeLinkNode pNode)
    {
        TreeLinkNode temp = pNode;
        while( pNode.next != null ) {
            pNode = pNode.next;
        }
        
        TreeLinkNode root = pNode;
        Bian(root);
        
        for( int i = 0 ; i < list.size(); i++  ) {
            if ( list.get(i) == temp ) {
                return ( i == list.size() - 1 ) ? null : list.get(i + 1 );
            }
        }
        return null;
    }
    
    public void Bian( TreeLinkNode root ){
        if( root != null ) {
            Bian(root.left);
            list.add(root);
            Bian(root.right);
        }
    }
}

当然也可以直接输出中序遍历的下一个结点。主要有以下几种情况:

  1. 根节点,只考虑其右子树的情况,若存在右节点,则是右节点的最左子节点,若没有最左子节点的值,则是它本身。若没有右节点,则为null。
  2. 左节点,考虑其右子树的情况,若存在右节点,则是右节点的最左子节点,若没有最左子节点的值,则是它本身。若没有右节点,则为其父节点。
  3. 右节点,考虑其右子树的情况,若存在右节点,则是右节点的最左子节点,若没有最左子节点的值,则需要沿着父节点追溯,直到找到某个结点是其父结点的左子树,如果存在这样的结点,那么这个结点的父结点就是我们要找的下一结点。如果没有以上情况,则返回null。
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        if(pNode == null) return null;
        if(pNode.next == null ) {
            // root node
            if(pNode.right == null) {
                return null;
            } else {
                TreeLinkNode pRight = pNode.right;
                while (pRight.left != null ) {
                    pRight = pRight.left;
                }

                return pRight;
            }
        }

        if ( pNode.next.left == pNode ) {
            // left node
            if(pNode.right == null )
                return pNode.next;
            else {
                TreeLinkNode pRight = pNode.right;
                while (pRight.left != null ) {
                    pRight = pRight.left;
                }
                return pRight;
            }

        } else {
            // right node
            // 看有没有右节点,有的话就再看右节点有没有左节点,最左;右节点没有左节点的话就返回右节点。
            // 没有就返回null

            if( pNode.right != null ) {
                TreeLinkNode pRight = pNode.right;
                while (pRight.left != null ) {
                    pRight = pRight.left;
                }
                return pRight;
            } else {
                TreeLinkNode pRight = pNode;
                if(pRight.next == null)
                    return null;
                while (pRight.next.left != pRight ) {
                    pRight = pRight.next;
                    if (pRight.next == null) {
                        return null;
                    }
                }
                return pRight.next;
            }
        }
    }
}

上面的情况,总结的较差。采取别人的归纳:

  1. 有右子树,下一结点是右子树中的最左结点
  2. 无右子树,且结点是该结点父结点的左子树,则下一结点是该结点的父结点
  3. 无右子树,且结点是该结点父结点的右子树,则我们一直沿着父结点追朔,直到找到某个结点是其父结点的左子树,如果存在这样的结点,那么这个结点的父结点就是我们要找的下一结点。并没有符合情况的结点则没有下一结点。
public TreeLinkNode GetNext(TreeLinkNode pNode) {
     // 1.
     if (pNode.right != null) {
         TreeLinkNode pRight = pNode.right;
         while (pRight.left != null) {
             pRight = pRight.left;
         }
         return pRight;
     }
     // 2.
     if (pNode.next != null && pNode.next.left == pNode) {
         return pNode.next;
     }
     // 3.
     if (pNode.next != null) {
         TreeLinkNode pNext = pNode.next;
         while (pNext.next != null && pNext.next.right == pNext) {
             pNext = pNext.next;
         }
         return pNext.next;
     }
     return null;
 }

JZ58 对称的二叉树

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

直接从上到下进行迭代。注意:比较两个二叉树是否对称,首先应该比较其根节点的值是否一致,然后比较其左右子树是否相互对应即 root1.left && root2.right, root1.right && root2.left

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

    boolean isSame(TreeNode r1, TreeNode r2) {
        if (r1 != null && r2 != null) {
            if(r1.val == r2.val)
                return isSame(r1.left, r2.right) && isSame(r1.right, r2.left);
            else
                return false;
        }

        if( r1 == null && r2 == null )
            return true;
        return false;
    }

一个不太完全的算法,不能处理节点值相同的情况。对该树分别执行一次左右根以及右左根的遍历,得到的即为两组遍历的值,分别对比即可。

    boolean isSymmetrical(TreeNode pRoot)
    {
        if ( pRoot == null ) return true;
        if( pRoot.left == null && pRoot.right == null )
            return true;
        if( (pRoot.left == null) ^ (pRoot.right == null) )
            return false;

        TreeNode head = pRoot;
        first(pRoot);
        last(head);

        for(int i = 0 ; i < firstList.size(); i++) {
            if( firstList.get(i) == lastList.get(i))
                continue;
            else
                return false;
        }
        return true;
    }


    void first(TreeNode root) {
        if(root.left != null )
            first(root.left);
        if(root.right != null )
            first(root.right);
        if(root != null)
            firstList.add(root.val);
    }

    void last(TreeNode root) {
        if(root.right != null )
            last(root.right);
        if(root.left != null )
            last(root.left);
        if(root != null)
            lastList.add(root.val);
    }

JZ59 按之字型顺序打印二叉树

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

首先按照中序遍历,对树进行遍历,这里需要注意,需要在每层的结果为ArrayList,之后将该层的结果放到ArrayList>中。最后遍历一次ArrayList>,并将其中的某些层按需要翻转。这里采用的翻转方法是出栈入栈。

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/

import java.util.*;
public class Solution {
    ArrayList> lists = new ArrayList>();
    public int Deepth(TreeNode root) {
        int deepth = 0;
        if( root == null )
            return 0;
        if (root != null ) {
            deepth ++;
            int left = 0 ;
            int right = 0;
            if (root.left != null )
                left = Deepth(root.left);
            if (root.right != null)
                right = Deepth(root.right);
            return Math.max(left, right) + deepth;
        }
        return deepth;
    }

    public ArrayList> Print(TreeNode pRoot) {
        ArrayList> res = new ArrayList>();
        if(pRoot == null)
            return lists;
        int deepth = Deepth(pRoot);
        for(int i = 0 ; i < deepth; i++) {
            ArrayList list = new ArrayList<>();
            lists.add(list);
        }
        Bianli(pRoot, 0);
        for(int i = 0 ; i < lists.size() ; i++ ) {
            if(i % 2 == 0 ) {
                res.add(lists.get(i));
            } else {
                res.add(reverse(lists.get(i)));
            }
        }
        return res;
    }

    public void Bianli(TreeNode pRoot, int deepth) {
        if(pRoot != null )
            lists.get(deepth).add(pRoot.val);
        if(pRoot.left != null) 
            Bianli(pRoot.left, deepth + 1);
        if(pRoot.right != null) 
            Bianli(pRoot.right, deepth + 1);
    }

    public ArrayList reverse(ArrayList list) {
        ArrayList newList = new ArrayList();
        Stack stack = new Stack<>();
        for(int i = 0 ; i < list.size() ; i++) 
            stack.push(list.get(i));
        for(int i = 0 ; i < list.size() ; i++) 
            newList.add(stack.pop());
        return newList;
    }

}

打印每层的数据可以看做是广度优先的遍历,即可以通过队列来实现。翻转可以倒着插入,也可以使用Collections.reverse()方法。

import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public ArrayList > Print(TreeNode pRoot) {
         ArrayList > res = new ArrayList >() ;
        if( pRoot == null ) return res;
        
        LinkedList queue = new LinkedList();
        boolean rev = true;
        
        queue.add(pRoot);
        
        while( !queue.isEmpty() ) {
            int size = queue.size();
            ArrayList list = new ArrayList();
            for(int i = 0 ; i < size ; i++ ) {
                TreeNode node = queue.poll();
                if(node == null)
                    continue;
                if(rev)
                    list.add(node.val);
                else
                    list.add(0, node.val);
                queue.offer(node.left);
                queue.offer(node.right);
            }
            if(list.size() != 0 ) 
                res.add(list);
            rev = !rev;
        }
        return res;
    }

}

JZ60 把二叉树打印成多行

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

和上一个一样,没什么好说的。甚至更简单,不需要翻转。

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

}

JZ61 序列化二叉树

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

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

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

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

直接按照前序遍历或中序遍历去访问二叉树的所有节点,然后对于不同的情况返回不同的值。而反序列化,利用String.split("!")方法,将序列化后的字符串分割开,得到一个个小的字符串。按情况生成对应的节点,或空。

String Serialize(TreeNode root) {
        return SerializeHelper(root);
    }
    
    String SerializeHelper(TreeNode root) {
        String str = new String();
        if(root == null) return str;
        str += root.val + "!";
        if(root.left != null)
            str += SerializeHelper(root.left);
        else
            str += "#!";
        if(root.right != null)
            str += SerializeHelper(root.right);
        else
            str += "#!";
        return str;
    }
    
    
    
    TreeNode Deserialize(String str) {
        if(str.length() == 0 || str == null) return null;
        String[] split = str.split("!");
        return Deserialize2(split);
    }
    
    private int index = 0;
    TreeNode Deserialize2(String[] strs) {
        if(strs[index].equals("#")) {
            index++;
            return null;
        } else {
            
            TreeNode node = new TreeNode(Integer.parseInt(strs[index]));
            index++;
            node.left = Deserialize2(strs);
            node.right = Deserialize2(strs);
            return node;
        }
    }

如果是为了取巧通过这道题目,可以将Node直接保存为全局变量。

JZ62 二叉搜索树的第k个结点

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

对该二叉搜索树实现中序遍历,那么遍历后得到的 ArrayList 中,第k-1个位置的数,即为所求的节点。但是此时需要注意,边界情况,即k<=0以及k>list.size()

TreeNode KthNode(TreeNode pRoot, int k)
    {
        if(pRoot == null) return null;
        
        Traverse(pRoot);
        if( k <= 0 ) return null;
        if( k > list.size() )
            return null;
        return list.get(k-1);
    }

    ArrayList list = new ArrayList<>();
    public void Traverse(TreeNode root) {
        if(root.left != null)
            Traverse(root.left);
        if(root != null )
            list.add(root);
        if(root.right != null)
            Traverse(root.right);
    }

JZ63 数据流中的中位数

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

先插入数据流,然后排序,然后再取得中位数。

    ArrayList nums = new ArrayList<>();
    public void Insert(Integer num) {
        nums.add(num);
        nums.sort(new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        });
    }

    public Double GetMedian() {
        int size = nums.size();

        if (size == 0 ) {
            return 0.00;
        } else if ( size % 2 == 1 ) {
            return nums.get(size / 2) * 1.00;
        } else {
            return (nums.get( size / 2) + nums.get( size / 2 - 1 ) )/2.00;
        }
    }

JZ64 滑动窗口的最大值

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

首先考虑边界情况。在size过大,或者过小时直接return一个空的list。另外就需要注意滑动窗口的大小,与滑动窗口起始点、终结点的关系。

public ArrayList maxInWindows(int [] num, int size)
    {
        ArrayList list = new ArrayList<>();
        if(size > num.length)
            return list;
        
        if(size == num.length) {
            int max = num[0];
            for(int i = 1; i < size; i++){
                max = num[i] > max ? num[i] : max;
            }
            list.add(max);
            return list;
        }
        
        if(size <= 0 )
            return list;
            
        for( int i = 0 ; i <= num.length - size ; i++ ){
            int max = num[i];
            for(int j = i + 1 ; j < i + size ; j ++ ) {
                max = num[j] > max ? num[j] : max;
            }
            list.add(max);
        }
        return list;
        
    }

JZ65 矩阵中的路径

注意观察给出的参数,可以发现,matrix和str均为一维数组。也就是说,rows和cols规定了矩阵的行列,所以为了使得能够在矩阵中上下左右移动,要么将二维坐标转化为一维,要么就把一维的转化为二维。

同时,也是一个很明显的递归。

当,i、j超界,位置上的字符不同,字符已经被用过时,不满足条件,返回false。满足当前位置相等时,进入下一次递归。进入下一次递归时,需要注意,该字符已经用过一次,需要将符号位置为true。如果没用,则需要将该值再次置为false,留待下一次匹配。

public class Solution {
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
        boolean[] flags = new boolean[matrix.length];
        for( int i = 0 ; i = rows || j < 0 || j >= cols || matrix[index] != str[pos] || flags[index] )
            return false;
        if( pos == str.length - 1 ) return true;
        flags[index] = true;
        if( helper(matrix, rows, cols, i + 1, j , str, pos + 1, flags) 
           || helper(matrix, rows, cols, i , j + 1, str, pos + 1, flags) 
           || helper(matrix, rows, cols, i - 1, j , str, pos + 1, flags)
           || helper(matrix, rows, cols, i , j - 1, str, pos + 1, flags))
            return true;
        
        flags[index] = false;
        return false;
        
    }

}

JZ66 机器人的运动范围

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

首先将问题拆解成几部分。

  1. 这个格子能不能走
  2. 下一个格子能不能走
  3. 求各位数之和

那么就从0, 0开始出发,进行动态规划。

依次判断以下问题即可:

  1. 这步在范围中吗?
  2. 这步走过吗?
  3. 这不能走吗?
  4. 下一步能走吗?
public class Solution {
    int counts = 0;
    public int movingCount(int threshold, int rows, int cols)
    {
        boolean[] flags = new boolean[rows*cols];
        if( rows == 0 || cols == 0) return 0;
        move(threshold, rows, cols, 0, 0, flags);
        return counts;
    }

    public boolean move(int threshold, int rows, int cols, int i, int j, boolean[] flags){
        int index = cols * i + j;

        // 是否越界
        if( i < 0 || i >= rows || j < 0 || j >= cols ) {
            return false;
        }

        // 判断这步走过吗?
        if(flags[index])
            return false;

        // 判断当前这步能走吗?
        if(count(i) + count(j) <= threshold) {
            if(flags[index] == false){
                flags[index] = true;
                counts++;
            }

            // 判断下一步能走吗?
            if( move(threshold, rows, cols, i-1, j , flags)
                    || move(threshold, rows, cols, i+1, j , flags)
                    || move(threshold, rows, cols, i, j-1 , flags)
                    || move(threshold, rows, cols, i, j+1 , flags)){
                return true;
            } else
                return false;
        } else {
            return false;
        }

    }

    public int count(int i ) {
        int sum = 0;
        while ( i != 0 ){
            sum += i % 10;
            i /= 10;
        }

        return sum;
    }
}

JZ67 剪绳子

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

根据数学来讲,一段绳子围成的矩形,只有当围成正方形的时候面积最大。那么延伸到这个问题上,只有当剪出来的长度相等时,才有可能得到最大的值。则有f(x)为乘积最大,x为单段绳子的长度,即f(x) = x^(n/x),要使得f(x)最大,对其求导,当一阶导数为零时,可以判断。在此式中,当x = e取得最大值。但x为整数,故x=3

那么问题,就转为到到底有多少个3相乘上。

  1. 能被3整除,(n/3)
  2. 余1,从前面的(n/3)个3中,拿一个3出来,与1组成2*2
  3. 余2,直接乘。
public class Solution {
    public int cutRope(int target) {
        if( target < 5 )
            return target;
            
        if( target % 3 == 0 ) {
            return (int) Math.pow(3, target / 3);
        } else if( target % 3 == 1 ) {
            return (int) Math.pow(3, target / 3 - 1) * 4;
        } else
            return (int) Math.pow(3, target / 3 ) * 2;
    }
}

暴力解法:

直接模拟剪绳子的过程。

public int cutRope(int target) {
        if(target == 2) {
            return 1;
        }
        else if(target == 3) {
            return 2;
        }
        return bak(target);
    }

    public int bak(int n) {
        if( n < 5 )
            return n;
        int max = 0;
        for(int i = 1 ; i < n ; ++i ){
            max = i * bak(n - i ) > max ? i * bak(n - i ) : max;
        }
        return max;
    }

你可能感兴趣的:(java,算法)