稀疏整数求和
华为笔试真题 10月28号 第二题 200分题型
题目描述
给定两个位数可能极多的非负整数,请求出它们相加后的结果。
这类整数的十进制表示通常只有少量数位不为 000,所以仅记录非零数字片段。每个片段写作 c:p,其中 ccc 是片段系数,ppp 是十进制位权的指数。
一个
c:p片段所贡献的数值为 c×10pc \times 10^pc×10p;ppp 对应系数最右侧数字所在的数位。
表示方式需要遵守以下约定:
- 指数满足 0≤p<2 000 000 0000 \le p < 2\,000\,000\,0000≤p<2000000000,允许的最大单项为
999:1999999999。 - 按数位从高向低列出片段,任何片段的系数中都不能出现数字 000;遇到零位时应断开片段。
- 系数 ccc 至多包含 333 个数字。若一段连续非零数字更长,就从左端开始,每次取尽可能多且不超过 333 位的数字作为一组。例如,
8765432应划为876、543、2。 - 每个整数的编码末尾都附上标记
0:0,数值为零时只写这一标记。相邻项之间以空格隔开。 - 所有输入编码均合法。每行编码的字符数连同空格计入后小于 512512512,并保证求和结果中各片段的指数也小于 2 000 000 0002\,000\,000\,0002000000000。
例如:
- 十进制数
8765004009的编码为876:7 5:6 4:3 9:0 0:0。 - 十进制数
8765004000的编码为876:7 5:6 4:3 0:0。
输入描述
- 首行给出其中一个非负整数的编码。
- 次行给出另一个非负整数的编码。
两行均采用上述片段表示方式。
输出描述
输出这两个数相加得到的数值。
结果也须按上述约定编码:连续非零数字从高位起每次最多取 333 位,并在末尾附上 0:0。
样例1
输入:
567:3 89:1 0:0
444:3 321:0 0:0
输出:
1:6 122:4 11:0 0:0
解释:
两行分别代表 567890567890567890 和 444321444321444321,相加可得 101221110122111012211。
零位将结果分为 1 与 12211 两段;后一段再从左向右划为 122、11,故得到上面的编码。
样例2
输入:
999:3 999:0 0:0
1:0 0:0
输出:
1:6 0:0
解释:
相加的数值是 999999999999999999 与 111,得到 100000010000001000000。连续进位后,只有指数为 666 的数位非零,编码因此只包含这一片段和结束标记。
题解和思路
思路
实现思路:模拟
- 解析输入编码,使用
哈希表存储每个非零数位,哈希表中key:value,其中- 键:十进制数位的位置
- 值:该位置上的数字
- 从低位开始从低位开始模拟竖式加法,也是用哈希表进行存储。
- 将结果进行重新编码,利用得到的结果哈希表的非零数位后,从高位向低位扫描:
- 遇到连续非零数字时,按从左到右每组最多 3 位进行划分。
- 遇到零位时,结束当前片段。
- 每个片段输出为
c:p。 - 最后输出
0:0。
- 时间复杂度为
O(KlogK)
C++
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 解析编码,将每个非零数位累加到对应位置
void parse(string line, map<ll, int>& digits) {
stringstream ss(line);
string token;
while (ss >> token) {
int k = token.find(':');
string c = token.substr(0, k);
ll p = stoll(token.substr(k + 1));
if (c == "0") {
continue;
}
// 从系数右侧开始拆分,每个数字对应一个十进制数位
for (int i = (int)c.size() - 1; i >= 0; i--) {
digits[p + (c.size() - 1 - i)] += c[i] - '0';
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string line1, line2;
getline(cin, line1);
getline(cin, line2);
// 存储两个整数对应数位的数字之和
map<ll, int> digits;
parse(line1, digits);
parse(line2, digits);
// 结果中非零数位的位置及对应数字
map<ll, int> res;
ll prev = -1;
int carry = 0;
// 从低位向高位模拟竖式加法
for (auto [p, val] : digits) {
// 如果中间存在零位,进位只会传递到紧邻的下一位
if (prev != -1 && p > prev + 1 && carry > 0) {
res[prev + 1] = carry;
carry = 0;
}
int sum = val + carry;
int digit = sum % 10;
carry = sum / 10;
if (digit != 0) {
res[p] = digit;
}
prev = p;
}
// 处理最高位之后剩余的进位
if (prev != -1 && carry > 0) {
res[prev + 1] = carry;
}
if (res.empty()) {
cout << "0:0\n";
return 0;
}
// 从高位向低位遍历,将连续非零数位每三个划分为一组
vector<pair<ll, int>> v(res.rbegin(), res.rend());
vector<string> ans;
string c = "";
ll last = -1;
for (auto [pos, digit] : v) {
// 遇到不连续的数位,或者当前组已经有三位,则结束当前片段
if (!c.empty() && (last - pos != 1 || c.size() == 3)) {
ans.push_back(c + ":" + to_string(last));
c.clear();
}
c += char('0' + digit);
last = pos;
}
if (!c.empty()) {
ans.push_back(c + ":" + to_string(last));
}
// 输出编码,并在末尾添加结束标记
for (int i = 0; i < (int)ans.size(); i++) {
if (i > 0) cout << ' ';
cout << ans[i];
}
cout << " 0:0\n";
return 0;
}
Java
java
import java.util.*;
import java.io.*;
public class Main {
// 解析编码,将每个非零数位累加到对应位置
static void parse(String line, TreeMap<Long, Integer> digits) {
String[] tokens = line.trim().split("\\s+");
for (String token : tokens) {
if (token.isEmpty()) {
continue;
}
int k = token.indexOf(':');
String c = token.substring(0, k);
long p = Long.parseLong(token.substring(k + 1));
if (c.equals("0")) {
continue;
}
// 从系数右侧开始拆分,每个数字对应一个十进制数位
for (int i = c.length() - 1; i >= 0; i--) {
long pos = p + (c.length() - 1 - i);
digits.put(pos, digits.getOrDefault(pos, 0) + c.charAt(i) - '0');
}
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line1 = br.readLine();
String line2 = br.readLine();
// 存储两个整数对应数位的数字之和
TreeMap<Long, Integer> digits = new TreeMap<>();
parse(line1, digits);
parse(line2, digits);
// 结果中非零数位的位置及对应数字
TreeMap<Long, Integer> res = new TreeMap<>();
long prev = -1;
int carry = 0;
// 从低位向高位模拟竖式加法
for (Map.Entry<Long, Integer> entry : digits.entrySet()) {
long p = entry.getKey();
int val = entry.getValue();
// 如果中间存在零位,进位只会传递到紧邻的下一位
if (prev != -1 && p > prev + 1 && carry > 0) {
res.put(prev + 1, carry);
carry = 0;
}
int sum = val + carry;
int digit = sum % 10;
carry = sum / 10;
if (digit != 0) {
res.put(p, digit);
}
prev = p;
}
// 处理最高位之后剩余的进位
if (prev != -1 && carry > 0) {
res.put(prev + 1, carry);
}
if (res.isEmpty()) {
System.out.println("0:0");
return;
}
// 从高位向低位遍历,将连续非零数位每三个划分为一组
List<Map.Entry<Long, Integer>> v =
new ArrayList<>(res.descendingMap().entrySet());
List<String> ans = new ArrayList<>();
StringBuilder c = new StringBuilder();
long last = -1;
for (Map.Entry<Long, Integer> entry : v) {
long pos = entry.getKey();
int digit = entry.getValue();
// 遇到不连续的数位,或者当前组已经有三位,则结束当前片段
if (c.length() > 0 && (last - pos != 1 || c.length() == 3)) {
ans.add(c.toString() + ":" + last);
c.setLength(0);
}
c.append((char) ('0' + digit));
last = pos;
}
if (c.length() > 0) {
ans.add(c.toString() + ":" + last);
}
// 输出编码,并在末尾添加结束标记
System.out.println(String.join(" ", ans) + " 0:0");
}
}
python
python
import sys
# 解析编码,将每个非零数位累加到对应位置
def parse(line, digits):
tokens = line.split()
for token in tokens:
k = token.index(':')
c = token[:k]
p = int(token[k + 1:])
if c == "0":
continue
# 从系数右侧开始拆分,每个数字对应一个十进制数位
for i in range(len(c) - 1, -1, -1):
pos = p + (len(c) - 1 - i)
digits[pos] = digits.get(pos, 0) + int(c[i])
def main():
line1 = sys.stdin.readline().rstrip('\n')
line2 = sys.stdin.readline().rstrip('\n')
# 存储两个整数对应数位的数字之和
digits = {}
parse(line1, digits)
parse(line2, digits)
# 结果中非零数位的位置及对应数字
res = {}
prev = -1
carry = 0
# 从低位向高位模拟竖式加法
for p in sorted(digits):
val = digits[p]
# 如果中间存在零位,进位只会传递到紧邻的下一位
if prev != -1 and p > prev + 1 and carry > 0:
res[prev + 1] = carry
carry = 0
s = val + carry
digit = s % 10
carry = s // 10
if digit != 0:
res[p] = digit
prev = p
# 处理最高位之后剩余的进位
if prev != -1 and carry > 0:
res[prev + 1] = carry
if not res:
print("0:0")
return
# 从高位向低位遍历,将连续非零数位每三个划分为一组
v = sorted(res.items(), reverse=True)
ans = []
c = ""
last = -1
for pos, digit in v:
# 遇到不连续的数位,或者当前组已经有三位,则结束当前片段
if c and (last - pos != 1 or len(c) == 3):
ans.append(c + ":" + str(last))
c = ""
c += str(digit)
last = pos
if c:
ans.append(c + ":" + str(last))
# 输出编码,并在末尾添加结束标记
print(" ".join(ans) + " 0:0")
if __name__ == "__main__":
main()
Javascript
js
const readline = require('readline');
// 解析编码,将每个非零数位累加到对应位置
function parse(line, digits) {
const tokens = line.trim().split(/\s+/);
for (const token of tokens) {
if (!token) {
continue;
}
const k = token.indexOf(':');
const c = token.substring(0, k);
const p = BigInt(token.substring(k + 1));
if (c === "0") {
continue;
}
// 从系数右侧开始拆分,每个数字对应一个十进制数位
for (let i = c.length - 1; i >= 0; i--) {
const pos = p + BigInt(c.length - 1 - i);
digits.set(pos, (digits.get(pos) || 0) + Number(c[i]));
}
}
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const lines = [];
rl.on('line', (line) => {
lines.push(line);
});
rl.on('close', () => {
const line1 = lines[0] || "";
const line2 = lines[1] || "";
// 存储两个整数对应数位的数字之和
const digits = new Map();
parse(line1, digits);
parse(line2, digits);
// 结果中非零数位的位置及对应数字
const res = new Map();
let prev = -1n;
let carry = 0;
// 从低位向高位模拟竖式加法
const entries = Array.from(digits.entries())
.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
for (const [p, val] of entries) {
// 如果中间存在零位,进位只会传递到紧邻的下一位
if (prev !== -1n && p > prev + 1n && carry > 0) {
res.set(prev + 1n, carry);
carry = 0;
}
const sum = val + carry;
const digit = sum % 10;
carry = Math.floor(sum / 10);
if (digit !== 0) {
res.set(p, digit);
}
prev = p;
}
// 处理最高位之后剩余的进位
if (prev !== -1n && carry > 0) {
res.set(prev + 1n, carry);
}
if (res.size === 0) {
console.log("0:0");
return;
}
// 从高位向低位遍历,将连续非零数位每三个划分为一组
const v = Array.from(res.entries())
.sort((a, b) => a[0] > b[0] ? -1 : a[0] < b[0] ? 1 : 0);
const ans = [];
let c = "";
let last = -1n;
for (const [pos, digit] of v) {
// 遇到不连续的数位,或者当前组已经有三位,则结束当前片段
if (c.length > 0 && (last - pos !== 1n || c.length === 3)) {
ans.push(c + ":" + last.toString());
c = "";
}
c += String(digit);
last = pos;
}
if (c.length > 0) {
ans.push(c + ":" + last.toString());
}
// 输出编码,并在末尾添加结束标记
console.log(ans.join(" ") + " 0:0");
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
"strings"
)
// 解析编码,将每个非零数位累加到对应位置
func parse(line string, digits map[int64]int) {
tokens := strings.Fields(line)
for _, token := range tokens {
k := strings.Index(token, ":")
c := token[:k]
p, _ := strconv.ParseInt(token[k+1:], 10, 64)
if c == "0" {
continue
}
// 从系数右侧开始拆分,每个数字对应一个十进制数位
for i := len(c) - 1; i >= 0; i-- {
pos := p + int64(len(c)-1-i)
digits[pos] += int(c[i] - '0')
}
}
}
func main() {
reader := bufio.NewReader(os.Stdin)
line1, _ := reader.ReadString('\n')
line2, _ := reader.ReadString('\n')
line1 = strings.TrimSpace(line1)
line2 = strings.TrimSpace(line2)
// 存储两个整数对应数位的数字之和
digits := make(map[int64]int)
parse(line1, digits)
parse(line2, digits)
// 结果中非零数位的位置及对应数字
res := make(map[int64]int)
positions := make([]int64, 0, len(digits))
for p := range digits {
positions = append(positions, p)
}
sort.Slice(positions, func(i, j int) bool {
return positions[i] < positions[j]
})
prev := int64(-1)
carry := 0
// 从低位向高位模拟竖式加法
for _, p := range positions {
val := digits[p]
// 如果中间存在零位,进位只会传递到紧邻的下一位
if prev != -1 && p > prev+1 && carry > 0 {
res[prev+1] = carry
carry = 0
}
sum := val + carry
digit := sum % 10
carry = sum / 10
if digit != 0 {
res[p] = digit
}
prev = p
}
// 处理最高位之后剩余的进位
if prev != -1 && carry > 0 {
res[prev+1] = carry
}
if len(res) == 0 {
fmt.Println("0:0")
return
}
// 从高位向低位遍历,将连续非零数位每三个划分为一组
v := make([]int64, 0, len(res))
for p := range res {
v = append(v, p)
}
sort.Slice(v, func(i, j int) bool {
return v[i] > v[j]
})
ans := make([]string, 0)
c := ""
last := int64(-1)
for _, pos := range v {
digit := res[pos]
// 遇到不连续的数位,或者当前组已经有三位,则结束当前片段
if len(c) > 0 && (last-pos != 1 || len(c) == 3) {
ans = append(ans, c+":"+strconv.FormatInt(last, 10))
c = ""
}
c += strconv.Itoa(digit)
last = pos
}
if len(c) > 0 {
ans = append(ans, c+":"+strconv.FormatInt(last, 10))
}
// 输出编码,并在末尾添加结束标记
fmt.Println(strings.Join(ans, " ") + " 0:0")
}