目录
*汉明距离
1 题目描述
两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数 x 和 y,计算并返回它们之间的汉明距离。
示例 1:
输入:x = 1, y = 4
输出:2
解释:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑
上面的箭头指出了对应二进制位不同的位置。
示例 2:
输入:x = 3, y = 1
输出:1
提示:
- 0 <= x, y <= 231 - 1
2 解题(Java)
2.1 方法一:异或 + 无符号右移
class Solution {
public int hammingDistance(int x, int y) {
int res = 0, z = x ^ y;
while (z != 0) {
res += z & 1;
z >>>= 1;
}
return res;
}
}
2.2 方法二:z = z & (z−1)
记 f(x)表示 x 和 x−1 进行与运算所得的结果(即f(x)=x & (x−1)),那么 f(x) 恰为 x 删去其二进制表示中最右侧的 1 的结果。
代码
class Solution {
public int hammingDistance(int x, int y) {
int res = 0, z = x ^ y;
while (z != 0) {
z &= z - 1;
res++;
}
return res;
}
}
3 复杂性分析
- 时间复杂度:O(log C),其中 C 是元素的数据范围,在本题中 logC=log231 =31;
- 空间复杂度:O(1);
*比特位计数
1 题目描述
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
示例 1:
输入: 2
输出: [0,1,1]
示例 2:
输入: 5
输出: [0,1,1,2,1,2]
进阶:
- 给出时间复杂度为O(n*sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗?
- 要求算法的空间复杂度为O(n)。
- 你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的__builtin_popcount)来执行此操作。
2 解题(Java)
class Solution {
public int[] countBits(int n) {
int[] res = new int[n+1];
for (int i=1; i<=n; i++) {
if (i % 2 == 0) res[i] = res[i / 2];
else res[i] = res[i - 1] + 1;
}
return res;
}
}
3 复杂性分析
- 时间复杂度:O(n);
- 空间复杂度:O(n);
*只出现一次的数字
1 题目描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
2 解题(Java)
解题思路
使用位运算。对于这道题,可使用异或运算 ^。异或运算有以下三个性质:
- 任何数和 0 做异或运算,结果仍然是原来的数,即 a ^ 0=a;
- 任何数和其自身做异或运算,结果是 0,即 a ^ a=0;
- 异或运算满足交换律和结合律,即 a ^ b ^ a=b ^ a ^ a=b ^ (a ^ a)=b ^ 0=b;
代码
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for (int num : nums) {
res ^= num;
}
return res;
}
}
3 复杂性分析
- 时间复杂度O(n):其中 n 为数组长度,只需要对数组遍历一次;
- 空间复杂度O(1)
不用加减乘除做加法*
1 题目描述
写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
示例:
输入: a = 1, b = 1
输出: 2
提示:
a, b 均可能是负数或 0
结果不会溢出 32 位整数
2 解题(Java)
2.1 解题思路
- 设两数字的二进制形式 a, b,其求和 s = a + b,a(i)代表 a 的二进制第 i 位,则分为以下四种情况:
a(i) | b(i) | 无进位和n(i) | 进位c(i+1) |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
- 观察发现,无进位和与异或运算规律相同,进位与与运算规律相同(并需左移一位)。因此,无进位和n与进位c的计算公式如下:
- n = a ^ b(无进位和,异或运算)
- c = a&b<<1(进位,与运算 + 左移一位)
- (和s)=(无进位和n) + (进位c)。即可将s = a + b转化为:s = n + c;
- 循环求n和c,直至进位c=0;此时s = n,返回n即可;
问:若数字a和b中有负数,则变成了减法,如何处理?
答:在计算机系统中,数值一律用补码来表示和存储。补码的优势:加法、减法可以统一处理(CPU只有加法器)。因此,以上方法同时适用于正数和负数的加法。
2.2 代码
class Solution {
public int add(int a, int b) {
while(b != 0) { // 当进位为 0 时跳出
int c = (a & b) << 1; // c = 进位
a ^= b; // a = 非进位和
b = c;
}
return a;
}
}
3 复杂性分析
- 时间复杂度 O(1): 最差情况下(例如 a= 0x7fffffff , b=1 时),需循环 32 次,使用 O(1) 时间;每轮中的常数次位操作使用 O(1) 时间;
- 空间复杂度 O(1) : 辅助变量c使用常数大小的额外空间;
*二进制中1的个数
1 题目描述
请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
示例 1:
输入:00000000000000000000000000001011
输出:3
示例 2:
输入:11111111111111111111111111111101
输出:31
2 解题(Java)
方法:逐位判断
根据 与运算 定义,设二进制数字 n ,则有:
- 若 n&1=0 ,则 n 二进制 最右一位 为 0 ;
- 若 n&1=1 ,则 n 二进制 最右一位 为 1 。
算法流程:
- 初始化数量统计变量 res = 0。
- 循环逐位判断: 当 n = 0 时跳出。
- res += n & 1 : 若 n&1=1 ,则统计数 res 加1。
- n >>>= 1 : 将二进制数字 n 无符号右移一位( Java 中无符号右移为 “>>>” ) 。
- 返回统计数量 res。
代码:
public class Solution {
public int hammingWeight(int n) {
int res = 0;
while(n != 0) {
res += n & 1;
n >>>= 1;
}
return res;
}
}
3 复杂性分析
- 时间复杂度 O(log2n): 此算法循环内部仅有移位、与、加等基本运算,占用 O(1) ;逐位判断需循环log2 n 次,其中log2 n 代表数字n最高位1的所在位数(例如 log 2 4=2, log2 16=4);
- 空间复杂度 O(1) : 变量 res 使用常数大小额外空间;
*数值的整数次方
1 题目描述
实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例 2:
输入: 2.10000, 3
输出: 9.26100
示例 3:
输入: 2.00000, -2
输出: 0.25000
解释: 2 ^ -2 = (1/2) ^ 2 = 1/4 = 0.25
说明:
-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [−2 ^ 31, 2 ^ 31 − 1] 。
2 解题(Java)
2.1 解题思路
- 求 x ^ n 最简单的方法是通过循环将 n 个 x 乘起来,依次求 x ^ 1, x ^ 2, …, x ^ (n-1), x ^ n ,时间复杂度为 O(n);
- 快速幂法可将时间复杂度降低至 O(log2 n),以下从 “二进制” 和“二分法”两个角度解析快速幂法;
2.1.1 快速幂解析(二进制角度)
- 利用十进制数字 n 的二进制表示,可对快速幂进行数学化解释;
- 对于任何十进制正整数 n ,设其二进制为“bm…b3b2b1” ( bi为二进制某位值,i∈[1,m] ),则有:
- 二进制转十进制: n = 1b1 + 2b2 + 4b3 + … + 2 ^ (m-1)bm;
- 幂的二进制展开:x ^ n = x ^ (1b1 + 2b2 + 4b3 + … + 2 ^ (m-1)bm) = x ^ 1b1 * x ^ 2b2 * x ^ 4b3…x ^ (2 ^ (m-1))bm;
- 根据以上推导,可把计算 x^n转化为解决以下两个问题:
- 计算 x ^ 1, x ^ 2, x ^ 4, …, x ^ (2^(m-1))的值:循环赋值操作x = x ^ 2即可;
- 获取二进制各位b1, b2, b3, …,bm的值:循环执行以下操作即可:
- n & 1(与操作):判断n二进制最右一位是否为1;
- n >> 1(移位操作):n右移一位(即删除最后一位);
- 因此,应用以上操作,可在循环中依次计算x ^ (2 ^ 0 * b1),x ^ (2 ^ 1 * b2), …x ^ (2 ^ (m-1) * bm)的值,并将所有x ^ (2 ^ (i - 1) * bi)累计相乘即可:
- 当bi = 0时:x ^ (2 ^ (i-1) * bi) = 1;
- 当bi = 1时:x ^ (2 ^ (i-1) * bi) = x ^ (2 ^ (i-1));
2.1.2 快速幂解析(二分法角度)
- 快速幂实际上是二分思想的一种应用;
- 二分推导:x ^ n = x ^ n/2 * x ^ n/2 = (x ^ 2) ^ n/2,令n/2为整数,需要分为奇偶两种情况(设向下取整除法符号为“//”):
- 当n为偶数: x ^ n = (x ^ 2) ^ n//2;
- 当n为奇数:x ^ n = x(x ^ 2) ^ n//2,即会多出一项x;
- 幂结果获取:
- 根据二分推导,可通过x = x ^ 2操作,每次把幂从n降至n//2,直至将幂降为0;
- 设res = 1,则初始状态x ^ n = x ^ n * res。在循环二分时,每当n为奇数时,将多出的一项x乘入res,当n=0时,返回res即可;
- 转化为位运算:
- 向下整除n//2等价于右移一位n>>1;
- 取余数n%2等价于判断二进制最右一位值n & 1;
2.2 算法流程
- 当x = 0时:直接返回0(避免后续x = 1/x操作报错);
- 初始化res = 1;
- 当n<0时:把问题转化至n>=0的范围内,即执行x = 1/x, n = -n;
- 循环计算:当n=0时跳出:
- 当n&1=1时:将当前x乘入res(即res *= x);
- 执行x = x ^ 2(即x *= x);
- 执行n右移一位(即n>>=1);
- 返回res;
注:
Java 代码中 int32 变量 n∈[−2147483648,2147483647] ,因此当 n = -2147483648时执行 n = -n 会因越界而赋值出错。解决方法是先将 n 存入 long 变量 b ,后面用 b 操作即可。
2.3 代码
class Solution {
public double myPow(double x, int n) {
if (x == 0) return 0;
long b = n;
double res = 1.0;
if (b < 0) {
x = 1 / x;
b = -b;
}
while (b > 0) {
if((b & 1) == 1) res *= x;
x *= x;
b >>= 1;
}
return res;
}
}
3 复杂性分析
- 时间复杂度O(log 2 N):二分的时间复杂度为对数级别。
- 空间复杂度O(1):res, b 等变量占用常数大小额外空间。
*数组中数字出现的次数 II
1 题目描述
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
限制:
1 <= nums.length <= 10000
1 <= nums[i] < 2^31
2 解题(Java)
class Solution {
public int singleNumber(int[] nums) {
/*统计所有数字的各二进制位中1的出现次数,并对3求余,结果就是要求的数在该位的二进制数
*/
int[] counts = new int[32];
for(int num : nums) {
for(int i = 0; i <= 31; i++) {
counts[i] += num & 1;
num >>= 1;
}
}
int res = 0;
for(int i = 31; i >= 0; i--) {
// 左移操作+或运算,可将counts数组中各二进位的值恢复到数字res上,由于最后一步不用左移,所以左移不能在运算之后,应在之前
res <<= 1;
res |= counts[i] % 3;
}
return res;
}
}
3 复杂性分析
- 时间复杂度 O(N): 其中 N 为数组 nums 的长度,遍历数组使用O(N) 时间;
- 空间复杂度 O(1): 数组 counts 长度恒为 32 ,占用常数大小的额外空间;
*数组中数字出现的次数
1 题目描述
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
限制:
2 <= nums.length <= 10000
2 解题(Java)
位运算:
class Solution {
public int[] singleNumbers(int[] nums) {
//第一步异或,相同的数都抵消了,得到两个不同的数异或后的结果
int res = 0;
for (int num : nums) {
res ^= num;
}
// 这两数异或的结果肯定至少有一位为1,找到最低位的那个即可,则这两个数一个在此位为0,另一个为1
int div = 1;
while ((res & div) == 0) {
div <<= 1;
}
// 按得到的最低位分组并异或操作,由于相同的数异或为0,所以最终两个分组里各自得到那两个不同的数
int a = 0, b = 0;
for (int num: nums) {
if ((num & div) == 0) a ^= num;
else b ^= num;
}
return new int[]{a, b};
}
}
3 复杂性分析
- 时间复杂度:O(n):需要遍历数组两次;
- 空间复杂度:O(1):需要常数空间存放若干变量;
*子集
1 题目描述
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
提示:
- 1 <= nums.length <= 10
- -10 <= nums[i] <= 10
- nums 中的所有元素 互不相同
2 解题(Java)
2.1 解法1(回溯)
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> tmp = new ArrayList<>();
int[] nums;
public List<List<Integer>> subsets(int[] nums) {
this.nums = nums;
backTrack(0);
return res;
}
void backTrack(int x) {
res.add(new ArrayList(tmp));
for (int i = x; i < nums.length; i++) {
tmp.add(nums[i]);
backTrack(i+1);
tmp.remove(tmp.size() - 1);
}
}
}
复杂性分析
时间复杂度O(n * 2 ^ n):一共 2 ^ n 个状态,每个状态使用 O(n) 的时间复制到答案列表中;
空间复杂度O(n):临时列表 temp 的空间代价是 O(n),递归时栈空间的代价为 O(n);
2.2 解法2(位运算)
解题思路
- 记原序列中元素的总数为 n。原序列中的每个数字 a_i的状态可能有两种,即「在子集中」和「不在子集中」;
- 我们用 1 表示「在子集中」,0 表示「不在子集中」,那么每一个子集可以对应一个长度为 n 的 0/1 序列,第 i 位表示 a_i 是否在子集中。例如,n = 3,a = {5,2,9} 时:
- 可以发现 0/1 序列对应的二进制数正好从 0 到 2 ^ (n - 1) 。因此可以枚举mask∈[0, 2 ^ (n - 1)],mask 的二进制表示是一个 0/1 序列,我们可以按照这个 0/1 序列在原集合当中取数。
代码
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
int n = nums.length;
for (int mask = 0; mask < (1 << n); mask++) {
temp.clear();
for (int i=0; i<n; i++) {
if ((mask & (1 << i)) != 0) {
temp.add(nums[i]);
}
}
res.add(new ArrayList(temp));
}
return res;
}
}
复杂性分析
- 时间复杂度O(n * 2 ^ n):一共 2 ^ n 个状态,每个状态需要 O(n) 的时间来构造子集;
- 空间复杂度O(n):临时列表 temp 的空间代价是 O(n);
*旋转图像
1 题目描述
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
示例 3:
输入:matrix = [[1]]
输出:[[1]]
示例 4:
输入:matrix = [[1,2],[3,4]]
输出:[[3,1],[4,2]]
提示:
- matrix.length == n
- matrix[i].length == n
- 1 <= n <= 20
- -1000 <= matrix[i][j] <= 1000
2 解题(Java)
将旋转转换为水平翻转+对角线翻转。
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
// 先水平翻转
for (int i = 0; i <= (n-1) / 2; i++) {
for (int j = 0; j < n; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - 1 - i][j];
matrix[n - 1 - i][j] = temp;
}
}
// 再主对角线翻转
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
}
3 复杂性分析
- 时间复杂度O(N ^ 2):两次双层for循环,故为O(N ^ 2);
- 空间复杂度O(1):数组就地操作,故为O(1);
*剪绳子 II
1 题目描述
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0] * k[1] *…*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 1000
2 解题(Java)
2.1 解题思路
- 数学推论一:将绳子以相等的长度等分,得到的乘积最大;
- 数学推论二:尽可能将绳子以长度3等分时,乘积最大;
- 切分规则:当把绳子尽可能切分成多个长度为3的片段时,可能剩余一段长度为1或2的情况:如果最后一段为2,保留,不再拆分为1+1;如果最后一段为1,把一份3+1替换为2+2,因为2 * 2 > 3 * 1;
大数求余解法:
- 大数越界:当a增大时,最后返回的3 ^ a以指数级别增长,可能超出int32甚至int64的取值范围,导致返回值错误;
- 大数求余问题:在仅使用int32类型存储的前提下,正确计算x ^ a对p求余(即x ^ a % p)的值;
- 解决方案:循环求余。基于求余运算规则:(xy)% p = [(x % p)(y % p)] % p;
循环求余:
由于本题中x = 3 < p,所以x % p = x,于是有
- x ^ a % p = [(x ^ (a-1) % p)(x % p)] % p = [(x ^ (a-1) % p)x] % p
2.2 算法流程
利用前面得到的公式,可通过循环操作依次求3 ^ 1, 3 ^ 2, …,3 ^ (a-1), 3 ^ a对p的余数,保证每轮中间值都在int64取值范围内。
2.3 代码
class Solution {
public int cuttingRope(int n) {
if (n < 4) return n - 1;
else if (n == 4) return n;
long res = 1;
while (n > 4) {
n -= 3;
res = res * 3 % 1000000007;
}
return (int)(res * n % 1000000007);
}
}
3 复杂性分析
- 时间复杂度O(N):while循环时间复杂度为O(N);
- 空间复杂度O(1):变量res占用常数大小的额外空间;
*数组中出现次数超过一半的数字
1 题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
限制:
1 <= 数组长度 <= 50000
2 解题(Java)
摩尔投票法: 核心理念为票数正负抵消 。
class Solution {
public int majorityElement(int[] nums) {
int vote = 0;
int res = nums[0];
for (int num : nums) {
if (vote == 0) res = num;
vote += res == num ? 1 : -1;
}
return res;
}
}
3 复杂性分析
- 时间复杂度 O(N): N 为数组 nums 长度,需遍历一次nums;
- 空间复杂度 O(1): 定义变量占用常数大小的额外空间;
1~n 整数中 1 出现的次数*
1 题目描述
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
示例 1:
输入:n = 12
输出:5
示例 2:
输入:n = 13
输出:6
限制:
1 <= n < 2^31
2 解题(Java)
2.1 解题思路
- 将 1 ~ n 的个位、十位、百位、…的 1 出现次数相加,即为 1 出现的总次数;
- 设数字 n 是个 x 位数,记 n 的第 i 位为 ni,则可将n写为nxnx-1…n2n1:
- 称“ni”为当前位,记为cur;
- 将“ni-1ni-2…n2n1”称为低位,记为low;
- 将“nxnx-1…ni+2ni+1”称为高位,记为high;
- 位因子记为digit;
某位中 1 出现次数的计算方法:
根据当前位 cur 值的不同,分为以下三种情况:cur=0/cur=1/cur=2,3,4…8,9
- 当cur=0时,此位1的出现次数只由高位high决定,计算公式为:high × digit;如下图所示,以 n = 2304 为例,求 digit = 10(即十位)的 1 出现次数。
- 当 cur = 1 时: 此位 1 的出现次数由高位 high 和低位 low 决定,计算公式为:high×digit+low+1;如下图所示,以 n = 2314 为例,求 digit = 10(即十位)的 1 出现次数。
- 当 cur=2,3,⋯,9 时: 此位 1 的出现次数只由高位 high 决定,计算公式为:(high+1)×digit;如下图所示,以 n = 2324为例,求 digit = 10(即十位)的 1 出现次数。
变量递推公式:
设计按照 “个位、十位、…” 的顺序计算,则 high / cur / low / digit 应初始化为:
high = n // 10
cur = n % 10
low = 0
digit = 1 # 个位
因此,从个位到最高位的变量递推公式为:
while high != 0 or cur != 0: # 当 high 和 cur 同时为 0 时,说明已经越过最高位,因此跳出
low += cur * digit # 将 cur 加入 low ,组成下轮 low
cur = high % 10 # 下轮 cur 是本轮 high 的最低位
high /= 10 # 将本轮 high 最低位删除,得到下轮 high
digit *= 10 # 位因子每轮 × 10
2.2 代码
class Solution {
public int countDigitOne(int n) {
int digit = 1, res = 0;
int high = n / 10, cur = n % 10, 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 /= 10;
digit *= 10;
}
return res;
}
}
3 复杂性分析
- 时间复杂度 O(logn) :循环次数为数字 n 的位数,即 log 10 n , 循环内的计算操作使用 O(1) 时间,因此循环使用O(logn) 时间;
- 空间复杂度 O(1) : 几个变量使用常数大小的额外空间;
表示数值的字符串*
1 题目描述
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、“-123”、“3.1416”、“-1E-16”、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。
2 解题(Java)
class Solution {
public boolean isNumber(String s) {
//去掉首尾空格
s = s.trim();
//标记是否遇到相应的情况
boolean numSeen = false;
boolean dotSeen = false;
boolean eSeen = false;
for (int i=0; i<s.length(); i++) {
if (s.charAt(i) >= '0' && s.charAt(i) <= '9') {
numSeen = true;
}
else if (s.charAt(i) == '.') {
if (dotSeen || eSeen) {//"."之前不能有“.”或"e",但“.1”是数字,所以不用加|| !numSeen
return false;
}
dotSeen = true;//"1."也是数字,所以不用加numSeen = false;
}
else if (s.charAt(i) == 'e' || s.charAt(i) == 'E') {
//e之前不能出现e,e之前必须有数,e的前一位可以是.
if(eSeen || !numSeen) {
return false;
}
eSeen = true;
numSeen = false;//重置numSeen,确保e之后必须出现数
}
else if (s.charAt(i) == '-' || s.charAt(i) == '+') {
//+—必须出现在0位置或者e后面的第一个位置
if (i != 0 && s.charAt(i-1) != 'e' && s.charAt(i-1) != 'E') {
return false;
}
}
else {//其他不合法字符
return false;
}
}
return numSeen;//必须有数字,且e之后也必须有数字
}
}
3 复杂性分析
- 时间复杂度O(N):N为字符串s的长度,判断需遍历字符串,使用O(N)时间;
- 空间复杂度O(1):numSeen、dotSeen、eSeen占用常数大小的额外空间;
*确定两个字符串是否接近
1 题目描述
2 解题(Java)
2.1 解题思路
-
根据题意,如果两个字符串word1和word2接近,必须且只要满足如下两个条件:
- word1和word2中包含同样种类的字符;
- 字符对应个数构成的集合的内容相等;
-
考虑用字符运算来完成,定义两个长度为26(题目说明只包含小写字母)的整型数组cnt1和cnt2,由字符串的每个字符进行-‘a’操作构成数组的索引,于是索引0代表‘a’,索引1代表’b’…;
-
遍历两字符串中的字符,执行cnt1[word1.charAt(i)-‘a’]++;cnt2[word2.charAt(i)-‘a’]++;于是可以分别得到两字符串中每个字符对应的个数;
-
对两数组进行字符包含判断,如果字符在其中一个字符串中个数为0而在另外一个字符串中个数不为0,则判定两字符串不接近;
-
经过字符包含判断后,还需判断字符对应个数构成的集合的内容相等,使用内置函数的排序功能先对两数组排序,然后比较两数组内容是否相等即可。
2.2 代码
class Solution {
public boolean closeStrings(String word1, String word2) {
if (word1.length() != word2.length()) return false;
int len = word1.length();
int[] count1 = new int[26], count2 = new int[26];
for (int i=0; i<len; i++) {
count1[word1.charAt(i)-'a']++;
count2[word2.charAt(i)-'a']++;
}
for (int i=0; i<26; i++) {
if ((count1[i]==0 && count2[i]>0) || (count1[i]>0 && count2[i]==0)) {
return false;
}
}
Arrays.sort(count1);
Arrays.sort(count2);
return Arrays.equals(count1, count2);
}
}
3 复杂性分析
- 时间复杂度O(N):字符串长度为N,for遍历字符串使用O(N)时间;
- 空间复杂度O(1):定义数组及变量占用常数空间;
*把字符串转换成整数
1 题目描述
写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−2∧31, 2∧31 − 1]。如果数值超过这个范围,请返回 INT_MAX (2∧31 − 1) 或 INT_MIN (−2∧31) 。
示例 1:
输入: "42"
输出: 42
示例 2:
输入: " -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
示例 3:
输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
示例 4:
输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
因此无法执行有效的转换。
示例 5:
输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。
因此返回 INT_MIN (−2∧31) 。
2 解题(Java)
- 字符转数字: “此数字的 ASCII 码” 与 “ 0 的 ASCII 码” 相减即可;
- 新建一个变量保存符号位,返回前加上正负;
- 遇到首个非数字的字符时,跳出循环;
- 从左向右遍历字符串,设当前位字符为 c,结果为 res,则res = 10×res+(ascii( c )−ascii(′0′));
- 判断越界:bndry = Integer.MAX_VALUE / 10;
- res>bndry:越界;
- res=bndry,x>Integer.MAX_VALUE % 10:越界或等于Integer的下界;
class Solution {
public int myAtoi(String str) {
char[] c = str.trim().toCharArray();
if (c.length == 0) return 0;
int res = 0, start = 1, sign = 1;
if (c[0] == '-') sign = -1;
else if (c[0] != '+') start = 0;
for (int i = start; i < c.length; i++) {
if (c[i] < '0' || c[i] > '9') break;
if ((res > Integer.MAX_VALUE / 10) || (res == Integer.MAX_VALUE / 10 && c[i] > Integer.MAX_VALUE % 10 + '0')) {
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
res = res * 10 + (c[i] - '0');
}
return sign * res;
}
}
3 复杂性分析
- 时间复杂度 O(N): 其中 N 为字符串长度,遍历字符串使用 O(N) 时间;
- 空间复杂度 O(N) : 删除首尾空格后需建立字符数组,最差情况下占用 O(N) 额外空间;
*剪绳子
1 题目描述
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 58
2 解题(动态规划)
2.1 解题思路
- 当n >= 2时,可以拆分成至少两个正整数;
- 设k是拆分出的第一个正整数,剩下的部分是n-k,n-k可以拆分,也可以不拆分;
- 创建数组dp,dp[i]表示将正整数i拆分成至少两个正整数后,这些正整数的最大乘积;
- i >= 2,假设对i拆分出的第一个正整数是j(1<=j<i),有以下两种方案:
- i - j不再拆分,乘积为j * (i - j);
- i - j继续拆分,乘积为j * dp[i - j];
- 因此当j固定时,有dp[i] = max(j * (i - j),j * dp[i - j])。由于j的范围是[1, i-1],因此需遍历所有的j才能得到dp[i]的最大值,故而得到状态转移方程如下:
- 最终得到dp[n]的值即为n拆分成至少两个正整数的和之后,这些正整数的最大乘积;
2.2 代码
class Solution {
public int cuttingRope(int n) {
int[] dp = new int[n+1];
for (int i=2; i<=n; i++) {
int curMax = 0;
for (int j=1; j<i; j++) {
curMax = Math.max(curMax, Math.max(j * (i - j), j * dp[i - j]));
}
dp[i] = curMax;
}
return dp[n];
}
}
2.3 复杂性分析
- 时间复杂度O(N ^ 2):双层for循环使用O(N ^ 2)时间;
- 空间复杂度O(N):数组dp占用O(N)空间;
3 解题(数学)
3.1 解题思路
- 数学推论一:将绳子以相等的长度等分,得到的乘积最大;
- 数学推论二:尽可能将绳子以长度3等分时,乘积最大;
- 切分规则:当把绳子尽可能切分成多个长度为3的片段时,可能剩余一段长度为1或2的情况:如果最后一段为2,保留,不再拆分为1+1;如果最后一段为1,把一份3+1替换为2+2,因为2 * 2 > 3 * 1;
算法流程:
- 当n <= 3时,按照规则应不切分,但由于题目要求至少剪成两段,因此剪出一段长度为1的绳子,返回n-1;
- 当n > 3时,求n除以3的整数部分a和余数部分b,共有三种情况:
- b=0,直接返回3 ^ a;
- b=1,将一个1+3转换为2+2,因此返回3 ^ (a-1) * 4;
- b=2,返回3 ^ a * 2;
3.2 代码
class Solution {
public int cuttingRope(int n) {
if(n <= 3) return n - 1;
int a = n / 3, b = n % 3;
if(b == 0) return (int)Math.pow(3, a);
if(b == 1) return (int)Math.pow(3, a - 1) * 4;
return (int)Math.pow(3, a) * 2;
}
}
3.3 复杂性分析
- 时间复杂度O(1):仅有求整、求余、次方运算;
- 空间复杂度O(1):a和b占用常数大小额外空间;
*求1+2+…+n
1 题目描述
求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
示例 1:
输入: n = 3
输出: 6
示例 2:
输入: n = 9
输出: 45
限制:
1 <= n <= 10000
2 解题(Java)
利用逻辑运算符的短路效应:
常见的逻辑运算符有三种,即 “与 && ”,“或 ∣∣ ”,“非 ! ” ;而其有重要的短路效应,如下所示:
if(A && B) // 若 A 为 false ,则 B 的判断不会执行(即短路),直接判定 A && B 为 false
if(A || B) // 若 A 为 true ,则 B 的判断不会执行(即短路),直接判定 A || B 为 true
本题需要实现 “当 n = 1 时终止递归” 的需求,可通过短路效应实现。
n > 1 && sumNums(n - 1) // 当 n = 1 时 n > 1 不成立 ,此时 “短路” ,终止后续递归
代码如下:
class Solution {
int res;
public int sumNums(int n) {
boolean x = n > 1 && sumNums(n - 1) > 0;
res += n;
return res;
}
}
3 复杂性分析
- 时间复杂度 O(n) : 计算 n+(n−1)+…+2+1 需要开启 n 个递归函数;
- 空间复杂度 O(n) : 递归深度达到 n ,系统使用 O(n) 大小的额外空间;