翻译
给定两个整数,用于表示一个分数的分子和分母,以字符串格式返回这个分数。
如果分数部分是重复的,将重复的部分用括号括起来。
例如,
给定numerator = 1,denominator = 2,返回“0.5”。
给定numerator = 2,denominator = 1,返回“2”。
给定numerator = 2,denominator = 3,返回“0.(6)”。
原文
Given two integers representing the numerator and denominator of a fraction,
return the fraction in string format.
If the fractional part is repeating, enclose the repeating part in parentheses.
For example,
Given numerator = 1, denominator = 2, return "0.5".
Given numerator = 2, denominator = 1, return "2".
Given numerator = 2, denominator = 3, return "0.(6)".
分析
首先排除2种情况:
- 分子为0,返回“0”。
- 分母为0,返回空字符串。
接着,来考虑3种情况:
- 可以整除,这样就可以直接转换成字符串。
- 不可以整除,但小数点后不是循环小数,这也同样可以直接转换成字符串。
- 不可以整除,且是循环小数,这就比较尴尬了,下面来详细探讨。
在探讨之前,我们先来看看这个运算过程。
4/2:直接得出2,并且不必加上小数点。
6/5:首先计算出整数部分为1,同时得到余数为1;得到整数后,化成字符串并加上小数点字符;余数乘以10,再除以5,得到整数部分为2;将上一步得到的2加到字符串后面,如果还有余数则重复上一步,没有则直接返回。
2/3:首先计算出整数部分为0,同时得到余数为2;得到整数后,化成字符串并加上小数点字符;余数乘以10,再除以3,得到整数部分为6,同时得到余数为2;将上一步得到的6加到字符串后面,继续将余数2乘以10,再除以3,得到整数部分为6……
那么问题来了,到底该如何判断是否有循环呢?
我们可以通过哈希表,将每个出现的余数都放到哈希表中,如果后面没有重复出现,那么由于一直在进行辗转除法,总归是很快会除尽的。
但是如果重复出现了,那就说明循环了。
但是也许会有读者有疑问,比如0.2525,分子是2525,分母是10000。看似这里重复了2525,但是其实运算过程中的余数分别是:2525、 5250 、 2500、 5000。所以说小数点后的重复与运算过程中的余数的重复是没有必然关系的。
相反,如果是2/3,运算中的余数则一直是6,因为2乘以10再除以3等于6,余下2;进一步还是2乘以10再除以3……
Ok,这个疑问解决了,但还有一个问题,虽然题目没有提到负数的问题,但显然为了严谨我们还是要考虑的。不过这也不难对吧,只要一开始都求出绝对值,最后根据原分子和原分母的正负性给可以判断要不要在最后得出的字符串前面加上负号。所以综上,得出代码如下……
#include <iostream>
#include <string>
#include <unordered_map>
#include <limits.h>
#include <stdlib.h>
using namespace std;
string fractionToDecimal(int numerator, int denominator) {
string decimal = "";
if (numerator == 0) return "0";
if (denominator == 0) return decimal;
long long num = abs(numerator);
long long den = abs(denominator);
long long rest = 0;
unordered_map<long long, int> restMap;
if (num % den == 0)
decimal = to_string(num / den);
else
decimal = to_string(num / den) + '.';
int index = decimal.length();
rest = num % den;
while (rest != 0 && restMap.find(rest) == restMap.end()) {
restMap[rest] = index++;
rest *= 10;
decimal += to_string(rest / den);
rest = rest % den;
}
if (rest != 0) {
decimal.insert(restMap[rest], 1, '(');
decimal.insert(decimal.length(), 1, ')');
}
if ((numerator < 0 && denominator < 0) || (
numerator > 0 && denominator > 0))
return decimal;
else
return "-" + decimal;
}
然而,提交到LeetCode上,还是出错了。
Input: -1
-2147483648
Output: "0.000000000-4-6-5-6-6-1-2-8-7-30-7-7-3-9-2-5-7-8-1-2-5"
Expected: "0.0000000004656612873077392578125"
这……是为什么呢?而且负号只在最后才出现一次,为什么结果中会有这么多次呢,唯一的解释是中间的负数到正数的转变有问题。
首先,由于我是在64位的操作系统,所以INT的范围是:
-2147483648 ~ 2147483647
由此可以看出来,对-2147483648,不论是直接取负,还是取绝对值,都会得到2147483648,但因为溢出了,所以最后还是回到了-2147483648。
不信的话,可以试试下面这个例子。
#include <iostream>
#include <limits.h>
#include <stdlib.h>
using namespace std;
int main()
{
int a = -2147483648;
cout << abs(a) << endl;
cout << -a << endl;
long long b = a;
cout << abs(b) << endl;
cout << -b << endl;
return 0;
}
那么这个问题也解决了,将上面的代码也相应的改成下面这个顺序。
long long num = numerator;
long long den = denominator;
num = abs(num);
den = abs(den);
然而这段代码我怎么看怎么不爽……改改改……
long long num = abs((long long)numerator);
long long den = abs((long long)denominator);
Ok,终于昨晚这道题,也写完这篇博客了。
代码
class Solution {
public:
string fractionToDecimal(int numerator, int denominator) {
string decimal = "";
if (numerator == 0) return "0";
if (denominator == 0) return decimal;
long long num = abs((long long)numerator);
long long den = abs((long long)denominator);
long long rest = 0;
unordered_map<long long, int> restMap;
if (num % den == 0)
decimal = to_string(num / den);
else
decimal = to_string(num / den) + '.';
int index = decimal.length();
rest = num % den;
while (rest != 0 && restMap.find(rest) == restMap.end()) {
restMap[rest] = index++;
rest *= 10;
decimal += to_string(rest / den);
rest = rest % den;
}
if (rest != 0) {
decimal.insert(restMap[rest], 1, '(');
decimal.insert(decimal.length(), 1, ')');
}
if ((numerator < 0 && denominator < 0) || (
numerator > 0 && denominator > 0))
return decimal;
else
return "-" + decimal;
}
};