就地运行长度解码?

时间:2012-01-08 03:55:53

标签: c encoding compression

给定一个运行长度编码的字符串,比如说“A3B1C2D1E1”,就地解码字符串。 编码字符串的答案是“AAABCCDE”。假设编码数组足够大以容纳解码后的字符串,即您可以假设数组大小= MAX [length(encodedstirng),length(decodedstring)]。

这似乎并不重要,因为仅将A3解码为“AAA”将导致覆盖原始字符串的“B”。

此外,不能假设解码的字符串总是大于编码的字符串。 例如:编码字符串 - 'A1B1',解码字符串为'AB'。有什么想法吗?

它始终是一个字母数字对,即不会要求您将0515转换为0000055555

4 个答案:

答案 0 :(得分:6)

如果我们还不知道,我们应首先扫描,将数字相加,以计算解码字符串的长度。

它始终是一个字母数字对,因此您可以从字符串中删除1而不会产生任何混淆。

A3B1C2D1E1

变为

A3BC2DE

以下是C ++中的一些代码,用于从字符串中删除1(O(n)复杂度)。

// remove 1s
int i = 0; // read from here
int j = 0; // write to here
while(i < str.length) {
    assert(j <= i); // optional check
    if(str[i] != '1') {
        str[j] = str[i];
        ++ j;
    }
    ++ i;
}
str.resize(j); // to discard the extra space now that we've got our shorter string

现在,此字符串保证比最终解码字符串短或相同。我们无法对原始字符串做出声明,但我们可以对此修改过的字符串进行说明。

(一个可选的,微不足道的步骤,现在是用前一个字母2替换每个A3BCCDE,但我们不需要这样做。)

现在我们可以从最后开始工作了。我们已经计算了解码字符串的长度,因此我们确切知道最终字符的位置。我们可以简单地将字符串从短字符串的末尾复制到最终位置。

在从右到左的复制过程中,如果我们遇到一个数字,我们必须制作位于数字左侧的字母的多个副本。您可能担心这可能会覆盖过多的数据。但我们之前证明,我们的编码字符串或其任何子字符串永远不会长于其对应的解码字符串;这意味着总会有足够的空间。

答案 1 :(得分:2)

以下解决方案为O(n)并就地。该算法不应该访问它不应该读取和写入的内存。我做了一些调试,看来我喂它的样本测试是正确的。


高级概述:

  • 确定编码长度。
  • 通过读取所有数字并将它们相加来确定解码长度。
  • 缓冲区结束是MAX(解码长度,编码长度)。
  • 从字符串末尾开始解码字符串。从缓冲区的末尾开始写。
  • 由于解码长度可能大于编码长度,因此解码后的字符串可能不会在缓冲区的开头处开始。如果需要,可以通过将字符串移到开头来纠正此问题。

int isDigit (char c) {
    return '0' <= c && c <= '9';
}

unsigned int toDigit (char c) {
    return c - '0';
}

unsigned int intLen (char * str) {
    unsigned int n = 0;
    while (isDigit(*str++)) {
        ++n;
    }
    return n;
}

unsigned int forwardParseInt (char ** pStr) {
    unsigned int n = 0;
    char * pChar = *pStr;
    while (isDigit(*pChar)) {
        n = 10 * n + toDigit(*pChar);
        ++pChar;
    }
    *pStr = pChar;
    return n;
}

unsigned int backwardParseInt (char ** pStr, char * beginStr) {
    unsigned int len, n;
    char * pChar = *pStr;
    while (pChar != beginStr && isDigit(*pChar)) {
        --pChar;
    }
    ++pChar;
    len = intLen(pChar);
    n = forwardParseInt(&pChar);
    *pStr = pChar - 1 - len;
    return n;
}

unsigned int encodedSize (char * encoded) {
    int encodedLen = 0;
    while (*encoded++ != '\0') {
        ++encodedLen;
    }
    return encodedLen;
}

unsigned int decodedSize (char * encoded) {
    int decodedLen = 0;
    while (*encoded++ != '\0') {
        decodedLen += forwardParseInt(&encoded);
    }
    return decodedLen;
}

void shift (char * str, int n) {
    do {
        str[n] = *str;
    } while (*str++ != '\0');
}

unsigned int max (unsigned int x, unsigned int y) {
    return x > y ? x : y;
}

void decode (char * encodedBegin) {
    int shiftAmount;
    unsigned int eSize = encodedSize(encodedBegin);
    unsigned int dSize = decodedSize(encodedBegin);
    int writeOverflowed = 0;
    char * read = encodedBegin + eSize - 1;
    char * write = encodedBegin + max(eSize, dSize);
    *write-- = '\0';
    while (read != encodedBegin) {
        unsigned int i;
        unsigned int n = backwardParseInt(&read, encodedBegin);
        char c = *read;
        for (i = 0; i < n; ++i) {
            *write = c;
            if (write != encodedBegin) {
                write--;
            }
            else {
                writeOverflowed = 1;
            }
        }
        if (read != encodedBegin) {
            read--;
        }
    }
    if (!writeOverflowed) {
        write++;
    }
    shiftAmount = encodedBegin - write;
    if (write != encodedBegin) {
        shift(write, shiftAmount);
    }
    return;
}

int main (int argc, char ** argv) {
    //char buff[256] = { "!!!A33B1C2D1E1\0!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" };
    char buff[256] = { "!!!A2B12C1\0!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" };
    //char buff[256] = { "!!!A1B1C1\0!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" };
    char * str = buff + 3;
    //char buff[256] = { "A1B1" };
    //char * str = buff;
    decode(str);
    return 0;
}

答案 2 :(得分:0)

这是一个非常模糊的问题,但如果你考虑它并不是特别困难。正如您所说,将A3解码为AAA并将其写入到位将覆盖字符B1,那么为什么不先将这些字符移到数组中呢?

例如,一旦你阅读了A3,你知道你需要为一个额外的角色腾出空间,如果它是A4你需要两个,依此类推。要实现这一点,你会在数组中找到字符串的结尾(事先做好并存储它的索引)。

然后循环,将字符移动到新的插槽:

开始:A|3|B|1|C|2||||||| 有一个名为end的变量存储索引5,即最后一个非空白的条目。

您在第一对中读到了一个名为cursor的变量来存储您当前的位置 - 所以在阅读A3后,它将被设置为1 (带有3的插槽)。

移动的伪代码:

var n = array [cursor] - 2; // n = 1,A3中的3,然后减去2以允许该对。

for(i = end; i&gt; cursor; i ++) {   array [i + n] = array [i]; }

这会让你:

A|3|A|3|B|1|C|2|||||

现在A已经存在,所以现在你要从n + 1中存储的索引开始写A cursor

for(i = cursor; i < cursor + n + 1; i++)
{
  array[i] = array[cursor - 1];
}

// increment the cursor afterwards!
cursor += n + 1;

,并提供:

A|A|A|A|B|1|C|2|||||

然后你指着下一对价值的开始,准备再去。我意识到这个答案有一些漏洞,虽然这是故意的,因为这是一个面试问题!例如,在您指定A1B1的边缘情况下,您需要一个不同的循环来向后移动后续字符而不是向前移动。

答案 3 :(得分:0)

另一个O(n ^ 2)解决方案如下。

鉴于答案的复杂性没有限制,这个简单的解决方案似乎完美无缺。

while ( there is an expandable element ):
    expand that element
    adjust (shift) all of the elements on the right side of the expanded element

其中:

  • 可用空间大小是数组中剩余的空元素数。

  • 可扩展元素是一个元素:

    expanded size - encoded size <= free space size
    

关键在于,在从游程长度代码到扩展字符串的过程中,每一步都至少存在 一个可以扩展的元素(易于证明)。