`
cloverprince
  • 浏览: 127397 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

字母排序问题

阅读更多
原题叙述:http://www.iteye.com/topic/15295

引用
假设有这样一种字符串,它们的长度不大于 26 ,而且若一个这样的字符串其长度为 m ,则这个字符串必定由 a, b, c ... z 中的前 m 个字母构成,同时我们保证每个字母出现且仅出现一次。比方说某个字符串长度为 5 ,那么它一定是由 a, b, c, d, e 这 5 个字母构成,不会多一个也不会少一个。嗯嗯,这样一来,一旦长度确定,这个字符串中有哪些字母也就确定了,唯一的区别就是这些字母的前后顺序而已。

现在我们用一个由大写字母 A 和 B 构成的序列来描述这类字符串里各个字母的前后顺序:

如果字母 b 在字母 a 的后面,那么序列的第一个字母就是 A (After),否则序列的第一个字母就是 B (Before);
如果字母 c 在字母 b 的后面,那么序列的第二个字母就是 A ,否则就是 B;
如果字母 d 在字母 c 的后面,那么 …… 不用多说了吧?直到这个字符串的结束。

这规则甚是简单,不过有个问题就是同一个 AB 序列,可能有多个字符串都与之相符,比方说序列“ABA”,就有“acdb”、“cadb”等等好几种可能性。说的专业一点,这一个序列实际上对应了一个字符串集合。那么现在问题来了:给你一个这样的 AB 序列,问你究竟有多少个不同的字符串能够与之相符?或者说这个序列对应的字符串集合有多大?注意,只要求个数,不要求枚举所有的字符串。



这个结构很有意思。给定一个字符串的集合,求把新的字母插入到最后一个字母之前或之后,生成的新的字符串集合。

直观的想法会是从头模拟,对于集合里面每个字符串,试着把新字母往里插,一个字符串能插出0到n个结果,n是字符串的长度。然后把每个字符串插出来的结果合并。但是,这个集合明显是指数式爆炸增长的阿。这样穷举显然是不行的。

穷举每一个元素不行,那就想想,集合是不是可以分成多个子集,每个子集的处理方式都是相同的?

很容易观察到,新字母的插入位置,只和最后一个字母相关。比如要把z插入由a-y组成的字符串里,我们只要关心y所在的位置就可以了。不管其他字母如何排列,我们都能用同样的方式插。

更好的想法是反过来想:

比如我们要把z插在字符串第9个位置,放置在y之后(After),
那么,“我把z插在第9个位置”这句话,就可以分解成如下几种情况:
“原来y在第0个位置,我把z插在第九个位置”
“原来y在第1个位置,我把z插在第九个位置”
“原来y在第2个位置,我把z插在第九个位置”
“原来y在第3个位置,我把z插在第九个位置”
“原来y在第4个位置,我把z插在第九个位置”
“原来y在第5个位置,我把z插在第九个位置”
“原来y在第6个位置,我把z插在第九个位置”
“原来y在第7个位置,我把z插在第九个位置”
“原来y在第8个位置,我把z插在第九个位置”

然后,我们再问自己:
原来的集合里,有多少个字符串满足“原来y在第0个位置”?经计算,有k[0]个
原来的集合里,有多少个字符串满足“原来y在第1个位置”?经计算,有k[1]个
原来的集合里,有多少个字符串满足“原来y在第2个位置”?经计算,有k[2]个
原来的集合里,有多少个字符串满足“原来y在第3个位置”?经计算,有k[3]个
...
原来的集合里,有多少个字符串满足“原来y在第7个位置”?经计算,有k[7]个
原来的集合里,有多少个字符串满足“原来y在第8个位置”?经计算,有k[8]个

最后,我们问自己:
新的集合中,有多少个字符串满足“现在的z在第9个位置”?
计算的方法就是把k[0],k[1],k[2]一直到k[8]全部加起来。


如果你要把z放到第9位置,但是要插到y的前面(Before),那就算算:
“原来y在第9个位置,我把z插在第九个位置”(原来y在第9个,插入z之后,y被挤到第10个了)
“原来y在第10个位置,我把z插在第九个位置”
“原来y在第11个位置,我把z插在第九个位置”
。。。
“原来y在第24个位置,我把z插在第九个位置”
然后加起来。


如果你把z能插的位置都算一遍,然后再加起来,就是结果了。但是,我们的中间步骤需要保留这个“集合中,有多少字符串,z在第n个位置”(for n in 0..25)


算法就出来了:

保留一个数组:第i个元素储存“集合里有多少字符串满足‘最后一个字母在第i个位置’”。
显然初始值是[1]。只有一个元素,就是1。因为集合是{"a"},a只能在第0个位置,且只有1种情况。
当读到一个字符(A或B),如果是A,就生成一个新的数组,第i个元素是旧数组第0,1,...,i-1个元素的数值的和。如果是B,就是i,i+1,...,n-1个元素之和。n是原数组的大小。
读完最后一个字母,算完,将新数组元素加起来,就是结果。


时间复杂度:O(n^3)。一共n个字母,数组长度随字母个数线性增长。每一个字母都是n^2的,n是数组长度。因为要对每个新数组的元素,遍历一遍旧数组。
空间复杂度:O(n)。可以用两个数组轮流使用。Haskell和Java都有垃圾回收,不用太在意。


实现:

module Main where

main = do
    ln <- getLine
    putStrLn $ show $ afterBefore $ ln

afterBefore :: [Char] -> Integer
afterBefore = sum . foldl induct [1]

induct :: [Integer] -> Char -> [Integer]
induct spectrum place = case place of
    'A' -> [sum (take n spectrum) | n <- [0..(length spectrum)]]
    'B' -> [sum (drop n spectrum) | n <- [0..(length spectrum)]]

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics