2.力扣2018年常見編程題總結(字符串)

1.給定一個字符串,驗證它是否是迴文串,只考慮字母和數字字符,可以忽略字母的大小寫。

說明:本題中,我們將空字符串定義爲有效的迴文串。

示例 1:

輸入: "A man, a plan, a canal: Panama"
輸出: true

解:首先對字符串去掉標點符號以及全部變爲小寫。去掉標點符號採用isalnum函數,該函數用來判斷字符是否爲數字或字母。將字母變爲小寫採用tolower函數,之後根據對稱中心判斷兩邊的字符是否一樣,每個都相同則代表是迴文串。

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

class Solution {
public:
	bool isPalindrome(string s) {
		int len = s.size(); 
		char  *p=new char[len];
		int j = 0;

		for (int i = 0; i < len; i++)
		{
			if (isalnum(s[i]))//判斷是否爲字面或數字
			{
				p[j] = tolower(s[i]);
				++j;
			}
		}
		for (int i = 0; i < j/2; i++)
		{
			if (p[i] ==p[j - i-1])
				continue;
			else
				return 0;
		}
		return 1;
	}
};
int main()
{
	Solution s1;
	cout << s1.isPalindrome("  A man, a plan, a canal: Panama");//("A man, a plan, a canal: Panama");
}

2.給定一個字符串 s,將 s 分割成一些子串,使每個子串都是迴文串。

返回 s 所有可能的分割方案。

示例:

輸入: "aab" 輸出: [ ["aa","b"], ["a","a","b"] ]

解:採用回溯法的思想,利用深度優先進行搜索,res存儲最終結果,out存儲臨時結果,通過遞歸調用DFS完成深度搜索,傳入字符串,以及開始的第0層。在DFS函數中,遞歸的中止條件爲遍歷字符串的起始位置=字符串的長度,將臨時結果壓入res棧中。從i=start位置開始,進行循環直到等於字符串的長度,這裏可以看作爲每次取字符串的start位置處開始判斷是否爲迴文,i表示移動的位置,即最開始從start處開始移動。循環體中:判斷start到i處的字符串是否爲迴文,是的話則截取start到i長度的字符串壓入out臨時結果變量中,再次遞歸調用DFS,此時應該判斷從i+1處開始後面的字符串是否爲迴文,此時又進入新的for循環。最後將結果壓入res後,需要將out結果彈出,裝入下一次的臨時結果。

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

class Solution {
public:
	void DFS(string s,int start,vector<string>& out, vector<vector<string>>&res)
	{
		//遞歸中止條件
		if (start ==s.size())
		{
			res.push_back(out);//彈出臨時結果到res中
			return;
		}
		for ( int i = start; i < (int)s.size(); i++)
		{
			if (isPalindrome(s, start,i))//判斷是否爲迴文
			{
				out.push_back(s.substr(start, i - start + 1));//壓入截取的字符串
				DFS(s, i + 1, out, res);//start位置處開始判斷後面的字符串
				out.pop_back();
			}
		}
	}
	vector<vector<string>> partition(string s) {
		vector<vector<string>> res;
		vector<string> out;
		DFS(s, 0, out, res);//採用深度優先搜索
		return res;
	}

	bool isPalindrome(string s, int start, int end)//迴文判斷
	{
		while (start<end)
		{
			if (s[start] != s[end])
				return false;
			++start;
			--end;
		}
		return true;
	}

};
int main()
{
	Solution s1;
	vector<vector<string>> result= s1.partition("aba");
	for (int i = 0; i < (int)result.size(); i++)
	{
		for (int j = 0; j < (int)result[i].size(); j++)
		{
			cout << result[i][j]<<" ";
		}
		cout << endl;
	}
}

3.給定一個非空字符串 s 和一個包含非空單詞列表的字典 wordDict,判定 s 是否可以被空格拆分爲一個或多個在字典中出現的單詞。

說明:拆分時可以重複使用字典中的單詞。你可以假設字典中沒有重複的單詞。

示例 1:

輸入: s = "leetcode", wordDict = ["leet", "code"]

輸出: true 解釋: 返回 true 因爲 "leetcode" 可以被拆分成 "leet code"。

示例 2:輸入: s = "applepenapple", wordDict = ["apple", "pen"]

輸出: true 解釋: 返回 true 因爲 "applepenapple" 可以被拆分成 "apple pen apple"

解:通過兩個for循環遍歷字符串的字符,建立狀態數組存放對應字符串的狀態

#include <iostream>
#include<unordered_set>
#include<string>
#include<vector>
#include<algorithm>

using namespace std;

class Solution {
public:
	bool wordBreak(string s, vector<string>& wordDict) {

		int len = s.size();//字符串長度
		vector<bool> state(len + 1, false);
		state[0] = true;
		string tmp;

		for (int i = 1; i <= len; i++)
		{
			for (int j = 0; j <= i; j++) 
			{
				//判斷前面j爲是否爲true,並且判斷從j到j+i-j-1處的字符串是否在字典中
				tmp = s.substr(j, i - j);
				//begin是vector的第一個單詞,end爲vector最後一個單詞的下一個
				if (state[j] && find(wordDict.begin(), wordDict.end(), tmp) != wordDict.end()) {
					state[i] = true;
				}
			}
		}
		return state[len];//0-len處的字符串是否在字典中
	}
};
int main()
{
	Solution s1;
	vector<string> wordDict{ "apple", "pen" };
    cout<< s1.wordBreak("applepenapple", wordDict);
}

4.給定一個非空字符串 s 和一個包含非空單詞列表的字典 wordDict,在字符串中增加空格來構建一個句子,使得句子中所有的單詞都在詞典中。返回所有這些可能的句子。

說明:分隔時可以重複使用字典中的單詞。你可以假設字典中沒有重複的單詞。

示例 1:

輸入: s = "catsanddog" wordDict = ["cat", "cats", "and", "sand", "dog"]

輸出: [   "cats and dog",   "cat sand dog" ]

解:

#include <iostream>
#include<unordered_set>
#include<string>
#include<vector>
#include<algorithm>

using namespace std;

class Solution {
public:
	vector<string> wordBreak(string s, vector<string>& wordDict) {

		vector<bool> dp(s.size() + 1, false);
		dp[0] = true;
		unordered_set<string> m(wordDict.begin(), wordDict.end());

		int maxLength = 0;
		for (int i = 0; i < (int)wordDict.size(); i++) {
			maxLength = std::max(maxLength, (int)wordDict[i].size());
		}

		for (int i = 1; i <= s.size(); i++) {
			for (int j = std::max(i - maxLength, 0); j < i; j++) {
				if (dp[j] && m.find(s.substr(j, i - j)) != m.end()) {
					dp[i] = true;
					break;
				}
			}
		}
		vector<string> res;
		vector<string> each;

		DFS(s, res, each, m);
		return res;
	}

	void DFS(string s, vector<string>& res, vector<string>& each, unordered_set<string> &m) {
		if (s.empty()) {
			string tmp;
			for (vector<string>::iterator it = each.begin(); it != each.end(); it++) {
				if (it != (each.end() - 1)) {
					tmp = tmp + *it + " ";
				}
				else {
					tmp = tmp + *it;
				}
			}
			res.push_back(tmp);
			return;
		}

		for (int i = 0; i < s.size(); i++) {
			string str = s.substr(0, i + 1);
			if (m.find(str) != m.end()) {
				each.push_back(str);
				DFS(s.substr(i + 1), res, each, m);
				each.pop_back();
			}
		}
	}
};
int main()
{
	Solution s1;
	vector<string> wordDict{ "cats", "dog", "sand", "and", "cat" },res;
	res = s1.wordBreak("catsanddog", wordDict);
	for (int i = 0; i < res.size(); i++)
	{
		cout<< res[i]<<endl;
	}
}

5.實現一個 Trie (前綴樹),包含 insertsearch, 和 startsWith 這三個操作。

示例:

Trie trie = new Trie();

trie.insert("apple");

trie.search("apple"); // 返回 true

trie.search("app"); // 返回 false

trie.startsWith("app"); // 返回 true

解:構建一個前綴樹,樹的根結點爲root,每個結點含有26個子結點分佈代表26個字母,首先是初始化這26個子節點爲空,若插入一個單詞,則在對應索引處創建一個新的結點,來代表單詞的第一個字母,對於第二個字母同樣在第一個字母結點下面創建26個子節點,在第二個字母對應索引處創建新的子節點用來代表第三個字母。索引未被使用的都爲空,使用了的存放的是下一個結點的地址。一個單詞結束後val值加一。

#include <iostream>
using namespace std;

class Node
{
public:
	int val=0;	//一個單詞的完結val加一
	Node* next[26];	//指針數組,每個數的結點有26個子節點來存放一個字母
	Node()
	{
		//初始化指針數組
		for (int i = 0; i < 26; i++)
		{
			next[i] = NULL;
		}
	}
};
class Trie {
public:
	Node* root;

	Trie()
	{
		root = new Node();//返回一個結點對象指針
	}

	void insert(string word) {

		int len = word.length();
		Node* p = root;
		for (int i = 0; i < len; i++)
		{
			if (p->next[word[i]-'a']==NULL)//判斷頭字母在樹中是否爲空
			{
				p->next[word[i] - 'a'] = new Node();//創建一個結點,該結點由root引申下來的
			}
			p = p->next[word[i] - 'a'];//指向下一個結點
		}
		p->val++;
	}

	bool search(string word) {

		int len = word.length();
		Node* find = root;
		for (int i = 0; i < len; i++)
		{
			if (find->next[word[i] - 'a'] == NULL)
			{
				return false;
			}
			find = find->next[word[i] - 'a'];
		}
		if (find->val != 0)
			return true;
		else
			return false;
	}

	bool startsWith(string prefix) {

		int len = prefix.length();
		Node* find = root;
		for (int i = 0; i < len; i++)
		{
			if (find->next[prefix[i]-'a']==NULL)
			{
				return false;
			}
			find = find->next[prefix[i] - 'a'];
		}
		return true;
	}
};
int main()
{
	Trie *obj = new Trie();	//創建一個前綴樹對象
	obj->insert("apple");	//插入字符串
	cout << obj->search("apple")<<endl;	//查找字符串
	cout << obj->startsWith("app");	//查找字符串
}

6.給定一個二維網格 board 和一個字典中的單詞列表 words,找出所有同時在二維網格和字典中出現的單詞。單詞必須按照字母順序,通過相鄰的單元格內的字母構成,其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個單元格內的字母在一個單詞中不允許被重複使用。

示例:

輸入: words = ["oath","pea","eat","rain"] and

board = [ ['o','a','a','n'],

                ['e','t','a','e'],

                ['i','h','k','r'],

                ['i','f','l','v'] ]

輸出: ["eat","oath"]

解:

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

class Solution {
public:
	struct TrieNode {
		TrieNode *child[26];
		string str;
		TrieNode() : str("") {
			for (auto &a : child) a = NULL;
		}
	};
	struct Trie {
		TrieNode *root;
		Trie() : root(new TrieNode()) {}
		void insert(string s) {
			TrieNode *p = root;
			for (auto &a : s) {
				int i = a - 'a';
				if (!p->child[i]) p->child[i] = new TrieNode();
				p = p->child[i];
			}
			p->str = s;
		}
	};

	vector<string> findWords(vector<vector<char> >& board, vector<string>& words) {
		vector<string> res;
		if (words.empty() || board.empty() || board[0].empty()) return res;
		vector<vector<bool> > visit(board.size(), vector<bool>(board[0].size(), false));
		Trie T;
		for (auto &a : words) T.insert(a);
		for (int i = 0; i < board.size(); ++i) {
			for (int j = 0; j < board[i].size(); ++j) {
				if (T.root->child[board[i][j] - 'a']) {
					search(board, T.root->child[board[i][j] - 'a'], i, j, visit, res);
				}
			}
		}
		return res;
	}
	void search(vector<vector<char> > &board, TrieNode *p, int i, int j, vector<vector<bool> > &visit, vector<string> &res) {
		if (!p->str.empty()) {
			res.push_back(p->str);
			p->str.clear();
		}
		int d[][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };
		visit[i][j] = true;
		for (auto &a : d) {
			int nx = a[0] + i, ny = a[1] + j;
			if (nx >= 0 && nx < board.size() && ny >= 0 && ny < board[0].size() && !visit[nx][ny] && p->child[board[nx][ny] - 'a']) {
				search(board, p->child[board[nx][ny] - 'a'], nx, ny, visit, res);
			}
		}
		visit[i][j] = false;
	}
};

int main()
{
	Solution S;
	vector<vector<char>> board = { {'o','a','a','n'},
								   {'e','t','a','e'},
								   {'i','h','k','r' },
								   {'i','f','l','v'} };
	vector<string>words = { "oath","pea","eat","rain" },re;
	re = S.findWords(board, words);
	for (int i = 0; i < re.size(); i++)
	{
		cout <<re[i] << endl;
	}
}

7.給定兩個字符串 s 和 t ,編寫一個函數來判斷 t 是否是 s 的字母異位詞。

示例 1:

輸入: s = "anagram", t = "nagaram" 輸出: true

示例 2:輸入: s = "rat", t = "car" 輸出: false

說明:你可以假設字符串只包含小寫字母。

解:字母異位詞即兩個字符串中的字母集一樣,個數一樣,只是順序不一樣,因此可以用散列表來做,統計第一個字符串中的每個字母出現的次數,對第二個字符串做同樣的操作,兩個統計結果若一樣則代表他們字母集合是一樣的。

代碼:

#include <iostream>

using namespace std;

class Solution {
public:
	bool isAnagram(string s, string t) {
		int res1[26] = { 0 };
		int res2[26] = { 0 };
		for (int i = 0; i < (int)s.length(); i++)
			res1[int(s[i] - 'a')] += 1;
		for (int i = 0; i < (int)t.length(); i++)
			res2[int(t[i] - 'a')] += 1;
		for (int i = 0; i < 26; i++)
		{
			if (res1[i]-res2[i]==0)
				continue;
			else 
				return 0;
		}
		return 1;
	}
};

int main()
{
	Solution s1;
	cout<<s1.isAnagram("rat", "cat");
	return 1;
}

8.給定一個字符串,找到它的第一個不重複的字符,並返回它的索引。如果不存在,則返回 -1。

案例:s = "leetcode" 返回 0.

s = "loveleetcode", 返回 2.

解:同樣利用散列的性質,對字符串統計出現的次數依次存放在res數組中,取出來的第一個爲1的對應索引即爲第一次出現的字母。

代碼:

#include <iostream>
using namespace std;

class Solution {
public:
	int firstUniqChar(string s) {
		int res[26] = { 0 }; 
		for (int i = 0; i < (int)s.length() ; i++)
		{
			res[(int)(s[i] - 'a')] += 1;		//res存放每個字母的統計次數			
		}
		for (int j = 0; j < (int)s.length(); j++)
		{
			if (res[(int)(s[j] - 'a')] == 1)	//最先出現的一定爲第一次出現的字母
				return j;
		}
		return -1;
	}
};

int main()
{
	Solution s1;
	cout<<s1.firstUniqChar("leetcode");
	return 1;
}

9.編寫一個函數,其作用是將輸入的字符串反轉過來。輸入字符串以字符數組 char[] 的形式給出。不要給另外的數組分配額外的空間,你必須原地修改輸入數組、使用 O(1) 的額外空間解決這一問題。你可以假設數組中的所有字符都是 ASCII 碼錶中的可打印字符。

示例 1:輸入:["h","e","l","l","o"] 輸出:["o","l","l","e","h"]

解:因爲題目要求用O(1)的額外空間來存儲中間變量,依次可以將待翻轉的字符串進行首尾的交換,用一個for循環完成

#include <iostream>
using namespace std;

class Solution {
public:
	void reverseString(vector<char>& s) {
		char temp;
		for (int i = 0; i < (int)s.size()/2; i++)
			swap(s[i], s[s.size() - i-1]);
	}
};

int main()
{
	Solution s1;
	vector<char> a={ 'h','e','l','p'};
	s1.reverseString(a);
	return 1;
}

 

 

 

 

 

 

 

 

 

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