Leetcode 【524、767、1053、1079】

问题描述:【Two Pointers】524. Longest Word in Dictionary through Deleting
解题思路:

这道题是给一个字符串s和一个单词数组,找到数组里面最长的单词,该单词可以通过删除s的某些字符来得到。如果答案不止一个,返回长度最长且字典序最小的单词。如果答案不存在,返回空字符串。

双指针法。对于单词数组中的每个单词 word,字符串 s 和 word 逐字符比较向后滑动。word 如果能滑到最后,说明可以由 s 删除字符得到,这时更新最大长度。如果下一个 word 的最大长度和上一个 word 最大长度一样,则比较它们的字典序,选取较小的字典序(ans = min(ans, word) 即可,ans 为上一个结果)。

时间复杂度为 O(len(d)*len(s)),d 为单词数组长度,s 为字符串的长度;空间复杂度为 O(1)。

Python3 实现:
class Solution:
    def findLongestWord(self, s: str, d: List[str]) -> str:
        ans = ""
        max_len = 0
        for word in d:
            if len(s) < len(word) or len(word) == 0: continue
            j = 0
            for i in range(len(s)):
                if s[i] == word[j]:  # 对应字符相同
                    j += 1
                if j == len(word):
                    if len(word) > max_len:
                        ans = word
                        max_len = len(word)
                    elif len(word) == max_len:   # 长度相同,保留最小字典序的word
                        ans = min(ans, word)
                    break  # 该word判断完成,终止循环
        return ans

问题描述:【Sort、Heap】767. Reorganize String
解题思路:

这道题是给一个字符串S,检查是否能重新排布其中的字母,使得两相邻的字符不同。

首先可以得知,如果某字符的次数超过 (len(S)+1) // 2,那么一定不可以重构字符串,返回空串。

方法1(Sort):

以 S = "acbaa" 为例,先按照 S 的每个字母出现的次数从大到小排列,得到一个列表,如 A = ['a','a','a','b','c'],然后建立一个和 S 相同长度的列表 ans = [None] * len(S),将 A 中的字符按顺序先安排在 ans 的偶数位置上(ans = ['a',None, 'a', None, 'a']),偶数位置放满后,将剩下一半数字放在奇数位置上。这样即可实现相邻字母不同。

更一般的,如 S = "aaabbbccce",我们将会得到 ans = ['a','b','a','c','a','c','b','c','b','e']。

Python3 实现:

class Solution:
    def reorganizeString(self, S: str) -> str:
        N = len(S)
        A = []
        for (cnt, s) in sorted([(S.count(s), s) for s in set(S)], reverse=True):  # 按字母次数从大到小排列
            if cnt > (N + 1) // 2:  return ""  # 任何一个字符的出现次数不能超过(N+1)//2
            A.extend(s * cnt)
        ans = [None] * N
        ans[::2], ans[1::2] = A[:(N+1)//2], A[(N+1)//2:]  # 将A中前一半字符安排在偶数位置上,后一半安排在奇数位置上
        return "".join(ans)

方法2(Heap):

按照字母出现的次数建立大根堆(实际上可以转化为按照字母出现的次数的负值建立小根堆,即 (-次数, 字母))。然后,每次从堆里面取出两个元素,依次加入到结果 ans 中,并将它们对应的次数减 1。如果不为 0,重新放入堆中。

这其实是一种贪婪的策略,每次取出的两个元素肯定是不相邻的。

例如,S = "abcaa",我们建立的堆的过程为:

       (-3, 'a')                        (-2, 'a')                          (-1, 'a')
        /      \         ->               /                   ->   
(-1, 'b')  (-1, 'c')                  (-1, 'c')     
  • 先取出两个元素,(-3, 'a') 和 (-1, 'b'),加入到结果 ans = "ab",然后次数各减 1,得到 (-2, 'a') 和 (0, 'b')。因为 'a' 不为 0,因此加入到堆中继续判断;
  • 再取出两个元素,(-2, 'a') 和 (-1, 'c'),加入到结果 ans = "abac",然后次数各减 1,得到 (-1, 'a') 和 (0, 'c')。因为 'a' 不为 0,因此加入到堆中继续判断;
  • 堆中元素数量小于 2,终止判断,将最后的一个字符 'a' 加入到结果,ans = "abaca",即得到了正确的答案。

Python3 实现:

import heapq
class Solution:
    def reorganizeString(self, S: str) -> str:
        N = len(S)
        heap = []
        for s in set(S):
            cnt = S.count(s)
            if cnt > (N + 1) // 2:
                return ""
            heapq.heappush(heap, (-cnt, s))  # 建立小根堆(-次数,字母)
        ans = ""
        while len(heap) >= 2:  # 堆中至少有两个元素
            cnt1, s1 = heapq.heappop(heap)  # 每次取出堆顶两个元素
            cnt2, s2 = heapq.heappop(heap)
            ans += s1 + s2  # 依次加入的结果
            cnt1 += 1  # 次数减少(存的是负值)
            cnt2 += 1
            if cnt1 < 0:  # 次数没有减少到0重新加入堆中
                heapq.heappush(heap, (cnt1, s1))
            if cnt2 < 0:
                heapq.heappush(heap, (cnt2, s2))
        if len(heap) == 1:  #如果S长度为奇数
            ans += heapq.heappop(heap)[1]
        return ans

问题描述:【Math】1053. Previous Permutation With One Swap
解题思路:

这道题是给一个正整数数组 A,返回可在一次交换(交换两数字 A[i] 和 A[j] 的位置)后得到的、按字典序排列小于 A 的最大可能排列。

这道题做了好久,前面提交了几次都 WA,就放下了。过了一周后再看这道题,突然有了思路,很快 AC 了。而且弄明白题意后,代码很简单。

很明显,这道题需要找规律。我们先观察以下几组输入输出数据:

[1,1,5] -> [1,1,5]
[1,9,4,6,7] -> [1,7,4,6,9]
[1,9,4,6,10] -> [1,6,4,9,10]
[3,1,1,3] -> [1,3,1,3]
[9,3,2,2,3] -> [9,2,3,2,3]
[8,5,7,2,4] -> [8,5,4,2,7]

由此,我们观察到规律,需要交换的第一个位置 first 是从右到左遍历的第一个逆序对 A[i] > A[j] 中 i 的位置(如 [8,5,7,2,4] 中从右到左遍历第一个逆序对为 7 > 2,则交换的第一个位置就是 first = 2)。如果不存在逆序对,说明原序列非严格递增(如 [1,1,5]),直接返回原数组即可。第二个交换的位置 second 是从 first 的下一个位置开始,小于 A[first] 且最靠近 A[first] 的最大值的索引位置(如 [1,9,4,6,10] 中,first = 1,小于 A[1] = 9 的最大值为 6,其对应索引 second = 3;再比如 [3,1,1,3] 中,first = 0,小于 A[0] = 3 的最大值是 1,但是要选择最靠近 A[first] 的 1,即 second = 1 而不是 2)。最后,交换 first 和 second 位置的元素即可得到答案。时间复杂度为 O(n)。

Python3 实现:
class Solution:
    def prevPermOpt1(self, A: List[int]) -> List[int]:
        switch = -1  # 第一个数交换的位置
        for i in range(len(A) - 1, 0, -1):
            if A[i] < A[i-1]:
                switch = i - 1
                break
        if switch == -1: # 升序,不交换
            return A
        second_max = ind = 0  # 第二个数及其交换的位置
        for i in range(switch + 1, len(A)):
            if second_max < A[i] < A[switch]:  # 小于A[switch]且离A[switch]的最大值
                second_max, ind = A[i], i
        A[switch], A[ind] = A[ind], A[switch]  # 交换两个位置的元素
        return A

问题描述:【BFS】1079. Letter Tile Possibilities
解题思路:

这道题是给一个字符串,返回所有非空字母序列的数目。

看到数据范围为 <= 7,因此用回溯法去做,对不同长度的字母序列进行全排列,并保存到集合中(去重)。

Python3 实现:
class Solution:
    def numTilePossibilities(self, tiles: str) -> int:
        def search(k, path):
            for i in range(N):
                if not b[i]:
                    path += tiles[i]
                    b[i] = True
                    ans.add(path)  # 不同长度的字母序列都保存
                    if k != N:
                        search(k+1, path)
                    b[i] = False
                    path = path[:-1]
                
        ans = set()  # 防止重复
        N = len(tiles)
        b = [False] * len(tiles)
        search(1, "")
        return len(ans)

还可以使用 Python 中 itertools 中自带的 permutations(str/list, k) 函数进行全排列统计,一行代码即可搞定:

class Solution:
    def numTilePossibilities(self, tiles: str) -> int:
        return sum(len(set(itertools.permutations(tiles, i))) for i in range(1, len(tiles) + 1))

其中,itertools.permutations(tiles, i) 得到不同长度的全排列,set 去重,len 取返回集合的长度,sum 对不同长度的序列求和。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 今日分享! 做人,不仅要阳光、要自信、更要有一个平和的心、 心与花的距离在于欣赏.在于懂得. 心与世界的距离在于容...
    龙小莉阅读 115评论 0 0