首页 > 代码库 > Python LeetCode(七)

Python LeetCode(七)

不会做

1. Search in Rotated Sorted Array

这道题的目的不在于解决它,而是如何更快的解决,这要求更低的时间复杂度,因此任何时间复杂度为n的方案应该都要被否决掉,只有n/2的解决方案才能被留下来。

这题不能简单的用二分查找的方式来做,因为它并不知道那个旋转的点在哪,因此并不知道应该向哪个方向移动,所以我们要确定target和mid是哪个方向的,像在二分查找中,如果 mid > target,则会将 right 移动到 mid 的位置,但其实不对,因为你并不知道此时mid是在哪个方向上,如果mid和target都是在右边,那你移动以后区间变小,再取mid,此时mid可能是在左边,mid肯定大于target,再把right移动到mid,此时区间则在左边了,就永远也找不到我们想要的值了。

所以我们要知道mid和target它们是否在同一个方向上,同个方向可以使用二分查找,但是如果两个方向则要移动到同一个方向上。如果都在右边

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        if nums ==[]:
            return -1
        left, right = 0, len(nums)-1
        infinity = 2**31
        mid = int(round((left+right)/2))
        while left < right:
            mid = int(round((left+right)/2))
            # 如果为true,则target和mid都在右边,否则都在左边
            if (nums[0] > target) == (nums[0] > nums[mid]):
                number = nums[mid] # 同个方向,按照二分来
            elif nums[0] > target: # target在右边,mid在左边
                number = -infinity # 保证left向右移动
            else: # target在左边,mid在右边
                number = infinity # 保证right向左移动
            if number < target:
                left = mid+1
            else:
                right = mid
            number = 0
        if nums != [] and nums[left] == target:
            return left
        else:
            return -1

总之就是要保证是在同一个方向上才能使用二分。

class Solution(object):
    def search(self, nums, target):
        if not nums: return -1
        lo, hi = 0, len(nums) - 1
        while lo < hi:
            mid = (lo + hi) / 2 #python2中返回的是int,但是python3中则会返回float
            if nums[mid] == target: return mid
            if nums[lo] <= nums[mid]: # sorted order
                if target>=nums[lo] and target<nums[mid]:
                    hi = mid - 1
                else: lo = mid + 1
            else: # drop between low, high
                if target>nums[mid] and target<=nums[hi]:
                    lo = mid + 1
                else: hi = mid - 1
        return lo if nums[lo] == target else -1

我们还可以使用python中的biset模块,通过biset去找到target的位置(也就是插入的位置)。

from bisect import bisect_left
def binary_search(nums, target):
    pos = bisect_left(nums, target)
    if nums[min(pos, len(nums) - 1)] == target:
        return pos
    else:
        return -1
class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        # Empty array
        if not nums:
            return -1
        # Size 1
        if len(nums) == 1:
            return 0 if nums[0] == target else -1
        # Array is sorted
        if nums[-1] > nums[0]:
            return binary_search(nums, target)        
        # Search left
        m = len(nums) // 2
        pos = self.search(nums[:m], target)
        if pos > -1:
            return pos
        # Search right
        pos = self.search(nums[m:], target)
        if pos == -1:
            return pos
        else:
            return pos + m

2. Search in Rotated Sorted Array II

这道题是上面一道题目的升级版本,允许数组中的元素相同,而之前的根据 mid >= left 来判断区间[left, mid]是否有序已经不行了,因为mid的值可能是相同的元素,比如[3,4,3,3,3]。因此要分成两部分,一个是mid > left ,一个是 mid == left,如果是 mid == left,则让 left+=1。

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: bool
        """
        left, right = 0, len(nums)-1
        mid = left
        while left <= right:
            mid = int(round((left + right)/2))
            if nums[mid] == target:
                return True
            if nums[mid] > nums[left]: # 左边部分有序
                if nums[left] <= target < nums[mid]: # target 在左边
                    right = mid-1
                else:
                    left = mid+1
            elif nums[mid] < nums[left]: # 右边部分有序
                if nums[mid] < target <= nums[right]: # target 在右边
                    left = mid+1
                else:
                    right = mid-1
            elif nums[mid] == nums[left]: # 特殊情况
                left+=1
        return False

发现一个很有意思的事情,不知道为什么如果从后面right往left计算,能够beat 98%,但是从left往right计算,只能beat 6%,我猜可能LeetCode的测试用例里面旋转的长度(右边)并不长,相反左边太长。不过从右边开始计算也是一样的原理。

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: bool
        """
        left, right = 0, len(nums)-1
        mid = left
        while left <= right:
            mid = int(round((left + right)/2))
            if nums[mid] == target:
                return True
            if nums[mid] > nums[right]: # 左边部分有序
                if nums[left] <= target < nums[mid]: # target 在左边
                    right = mid-1
                else:
                    left = mid+1
            elif nums[mid] < nums[right]: # 右边部分有序
                if nums[mid] < target <= nums[right]: # target 在右边
                    left = mid+1
                else:
                    right = mid-1
            elif nums[mid] == nums[right]: # 特殊情况
                right-=1
        return False

同样的原理,如果是已经拍好了序,并且target属于那个部分的,则使用二分,否则继续移动,不过显然递归比上面迭代的时间复杂度要高一些。

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: bool
        """
        if not nums or target is None:
            return False
        return self.helper(nums, 0, len(nums) - 1, target)
    def helper(self, nums, left, right, target):
        # base case
        if left > right:
            return False
        mid = left + (right - left) / 2
        if nums[mid] == target:
            return True
        if nums[mid] > nums[left]:
            # means the left part is sorted.
            if nums[left] <= target < nums[mid]:
                return self.binary_search(nums, left, mid - 1, target)
            # recursion on right part.
            return self.helper(nums, mid + 1, right, target)
        elif nums[mid] < nums[left]:
            # means the right part is sorted.
            if nums[mid] < target <= nums[right]:
                return self.binary_search(nums, mid + 1, right, target)
            # recursion on left part.
            return self.helper(nums, left, mid - 1, target)
        else:
            # recursion on both part.
            return self.helper(nums, left, mid - 1, target) or                   self.helper(nums, mid + 1, right, target)
    def binary_search(self, nums, left, right, target):
        while left <= right:
            mid = left + (right - left) / 2
            if nums[mid] == target:
                return True
            elif nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        return False

3. Wildcard Matching

这道题目听说是很难的一道题,我一开始的想法就是使用递归,但是难点在于如何处理*,因为这个星号可以代表0到多个字符,而且有可能会遇到递归一开始匹配正确后面不正确,但实际上应该从后面开始匹配,这就需要考虑到各种情况,得用DFS。

class Solution(object):
    # p为匹配模式,s为字符串
    def recursive(self, s, p, si, pi, cur):
        first = True
        n_cur = cur
        while si < len(s) and pi < len(p) and (s[si] == p[pi] or p[pi] == ‘?‘):
            si += 1
            pi += 1
        if pi == len(p):
            return si == len(s)
        if p[pi] == ‘*‘:
            while pi < len(p) and p[pi] == ‘*‘:
                pi += 1
            if pi >= len(p):
                return True
            for i in range(si, len(s)):
                # 表明开始重合,从这里再度开始递归
                if p[pi] != s[i] and p[pi] != ‘?‘:
                    continue
                if first:
                    cur += 1
                    first = False
                # 可能存在多次重合但是还不算真正匹配的情况
                if self.recursive(s, p, i, pi, cur + 1):
                    return True
                if cur > n_cur + 1: # 正常来说n_cur = cur + 1
                    return False
        return False
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        return self.recursive(s, p, 0, 0, 0)
class Solution(object):
    # p为匹配模式,s为字符串
    def recursive(self, s, p, si, pi):
        while si < len(s) and pi < len(p) and (s[si] == p[pi] or p[pi] == ‘?‘):
            si += 1
            pi += 1
        if pi == len(p):
            return si == len(s)
        if p[pi] == ‘*‘:
            while pi < len(p) and p[pi] == ‘*‘:
                pi += 1
            if pi >= len(p):
                return True
            for i in range(si, len(s)):
                # 表明开始重合,从这里再度开始递归
                if p[pi] != s[i] and p[pi] != ‘?‘:
                    continue
                # 可能存在多次重合但是还不算真正匹配的情况
                if self.recursive(s, p, i, pi):
                    return True
        return False
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        return self.recursive(s, p, 0, 0)

前面那个解法是我从网上看到的,但是我觉得那个cur并没有用,所以我在下面的解法中把它给删除掉,执行结果都是TLE。

这种题目还可以使用DP来做。

我们定义一个二维数组dp,横坐标为待匹配字符串,纵坐标为模式字符串,dp[i][j]则代表到模式字符串从0到 i 对应待匹配字符串的的0到 j 是否是匹配的。举个例子:

pattern = "a*bc"
str = "abbc"

我们可以根据前面提到的画出整个二维数组

\\abbc
\TFFFF
aFTFFF
*FTTTT
bFFTTF
cFFFFT

我们可以发现一个规律,每当遇到两个字符不相等的时候,那么数组的值则肯定是False,相反相等的时候肯定是True,这里需要注意的是*,这里则需要考虑到它当前可能匹配0个字符串或者匹配多个字符,比如上面中的a*ab的情况,此时我们需要发现a*及a或者a和ab其中有任何一个成功匹配的,它的结果也肯定为T,所以我们可以写出它的状态转义方程:

  1. dp[i][j] = dp[i-1]j-1
  2. dp[i][j] = dp[i-1][j] or dp[i]j-1
  3. 其它情况为False

于是我们可以很简单的写出程序了(下面的程序的i,j和状态转义方程是相反的,但是原理是相同的)

class Solution(object):
    # p为匹配模式,s为字符串
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        if len(s) != len(p) - p.count(‘*‘):
            return False
        newp = ""
        i = 0
        while i < len(p):
            newp += p[i]
            if p[i] == ‘*‘:
                while i + 1 < len(p) and p[i + 1] == ‘*‘:
                    i += 1
            i += 1
        sl, pl = len(s), len(newp)
        dp = [[False for x in range(pl + 1)] for y in range(sl + 1)]
        dp[0][0] = True
        if pl > 0 and p[0] == ‘*‘:
            dp[0][1] = True
        for x in range(1, sl + 1):
            for y in range(1, pl + 1):
                if newp[y - 1] != ‘*‘:
                    dp[x][y] = dp[x - 1][y - 1] and (s[x - 1] == newp[y - 1] or newp[y - 1] == ‘?‘)
                else:
                    dp[x][y] = dp[x - 1][y] or dp[x][y - 1]
        return dp[sl][pl]

同样的原理,我们还可以把它缩减成一维数组,你可以把它想象成在二维数组中计算每一行的数据,如果遇到*则更新当前行的数据;为什么可以这么做呢?我们可以根据前面提到的公式发现,其中当前的数据依赖于j的变化,也就是待匹配字符串的值,我们还需要在外面写个模式串的循环,其实和二维数组的做法的时间复杂度是一样的,但是缩减了空间,但是并不是所有的都可以这么做,这个取决于你的依赖项是什么。总而言之,其原理还是一样的,只是想办法让它们的数据能够共存到一位数组中。

class Solution:
    # @return a boolean
    def isMatch(self, s, p):
        length = len(s)
        if len(p) - p.count(‘*‘) > length:
            return False
        dp = [True] + [False]*length
        for i in p:
            if i != ‘*‘:
                # 因为依赖项是前面的值,所以不能从前面往后面扫,得从后往前计算
                for n in reversed(range(length)):
                    dp[n+1] = dp[n] and (i == s[n] or i == ‘?‘)
            else:
                # 更新当前行的数据
                for n in range(1, length+1):
                    dp[n] = dp[n-1] or dp[n]
            dp[0] = dp[0] and i == ‘*‘
        return dp[-1]

贪心算法

下标描述
si待匹配字符串的移动下标
pi模式串的移动下标
lastmatch上一次匹配的待匹配字符串的下标
laststar上一次匹配的模式串的下标
  1. 如果当前相等或者模式串中字符为?,则移动相互的下标即可;
  2. 如果当前模式串字符为*,分别纪录lastmatch、laststar,并且移动模式串下标,但是不移动待匹配字符串下标,因为可能存在匹配0个字符串的情况;
  3. 如果当前相互对应的字符不再相等且不为*,如果前面有*号,说明之前的匹配失败了,模式字符串下标回到之前纪录laststar的后一位,不再移动,专门用来给待匹配字符串字符来匹配,这段时间内,si会不断的向前移动,直到匹配到相互的值相等才移动模式字符串的下标;
  4. 如果前面的情况都不符合,则肯定为False;

看看我的抽象派画风。

技术分享

class Solution(object):
    # p为匹配模式,s为字符串
    def isMatch(self, s, p):
        si, pi = 0, 0
        lastmatch, laststar = -1, -1
        sl, pl = len(s), len(p)
        if pl - p.count(‘*‘) > sl:
            return False
        # 注意条件顺序
        while si < sl:
            if pi < pl and (s[si] == p[pi] or p[pi] == ‘?‘):
                pi += 1
                si += 1
            elif pi < pl and p[pi] == ‘*‘:
                lastmatch, laststar = si, pi  # 之所以不更新lastmatch是因为考虑到*只匹配0个字符串
                pi += 1
            # 再次进到这个判断,说明当前下标对应的值不相等
            elif laststar != -1:
                pi = laststar + 1  # pi当前不是*,并且回到上一次星的后面,专门用来给si匹配
                lastmatch += 1  # 必须更新lastmatch,因为之前已经不想等,如果在回到开始的状态就会陷入死循环
                si = lastmatch
            else:
                return False
        # 可能存在p的末尾都是*的情况
        while pi < len(p) and p[pi] == ‘*‘:
            pi += 1
        # 最后匹配成功模式字符串的下标必然为其长度,表示已经匹配完成
        return pi == pl

tips:不要小看保存你的长度值,如果你频繁的用到的话,最好保存下来,比如在这里,我保存下来以后可以让我提升%10的beat submissions!

一样的原理,但是使用了递归的方式来做

class Solution(object):
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        seen = {}
        wild_single, wild_multi = "?", "*"
        # seen has the pattern - source tuple as key, and bool result as success
        source, pattern = s, p
        def is_match(sindex, pindex):
            key = (sindex, pindex)
            if key in seen:
                return seen[key]
            result = True
            # if there‘s no string, and pattern is not only * then fail
            if sindex >= len(source):
                for wildindex in xrange(pindex, len(pattern)):
                    if pattern[wildindex] != wild_multi:
                        result = False
                        break
            # there‘s a string, but no pattern
            elif pindex >= len(pattern):
                result = False
            # if next pattern is multi though, that‘s something
            elif pattern[pindex] == wild_multi:
                # for zero, simply check sindex, pindex + 1
                result = is_match(sindex, pindex + 1) # just for easier debug
                # if zero, than it‘s a match
                # otherwise we need to check multi
                # for that, if char is not a wild, then it has to match the source,
                result = result or is_match(sindex + 1, pindex)
            else:
                # either a regular char, or wild_single
                result = (( pattern[pindex] == wild_single or pattern[pindex] == source[sindex]) and 
                                    is_match(sindex + 1, pindex + 1))
            seen[key] = result
            return result
        if (len(p) - p.count(wild_multi) > len(s)):
            return False
        return is_match(0, 0)  

不同的做法

1. Reverse Bits

这道题目显然有非常多的做法。

我的做法是使用reversed函数,再往其后面补0即可,beat 59%。

class Solution:
    # @param n, an integer
    # @return an integer
    def reverseBits(self, n):
        # n为0,或者n为负数,效率问题
        if n == 0:
            return 0
        binary = bin(n)
        if n < 0:
            binary = list(binary.lstrip(‘-0b‘))
        else:
            binary = list(binary.lstrip(‘0b‘))
        binary = list(reversed(binary))
        if len(binary) < 32:
            binary.extend([‘0‘] * (32-len(binary)))
        res = int("".join(binary), 2)
        return -res if n < 0 else res

还有在网上看到的很简单的写法,使用format即可,format中的0指的是用0来填充,剩下的32代表有32位,b代表二进制

class Solution:
    # @param n, an integer
    # @return an integer
    def reverseBits(self, n):
        return int(‘0b‘ + ‘{:032b}‘.format(n)[::-1], 2)

还有一种位操作的写法,就是通过向左移动新数字以及向右移动给定的数字,将其进行或操作,这样就能保证给定数字的原先的低位在新数字的高位,从而达到题目的要求。

class Solution:
    # @param n, an integer
    # @return an integer
    def reverseBits(self, n):
        m = 0
        for i in xrange(32):
            m = (m << 1) | (n & 1)
            n = n >> 1
        return m

Python LeetCode(七)