剑指offer学习笔记 第一个只出现一次的字符

面试题50:第一个只出现一次的字符。
1.在字符串中找到第一个只出现一次的字符。如输入"abaccdeff",则输出’b’。

最直观想法是从头开始扫描整个字符串,每当访问到一个字符时,拿这个字符和后面每个比较,如果在后面没有重复的字符,则这个字符就是只出现一次的字符,如果字符串有n个字符,则时间复杂度为O(n²),太慢了。

题目与字符出现的次数有关,我们可以统计每个字符在该串中出现的次数,为实现它,我们需要根据字符来查找它出现的次数,即将一个字符映射为一个数字,哈希表可以完成此功能。

我们需要从头到尾扫描字符串两次,第一次每扫描到一个字符,就在哈希表的对应项中把次数加1,接下来第二次扫描就得到字符出现的次数,第一个只出现一次的字符就是符合要求的输出。

哈希表比较复杂,C++标准库中就有map和unordered_map实现了哈希表功能,但本题只需要很简单的哈希表即可,可以直接实现,字符char是一个长度为8的数据类型,因此总共有256种可能,于是我们创建一个长度为256的数组,每个字母根据其ASCII码值作为数组的下标,而数组中存储的是每个字符出现的次数,这样就创建了一个大小为256、以字符ASCII码为键值的哈希表。

第一次扫描时,在哈希表中更新一个字符出现的次数的时间是O(1)。如果字符串长度为n,那么第一次扫描时间就是O(n),第二次扫描时,同样在O(1)的时间内读出一个字符出现的次数,所以时间复杂度是O(n),因此总的时间复杂度是O(n),同时我们需要一个包含256个int的辅助数组,它的大小是1KB,由于这个数组大小是个常数,因此这种算法的空间复杂度是O(1):

#include <iostream>
#include <vector>
using namespace std;

char FirstNotRepeatingChar(const char* s) {
	if (s == nullptr) {
		return '\0';
	}

	vector<int> res(256);
	const char* pc = s;
	while (*pc != '\0') {
		++res[*pc++];
	}
	
	pc = s;
	while (*pc != '\0') {
		if (res[*pc++] == 1) {
			return *--pc;
		}
	}
}

int main() {
	cout << FirstNotRepeatingChar("abaccdef") << endl;
}

上题中哈希表大小与字符有关,如果是汉字,那么消耗的空间会很大。

相关题目:
(1)给定两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有字符,为解决这个问题,可以创建一个用数组实现的简单哈希表存储第二个字符串中内容来解决这个问题,这样我们从头扫描第一个字符串的每个字符时,只需要用O(1)的时间来判断该字符是不是在第二个字符中,如果第一个字符串长度为n,那么时间复杂度为O(n)。

(2)定义一个函数,删除字符串中所有重复出现的字符,如输入"google",则输出"gole",我们可以创建一个用布尔数组实现的简单哈希表,数组中元素的意义为其下标看做ASCII码后对应的字母在字符串中是否已经出现,数组会被初始化为全是false,每当扫描到一个字符,就将数组中对应位置值置为true,当扫描到一个字符且数组中对应位置已经是true时,删除这个字符,用O(1)的时间就能判断出一个字符在前面是否已经出现过,如果字符串的长度是n,那么总的时间复杂度是O(n)。

(3)英语中,如果两个单词出现的字母相同,并且每个字母出现的次数也相同,那么这两个单词互为变位词,如"silent"和"listen"等,实现一个函数,判断两词是否互为变位词,我们可以用一个数组实现的简单哈希表来表示,扫描其中一个单词时,记下所有的字符出现次数,然后扫描第二个单词,每扫描到一个字符,将数组中对应字符位置的出现次数减1,如果最后数组中的值都是0,那么这两个单词就互为变位词。

2.字符流中第一个只出现一次的字符。实现一个函数,用来找出字符流中第一个只出现一次的字符。如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符为’g’,当从该字符流中读出前6个字符"google"时,第一个只出现一次的字符是’l’。

字符只能一个一个从字符流中读出来,可以定义一个数据容器来保存字符在字符流中的位置,当一个字符第一次从字符流中被读出来时,把它在字符流中的位置保存到数据容器中,当这个字符再次从字符流中读出来时,那么它就不是只出现一次的字符,也就可以被忽略了,这时把它在数据容器里保存的值更新成一个特殊的值,如负数值。

为尽可能高效地解决这个问题,需要在O(1)的时间内往数据容器里插入一个字符以及更新一个字符对应的值,这个数据容器可以用数组来实现,用字符的ASCII值作为哈希表的键值,把字符对应的位置作为哈希表的值:

#include <iostream>
#include <sstream>
using namespace std;

class CharStatistics {
public:
	CharStatistics() : index(0) {
		for (int i = 0; i < 256; ++i) {
			occurence[i] = -1;
		}
	}

	void Insert(char ch) {
		if (occurence[ch] == -1) {    //如该字符还未出现过,赋值为index
			occurence[ch] = index;
		}
		else if (occurence[ch] >= 0) {    //如该字符已经出现过,赋值为-2
			occurence[ch] = -2;
		}
		++index;
	}

	char FirstAppearingOnce() {
		char ch = '\0';    //保存结果,变量要初始化
		int minIndex = numeric_limits<int>::max();    //先将最小的索引赋值为最大的int值,由于我们要找到最小的index,这样每个index都会小于它
		for (int i = 0; i < 256; ++i) {
			if (occurence[i] >= 0 && occurence[i] < minIndex) {    //大于等于0说明只出现过一次,小于minIndex说明它是更靠前的只出现一次的字符
				ch = static_cast<char>(i);
				minIndex = occurence[i];
			}
		}
		return ch;
	}
private:
	int occurence[256];    //-1表示没出现过,-2表示出现过一次以上,非负数表示出现过一次且这个非负数是出现的坐标
	int index;
};

int main() {
	CharStatistics cs;
	istringstream sin("google");
	char c = '\0';
	while (sin >> c) {
		cs.Insert(c);
		char res = cs.FirstAppearingOnce();
		if (res) {
			cout << res << endl;
		}
		else {
			cout << "全部字符都重复出现了。" << endl;
		}
	}
}
发布了211 篇原创文章 · 获赞 11 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/tus00000/article/details/105103078