原理
利用vector
对很大的数据(long long
没法存下,一般用string
存储)进行计算,可以分为高精度加减乘除,对于给定A、B
(位数最多为 1 0 6 10^6 106),给定b
(b<=1000
),求:(1)A+B
;(2)A-B
;(3)A*b
;(4)A/b
。
具体每个细节可以参考下面的题目。
问题描述
分析
首先就要考虑数据的存储方式。因为数据非常大,因此不能使用long long
或者其他类型表示,否则会溢出。我们可以首先将数据读入字符串中,然后存入数组中(C++
中对应vector
,Java
中对应List
),因为我们的计算都是从个位开始的,因此数据的最低位存储在索引为0
的位置。
假设两个需要相加的数据存储到了A、B
中(A、B
是vector
或者List
),我们按照小学的加法运算将结果存储到另一个数组C
中即可,计算过程中使用t
表示进位。
最后将C
中的每个数据按照索引由大到小数据即是结果。
另外可以采用压位的写法,即在加法中将9
个比特位存放到一个int
中,这样比较节省空间,同时也能提升运行效率。
代码
#include
#include
using namespace std;
vector<int> add(vector<int> &A, vector<int> &B) {
vector<int> C;
for (int i = 0, t = 0; i < A.size() || i < B.size() || t; i++) {
// t:进位
if (i < A.size()) t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
return C;
}
int main() {
string a, b;
cin >> a >> b;
vector<int> A, B;
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - '0');
vector<int> C = add(A, B);
for (int i = C.size() - 1; i >= 0; i--)
printf("%d", C[i]);
return 0;
}
// 压位写法:压9位
#include
#include
using namespace std;
const int base = 1000000000;
vector<int> add(vector<int> &A, vector<int> &B) {
vector<int> C;
for (int i = 0, t = 0; i < A.size() || i < B.size() || t; i++) {
if (i < A.size()) t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % base);
t /= base;
}
return C;
}
int main() {
string a, b;
cin >> a >> b;
vector<int> A, B;
for (int i = a.size() - 1, s = 0, j = 0, t = 1; i >= 0; i--) {
s += (a[i] - '0') * t; // 假设12345, 则s中存储的要是12345
j++, t *= 10;
if (j == 9 || i == 0) {
A.push_back(s);
s = 0, j = 0, t = 1;
}
}
for (int i = b.size() - 1, s = 0, j = 0, t = 1; i >= 0; i--) {
s += (b[i] - '0') * t;
j++, t *= 10;
if (j == 9 || i == 0) {
B.push_back(s);
s = 0, j = 0, t = 1;
}
}
vector<int> C = add(A, B);
cout << C.back();
for (int i = C.size() - 2; i >= 0; i--)
printf("%09d", C[i]);
return 0;
}
import java.util.*;
public class Main {
private static List<Integer> add(List<Integer> A, List<Integer> B) {
List<Integer> C = new ArrayList<>();
int t = 0;
for (int i = 0; i < A.size() || i < B.size(); i++) {
if (i < A.size()) t += A.get(i);
if (i < B.size()) t += B.get(i);
C.add(t % 10);
t /= 10;
}
if (t != 0) C.add(1);
return C;
}
public static void main(String[] args) {
// 读入数据
Scanner scan = new Scanner(System.in);
char[] a = scan.next().toCharArray(), b = scan.next().toCharArray();
List<Integer> A = new ArrayList<>(), B = new ArrayList<>();
for (int i = a.length - 1; i >= 0; i--) A.add(a[i] - '0');
for (int i = b.length - 1; i >= 0; i--) B.add(b[i] - '0');
List<Integer> C = add(A, B);
for (int i = C.size() - 1; i >= 0; i--)
System.out.print(C.get(i));
}
}
问题描述
分析
和高精度加法的存储方式一致,假设两个需要相减的数据已经存储到了A、B
中。这里需要保证A>=B
,如果不满足的话,交换A、B
,最后输出结果的时候最前面输出一个负号即可。
这里的减法也是模拟小学中的减法过程,从个位开始减,最开始让借位t=0
,第i
位应该是A[i]-B[i]-t
,如果这个数是负数,说明需要向高位借位,接一个算十个,一直这样计算每一位即可。
代码
#include
#include
using namespace std;
// 如果A>=B,返回true
bool cmp(vector<int> &A, vector<int> &B) {
if (A.size() != B.size()) return A.size() > B.size();
for (int i = A.size() - 1; i >= 0; i--)
if (A[i] != B[i])
return A[i] > B[i];
return true;
}
vector<int> sub(vector<int> &A, vector<int> &B) {
vector<int> C;
for (int i = 0, t = 0; i < A.size(); i++) {
t = A[i] - t;
if (i < B.size()) t -= B[i];
C.push_back((t + 10) % 10);
if (t < 0) t = 1; // 说明有借位
else t = 0;
}
// 去掉前导零
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main() {
string a, b;
cin >> a >> b;
vector<int> A, B;
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - '0');
vector<int> C;
if (cmp(A, B)) {
C = sub(A, B);
} else {
C = sub(B, A);
printf("-");
}
for (int i = C.size() - 1; i >= 0; i--)
printf("%d", C[i]);
return 0;
}
import java.util.*;
public class Main {
// 判断是否有A >= B
private static boolean cmp(List<Integer> A, List<Integer> B) {
if (A.size() != B.size()) return A.size() > B.size();
for (int i = A.size() - 1; i >= 0; i--)
if (A.get(i) != B.get(i))
return A.get(i) > B.get(i);
return true;
}
// C = A - B
private static List<Integer> sub(List<Integer> A, List<Integer> B) {
List<Integer> C = new ArrayList<>();
int t = 0;
for (int i = 0; i < A.size(); i++) {
t = A.get(i) - t;
if (i < B.size()) t -= B.get(i);
C.add((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
while (C.size() > 1 && C.get(C.size() - 1) == 0) C.remove(C.size() - 1);
return C;
}
public static void main(String[] args) {
// 读入数据
Scanner scan = new Scanner(System.in);
char[] a = scan.next().toCharArray(), b = scan.next().toCharArray();
List<Integer> A = new ArrayList<>(), B = new ArrayList<>();
for (int i = a.length - 1; i >= 0; i--) A.add(a[i] - '0');
for (int i = b.length - 1; i >= 0; i--) B.add(b[i] - '0');
// 算法代码
List<Integer> C;
if (cmp(A, B)) C = sub(A, B);
else {
C = sub(B, A);
System.out.print("-");
}
for (int i = C.size() - 1; i >= 0; i--)
System.out.print(C.get(i));
}
}
问题描述
分析
和高精度加法的存储方式一致,假设较大的数已经存储到A
中了,较小的数据直接使用int
存储即可,假设存储到b
中。
从A
的个位开始计算,每次让A[i]
乘以b
,当前位数据为A[i]*b % 10
,进位为A[i]*b / 10
。
代码
#include
#include
using namespace std;
vector<int> mul(vector<int> &A, int b) {
vector<int> C;
for (int i = 0, t = 0; i < A.size() || t; i++) {
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
// 去掉前导零
while (C.size() > 1 && !C.back()) C.pop_back();
return C;
}
int main() {
string a;
int b;
cin >> a >> b;
vector<int> A;
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
vector<int> C = mul(A, b);
for (int i = C.size() - 1; i >= 0; i--)
printf("%d", C[i]);
return 0;
}
import java.util.*;
public class Main {
// C = A * b
private static List<Integer> mul(List<Integer> A, int b) {
List<Integer> C = new ArrayList<>();
int t = 0; // 进位
for (int i = 0; i < A.size() || t != 0; i++) {
if (i < A.size()) t += A.get(i) * b;
C.add(t % 10);
t /= 10;
}
// 去除前导0
while (C.size() > 1 && C.get(C.size() - 1) == 0) C.remove(C.size() - 1);
return C;
}
public static void main(String[] args) {
// 读入数据
Scanner scan = new Scanner(System.in);
char[] a = scan.next().toCharArray();
int b = scan.nextInt();
List<Integer> A = new ArrayList<>();
for (int i = a.length - 1; i >= 0; i--) A.add(a[i] - '0');
// 算法代码
List<Integer> C = mul(A, b);
for (int i = C.size() - 1; i >= 0; i--) System.out.print(C.get(i));
}
}
扩展:大整数乘以大整数:对应Leetcode 0043 字符串相乘。
问题描述
分析
和高精度加法的存储方式一致,假设较大的数已经存储到A
中了,较小的数据直接使用int
存储即可,假设存储到b
中。
这里模拟小学的除法进行计算,我们应该从A
的最高位开始一位一位的考虑,用r
记录余数,初始r=0
,每次让r = r*10 + A[i]
,之后当前为的数据为r/b
,余数r
变为r%b
。可以结合如下例子进行思考:
代码
#include
#include
#include
using namespace std;
vector<int> div(vector<int> &A, int b, int &r) {
vector<int> C;
for (int i = A.size() - 1; i >= 0; i--) {
r = r * 10 + A[i];
C.push_back(r / b);
r %= b;
}
reverse(C.begin(), C.end()); // 低位在索引小的位置,为了和加减乘统一
// 去掉前导0
while (C.size() > 1 && !C.back()) C.pop_back();
return C;
}
int main() {
string a;
int b;
cin >> a >> b;
vector<int> A;
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
int r = 0;
vector<int> C = div(A, b, r);
for (int i = C.size() - 1; i >= 0; i--)
printf("%d", C[i]);
printf("\n%d\n", r);
return 0;
}
import java.util.*;
public class Main {
private static int r; // 余数
// A / b , 商是C,余数是 r
private static List<Integer> div(List<Integer> A, int b) {
List<Integer> C = new ArrayList<>();
for (int i = A.size() - 1; i >= 0; i--) {
r = r * 10 + A.get(i);
C.add(r / b);
r %= b;
}
Collections.reverse(C);
// 去掉前导0
while (C.size() > 1 && C.get(C.size() - 1) == 0) C.remove(C.size() - 1);
return C;
}
public static void main(String[] args) {
// 读入数据
Scanner scan = new Scanner(System.in);
String a = scan.next();
int b = scan.nextInt();
List<Integer> A = new ArrayList<>();
for (int i = a.length() - 1; i >= 0; i--) A.add(a.charAt(i) - '0');
// 算法代码
List<Integer> C = div(A, b);
for (int i = C.size() - 1; i >= 0; i--) System.out.print(C.get(i));
System.out.println();
System.out.print(r);
}
}
题目描述:Leetcode 0043 字符串相乘
分析
本题的考点:高精度乘法。
首先我们将数据存储到数组A、B
中,其中A[0]、 B[0]
存储的数字的个位,结果存储到C
数组中,分为两步:
(1)不考虑进位直接将 A [ i ] × B [ j ] A[i] \times B[j] A[i]×B[j]的结果存到 C [ i + j ] C[i + j] C[i+j]中;
(2)处理C
中的进位。如下图: 123 × 456 123 \times 456 123×456:
代码
class Solution {
public:
string multiply(string num1, string num2) {
vector<int> A, B;
int n = num1.size(), m = num2.size();
for (int i = n - 1; i >= 0; i--) A.push_back(num1[i] - '0');
for (int i = m - 1; i >= 0; i--) B.push_back(num2[i] - '0');
// (1) 不考虑进位,将结果存入C中
vector<int> C(n + m);
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
C[i + j] += A[i] * B[j];
// (2) 考虑进位
for (int i = 0, t = 0; i < C.size(); i++) {
t += C[i];
C[i] = t % 10;
t /= 10;
}
// 处理输出
int k = C.size() - 1;
while (k && C[k] == 0) k--;
string res;
while (k >= 0) res += C[k--] + '0';
return res;
}
};
class Solution {
public String multiply(String num1, String num2) {
int n = num1.length(), m = num2.length();
int[] A = new int[n], B = new int[m];
for (int i = n - 1; i >= 0; i--) A[n - 1 - i] = num1.charAt(i) - '0';
for (int i = m - 1; i >= 0; i--) B[m - 1 - i] = num2.charAt(i) - '0';
// (1) 不考虑进位,将结果存入C中
int[] C = new int[n + m];
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
C[i + j] += A[i] * B[j];
// (2) 考虑进位
for (int i = 0, t = 0; i < C.length; i++) {
t += C[i];
C[i] = t % 10;
t /= 10;
}
// 处理输出
int k = C.length - 1;
while (k > 0 && C[k] == 0) k--;
StringBuilder sb = new StringBuilder();
while (k >= 0) sb.append((char)(C[k--] + '0'));
return sb.toString();
}
}
class Solution:
def multiply(self, num1: str, num2: str) -> str:
n = len(num1); m = len(num2)
A = []; B = []
for i in range(n - 1, -1, -1):
A.append(ord(num1[i]) - ord('0'))
for i in range(m - 1, -1, -1):
B.append(ord(num2[i]) - ord('0'))
C = [0 for _ in range(n + m)]
for i in range(n):
for j in range(m):
C[i + j] += A[i] * B[j]
t = 0
for i in range(len(C)):
t += C[i]
C[i] = t % 10
t //= 10
# 处理输出
k = len(C) - 1
while k > 0 and C[k] == 0:
k -= 1
res = ""
while k >= 0:
res += str(C[k])
k -= 1
return res
时空复杂度分析
时间复杂度: O ( n × m ) O(n \times m) O(n×m),n
为num1
的长度,m
为num2
的长度。
空间复杂度: O ( n + m ) O(n + m) O(n+m),n
为num1
的长度,m
为num2
的长度。
如果数据范围很大,可以考虑使用FFT
将时间复杂度优化为 O ( n × l o g ( n ) ) O(n \times log(n)) O(n×log(n))。
题目描述:Leetcode 0066 加一
分析
本题的考点:高精度加法。
直接使用加法的规则模拟一遍即可,因为加上的是1
,因此最终的结果最多比digit
多一位,比如99+1=100
,因此使用原数组记录结果即可。
注意个位在digits[0]
这样比较方便计算,因此需要对输入进行翻转。
代码
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
reverse(digits.begin(), digits.end());
int t = 1;
for (auto &x : digits) {
t += x;
x = t % 10;
t /= 10;
}
if (t) digits.push_back(t);
reverse(digits.begin(), digits.end());
return digits;
}
};
class Solution {
public int[] plusOne(int[] digits) {
reverse(digits);
int t = 1;
for (int i = 0; i < digits.length; i++) {
t += digits[i];
digits[i] = t % 10;
t /= 10;
}
if (t != 0) {
int[] res = new int[digits.length + 1];
System.arraycopy(digits, 0, res, 0, digits.length);
res[digits.length] = 1;
reverse(res);
return res;
}
reverse(digits);
return digits;
}
private void reverse(int[] nums) {
for (int i = 0, j = nums.length - 1; i < j; i++, j--) {
int t = nums[i]; nums[i] = nums[j]; nums[j] = t;
}
}
}
时空复杂度分析
时间复杂度: O ( n ) O(n) O(n),n
为数组长度。
空间复杂度: O ( n ) O(n) O(n)。
题目描述:Leetcode 0067 二进制加和
分析
本题的考点:高精度加法。
直接使用加法的规则模拟一遍即可。为了处理方便,让个位在数组最低位。
代码
class Solution {
public:
string addBinary(string a, string b) {
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());
string res;
for (int i = 0, t = 0; i < a.size() || i < b.size() || t; i++) {
if (i < a.size()) t += a[i] - '0';
if (i < b.size()) t += b[i] - '0';
res += to_string(t % 2);
t /= 2;
}
reverse(res.begin(), res.end());
return res;
}
};
class Solution {
public String addBinary(String a, String b) {
char[] ca = new StringBuilder(a).reverse().toString().toCharArray();
char[] cb = new StringBuilder(b).reverse().toString().toCharArray();
StringBuilder sb = new StringBuilder();
for (int i = 0, t = 0; i < ca.length || i < cb.length || t != 0; i++) {
if (i < ca.length) t += ca[i] - '0';
if (i < cb.length) t += cb[i] - '0';
sb.append((char)(t % 2 + '0'));
t /= 2;
}
sb = sb.reverse();
return sb.toString();
}
}
时空复杂度分析
时间复杂度: O ( n ) O(n) O(n),n
为字符串长度。
空间复杂度:考虑输出 O ( n ) O(n) O(n)。
题目描述:Leetcode 0166 分数到小数
分析
本题的考点:模拟、高精度除法。
可以分为如下步骤:
(1)计算结果对应的符号;
(2)计算整数部分;
(3)计算小数部分;
关键在于计算小数部分,即如何判断是否是循环小数,以及找出循环节的位置。这里就是模拟手工除法的过程,每次将余数乘10再除以除数,当同一个余数出现两次时,我们就找到了循环节。
代码
class Solution {
public:
string fractionToDecimal(int numerator, int denominator) {
typedef long long LL;
LL x = numerator, y = denominator;
if (x % y == 0) return to_string(x / y);
string res;
if ((x < 0) ^ (y < 0)) res += '-';
x = abs(x), y = abs(y);
res += to_string(x / y) + '.', x %= y;
unordered_map<LL, int> hash; // (余数,余数在字符串中的位置)
while (x) {
hash[x] = res.size();
x *= 10;
res += to_string(x / y), x %= y;
if (hash.count(x)) {
res = res.substr(0, hash[x]) + '(' + res.substr(hash[x]) + ')';
break;
}
}
return res;
}
};
class Solution {
public String fractionToDecimal(int numerator, int denominator) {
long x = numerator, y = denominator;
if (x % y == 0) return String.valueOf(x / y);
StringBuilder sb = new StringBuilder();
if ((x < 0) ^ (y < 0)) sb.append("-");
x = Math.abs(x);
y = Math.abs(y);
sb.append(x / y).append(".");
x %= y;
HashMap<Long, Integer> hash = new HashMap<>();
while (x != 0) {
hash.put(x, sb.length());
x *= 10;
sb.append(x / y);
x %= y;
if (hash.containsKey(x)) {
return sb.substring(0, hash.get(x)) + '(' + sb.substring(hash.get(x)) + ')';
}
}
return sb.toString();
}
}
时空复杂度分析
时间复杂度: O ( n ) O(n) O(n),n
是结果的程度。
空间复杂度: O ( 1 ) O(1) O(1)。哈希表最多存放10个数据,之后必定重复。
题目描述:Leetcode 0306 累加数
分析
本题的考点:高精度加法。
本题枚举前两个数则可以唯一确定后面的数,因此枚举所有的前两个数即可。
因为数据可能会超过int
最大值,因此需要用到高精度加法。
代码
class Solution {
public:
bool isAdditiveNumber(string num) {
for (int i = 0; i < num.size(); i++)
for (int j = i + 1; j + 1 < num.size(); j++) {
int a = -1, b = i, c = j; // 第一个数:num[a+1,b];第二个数:num[b+1,c]
while (true) {
if ((b - a > 1 && num[a + 1] == '0') || (c - b > 1 && num[b + 1] == '0')) break; // 有前导0
auto x = num.substr(a + 1, b - a), y = num.substr(b + 1, c - b);
auto z = add(x, y);
if (num.substr(c + 1, z.size()) != z) break; // 下一个数不匹配
a = b, b = c, c += z.size();
if (c + 1 == num.size()) return true;
}
}
return false;
}
string add(string x, string y) {
vector<int> A, B, C;
for (int i = x.size() - 1; i >= 0; i--) A.push_back(x[i] - '0');
for (int i = y.size() - 1; i >= 0; i--) B.push_back(y[i] - '0');
for (int i = 0, t = 0; i < A.size() || i < B.size() || t; i++) {
if (i < A.size()) t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
string z;
for (int i = C.size() - 1; i >= 0; i--) z += to_string(C[i]);
return z;
}
};
class Solution {
public boolean isAdditiveNumber(String num) {
char[] nums = num.toCharArray();
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j + 1 < nums.length; j++) {
int a = -1, b = i, c = j; // 第一个数:num[a+1,b];第二个数:num[b+1,c]
while (true) {
if ((b - a > 1 && nums[a + 1] == '0') || (c - b > 1 && nums[b + 1] == '0')) break; // 有前导0
String x = num.substring(a + 1, b + 1), y = num.substring(b + 1, c + 1);
String z = add(x, y);
if (c + 1 + z.length() > num.length() || !num.substring(c + 1, c + 1 + z.length()).equals(z)) break;
a = b;
b = c;
c += z.length();
if (c + 1 == num.length()) return true;
}
}
}
return false;
}
// 高精度加法
private String add(String x, String y) {
List<Integer> A = new ArrayList<>(), B = new ArrayList<>(), C = new ArrayList<>();
for (int i = x.length() - 1; i >= 0; i--) A.add(x.charAt(i) - '0');
for (int i = y.length() - 1; i >= 0; i--) B.add(y.charAt(i) - '0');
for (int i = 0, t = 0; i < A.size() || i < B.size() || t != 0; i++) {
if (i < A.size()) t += A.get(i);
if (i < B.size()) t += B.get(i);
C.add(t % 10);
t /= 10;
}
StringBuilder sb = new StringBuilder();
for (int i = C.size() - 1; i >= 0; i--) sb.append(C.get(i));
return sb.toString();
}
}
时空复杂度分析
时间复杂度: O ( n 3 ) O(n ^ 3) O(n3),n
为字符串num
长度。
空间复杂度: O ( n ) O(n) O(n)。