剑指Offer第一章——整数

1. 整数

1.1 整数的基础知识

整数是一种基本的数据类型。编程语言可能会提供占据不同内存 空间的整数类型,每种类型能表示的整数的范围也不相同。例如,Java中有4种不同的整数类型,分别为8位的byte(-2^{7}2^{7}-1)、16位 的short(-2^{15}2^{15}-1)、32位的int(-2^{31}2^{31}-1)和64位的long(-2^{63}2^{63}-1)。

面试题1:整数除法

题目:输入2个int型整数,它们进行除法计算并返回商,要求 不得使用乘号'*'、除号'/'及求余符号'%'。当发生溢出时,返回最 大的整数值。假设除数不为0。例如,输入15和2,输出15/2的结 果,即7。

public class Offer_001_整数除法 {
    public static void main(String[] args) {
        System.out.println(divide(15, 7));
    }

    public static int divide(int dividend, int divisor) {
        // 当发生溢出时,返回最大的整数值
        if (dividend == Integer.MIN_VALUE && divisor == -1)
            return Integer.MAX_VALUE;

        // 将减数和被减数都转化成负数,因为负数的取值范围更大
        int negative = 2;
        if (dividend > 0) {
            negative--;
            dividend = -dividend;
        }
        if (divisor > 0) {
            negative--;
            divisor = -divisor;
        }

        // 计算结果并根据正负返回
        int result = divideCore(dividend, divisor);
        return negative == 1 ? -result : result;
    }

    public static int divideCore(int dividend, int divisor) {
        int result = 0;
        // 如果被减数 小于 减数一直循环,类似于快速幂
        // 这里要注意是小于,因为负数的小于就相当于正数的大于了
        while (dividend <= divisor) {
            int temp = divisor;
            int quotient = 1;
            // 防止溢出
            while (temp + temp >= Integer.MIN_VALUE && dividend <= temp + temp) {
                // 翻倍。
                temp += temp;
                quotient += quotient;
            }

            result += quotient;
            dividend -= temp;
        }

        return result;
    }
}

1.2 二进制

与、或和异或的运算规律如表1.1所示。

面试题2:二进制加法

题目:输入两个表示二进制的字符串,请计算它们的和,并以 二进制字符串的形式输出。例如,输入的二进制字符串分别
是"11"和"10",则输出"101"。

public class Offer_002_二进制加法 {
    public static void main(String[] args) {
        System.out.println(addBinary("101", "1"));
    }

    public static String addBinary(String a, String b) {
        StringBuilder stringBuilder = new StringBuilder();
        // 获取两个二进制字符串的长度,减一为下标
        int i = a.length() - 1;
        int j = b.length() - 1;
        // 存储进位
        int carry = 0;
        while (i >= 0 || j >= 0) {
            // 都从字符串末尾开始遍历
            int digitA = i >= 0 ? a.charAt(i--) - '0' : 0;
            int digitB = j >= 0 ? b.charAt(j--) - '0' : 0;
            // 计算该位的二进制之和
            int sum = digitA + digitB + carry;
            carry = sum >= 2 ? 1 : 0;
            // 如果进位则和 -2
            sum = sum >= 2 ? sum - 2 : sum;
            stringBuilder.append(sum);
        }
        // 判断最后是否有进位
        if (carry == 1)
            stringBuilder.append(carry);

        // 因为是从低位开始存的 所以返回是先反转
        return stringBuilder.reverse().toString();
    }
}

面试题3:前n个数字二进制形式中1的个数 

题目:输入一个非负数n,请计算0到n之间每个数字的二进制形 式中1的个数,并输出一个数组。例如,输入的n为4,由于0、1、2、3、4的二进制形式中1的个数分别为0、1、1、2、1,因此输出数 组[0,1,1,2,1]。 

public class Offer_003_前n个数字二进制形式中1的个数 {
    public static void main(String[] args) {
        System.out.println(Arrays.toString(countBits(11)));
    }

    public static int[] countBits(int n) {
        int[] result = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            // result[i] 为数字 i 有几个 ‘1’
            // 而result[i & (i - 1)] 比 result[i] 少 1 个
            result[i] = result[i & (i - 1)] + 1;
        }

        return result;
    }
}

 关于 i 与 i & (i-1):

        假设 i = 10,i 的二进制为 "1010",i - 1 的二进制为 "1001",故 i & (i - 1) 为 "1000",比 i 的二进制少了一个数字 '1'。


 面试题4:只出现一次的数字

题目:输入一个整数数组,数组中只有一个数字出现了一次,而其他数字都出现了3次。请找出那个只出现一次的数字。例如,如 果输入的数组为[0,1,0,1,0,1,100],则只出现一次的数字是 100。

分析:这个题目有一个简化版的类似的题目“输入数组中除一个 数字只出现一次之外其他数字都出现两次,请找出只出现一次的数 字”。任何一个数字异或它自己的结果都是0。如果将数组中所有数字 进行异或运算,那么最终的结果就是那个只出现一次的数字。

public class Offer_004_只出现一次的数字 {
    public static void main(String[] args) {
        System.out.println(singleNumber(new int[]{1, 0, 0, 1, 1, 0, 100}));
    }

    public static int singleNumber(int[] nums) {
        // int数值有32位,记录所有数字每一位之和
        int[] bitSums = new int[32];
        for (int num : nums) {
            for (int i = 0; i < 32; i++) {
                // 通过移位运算之后,与 1 进行 & 运算,只添加那一位上面的数值
                bitSums[i] += (num >> (31 - i)) & 1;
            }
        }

        // 计算结果,将每一位上面的数值之和存放至result变量中,并通过%3来处理出现过三次的数字,算得的结果就是只出现了一次的数字
        int result = 0;
        for (int i = 0; i < 32; i++) {
            result = (result << 1) + bitSums[i] % 3;
        }
        return result;
    }
}

举一反三

            题目:输入一个整数数组,数组中只有一个数字出现m次,其他数 字都出现n次。请找出那个唯一出现m次的数字。假设m不能被n整除。

分析:解决面试题4的方法可以用来解决同类型的问题。如果数组 中所有数字的第i个数位相加之和能被n整除,那么出现m次的数字的第 i个数位一定是0;否则出现m次的数字的第i个数位一定是1。


面试题5:单词长度的最大乘积

题目:输入一个字符串数组words,请计算不包含相同字符的两 个字符串words[i]和words[j]的长度乘积的最大值。如果所有字符 串都包含至少一个相同字符,那么返回0。假设字符串中只包含英文 小写字母。例如,输入的字符串数组words为
["abcw","foo","bar","fxyz","abcdef"],数组中的字符 串"bar"与"foo"没有相同的字符,它们长度的乘积为9。"abcw"与"fxyz"也没有相同的字符,它们长度的乘积为16,这是 该数组不包含相同字符的一对字符串的长度乘积的最大值。

分析:解决这个问题的关键在于如何判断两个字符串str1和str2 中没有相同的字符。一个直观的想法是基于字符串str1中的每个字符 ch,扫描字符串str2判断字符ch是否出现在str2中。如果两个字符串 的长度分别为pq,那么这种蛮力法的时间复杂度是Opq)。

优化:

用哈希表记录字符串中出现的字符

        也可以用哈希表来优化时间效率。对于每个字符串,可以用一个 哈希表记录出现在该字符串中的所有字符。在判断两个字符串str1和 str2中是否有相同的字符时,只需要从'a'到'z'判断某个字符是否在 两个字符串对应的哈希表中都出现了。在哈希表中查找的时间复杂度 是O(1)。

        这个题目假设所有字符都是英文小写字母,只有26个可能 的字符,因此最多只需要在每个字符串对应的哈希表中查询26次就能 判断两个字符串是否包含相同的字符。26是一个常数,因此可以认为 应用哈希表后判断两个字符串是否包含相同的字符的时间复杂度是 O(1)。

但是使用哈希表用来判断一个字符串中是否出现某个字符需要判断26次,还可以优化成一次。

用整数的二进制数位记录字符串中出现的字符

        前面的解法是用一个长度为26的布尔型数组记录字符串中出现的 字符。布尔值只有两种可能,即true或false。这与二进制有些类似,在二进制中数字的每个数位要么是0要么是1。因此,可以将长度为26 的布尔型数组用26个二进制的数位代替,二进制的0对应布尔值 false,而1对应true。

        Java中int型整数的二进制形式有32位,但只需要26位就能表示一 个字符串中出现的字符,因此可以用一个int型整数记录某个字符串中 出现的字符。如果字符串中包含'a',那么整数最右边的数位为1;如 果字符串中包含'b',那么整数的倒数第2位为1,其余以此类推。这样 做的好处是能更快地判断两个字符串是否包含相同的字符。如果两个 字符串中包含相同的字符,那么它们对应的整数相同的某个数位都为 1,两个整数的与运算将不会等于0。如果两个字符串没有相同的字 符,那么它们对应的整数的与运算的结果等于0。

代码如下👇:

public class Offer_005_单词长度的最大乘积 {
    public static void main(String[] args) {
        System.out.println(maxProduct(new String[]{"qwe", "dhdgf"}));
    }


    public static int maxProduct(String[] words) {
        // 只用一个int值来表示一个String中是否含有某个字符
        int[] flags = new int[words.length];
        for (int i = 0; i < words.length; i++) {
            for (char ch : words[i].toCharArray()) {
                // 使用 '|' 运算符来将它这个String中的字符存入int中。其中第一位如果是1,代表字符串含有'a',以此类推
                flags[i] |= 1 << (ch - 'a');
            }
        }

        int max = 0;
        for (int i = 0; i < words.length; i++) {
            for (int u = i + 1; u < words.length; u++) {
                // 遍历每一个字符串如果 & 运算的值为0,则说明没有含有一个同样的字符
                if ((flags[i] & flags[u]) == 0)
                    max = Math.max(max, words[i].length() * words[u].length());
            }
        }

        return max;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值