【Leetcode】收集

萬事總要有個開頭,來吧。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)
View Code

 

■  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就是後一個問題的解的下標),下者是「查找最後一個不大於目標值的數」  或者  「查找第一個大於目標值的數」,兩個問題相關性相似。

相關文章
相關標籤/搜索