String 就地游程编码算法

String 就地游程编码算法,string,algorithm,run-length-encoding,String,Algorithm,Run Length Encoding,我遇到了一个面试问题: 给定一个输入字符串:aaaaa bcddddee,将其转换为a5b1c1d4e2 一个额外的限制是,这需要在适当的位置执行,这意味着不应该使用额外的空间(数组) 可以保证编码的字符串将始终适合原始字符串。换句话说,像abcde这样的字符串不会出现,因为它将被编码为a1b1c1d1e1,这比原始字符串占用更多的空间 面试官给我的一个提示是遍历字符串一次,然后找到节省下来的空间 但我还是被卡住了,因为有时候,如果不使用额外的变量,输入字符串中的某些值可能会被覆盖 如果您有任何

我遇到了一个面试问题:

给定一个输入字符串:
aaaaa bcddddee
,将其转换为
a5b1c1d4e2

一个额外的限制是,这需要在适当的位置执行,这意味着不应该使用额外的空间(数组)

可以保证编码的字符串将始终适合原始字符串。换句话说,像
abcde
这样的字符串不会出现,因为它将被编码为
a1b1c1d1e1
,这比原始字符串占用更多的空间

面试官给我的一个提示是遍历字符串一次,然后找到节省下来的空间

但我还是被卡住了,因为有时候,如果不使用额外的变量,输入字符串中的某些值可能会被覆盖


如果您有任何建议,我们将不胜感激。

可能只是正常地对其进行编码,但是如果您看到您的输出索引超过了输入索引,请跳过“1”。然后,当您完成后,返回并在所有字母后插入1,而不进行计数,将字符串的其余部分向后移动。在最坏的情况下是O(N^2)(没有重复的字母),所以我认为可能有更好的解决方案

编辑:似乎我错过了最后一个字符串始终适合源代码的部分。有了这个限制,是的,这不是最佳解决方案


EDIT2:O(N)版本的它将在第一次传递期间也计算最终压缩长度(在一般情况下可能比源长度大),设置指向它的指针p1,指向省略了1的压缩字符串的指针p2(p2因此这是一个很好的面试问题)

要点 有两个关键点:

  • 单个字符必须编码为
    c1
  • 编码长度将始终小于原始数组
  • 从1开始,我们知道每个字符需要至少2个位置进行编码。也就是说,只有单个字符需要更多空间进行编码

    简单方法 从关键点来看,我们注意到单个字符在编码过程中给我们带来了很多问题,因为它们可能没有足够的位置容纳编码的字符串。那么我们先把它们留下,然后先压缩其他字符怎么样

    例如,我们从后面编码
    aaaaa bcddddee
    ,同时先保留单个字符,我们将得到:

    aaaaabcddddee
    _____a5bcd4e2
    
    然后我们可以安全地从头开始编码部分编码的序列,给定关键点2,这样就有足够的空间

    分析

    似乎我们已经有了解决方案,我们完成了吗?不。考虑这个字符串:

    aaa3dd11ee4ff666
    
    这个问题没有限制字符的范围,因此我们也可以使用数字。在这种情况下,如果我们仍然使用相同的方法,我们将得到以下结果:

    aaa3dd11ee4ff666
    __a33d212e24f263
    
    好的,现在告诉我,如何区分游程长度和原始字符串中的数字

    嗯,我们需要试试别的

    让我们将编码优点(E)定义为:编码序列和原始连续字符序列之间的长度差。

    例如,
    aa
    具有
    E=0
    ,因为
    aa
    将被编码为
    a2
    ,并且它们没有长度差异;
    aaa
    具有
    E=1
    ,因为它将被编码为
    a3
    ,编码后的字符与原始字符之间的长度差异为
    1
    。让我们看看单个字符c在这种情况下,它的
    E
    是什么?是的,它是
    -1
    。从定义中,我们可以推导出
    E
    的公式:
    E=ori\u len-encoded\u len

    现在让我们回到问题上来。从关键点2开始,我们知道编码的字符串总是比原始字符串短。我们如何使用
    E
    来重新表述这个关键点

    非常简单:
    sigma(E_i)>=0
    ,其中
    E_i
    是第i个连续字符子串的
    编码优势

    例如,您在问题中给出的示例:
    aaaaa bcddddee
    ,可以分为5个部分:

    E(0) = 5 - 2 = 3  // aaaaa -> a5
    E(1) = 1 - 2 = -1 // b -> b1
    E(2) = 1 - 2 = -1 // c -> c1
    E(3) = 4 - 2 = 2  // dddd -> d4
    E(4) = 2 - 2 = 0  // ee -> e2
    
    sigma将是:
    3+-1+-1)+2+0=3>0
    。这意味着编码后将剩下3个空格

    但是,从这个例子中,我们可以看到一个潜在的问题:因为我们在做求和,即使最终答案大于0,也有可能在中间得到一些否定!

    是的,这是一个问题,而且非常严重。如果我们得到
    E
    低于
    0
    ,这意味着我们没有足够的空间对当前字符进行编码,并将覆盖后面的一些字符

    但是,为什么我们需要从第一组中总结它呢?为什么我们不能从中间的某个地方开始总结以跳过否定部分呢?让我们来看一个例子:

    2 0 -1 -1 -1 1 3 -1
    
    如果我们从一开始就进行汇总,在索引4(基于0)处添加第三个
    -1
    后,我们将降至0以下;如果我们从索引5进行汇总,当我们到达末尾时,我们将返回到索引0,我们没有问题

    算法 该分析为我们提供了关于算法的见解:

  • 从头开始,计算当前连续组的
    E
    ,并将其添加到总数
    E_total
  • 如果
    E_total
    仍然为非负(>=0),我们很好,可以安全进入下一组
  • 如果
    E_total
    低于0,我们需要从当前位置重新开始,即清除
    E_total
    ,然后进入下一个位置
  • 如果我们到达序列的末尾,并且
    E_total
    仍然是非负的,那么最后一个起点就是一个好的开始!这一步需要
    O(n)
    时间。通常我们需要循环并再次检查,但由于关键点2,我们肯定会得到一个有效的答案,因此我们可以安全地停在这里

    然后,我们可以回到起点,在到达终点后,开始传统的游程编码
    Group 1: a     -> E_total += -1 -> E_total = -1 < 0 -> E_total = 0, pos = 1;
    Group 2: b     -> E_total += -1 -> E_total = -1 < 0 -> E_total = 0, pos = 2;
    Group 3: cc    -> E_total += 0  -> E_total = 0 >= 0 -> proceed;
    Group 4: ddd   -> E_total += 1  -> E_total = 1 >= 0 -> proceed;
    Group 5: e     -> E_total += -1 -> E_total = 0 >= 0 -> proceed;
    Group 6: f     -> E_total += -1 -> E_total = -1 < 0 -> E_total = 0, pos = 9;
    Group 7: ggggg -> E_total += 3  -> E_total = 3 >= 0 -> proceed;
    Group 8: hhhhh -> E_total += 3  -> E_total = 6 >= 0 -> end;
    
             v this is the starting point
    abccdddefggggghhhhh
    abccdddefg5h5______
                 ^ last_available_pos, we need to make use of these remaining spaces
    abccdddefg5h5a1b1c2
    d3e1f1___g5h5a1b1c2
          ^^^ remove the white space
    d3e1f1g5h5a1b1c2
              ^ last_available_pos, rotate
    a1b1c2d3e1f1g5h5
    
    s = "wwwwaaadexxxxxxywww"
    
    s = s + '#'
    
    s = "wwwwaaadexxxxxxywww#"
    
    j = 0 // s[j] = w
    
    print(s[j], i - j) // i = 4, j = 0
    j = i              // j = 4, s[j] = a
    
    Output: w4
    
    print(s[j], i - j) // i = 7, j = 4 => a3
    j = i              // j = 7, s[j] = d
    
    Output: w4a3
    
    
    .
    .  (Skipping to the second last)
    .
    
    j = 15, s[j] = y, i = 16, s[i] = w
    print(s[j], i - y) => y1
    
    Output: w4a3d1e1x6y1
    
    j = 16, s[j] = w and we cannot print it's count 
    because we've no 'mis-matching' character
    
    void compress(string s){
        int j = 0;
        s = s + '#';
        for(int i=1; i < s.length(); i++){
            if(s[i] != s[j]){
                cout << s[j] << i - j;
                j = i;
            }
         }
    }
    
    int main(){
        string s = "wwwwaaadexxxxxxywww";
        compress(s);
        return 0;
    }
    
    #include<bits/stdc++.h>
    using namespace std;
    int dig(int n){
        int k=0;
        while(n){
            k++;
            n/=10;
        }
        return k;
    }
    void stringEncoding(string &n){
        int i=0;
        for(int i=0;i<n.size();i++){
            while(n[i]==n[i+j])j++;
            n.erase((i+1),(j-1));
            n.insert(i+1,to_string(j));
            i+=(dig(j));
        }
    }
    int main(){
        ios_base::sync_with_stdio(0), cin.tie(0);
        string n="kaaaabcddedddllllllllllllllllllllllp";
        stringEncoding(n);
        cout<<n;
    }