在Leetcode上刷题时,刷到题目Valid Anagram,给定两个字符串st,编写一个函数来确定t是否是s的一个anagram,谷歌翻译对anagram的解释是通过重新排列另一个单词的字母顺序而组成的一个新单词,比如cinema是iceman的anagram。本质就是判断st是否有一样的字母组成。

什么是Segmentation fault

我看到这个题目的第一印象就是对字符串st中的字母排序,然后逐一比对,如果全部相同,就返回true,否则返回false

#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

int charcmp(const void *a, const void *b) {
    return *(char *)a - *(char *)b;
}

bool isAnagram(char* s, char* t) {
    int i;
    int s_len;
    int t_len;

    s_len = strlen(s);
    t_len = strlen(t);
    if (s_len != t_len) {
        return false;
    }
    qsort(s, s_len, sizeof(s[0]), charcmp);
    qsort(t, t_len, sizeof(t[0]), charcmp);
    for (i = 0; i < s_len; i++) {
        if (s[i] != t[i]) {
            return false;
        }
    }
    return true;
}

程序编译没有任何问题,但运行出错,提示Segmentation fault (core dumped)。咦!好像见过,这不社区来着。查了查维基,存储器段错误会出现在当程序企图访问CPU无法定址的存储器区块时。

产生Segmentation fault的情景

stackoverflow上的一个回答有更详细的解释,存储器段错误是由于访问没有权限的内存而导致的一种错误,它可以防止破坏内存和引入难调试的内存bug。
在C语言中有很多情景都会导致存储器段错误,如对一个空指针解引用(dereference),

int *p = NULL;
*p = 1;

另一种常见原因是对只读内存区域执行写操作,这也是上段程序产生bug的原因。

char *str = "Foo";
*str = 'b';

还有一种就是悬空指针,它指向根本不存在的东西。

char *p = NULL;
{
    char c;
    p = &c;
}

p是一个悬空指针,因为变量c是代码块作用域,代码执行后,就被销毁了,不复存在。此时,如果对p进行解引用,就可能会出现存储器段错误,此处的代码在gcc下并没有报错,是因为程序简单。

解决问题

st是字符串常量,是不可修改的,我想对它重新排序,势必需要改变字符串常量。解决办法就是复制一份,对复制后的字符数组排序。

bool isAnagram(char* s, char* t) {
    int i;
    int s_len;
    int t_len;
    char *s_cpy;
    char *t_cpy;

    s_len = strlen(s);
    t_len = strlen(t);
    s_cpy = (char *)malloc(sizeof(char)*s_len);
    t_cpy = (char *)malloc(sizeof(char)*t_len);
    strcpy(s_cpy, s);
    strcpy(t_cpy, t);

    if (s_len != t_len) {
        return false;
    }
    qsort(s_cpy, s_len, sizeof(s_cpy[0]), charcmp);
    qsort(t_cpy, t_len, sizeof(t_cpy[0]), charcmp);
    for (i = 0; i < s_len; i++) {
        if (s_cpy[i] != t_cpy[i]) {
            return false;
        }
    }
    return true;
}

问题解决了,不过对于这个问题还有更好的算法,使用哈希表。

bool isAnagram(char* s, char* t) {
    char alphabet[26];
    char *ptr;
    int i;
    
    for (i = 0; i < 26; i++) {
        alphabet[i] = 0;
    }
    for (ptr = s; *ptr != '\0'; ptr++) {
        alphabet[*ptr-'a']++;
    }
    for (ptr = t; *ptr != '\0'; ptr++) {
        alphabet[*ptr-'a']--;
    }
    for (i = 0; i < 26; i++) {
        if (alphabet[i] != 0) {
            return false;
        }
    }
    return true;
}

排序是需要成本的,使用哈希表就可以节省下这个资源,性能就快了不少,哈希是4ms,上一个排序则是24ms。


参考资料


Duke
4 声望0 粉丝