Python LeetCode(九)

不会做

1. Path Sum

class Solution(object):
    def hasPathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: bool
        """
        if root is None:
            return False
        if root.left is None and root.right is None and sum == root.val:
            return True
        return self.hasPathSum(root.left, sum-root.val) or self.hasPathSum(root.right, sum-root.val)
class Solution(object):
    def hasPathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: bool
        """
        if root:
            diff = sum - root.val
            if root.left or root.right:
                left = self.hasPathSum(root.left, diff)
                right = self.hasPathSum(root.right, diff)
                return left or right
            return diff == 0
        return False
class Solution(object):
    def hasPathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: bool
        """
        if not root: return False
        #children = [c for c in (root.left, root.right) if c]
        for c in (root.left, root.right):
            if c and self.hasPathSum(c, sum - root.val):
                return True
        return not (root.left or root.right) and sum == root.val

其原理在于判断和是否出现在字典中。

class Solution(object):
    def hasPathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: bool
        """
        def DFS(node, current_sum, sum_dict):
            current_sum += node.val
            # if this is a leaf node:
            if not node.left and not node.right:
                sum_dict[current_sum] = 1
            if node.left:
                DFS(node.left, current_sum, sum_dict)
            if node.right:
                DFS(node.right, current_sum, sum_dict)
            current_sum -= node.val
        if not root:
            return False
        sum_dict = dict()
        DFS(root, 0, sum_dict)
        if sum in sum_dict:
            return True
        return False

2. Find Duplicate Subtrees

我一开始的想法是选中其中的一个结点的路径,接着再选中下一个结点的路径进行比较,同时还有考虑到是一颗子树,也就是说它要到达叶子结点才行,于是我就不知道怎么做了。。。

其实原理很简单,对一个结点用一种唯一的字符串来标识,将其作为哈希表的关键字,保存该结点到哈希表中,这样的话我们就只需要统计任何一个关键字中对应的结点个数大于等于2的,说明存在至少2个结点以上的重复子树。这里需要注意的是,标识符需要保证其唯一性,所以不要使用中序表示法,为什么呢?比如下面这一张图:

假设我们使用中序表示法来表示结点的路径,统计出每个结点的路径为下图:

我们可以发现出,第2个的结点和第5个结点的标识符是一样的!但是显然它们的子结点一个在左边,一个在右边,显然是不重复的两个子树,因此不要拿中序表示法来唯一标示结点路径。一般使用后序作为标识符,如果可以的话,继续对该字符串进行更复杂的序列化。

import collections
class Solution(object):
    def findDuplicateSubtrees(self, root):
        """
        :type root: TreeNode
        :rtype: List[TreeNode]
        """
        def merkle(node):
            if not node:
                return '#'
            left = merkle(node.left)
            right = merkle(node.right)
            index = left  + right + str(node.val)
            count[index].append(node)
            return index
        count = collections.defaultdict(list)
        merkle(root)
        return [nodes[0] for nodes in count.values() if nodes[1:]]

对计算的后序表示法进行hash,进一步保证了其唯一性。

import collections
class Solution(object):
    def findDuplicateSubtrees(self, root):
        """
        :type root: TreeNode
        :rtype: List[TreeNode]
        """
        from hashlib import sha256
        def hash_(x):
            s = sha256()
            s.update(x)
            return s.hexdigest()
        def merkle(node):
            if not node:
                return '#'
            left = merkle(node.left)
            right = merkle(node.right)
            index = hash_(left + str(node.val) + right)
            count[index].append(node)
            return index
        count = collections.defaultdict(list)
        merkle(root)
        return [nodes[0] for nodes in count.values() if nodes[1:]]

3. Copy List with Random Pointer

题意为复制一个复杂链表,即

我想大多数人的想法就是一个一个来复制,先整体复制一遍,接着再复制其中结点的连接所有任意结点,显然这需要两层循环,试想如果每个结点都连接其它的所有结点,那么它的时间复杂度就是n*(n-1),但其实,我们可以在n的复杂度下解决这个问题。

复制

在原来的链表的基础之上进行复制,根据链表上的每一个结点创建对应的复制结点,并且将该复制结点的位置放置在结点的后面,接下来处理复制结点的subling,最后将复制结点单独拆分出来即可,一共需要三个步骤,这其实就是一种用空间换取时间的方法:

class Solution(object):
    def copyRandomList(self, head):
        """
        :type head: RandomListNode
        :rtype: RandomListNode
        """
        def cloneNodes(head):
            if head:
                # new node sits next to old node
                cur = head
                while cur:
                    new_cur = RandomListNode(cur.label)
                    new_cur.next = cur.next
                    cur.next     = new_cur
                    cur          = new_cur.next
            return head
        def setRandoms(head):
            if head:
                # new node's random is old node's random's next
                prev = head
                cur  = head.next
                while cur:
                    cur.random = prev.random.next if prev.random else None 
                    prev       = cur.next
                    cur        = prev.next if prev else None
            return head
        def extractClone(head):
            if head:
                # split two lists
                oldhead = head
                newhead = head.next
                curold = head
                curnew = newhead
                while curold and curnew:
                    curold.next = curnew.next
                    curnew.next = curnew.next.next if curnew.next else None
                    curold  = curold.next
                    curnew  = curnew.next
                return (oldhead,newhead)
            return (None,None)
        head = cloneNodes(head)
        head = setRandoms(head)
        head,newhead = extractClone(head)
        return newhead

递归

同样使用了字典,用空间换取时间,在设置next的过程中同时创建了其它的结点,就是一个DFS的过程。

class Solution(object):
    mp = dict()
    def copyRandomList(self, head):
        """
        :type head: RandomListNode
        :rtype: RandomListNode
        """
        if head is None:
            return None
        if head in self.mp:
            return self.mp[head]
        self.mp[head] = RandomListNode(head.label)
        self.mp[head].next = self.copyRandomList(head.next)
        self.mp[head].random = self.copyRandomList(head.random)
        return self.mp[head]

哈希

使用字典纪录下来每个结点,再处理每个结点的连接的任意结点,使用字典的好处在于可以在O(1)的时间内获取到对应的结点,不需要再遍历来找,这也是一种用空间换取时间的方法。

class Solution(object):
    def copyRandomList(self, head):
        """
        :type head: RandomListNode
        :rtype: RandomListNode
        """
        dic = dict()
        m = n = head
        while m:
            dic[m] = RandomListNode(m.label)
            m = m.next
        dic[None] = None
        while n:
            dic[n].next = dic[n.next]
            dic[n].random = dic[n.random]
            n = n.next
        return dic[head]

4. Valid Triangle Number

这道题我一开始是利用了Three Sum中的思想,就是先选中一个元素,剩下两个元素根据移动数组的下标来找到,但是有个很大的问题在于-你不知道在什么时候左移还是右移?不像是在之前的题目中能够清楚的判断,这里左移和右移在某些情况都是可以的。

数学思想

在已经排了序的情况下,可以考虑一整个区间的元素的情况,比如说存在数组[2,3,4,5],当前选中数字5,考虑在2到4之间选择两个元素,我们自己计算的话,其中2,4,5和3,4,5可以满足三角形的条件,发现了没有,此时4和5是固定的,移动的是left下标,我们就可以直接用right-left得到这个区间的个数,也就是符合三角形的个数,这是为什么呢?因为三角形的条件为任意两条边之和大于第三边,之差小于第三边,那么如果相较于当前值都小的left和right之和大于了当前值,此时right和left之差也必然小于当前值,所以符合三角形条件,既然left和right之和都大于了当前值,那么之后的区间同样符合前面的条件,所以其区间的值的个数就是我们求的解。

class Solution(object):
    def triangleNumber(self, nums):
        ans = 0
        nums.sort()
        for i in range(2, len(nums)):
            start = 0
            end = i - 1
            while start < end:
                if nums[start] + nums[end] > nums[i]:
                    ans += end - start
                    end -= 1
                else:
                    start += 1
        return ans

确定两个+二分

选确定两个元素,接下来的元素通过二分查找找到最小的比前面两个元素之和要大的元素。

class Solution(object):
    def triangleNumber(self, nums):
        ans = 0
        nums.sort()
        l = len(nums)
        for i in range(l):
            for j in range(i+1, l):
                s = nums[i] + nums[j]; left, right = j + 1, l
                while left < right:
                    mid = int((left + right) / 2)
                    if nums[mid] < s:
                        left = mid+1
                    else:
                        right = mid
                ans += right - j - 1
        return ans

ans += right - j - 1和前面的意思是类似的,因为计算出来的right是比已经确定的两个元素的和要大的最小的值,那么在right之前的都可以满足条件。

???

class Solution(object):
    def triangleNumber(self, nums):
        counter = collections.Counter(nums)  # AC
        counter.pop(0, None)
        uniq = sorted(counter.keys())
        cnt = [0]
        # 计算个数
        for x in uniq:
            cnt.append(cnt[-1] + counter[x])
        ans = 0
        for j, v in enumerate(uniq):
            k = len(uniq) - 1
            i = j
            while 0 <= i <= j <= k:
                while k > j and uniq[i] + uniq[j] <= uniq[k]:
                    k -= 1
                if i < j:
                    ans += counter[uniq[i]] * counter[uniq[j]] * (cnt[k + 1] - cnt[j + 1])
                    ans += counter[uniq[i]] * counter[uniq[j]] * (counter[uniq[j]] - 1) / 2
                else:
                    ans += counter[uniq[i]] * (counter[uniq[i]] - 1) / 2 * (cnt[k + 1] - cnt[j + 1])
                    ans += counter[uniq[i]] * (counter[uniq[i]] - 1) * (counter[uniq[i]] - 2) / 6
                i -= 1
        return ans

优化一下

1. 3Sum

这道题虽然简单,但是它很经典,经常被面试官提到,而且它的解法多种多样,适合考验人。

数组两边移动

class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        res = []
        nums.sort()
        for i in xrange(len(nums)-2):
            if nums[i] > 0 or (i > 0 and nums[i] == nums[i-1]):
                continue
            l, r = i+1, len(nums)-1
            while l < r:
                s = nums[i] + nums[l] + nums[r]
                if s < 0:
                    l +=1 
                elif s > 0:
                    r -= 1
                else:
                    res.append((nums[i], nums[l], nums[r]))
                    while l < r and nums[l] == nums[l+1]:
                        l += 1
                    while l < r and nums[r] == nums[r-1]:
                        r -= 1
                    l += 1; r -= 1
        return res

上面移动数组两边下标的做法可以单独封装成一个函数,如下:

class Solution(object):
    def towsum(self, nums, i, j, target):
        res = []
        while i < j:
            s = nums[i] + nums[j]
            if s < target:
                i += 1
            elif s > target:
                j -= 1
            else:
                res.append([nums[i], nums[j]])
                while i < j and nums[i] == nums[i+1]:
                    i += 1
                while i < j and nums[j] == nums[j-1]:
                    j -= 1
                i += 1
                j -= 1
        return res
    def threeSum(self, nums):
        nums.sort()
        length = len(nums)
        res = []
        for i, n in enumerate(nums):
            if n > 0: return res  # 如果第一个数都大于0,后面不再考虑
            if i == 0 or nums[i] != nums[i-1]:
                r = self.towsum(nums, i+1, length-1, -n)
                if r:
                    for x in r:
                        res.append([n] + x)
        return res

统计正负数

import collections
class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        res = []
        counter = collections.defaultdict(int)
        for x in nums:
            counter[x] += 1
        uniq = counter.keys() # 不重复的值
        pos = sorted(x for x in uniq if x >= 0)  # 正数
        neg = sorted(x for x in uniq if x < 0)  # 负数
        if 0 in counter and counter[0] >= 3:
            res.append([0]*3)
        for p in pos:
            for n in neg:
                r = -(p+n)
                if r in counter:
                    if (r == p or r == n) and counter[r] > 1:
                        res.append([n, r, p])  # r相等,放在中间
                    elif r < n:
                        res.append([r, n, p])  # r最小
                    elif r > p:
                        res.append([n, p, r])  # r最大
        return res

2. Maximum Average Subarray I

很简单,但是一开始我的想法就是移动窗口,remove掉前面那个,insert最新的,显然这两个操作会花费很大的时间,如下,TLE。

class Solution(object):
    def findMaxAverage(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: float
        """
        length = len(nums)
        temp = nums[:k]
        max_val = sum(temp)
        for i in xrange(k, length):
            temp.pop(0)
            temp.append(nums[i])
            if sum(temp) > max_val:
                max_val = sum(temp)
        return max_val/float(k)

我们可以计算差值即可,就是最新加入的和删除掉的值的差值,加到总的和中即可,如下,AC

class Solution(object):
    def findMaxAverage(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: float
        """
        s = sum(nums[:k])
        m = s
        for i in range(k, len(nums)):
            s += nums[i] - nums[i-k]
            m = max(m, s)
        return m / float(k)

也可以计算累积的和,然后减去前面的和,得到区间和,取最大值即可。

class Solution(object):
    def findMaxAverage(self, nums, k):
        s = [0]
        for x in nums:
            s.append(s[-1]+x)
        ma = max(s[i+k] - s[i] for i in range(len(nums)-k+1))
        return ma / float(k)
posted @ 2017-08-05 15:51  banananana  阅读(228)  评论(0)    收藏  举报