万事总要有个开头,来吧。html
问题原题看状况,若是我能用中文准确地表述出来的话那就用中文说一下。也有可能彻底不说…python
■ twoSum正则表达式
问题: 参数是一个数组nums和一个目标数target,寻找nums中两个数的和是target,返回这两个数的下标。同一个数不能重复使用。假设只有一个正确的解。算法
注意点: 因为同一个数不能重复使用,用掉一个数以后这个数就不能再计入使用范围。但同时也要注意[3,3] 6这种状况,即同一个数不能用两次,可是要考虑有两个相同值的数express
朴素解法:数组
def sumTwo(nums,targets): for i in nums: idx_1 = nums.index(i) try: idx_2 = nums.index(target-i,idx_1+1) except ValueError,e: continue return [idx_1,idx_2]
这个解法比较好地利用了python里的切片机制,以及index方法的beg参数。不愧是python,代码简洁得一比。不过这种解法的耗时也是挺久的,大概在500ms上下。分析一下的话,比较耗时的部分应该是两个下标的肯定部分了。首先for i in nums自己是个O(n)的循环,而后循环体中的index方法其实又是一个O(n)的操做,使得总体是一个O(n^2)的操做。app
那么有没有什么办法,好比对于取下标不用index这种O(n)的方法?以空间换时间,天然能够开辟另外一个结构去提早维护好整个列表中的下标状况,到时候直接去那个结构里取值就行了。好比用一个字典(哈希表)来维护:ide
def twoSum(nums, target): count = 0 idx_map = {} while count < len(nums): idx_map[nums[count]] = count count += 1 count = 0 while count < len(nums): m = target - nums[count] if m in idx_map and idx_map[m] != count: return [count,idx_map[m]] count += 1
虽然是两个循环,可是没有嵌套关系,因此整体仍是O(n),第一个循环将列表的值和下标分别做为键和值充入字典。不用担忧相同的值会产生覆盖的状况,由于题目只说有一个解并给出这个解就行了,并且后面的循环遍历也是从前日后进行的,因此不会发生信息丢失,找不到配对的状况。 第二个循环中的idx_map[m] != count条件其实就是排除了同一个数使用两次的状况,如[3,2,5]和6,不能返回[0,0]函数
■ AddTwoNumbers优化
问题: 输入是两个链表节点实例,Python的话给出的链表定义是:
# Definition for singly-linked list. # class ListNode(object): # def __init__(self, x): # self.val = x # self.next = None
而后链表的val都是一个非负的int,可将它反向链接成一个数字,好比(3 -> 4 -> 2)这样一个链表的话,获得的是243这个数字。题目的输入就是这样两个链表,而后求两个链表获得的数字相加以后的和,再把这个和给转换成这个链表格式,要求输出是这个链表的头一个元素。
下面是我写的两种解法,耗时都在70ms左右。
解法一:
充分利用Python内置的各类类型转换和反序等方法,简单地将输入链表转化为list,处理为整数相加后再将获得的结果处理回一个链表。
def addTwoNumbers(self, l1, l2): """ :type l1: ListNode :type l2: ListNode :rtype: ListNode """ lst1,lst2 = [l1.val],[l2.val] while l1.next: l1 = l1.next lst1.append(l1.val) while l2.next: l2 = l2.next lst2.append(l2.val) tmp = list(str(int(''.join([str(i) for i in reversed(lst1)])) + int(''.join([str(i) for i in reversed(lst2)])))) last = ListNode(tmp[0]) for num in tmp[1:]: ln = ListNode(num) ln.next = last last = ln return last
解法二:
从前向后分别遍历两个列表,按照通常数学中相加,从个位数开始相加,超过10的则取余数进位,而后加下一位。不过考虑到两个链表长度不一样,相加结果若是恰好要多进一个1等等状况,细节上的控制仍是挺复杂的:
def addTwoNumbers(l1, l2): """ :type l1: ListNode :type l2: ListNode :rtype: ListNode """ res = dummy = ListNode(-1) stepUpFlag = False while l1 or l2: v1 = l1.val if l1 is not None else 0 v2 = l2.val if l2 is not None else 0 s = v1 + v2 if stepUpFlag: s += 1 stepUpFlag = False if s >= 10: s %= 10 stepUpFlag = True dummy.val = s if l1: l1 = l1.next if l2: l2 = l2.next if l1 or l2: newNode = ListNode(-1) dummy.next = newNode dummy = newNode elif stepUpFlag: newNode = ListNode(1) dummy.next = newNode break return res
■ Longest Substring Without Duplicate Character
问题: 寻找一个字符串中最长的无重复字符的子串长度。输入是一个字符串,输出只要求这个最长子串的长度便可。须要注意是子串而不是子序列,因此选出来的子串在原串中必须是挨在一块儿的。这个问题看似简单,我最开始也以为确定在笔记中哪里提到过作法。可是仔细找了找还真没找到…只好从头开始想。
想法: 要是放在之前,只是实现功能就好,那么我确定会走上暴力的路… 暴力嘛,很简单,挨个遍历子字符串,碰到有重复字符的就跳过,而后找出最大长度的就行了。细节上也有不少能够优化的地方。由于没尝试就不写了。除了从字符串自己出发的暴力,也能够从符合要求子串出发。好比构建一个窗口,从len(s)长度开始分别去套各个相应长度的子串,判断是否有duplicate。固然二者其实本质同样,只是呈现方法不一样。
而后想有没有更加聪明的方法来解决这个问题。假如我手上有一个字符串"abcacabcb
",首先我会从头开始数,abc三个没问题,到第四个a的时候发现重复,那只能把第一个a砍掉。继续日后,到第五个c,发现最前面的bc两个都要砍掉。这时就发现一些东西了。
从头开始遍历,当碰到一个字符前面出现过期,那么(大概)前面出现的那个位置以前的全部东西都不能要了。这种想法还须要考虑一下出现...a...b...b...a...这种状况的时候,此时扫描到最后一个a时,最前面的a前面的东西固然要砍掉,然而其实在这以前,扫描到第二个b的时候,就连同b前面的东西早就被砍掉了。因此此时应该关注的子串是从第一个b开始到a的这部分而不是两个a之间的那部分了。
每得到到一个合法的子串就把这个子串的长度记录下来,而后一路max到底就能得出最大长度了。另外回忆一下SumTwo,在遍历列表的过程当中若要对列表作一个局部处理的时候,搞一个子列表再遍历会增长复杂度。一个比较好的办法是用哈希表(字典)来记录列表中元素和下标的映射关系。这里也是这么作的。
解法:
def lengthOfLongestSubstring(s): """ :type s: str :rtype: int """ charMap = {} maxLength = 0 stPoint = -1 # 这个变量记录的就是“要砍掉字符x以前的全部东西”,x所在的位置 # 初始化为-1是由于,抽取子串时,其实x也是不须要的。而在遍历的一开始,显然第一个字符是要算进来的,因此初始化不为0为-1 for i,char in enumerate(s): if char not in charMap: # 这个字符第一次首次出如今字符串中 charMap[char] = i maxLength = max(maxLength,i-stPoint) # 本字符到stPoint为止的内容是一个符合要求的子串,将其和当前maxLen中大者保存 else: if charMap[char] > stPoint: # 即...a...b...a...b...状况,此时要砍掉第一个b之前的全部东西,而且比价当前maxLen和两个b之间的长度大小,取其大者 maxLength = max(maxLength,i-charMap[char]) stPoint = charMap[char] else: # 即...b...a...a...b...状况,此时比较maxLen和第一个a到第二个b之间的内容长度,取其大者 # 注意,不能直接取i - stPoint由于maxLen不必定是两个a之间的长度。可能前面有更长的。 maxLength = max(maxLength,i-stPoint) # 更新本字符最后出现位置,为后面可能再次出现时提供stPoint参考 charMap[char] = i return maxLength
■ Median of Two Sorted Arrays
问题: 给出了两个各自已经按照从小到大排序的数组。求这两个数组构成的集合的中位数是多少。另外原题还要求总耗时应低于O(log(m+n)),m和n是两个数组的长度。
注意点: 若是利用Python编写,那么很朴素的思路就是将两个数组合并,而后排序新数组,而后找出中位数就好了… 若是不利用sort之类方法,所有本身写,则须要注意下逻辑的构成。
def median(nums1,nums2): nums1.extend(nums2) nums1.sort() leng = len(nums1) if leng % 2 == 0: return (nums1[leng/2-1] + nums1[leng/2])/2 else: return nums1[leng//2]
不利用 python捷径的办法:就是一个很普通的没有任何trick的算法。以扫描过中位数位置为目标(两数组长度之和是奇数仍是偶数虽然会影响中位数的具体计算方式,可是在扫描右边界的选择上是同样的。奇数则扫描到下标为len//2的元素,而偶数也须要扫描到len//2来求中位数),从头开始扫描两个数组,而且维护两个辅助变量num1和num2。扫描过程当中逐渐从两数组剩余未被扫描的数字中选出最小者,而后始终保持num1小于num2。当扫描结束以后,num1和num2应该是这样两个数:假如将两个数组合并并安小到大排序获得数组N,总的长度是L的话,那么num1和num2应该分别是N[L//2-1]和N[L//2]。此时再根据L的奇偶不一样,写出中位数便可。奇数的话直接取N[L//2],偶数的话取N[L//2-1]和N[L//2]的平均。
可能须要注意的点: 边界处理,当两个数组为空的状况怎么办。另外还有一个很常见的状况是加入一个数组比另外一个短不少怎么办?因为最开始扫描是在两个数组开头差很少同步推动的(只要两数组的值别差太大),那么必然会碰到第一个数组已经遍历到底,第二个数组还没遍历完,也尚未遍历到中位数位置。此时就须要根据遍历游标i1和i2与各自数组长度m和n之间大小比较的一个判断。
def findMedianSortedArrays(nums1, nums2): """ :type nums1: List[int] :type nums2: List[int] :rtype: float """ num1,num2 = 0,0 m,n = len(nums1),len(nums2) i1,i2 = 0,0 midLen = (m+n)//2 # midLen是为找到中位数须要遍历的两个数组中的元素个数(下标),好比若总长5,则须要遍历到总下标为2的,若总长8则遍历到4(中位数是3和4的平均,所以要到4) for i in range(midLen+1): # 由于midLen是下标,为了能够肯定遍历到下标是midLen的,因此要+1 num1 = num2 # 始终确保num1是小于num2的 if i1 < m and i2 < n: if nums1[i1] <= nums2[i2]: num2 = nums1[i1] i1 += 1 else: num2 = nums2[i2] i2 += 1 elif i1 < m: num2 = nums1[i1] i1 += 1 elif i2 < n: num2 = nums2[i2] i2 += 1 else: return None if (m+n) % 2 == 0: return (num1+num2)/2.0 else: return float(num2)
■ Find longest palindromic string
问题: 寻找一个字符串中的最长回文子字符串。
能够暴力解,可是通不过time limit验证。另外能够经过DP 或者 manacher算法 来解开。
manacher解法: https://www.cnblogs.com/franknihao/p/9342907.html
■ zigzag conversation
问题: 函数给出一个字符串和一个rowNum参数N(一个整数),而后将字符串按照顺序从上垂直向下写N个。当写到S[N-1]字符时S[N]字符将写在右边一列的上面一行,S[N+1]写到S[N]的右上方,如此直到写回第一行,以后再垂直向下写。最终须要输出的是整个写出来的东西从左上角开始一行一行向右扫描得到的字符串。
例子:
"ABCDEFGHIJKLMN",若是指定rowNum是3的话那么写成:
这个图样的输出就是AEIMDBFHJLNCGK
若是rowNum是5,那么,因此输出就是AIBHJCGKDFLNEM。
解法:最直观的的,能够构建一个多维数组而后将上面所说的规则进行填写字符。固然这样作的空间开销可能比较大一些。若是仔细分析一下这个ZigZag的结构的话能够看到,其实每一个字符和其下标的位置仍是必定关系的。好比第k行从左到右的各个字符的下标应该依次是 (2*0+k)(2*n-k)(2*n+k)(4*n-k)...其中n等于rowNum-1 。按照这种规则,在作一些边界处理(好比第0行和第n行有可能会出现2n-k == 2n+k的状况,此时不处理边际问题可能会出现重复字符的状况),就写成了下面这段代码:
class Solution(object): def convert(self, s, numRows): """ :type s: str :type numRows: int :rtype: str """ n = numRows - 1 if len(s)-1 <= n or n == 0: # 边际状况处理 return s elif n == -1: return '' res = '' for k in range(n+1):# 遍历每一行 d = 0 res += s[d+k] # 加第一列的值 d += 2*n if k in (0,n): # 当k等于0或者n的时候,会发生相似于2n-k==n+k或者2n-k==2n+k,有些字符被算两次,因此特殊处理 while d+k < len(s): res += s[d+k] d += 2*n continue while d-k < len(s): # 一直以2n为步长不断向右,每向右一次都要尝试把d-k和d+k两个字符按照顺序打出来,若是碰到越界的状况代表本行打完,去下一行 res += s[d-k] if d+k < len(s): res += s[d+k] d += 2*n return res
■ string to interger
问题: 将一个字符串转化成整数。条件以下:
1. 字符串开头可能有若干个空格(真空格,\t\n这些不算)
2. 字符串可能不只仅由数字组成,认为只有最开始的有效字符是数字的才能成功转换,好比100 words能够转化为100,可是words 100认为不能转化。不能转化的通通返回0
3. 数字的界限是[-2^31,2^31-1],对于超出这个界限的数字,大于上限者返回上限,小于下限者返回下限。
解法: 利用Python的re模块能够偷个懒:
import re class Solution(object): def myAtoi(self, str): """ :type str: str :rtype: int """ m = re.match('^ *([\+\-]?\d+).*$',str) if not m: return 0 num = int(m.group(1)) if num > 2**31-1: return 2**31-1 elif num < -2**31: return -2**31 else: return num
■ regular expression matching
问题: 构造一个简单的正则表达式引擎。正规的正则表达式包括了不少种元素,这里的简单正则只指.和*两个pattern(.是通配符,*是前面字符的0到任意多个)。
这个题看似简单,实则猛如虎… 搞了半天没搞定,直接抄答案了:
class Solution(object): def isMatch(self, s, p): """ :type s: str :type p: str :rtype: bool """ m,n = len(s),len(p) if not p: return not s firstMatch = m >= 1 and (s[0]==p[0] or p[0]=='.') if n >= 2 and p[1] == '*': return self.isMatch(s,p[2:]) or (firstMatch and self.isMatch(s[1:],p)) else: return firstMatch and self.isMatch(s[1:],p[1:])
这个算法的想法核心是,比较原串和模式串的第一个字符是否匹配,记录下匹配结果firstMatch(固然,能匹配的前提是此时原串和模式串都不为空),而后再看模式串的后一个字符,按照是不是*划分两种状况。
当是*的时候,意味着两种可能,1. N*(N是模式串的第一个字符)匹配空串,即*的含义是0个N字符,此时firstMatch确定是False,此时能够直接跳过模式串的前两个字符,直接开始后面的匹配,所以递归调用了isMatch(s,p[2:]); 2. N*匹配到了一些N字符,此时须要有firstMatch是True,而后s日后一格而p保持不变,继续日后看s后面的字符是否还能和这个N*进行匹配。
当不是*的时候比较好办,只要看第一个字符匹配结果是否为True,若是是True就意味着能够将原串和模式串都向右移动一格继续匹配,若是是False,那么就意味着在第一个字符这里就不匹配也就没有了匹配后文的必要,直接返回False便可。
这种递归的办法虽然能够解出答案,可是并非效率很高,更好的办法是使用DP。因为还不是很熟悉DP,就不写了…
■ Container with largest volumn
问题: 参数是一个数组,若是按照下标为横坐标,元素的值做为纵坐标那么咱们能够画出一幅柱状图。现要以这个柱状图中的其中两个柱子做为边界,二者横坐标的差做为宽,二者值中较小的值为高,相乘获得的面积中最大时能有多少。
这道题颇有意思,原题中应用色彩更强,说的是两个柱子之间实际上是个木桶,问木桶最多能够装多少水。固然装多少水是由短的一端决定的。
解法:
暴力解确定能够,找出每种柱子组合的面积而后找到最大的就好了。
而后在这个基础之上,我又想了一个优化。好比每遍历一个新的i的时候,都设置一个游标j从最后一个元素开始往前遍历数组h。当遍历到一个值比以前得到到的最大的h[j]小,那么这个就能够直接跳过了。由于宽不及以前的大,高也确定不及以前的大。
而后按照这种想法去submit,没想到time exceeded了… 因而只能再想一想。
仔细一想,其实对于一个固定的i来讲,若是h[j-1]比h[j]小,那么不管如何都不可能将j-1后得到一个更大的面积。相反,若是h[j-1]比h[j]大,那么在宽上损失的一点面积是有可能经过高的提高补回来,从而比现有值大的。可是这样的补有另一个隐含条件,那就是h[i]>h[j]。若是目前h[i]已经比h[j]要小了,那么j再减一,即便h[j]高到天花板,因为最终面积由短板决定,所以仍是这么点。。
综合来看,只要h[i]>h[j]那么就能够将j -= 1尝试看是否有更大的面积。反过来,若是h[i]<h[j],此时比j小的都不用试了,由于短板的i决定了最终面积,j继续减少,面积就算有变化也是往小了变。
再综合一下,一个很简洁的算法就是这样了: 让i,j两个游标分别从先后开始遍历,比较h[i]和h[j],若是前者较小,那么i++,若是后者较小那么j--,两边向中间逼近,取整个过程当中面积出现过的最大值就OK了。算法十分简单,但不太直观… 一会儿想到有点难,总之先积累起来吧。
代码:
class Solution(object): def maxArea(self, height): """ :type height: List[int] :rtype: int """ i,j = 0,len(height)-1 amax = 0 while i < j: amax = max(amax,(j-i)*min(height[i],height[j])) if height[i] < height[j]: i += 1 else: j -= 1 return amax
■ threeSum
twoSum的升级版,问题:
找出一个数组中知足全部a + b + c = 0的(a,b,c)。a,b,c之间无顺序要求。相同的一组abc在解集中只能有一个元素。
如 [-1, 0, 1, 2, -1, -4]的返回应该是[ [-1, 0, 1], [-1, -1, 2] ]
解决方案:
凭我能力仍是没想出非暴力的算法… 因此仍是抄了别人的解决方案。
首先,条件a+b+c=0是能够转化成-a = b+c。也就是说,能够选定一个a,取其相反数-a,而后再从剩余的数中找到一对b和c相加等于-a便可。这至关因而将3sum转化为一个2sum问题。可是这里还有两个问题,1是须要找出全部的解而非一个解,二是对于一个凌乱的数组而言,要找出这样一对数必然是要O(n^2)时间的。
因此第一步咱们对输入的数组进行排序,在有序的时候能够经过首位两游标不断向中间逼近的办法来特定两个符合必定条件的元素。这样的过程是O(n)的,而不是两个for i,for j的循环嵌套的O(n^2)操做。而后从前到后去遍历各个元素,每遍历到一个特定的元素的时候将其视为a,而后在a后面的全部元素中找出b,c。因为a后面至少要有两个元素,因此这个遍历在倒数第三个元素处中止。因为b,c存在于一个有序的排列中,因此能够按照上面说的那样,头尾两游标去作。
这里还有一些能够优化的地方。好比遍历取a的时候,若是a已经大于0,那么此时b,c不管如何取b+c都>0,不会等于-a。另外,在遍历a以及两游标遍历的过程当中,若是碰到几个连着相同的值,因为最终结果中相同的组合只能有一个,因此能够直接跳过。
所以最终写出的程序是这样的:
class Solution(object): def threeSum(self, nums): """ :type nums: List[int] :rtype: List[List[int]] """ nums.sort() fix = 0 res = [] while fix < len(nums) - 2: if nums[fix] > 0: break target = - nums[fix] i,j = fix + 1, len(nums) - 1 while i < j: if nums[i] + nums[j] == target: res.append([nums[fix],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 elif nums[i] + nums[j] < target: i += 1 elif nums[i] + nums[j] > target: j -= 1 while nums[fix] == nums[fix+1] and fix < len(nums) - 2: fix += 1 fix += 1 return res
■ GenerateParentheses
问题, 给出一个数字n,要求写出全部形式正确,而且包含了n对小括号"()"的字符串。好比当n=2时,应该返回的是["()()", "(())"],因为")(()"这样的是非法的,因此不需给出。
算法:这个问题看起来彷佛很水,可是卧槽…我想到的办法是暴力枚举,而后将每一个字符串进行很早之前接触过的+1, -1那种形式的is_valid检查。不过暴力果真太low了,下面这个算法是基于DFS的生成:
def generate(n): res = [] def backtrace(s, left, right): ''' 递归过程当中s保持为一个合法的字符串 left,right分别表示该字符串中左、右括号的个数 ''' if len(s) == 2*n: res.append(s) return if left < n: backtrace(s+'(', left+1, right) if right < left: backtrace(s+')', left, right+1) backtrace('',0,0) return res
■ DivideTwoNumber
问题: 给出dividend(被除数)和divisor(除数),作一个整数除法并返回结果。要求不要用到乘号除号或者取模操做。另外还有一个限制默认int表示范围是[-2^31, 2^31-1],超出此范围的结果返回封顶值便可。
算法: 这个题看起来就很是有意思。一开始我想不用乘除法,大不了不断减去除数,看减去了几回就好了。然而对于除数很小的状况,很容易TLE。这说明线性地一个一个减去除数太慢了,因而想到可否加快这个减少的过程。若是能够用乘号,那么能够直接另divisor *= 2,这样能够两倍两倍地减除数,系数能够调得很大。但是不让用乘号啊怎么办…
反正我本身没想到… 后来看了下别人的答案,发现有位操做这条路能够走。(受到启发,之后可用位操做来代替*2)
为了方便令被除数是a,除数是b,那么经过不断的b << 1能够将b扩大到 b * 2 ^ n,当n再加上1则b会大于a。这样一次减少就能够将a的“一大半”都减掉,比较有效率。对于a减少以后剩余部分,能够重置n后作一个一样的操做。这样a减少的速度是指数级的,比原先线性的要快不少。
其余无非就是一些正负的考虑,以及溢出的处理等等,解释和心得都写在注释里面了:
def divideTwoNumber(,b):class Solution(object): def divide(self, dividend, divisor): """ :type dividend: int :type divisor: int :rtype: int """ minus = (dividend < 0) ^ (divisor < 0) # 异或操做判异号,这个比较有意思,通常只想到a<0 == b<0之类的 a,b = abs(dividend),abs(divisor) # 撇开同异号影响后只关注绝对值 res = 0 while a >= b: c = -1 powb = b while a >= powb: # 寻找a能承受的最大c powb <<= 1 c += 1 a -= b << c # 减去a的“大半部分” res += 1 << c # 记录下这个大半部分其实包含了几个b return max(-res,-2**31) if minus else min(res,2**31-1)
顺便一提,看了其余还有答案是用math.exp结合math.log,至关因而把普通运算作成了指对运算。也有用// 或者 *=这类擦边球的。。不过包括上面的位运算在内,本质上要计算出这个商总归是要进行乘除运算的,只不过是如何在代码里不提到乘除号。能够说这个题也算是个让你熟悉语言各类非简单四则运算套路的题目。
■ Find First and Last Position in Sorted Array
问题: 输入是一个数组和一个target值。这个数组array是通过排序的。要求返回这个数组中,target元素第一次出现和最后一次出现的下标值。若是target元素根本不存在于数组中则能够返回[-1,-1]。
好比[5,7,7,8,8,8,10],8这样一组输入,获得的输出是[3,5]。另要求算法的时间复杂度是O(logn)
解决: 看到O(logn),想到了能够经过二分查找来解决。因而第一个比较朴素的想法是经过二分查找定位到某个target元素后,以此为中心,向两边扩散寻找最左边下标和最右边下标。不过若是target元素重复次数比较多,这样扩散操做会致使O(n)的复杂度,所以不算是一个很好的办法。
另外一个办法是经过两个二分查找分别找到最左和最右target的下标。咱们知道通常的二分查找,默认扫描数组中不存在重复元素,这也就是说若是存在多个target的时候,mid碰到某一个就当即返回。 为了找到最左或者最右的target,势必要将二分查找主循环中的那个return语句去掉。
通常的二分查找:
def search(nums,target): low,high = 0,len(nums) - 1 while low < high: mid = (low + high) // 2 if nums[mid] == target: return mid if nums[mid] < target: low = mid + 1 else: high = mid - 1 return -1
而后咱们再来分析下,若是将return语句去掉了,那么nums[mid]值仍然小于target的时候无所谓,大于等于target的时候都会走else分支。而等于target的时候high的继续左移则能够保证high有可能能够移动到最左侧的target元素的下标。不过须要额外注意的一点是,若是mid的位置恰好是最左边的target,那若是让high = mid - 1,此时[low,high]区间内就不存在target元素了,最终会返回-1。 这显然不是咱们指望的,所以须要考虑如何修改。 其实只要将high = mid便可。这样能够保证high的左移不会超出target的范围。
固然,跳出循环时,若是nums[high]的值不是target,这就说明原数组中没有target元素,能够直接返回-1了。
另外一方面,上面的修改是针对求最靠左的,若是是求最靠右的呢?只要修改两个地方就能够了,就是把nums[mid] < target改成nums[mid] <= target ; 以及把high = mid改回high = mid - 1。因为有了等于号,至关于low最大能够到target带的最右端,也就是咱们想要求的东西了.
不过这里还有一些细节须要注意,由于在这样的代码中,low能够右移且high能够左移,若是恰好low遍历到target带最右端,且high恰好是low+1,此时mid是low,而nums[mid]知足<=target,因此low会再+1,此时low和high相等,跳出循环。因此说,要找到target带最右端的值,在跳出循环以后还须要看下nums[low]是不是target,若是是那么能够直接返回,若是不是,就得返回low - 1
综上,代码:
class Solution(object): def searchRange(self, nums, target): """ :type nums: List[int] :type target: int :rtype: List[int] """ if len(nums) == 0: return [-1,-1] # 空特殊值处理 low,high = 0,len(nums)-1 left,right = None,None while low < high: # 第一个循环肯定target带左端值 mid = (low + high) // 2 if nums[mid] < target: low = mid + 1 else: high = mid if nums[high] != target: return [-1,-1] left = high low,high = 0,len(nums) - 1 while low < high: # 第二个循环肯定target带右端值,相比第一个循环代码上两处改动 mid = (low + high) // 2 if nums[mid] <= target: #修改处1 low = mid + 1 else: high = mid - 1 # 修改处2 if nums[low] != target: # low有恰好出target带的风险,作一个额外判断 right = low - 1 else: right = low return [left,right]
值得一提的是,上下两个循环也是解题中常常会用到的两种二分查找模式。上者是“查找第一个不小于目标值的数” 或者 “查找最后一个小于目标值的数” 的模式(这两个问题之间的相关性是,前一个问题的解的下标减1就是后一个问题的解的下标),下者是“查找最后一个不大于目标值的数” 或者 “查找第一个大于目标值的数”,两个问题相关性相似。