类型一:字符串
1. 有效括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
public boolean isValid(String s) {
Map<Character,Character>map = new HashMap<Character,Character>();
map.put(')','(');
map.put(']','[');
map.put('}','{');
Stack<Character> stack = new Stack<Character>();
for(int i=0;i<s.length();i++){
char c = s.charAt(i);
if(!stack.isEmpty()&&map.containsKey(c)&&map.get(c)==stack.peek()){
stack.pop();
}else{
stack.push(c);
}
}
if(stack.isEmpty()){
return true;
}else{
return false;
}
}
解法一:
-
引入两块新的内存,map和stack
-
stack用来存储字符的信息,通过出栈和入栈来进行操作
-
最后判断stack是否为空
解法二:通过一个栈来进行实现
实现原理:
"({[ ]})"
-
按照正确的匹配逻辑,如果是左括号,则对应的右括号入栈。
-
如果是右括号,则进行出栈操作
-
判断右括号是否对应
原因:
-
当我们遇到左括号 ( 时,我们 不是把 ( 压栈,而是压入它的配对 )
-
所以栈顶永远存的是"下一步期待出现的右括号"
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for(char c:s.toCharArray()){
if(c=='('){
stack.push(')');
}else if(c=='{'){
stack.push('}');
}else if(c=='['){
stack.push(']');
}else{
//栈为空,可能全部为右括号
if(stack.isEmpty() || stack.pop()!=c){
return false;
}
}
}
return stack.isEmpty();
}
2. 反转字符串:
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
public void reverseString(char[] s) {
int i=0;
int j = s.length-1;
while(i<j){
char temp = s[i];
s[i] = s[j];
s[j] = temp;
i++;
j--;
}
}
思想:通过双指针进行操作,没次交换以后
左指针进行++操作,右指针进行--操作。
为什么可以交换数组元素?
-
在 Java 中,数组是对象,数组名 s 是对数组对象的引用
-
数组元素是可变的(primitive 类型或对象引用都可以修改)
s[left] = s[right];
s[right] = temp;
拿到数组的某个位置,直接修改该位置的值
原数组 s 内的元素就被改变了
这是原地修改(in-place),不需要返回新的数组
3. 验证回文串
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,
短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。
字母和数字都属于字母数字字符。给你一个字符串 s,
如果它是回文串 ,返回 true ;否则,返回 false 。
方法一:
思路:
1.先进行原始数据的处理,即先把数据处理成全部是字母和数字组成的小写字符串。
- 通过双指针进行回文数的判断
public boolean isPalindrome(String s) {
StringBuffer StringBuffer = new StringBuffer();
for(Character ch:s.toCharArray()){
if(Character.isLetterOrDigit(ch)){
StringBuffer.append(ch);
}
}
String str = StringBuffer.toString().toLowerCase();
int left = 0;
int right = str.length()-1;
while(left<right){
if(str.charAt(left)!=str.charAt(right)){
return false;
}
left ++;
right --;
}
return true;
}
Character:
isLetter():判断是否是字母。
isDigit: 判断是否是数字
isLetterOrDigit: 判断是否字母或者Digit
方法二:
思路:
-
直接在原始数据上面进行回文数的判断
-
优点不会创建新的对象,浪费资源
public boolean isPalindrome(String s) {
int left =0;
int right = s.length()-1;
while(left<right){
while(left<right&& !Character.isLetterOrDigit(s.charAt(left))){
left++;
}
while(left<right&& !Character.isLetterOrDigit(s.charAt(right))){
right--;
}
if(Character.toLowerCase(s.charAt(left))!=Character.toLowerCase(s.charAt(right))){
return false;
}
left++;
right--;
}
return true;
}
上面的两种方法,为啥第一种执行的时间短?
为什么方法一更快?
避免创建新对象
方法二会生成一个新的 StringBuffer,再生成一个 String → 额外分配内存 + 拷贝字符数组
方法一直接在原字符串上操作,没有内存分配开销
减少字符拷贝
方法二每个字母/数字字符都要 append + toLowerCase → 拷贝操作
方法一只比较,不生成任何数组
更少循环操作
方法一跳过非字母数字,只比较必要字符
方法二先遍历一次构建过滤字符串,再遍历一次判断 → 实际上多了一次遍历
4. 最后一个单词的长度
给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。
返回字符串中 最后一个 单词的长度。
单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。
思路:从右向左进行遍历,获取第一个不为字母的空格。
public int lengthOfLastWord(String s) {
String str = s.trim();
int n=0;
for(int i=str.length()-1;i>=0;i--){
if(str.charAt(i) == ' '){
return n;
}
n++;
}
return n++;
}
public int lengthOfLastWord(String s) {
String str = s.trim();
String[] strs = s.split(" ");
if(strs.length==0){
return 0;
}
return strs[strs.length-1].length();
}
5. 找出字符串中第一个匹配项的下标
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle
字符串的第一个匹配项的下标(下标从 0 开始)。
如果 needle 不是 haystack 的一部分,则返回 -1 。
public int strStr(String haystack, String needle) {
int first = haystack.indexOf(needle);
int last = haystack.lastIndexOf(needle);
if(first!=-1){
return first;
}
if(last!=-1){
return last;
}
return -1;
}
思路:
第一次出现的元素的位置:indexOf()
最后一次出现的元素的位置:lastIndexOf()