LintCode-关于一次遍历解决循环单词问题的思考

题目:The words are same rotate words if rotate the word to the right by loop, and get another. Count how many different rotate word sets in dictionary.

看到题目后,首先要考虑的问题是如何判断两个单词是否属于同一个循环单词,常规的思路无非是将两个单词,也就是两个字符串进行比较,比较的方法有很多,比如:逐字移位然后与另一个进行比较,或者比较trick的方法是将其中一个字符串扩展一倍,然后看另一个是不是已扩展字符串的子串(例:abcd与 cdab, abcd扩展后为 abcdabcd, 那么cdab为扩展后的字串,所以这两个属于同一组循环单词。)很容易证明上面两个方案是完备的,这里的完备指的是:如果两个单词互为循环单词,那么肯定满足上面两个条件;如果两个单词满足上面两个之一,那么这两个单词肯定互为循环单词。
但是上面两个方案的明显缺点在于,需要一对一的进行比较,也就是说时间复杂度为O(N^2)。那么有没有办法在O(N)的时间范围内计算出所有的循环单词呢。其实是可以尝试的。
要想在O(N)时间范围内处理所有循环单词,那么必须找出一个唯一识别一组循环单词的特征,即abcd和cdab可以映射到同一个值(或者向量)上,并且这个值(或者向量)仅与字符 a,b c,d及其顺序有关。
现在从一般角度上去考虑或者衡量循环单词(abcd, dabc, cdab, bcda)的特征。总结起来有如下三个特征:
 1. 长度相同
 2. 字符一样
 3. 每个字符的前后字符总是一样
 这三个特征可以唯一描述一组循环单词,有兴趣的同学可以证明一下,或者举出一个反例。那么现在的问题是如何将这些特征应用起来,长度可以用一个数来表示, 字符一样该如何用算子描述,字符的和,抑或平方和,很显然“和”及“平方和”都只是必要而非充分的描述。但暂时找不到更好的算子,可以先把“和”留下来,毕竟有些作用。现在看第三个特征,每个字符的前后字符总是一样, 这应该是循环单词最重要的特征了。什么算子可以用来描述一个变量的前后关系呢,显然是梯度。以前向梯度来看, abcd, dabc, cbad, bcda 的梯度数组分别是[-1,-1,-1,3], [3, -1, -1, -1], [-1, 3, -1, -1], [-1,-1, 3, -1]。有趣的是循环单词的一阶前向梯度也是互为循环的,更进一步的,循环单词的任意阶梯度都是互为循环的。这就有点尴尬了,为了计算循环单词,需要计算循环数组(本质上也是循环单词)。
 虽然如此,但不妨重新应用第二个暂时的算子和, 假如有两个单词,它们的长度,字符和,以及一阶前向梯度和一致,它们是否一定互为循环单词?显然不是,简单的一个反例[a,b,a,b] 和[ a,a,b,b],一阶梯度为[-1, 1, -1, 1] 和[ 0, -1, 0, 1], 两者满足上诉三个条件,但不互为循环单词,但互为循环单词的概率有多少呢,在lintCode中大概试了一下,只识别了25%左右的数据(最后一个case中,有4411个数,只识别了1000多个)。由于梯度有正负,而和很容易消去这个特征,这是很自然的想到采用平方和来代替,这时成功的概率有多少呢,其实也没有提高多少,具体数忘记了,究其原因还是在于正负的熟悉被消掉了(-n * -n) = (n* n)。正负对于梯度来说很重要。因此需要将正负梯度分开来计算平方和(分开正负后使用平方和,是因为平方和能够扩大差异)。此时需要计算的特征有:
 1. 字符串长度
 2. 字符串和
 3. 一阶导中正整数个数
 4. 一阶导中正整数的平方和
 5. 一阶导中负整数个数
 6. 一阶导中负整数平方和
这时又要问了,满足以上6个条件的单词,一定互为循环单词吗?也不一定是,但我一时想不到合适的单词例子,只能举一个一阶导的例子,比如[-1, -1, 1,1] 和[ -1, 1, -1, 1]满足上面6个条件但不互为循环。感兴趣的同学,可以找出一个反例,那么这个向量特征的成功率是多少呢,lintCode里为 4391/4411, 成功率大概为99.5%左右。
是不是只能到这个程度了呢,我并不这样认为,从上面的过程来看,一阶导很大程度上概括了循环单词的第三个特征。存在意外情况是可以理解的。因为对于一阶导[-1, -1, 1,1]来说,是存在这样的字符串的[x, x+ 1, x + 2, x+1]。 那么有没有哪个字符串的二阶导为[-1, -1, 1,1](其是是对[-a, -a, a, a]特殊情况的简化)。可以简单计算一下:
二阶导:[-1, -1, 1,1]
一阶导:[x, x+ 1, x + 2, x+1]
字符串:[y, y-x, y-2x -1, y-3x -3]
那么可以得到: y-3x - 3 - y = x + 1; ==> x = -1; 由于对于字符来说,没有负数,因此可以简单的认为,以下十个特征可以完备的描述正循环单词:
 1. 字符串长度
 2. 字符串和
 3. 一阶导中正整数个数
 4. 一阶导中正整数的平方和
 5. 一阶导中负整数个数
 6. 一阶导中负整数平方和
 7. 二阶导中正整数个数
 8. 二阶导中正整数平方和
 9. 二阶导中负整数个数
 10. 二阶导中负整数平方和
用以上十个特征,在lintcode中测试,可以通过所有case,代码如下:
class Solution {
public:
    /*
     * @param words: A list of words
     * @return: Return how many different rotate words
     */
    int countRotateWords(vector<string> words) {
        // Write your code here

        int nLen = words.size();
        if ( nLen == 0 )
        {
            return 0;
        }
        set<vector<int> >singset;
        for(int i = 0; i < nLen; i++ )
        {
            vector<int> vec;
            retSum(words[i], vec);
            singset.insert(vec);
        }
        return singset.size();
    }

    int retSum(string& p_str, vector<int>& p_vec)
    {
        if ( p_str.empty() )
        {
            return 0;
        }
        int p_nLen = p_str.size();
        int posnum1 = 0, posnum2 = 0, negnum1 = 0, negnum2 = 0;
        int nSum = 0;
        int possum1 = 0, possum2 = 0, negsum1 = 0, negsum2 = 0;
        for ( int i = 0; i < p_nLen; ++i )
        {
            nSum += (int)p_str[i];
            int diff1 = (int)p_str[i] - (int)p_str[(i+1) % p_nLen];
            int diff2 = (int)p_str[i] - 2* (int)p_str[(i+1) % p_nLen] + (int)p_str[(i+2) % p_nLen];

            if ( diff1 > 0 )
            {
                posnum1++;
                possum1 += diff1 * diff1;
            }
            else
            {
                negnum1++;
                negsum1 += diff1 * diff1;
            }

            if ( diff2 > 0 )
            {
                posnum2++;
                possum2 += diff2 * diff2;
            }
            else
            {
                negnum2++;
                negsum2 += diff2 * diff2;
            }

        }
        p_vec.push_back(p_nLen);
        p_vec.push_back(nSum);
        p_vec.push_back(posnum1);
        p_vec.push_back(possum1);
        p_vec.push_back(negnum1);
        p_vec.push_back(negsum1);
        p_vec.push_back(posnum2);
        p_vec.push_back(possum2);
        p_vec.push_back(negnum2);
        p_vec.push_back(negsum2);
        return 1;
    }
};
但对于这10个特征是否真的能够完备表述正的循环单词,很抱歉我无法给出完整的,严谨的推导,希望有感兴趣的同学可以不吝赐教。
性能简单讨论,向量可以映射到高维(10维)空间中的一个点,即可以用key,map的方式插入,因此可以认为时间复杂度为O(N),但在数据量较少的情况下直接比较反而更好。
以上只是本人的一些思考,好坏勿论,仅此记录。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值