力扣第43题-字符串相乘

1.有了过往题目的经验,直接可以舍弃掉"先将字符转换为数字,相乘得出结果后转换成字符串"的错误想法,因为给定的两个字符串长度最长可到200,相乘一定会溢出。
2.想到可以用小学列竖式计算的方式来进行计算,每次只取其中一位进行相乘计算,得出的结果只会影响当前位和下一位(进位)。所以可以使用两个for循环来遍历两个字符串的每一位,每次内层循环开始的时候初始化进位值carry为0,并初始化当前指针指向外层循环字符的所在位。执行过程中处理相乘的结果,加上进位值后得出临时值tmp,对10整除后填入carry,而在确定下一位的值时还需要判断本位加上tmp对10取余的值mul后是否还小于10,如果小于10则直接填入本位,如果大于等于10需要再次对10取余后填入本位,同时让carry加上mul对10整除的值。
3.当一次内层循环结束后,还需要判断进位值carry是否为0,如果不为0还需要填入下一位。当外层循环也结束后,当前cur的值一定等于数字实际长度,可以根据cur进行结果字符串的内存申请,同时逆序遍历数组依次转换为字符填入结果字符串即可。
4.基于以上代码逻辑,可写出完整代码如下:
cpp
1. char* multiply(char* num1, char* num2) {
2. int len1 = strlen(num1); // 数字1的长度
3. int len2 = strlen(num2); // 数字2的长度
4. // 乘积的最大长度 = len1 + len2(比如 99*99=9801,2+2=4)
5. int nums[len1 + len2];
6. // 初始化数组为0,避免脏数据
7. memset(nums, 0, sizeof(int) * (len1 + len2));
8. int cur, carry; // cur:当前要填充的位置;carry:进位
9.
10. // 特判:如果任一数是"0",直接返回"0"(避免后续无效计算)
11. if ((len1 == 1 && num1[0] == '0') || (len2 == 1 && num2[0] == '0')){
12. char* res = (char*)malloc(sizeof(char) * 2);
13. res[0] = '0';
14. res[1] = '\0';
15. return res;
16. }
17.
18. // ===================== 核心:模拟竖式乘法 =====================
19. // 从右往左遍历num1(个位开始乘)
20. for (int i = len1 - 1; i >= 0; i--){
21. // cur 初始为 len1-1-i:表示当前num1位对应的结果低位偏移(比如i=len1-1 → cur=0,对应个位)
22. cur = len1 - 1 - i;
23. carry = 0; // 每次乘新的num1位时,进位重置为0
24. // 从右往左遍历num2
25. for (int j = len2 - 1; j >= 0; j--){
26. // 计算当前位乘积 + 之前的进位
27. int tmp = (num1[i] - '0') * (num2[j] - '0') + carry;
28. // 当前结果位 = 原数组值 + 乘积的个位
29. int mul = nums[cur] + tmp % 10;
30. // 新的进位 = 乘积的十位
31. carry = tmp / 10;
32.
33. if (mul < 10){
34. // 无进位,直接赋值
35. nums[cur++] = mul;
36. } else {
37. // 有进位,加到carry里,当前位取个位
38. carry += mul / 10;
39. nums[cur++] = mul % 10;
40. }
41. }
42.
43. // 内层循环结束后,如果还有进位,加到结果的下一位
44. if (carry != 0){
45. nums[cur++] += carry;
46. }
47. }
48.
49. // ===================== 转换为字符串 =====================
50. // 分配结果字符串内存(cur是有效位数,+1存\0)
51. char* res = (char*)malloc(sizeof(char) * (cur + 1));
52. res[cur] = '\0'; // 字符串结束符
53. // 逆序遍历nums数组(因为我们是从低位往高位存的),转成字符
54. for (int i = cur - 1; i >= 0; i--){
55. res[i] = nums[cur - 1 - i] + '0';
56. }
57.
58. return res;
59. }
该算法的时间复杂度为O(len1×len2),空间复杂度为O(len1+len2),已是本题标准解。
5.值得注意的是,本题最特殊的情况应该是提供的两个字符串有至少一个为'0',如果不进行处理会导致最后输出的字符串是类似'000000'的形式,所以要单独进行这种特殊情况的判断。
力扣第67题-二进制求和

1.本题和力扣第2题-两数相加很相似,区别是本题是二进制计算,但思路都一样。使用一个整形数组来逆序记录每一位的值,然后使用两个指针分别从两个字符串的末尾开始遍历、相加,中间需要注意处理进位,以及处理至少其中一个字符串为'0'的特殊情况。最后给结果字符串开辟内存空间,遍历整形数组来为结果字符串幅值即可。
2.基于以上思想,可写出完整代码如下:
cpp
1. char* addBinary(char* a, char* b) {
2. // 1. 获取两个字符串长度
3. int len1 = strlen(a);
4. int len2 = strlen(b);
5.
6. // 2. 结果最大长度 = 较长的长度 + 1(最高位可能进位)
7. int numsLen = fmax(len1, len2) + 1;
8. int nums[numsLen]; // 存储每一位计算结果(低位在前)
9.
10. // 3. 特判:如果其中一个是 0,直接返回另一个
11. if (b[0] == '0'){
12. return a;
13. }
14. if (a[0] == '0'){
15. return b;
16. }
17.
18. // 4. 指针从末尾开始(个位开始加)
19. int i = len1 - 1;
20. int j = len2 - 1;
21. int cur = 0; // 结果数组当前位置
22. int isTwo = 0; // 进位值(0 或 1)
23.
24. // ===================== 核心加法 =====================
25. // 5. 两个数都还有位:一起加
26. while (i >= 0 && j >= 0){
27. // 计算当前位总和:a位 + b位 + 进位
28. int tmp = a[i] + b[j] - 2 * '0' + isTwo;
29. isTwo = tmp / 2; // 新进位:逢2进1
30. nums[cur++] = tmp % 2; // 当前位保留 0 或 1
31. i--;
32. j--;
33. }
34.
35. // 6. a 还有剩余位(b已用完)
36. while (i >= 0){
37. int tmp = a[i] - '0' + isTwo;
38. isTwo = tmp / 2;
39. nums[cur++] = tmp % 2;
40. i--;
41. }
42.
43. // 7. b 还有剩余位(a已用完)
44. while (j >= 0){
45. int tmp = b[j] - '0' + isTwo;
46. isTwo = tmp / 2;
47. nums[cur++] = tmp % 2;
48. j--;
49. }
50.
51. // 8. 最后还有进位,必须加上
52. if (isTwo != 0){
53. nums[cur++] = isTwo;
54. }
55.
56. // ===================== 转成字符串 =====================
57. // 9. 开辟结果内存
58. char* res = (char*)malloc(sizeof(char) * (cur + 1));
59. res[cur] = '\0';
60.
61. // 10. 逆序:nums 低位在前 → 转成高位在前的字符串
62. for (int k = 0; k < cur; k++){
63. res[k] = nums[cur - 1 - k] + '0';
64. }
65.
66. return res;
67. }
该算法时间复杂度和空间复杂度均为O(max(len(a), len(b))),是本题的标准解。
3.力扣上有更加精简的代码版本如下:
cpp
1. char* addBinary(char* a, char* b) {
2. int alen = strlen(a); // 字符串 a 的长度
3. int blen = strlen(b); // 字符串 b 的长度
4. // 开辟结果数组:最长可能长度 = 较长长度 + 2(进位 + \0)
5. // calloc 自动全部初始化为 0,自带结束符
6. char* ans = (char*)calloc(fmax(alen, blen) + 2, sizeof(char));
7.
8. int ap = alen - 1; // a 的指针:从最后一位(个位)开始
9. int bp = blen - 1; // b 的指针:从最后一位(个位)开始
10. int p = 0; // 结果数组的下标(从 0 开始存)
11. int temp = 0; // 进位(0 或 1)
12.
13. // 核心循环:只要 a 没加完 或 b 没加完,就继续加
14. while (ap >= 0 || bp >= 0) {
15. // 三数求和:a当前位 + b当前位 + 进位
16. int sum =
17. (ap >= 0 ? a[ap] - '0' : 0) + // a 还有位就取,没位补 0
18. (bp >= 0 ? b[bp] - '0' : 0) + // b 还有位就取,没位补 0
19. temp; // 加上进位
20.
21. // 判断是否进位
22. if (sum > 1) {
23. sum %= 2; // 当前位只保留 0 或 1
24. temp = 1; // 进位 = 1
25. } else {
26. temp = 0; // 不进位
27. }
28.
29. ans[p++] = '0' + sum; // 把数字转字符存入结果数组
30. --ap; // a 指针左移
31. --bp; // b 指针左移
32. }
33.
34. // 最后如果还有进位,必须加 1
35. if (temp)
36. ans[p++] = '1';
37.
38. // ===================== 关键:翻转字符串 =====================
39. // 因为我们是 低位先存 → 数组顺序是反的 → 必须翻转
40. int l = 0;
41. int r = strlen(ans) - 1;
42. while (l < r) {
43. char c = ans[l];
44. ans[l] = ans[r];
45. ans[r] = c;
46. ++l;
47. --r;
48. }
49.
50. return ans; // 返回正确顺序的结果
51. }
该算法有如下几点优化:
①使用一个三目运算代替了我的代码中的三段循环代码:
cpp
1. int sum=(ap>=0?a[ap]-'0':0)+(bp>=0?b[bp]-'0':0)+temp;
②不需要使用额外的整形数组来逆序记录每一位的值,而是直接将答案先逆序存到结果字符串中,最后遍历翻转即可。