不会做
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"
我们可以根据前面提到的画出整个二维数组
\ | \ | a | b | b | c |
---|---|---|---|---|---|
\ | T | F | F | F | F |
a | F | T | F | F | F |
* | F | T | T | T | T |
b | F | F | T | T | F |
c | F | F | F | F | T |
我们可以发现一个规律,每当遇到两个字符不相等的时候,那么数组的值则肯定是False,相反相等的时候肯定是True,这里需要注意的是*
,这里则需要考虑到它当前可能匹配0个字符串或者匹配多个字符,比如上面中的a*
和ab
的情况,此时我们需要发现a*及a
或者a和ab
其中有任何一个成功匹配的,它的结果也肯定为T,所以我们可以写出它的状态转义方程:
于是我们可以很简单的写出程序了(下面的程序的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 | 上一次匹配的模式串的下标 |
- 如果当前相等或者模式串中字符为
?
,则移动相互的下标即可; - 如果当前模式串字符为
*
,分别纪录lastmatch、laststar,并且移动模式串下标,但是不移动待匹配字符串下标,因为可能存在匹配0个字符串的情况; - 如果当前相互对应的字符不再相等且不为
*
,如果前面有*
号,说明之前的匹配失败了,模式字符串下标回到之前纪录laststar的后一位,不再移动,专门用来给待匹配字符串字符来匹配,这段时间内,si会不断的向前移动,直到匹配到相互的值相等才移动模式字符串的下标; - 如果前面的情况都不符合,则肯定为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