LeetCode 1079. Letter Tile Possibilities

问题描述

有一些砖块的集合 tiles,每个砖块印有一个字母 tiles[i]。返回可以组合的非空字符串的个数。

栗 1:

输入:"AAB"
输出:8

可能的序列为:A, B, AA, AB, BA, AAB, ABA, BAA

栗 2:

输入:"AAABBC"
输出:188

注意:

  1. 1 <= tiles.length <= 7
  2. 字符包含大写字母

想看英文原文的戳这里

解题思路

我的解法

根据栗 1 可以看出,可能的组合包括由一个字符组成,两个字符组成,...,n 个字符组成的所有字符串。其中 n 为给定的 tiles 的长度。

比如给定 tiles = "AAB",那么我们可以手动计算出如下结果:

长度为 1 的字符串:AB
长度为 2 的字符串:ABAABA
长度为 3 的字符串:AABABABAA

从上,我们可以发现一些规律。

  1. 长度为 1 的字符串,由 tiles 中不相同的字符组成。

    AAB 中不同的字符为 AB。于是组成了两个长度为 1 的字符串。

  2. 长度为 2 的字符串,在长度为 1 的字符串基础上再组合一个字符而成。

    这一个字符在 AB 中任意挑选一个即可。
    于是乎,AAB 分别组合,BAB 分别组合。去重后得到 AAABBA。需要注意新的字符串需满足 AB 的个数限制,这里都是满足的。

  3. 长度为 3 的字符串,在长度为 2 的字符串的基础上再组合一个字符而成。

    同上,AAABBA 分别与 AB 组合,去重后得到 AAAABABAAAABABBBAB

    但是有些是不满足条件的,因为各个字符的数量是有限制的,比如 AAAABBBAB。因为 A 总共只有 2 个,B 只有 1 个,所以需要筛除。

因此,对于数量个数问题,我们需要记录下当前字符串中每个字符的个数,以便在下一次组合时判断是否能组合为新的字符串。

比如 AB,记录字符个数为 { A: 1, B: 1}

B 组合时,由于 B 总共的个数只有 1 个,所以组合不了。
A 组合时,由于 A 总共的个数 2 个,所以可以组合,更新记录为 { A: 2, B: 1}

因此,思路如上所述,根据上一个字符串集合再分别组合一个字符得到新的字符串集合,直至字符串长度达到 n 为止。

点击查看具体代码

递归解法

这种方式比较简单,主要思想也是通过记录字符串中每个字符的个数来判断是否能组成新的字符串,但是不需要记录上一次的字符串集合

比如 AAB,字符个数为 A: 2, B: 1

对于长度为 1 的字符串,可以任意取 AB
对于长度为 2 的字符串:

  • 如果上一步中取 A,那么剩余字符个数为 A: 1, B: 1AB 都还剩一个,可以任意取其一,则有 AAAB
  • 如果上一步取 B,那么剩余字符个数为 A: 2, B: 0,只能取 A,则有 BA

对于长度为 3 的字符串:

  • 如果上一步中取 AA,剩余字符数为 A: 0, B: 1,只能取 B,则有 AAB
  • 如果取 AB,剩余字符数为 A: 1, B: 0, 只能取 A,则有 ABA
  • 如果取 BA,剩余字符数为 A: 1, B: 0,只能取 A,则有 BAA

代码也很简洁,如下所示。有种一层层剥茧的意思,每个字符的个数在逐个减少。

// 记录每个字符的数量
public int numTilePossibilities(String tiles) {
    int[] count = new int[26];
    for (char c : tiles.toCharArray()) count[c - 'A']++;
    return dfs(count);
}

int dfs(int[] arr) {
    int sum = 0;

    // 遍历每个可能出现的字符,将其个数 -1
    for (int i = 0; i < 26; i++) {
        // 当前可用字符数为 0
        if (arr[i] == 0) continue;
        
        // 个数 +1
        sum++;
        
        // 字符数 -1
        arr[i]--;
        
        // 继续计算 -1 之后,可能的组合数
        sum += dfs(arr);
            
        // 恢复字符数
        arr[i]++;
    }
    return sum;
}

假设字符串为 AAB,那么其调用堆栈如下:

sum = 0
dfs({A:2, B:1})
    // 第一次循环
    sum += 1
    // 取 A,A-1
    dfs({A:1, B:1})
    
        // 第一次循环
        sum += 1
        // 取 A,A-1
        dfs({A:0, B:1})
            sum += 1
            
            // 由于 A 已经为 0,跳过,只能 B-1
            dfs({A:0, B:0})

        // 第二次循环
        sum += 1
        // 取 B,B-1
        dfs({A:1, B:0})
            
            sum += 1
            // 由于 B 已经为 0,跳过,只能 A-1
            dfs({A:0, B:0})


    // 第二次循环
    sum += 1
    // 取 B,B-1。由于有恢复的步骤,所以此时 A 仍为 2。
    dfs({A:2, B:0})
    
        sum += 1
        // A-1
        dfs({A:1, B:0})
        
            sum += 1
            // 由于 B 已经为 0,跳过,只能 A-1
            dfs({A:0, b:0})

数学解法

利用数学公式,若长度为 n 的字符串中有 i 个不同字符,其重复的次数分别为 m[1], ..., m[i] 。则可组合的个数如下:

n! / (m[1]! * m[2]! * .. * m[i]!)

那么就需要确定长度为 n 的字符串,会有哪些不同字符及其个数的组合方式。

AAABB 为例,若想构成长度为 4 的字符串,组合可以是 3A1B2A2B,即满足 A + B 的个数等于 4 即可。现在只需要找出这些组合,套用公式分别计算出个数相加,即得到长度为 4 的字符串所对应的全部排列数。

但注意 AAABBAAA 是相同组合,因为它们的字符及对应的个数都相同,与顺序无关。

因此,只需计算出长度分别为 1~n 的字符串组合,利用上述公式求和即可。

c++ 递归解法,采用递归计算出组合并去重。

python 解法,该种方法计算组合比较巧妙,也是采用数学方法。

穷举法

使用递归计算出所有的排列组合,放入 set 中去重。

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