面试题64.求1+2+…+n
本题使用暴力法和迭代法都必须借助乘除或者条件判断语句,所以这两种方法都不可取。那如果使用递归,有没有除了 if 等判断语句之外的其他方法来终止递归?
- Java 中,为构成语句,需加一个辅助布尔量 x ,否则会报错;
- Java 中,开启递归函数需改写为 sumNums(n - 1) > 0 ,此整体作为一个布尔量输出,否则会报错;
class Solution {
int res = 0;
public int sumNums(int n) {
boolean x = n > 1 && sumNums(n - 1) > 0;
res += n;
return res;
}
}
- 时间复杂度 O(n) : 计算 n+(n−1)+…+2+1 需要开启 n 个递归函数。
- 空间复杂度 O(n) : 递归深度达到 n ,系统使用 O(n) 大小的额外空间。
———————————————————————————————————————
面试题65.不用加减乘除做加法
如果是十进制,我们是如何完成加法计算的?
- 99 + 111 = ?
1.个、十、百位的数字分别相加先不管进位的问题
个位:9 + 1 = 0
十位:9 + 1 = 0
百位:0 + 1 = 1
得到临时结果:100
2.计算进位的数字
1 + 9 = 10
10 + 90 = 100
得到进位结果:10 + 100 = 110
3.相加得结果
100 + 110 = 210
如何用二进制来实现加法?
- 15 + 12 = ?
12 二进制:1100
15 二进制:1111
1.各位置上的数字分别相加先不管进位的问题:
1100 + 1111 = 0011
得到临时二进制结果:0011
2.计算进位的数字
0100 + 0100 = 1000
1000 + 1000 = 10000
得到进位结果:11000
3.相加得到结果
0011 + 11000 = 11011(十进制:27)
第一步骤不用加法如何得到相同结果?
异或:相同为0,不同为1
1100 ^ 1111 = 0011
第二步骤不用加法如何得到此结果?
如果同一个位置上的数字相与能得到1,则说明这两个数在这一位置上都是1,则相加后就会产生进位,相与后往左移一位,即得到进位
(1100 & 1111) << 1 = 11000
第三步骤不用加法如何得到结果?
第三步是前两步的和,还是个加法;如果不用加法,就只能不断调用前两步的步骤。
从上图可以看出,这个就是在不断重复第一,第二的步骤,那么退出条件是什么?
如果进位为0,则这个循环就会一直进行下去,永远不会停,所以终止条件就是进位=0
class Solution {
public int add(int a, int b) {
while(b != 0) {
int temp = (a & b) << 1; //计算a+b的进位
a = (a ^ b); //计算非进位和
b = temp;
}
return a;
}
}
- 时间复杂度 O(1): 最差情况下(例如 a= 0x7fffffff , b=1 时),需循环 32 次,使用 O(1) 时间;每轮中的常数次位操作使用 O(1) 时间。
- 空间复杂度 O(1): 使用常数大小的额外空间。
———————————————————————————————————————
面试题66.构建乘积数组
用两个数组,一个数组维护数的左侧的乘积和,一个数组维护数的右侧的乘积和,最后再将这两个数组上的数分别相乘,得到最后的结果
class Solution {
public int[] constructArr(int[] a) {
if(a == null || a.length < 1) return new int[0];
int len = a.length;
int[] left = new int[len];
int[] right = new int[len];
left[0] = 1;
right[len - 1] = 1;
//求左侧乘积和
for(int i = 1; i < len; i++) {
left[i] = left[i - 1] * a[i - 1];
}
//求右侧乘积和
for(int i = len - 2; i >= 0; i--) {
right[i] = right[i + 1] * a[i + 1];
}
int[] res = new int[len];
for(int i = 0; i < len; i++) {
res[i] = left[i] * right[i];
}
return res;
}
}
优化
class Solution {
public int[] constructArr(int[] a) {
if(a.length == 0) return new int[0];
int[] b = new int[a.length];
b[0] = 1;
int tmp = 1;
for(int i = 1; i < a.length; i++) {
b[i] = b[i - 1] * a[i - 1];
}
for(int i = a.length - 2; i >= 0; i--) {
tmp *= a[i + 1];
b[i] *= tmp;
}
return b;
}
}
- 时间复杂度 O(N) : 其中 N 为数组长度,两轮遍历数组 a ,使用 O(N) 时间。
- 空间复杂度 O(1) : 变量 tmp 使用常数大小额外空间(数组 b 作为返回值,不计入复杂度考虑)。
———————————————————————————————————————
面试题67.把字符串转换成整数
有以下四种字符需要考虑:
- 首部空格:删除之即可
- 符号位:三种情况,即 ‘’+’’ , ‘‘−’’ , ''无符号" ;新建一个变量保存符号位,返回前判断正负即可。
- 非数字字符:遇到首个非数字的字符时,应立即返回。
- 数字字符:转换并拼接
数字越界处理:
在每轮数字拼接前,判断 res 在此轮拼接后是否超过 2147483647,若超过则加上符号位直接返回。
设数字拼接边界 bndry = 2147483647 / 10 = 214748364 ,则以下两种情况越界:
class Solution {
public int strToInt(String str) {
char[] c = str.trim().toCharArray();
if(c.length == 0) return 0;
int res = 0, bndry = Integer.MAX_VALUE / 10;
int i = 1;
int sign = 1; //符号位
if(c[0] == '-') sign = -1;
else if(c[0] != '+') i = 0;
for(int j = i; j < c.length; j++) {
if(c[j] < '0' || c[j] > '9') break;
if(res > bndry || res == bndry && c[j] > '7')
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
res = res * 10 + (c[j] - '0');
}
return sign * res;
}
}
- 时间复杂度 O(N) : 其中 N 为字符串长度,线性遍历字符串占用 O(N) 时间。
空间复杂度 O(N) : 删除首尾空格后需建立新字符串,最差情况下占用 O(N) 额外空间。
若不使用 trim() / strip() 方法,而从头开始遍历字符串,则可以将空间复杂度降低至 O(1)
class Solution {
public int strToInt(String str) {
int res = 0, bndry = Integer.MAX_VALUE / 10;
int i = 0, sign = 1, length = str.length();
if(length == 0) return 0;
while(str.charAt(i) == ' ')
if(++i == length) return 0; //超出长度
if(str.charAt(i) == '-') sign = -1;
//如果有符号位,则往后跳过这个符号位
if(str.charAt(i) == '-' || str.charAt(i) == '+') i++;
for(int j = i; j < length; j++) {
if(str.charAt(j) < '0' || str.charAt(j) > '9') break;
if(res > bndry || res == bndry && str.charAt(j) > '7')
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
res = res * 10 + (str.charAt(j) - '0');
}
return sign * res;
}
}