题目:
Write an algorithm to determine if a number is "happy".
A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy numbers.
Example:
Input: 19 Output: true Explanation: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1
这道题的意思是,给定一个数字,让这个数字的每一位数平方求和,得到的新数再继续这么操作,最后看是否会得到1,如果是的话就是happy number,否则就不是。
这道题看到的时候还是很懵逼的,不知道怎么去让它判断,看了discussion总结了以下几种解法:
1. 神秘的数学特性
不停地计算,如果出现了4那么就肯定不快乐,而出现了1就是快乐。为什么出现4就不快乐呢?
4*4=16;1*1+6*6=37;3*3+7*7=58;5*5+8*8=89;8*8+9*9=145;1*1+4*4+5*5=42;4*4+2*2=20;2*2+0*0=4;无限循环。
具体大佬们是怎么发现这个规律的,以及它为什么是正确的,我就不追究了,总之得到这个规律就好,其实重点还是在后面几种做法上。
这个解法的时间复杂度我猜是O(n^2)因为有两个loop?空间复杂度O(1)。代码运行时间0ms,空间8.2M,都打败了100%:
class Solution {
public:
int digitSquareSum(int n) {
int result = 0;
while (n) {
result += (n % 10) * (n % 10);
n /= 10;
}
return result;
}
bool isHappy(int n) {
if (n <= 0) {
return false;
}
while (true) {
if (n == 1) {
return true;
}
else if (n == 4) {
return false;
}
n = digitSquareSum(n);
}
return true;
}
};
2022.11.8
这次是自己写出来了递归,递归的终止条件是if n < 4,因为3^2 = 9,所以只要大于3的都会会到只有一位数的情况(虽然这也只是直觉,我并没有严格的数学证明)。但是忘了4的这种情况。于是终止条件改成了if n <= 4,然后加了个if n ==4 return false就好了。就,脑筋急转弯了。
class Solution {
public boolean isHappy(int n) {
if (n == 4) {
return false;
} else if (n < 4) {
return n == 1;
}
int sum = 0;
while (n != 0) {
sum += Math.pow(n % 10, 2);
n /= 10;
}
return isHappy(sum);
}
}
2. 采用set存放已经出现过的数字平方之和
其实这个方法也需要数学证明,通过采用set来存放已经出现过的数字,如果出现了重复的数字,且这个重复的数字不是1,那么就不是happy number,算法的正确性待证明。实现出来代码的时间复杂度我猜还是O(n^2),运行时间4ms,66.16%,空间复杂度我猜是O(n),8.3M,76.92%:
class Solution {
public:
int digitSquareSum(int n) {
int result = 0;
while (n) {
result += (n % 10) * (n % 10);
n /= 10;
}
return result;
}
bool isHappy(int n) {
if (n <= 0) {
return false;
}
set<int> results;
while (true) {
int sum = digitSquareSum(n);
if (results.count(sum) != 0) {
return sum == 1;
}
else {
results.insert(sum);
n = sum;
}
}
return true;
}
};
2022.11.8
嗯,完全忘了还能用set来存,如果出现重复就说明进循环了。但是,也得base on循环就一定不快乐的理论基础吧。不管了。循环终止条件是如果set里出现了重复的数字,否则遇到1就return true,要么就最后return false。
class Solution {
public boolean isHappy(int n) {
Set<Integer> set = new HashSet<>();
while (!set.contains(n)) {
if (n == 1) {
return true;
}
set.add(n);
n = getSquareSum(n);
}
return false;
}
private int getSquareSum(int n) {
int sum = 0;
while (n != 0) {
sum += Math.pow(n % 10, 2);
n /= 10;
}
return sum;
}
}
3. 快慢指针
这个方法借鉴了判断链表是否存在环的技巧,但是它还是基于“经过数字平方之和计算后总会产生循环,如果循环的元素不是1就不是happy number”的这个定理之上的,算法的正确性证明是逃不掉的orz 在这里可以把这个循环看作是一个环,设置一个每次走两步的快指针和一个每次走一步的慢指针,最后两个指针总会相遇在环中。这道题中只需要判断两个指针相遇时它们指向的数字即可,如果是happy number,那么这个环应该永远是1(数学证明省略)。代码如下,运行时间4ms,66.16%,空间7.9M,100%:
class Solution {
public:
int digitSquareSum(int n) {
int result = 0;
while (n) {
result += (n % 10) * (n % 10);
n /= 10;
}
return result;
}
bool isHappy(int n) {
if (n <= 0) {
return false;
}
int fast = digitSquareSum(digitSquareSum(n));
int slow = digitSquareSum(n);
while (fast != slow) {
fast = digitSquareSum(digitSquareSum(fast));
slow = digitSquareSum(slow);
}
return fast == 1;
}
};
这道题的数学证明暂时不想看,留个坑以后填。
2022.11.8
就,还是没有想到快慢指针的做法,以及想到了也不知道怎么给它应用到linkedlist的方法上。最后看了答案还是debug了半天,主要是卡在了n = 1和n = 10这种情况,我的while条件是slow != fast,但是这两种情况都是slow == fast = 1,所以就不会进入循环,直接到外层的return,所以外层return的时候就return slow == 1就行。
class Solution {
public boolean isHappy(int n) {
int fast = getSquareSum(getSquareSum(n));
int slow = getSquareSum(n);
while (fast != slow) {
if (fast == 1) {
return true;
}
fast = getSquareSum(getSquareSum(fast));
slow = getSquareSum(slow);
}
return slow == 1;
}
private int getSquareSum(int n) {
int sum = 0;
while (n != 0) {
sum += Math.pow(n % 10, 2);
n /= 10;
}
return sum;
}
}