Solution to Leetcode - Happy Number

As part of 30 days challenge during the Covid-19 pandemic, Leetcode published this problem on the second day:

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.

Explanation

Happy Number = repeated sum of squares of digits will end in 1 Non Happy Number = repeated sum of squares will lead in an infinite loop.

Thought Process for Solution

The main task is to figure out when to stop out of the infinite loop and conclude that a number is not happy. Once you can figure this out, the code is not very tricky.

Let’s look at a sequence of a unhappy number, 42:

    input   = 42
=> 16 + 4   = 20
=> 4 + 0    = 4
=> 16       = 16
=> 1 + 36   = 37
=> 9 + 49   = 56
=> 25 + 36  = 61
=> 36 + 1   = 37

It’s evident from 37 that it will start to loop again from the 37 we have already seen. This is important. We can now stop iterating because any number of time we try 37, it’s going to give same sequence 37 -> 56 -> 61 -> 37 -> 56 -> ....

From here, the solution is simple, keep a track of already seen numbers and when ever a new number is found, add it to this set. We are good till the numbers are unique, but if we find a number we have already seen, that means we have detected a loop.

Pseudo Code

seen = {}
while number not in already seen and number is not 1:
    find square of digits of number
    add it to seen

Code

public class HappyNumber {

    static boolean isHappy(int n) {
        if (n <= 0) {
            return false;
        }
        Set<Integer> alreadySeen = new TreeSet<>();

        int current = n;
        while (!alreadySeen.contains(current) && current != 1) {
            alreadySeen.add(current);
            current = squareOfDigits(current);

        }

        return current == 1;
    }

    static int squareOfDigits(int number) {
        int[] digits = intToDigits(number);
        return IntStream.of(digits).reduce(0, (a, b) -> a + b * b);
    }

    static int[] intToDigits(int number) {
        return String.valueOf(number).chars().map(it -> it - '0').toArray();
    }

}

Some test cases to validate

public class HappyNumberTest extends TestCase {

    public void testIntToDigits() {
        assertArrayEquals(new int[]{0}, HappyNumber.intToDigits(0));
        assertArrayEquals(new int[]{1}, HappyNumber.intToDigits(1));
        assertArrayEquals(new int[]{2}, HappyNumber.intToDigits(2));

        assertArrayEquals(new int[]{1, 0}, HappyNumber.intToDigits(10));
        assertArrayEquals(new int[]{2, 2}, HappyNumber.intToDigits(22));
        assertArrayEquals(new int[]{9, 9}, HappyNumber.intToDigits(99));

        assertArrayEquals(new int[]{1, 0, 0}, HappyNumber.intToDigits(100));
        assertArrayEquals(new int[]{1, 0, 1}, HappyNumber.intToDigits(101));
        assertArrayEquals(new int[]{5, 0, 0}, HappyNumber.intToDigits(500));

    }

    public void testSquareOfDigits() {
        assertEquals(0, HappyNumber.squareOfDigits(0));
        assertEquals(1, HappyNumber.squareOfDigits(1));
        assertEquals(9, HappyNumber.squareOfDigits(3));
        assertEquals(25, HappyNumber.squareOfDigits(5));

        assertEquals(29, HappyNumber.squareOfDigits(25));
        assertEquals(37, HappyNumber.squareOfDigits(16));
        assertEquals(145, HappyNumber.squareOfDigits(89));
        assertEquals(42, HappyNumber.squareOfDigits(145));

    }

    public void testIsHappy() {
        assertTrue(HappyNumber.isHappy(1));
        assertTrue(HappyNumber.isHappy(10));
        assertTrue(HappyNumber.isHappy(100));
        assertTrue(HappyNumber.isHappy(13));
        assertTrue(HappyNumber.isHappy(31));
        assertTrue(HappyNumber.isHappy(19));

        assertFalse(HappyNumber.isHappy(25));
        assertFalse(HappyNumber.isHappy(145));
        assertFalse(HappyNumber.isHappy(41));
        assertFalse(HappyNumber.isHappy(42));
        assertFalse(HappyNumber.isHappy(0));
        assertFalse(HappyNumber.isHappy(99));
        assertFalse(HappyNumber.isHappy(573));

    }
}

Conclusion

It’s an interesting problem with a very simple solution. Once you get the bright idea behind how to stop the loop, the solution becomes evident.