import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

/**
 * The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the
 * "root." Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that
 * "all houses in this place forms a binary tree". It will automatically contact the police if two directly-linked
 * houses were broken into on the same night.
 * <p>
 * Determine the maximum amount of money the thief can rob tonight without alerting the police.
 * <p>
 * Example 1:
 *     3
 *    / \
 *   2   3
 *    \   \
 *     3   1
 * Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.
 * Example 2:
 *      3
 *     / \
 *    4   5
 *   / \   \
 *  1   3   1
 * Maximum amount of money the thief can rob = 4 + 5 = 9.
 * <p>
 * Tags: Tree, Depth-first Search
 * Similar Problems: (E) House Robber, (M) House Robber II
 */
public class HouseRobber3 {

    private HouseRobber3 h;

    /**
     * If current house is robbed, then subtrees' roots cannot be robbed.
     * The maximum is
     *  root.val + rob(root.left.left) + rob(root.left.right) + rob(root.right.left) + rob(root.right.right)
     * If current house is robbed, then subtrees' roots can be robbed.
     * The maximum is
     *  rob(root.left) + rob(root.right)
     * The base case is where we don't need to calculate sub problems.
     * When root is null in our case. Just return 0.
     */
    public int rob(TreeNode root) {
        int[] res = dfs(root);
        return Math.max(res[0], res[1]);
    }

    /**
     * @return An array of two integers, the first one is maximum with current node; the second is without current node.
     */
    private int[] dfs(TreeNode node) {
        if (node == null) return new int[2]; // Base case
        int[] res = new int[2];
        int[] left = dfs(node.left);
        int[] right = dfs(node.right);
        res[0] = node.val + left[1] + right[1];
        res[1] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        return res;
    }

    @Before
    public void setUp() {
        h = new HouseRobber3();
    }

    @Test
    public void testExamples() {
        TreeNode root = new TreeNode(3);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.right = new TreeNode(3);
        root.right.right = new TreeNode(1);
        Assert.assertEquals(7, h.rob(root));

        root = new TreeNode(3);
        root.left = new TreeNode(4);
        root.right = new TreeNode(5);
        root.left.left = new TreeNode(1);
        root.left.right = new TreeNode(3);
        root.right.right = new TreeNode(1);
        Assert.assertEquals(9, h.rob(root));

        root = new TreeNode(4);
        root.left = new TreeNode(1);
        root.left.left = new TreeNode(2);
        root.left.left.left = new TreeNode(3);
        Assert.assertEquals(7, h.rob(root));
    }

    @After
    public void tearDown() {
        h = null;
    }

    public class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int x) { val = x; }
    }

}

add House Robber 3