1. 整数
1.1 整数的基础知识
整数是一种基本的数据类型。编程语言可能会提供占据不同内存 空间的整数类型,每种类型能表示的整数的范围也不相同。例如,Java中有4种不同的整数类型,分别为8位的byte(-~-1)、16位 的short(-~-1)、32位的int(-~-1)和64位的long(-~-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 二进制
面试题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中。如果两个字符串 的长度分别为p和q,那么这种蛮力法的时间复杂度是O(pq)。
优化:
❖ 用哈希表记录字符串中出现的字符
也可以用哈希表来优化时间效率。对于每个字符串,可以用一个 哈希表记录出现在该字符串中的所有字符。在判断两个字符串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;
}
}