HappyNumber [source code]

public class HappyNumber {
static

public class Solution {
    public boolean isHappy(int n) {
        int slow = n, fast = n;
        do {
            slow = getSquareSum(slow);
            fast = getSquareSum(fast);
            fast = getSquareSum(fast);
        } while (slow != fast);
        return slow == 1;
    }

    public int getSquareSum(int n) {
        int res = 0;
        while (n > 0) {
            int digit = n % 10;
            res += digit * digit;
            n /= 10;
        }
        return res;
    }
}

    public static void main(String[] args) {
        HappyNumber.Solution tester = new HappyNumber.Solution();
        System.out.println(tester.isHappy(Integer.parseInt(args[0])));
    }
}

这个题目的难点肯定是在于return false的情形, 关键是要怎么判断出来进入了死循环. 这个问题我想了半天最后还是一点思路都没有.
这里这个答案是 discussion 最优解, 是一个比较直观而且通用的解法, 这个是作者自己的解释:

I see the majority of those posts use hashset to record values. Actually, we can simply adapt the Floyd Cycle detection algorithm. I believe that many people have seen this in the Linked List Cycle detection problem.

最后的速度也是非常好(2(83), 几乎可以算是一个 submission 最优解了), space 更是只有O(1).
这个代码的本质我现在还不是完全理解, 不过是一个很通用的算法: 针对死循环检测;

不过感觉这里其实偷欢了一点概念: 这里做的其实不是一个通用的死循环检测, 而是一个cycle detection. 这两个严格来说并不是完全等价的问题. 事实上, 我就没有想到要用cycle detection来做这个题目.

在这个问题上, 我也尝试过用举例子的方法来找到思路, 但是我举例子的方式太粗犷了, 我直接就写了一个小函数, 然后从0到30的每一个数字都跑一下, 人工判断是否进入了死循环. 我当时这么做的原因是因为感觉这个题目可能是一个 pattern题. 当这个尝试失败了以后, 我直接就放弃了.

感觉这个思维习惯有问题, 我现在思考问题的时候找方向完全就是凭感觉, 脑子里冒出来什么就用什么. 脑子里要是什么都没有冒出来的时候就死机. 为了避免今后有这种情况, 建议每一个方向都在纸上写下来. 有点像backtracking search.

  • pattern 题?
  • 没思路? 举例总结简单升级找思路;

这样的做法.

这题你找几个 unhappy 的 number 算一算, 是可以发现这里的死循环是有 cycle 的(有些其他的死循环不一定就肯定是有 cycle, 比如说可能是发散, 越来越大). 然后就是怎么找 cycle 了.
这里给出的是一种方法, 另外一个思路就是 hashset, 这个更直接, 类似于之前看到的东南西北 string 题一样.

至于怎么理解这个 Floyd 算法? 还是 enumeration.

  • 如果是一个happy 的, 那么 fast 会先停在1, 然后 slow 会停在1, 然后循环结束;
  • 如果是一个 unhappy 的, fast 走的比较快, 最后肯定会赶上 slow(因为有 cycle), 这个时候也是停止循环, 但是这个时候的停止点就不是1了.

另外一个重要的问题, 这里证明有 cycle/loop 的过程我并没有证明, 只是通过举例子简单归纳了一下, discussion 上面有人给出了一个非常出色的证明:
Earlier posts gave the algorithm but did not explain why it is valid mathematically, and this is what this post is about: present a "short" mathematical proof.

First of all, it is easy to argue that starting from a number I, if some value - say a - appears again during the process after k steps, the initial number I cannot be a happy number. Because a will continuously become a after every k steps.

Therefore, as long as we can show that there is a loop after running the process continuously, the number is not a happy number.

There is another detail not clarified yet: For any non-happy number, will it definitely end up with a loop during the process? This is important, because it is possible for a non-happy number to follow the process endlessly while having no loop.

To show that a non-happy number will definitely generate a loop, we only need to show that for any non-happy number, all outcomes during the process are bounded by some large but finite integer N. If all outcomes can only be in a finite set (2,N], and since there are infinitely many outcomes for a non-happy number, there has to be at least one duplicate, meaning a loop!

Suppose after a couple of processes, we end up with a large outcome O1 with D digits where D is kind of large, say D>=4, i.e., O1 > 999 (If we cannot even reach such a large outcome, it means all outcomes are bounded by 999 ==> loop exists). We can easily see that after processing O1, the new outcome O2 can be at most 9^2D < 100D, meaning that O2 can have at most 2+d(D) digits, where d(D) is the number of digits D have. It is obvious that 2+d(D) < D. We can further argue that O1 is the maximum (or boundary) of all outcomes afterwards. This can be shown by contradictory: Suppose after some steps, we reach another large number O3 > O1. This means we process on some number W <= 999 that yields O3. However, this cannot happen because the outcome of W can be at most 9^23 < 300 < O1.

这个证明总体来说非常聪明和完整. 一个小的改动是: suppose O3 to be the first number that exceeds O1. 这样我们才能说W <= 999.


题外话, 刚开始没有想出来正确答案的时候, 用这个代码来举例子:

int res = n;  
while (res != 1) {  
    n = res;  
    res = 0;  
    while (n > 0) {  
        int digit = n % 10;  
        res += digit * digit;  
        n = n / 10;  
    }  
}

这个代码刚开始都不觉得很好写, 因为你第二个 iteration 的 beginning 要干的事情和最开始第一个 iteration 之前要干的事情是不一样的.
解决的办法就是 res 的 init 放在 loop 里面; 在 loop 外面让 res 记录 n, 然后在第一个iteration 之前开始进行正式的 init.
当然这个问题还有其他的解决思路, 比如可以用do while来解决(有点在于把第一个termination condition的判断放到了 iteration1的后面); 另外一个最直接的办法是把 while 的header 直接改成 true(也就是无视), 然后在 body 里面加 break 之类的出口. 达到的效果其实是类似的, 就是让第一次 condition 的判断在第一次执行 body 之后才发生.

基于这个代码, 我尝试写了一个算法:

public class Solution {  
    public boolean isHappy(int n) {  
        int res = n;  
        while (res > 10) {  
            n = res;  
            res = 0;  
            while (n > 0) {  
                int digit = n % 10;  
                res += digit * digit;  
                n = n / 10;  
            }  
        }  
        if (res == 1) return true;  
        return false;  
    }  
}

这个算法看起来是对的, 不过其实有问题, 7就出问题了. //看 ver2, 这个思路居然是对的, 而且是最优解;


Problem Description

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: 19 is a happy number

1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1
Credits:
Special thanks to @mithmatt and @ts for adding this problem and creating all test cases.

Difficulty:Easy
Category:Algorithms
Acceptance:40.36%
Contributor: LeetCode
Companies
uber airbnb twitter
Related Topics
hash table math
Similar Questions
Add Digits Ugly Number

results matching ""

    No results matching ""