2020年3月20日阿里內推筆試題


  因爲第一批筆試的緣故加上自己家裏買的攝像頭還在路上,所以這一次的筆試我是沒有參加的,根據網上整理出來的題目描述自己完成了這兩道算法題。因爲自己這樣做,並沒有得到驗證,如果哪裏有誤的地方還希望大家能夠積極指正。

題目描述

有一疊撲克牌,每張牌介於1和10之間
有四種出牌方法:

單出1張
出2張對子
出五張順子,如12345
出三連對子,如112233

給10個數,表示1-10每種牌有幾張,問最少要多少次能出完

題目分析

  這個題目如果需要找特定的規則去判斷,顯然是可以的,但是條件有點多。所以就不考慮這種方式,乾脆根據題目中的幾種情形分別去回溯,然後對回溯的結果去比較,選擇最小的一種方式來打牌。
  能出三個對子,顯然需要要求,i,i+1,i+2的數目都大於2。同理,要出順子也要滿足一定的條件,我們只需要對該條件進行分別進行判斷即可。
  大致整理一下代碼的核心思路,從小往大出牌,研究每一個數字可以如何出完。如果不能打連對或者連子的時候,出牌的次數是一定的,如果可以打的時候,計算打連對最少需要出的次數,打連子最少出的次數,然後打對子最少需要出的次數,然後最終取最小,就是出完當前數字的最少次數,然後依次計算每個數字出完的最小次數。
  這裏我只給出核心代碼,也並沒有處理輸入輸出的情況。大家自行處理。

python代碼

class Solution1:
    def leastTimes(self,nums,begin=0):
        res=sum(nums[begin:]) # 表示剩餘牌的總數
        if not res:return 0
        if nums[begin]>0:
            if begin+2<10 and nums[begin]>1 and nums[begin+1]>1 and nums[begin+2]>1:
                for i in range(begin,begin+3): # 打了連對,狀態改變
                    nums[i]-=2
                res=min(1+self.leastTimes(nums,begin),res)
                for i in range(begin,begin+3): # 回溯,恢復狀態
                    nums[i]+=2

            if begin+4<10 and nums[begin+1] and nums[begin+2] and nums[begin+3] and nums[begin+4]:
                for i in range(begin, begin + 5): # 打了連子,狀態改變
                    nums[i] -= 1
                res=min(1+self.leastTimes(nums,begin),res)
                for i in range(begin, begin + 5): # 回溯,恢復狀態
                    nums[i] += 1
            if nums[begin]>1:
                nums[begin] -= 2 # 出對子,因爲出對子之後,可能當前的牌還有剩餘,所以繼續從當前牌型去搜索。
                return min(1+self.leastTimes(nums,begin),res)

            else : # 只有一張,只能考慮單張
                return min(res,1+self.leastTimes(nums,begin+1))
        else: # 表示沒有當前牌型,直接打下一張牌
            return self.leastTimes(nums,begin+1)

  這個代碼就是一個普通的回溯法,需要注意的是雖然知道這個遞歸有很多的重複計算,但是因爲牌只有10種類型,沒有必要優化了。這個題目,是無法優化成動態規劃的(我沒有想到)。
  我們可以在遞歸的時候,使用中間數目爲0的牌型把數組分段,然後最終的結果就是對多段分別遞歸再求和即可,根據上面的代碼很好改。不過多贅述。

題目描述

首先定義上升字符串,對於任意的0<i<len(s)0<i<len(s),s[i]≥s[i−1],比如aaa,abc是,acbd不是
給n個上升字符串,選擇任意個拼起來,問能拼出來的最長上升字符串長度

  這個題目很類似於一個經典的題目,課表安排的問題,課表安排的問題可以使用動態規劃,也可以使用貪心。但是這個題目和課表安排的題目有一點不同的地方就是課表只需要安排的課程數目多即可,而該問題需要總的字符串長度較長。這個區別決定了這個問題不能使用貪心處理。
  但是課程安排問題的動態規劃方法還是可以使用的,所以這個題目就是動態規劃。
  我們定義一個上升字符串爲SijS_{ij},表示該字符串的首字符是 i ,尾字符是 j,則dpj=max(dpj1,dpi+Sij.size())ijdp_j=max(dp_{j-1},dp_i+S_{ij}.size())\quad i\le j。解釋一下這個公式,表示的是以字符j結尾的上升字符串最長的長度是以字符j-1結尾的上升字符串的最長長度,和以字符i結尾加上字符串從SijS_{ij}的長度的最大值。需要注意這裏的i 不是一個值,而是所有可能的取值。
  我們要處理的問題是根據結尾字符j 來處理的,所以我們需要對每一個字符串根據結尾字符排一個序。爲了防止’hhhh’這種情況提前出現,因爲這種情況提前出現了,就會過早使用dphdp_{*h}的和ShhS_{hh}長度更新dphdp_{*h}的結果,我們就需要把ShhS_{hh}放在ShS_{*h}之後處理。
  也就是說,我們根據最後一個字符排序,如果最後一個字符相同,我們則需要把第一個字符越大的越往後邊放。而且排序的過程中,因爲最後一個字符只有26種情況,這裏我們可以考慮使用基數排序或者桶排序,排序我這裏直接使用的是內建的排序,如果大家需要優化可以採用其他的。具體優化細節不談。

python代碼

def longestAscString(strs:List[str])-> int:
    strs=sorted(strs,key=lambda x:(x[-1],x[0]))
    dp=[0]*26
    res=0
    last=0
    for string in strs:
        begin=ord(string[0])-ord('a')
        end=ord(string[-1])-ord('a')
        for i in range(last,end): # 沒有這些字符結尾的最長長度,使用前面的最長長度更新
            dp[i+1]=dp[last]
        dp[end]=max(dp[end],dp[begin]+len(string))
        res=max(res,dp[end])
        last=end

    return res

a=[
    "bcdefhijk",
    "bcd",
    "aaa",
    "eeeefghhh",
    "zzzz",
]
b=['abc',
'hpq',
'qrt',
'jklmnopqr',
'abcjklmnopqr',]

c=['abcd',
'deft',
'efghmnt',
'defghjkl',
'abcddefghjkl',]
print(longestAscString(c))

  我這裏以字符jj結尾的最大長度的上升字符串長度使用數組來存儲,爲了方便映射,使用ACill碼值來映射,所以是ord(i)-ord('a')
  可以看到空間複雜是O(n)O(n)的,算法的時間複雜度瓶頸在排序這裏,如果使用O(n)O(n)級別的排序可以將算法的複雜度降到O(n)O(n)。使用普通的排序,則時間複雜度是O(nlgn)O(nlgn)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章