[Python] 算法心得—字符串篇

六個(gè)算法問題万栅,使用python學(xué)習(xí)算法匪补。

1.1 旋轉(zhuǎn)字符串

給定字符串伞辛,要求把字符串前面若干個(gè)字符移動(dòng)到字符串尾部。要求時(shí)間復(fù)雜度O(n)夯缺,空間復(fù)雜度O(1)始锚。
如:'abcdefg'前面的2個(gè)字符'a'和'b'移到字符串尾部,就是'cdefgab'喳逛。

解法一:暴力移位法
def left_shift_one(s):
    slist = list(s)
    temp = slist[0]
    for i in range(1, len(s)):
        slist[i - 1] = slist[i]
    slist[len(s) - 1] = temp
    s = ''.join(slist)
    return s


def left_rotate_str(s, n):
    while n > 0:
        s = left_shift_one(s)
        n -= 1
    return s

時(shí)間復(fù)雜度O(n*len(s)), 空間復(fù)雜度O(1)棵里。

解法二:三步反轉(zhuǎn)法

例如給定字符串'abcdefg'润文,若要讓ab翻轉(zhuǎn)到最后姐呐,只需三步操作:
step 1. 字符串分為兩部分,即:X=ab, Y=cdefg典蝌;
step 2. 將X曙砂、Y均反轉(zhuǎn),得到:X=ba, Y=gfedc骏掀;
step 3. 將step2得到的結(jié)果反轉(zhuǎn)鸠澈,即將'bagfedc'反轉(zhuǎn),得到:cdefgab截驮,即為想要的結(jié)果笑陈。

def reserve_str(s):
    s_list = list(s)
    start, end = 0, len(s)-1
    while start < end:
        s_list[start], s_list[end] = s_list[end], s_list[start]
        start += 1
        end -= 1
    return ''.join(s_list)


def left_rotate_str(s, n):
    x, y = list(s[:n]), list(s[n:])
    x = reserve_str(x)
    y = reserve_str(y)
    res = reserve_str(x+y)
    return ''.join(res)

時(shí)間復(fù)雜度O(n),空間復(fù)雜度O(1)葵袭。

1.2 鏈表反轉(zhuǎn)

給出一個(gè)鏈表和一個(gè)數(shù)k涵妥,比如:鏈表為1->2->3->4->5->6, k=2,翻轉(zhuǎn)后為2->1->6->5->4->3坡锡;若k=3蓬网,翻轉(zhuǎn)后3->2->1->6->5->4。

鏈表反轉(zhuǎn)的方式
解法一: 循環(huán)迭代

基本思想就是將每個(gè)節(jié)點(diǎn)的向前鹉勒、向后指向變換帆锋。

def reverse_loop(head):
    if not head or not head.next:
        return head
    pre = None
    while head:
        next = head.next # 緩存當(dāng)前節(jié)點(diǎn)的向后指針,下次迭代用
        head.next = pre # 把當(dāng)前節(jié)點(diǎn)的向前指針作為當(dāng)前節(jié)點(diǎn)的向后指針
        pre = head # 作為下次迭代(當(dāng)前節(jié)點(diǎn))的向前指針
        head = next # 作為下次迭代的(當(dāng)前)節(jié)點(diǎn)
    return pre
解法二:遞歸

遞歸的思想就是:

head.next = None
head.next.next = head.next
head.next.next.next = head.next.next
...

代碼實(shí)現(xiàn):

def reverse_recursion(head):
    if not head or not head.next:
       return head
    new_head = reverse_recursion(head.next)
    
    head.next.next = head
    head.next = None
    
    return new_head
解法
def divide_linked_list(head, k):
    if head is None or head.next is None:
        return head
    p1 = head
    p2 = head.next
    while k >0:
        tmp = p2.next
        p2.next = p1
        p1 = p2
        p2 = tmp
        k -= 1
    p3 = reverse_recursion(p2)   # 后部分翻轉(zhuǎn)
    head.data = p3.data
    head.next = p3.next
    return p1

2.1 字符串包含

給定一長一短的兩個(gè)字符串A禽额,B锯厢,假設(shè)A長B短,要求判斷B是否包含在字符串A中绵疲。

解法一:暴力法
def string_contain(string_a, string_b):
    for b in string_b:
        if b not in string_a:
            return False
    return True

假設(shè)A字符串長n哲鸳,B字符串長m,那么這種暴力解需要O(n*m)次操作盔憨。

解法二:線性掃描

這種方法首先需要將A, B字符串排序徙菠,隨后同時(shí)對(duì)這兩個(gè)字符串依次輪詢。

def string_contain(string_a, string_b):
    list_a = sorted(string_a)
    list_b = sorted(string_b)
    pa, pb = 0, 0
    while pb < len(list_b):
        while pa < len(list_a) and (list_a[pa] < list_b[pb]):
            pa += 1
        if (pa >= len(list_a)) or (list_a[pa] > list_b[pb]):
            return False
        pb += 1
    return True

排序需要O(mlogm) + O(nlogn)次操作郁岩,之后的線性掃描則只需要O(m+n)次操作婿奔。

解法三:素?cái)?shù)對(duì)應(yīng)

將每個(gè)字母與一個(gè)素?cái)?shù)相對(duì)應(yīng)。遍歷長字符串A问慎,將每個(gè)字母對(duì)應(yīng)的素?cái)?shù)相乘得到一個(gè)乘積萍摊。再遍歷短字符串B,將每個(gè)字符對(duì)應(yīng)的素?cái)?shù)除A的乘積如叼,若不能整除冰木,說明B中對(duì)應(yīng)的字符不在A字符串中。

from functools import reduce

def string_contain(string_a, string_b):
    char_map = {'a': 2, 'b': 3, 'c': 5, 'd': 7, 'e': 11, 'f': 13, 'g': 17, 'h': 19, 'i': 23, 'j': 29, 'k': 31, 'l': 37,
                'm': 41, 'n': 43, 'o': 47, 'p': 53, 'q': 59, 'r': 61, 's': 67, 't': 71, 'u': 73, 'v': 79, 'w': 83,
                'x': 89, 'y': 97, 'z': 101}
    pd_a = reduce(lambda x, y: x * y, [char_map[x] for x in string_a])
    for b in list(string_b):
        if pd_a % char_map[b] != 0:
            return False
    return True

需要O(m+n)次操作,但要考慮素?cái)?shù)相乘的結(jié)果有可能導(dǎo)致正數(shù)溢出踊沸。

3.1 回文字符串

判斷一個(gè)字符串正著讀和倒著讀是否一樣歇终,比如:'abcba'即為回文字符串。

解法一:頭尾指針向中間掃描

同時(shí)從字符串的頭尾向中間掃描逼龟,知道頭尾相遇评凝。如果所有字符都一樣,那么這就是一個(gè)回文字符串腺律。

def is_palindrome(s):
    s = list(s)
    if len(s)>0:
        start, end = 0, len(s)-1
        while start<end:
            if s[start] != s[end]:
                return False
            start += 1
            end -= 1
        return True
    return True
解法二:中間向頭尾掃描

也可以從字符串的中間向頭尾掃描奕短,如果所有字符都一樣,就認(rèn)為這是個(gè)回文字符串匀钧。

def is_palindrome(s):
    s = list(s)
    if len(s) > 0:
        front = len(s) // 2 - 1 if len(s) % 2 == 0 else len(s) // 2
        back = len(s) // 2
        while front >= 0 and back <= len(s)-1:
            if s[front] != s[back]:
                return False
            front -= 1
            back += 1
        return True
    return True

3.2 回文鏈表

如何判斷一條單向鏈表是不是'回文'呢翎碑?
對(duì)于單項(xiàng)鏈表也可以像字符串那樣,找到其中點(diǎn)榴捡,然后將后半翻轉(zhuǎn)(見 1.1.2 鏈表反轉(zhuǎn))杈女,再同時(shí)從鏈表頭部和中間開始遍歷比較。

def is_palindrome(self, head):
    fast = slow = head
    while fast and fast.next:
        fast = fast.next.next
        slow = slow.next
    node = None
    while slow:
        current = slow
        slow = slow.next
        current.next = node
        node = current
    while node:
        if node.data != head.data:
            return False
        node = node.next
        head = head.next
    return True

4.1 最長回文子串

給定一個(gè)字符串吊圾,求它的最長回文子串的長度达椰。

解法一:遍歷所有元素

首先想到的是遍歷字符串中的所有元素,用兩個(gè)指針以某個(gè)元素為中心向左右擴(kuò)展项乒,記錄并更新得到的最長的回文長度啰劲。在這里需要注意,奇數(shù)長度與偶數(shù)長度的回文字符串的判斷稍有區(qū)別檀何,需要分開處理蝇裤。

def _odd(i, max, s, id):
    j, temp = 0, 0
    while j <= i and i + j < len(s):
        if s[i - j] != s[i + j]:
            break
        temp = j * 2 + 1
        j += 1
    if temp > max:
        max = temp
        id = i
    return max, id

def _even(i, max, s, id):
    j, temp = 0, 0
    while j <= i and i + j + 1 < len(s):
        if s[i - j] != s[i + j + 1]:
            break
        temp = j * 2 + 2
        j += 1
    if temp > max:
        max = temp
        id = i
    return max, id

def longest_palindrome(s):
    if len(s) == 0:
        return 0
    max, id = -1, 0
    for i in range(len(s)):
        odd_res = _odd(i, max, s, id)
        max, id = odd_res[0], odd_res[1]
        even_res = _even(i, max, s, id)
        max, id = even_res[0], even_res[1]
    return s[id - max // 2:id + (max + 1) // 2] if max % 2 != 0 else s[id - max // 2+1:id + max // 2 + 1]
解法二: 統(tǒng)一處理奇偶

上述解法中,我們需要特別區(qū)分奇偶回文字符串的情況频鉴。那么有沒有方法可以將所有情況都轉(zhuǎn)換為奇數(shù)處理呢栓辜?

可以通過在每個(gè)字符的兩邊都插入一個(gè)特殊的符號(hào),將所有可能的奇數(shù)或偶數(shù)長度的回文子串都轉(zhuǎn)換成了奇數(shù)長度垛孔。比如 abba 變成 #a#b#b#a#藕甩,aba變成 #a#b#a#。

此外周荐,還可以在首位各加上特殊符號(hào)狭莱,這樣就不用特殊處理越界問題,比如^#a#b#a#$概作。

def longest_palindrome(s):
    s_j = '#'.join('^{}$'.format(s))
    if len(s_j) == 3:
        return 0
    max, id = -1, 0
    for i in range(len(s_j)):
        j = 0
        temp = 0
        while j <= i and i + j < len(s_j):
            if s_j[i - j] != s_j[i + j]:
                break
            temp = j * 2
            j += 1
        if temp > max:
            max = temp
            id = i // 2 - 1
    return s[id - max // 4:id + (max + 1) // 4 + 1] if max % 4 != 0 else s[id - max // 4 + 1:id + max // 4 + 1]
解法三: Manacher算法

下面將用到神奇的Manacher算法腋妙,使求最長回文子串的時(shí)間復(fù)雜度為線性O(shè)(N)。

Manacher算法解析

def longest_palindrome(s):
    # Transform S into T.
    # For example, S = "abba", T = "^#a#b#b#a#$".
    # ^ and $ signs are sentinels appended to each end to avoid bounds checking
    s_j = '#'.join('^{}$'.format(s))
    n = len(s_j)
    P = [0] * n
    id = mx = 0
    for i in range(1, n - 1):
        P[i] = (mx > i) and min(mx - i, P[2 * id - i])  # equals to i' = id - (i-id)
        # Attempt to expand palindrome centered at i
        while s_j[i + 1 + P[i]] == s_j[i - 1 - P[i]]:
            P[i] += 1

        # If palindrome centered at i expand past mx,
        # adjust center based on expanded palindrome.
        if i + P[i] > mx:
            id, mx = i, i + P[i]

    # Find the maximum element in P.
    maxLen, centerIndex = max((n, i) for i, n in enumerate(P))
    return s[(centerIndex - maxLen) // 2: (centerIndex + maxLen) // 2]

5 交替字符串

輸入三個(gè)字符串s1讯榕、s2和s3骤素,判斷第三個(gè)字符串s3是否由前兩個(gè)字符串s1和s2交錯(cuò)而成匙睹,即不改變s1和s2中各個(gè)字符原有的相對(duì)順序,例如當(dāng)s1 = “aabcc”谆甜,s2 = “dbbca”垃僚,s3 = “aadbbcbcac”時(shí),則輸出True规辱,但如果s3=“accabdbbca”,則輸出False栽燕。

這道題我們考慮用動(dòng)態(tài)規(guī)劃的方法罕袋,定義dp[i][j]為s1的前i和字串和s2和前j個(gè)字串是否構(gòu)成交替字符串。

可以分兩種情況討論:

    1. 如果s1當(dāng)前字符(即s1[i-1])等于s3當(dāng)前字符(即s3[i+j-1])碍岔,且dp[i-1][j]為真浴讯,那么可以取s1當(dāng)前字符而忽略s2的情況,dp[i][j]返回真;
    1. 如果s2當(dāng)前字符等于s3當(dāng)前字符蔼啦,并且dp[i][j-1]為真榆纽,那么可以取s2而忽略s1的情況,dp[i][j]返回真捏肢,其它情況奈籽,dp[i][j]返回假

狀態(tài)轉(zhuǎn)移為:
dp[i][j] = (dp[i-1][j]&& s1[i-1]==s3[i+j-1]) || dp[i][j-1] && s2[j-1]==s3[i+j-1]

代碼實(shí)現(xiàn)如下:

def is_inter_leave(s1, s2, s3):
    l_1, l_2, l_3 = len(s1), len(s2), len(s3)
    if l_1 + l_2 != l_3:
        return False
    dp = [[False for _ in range(l_1 + 1)] for _ in range(l_2 + 1)]
    dp[0][0] = True
    for i in range(1, l_1 + 1):
        dp[0][i] = dp[0][i - 1] and (s3[i - 1] == s1[i - 1])
    for i in range(1, l_2 + 1):
        dp[i][0] = dp[i - 1][0] and (s3[i - 1] == s2[i - 1])
    for i in range(1, l_2 + 1):
        for j in range(1, l_1 + 1):
            if (dp[i][j - 1] and s1[j - 1] == s3[i + j - 1]) or (dp[i - 1][j] and s2[i - 1] == s3[i + j - 1]):
                dp[i][j] = True
    return dp[l_2][l_1]

6 字符串編輯距離

給定一個(gè)源串和目標(biāo)串,能夠?qū)υ创M(jìn)行如下操作:

  • 在給定位置上插入一個(gè)字符
  • 替換任意字符
  • 刪除任意字符
    寫一個(gè)程序鸵赫,返回最小操作數(shù)衣屏,使得對(duì)源串進(jìn)行這些操作后等于目標(biāo)串,源串和目標(biāo)串的長度都小于2000辩棒。

這題我們依舊可以采用動(dòng)態(tài)規(guī)劃的方法狼忱。例如字符串'ALGORITHM'變成'ALTRUISTIC',那么把相關(guān)字符各自對(duì)齊后一睁,如下圖:

把源串S[0...i]='ALGORITHM'編輯成下面的目標(biāo)串T[0...j]='ALTRUISTIC'钻弄,對(duì)于字符s[i]和t[j]的對(duì)應(yīng),有以下四種情況:

  • 目標(biāo)串空白者吁,即S + 字符X窘俺,T + 空白,意味著源串要?jiǎng)h字符砚偶,則操作數(shù)為dp[i-1, j] + 1
  • 源串空白批销,即S + 空白,T + 字符染坯,意味著源串需要插入字符均芽,則操作數(shù)為dp[i, j-1] + 1
  • 源串的字符和目標(biāo)串的字符不一樣,即S + 字符X单鹿,T + 字符Y掀宋,S變成T,意味著源串要修改字符,操作數(shù)為dp[i - 1, j - 1]
  • 源串的字符和目標(biāo)串的字符一樣劲妙,不用修改湃鹊,操作數(shù)為dp[i - 1, j - 1] + 1

代碼如下:

def edit_distance(source, target):
    len_s = len(source)
    len_t = len(target)
    dp = [[0 for _ in range(len_t+1)] for _ in range(len_s+1)]
    for i in range(len_s+1):
        dp[i][0] = i
    for j in range(len_t+1):
        dp[0][j] = j
    for i in range(1, len_s+1):
        for j in range(1, len_t+1):
            if source[i-1] == target[j-1]:
                dp[i][j] = dp[i-1][j-1]
            else:
                dp[i][j] = 1+ min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])
    return dp[len_s][len_t]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市镣奋,隨后出現(xiàn)的幾起案子币呵,更是在濱河造成了極大的恐慌,老刑警劉巖侨颈,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件余赢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡妻柒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痹屹,“玉大人志衍,你說我怎么就攤上這事〈航校” “怎么了暂殖?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵呛每,是天一觀的道長晨横。 經(jīng)常有香客問我,道長啥供,這世上最難降的妖魔是什么伙狐? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上顷帖,老公的妹妹穿的比我還像新娘妄呕。我一直安慰自己绪励,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蛉腌,像睡著了一般烙丛。 火紅的嫁衣襯著肌膚如雪河咽。 梳的紋絲不亂的頭發(fā)上介评,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天情屹,我揣著相機(jī)與錄音,去河邊找鬼。 笑死凌摄,一個(gè)胖子當(dāng)著我的面吹牛忙干,可吹牛的內(nèi)容都是我干的捐迫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼懈玻,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼艺栈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起毅人,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丈莺,沒想到半個(gè)月后划煮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缔俄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年弛秋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俐载。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蟹略,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出遏佣,到底是詐尸還是另有隱情挖炬,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布状婶,位于F島的核電站茅茂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏太抓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一令杈、第九天 我趴在偏房一處隱蔽的房頂上張望走敌。 院中可真熱鬧,春花似錦逗噩、人聲如沸掉丽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捶障。三九已至,卻和暖如春纲刀,著一層夾襖步出監(jiān)牢的瞬間项炼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工示绊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锭部,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓面褐,卻偏偏與公主長得像拌禾,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子展哭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 本文出自 Eddy Wiki 湃窍,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-code.h...
    eddy_wiki閱讀 9,329評(píng)論 0 30
  • 在朋友的推薦下得知了樂刻健身闻蛀,本著反正這么便宜,買個(gè)一個(gè)月也不虧的想法辦了張?jiān)驴校挥直局凑@么便宜就約個(gè)教練上節(jié)...
    ShaneJL閱讀 2,645評(píng)論 0 3
  • 跑步觉痛、足球操、跳繩……這是太康縣龍曲鎮(zhèn)第一初級(jí)中學(xué)精心設(shè)計(jì)和科學(xué)安排的大課間活動(dòng)墨坚。 整齊劃一的步伐 節(jié)奏感強(qiáng)烈的足...
    跟著蝸牛學(xué)走路閱讀 667評(píng)論 0 1
  • ……每天看一點(diǎn)
    A_取個(gè)帥氣的昵稱吧閱讀 208評(píng)論 0 0
  • 2017屆畢業(yè)生跳蚤市場(chǎng)安排及須知 2017屆畢業(yè)生跳蚤市場(chǎng)開放時(shí)間為6月10日-12日秧饮,每天16:00—20:...
    金魚Joey閱讀 196評(píng)論 0 0