无变量字符串反向

时间:2015-09-22 16:14:15

标签: c string data-structures

在最近的一次采访中,我被问到一个非常简单的问题,即在没有任何额外变量和任何内置函数的情况下反转字符串(不仅仅是打印)。我能想到的最接近的是:

<div id="trex-widget391" class="trex-popup-widget"></div> 
   <script>
       function trexCallback391(trex){document.getElementById("trex-widget391").innerHTML=trex.html;}
       jQuery(".trex-popup-widget").mouseover(function(e){
           if(!sessionStorage.seenPopup) {
               jQuery("#trex_overlay_fsl_popup").fadeIn('slow');
               sessionStorage.seenPopup = "yes";
           }
       });
   </script> 
   <script src="http://clanci.geek.hr/widget/widget.php?id=391" async defer></script>

我使用变量#include<stdio.h> #include<string.h> int main() { char ch[100]; scanf("%s",&ch); int i=0; while(i<strlen(ch)/2) { ch[i]=ch[strlen(ch)-1-i]+ch[i]; ch[strlen(ch)-1-i]=ch[i]-ch[strlen(ch)-1-i]; ch[i]=ch[i]-ch[strlen(ch)-1-i]; i++; } printf("%s",ch); return 0; } 拒绝了我的解决方案。如果不使用计数器变量,这怎么可能呢?有没有其他方法可以解决这个问题?

修改

这些是确切的问题(无论或多或少):

在不使用C中的任何变量或内置函数的情况下反转字符串。

4 个答案:

答案 0 :(得分:5)

两个三种可能的实现:一种只反向打印字符串。另一个反转字符串在内存和就地。两者都假设允许定义自己的递归函数,并且参数不计为变量。在任何情况下,参数本身都是const ant,因此可以说不是变量。

void printRev(const char * const s){
    if(*s != '\0'){ // or just: if(*s){
        printRev(s + 1);
        putchar(*s);
    }
}

是否通过字符串'前缀'递归:首先递归直到到达结尾,然后在递归调用返回后打印每个字符。

void revStr(char * const s, const int len){
    if(len > 0){
        if(s[0] != s[len]){
            s[0] ^= s[len];
            s[len] ^= s[0];
            s[0] ^= s[len];
        }

        revStr(s + 1, len - 2);
    }
}

稍微复杂一点:XOR-swaps字符串的'first'字符带'last'。然后使用下一个字符作为字符串的开头进行递归,并且长度减少2。因此,在下一次迭代中,第二个字符成为第一个字符,倒数第二个字符成为最后一个字符。为此,s指针本身仍为const,但显然指向的字符已被修改。

第二个函数需要字符串长度作为输入参数,这也可以(递归地)完成,而不需要内置的strlen函数:

int myStrlen(const char * const s){
    if(*s != '\0'){
        return 1 + myStrlen(s + 1);
    }

    return 0;
}

<强> ADDIT

这是一个不使用长度参数的版本,但需要一个不相交的输出字符串,并且输入字符串是可修改的。它通过使用NUL字符替换len - 2中的最后一个字符来模拟revStr中的src表达式。

void copyRev(char * const restrict dst, char * const restrict src){
    if(src[0] != '\0'){
        dst[0] = src[myStrlen(src) - 1];
        dst[myStrlen(src) - 1] = src[0];
        src[myStrlen(src) - 1] = '\0';

        copyRev(dst + 1, src + 1);
    }
}

答案 1 :(得分:2)

我们可以重新分配字符串内存以使其大小加倍  然后我们可以将字符串memcpy到下半部分。

e.g。 S T R \ 0 S T R \ 0

现在我们可以一次将1个字符记忆到下半年的原始字符串。

这假设一个ascii字符串,其中'\ 0'终止字符。

void reverseString(const char** pStr)
{
    *pStr = realloc(*pStr, strlen(*pStr)*2 + 2);
    memcpy(*pStr + strlen(*pStr), *pStr, strlen(*pStr));
    while (*pStr)
    {
        memcpy(*pStr, *pStr + strlen(*pStr + 2), 1);
        ++ (*pStr);
    }

}

答案 2 :(得分:0)

使用C11的能力创建复合文字作为参数并返回值:

typedef struct foo_T {
  char *s;
  char *end;
  char left, right;
} foo_T;

foo_T foo(const foo_T f) {  // No variables, just constant parameter
  if (f.s < f.end) {
    *f.s = f.right;
    *f.end = f.left;
    return (foo_T){f.s+1,f.end-1,*f.s,*f.end};
  }
  return f;
}

char *endptr(char * const s) {  // No variables, just constant parameter
  if (*s == 0) return s-1;
  return endptr(s + 1);
}

void rev_novar(char *s) {
  if (*s && s[1]) {
    foo ((foo_T){s,endptr(s),*s,*endptr(s)});
  }
}

测试

void test_rev(const char *s) {
  char buf[100];
  memset(buf, 77, sizeof buf);
  strcpy(&buf[1], s);
  printf("%d '%s' %d\n", buf[0], &buf[1], buf[strlen(buf) + 1]);
  rev_novar(&buf[1]);
  printf("%d '%s' %d\n\n", buf[0], &buf[1], buf[strlen(buf) + 1]);
}

int main(void) {
  test_rev("123");
  test_rev("1234");
  test_rev("1");
  test_rev("");
  return 0;
}

测试结果。 (77是左右哨兵。)

77 '123' 77
77 '321' 77

77 '1234' 77
77 '4231' 77

77 '1' 77
77 '1' 77

77 '' 77
77 '' 77

答案 3 :(得分:0)

我的猜测是面试官错了。如果没有任何变量,您可以使用一系列硬编码的if语句来查找字符串的结尾,然后使用硬编码的交换序列。它在概念上与排序网络类似,排序网络用于排序固定数量的值(最多16个,基于维基文章)。例如,对于长度为5的字符串:

void reverse(char str[])
{
    if(str[0] == 0 || str[1] == 0)
        return;
    if(str[2] == 0){
        str[0] ^= str[1]; str[1] ^= str[0]; str[0] ^= str[1];
        return;
    }
    if(str[3] == 0){
        str[0] ^= str[2]; str[2] ^= str[0]; str[0] ^= str[2];
        return;
    }
    if(str[4] == 0){
        str[0] ^= str[3]; str[3] ^= str[0]; str[0] ^= str[3];
        str[1] ^= str[2]; str[2] ^= str[1]; str[1] ^= str[2];
        return;
    }
    if(str[5] == 0){
        str[0] ^= str[4]; str[4] ^= str[0]; str[0] ^= str[4];
        str[1] ^= str[3]; str[3] ^= str[1]; str[1] ^= str[3];
        return;
    }
}

即使使用上面的代码,XOR操作也需要一个内部变量(寄存器),除非处理器有一个xor内存到内存指令,(比较为零可以在X86 / X64处理器上使用立即数)。

示例代码,如果允许常量指针和递归,但这实际上使用堆栈作为变量的多个实例(指针),因为它们被操作为指针+ 1和/或指针-1,并且因为reverse1()是tail递归,优化编译器可能会将其转换为循环,将beg和end作为常规变量。

void reverse0(char * const beg, char * const end);
void reverse1(char * const beg, char * const end);

/* entry function */
void reverse(char str[])
{
    if(str[0] == 0 || str[1] == 0)
        return;
    reverse0(&str[0], &str[2]);
}

/* find end of str */
void reverse0(char * const beg, char * const end)
{
    if(*end != 0){
        reverse0(beg, end+1);
        return;
    }
    reverse1(beg, end-1);           
}

/* reverse str */
void reverse1(char * const beg, char * const end)
{
    *beg ^= *end;
    *end ^= *beg;
    *beg ^= *end;
    if((beg+1) < (end-1))
        reverse1(beg+1, end-1);
}