题目来源
题目描述
题目解析
- 不能直接相除,因为无法区分循环小数。
- 将分数转成整数或者小数,做法是计算分子和分母相除的结果。可能的结果有三种:整数、小数、无限循环。
- 首先可以明确,两个数相除要么是「有限位小数」,要么是「无限循环小数」,而不可能是「无限不循环小数」。
- 问题是怎么区分[无限循环小数]与[有限位小数]呢?关键点在于循环,可以用unordered_map记录某个小数是否出现过,如果出现过,就说明出现了循环。
如下:
- 判断正负,利用分子分母相乘符号判断;
- 确定整数部分,判断有无小数部分;
- 看小数部分是否有重复:小数部分用map来存储每次的余数的位置,一旦重复,说明有循环,跳出,否则直到余数为0退出;
- 防止整型溢出,使用long,注意 INT_MIN
模拟
将分数转成整数或者小数,做法是计算分子和分母相除的结果。可能的结果有三种:整数、小数、无限循环。
- 如果分子可以被分母整除,那么结果是小数。将分子除以分母的商以字符串的形式返回即可。比如
2/1 = 2
- 如果分子不可以被分母整除,则结果是有限小数或无限循环小数,需要通过模拟长除法的方式计算结果。
关于长除法:
- 为了方便处理,首先根据分子和分母的正负决定结果的正负(注意此时分子和分母都不为 0),然后将分子和分母都转成正数,再计算长除法。
计算长除法时,首先计算结果的整数部分,将下面部分依次拼接到结果中:
- 如果结果是负数则将负号拼接到结果中,如果是正数则跳过这一步
- 将整数部分拼接到结果中
- 将小数点拼接到结构中
完成上面拼接之后,根据余数计算小数部分
- 计算小数部分时,每次将余数乘以 10,然后计算小数的下一位数字,并得到新的余数。重复上述操作直到余数变成 00 或者找到循环节。
- 如果余数变成 0,则结果是有限小数,将小数部分拼接到结果中。
- 如果找到循环节,则找到循环节的开始位置和结束位置并加上括号,然后将小数部分拼接到结果中。
- 如何判断是否找到循环节?注意到对于相同的余数,计算得到的小数的下一位数字一定是相同的,因此如果计算过程中发现某一位的余数在之前已经出现过,则为找到循环节。为了记录每个余数是否已经出现过,需要使用哈希表存储每个余数在小数部分第一次出现的下标。
#include <cstdlib>
#include <cstring>
#include <vector>
#include <iostream>
#include <map>
#include <unordered_map>
using namespace std;
class Solution {
public:
string fractionToDecimal(int numerator, int denominator) {
long a = numerator, b = denominator;
// 1. 如果本身能够整除,直接返回计算结果
if(a % b == 0){
return std::to_string(a / b);
}
//2. ----
std::string ans;
if(a * b < 0){ // 如果其一为负数,先追加负号
ans.push_back('-');
}
a = std::abs(a), b = std::abs(b);
// 整数部分
ans.append(std::to_string(a / b) + "."); // 计算小数点前的部分,并将余数赋值给 a
// 小数部分
std::map<int, int> map ;
a = a % b;
while (a != 0){
map.insert({a, ans.length()});
a = a * 10;
ans.append(std::to_string(a / b));
a = a % b;
if(map.count(a)){ // 如果当前余数之前出现过,则将 [出现位置 到 当前位置] 的部分抠出来(循环小数部分)
int u = map[a];
std::string t;
t.append(ans.substr(0, u ) + "(")
.append(ans.substr(u , ans.size() - u) + ")");
return t;
}
}
return ans;
}
};