题解 | #病毒扩散#

牛牛的字符串

http://www.nowcoder.com/practice/0cc3b103e43f4ec093be586df292822e

题意理解

当第i个字母小于第i+k个字母时,可以交换两个字母顺序,记作一次交换。我们要求给定的字符串中,最多可以进行几次交换,直到不存在符合交换条件的两个对应字母。

方法一

可以看到,第i、i+k、i+2k、i+3k等等这些字母之间进行交换,和其它字母无关,所以可以把这样的字母序列提取出来,依次计算每一个序列里面的最多交换次数再相加。对于一个序列,我们使用逆序对(小的字母在前视为逆序)的个数作为该序列的最多交换次数。

正确性证明:
1、若一个序列存在逆序对,则必存在相邻两个字母是逆序的。
2、交换这两个相邻的逆序字母,只会减少一个逆序对。
有了以上两点就可以保证序列逆序对的个数是该序列的最多交换次数。

至于如何求逆序对的个数,这里使用了归并排序的思想,序列a[begin]~a[end]的逆序对个数等于序列a[begin]~a[mid]的逆序对个数加序列a[mid+1]~a[end]的逆序对个数,再加上将两者合并时产生的逆序对个数。

具体代码如下:

class Solution {
public:
    /**
     * 
     * @param s string字符串 s.size() <= 1e5
     * @param k int整型 k <= s.size()
     * @return int整型
     */
    vector<char> v;
    //合并两个序列
    int merge(vector<char> &a, int begin, int mid, int end)
    {
        int p = begin, q = mid + 1;
        int count = 0;
        vector<char> temp;
        //先通过比较大小,将大的字母先插入
        while (p<=mid && q<=end)
        {
            if (a[p]>=a[q])
            {
                temp.push_back(a[p++]);
            }
            else
            {
                count += mid - p + 1;//计算a[q]对应逆序对个数
                temp.push_back(a[q++]);
            }
        }
        //将剩余的字母插入
        while (p<=mid) temp.push_back(a[p++]);
        while (q<=end) temp.push_back(a[q++]);
        //更新a[begin]~a[end]
        for (int i=0;i<temp.size();i++)
        {
            a[i+begin] = temp[i];
        }
        return count;
    }
    //求逆序对个数
    int countReversed(vector<char> &a, int begin,int end)
    {  
        if (begin < end)
        {  
            int mid = (begin + end) / 2;
            //递归过程
            return (countReversed(a, begin, mid) +
                countReversed(a, mid + 1, end) + merge(a, begin, mid, end));
        }
        else
        {
            return 0;
        }
    }
    
    int turn(string s, int k) {
        // write code here
        int ans = 0;
        for (int i=0;i<k;i++)
        {
            v.clear();
            //提取出第i,i+k,i+2k等等字母作为一个序列
            for (int j=i;j<s.length();j+=k)
            {
                v.push_back(s[j]);
            }
            ans += countReversed(v, 0, v.size()-1);
        }
        return ans;
    }
};

时间复杂度:O(NlogN)O(NlogN)O(NlogN)。求逆序对用到的归并思想,其复杂度为O(NklogNk)O(\frac{N}{k}log\frac{N}{k})O(kNlogkN)。尽管主程序中有一个双重for循环,内层循环对countReversed函数的调用次数无影响,外层循环控制的该函数执行次数为k次,所以总的时间复杂度为O(NlogN)O(NlogN)O(NlogN)
空间复杂度:O(N)O(N)O(N)。创建了数组v记录抽取的序列和数组temp记录合并时排好序的新数组。

方法二

同方法一,我们先按照间隔k提取出字符串v,分别进行处理。要求每一个字母前面有多少个小于它的字母。转换一下思路,我们可以求小于它的字母有多少在它前面。

示意图如下: alt

我们在v上从前向后遍历,如果一个字母出现了,就是用count数组记录,对应count加1,。当遍历到v[j]的时候,v[0]~v[j-1]对应的count都进行了计数,如果没出现的字母其对应count为0。此时遍历小于v[j]的字母,将其对应count值加入到最终答案ans中去。

具体代码如下:

class Solution {
public:
    /**
     * 
     * @param s string字符串 s.size() <= 1e5
     * @param k int整型 k <= s.size()
     * @return int整型
     */
    vector<char> v;
    
    
    int turn(string s, int k) {
        // write code here
        int ans = 0;
        for (int i=0;i<k;i++)
        {
            int count[26] = {0};
            v.clear();
            count[s[i]-'a'] = 1;
            //提取字符串v
            for (int j=i;j<s.length();j+=k)
            {
                v.push_back(s[j]);
            }
            for (int j=1;j<v.size();j++)
            {
                //查看小于v[j]的字母中有几个在它前面
                for (int h=0;h<v[j]-'a';h++)
                {
                    ans += count[h];
                }
                count[v[j]-'a']++;
            }
        }
        return ans;
    }
};

时间复杂度:O(N)O(N)O(N)。一共有三重循环,但是s中的每个字母只遍历了一遍;最内层的循环每次至多遍历25个字母,为常数项。
空间复杂度:O(N)O(N)O(N)。创建了数组v记录抽取的序列,数组count长度为常数。

全部评论

相关推荐

自从我室友在计算机导论课上听说了“刷&nbsp;LeetCode&nbsp;是进入大厂的敲门砖”,整个人就跟走火入魔了一样。他在宿舍门口贴了一张A4纸,上面写着:“正在&nbsp;DP,请勿打扰,否则&nbsp;Time&nbsp;Limit&nbsp;Exceeded。”日记本的扉页被他用黑色水笔加粗描了三遍:“Talk&nbsp;is&nbsp;cheap.&nbsp;Show&nbsp;me&nbsp;the&nbsp;code。”连宿舍聚餐,他都要给我们讲解:“今天的座位安排可以用回溯算法解决,但为了避免栈溢出,我建议用动态规划。来,这是状态转移方程:dp[i][j]&nbsp;代表第&nbsp;i&nbsp;个人坐在第&nbsp;j&nbsp;个位置的最优解。”我让他去楼下取个快递,他不直接去,非要在门口踱步,嘴里念念有词:“这是一个图的遍历问题。从宿舍楼(root)到驿站(target&nbsp;node),我应该用&nbsp;BFS&nbsp;还是&nbsp;DFS?嗯,求最短路径,还是广度优先好。”和同学约好出去开黑,他会提前发消息:“集合点&nbsp;(x,&nbsp;y),我们俩的路径有&nbsp;k&nbsp;个交点,为了最小化时间复杂度,应该在&nbsp;(x/2,&nbsp;y/2)&nbsp;处汇合。”有一次另一个室友低血糖犯了,让他帮忙找颗糖,他居然冷静地分析道:“别急,这是一个查找问题。零食箱是无序数组,暴力查找是&nbsp;O(n)。如果按甜度排序,我就可以用二分查找,时间复杂度降到&nbsp;O(log&nbsp;n)。”他做卫生也要讲究算法效率:“拖地是典型的岛屿问题,要先把连通的污渍区块都清理掉。倒垃圾可以用双指针法,一个指针从左往右,一个从右往左,能最快匹配垃圾分类。”现在我们宿舍的画风已经完全变了,大家不聊游戏和妹子,对话都是这样的:“你&nbsp;Two&nbsp;Sum&nbsp;刷了几遍了?”“别提了,昨天遇到一道&nbsp;Hard&nbsp;题,我连暴力解都想不出来,最后只能看题解。你呢?”“我动态规划还不行,总是找不到最优子结构。今天那道接雨水给我整麻了。”……LeetCode&nbsp;真的害了我室友!!!
老六f:编程嘉豪来了
AI时代还有必要刷lee...
点赞 评论 收藏
分享
牛马43373018...:这人真懂什么叫熵吗
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务