剑指offer之每日6题 ----- 第七天

原题链接

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

题目描述

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

思路

题目中是在一个排序数组中,可以利用二分查找去找到第一次出现数字的位置和最后一次出现数字的位置,这样就可以得到该数字出现的次数了

 public int getNumberOfK(int[] array, int k) {
        //找下界
        int pre, last, l, r;
        for (l = 0, r = array.length; l < r; ) {
            int mid = (l + r) >> 1; //向下取整
            if (array[mid] < k) {
                l = mid + 1;
            } else {
                r = mid;
            }
        }
        //求上界,注意开闭区间
        pre = r;
        for (l = -1, r = array.length - 1; l < r; ) {
            int mid = (l + r + 1) >> 1;
            if (array[mid] <= k) {
                l = mid;
            } else {
                r = mid - 1;
            }
        }
        last = l;
        return last - pre + 1;
    }

2.二叉树的深度

题目描述

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

思路

递归,只要一个节点的时候,深度为1,取左右子树深度的较大值+1

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

3.平衡二叉树

题目描述

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

思路

平衡二叉树的定义是左右子树的深度必须小于等于1,上题已经实现了求二叉树的深度,这个也直接递归

 public boolean isBalanced_Solution(TreeNode root) {
        if (root == null)
            return true;
        int left = getTreeDepth(root.left);
        int right = getTreeDepth(root.right);
        if (Math.abs(left - right) <= 1)
            return true;
        return false;
    }

上面这种递归,会重复遍历一个节点,我们可以先判断左右子树是否是平衡二叉树,然后再判断根节点是否是平衡二叉树,并且每次把节点的深度保存下来,这样就不会重复遍历节点了。也就是后序遍历,自底向上遍历

 public boolean isBalance = true;
    public boolean isBalanced_Solution(TreeNode root) {
        getDepth(root);
        return isBalance;
    }
    public int getDepth(TreeNode root) {
        if (root == null)
            return 0;
        int left = getDepth(root.left);
        int right = getDepth(root.right);
        if (Math.abs(left - right) > 1) {
            isBalance = false;
        }
        return right > left ? right + 1:left + 1;
    }

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

题目描述

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

思路

利用两个相同的数异或结果为0的特性来做,这个题目改成一整型数组只有一个数字只出现一次,那么直接将所有数字进行异或运算,最后结果就是这个数字。异或的特性:

  • 两个相同的数字异或结果为0
  • 0异或任一数字,结果都为原数字

如果这个数组分成两个子数组,满足:

  • 相同的数字存在在同一个子数组中
  • 不同的数字存在不同的子数组中

那么这两个子数组分别进行异或运算,就可以分别得到这两个数字。那么怎么样才能够分组呢,以什么条件来分组呢。可以将所有数字进行异或,这样得到的结果就是这两个数字的异或和,假如结果是0010,那么就以这个倒数第二位是否为1,来进行分组。因为相同的数字,每个位置的数都是相同的,而这个位置上是都为1,不同的数字这个位置上的数字肯定是不同的。

 //num1,num2分别为长度为1的数组。传出参数
    //将num1[0],num2[0]设置为返回结果
    public void findNumsAppearOnce(int[] array, int num1[], int num2[]) {
        if (array == null || array.length <= 1) {
            num1[0] = num2[0] = 0;
            return;
        }
        int len = array.length;
        int index = 0;
        int sum = 0;
        //将所有值进行异或运算
        for (int i = 0; i < len; i++) {
            sum ^= array[i];
        }
        //找到第一个为1的位置
        for (index = 0; index < 32; index++) {
            if ((sum & (1 << index)) != 0)
                break;
        }
        for (int i = 0; i < len; i++) {
            if ((array[i] & (1 << index)) != 0) {
                num2[0] ^= array[i];
            } else {
                num1[0] ^= array[i];
            }
        }
    }
扩展

数组a中只有一个数出现一次,其他数都出现了2次,找出这个数字

  public static int find1From2(int[] array) {
        int res = 0;

        for (int i = 0; i < array.length; i++) {
            res ^= array[i];
        }
        return res;
    }

数组a中只有一个数出现一次,其他数字都出现了3次,

找出这个数字这里就不能使用异或了,因为异或3次之后,结果还是原数字,但可以用位运算 将所有数字的二进制表示的每一位都加起来,如果某一位的和能被3整数,那么只出现一次的数字二进制表示中 对应的那一位就是0,否则就是1

  public static int find1From3(int[] array) {
        int[] bits = new int[32];
        int len = array.length;
        for (int i = 0; i < len; i++) {
            int bitmask = 1;
            for (int j = 31; j >= 0; j--) {
                int bit = array[i] & bitmask;
                if (bit != 0)
                    bits[j] += 1;
                bitmask = bitmask << 1;
            }
        }
        int result = 0;
        for (int i = 0; i < 32; i++) {
            result = result << 1;
            result += bits[i] % 3;
        }
        return result;
    }

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

题目描述

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

思路

考虑用两个数small和big分别表示序列的最小值和最大值,分别初始化1和2,如果从small到big的序列和大于S,则去掉序列中的最小值,继续判断,如果小于S,则增大big,继续判断,一直增加small到(1+s)/2

   public ArrayList<ArrayList<Integer>> findContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        int small = 1; int big = 2;
        while (big > small) {
            //由于是连续的,差为1的一个序列,那么求和公式是(a0+an)*n/2
            int curSum = (small + big) * (big - small + 1) / 2;
            //相等
            if (curSum == sum) {
                ArrayList<Integer> arrayList = new ArrayList<>();
                for (int i = small; i <= big; i++) {
                    arrayList.add(i);
                }
                result.add(arrayList);
                small++;
            } else if (curSum < sum) {//如果当前和小于sum,则需要增大big
                big++;
            } else { 
                small++;
            }
        }
        return result;
    }

6.和为S的两个数字

题目描述

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

思路

设置头尾指针,当头尾指针指向的数字和大于S的时候,因为数组是递增的,所以可以让尾指针向前移动,如果和小于S的话,就让头指针向前移动

 public ArrayList<Integer> findNumbersWithSum(int[] array, int sum) {
        ArrayList<Integer> result = new ArrayList<>();
        int low = 0;
        int high = array.length - 1;
        while (high > low) {
            int curSum = array[low] + array[high];
            if (curSum == sum) {
                result.add(array[low]);
                result.add(array[high]);
                break;
            } else if (curSum < sum) {
                low++;
            } else {
                high--;
            }
        }
        return result;
    }

猜你喜欢

转载自blog.csdn.net/YuQing_Cat/article/details/86489274