4
# Definition for a binary tree node.6
# def __init__(self, val=0, left=None, right=None):11
def minCameraHelper(self, root: Optional[TreeNode]) -> (int, int):14
# cam(era) is best score for valid tree with camera at root15
# uncam(era) is best score for valid tree without camera at root16
# uncov(ered) is best score for invalid tree, where the only invalidity (i.e. the only uncovered node) is the root node18
# Note: maxint (float("inf")) is used to signify situations that don't make sense or can't happen.19
# Anywhere there is a float("inf"), you can safely replace that with a 1 as 1 is as bad or worse than worst practical,20
# but I stick with maxint to highlight the nonsensical cases for the reader!22
if not root.left and not root.right:24
# Note: "Uncam" setting doesn't make much sense (a leaf with no parents can either have a camera or be uncovered,25
# but not covered with no camera)26
return 1, float("inf"), 029
left_cam, left_uncam, left_uncov = self.minCameraHelper(root.left)31
# base case: empty child32
# Need to prevent null nodes from providing coverage to parent, so set that cost to inf33
left_cam, left_uncam, left_uncov = float("inf"), 0, 036
right_cam, right_uncam, right_uncov = self.minCameraHelper(root.right)38
# base case: empty child39
# Need to prevent null nodes from providing coverage to parent, so set that cost to inf40
right_cam, right_uncam, right_uncov = float("inf"), 0, 042
# Get the possible combinations for each setting43
cam_poss = itertools.product(44
[left_cam, left_uncam, left_uncov], [right_cam, right_uncam, right_uncov]47
(left_cam, right_cam),48
(left_uncam, right_cam),49
(left_cam, right_uncam),51
uncov_poss = [(left_uncam, right_uncam)]53
# Compute costs for each setting54
cam = min([x + y for x, y in cam_poss]) + 155
uncam = min([x + y for x, y in uncam_poss])56
uncov = min([x + y for x, y in uncov_poss])58
return cam, uncam, uncov60
def minCameraCover(self, root: Optional[TreeNode]) -> int:61
cam, uncam, _ = self.minCameraHelper(root)62
return min(cam, uncam)