这道题目的逻辑是很简单的,难点是在于如何判断已经循环够了,该跳出循环了?首先,如果这个数是快乐数,最终会等于1,那么就可以直接跳出循环并返回了;但是,如果这个数不是快乐数,他就会无限循环,那还有什么规律吗?我发现,对于10以下的数,只有1和7是快乐数,其他都不是,那么这就意味着当我迭代发现当前数是小于10并且不是7和1时,就该结束循环返回false
了,这种解法我称之为规律法,可能并不是十分正统的思路。
那么有其他正统的思路吗?如果我能记住我之前判断过什么数字,当我又重新遇到这个数字的时候,那不就代表着存在死循环,可以返回false
了。实现方法自然而然就想到了哈希表。
既然说遇到某个数字会产生死循环,那么是不是特定的几个数字呢?我手写推算了几位数字发现,对于非快乐数而言最终都会掉进4, 16, 37, 58, 89, 145, 42, 20这个循环当中,那就意味着只要在迭代过程中的数是以上之一,那么他就不是快乐数了,这是基于记忆(哈希表)和规律的思路。
循环,相当于一个链表中拥有回路,在【LeetCode - Java】141. 环形链表 (简单)中介绍过一种高效的快慢指针算法,可以快速地进行判断,对于这一道题目是同样适用的。
public boolean isHappy(int n) { int sum; while (n != 1) { if (n < 10 && n != 7) return false; sum = 0; while (n != 0) { sum += (int) Math.pow(n % 10, 2); n /= 10; } n = sum; } return true; }
public boolean isHappy(int n) { HashSet<Integer> set = new HashSet<>(); int sum; while (!set.contains(n)) { set.add(n); sum = 0; while (n != 0) { sum += Math.pow(n % 10, 2); n /= 10; } if (sum == 1) return true; n = sum; } return false; }
public boolean isHappy(int n) { HashSet<Integer> set = new HashSet<>(Arrays.asList(4, 16, 37, 58, 89, 145, 42, 20)); while (!set.contains(n)) { if (n == 1) return true; int sum = 0; while (n != 0) { sum += Math.pow(n % 10, 2); n /= 10; } n = sum; } return false; }
public boolean isHappy(int n) { int slow = n; int fast = next(next(n)); while (slow != fast && fast != 1) { slow = next(slow); fast = next(next(fast)); } return fast == 1; } public int next(int n) { int sum = 0; while (n != 0) { // sum += Math.pow(n % 10, 2); sum += (n % 10) * (n % 10); n /= 10; } return sum; }
理论上,上述所有方法的时间复杂度都是线性复杂度,因此时间差异都不大,哈希表的时间消耗较大应该是哈希表的存储查询导致的。另外,一个很有意思的点是在快慢指针算法当中,我尝试自己使用乘法进行平方运算,结果发现比使用Math.pow
函数内存消耗更低。