# 本章的内容是树形dp,非常重要,熟练掌握后可以解决大部分二叉树问题。
# 以及大数据问题的补充点,以及之前学习过程中没有完成的二进制运算(感谢Golang)
class TreeNode(object):
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

    def __repr__(self):
        return str(self.value)


# 以上是本章所需的二叉树节点定义

# 求一颗二叉树的最大距离
# 解题思路:最大距离的可能性有三种:1.头节点不在最大距离内,最大距离由左树贡献; 2.头节点不在最大距离内,最大距离由右树贡献; 3.头节点在最大距离内,最大距离左树高度+右树高度+1贡献
def max_Distance(head):
    if head == None:
        return [0, 0]
    leftdata = max_Distance(head.left)
    rightdata = max_Distance(head.right)
    distance = max(leftdata[0], rightdata[0], leftdata[1] + rightdata[1] + 1)
    height = max(leftdata[1], rightdata[1]) + 1
    return [distance, height]


'''
    由上题引出,树形dp第一步:
        以某个节点为头节点的子树中,先分析有哪些可能性,分析尽可能的朝着向左右树要信息的思路去想
    第二步:
        根据第一步的分析。列出所有需要的信息
    第三步:
        合并第二步得到信息,对所有树提出同样的要求,找到return值
    第四步:
        设置basecase(多半是head==None)返回的值,得到并处理左右树的信息,返回处理后的信息
'''


# 员工有一个属性,快乐值;一个列表,表示下级。类似于多叉树
class Employee(object):
    def __init__(self, happy):
        self.happy = happy
        self.subordinates = []

    def add(self, *subordinates):
        for subordinate in subordinates:
            self.subordinates.append(subordinate)

    def __repr__(self):
        return str(self.happy)


# 现在公司要办party,可以决定那些员工来,哪些不来。
# 如果某个员工来了,他的下级都不能来
# 怎样让party的hapy最高
# 解题思路:
#     1.根节点来:
#             root+ a(a不来)+ b(b不来)+ c(c不来)
#     2.根节点不来
#             0+ a(max(来,不来))+ b(max(来,不来))+ c(max(来,不来))
def party_happy(root):
    if root.subordinates == []:
        return [root.happy, 0]
    come = root.happy
    uncome = 0
    for subordinate in root.subordinates:
        res = party_happy(subordinate)
        come += res[1]
        uncome += max(res[0], res[1])
    return [come, uncome]


# Morris遍历
# 一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1)通过利用原树中大量空闲指针的方式,达到节省空间的目的
# 用叶子节点指针移动的方法模拟递归的过程,
def morris(root):
    if root == None:
        return
    cur = root
    while cur != None:
        leftmostRight = cur.left
        # 利用左子树最右节点的右指针是否指向自己,来判断是否是第一次来到整个节点
        # 如果指向空,说明是第一次来到这个节点
        # 如果指向自己,说明是第二次
        if leftmostRight != None:
            while leftmostRight.right != None and leftmostRight.right != cur:
                leftmostRight = leftmostRight.right
            if leftmostRight.right == None:
                leftmostRight.right = cur
                cur = cur.left
                continue
            else:
                leftmostRight.right = None
        # 如果没有左树,往右走,如果自己是叶子节点,在之前就已经被和之前的某个节点连成环了
        cur = cur.right


def preorder_morris(root):
    if root == None:
        return
    cur = root
    while cur != None:
        leftmostRight = cur.left
        # 利用左子树最右节点的右指针是否指向自己,来判断是否是第一次来到整个节点
        # 如果指向空,说明是第一次来到这个节点
        # 如果指向自己,说明是第二次
        if leftmostRight != None:
            while leftmostRight.right != None and leftmostRight.right != cur:
                leftmostRight = leftmostRight.right
            if leftmostRight.right == None:
                # 说明是第一次来到cur,往cur左移动的同时,让leftmostRight和cur练成环,方便以后回到cur.
                leftmostRight.right = cur
                print(cur)
                cur = cur.left
                continue
            else:
                # 说明是第二次来到cur,说明左树都走完了.断开环,往右走
                leftmostRight.right = None
                cur = cur.right
        else:
            # 如果没有左树,往右走,如果自己是叶子节点,在之前就已经被和之前的某个节点连成环了
            # 如果没有左树,所以这是第一次来到
            print(cur)
            cur = cur.right


def inorder_morris(root):
    if root == None:
        return
    cur = root
    while cur != None:
        leftmostRight = cur.left
        # 利用左子树最右节点的右指针是否指向自己,来判断是否是第一次来到整个节点
        # 如果指向空,说明是第一次来到这个节点
        # 如果指向自己,说明是第二次
        if leftmostRight != None:
            while leftmostRight.right != None and leftmostRight.right != cur:
                leftmostRight = leftmostRight.right
            if leftmostRight.right == None:
                leftmostRight.right = cur
                cur = cur.left
                continue
            else:
                leftmostRight.right = None
        # 如果没有左树,往右走,如果自己是叶子节点,在之前就已经被和之前的某个节点连成环了
        print(cur)
        cur = cur.right


def postorder_morris(root):
    if root == None:
        return
    cur = root
    while cur != None:
        leftmostRight = cur.left
        # 利用左子树最右节点的右指针是否指向自己,来判断是否是第一次来到整个节点
        # 如果指向空,说明是第一次来到这个节点
        # 如果指向自己,说明是第二次
        if leftmostRight != None:
            while leftmostRight.right != None and leftmostRight.right != cur:
                leftmostRight = leftmostRight.right
            if leftmostRight.right == None:
                leftmostRight.right = cur
                cur = cur.left
                continue
            else:
                leftmostRight.right = None
                reversed(root.left)
        # 如果没有左树,往右走,如果自己是叶子节点,在之前就已经被和之前的某个节点连成环了
        cur = cur.right


def reversed(root):
    pre = None
    while root != None:
        thenext = root.right
        root.right = pre
        pre = root
        root = thenext
    tail = pre
    cur = tail
    while cur != None:
        print(cur)
        cur = cur.right
    pre = None
    while root != None:
        thenext = root.right
        root = pre
        pre = root
        root = thenext


'''
    二叉树遍历问题用Morris
    需要合并两个分支的用递归方法
    大数据问题补充
'''

# 大数据题目的解题技巧
# 1. 哈希函数可以把数据按照种类均匀分类
# 2. 布隆过滤器用于合计的建立和查询,并可以节省大量空间
# 3. 一致性哈希解决数据服务器的负载管理问题
# 4. 利用并查集结构解决岛问题的并行计算
# 5. 位图解决某一范围数字的出现情况
# 6. 利用分段统计思想、并进一步节省大量空间
# 7. 利用堆和外排序来做多个处理单元的结果合并


# 32位无符号整数(uint32)的范围是 0~4294967295,现在有一个正好包含40亿个无符号整数的文件,所以文件中一定有没有出现过的数.
# 使用最多1GB的内存,找到所有没出现的数?     放到位图中描黑: 2^32/8≈500M,每1比特代表一个数
# 如果只是用3KB内存,找到一个没出现的数?     把所有的空间尽可能的申请位int类型的数组,3kb可以申请700+的数组,去向下取最近的2的指数512, 2^32/512=8388608.
#                                       用arr统计词频,每一个单元代表把8388608范围,扫描一遍文件,必定至少有一个单元小于其他的,说明这个范围内的数比较少.
#                                       假设是arr[0]比较少,说明0~8388607的数比较少,那么这时候用arr统计0~8388607的数,arr[0]代表0~16383.找到较少的那个范围,用位图描黑统计出现情况,只需要16384bit=2KB


# 有一个包含100亿个URL的大文件,假设灭个URL占用64B,找出其中所有重复的URL
# 布隆过滤器: 每读取一个就标记,重复就记录
# 哈希函数: 用哈希函数分流,保证每中URL都能进出同一个组.再在每个组统计重复URL


# 设计一种可行方法在亿级数据求出Top100的词汇.
# 思路: 先把数据分流统计词频把放入一个大根堆中,再把每个小文件的堆顶拿出来再组成一个大根堆.
#       每次弹出一个词就从它的源文件中获取一个继续比较.就是不停的让每个小文件的最大词频比较


# 32位无符号整数(uint32)的范围是 0~4294967295,现在有一个正好包含40亿个无符号整数的文件
# 使用1GB的内存的内存,找出所有出现了两次的数:
#     1.哈希函数分流; 2. 用位图: 位图只能表示一个数是否出现过,但是可以用两位来表示更多的信息;
#                              因为我们只需要考虑0、1、2、更多次,一共四种情况,所以可以用00\01\10\11来表示.需要正好4294967296/8/2=1GB

# 使用10KB的内存,找到这40亿个整数的中位数:
#                              同理找没出现,10KB申请2560个数组,去向下取的2^11=2048. 2^32/2048=2097152,arr[0]代表0~209751数的词频.
#                              找到中位数所在的范围,再统计那个范围的词频。arr[]代表1024个数,找到中位数所在的那个范围,最后直接那个范围统计每个数的词频


# 有一个无序的10G的无序int文件,用有限的内存,把它变成有序的int文件。
# 解题思路:
#         方法一:
#           一条记录(value,出现的次数count),16btye.
#           把文件按照之前的方法分流,用所有的内存申请一个小根堆,每次值排序一个范围内的数
#           记录下来,直到把所有范围都排序完.
#         方法二:
#           用所有内存申请一个大根堆排序,每一轮排序完,把阈值设定为上一轮最大的数,周而复始



'''
    下面是位运算的一些方法,因为python变量类型变化过于诡异,使用的语言是Golang语言
'''


# 位运算

# 给定两个有符号的32位整数a和b,不做任何判断返回a和b中较大的
'''
func sign(n int32) int32 {
	// 取符号位,正数为0负数为1
	return ((n >> 31) & 1) ^ 1
}
func getmax(a, b int32) int32 {
	c := a - b

	sign_A := sign(a)
	sign_B := sign(b)
	sign_C := sign(c)
	// 如果a、b符号不一样,为1;一样为0
	diffSab := sign_A ^ sign_B
	// 如果a、b符号一样,为1;不一样为0
	sameSab := diffSab ^ 1
	// 当a、b符号不一样,且a是非负数,返回a
	// 当a、b符号一样,且a-b是非负数,返回a
	return_A := diffSab*sign_A + sameSab*sign_C
	// 否则返回b
	return_B := return_A ^ 1
	return a*return_A + b*return_B
}
func whoisbigest(a, b int32) string {
	who := getmax(a, b)
	switch who {
	case a:
		return fmt.Sprintf("A is the bigest one")
	case b:
		return fmt.Sprintf("B is the bigest one")
	default:
		return fmt.Sprintf("err")
	}
}
'''


# 判断一个数是不是2的幂、4的幂
# 2的幂就是只有一个1,4的幂的出了是2的幂以外,它的1还只在偶数位上
'''
func getrightone1(n uint64) uint64 {
	// n取反,那么n最右边的1就变成了n2最右的0,n2+1最右的0就变成了最右的1且其他位置肯定不一样,再和n做与运算就能得到最右的1了
	n2 := ^n
	n2 += 1
	return n2 & n
}
func isPowerofTwo1(n uint64) bool {
	// 如果n是2的幂,则n一定只有一个1,那么去掉了最右的那个1之后,n就变成0了
	switch getrightone1(n) - n {
	case 0:
		return true
	default:
		return false
	}

}
func isPowerofTwo2(n uint64) bool {
	// 如果n是2的幂,则n一定只有一个1,n+1就会打乱那个1,再和n做与运算,1的位置肯定不重合
	n2 := n - 1
	switch n & n2 {
	case 0:
		return true
	default:
		return false
	}
}
func isPowerofFour(n uint64) bool {
	// 在n是2的幂的前提下,如果n与上10101010101...不等于0,说明1在基数位上
	if isPowerofTwo2(n) && n&0x5555555555555555 != 0 {
		return true
	}
	return false
}
'''


# 位运算做到加减乘除法
# 异或运算(相同为1,不同为0)就是无进位相加的结果,与运算(同1为1)再左移一位就是进位信息
'''
func add(a, b int32) int32 {
	if b == 0 {
		return a
	}
	OR := a ^ b
	AND := (a & b) << 1
	res := add(OR, AND)
	return res
}
func getnegative(n int32) int32 {
	return add(^n, 1)
}
func sub(a, b int32) int32 {
	return add(a, getnegative(b))
}
func mul(a, b int32) int32 {
	var n int32
	if b>>31 != 0 {
		b = ^b + 1
		a = ^a + 1
	}
	for {
		if b == 0 {
			return n
		}
		// 如果左移一位再右移一位值不变,就不相加
		if b&1 == 1 {
			n = add(n, a)
		}
		a = a << 1
		b = b >> 1
	}
}
func div(a, b int32) int32 {
	var needed bool = false
	if b>>31 != 0 {
		if a>>31 != 0 {
			b = getnegative(b)
			a = getnegative(a)
		} else {
			b = getnegative(b)
			needed = true
		}
	} else if a>>31 != 0 {
		a = getnegative(a)
		needed = true
	}
	n := 32
	var res int32
	for {
		if n == -1 {
			if needed {
				res = ^res + 1
			}
			return res
		}
		if (a >> n) >= b {
			a = sub(a, (b << n))
			res = add(res, (1 << n))
		}
		n -= 1
	}
}
'''