2020-07演算法刷題集

末_梟發表於2020-07-16

前言

前段時間一直忙於上課與其它事情,一直都沒有抽出時間來刷演算法題,現在看來,浪費了許多時間,非常後悔。
錯過了昨天,今天將是一個非常好的起點。所以重新開始刷演算法題。
(2020-07-25)這段時間每天刷著一道簡單難度和一道中等難度的題目,花費的時間也不多。只是總在詢問自己刷題的意義在什麼地方?今天有了能讓自己滿意的答案——享受於每一個邏輯和細節處理,享受每一個字元,每一句程式碼所造成的影響。或許有時會急躁於一個個疏忽,但最終都會明白,急躁並不能解決問題,靜下來才能知道問題出在哪裡。問題的形成和解決是一個過程,需要經驗,需要冷靜,同樣也需要跨越自己現在的位置。

  • 0715-一年中的第幾天

  1. 來源
    力扣(LeetCode)-1154-一年中的第幾天
  2. 問題描述
  • 給你一個按 YYYY-MM-DD 格式表示日期的字串 date,請你計算並返回該日期是當年的第幾天。
    通常情況下,我們認為 1 月 1 日是每年的第 1 天,1 月 2 日是每年的第 2 天,依此類推。每個月的天數與現行公元紀年法(格里高利曆)一致

  • 示例
    示例 1:
    輸入:date = “2019-01-09”
    輸出:9
    示例 2:
    輸入:date = “2019-02-10”
    輸出:41

  1. 演算法分析
  1. 獲取關鍵
  • 輸入:字串,以“-”分隔,公元紀年法
  • 返回:第幾天
  1. 處理邏輯
  • 這道題都沒什麼好說的,有些以下常識即可完成
    • 一年有12個月,有兩類,一種是平年,二月有28天,一種是閏年,二月有29天
    • 平閏年的區別:閏年可以被4整除。但是,如果是100的倍數的話,必須被400整除,才是閏年。例如1900年可以被4整除,但由於是100的倍數,所以應該被除於400,不能被整除,所以是平年,二月有28天;2000年能被400整除,是閏年。二月有29天
    • 1、3、5、7、8、10、12這些月份有31天
    • 2、4、6、9、11這些月份有30天
  1. 細節處理
  • 100整倍年需要除於400,其它也沒啥了
  1. 程式碼實現
class Date(object):
    def __init__(self,dateStr):
        """ 2010-10-09 """
        self.date = dateStr
    def run(self):
        year = int(self.date[:4])
        month = int(self.date[5:7])
        day = int(self.date[-2:])
        """ 1、3、5、7、8、10、12 """
        daysList = [31,28,31,30,31,30,31,31,30,31,30,31]
        if year % 100 == 0:
            if year % 400 == 0:
                """ 閏年 """
                daysList[1] = 29
        else:
            if year % 4 == 0:
                daysList[1] = 29
        result = 0
        for i in range(month-1):
            result += daysList[i]
        result += day
        return result
if __name__ == "__main__":
    dateObj = Date('2019-02-10')
    print(dateObj.run())
#41

執行結果:

  1. 思考

題目關鍵詞:關於日期的常識
這道題沒什麼難度,但是如果不具備日期的常識,將無法解題。這是比較關鍵的。所以,應該多刷一些關於常識的題目,積累邏輯處理的流程之外,更重要的是知道這些常識。

  • 0716-分數加減運算

  1. 來源
    力扣(LeetCode)-592-分數加減運算
  2. 問題描述
  • 給定一個表示分數加減運算表示式的字串,你需要返回一個字串形式的計算結果。 這個結果應該是不可約分的分數,即最簡分數。 如果最終結果是一個整數,例如 2,你需要將它轉換成分數形式,其分母為 1。所以在上述例子中, 2 應該被轉換為 2/1。

  • 示例 1:
    輸入:"-1/2+1/2"
    輸出: “0/1”

  • 示例 2:
    輸入:"-1/2+1/2+1/3"
    輸出: “1/3”
  1. 演算法分析
  1. 獲取關鍵
  • 輸入:字串,加減
  • 返回:最簡,字串,分數
  1. 處理邏輯
  • 從字串中獲取分數物件,並獲取對應的運算子,+或者-
  • 對所有物件進行通分,即需要求得所有分數物件的分母的最小公倍數
    經過這一步處理後,所有的分數的分母將會統一,為最小公倍數
  • 對所有的分數物件的分子進行乘積處理,乘積的值是最小公倍數除於對應分母的結果
    求出所有分子的和
    求出分子和與最小公倍數的最大公因數,最大公因數可以通過以下方法獲得。
    假 設 存 在 兩 個 對 象 a , b a , b 的 最 小 公 倍 數 使 用 [ a , b ] 表 示 , 最 大 公 因 數 使 用 ( a , b ) 表 示 將 會 有 : a ∗ b = ( a , b ) ∗ [ a , b ] 假設存在兩個物件a,b\\ a,b的最小公倍數使用[a,b]表示,最大公因數使用(a,b)表示\\ 將會有:a*b = (a,b)*[a,b] a,ba,b使[a,b],使(a,b)ab=(a,b)[a,b]
    將分子和最小公倍數同時除以最大公因數
    返回結果
  1. 細節處理
  • 如果最終的分子和為0時,可以直接返回“0/1”,,不需要求出分子和最小公倍數的最大公因數,否則正常處理
  1. 程式碼實現
class Solution:
    def beautyResultList(self,worklist):
        resultList = []
        for i in worklist:
            if i != '':
                eleList = i.split('/')
                resultList.append((int(eleList[1]),int(eleList[0])))
        return resultList
    def commonList(self,worklist,commonNum):
        for i in range(len(worklist)):
            value = commonNum/worklist[i][0]*worklist[i][1]
            worklist[i] = value
        return worklist
    def gcd(self,n1,n2):
        """greatest common divisor function """
        return self.gcd(n2, n1 % n2) if n2 > 0 else n1
    def lcm(self,n1,n2):
        """lowest common multiple function"""
        return n1 * n2 // self.gcd(n1, n2)
    def getMinCommonMultiple(self,worklist):
        for i in range(len(worklist)-1):
            n1,n2 = worklist.pop(0),worklist.pop(0)
            worklist.insert(0,self.lcm(n1,n2))
        return worklist[0]
    def fractionAddition(self, expression: str) -> str:
        positive,loseList = [],[]
        if expression[0] != '+' or expression[0] != '-':
            expression = '+'+expression
        for i in range(len(expression)):
            if (expression[i]=='-'):
                ele = ''
                for j in range(i+1,len(expression)):
                    if(expression[j]!='+') and (expression[j]!='-'):
                        ele += expression[j]
                    else:
                        break
                loseList.append(ele)
            elif (expression[i]=='+'):
                ele = ''
                for j in range(i+1,len(expression)):
                    if(expression[j]!='+') and (expression[j]!='-'):
                        ele += expression[j]
                    else:
                        break
                positive.append(ele)
        positive = self.beautyResultList(positive)
        loseList = self.beautyResultList(loseList)
        parentList = []
        for i in positive:
            parentList.append(i[0])
        for i in loseList:
            parentList.append(i[0])
        minCommonNum = self.getMinCommonMultiple(parentList)
        positive = self.commonList(positive,minCommonNum)
        loseList = self.commonList(loseList,minCommonNum)
        posSum = sum(positive)
        loseSum = sum(loseList)
        son = int(posSum-loseSum)
        if son == 0:
            return '0'+'/'+str(1)
        else:
            maxCommonNum = son*minCommonNum/self.getMinCommonMultiple([son,minCommonNum])
            return str(int(son/maxCommonNum))+"/"+str(int(minCommonNum/maxCommonNum))
if __name__ == "__main__":
    obj = Solution()
    print(obj.fractionAddition("-1/2+1/2"))
# 0/1
  1. 思考

題目關鍵詞:
這道題主要側重於考察數學知識中的最小公倍數最大公因數。另外在解析字串的過程中,也是需要一些處理邏輯的,但是如果引用正規表示式的話,將會大大減少工作量。這等同於是否造輪子的區別。
另外有一些細節上的處理,也非常關鍵,在測試過程中遇到了好幾個問題,都是因為細節處理上的疏忽。

  • 0717-移動石子直到連續

  1. 來源
    力扣(LeetCode)-1033-移動石子直到連續
  2. 問題描述
  • 三枚石子放置在數軸上,位置分別為 a,b,c。
    每一回合,我們假設這三枚石子當前分別位於位置 x, y, z 且 x < y < z。從位置 x 或者是位置 z 拿起一枚石子,並將該石子移動到某一整數位置 k 處,其中 x < k < z 且 k != y。
    當你無法進行任何移動時,即,這些石子的位置連續時,遊戲結束。
    要使遊戲結束,你可以執行的最小和最大移動次數分別是多少? 以長度為 2 的陣列形式返回答案:answer = [minimum_moves, maximum_moves]

  • 示例
    輸入:a = 1, b = 2, c = 5
    輸出:[1, 2]
    解釋:將石子從 5 移動到 4 再移動到 3,或者我們可以直接將石子移動到 3。

  1. 演算法分析
  1. 獲取關鍵
  • 輸入:一維數軸,3個整數,移動到某個整數處
  • 返回:一個陣列
  1. 處理邏輯
  • 首先有一個數軸,數軸上有3個數,移動3個數,使得連續。但每一回合只能移動兩邊的數。
    也有幾個概念,如果a,b連續,那麼就有a+1=b;同理b,c連續則有b+1=c
    因為不按順序地輸入,所以需要進行升序排序;根據大小重新定義為a,b,c
    可以分為以下幾種情況:
    • 情況1:3個數是連續的
      返回[0,0]即不需要移動
    • 情況2:左邊兩個數是連續的
      那麼移動最少次數為1,即將右邊的數c移動1次,移動(飛)到b+1處;
      最多次數,產生於將c每次移動1個單位,移動到b+1處,則有c-b-1
    • 情況3:右邊兩數是連續的
      同上,最少次數為1,最多次數為b-a-1
      -情況4:左右兩數都不連續 但左右兩數相差大於2
      最少次數產生於 將a移動1次,移動(飛)到b-1處,將c移動1次,移動(飛)到b+1處,共2次;
      最多次數產生於 每次一個單位,將a移動到b-1處,將c移動到b+1處,則有
      ( c − b − 1 ) + ( b − 1 − a ) = c − a − 2 (c-b-1)+(b-1-a)=c-a-2 (cb1)+(b1a)=ca2
      -情況5:左右兩數不連續,但左右兩數有一個相差等於2,例如以下情況:
      在這裡插入圖片描述
      區別於情況4的最少移動次數,將離得遠的數移動到左右相差2的中間即可。例如上面的將1移動到4的位置;也如將7移動到2的位置;
      最多次數還是和情況4一致。
  1. 細節處理
  • 其中情況5沒有考慮到,所以演算法過不去。看錯誤示例知道存在這個問題,所以補充了
  1. 程式碼實現
class Solution:
    def numMovesStones(self, a, b, c):
        a,b,c = sorted([a,b,c])
        #三連續
        if a+1 ==b and b+1 == c:
            return [0,0]
        #左連續
        if a+1 == b and b+1 < c:
            return [1,c-b-1]
        #右連續
        if a+1 <b and b+1 == c:
            return [1,b-a-1]
        if a+2 == b or b+2 == c:
            # return [1,b-a-1+c-b-1]
            return [1,c-a-2]
        #左右不連續
        if a+1 < b and b+1 <c:
            # return [2,b-a-1+c-b-1]
            return [2,c-a-2]
if __name__ == "__main__":
    obj = Solution()
    print('[1,2,5]:',obj.numMovesStones(1,2,5))
    print('[1,3,5]:',obj.numMovesStones(1,3,5))
    print('[1,3,7]:',obj.numMovesStones(1,3,7))

在這裡插入圖片描述
5. 思考

題目關鍵詞:移動
這道演算法的最小值和最大值都產生於移動,只不過產生的過程是不同的。
最少次數產生於跨度比較大的移動,更加確切的說是飛。而最多次數產生於一個單位的移動,每次只移動1個單位,不斷縮小3個數的距離。
另外,每一種情況都有特殊性,區別在於最少次數的產生。因為情況不同,最少次數也不同。
最後,這道題也是比較簡單,也因為這幾天比較忙,所以刷幾道簡單的。

  • 0719-拼寫單詞

  1. 來源
    力扣LeetCode-1160
  2. 問題描述
  • 給你一份『詞彙表』(字串陣列) words 和一張『字母表』(字串) chars。
    假如你可以用 chars 中的『字母』(字元)拼寫出 words 中的某個『單詞』(字串),那麼我們就認為你掌握了這個單詞。
    注意:每次拼寫(指拼寫詞彙表中的一個單詞)時,chars 中的每個字母都只能用一次。
    返回詞彙表 words 中你掌握的所有單詞的 長度之和。

  • 示例
    輸入:words = [“cat”,“bt”,“hat”,“tree”], chars = “atach”
    輸出:6
    解釋:
    可以形成字串 “cat” 和 “hat”,所以答案是 3 + 3 = 6。

  1. 演算法分析
  1. 獲取關鍵
  • 輸入:一個列表,包含多個字串,一個字串
  • 返回:返回列表中滿足條件的字串的總和
  1. 處理邏輯
  • 定義字串為字典,列表為待驗證的字串的集合
  • 分析字典中每一個字元的出現次數,使用dict(),格式{“x”:counts}
  • 遍歷集合,取出每一個待驗證的字串,並分析字串的每一個字元的出現次數,同上的處理方式
  • 如果待驗證字串字典中的鍵在字典中,且出現次數小於或等於字典,則表示該鍵滿足條件,記錄對應的數值
  • 當遍歷完成一個待驗證字串字典時,如果數值等於該字串的長度,則表示這個待驗證字串滿足條件,記錄對應的長度。
  • 遍歷完成時,返回最終的長度即可。
  1. 細節處理
  • 物件的位置與區別需要分析好,不然很容易出現混淆。
  1. 程式碼實現
class Solution:
    def counter(self,chars):
        worddict = {}
        for i in chars:
            if(i in worddict):
                worddict[i] = worddict[i]+1
            else:
                worddict[i] = 1 
        return worddict
    def countCharacters(self, words, chars):
        charsdict = self.counter(chars)
        result = 0
        for i in words:
            i_dict = self.counter(i)
            flag = 0
            for j in i_dict:
                if j in charsdict and charsdict[j] >= i_dict[j]:
                    flag += i_dict[j]
            if flag == len(i):
                result += len(i)
        return result
if __name__ == "__main__":
    obj = Solution()
    words = ["cat","bt","hat","tree"]
    chars = "atach"
    # words = ["hello","world","leetcode"]
    # chars = "welldonehoneyr"
    words = ["dyiclysmffuhibgfvapygkorkqllqlvokosagyelotobicwcmebnpznjbirzrzsrtzjxhsfpiwyfhzyonmuabtlwin","ndqeyhhcquplmznwslewjzuyfgklssvkqxmqjpwhrshycmvrb","ulrrbpspyudncdlbkxkrqpivfftrggemkpyjl","boygirdlggnh","xmqohbyqwagkjzpyawsydmdaattthmuvjbzwpyopyafphx","nulvimegcsiwvhwuiyednoxpugfeimnnyeoczuzxgxbqjvegcxeqnjbwnbvowastqhojepisusvsidhqmszbrnynkyop","hiefuovybkpgzygprmndrkyspoiyapdwkxebgsmodhzpx","juldqdzeskpffaoqcyyxiqqowsalqumddcufhouhrskozhlmobiwzxnhdkidr","lnnvsdcrvzfmrvurucrzlfyigcycffpiuoo","oxgaskztzroxuntiwlfyufddl","tfspedteabxatkaypitjfkhkkigdwdkctqbczcugripkgcyfezpuklfqfcsccboarbfbjfrkxp","qnagrpfzlyrouolqquytwnwnsqnmuzphne","eeilfdaookieawrrbvtnqfzcricvhpiv","sisvsjzyrbdsjcwwygdnxcjhzhsxhpceqz","yhouqhjevqxtecomahbwoptzlkyvjexhzcbccusbjjdgcfzlkoqwiwue","hwxxighzvceaplsycajkhynkhzkwkouszwaiuzqcleyflqrxgjsvlegvupzqijbornbfwpefhxekgpuvgiyeudhncv","cpwcjwgbcquirnsazumgjjcltitmeyfaudbnbqhflvecjsupjmgwfbjo","teyygdmmyadppuopvqdodaczob","qaeowuwqsqffvibrtxnjnzvzuuonrkwpysyxvkijemmpdmtnqxwekbpfzs","qqxpxpmemkldghbmbyxpkwgkaykaerhmwwjonrhcsubchs"]
    chars = "usdruypficfbpfbivlrhutcgvyjenlxzeovdyjtgvvfdjzcmikjraspdfp"
    # words = ["hello","world","leetcode"]
    # chars = "welldonehoneyr"
    print(obj.countCharacters(words,chars))

執行截圖:在這裡插入圖片描述
5. 思考

題目關鍵詞:字典
有時需要認真的閱讀題目,其實題目中的很多資訊,已經告訴我們處理的方式。例如這道題,最開始時,我是準備將每一個字元替換,當完成後,如果字元都被替換完成後,如果替換次數等於待驗證字串的長度,那麼就表示符合條件。當然這可以實現,但有許多細節並沒有扣好,而且如果字串的長度過大時,非常消耗記憶體。
最後,採用了字典記錄字串的字元與出現次數,最終完成,有許多細節是不需要處理的,而且也並無細節。使用合適的資料型別非常關鍵,這也驗證了那句"演算法+資料結構=程式"。

  • 0720-有效的迴旋鏢

  1. 來源
    力扣LeetCode-1037-有效的迴旋鏢
  2. 問題描述
  • 迴旋鏢定義為一組三個點,這些點各不相同且不在一條直線上。給出平面上三個點組成的列表,判斷這些點是否可以構成迴旋鏢。

  • 示例
    輸入:[[1,1],[2,3],[3,2]]
    輸出:true

  1. 演算法分析
  1. 獲取關鍵
  • 輸入:一個列表,列表中有3個子列表,子列表中有2個元素,分別代表下x,y座標,則3個子列表表示3個點
  • 返回:判斷3個點是否共線。如果共線,則返回Flase,否則True
  1. 處理邏輯
  • 偏向於數學問題。有許多方法可以解決,例如斜率、3點是否構成三角形、點是否在直線上。
  1. 斜率:3個點中隨機選取2個點,構成1條直線,獲取2個直線,並求得斜率。如果2條直線的斜率不同,那麼表示3點不共線。
  2. 三點是否構成三角形:之前有一篇“點在三角形內”,可以用其中的一個面積公式,如果面積為0,則表示構成直線。(程式碼實現2)
  3. 點是否在直線上,取兩個點,構成一條直線方程,判斷點是否滿足直線方程,如果滿足,則表示三點共線
    4.向量法:三點兩兩組合構成兩個向量,判斷這兩個向量是否共線即可。
    -這一道題使用兩種方法,一種是向量法,另一種就是三點是否構成三角形。
  • . 細節處理
    如果需要使用除法的話,可以兩兩交叉乘,避免分母為0的情況發生。
  1. 程式碼實現

這裡分別使用兩個思路三角形面積公式和向量法實現

  • 三角形面積公式
class Solution:
    def isBoomerang(self, points: List[List[int]]) -> bool:
        a = (points[0][0],points[0][1])
        b = (points[1][0],points[1][1])
        c = (points[2][0],points[2][1])
        ##S=(1/2)*(x1y2+x2y3+x3y1-x1y3-x2y1-x3y2)
        s = a[0]*b[1]+b[0]*c[1]+c[0]*a[1]
        y = a[0]*c[1]+b[0]*a[1]+c[0]*b[1]
        if s==y:
            return False
        else:
            return True
  • 向量法
class Solution:
    def diffOfList(self,list1,list2):
        return (list1[0]-list2[0],list1[1]-list2[1])
    def isBoomerang(self, points) -> bool:
        if points[1]!=points[0] and points[1]!=points[2] and points[0]!=points[2]:
            vector_a = self.diffOfList(points[0],points[1])
            vector_b = self.diffOfList(points[0],points[2])
            if vector_a[0] and vector_a[1]:
                if vector_b[0]/vector_a[0] != vector_b[1]/vector_a[1]:
                    return True
                else:
                    return False
            elif not vector_a[0] and vector_a[1]:
                if not vector_b[0]:
                    return False
                else:
                    return True
            elif not vector_a[1] and vector_a[0]:
                if not vector_b[1]:
                    return False
                else:
                    return True
            else:
                return False
        else:
            return False
if __name__ == "__main__":
    obj = Solution()
    points = [[1,1],[2,3],[3,2]]
    points = [[1,1],[2,2],[3,3]]
    points = [[0,0],[1,2],[0,1]]
    points = [[1,1],[2,3],[3,2]]
    print(obj.isBoomerang(points))
  1. 思考

題目關鍵詞:點,直線
這道題更偏向於數學知識上,其實除了上面的那些解法之外,還有許多種方式。
除此之外,也沒啥好說的了。

  • 0722-最後一塊石頭的重量

  1. 來源
    力扣(LeetCode)-1046-最後一塊石頭的重量
  2. 問題描述
  • 有一堆石頭,每塊石頭的重量都是正整數。
  • 每一回合,從中選出兩塊 最重的 石頭,然後將它們一起粉碎。假設石頭的重量分別為 x 和 y,且 x <= y。那麼粉碎的可能結果如下:
  1. 如果 x == y,那麼兩塊石頭都會被完全粉碎;
  2. 如果 x != y,那麼重量為 x 的石頭將會完全粉碎,而重量為 y 的石頭新重量為 y-x。
  • 最後,最多隻會剩下一塊石頭。返回此石頭的重量。如果沒有石頭剩下,就返回 0。

  • 示例
    輸入:[2,7,4,1,8,1]
    輸出:1
    解釋:
    先選出 7 和 8,得到 1,所以陣列轉換為 [2,4,1,1,1],
    再選出 2 和 4,得到 2,所以陣列轉換為 [2,1,1,1],
    接著是 2 和 1,得到 1,所以陣列轉換為 [1,1,1],
    最後選出 1 和 1,得到 0,最終陣列轉換為 [1],這就是最後剩下那塊石頭的重量。

  1. 演算法分析
  1. 獲取關鍵
  • 輸入:一個列表,列表內元素都是整數
  • 返回:一個整數
  1. 處理邏輯
  • 每一回合選取列表中的最重兩塊石頭,進行碰撞,碰撞後並返回剩餘重量到列表。
    碰撞:大減小
    最簡單的方法:
  • 對列表進行排序,彈出兩個最大的元素,求差。當差不為0時,插入到列表中
  • 當列表長度大於1時重複以上操作
  • 最後將會返回一個空列表或者長度為1的列表
    當列表為空時,返回0即可
    當列表長度為1時,返回列表第一個元素即可
  1. 細節處理
  • 需要考慮到列表為空的條件存在:即列表中最後一回閤中,兩塊石頭的重量相等。
  1. 程式碼實現
class Solution:
    def lastStoneWeight(self, stones: List[int]) -> int:
        while len(stones) > 1:
            stones.sort(reverse=True)
            max = stones.pop(0)
            min = stones.pop(0)
            if max != min:
                stones.append(max-min)
        if stones:
            return stones[0]
        return 0
  1. 思考

題目關鍵詞:貪心,排序
其實這道題挺簡單的,但並沒有實現得多麼優美,也沒有參考其它的辦法。知識簡單的刷題而已。
另外也實現了一種想法:就是隻執行一次排序,然後每次將碰撞後的結果插入列表的某個位置,保證列表的元素數值順序不變。
這裡使用了二分插入排序,但是效率非常不穩定,可以由於插入演算法並沒有寫得完美。程式碼如下:

class Solution:
    def insert(self,ele,ls):
        if not ls:
            return [ele]
        elif len(ls) == 1:
            if ls[0]>ele:
                ls.insert(0,ele)
            else:
                ls.append(ele)
            return ls
        else:
            low = 0
            hight = len(ls)
            while hight > low:
                mid = int((hight-low)/2)+low
                if ls[mid] > ele:
                    if ele > ls[mid-1]:
                        ls.insert(mid,ele)
                        return ls
                    hight = mid-1
                elif ele > ls[mid]:
                    if len(ls)-2>mid and ls[mid+1] > ele:
                        ls.insert(mid+1,ele)
                        return ls
                    low = mid+1
                else:
                    ls.insert(mid,ele)
                    return ls
            ls.insert(low,ele)
            return ls
    def lastStoneWeight(self, stones: List[int]) -> int:
        stones.sort()
        while len(stones) > 1:
            max = stones.pop(-1)
            min = stones.pop(-1)
            if max != min:
                stones = self.insert(max-min,stones)
        if stones:
            return stones[0]
        else:
            return 0
  • 0723-有效三角形的個數

  1. 來源
    力扣(LeetCode)-611-有效三角形的個數
  2. 問題描述
  • 給定一個包含非負整數的陣列,你的任務是統計其中可以組成三角形三條邊的三元組個數。

  • 示例
    輸入: [2,2,3,4]
    輸出: 3
    解釋:
    有效的組合是:
    2,3,4 (使用第一個 2)
    2,3,4 (使用第二個 2)
    2,2,3

  1. 演算法分析
  1. 獲取關鍵
  • 輸入:陣列,非負整數,取出3個整陣列合,判斷是否符合三角形
  • 返回:滿足條件的三角形個數
  1. 處理邏輯
  • 關鍵:三條邊中,最小的兩條邊大於第三邊,則三條邊即可構成三角形
    首先對列表進行排序,難解釋,直接看圖吧
    在這裡插入圖片描述>3. 細節處理
  • 根據關鍵,需要知道邊的有效範圍和符合條件的邊的個數
  1. 程式碼實現
class Solution:
    def triangleNumber(self, nums: List[int]) -> int:
        nums.sort()
        result,len_nums = 0,len(nums)
        for i in range(len_nums-2):
            if nums[i] == 0 : continue
            current = i+2
            for x in range(i+1,len_nums-1):
                while len_nums > current and nums[i]+nums[x] > nums[current]:
                    current += 1
                result += current -x-1
        return result
  1. 思考

題目關鍵詞:排序,三角形
其實一開始是準備3個for迴圈,獲取滿足條件的邊的集合,最後返回集合的長度即可
但是最後發現,效率太低了,最後使用了動態規劃。效率雖然不是很高,但也跑過去了。

  • 0724-(簡單)高度檢查器

  1. 來源
    力扣(LeetCode)-1151-高度檢查器
  2. 問題描述
  • 學校在拍年度紀念照時,一般要求學生按照 非遞減 的高度順序排列。
    請你返回能讓所有學生以 非遞減 高度排列的最小必要移動人數。
    注意,當一組學生被選中時,他們之間可以以任何可能的方式重新排序,而未被選中的學生應該保持不動。

  • 示例
    輸入:heights = [1,1,4,2,1,3]
    輸出:3
    解釋:
    當前陣列:[1,1,4,2,1,3]
    目標陣列:[1,1,1,2,3,4]
    在下標 2 處(從 0 開始計數)出現 4 vs 1 ,所以我們必須移動這名學生。
    在下標 4 處(從 0 開始計數)出現 1 vs 3 ,所以我們必須移動這名學生。
    在下標 5 處(從 0 開始計數)出現 3 vs 4 ,所以我們必須移動這名學生。

  1. 演算法分析
  1. 獲取關鍵
  • 輸入:一個列表
  • 返回:一個整數
  1. 處理邏輯
  • 這道題官方的問題描述和示例都是有問題的。初看時,一直get不到出題人的意圖。甚至覺得是否要寫一個排序演算法,通過最少的移動次數,完成排序後並返回移動次數。但通過示例的返回值,知道了這道題的意圖——對所給集合進行升序排序。並將完成排序之後的集合與原集合進行比較,看有同一位置上有多少個元素不同。
  • 例如原集合[1,1,4,2,1,3]與完成排序的集合[1,1,1,2,3,4],
    有位置2(從0開始),4,5的3個位置上的元素不同,所以返回3.
  • 所以解決的辦法就是進行一一比較
  1. 細節處理
  1. 程式碼實現
class Solution:
    def heightChecker(self, heights):
        copy_heights = heights.copy()
        copy_heights.sort()
        count = 0
        for i in range(len(heights)):
            if heights[i]!= copy_heights[i]:
                count += 1
        return count
  1. 思考

題目關鍵詞:簡單,排序

  • 0724-(中等)求解方程

  1. 來源
    力扣(LeetCode-640-求解方程)
  2. 問題描述
  • 求解一個給定的方程,將x以字串"x=#value"的形式返回。該方程僅包含’+’,’ - '操作,變數 x 和其對應係數。
    如果方程沒有解,請返回“No solution”。
    如果方程有無限解,則返回“Infinite solutions”。
    如果方程中只有一個解,要保證返回值 x 是一個整數。

  • 輸入: “x+5-3+x=6+x-2”
    輸出: “x=2”

  1. 演算法分析
  1. 獲取關鍵
  • 輸入:一個字串,一個方程
  • 返回:返回解,如果特殊情況,則返回對應的值
  1. 處理邏輯
  • 前言: 邏輯並不複雜,只是處理過程有點複雜。
  • 解析字串,獲取等號兩邊的元素
  • 根據正負收集元素(最關鍵的一步):假如以等號左邊的正數為正的元素,那麼等號右邊的負數則為正。其它相反即可
    -分別處理正負元素,返回解即可
  1. 細節處理
  • 其中正負收集元素的這一步中,非常關鍵,可以使用兩個集合分別收集正負元素。最後統計集合的元素,,返回解。
  1. 程式碼實現
class Solution:
    def sum(self,workList):
        nums,count = 0,0
        for i in workList:
            if i:
                if 'x' not in i:
                    nums += int(i)
                else:
                    if len(i) == 1:
                        count += 1
                    else:
                        count += int(i[:len(i)-1])
        return nums,count
    def getELement(self,workStr,pList,mList):
        # '+x+5+x' len = 8 index = 7
        current = 0
        while len(workStr)-1>current:
            if workStr[current]=='+':
                plushStr = ''
                k = 0
                for i in range(current+1,len(workStr)):
                    k = i
                    if i == len(workStr)-1:
                        plushStr += workStr[i]
                        break
                    if workStr[i] != '+' and workStr[i] != '-':
                        plushStr += workStr[i]
                    else:
                        break
                pList.append(plushStr)
                current = k
            else:
                minusStr = ''
                k = 0
                for i in range(current+1,len(workStr)):
                    k = i
                    if i == len(workStr)-1:
                        minusStr += workStr[i]
                        break
                    if workStr[i] != '+' and workStr[i] != '-':
                        minusStr += workStr[i]
                    else:
                        break
                mList.append(minusStr)
                current = k
        return pList,mList
    def solveEquation(self, equation: str) -> str:
        workList = equation.split('=')
        if workList[0] != '+' and workList[0] != '-': 
            front = '+'+workList[0]
        else:
            front = workList[0]
        if workList[1] != '+' and workList[1] != '-': 
            back = '+'+workList[1]
        else:
            back = workList[1]
        # plus or minus
        plusList,minusList = [],[]
        plusList,minusList = self.getELement(front,plusList,minusList)
        plusList,minusList = self.getELement(back,minusList,plusList)
        plus = self.sum(plusList)
        minus = self.sum(minusList)
        if plus[1] == minus[1]:
            if plus[0] != minus[0]:
                return 'No solution'
            else:
                return 'Infinite solutions'
        else:
            if plus[1] > minus[1]:
                return 'x={}'.format(int((minus[0]-plus[0])/(plus[1]-minus[1])))
            else:
                return 'x={}'.format(int((plus[0]-minus[0])/(minus[1]-plus[1])))
  1. 思考

題目關鍵詞:解析
在現實中簡單的一個加減法求解方程,使用程式碼實現,卻如此複雜。說明自己的程式碼水平還是需要有所提高的。

  • 0725-(簡單)分糖果Ⅱ

  1. 來源
    力扣(LeetCode)-1103-分糖果Ⅱ
  2. 問題描述
  • 排排坐,分糖果。
    我們買了一些糖果 candies,打算把它們分給排好隊的 n = num_people 個小朋友。
    給第一個小朋友 1 顆糖果,第二個小朋友 2 顆,依此類推,直到給最後一個小朋友 n 顆糖果。
    然後,我們再回到隊伍的起點,給第一個小朋友 n + 1 顆糖果,第二個小朋友 n + 2 顆,依此類推,直到給最後一個小朋友 2 * n 顆糖果。
    重複上述過程(每次都比上一次多給出一顆糖果,當到達隊伍終點後再次從隊伍起點開始),直到我們分完所有的糖果。注意,就算我們手中的剩下糖果數不夠(不比前一次發出的糖果多),這些糖果也會全部發給當前的小朋友。
    返回一個長度為 num_people、元素之和為 candies 的陣列,以表示糖果的最終分發情況(即 ans[i] 表示第 i 個小朋友分到的糖果數)。

  • 輸入:candies = 7, num_people = 4
    輸出:[1,2,3,1]
    解釋:
    第一次,ans[0] += 1,陣列變為 [1,0,0,0]。
    第二次,ans[1] += 2,陣列變為 [1,2,0,0]。
    第三次,ans[2] += 3,陣列變為 [1,2,3,0]。
    第四次,ans[3] += 1(因為此時只剩下 1 顆糖果),最終陣列變為 [1,2,3,1]。

  1. 演算法分析
  1. 獲取關鍵
  • 輸入:糖果的個數,人的個數
  • 返回:以集合的方式返回每個人收到糖果的個數
  1. 處理邏輯
  • 按照題目的邏輯走一遍即可
  1. 細節處理
  • 定位索引這個地方可以使用求餘符號%實現。
    例如:設定有n個小朋友
    第0次分糖果,應該定位到第0位小朋友,即0%n=0
    第n+1次分糖果,應該定位到第1位小朋友,即(n+1)%n=1
  1. 程式碼實現
class Solution:
    def distributeCandies(self, candies: int, num_people: int) -> List[int]:
        resultList = [0 for _ in range(num_people)]
        index,count = 0,1
        while candies:
            if candies >= count:
                resultList[index%num_people] += count
                candies = candies - count
                index += 1
                count += 1
            else:
                resultList[index%num_people] += candies
                candies = 0
        return resultList
  1. 思考

題目關鍵詞:順序 分
從看到問題到寫完,沒有花多少時間,而且直接一遍過。
可能因為這道題非常簡單吧。

  • 0725-(中等)用最少數量的箭引爆氣球

  1. 來源
    力扣(LeetCode)-452-用最少數量的箭引爆氣球
  2. 問題描述
  • 在二維空間中有許多球形的氣球。對於每個氣球,提供的輸入是水平方向上,氣球直徑的開始和結束座標。由於它是水平的,所以y座標並不重要,因此只要知道開始和結束的x座標就足夠了。開始座標總是小於結束座標。平面內最多存在104個氣球。
    一支弓箭可以沿著x軸從不同點完全垂直地射出。在座標x處射出一支箭,若有一個氣球的直徑的開始和結束座標為 xstart,xend, 且滿足 xstart ≤ x ≤ xend,則該氣球會被引爆。可以射出的弓箭的數量沒有限制。 弓箭一旦被射出之後,可以無限地前進。我們想找到使得所有氣球全部被引爆,所需的弓箭的最小數量。

  • 輸入:[[10,16], [2,8], [1,6], [7,12]]
    輸出:2
    解釋:
    對於該樣例,我們可以在x = 6(射爆[2,8],[1,6]兩個氣球)和 x = 11(射爆另外兩個氣球)。

  1. 演算法分析
  1. 獲取關鍵
  • 輸入:一個列表
  • 返回:需要最少的弓箭數量
  1. 處理邏輯
  • 這道題是屬於貪心演算法,所以有兩個常規操作:排序和貪心選擇
    問題描述中有一句非常關鍵:由於它是水平的,所以y座標並不重要,因此只要知道開始和結束的x座標就足夠了。
    上面的這句關鍵說明只有x軸方向,即一維的。另外其實更確切地說,排序的關鍵在於結束座標。如下:
    在這裡插入圖片描述那麼引爆以上氣球需要2個弓箭。一個弓箭在x=3,引爆氣球1,2和2;另一個弓箭引爆氣球4即可。
  1. 細節處理
  1. 程式碼實現
class Solution:
    def findMinArrowShots(self, points:list) -> int:
        points.sort(key=lambda x:(x[1]))
        flag = 0
        while points:
            workList = []
            workList.append(points.pop(0))
            while points:
                if workList[0][1] >= points[0][0]:
                    workList.append(points.pop(0))
                else:
                    break
            flag += 1
        return flag
  1. 思考

題目關鍵詞:排序,貪心
以前解決問題,都是按照c或者c++的思維,手寫排序,手寫邏輯。固然非常鍛鍊程式設計能力,但也是重複造輪子。所以,有些時候,使用一些輪子非常重要,當然這些輪子的使用前,必須知道這個輪子是怎麼造的。例如程式碼中的

points.sort(key=lambda x:(x[1]))

即代表根據物件的位置1進行排序。
另外,貪心演算法並不是一種“完美“的解決方案:

  1. 當存在多個最優方案時,無法列舉所有的方案
  2. 最終的方案是基於每一次的最優選擇,而許多問題並不全是走好每一步就代表可以得到最優方案。例如動態規劃問題等等。
  • 0727-(中等)搜尋推薦系統

  1. 來源
    力扣(LeetCode)-1268-搜尋推薦系統
  2. 問題描述
  • 給你一個產品陣列 products 和一個字串 searchWord ,products 陣列中每個產品都是一個字串。
    請你設計一個推薦系統,在依次輸入單詞 searchWord 的每一個字母后,推薦 products 陣列中字首與 searchWord 相同的最多三個產品。如果字首相同的可推薦產品超過三個,請按字典序返回最小的三個。
    請你以二維列表的形式,返回在輸入 searchWord 每個字母后相應的推薦產品的列表。

  • 輸入:products = [“mobile”,“mouse”,“moneypot”,“monitor”,“mousepad”], searchWord = “mouse”
    輸出:[
    [“mobile”,“moneypot”,“monitor”],
    [“mobile”,“moneypot”,“monitor”],
    [“mouse”,“mousepad”],
    [“mouse”,“mousepad”],
    [“mouse”,“mousepad”]
    ]
    解釋:按字典序排序後的產品列表是 [“mobile”,“moneypot”,“monitor”,“mouse”,“mousepad”]
    輸入 m 和 mo,由於所有產品的字首都相同,所以系統返回字典序最小的三個產品 [“mobile”,“moneypot”,“monitor”]
    輸入 mou, mous 和 mouse 後系統都返回 [“mouse”,“mousepad”]

  1. 演算法分析
  1. 獲取關鍵
  • 輸入:xxx
  • 返回:xxx
  1. 處理邏輯
  • 通過問題描述,覺得很高大上,其實通過暴力法很簡單
  • 對產品組進行排序,由於產品組都是字串,所以排序是根據字典排序。
  • 通過兩個for即可不斷遍歷產品組,獲得符合條件產品即可。
  1. 細節處理
  1. 程式碼實現
class Solution:
    def suggestedProducts(self, products, searchWord):
        products.sort()
        result = [[] for _ in range(len(searchWord))]
        workStr = ''
        lenStr = 0
        for i in searchWord:
            workStr += i
            lenStr += 1
            for j in products:
                if j[:lenStr] == workStr and len(result[lenStr-1]) < 3:
                    result[lenStr-1].append(j)
        return result
  1. 思考

題目關鍵詞:暴力 遍歷
現在刷題都是暴力法解決的,往往遇到最多的不是出現錯誤,而是超時。有時總在技巧和暴力之間猶豫不決。兩個方向各有好處,技巧鍛鍊思維,暴力鍛鍊邏輯。雖然更偏向於技巧,但有時因為時間和思維有限,所以走向暴力法。

相關文章