验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: "A man, a plan, a canal: Panama"
输出: true
示例 2:
输入: "race a car"
输出: false
class Solution {
public boolean isPalindrome(String s) {
//replace是替换单个的字符串,replaceAll是使用的正则表达式,用方括号括起来
s=s.replaceAll("[^a-zA-Z0-9]","");
System.out.println(s);
int length=s.length();
int mid=length/2;
boolean even;
if(length%2==0) even=true;
else even=false;
Stack<Character> stack=new Stack<Character>();
char[] array=s.toCharArray();
for(int i=0;i<mid;i++){
stack.push(array[i]);
}
if(!even) mid++;
for(int i=mid;i<length;i++){
char c=stack.pop();
System.out.print(c);
System.out.println(array[i]);
if(!equals(array[i],c))
return false;
}
return true;
}
public boolean equals(char a,char b){
//System.out.println(a+' '+b);
if(Character.isUpperCase(a))
a=Character.toLowerCase(a);
if(Character.isUpperCase(b))
b=Character.toLowerCase(b);
if(a==b) return true;
else return false;
}
}
用的是栈,但其实感觉还是有一点小题大做了,直接访问对应位置的字符其实就可以了。
分割回文串
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例:
输入: "aab"
输出:
[
["aa","b"],
["a","a","b"]
]
class Solution {
public List<List<String>> partition(String s) {
//最初是尝试使用动态规划求所有的字符串组合,但是似乎并不合适,动态规划用来求最大回文串长度
// List<List<String>> result = new ArrayList<List<String>>();
// char[] chars=s.toCharArray();
// int length=s.length();
// boolean[][] dp=new boolean[length][length]; //使用动态规划,dp[i][j]表示从i到j的子串是否为回文串
// for(int i=0;i<length;i++){
// dp[i][i]=true;
// }
// for(int i=0;i<length-1;i++){
// if(chars[i]==chars[i+1])
// dp[i][i+1]=true;
// else
// dp[i][i+1]=false;
// }
// for(int i=0;i<length;i++){ //遍历每一行
// for(int l=2;l<length;l++){
// for(int j=0;j<length;j++){
// if(l+j>=length)
// break;
// if(dp[j+1][l+j-1]==false)
// dp[i][l+j]=false;
// else if(chars[j]==chars[l+j])
// dp[i][l+j]=true;
// else
// dp[i][l+j]=false;
// }
// }
// }
// for(int i=0;i<length;i++){
// ArrayList<String> vector=new ArrayList<String>();
// for(int j=i;j<length;j++){
// if(dp[i][j]==true)
// vector.add(s.substring(i,j));
// }
// result.add(vector);
// }
// for(int i=0;i<length;i++){
// for(int j=0;j<length;j++){
// System.out.print(dp[i][j]);
// System.out.print(" ");
// System.out.println("");
// }
// }
// return result;
}
}
选择使用回溯法遍历得到所有的可能,也就是深度优先搜索。
class Solution:
# 是要用到DFS(深度优先搜索算法)
# result = []
def isPartition(self, s: str):
start = 0
end = len(s) - 1
while (start <= end):
if (s[start] == s[end]):
start += 1
end -= 1
else:
return False
return True
def nextWords(self, s: str, index: int, List,result):
if (index == len(s)):
temp2=List.copy()
result.append(temp2)
return
for i in range(index, len(s)):
temp = s[index:i + 1]
if (self.isPartition(temp)):
List.append(temp)
self.nextWords(s, i + 1, List,result)
del List[len(List) - 1]
def partition(self, s: str):
result=[]
r = []
self.nextWords(s, 0, r,result)
return result
决定选择放弃Java选择Python来写算法,但是发现确实有很多坑
- 列表在传递的时候要用 .copy() 函数(a=b.copy()),这样对a的修改就不会影响b,因为其实所有的nextWords()函数使用的都是相同的一个list,也就是在partition中定义的r,如果不使用copy函数就会出现最终的返回值是一堆空的列表。
- LeetCode提交代码之后进行验证的方法应该是创建一个这个类的对象,并重复调用主方法,在最开始的时候result定义的是类内的变量,就会出现上一种情况的执行情况仍然会保存在result中,改为函数类内的变量问题得以解决。Java中就没有这个问题,还是要再多熟悉一些,加油啦
分割字符串
给定一个非空字符串 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"。
注意你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
看到这个题目的最初的想法是和分割回文串的思想是一样的,用回溯法来解答,但是就会出现这样一种情况,s="aaaaaaaaaaaaaaaaaaaaaaaaaaaab",wordDict=['a','aaa','aaaaaa'] 类似这样的情况会导致时间复杂度很高,一直要经历对于‘a’的考虑,没有通过submit。
另一种思路是使用动态规划的方法,dp[i]表示长度为 i 的字符串是否可以被分割成单词,这样只需考虑前面的长度的情况,而不需要考虑前面是怎么分割的,不需要考虑回溯
class Solution:
def wordBreak(self, s: str, wordDict) -> bool:
dp = [False for i in range(len(s) + 1)] # dp[i]表示长度为i的字符串是否可以拆分
dp[0] = True
for i in range(1, len(s) + 1):
self.getDp(dp, i, wordDict, s)
#print(dp)
return dp[len(s)]
def getDp(self, dp, index, wordDict, s): # dp[index]
for i in range(index - 1, -1, -1):
if dp[i] == True:
ss=s[i:index]
if s[i:index] in wordDict:
dp[index] = True
# 这里在刚开始写的时候忘记return了,结果就是一直是false
return
dp[index] = False
分割回文串Ⅱ
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。
说明:
- 分隔时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
示例 1:
输入:
s = "catsanddog"
wordDict = ["cat", "cats", "and", "sand", "dog"]
输出:
[
"cats and dog",
"cat sand dog"
]
示例 2:
输入:
s = "pineapplepenapple"
wordDict = ["apple", "pen", "applepen", "pine", "pineapple"]
输出:
[
"pine apple pen apple",
"pineapple pen apple",
"pine applepen apple"
]
解释: 注意你可以重复使用字典中的单词。
示例 3:
输入:
s = "catsandog"
wordDict = ["cats", "dog", "sand", "and", "cat"]
输出:
[]
思路与上一个题目是一样的,只不过是 dp 中存放的是上一个是可拆分串的长度的列表,即 dp[ i ] = [ j, k ] 表示长度为 i 的可拆分串是在长度为 j 或长度为 k 的可拆分串上构成的。
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> List[str]:
dp=[[] for i in range(len(s)+1)]
results=[[] for i in range(len(s))]
dp[0].append(-1)
for i in range(1,len(s)+1):
self.getDp(dp,i,wordDict,s)
return self.getResult(dp,results,s)
def getDp(self,dp,index,wordDict,s):
for i in range(index-1,-1,-1):
if len(dp[i])!=0:
ss=s[i:index]
if s[i:index] in wordDict:
dp[index].append(i)
def getResult(self,dp,results,s):
for i in dp[len(s)]:
result=[s[i:len(s)]]
self.getR(dp,results,s,i,result)
temp=[]
for i in results:
if len(i)!=0:
s=''
for item in i:
s=s+item+' '
s=s.strip()
temp.append(s)
results=temp.copy()
print(temp)
return temp
def getR(self,dp,results,s,index,result):
if index==0:
results.append(result)
return
for i in dp[index]:
r=result.copy()
r.insert(0,s[i:index])
self.getR(dp,results,s,i,r)
另外需要看清题目,输出的结果是字符串的列表,不是列表的列表,还要注意的就是添加每个字符串中的单词的顺序。
实现 Trie (前缀树)
实现一个 Trie (前缀树),包含 insert
, search
, 和 startsWith
这三个操作。
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 true
trie.search("app"); // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");
trie.search("app"); // 返回 true
说明:
- 你可以假设所有的输入都是由小写字母
a-z
构成的。 - 保证所有输入均为非空字符串。
struct TrieNode {
//static constexpr size_t kAsciiCount = 256;
TrieNode(char value)
:value(value), isEnd(false), isRoot(false){
}
bool isRoot;
bool isEnd;
char value;
vector<TrieNode*> subNodes;
};
class Trie {
public:
TrieNode* root;
/** Initialize your data structure here. */
Trie() {
root = new TrieNode('R');
(*root).isRoot = true;
}
/** Inserts a word into the trie. */
void insert(string word) {
if (word.empty())
return;
TrieNode* current = root;
for (char c : word) {
TrieNode* temp = getNode(c, current->subNodes);
if (temp == NULL)
{
TrieNode* newNode = new TrieNode(c);
current->subNodes.push_back(newNode);
current = newNode;
}
else
current = temp;
}
current->isEnd = true;
}
TrieNode* getNode(char c, vector<TrieNode*> subNodes) {
if (subNodes.size() == 0)
return NULL;
for (TrieNode* node : subNodes) {
if (node->value == c)
return node;
}
return NULL;
}
/** Returns if the word is in the trie. */
bool search(string word) {
TrieNode* current = root;
for (char c : word) {
TrieNode* temp = getNode(c, current->subNodes);
if (temp == NULL)
return false;
else
current = temp;
}
return current->isEnd;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
TrieNode* current = root;
for (char c : prefix) {
TrieNode* temp = getNode(c, current->subNodes);
if (temp == NULL)
return false;
else
current = temp;
}
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* bool param_2 = obj.search(word);
* bool param_3 = obj.startsWith(prefix);
*/
用C++的话就要好好区分传引用,传值和传地址的区别了。
字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。(https://baike.baidu.com/item/字典树/9825209?fr=aladdin)
单词搜索Ⅱ
有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
说明:
你可以假设字符串只包含小写字母。
进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
所谓的字母异位词的意思就是组成两个字符串的字母是完全相同的,只不过个数是不同的,不需要想的很复杂了
class Solution {
//没有理解字母异位词的含义是什么,意思是只要是那些字母就可以了
//map是C++中的容器,它提供一对一的哈希表,第一个为关键字,只可以出现一次,第二个为键值,map内部自建一棵红黑树
public:
bool isAnagram(string s, string t) {
if(s.length()!=t.length())
return false;
map<char,int> sCount,tCount;
map<char,int>::iterator it;
for(int i=0;i<s.length();i++){
char c=s.at(i);
if(sCount.find(c)==sCount.end()){
sCount.insert(pair<char,int>(c,1));
}
else{
sCount[c]+=1;
}
}
for(int i=0;i<t.length();i++){
char c=t.at(i);
if(tCount.find(c)==tCount.end()){
tCount.insert(pair<char,int>(c,1));
}
else{
tCount[c]+=1;
}
}
//比较两个字符串中的字符的个数
//使用迭代器对map进行遍历
it=sCount.begin();
while(it!=sCount.end()){
if(tCount[it->first]!=it->second)
return false;
it++;
}
return true;
}
};
在这里使用了用map存储的方式,存入和读取起来都是很慢的,但是这样做解决了如果字符中有Unicode编码的字符如何解决的问题。
其他的解决方式
- 按照题目已知,给每个字符串定义一个长度为26的数组存储当前字符的个数。(无法解决存在Unicode编码的情况)
- 分别将两个字符串中的字符进行排序,检查对应位置是否相同
字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
案例:
s = "leetcode"
返回 0.
s = "loveleetcode",
返回 2.
题目给的提示说可以假设只包含小写字母。
其实是一个很简单的题目,自己想的太过复杂了,其实只需要使用哈希表记录每个字符出现的次数,然后再从头开始遍历一次这个字符串,检查字符的出现次数是否为1.
class Solution {
public:
//bool find(char,vector<char>);
int firstUniqChar(string s) {
//最初选择的方法是将字符串转化为字符串数组后进行排序,再找出唯一的字符的下标,但那样返回的是排序之后的下标
///第三种,建立一个以26个字母为下标的哈希表
int count[26];
for(int i=0;i<26;i++)
count[i]=0;
for(int i=0;i<s.length();i++){
count[s.at(i)-'a']++;
}
for(int i=0;i<s.length();i++){
if(count[s.at(i)-'a']==1)
return i;
}
return -1;
}
};
再第一次运行这个代码的时候发生了生成错误的问题,原因是少了一个括号!
反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[]
的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 1:
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:
输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
class Solution {
public:
void reverseString(vector<char>& s) {
int len=s.size();
char temp;
for(int i=0;i<len/2;i++){
temp=s[i];
s[i]=s[len-1-i];
s[len-1-i]=temp;
}
}
};