面試題目: 第一個非重複的字符

面試題目: 第一個非重複的字符

面試官: 看你的簡歷還不錯,本科也是學的計算機專業,要不咱們先做道題.
我: (瑟瑟發抖)好.

面試官: 給你一個字符串,請找出第一個只出現一次的字符.
我: 我思考一下,如果不存在只出現一次的字符,返回什麼?
面試官: 返回空’ '.順便說一下,字符串中只包含acii碼字符.

我: (內心獨白:這個題目簡單啊,可以直接暴力,管他呢,先給出一個暴力解) ok. 我可以用一個兩層循環,第一層遍歷每一個字符,第二層判斷是否出現一次,如果是,因爲我是從從遍歷,可以直接返回這個結果.
面試官: 恩,不錯,可以解決這個問題,請給我代碼吧.
我: 您看下這個代碼.

char first_char_1(string s) {
	if(s.size() == 0) return ' ';
	if(s.size() == 1) return 1;

	char ans = ' ';
	for(int i = 0; i < s.size(); i++) {
		int k = 0;
		for(int j = 0; j < s.size(); j++) {
			if(s[i] == s[j]) k ++;
		}
		if(k == 1) {
			ans = s[i];
			break;
		}
	}
	return ans;
}

面試官: 考慮到邊界條件,不錯. 如果出現一個的字符都在前面,你這個代碼效率應該不錯,但是一般不會這樣,你這個代碼還能再運行快點嗎?
我: 我這個思路的時間複雜度是O(n*n),主要是因爲我在判斷重複字符的時候用到了兩層循環,如果先排序判斷是否重複,時間複雜度就是O(nlogn).我再從排序好的字符中找到出現一次的字符.
面試官: 你這個思路不錯,時間也會更快,但是排序之後,你找到的出現一次的字符,不一定就是第一個出現的吧?
我: 哦,對,那我應該在排序的時候記錄每個字符的原始下標,這樣我就需要用到一個結構體數據結.我能使用額外的空間嗎?
面試官: 可以,聽着你的想法還行,你寫下代碼吧.
我: …, 您看下代碼

struct Node {
	char c;
	int index;
	Node() {}
	Node(char _c, char _index):c(_c),index(_index) {}
};

int cmp(Node a, Node b) {
	return a.c < b.c;
}

char first_char_2(string s) {
	if(s.size() == 0) return ' ';
	if(s.size() == 1) return s[0];

	Node a[s.size() + 1];
	for(int i = 0; i < s.size(); i++) {
		a[i] = Node(s[i], i);
	}
	sort(a, a + s.size(), cmp);
	
	Node ans(' ', s.size()+1);
	for(int i = 0; i < s.size(); i++) {
		// cout<<a[i].c<<" "<<a[i].index<<endl;
		if(i == 0) {
			if(a[i].c != a[i+1].c && ans.index > a[i].index) {
				ans = a[i];
			}
		}
		else if(i == s.size()-1) {
			if(a[i].c != a[i-1].c && ans.index > a[i].index) {
				ans = a[i];
			}
		}
		else {
			if(a[i].c != a[i-1].c && a[i].c != a[i+1].c && ans.index > a[i].index) {
				ans = a[i];
			}
		}
	}
	return ans.c;
}

面試官: 定義一個結構體保存字符和下標,之後排序,在排序好的數組中找到出現一次且下標最小的一個字符,時間複雜度是: O(n) + O(nlogn) + O(n). 運行時間複雜度總的可以算是O(nlogn),是比之前快了,你還可以給出更優的方法嗎?比如使用一個什麼數據結果?
我: 數據結構,空間換時間,記錄每個字符出現的字符. 方法二的優化方式是排序,可以直接判斷某個字符是否出現多次。對於方法一計算每一個字符的個數,我可以從個數出發,遍歷字符串,使用hash表記錄每個字符出現的字數. 再從頭遍歷一下字符串,遇到出現一個的字符就返回結果.

面試官: 點點頭.示意我寫下代碼.
我: …, 寫好了.

char first_char_3(string s) {
	if(s.size() == 0) return ' ';
	if(s.size() == 1) return 1;
	
	char ans = ' ';
	int cnt[256] = {0}; // hash 表
	for(int i = 0; i < s.size(); i++) {
		cnt[s[i]] ++;
	}
	for(int i = 0; i < s.size(); i++) {
		if(cnt[s[i]] == 1) {
			ans = s[i];
			break;
		}
	}
	return ans;
}

面試官: 不錯,現在的時間複雜度是O(n)+O(n),總的時間複雜度是O(n). 你反應還挺快,這個算法已經很快了,我想再考考你,你還能繼續加快這個算法嗎?
我: 還能優化?這個已經是最優的吧,因爲要返回的是第一個出現一次的字符,所以只能第一次記錄次數,第二次得到結果。必須需要遍歷兩次吧.
面試官: 你說的沒錯,但是在實際中,字符串都是很長的,我前面說過字符串都是由256個acii碼組成的,你可以從這個角度考慮下.
我: 你的意思是,同樣使用hash表,第一個循環負責記錄字符的次數,這個必須要有. 那我只能從第二個循環思考,如果我在第一層循環中,不僅記錄下來每個字符的次數,還記錄下來這個字符第一次出現的下標,那我就可以在256個字符中直接找到結果. 時間複雜度就是:O(n) + O(256).
面試官: 你的想法是對的,你寫代碼試試吧.
我: 寫好了.

struct TNode {
	int k;
	int index;
	TNode() {}
	TNode(int _k, int _index):k(_k), index(_index) {}
};

char first_char_4(string s) {
	if(s.size() == 0) return ' ';
	if(s.size() == 1) return s[0];
	
	TNode cnt[256];
	for(int i = 0; i < 256; i++) {
		cnt[i].k = 0;
		cnt[i].index = -1;
	}

	for(int i = 0; i < s.size(); i++) {
		if(cnt[s[i]].index == -1) {
			cnt[s[i]].index = i;
		}
		cnt[s[i]].k ++;
	}
	
	int t_index = s.size() + 1;
	int ans = (char) ' ';
	for(int i = 0; i < 256; i++) {
		if(cnt[i].k == 1 && cnt[i].index < t_index) {
			t_index = cnt[i].index;
			ans = i;
		}
	}
	return (char) ans;
}

面試官: 看你的思路和代碼能力都還不錯. 你先定義一個數據結構保存字符出現的次數和第一次的下標,在256個字符中找到出現一次,而且只有下標最小的字符. 在n很大的時候,時間複雜度是很小. 不錯,算法題就這樣吧,咱們談談你簡歷上的項目吧.
我: bala bala …

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章