不知不觉就第十一天了,加油!!!
20. 有效的括号
我用的是Java自带Stack(栈)实现的匹配(我的上一篇文章总结了Java自带的栈(Stack)、队列(Queue)、双端队列(Deque))。这里有个小细节:当遇到朝右的括号时,加入栈中的是朝左的括号,即,他的匹配项,或者互补项。这与“两数之和”寻找两数时,使用哈希法有异曲同工之妙!“两数之和”中(第六天打卡文章“两数之和”中有详解),是在哈希表里查找是否存在目标值的互补项,这种存入互补项,之后直接对比互补项是否相等的思路,还是非常妙的。
代码:
public boolean isValid(String s) {
Stack<Character> check = new Stack<>();
char[] ch = s.toCharArray();
for(int i = 0; i < ch.length; i++) {
switch(ch[i]) { // 加入它的互补项(朝左的括号),等等匹配时,只需要判断是否相等即可
case '(':
check.add(')');
break;
case '{':
check.add('}');
break;
case '[':
check.add(']');
break;
}
if(ch[i] == ')' || ch[i] == '}' || ch[i] == ']' ) {// 遇到朝左的括号,开始匹配
if(check.isEmpty()) { // 遇到左括号,但是栈为空,则返回false
return false;
}else { // 不为空,那么开始配对
// 经过刚才的入栈,栈里是 朝左的 括号,只需判断是否相等即可
if(check.peek() != ch[i]) {
return false; // 遇到不匹配的括号
}else {
check.pop(); // 相等则出栈
}
}
}
}
// 判断栈是否为空,不为空,那么说明还有没有匹配的左括号,返回false
if(!check.isEmpty()) {
return false;
}else {// 为空,说明全部配对~
return true;
}
// 其实可以直接return check.isEmpty();
}
哈希法的尝试:
当然,我也尝试了用哈希法(有关哈希法常用的三个哈希表:set、map,第六天打卡文章中也有总结)来解决,下面是思路:
哈希法代码如下:
/**
* 哈希法
* 用HashMap作为哈希表,key:(朝右括号对应的)朝左的括号
* value:(需要的)朝左的括号数
* 根据我设计的这个key-value组合,即,当遇到一个朝右括号,就设置一个朝左括号(key),并设置朝左括号数(value)加1,
* 后续遇到一次朝左的括号(key),就让朝左括号数(value)减1
* 当遇到value小于0,说明“左括号必须以正确的顺序闭合”不满足了
* 当第一次遍历字符串结束之后,再遍历一次map,看看是否有value值不为0的,有则说明无效
*/
public boolean isValid(String s) {
// 哈希法:
Map<Character,Integer> checkMap = new HashMap<>();
char[] ch = s.toCharArray();
for(char c : ch){
// 当遇到一个朝右括号,就设置一个朝左括号(key),并设置朝左括号数(value)加1
if(c == '(') {
checkMap.put(')', checkMap.getOrDefault(')', 0) + 1);
}
else if(c == '{') {
checkMap.put('}', checkMap.getOrDefault('}', 0) + 1);
}
else if(c == '[') {
checkMap.put(']', checkMap.getOrDefault(']', 0) + 1);
}
else if(checkMap.containsKey(c)) { // 如果此时遇到了一个有匹配的朝右括号的 朝左括号(说明之前遇到了与他匹配的朝左的括号,并且设置了键值对)
checkMap.put(c,checkMap.get(c) - 1); // 把这个朝左括号所对应的次数减1
}
else { // 走到这里,说明是遇到了没有匹配的 朝左括号,即:在这个 朝左的 括号之前,没有出现过与他匹配的朝右的括号,那么直接返回false
// 例如:{) 、 {})等等
return false;
}
}
// 开始遍历value不等于0的key
for (Map.Entry<Character, Integer> entry : checkMap.entrySet()) {
if(entry.getValue() != 0) {
return false;
}
}
return true;
}
但是。。。。似乎解决不了┓(;´_`)┏ 有一个测试用例是:
这个测试用例似乎想测试:前一位是不是遍历过的元素。哈希法好像解决不了了呢,emmmmm,反正我是没想到解决方法了,留给以后解决吧 _(:3」∠❀)_ 累了累了,下一道了。
对了!最后,char类的包装类是:Character,集合定义泛型时记得别用错了。
1047. 删除字符串中的所有相邻重复项
思路,很简单,就是用栈去存储每一位字符,存入之前判断栈顶是否与之相同(第一次不用判断直接压入),如果相同(即相邻元素重复)则不存入,且弹出栈顶元素。
代码:
public String removeDuplicates(String s) {
char[] ch = s.toCharArray();
Deque<Character> check = new ArrayDeque<>(); // 栈比较好处理删除,但是输出结果的时候不方便,所以用双端队列
for(char c : ch) {
if(check.isEmpty()){ // 第一次,直接入队,无序判断删除
check.addLast(c);
}
else if(c != check.peekLast()){
check.addLast(c);
}
else{
check.removeLast(); // 遇到之前储存过的队列尾与待入队的字符相同,则删除掉队尾的这个字符
continue;
}
}
String result = "";
while(!check.isEmpty()){
result += check.removeFirst();
}
return result;
}
卡哥代码里也是用的Deque,但是方法是直接使用与Stack同名的push(压入元素)和pop(弹出栈顶元素)。我上一篇文章总结了双端队列(Deque)的常用方法以及Deque内部与队列和栈同名的方法。代码随想录里还有双指针法和用字符串作为栈的解法,比用队列速度更快,二刷再来吧,下一道下一道~。
代码随想录代码:
class Solution {
public String removeDuplicates(String S) {
//ArrayDeque会比LinkedList在除了删除元素这一点外会快一点
//参考:https://stackoverflow.com/questions/6163166/why-is-arraydeque-better-than-linkedlist
ArrayDeque<Character> deque = new ArrayDeque<>();
char ch;
for (int i = 0; i < S.length(); i++) {
ch = S.charAt(i);
if (deque.isEmpty() || deque.peek() != ch) {
deque.push(ch);
} else {
deque.pop();
}
}
String str = "";
//剩余的元素即为不重复的元素
while (!deque.isEmpty()) {
str = deque.pop() + str;
}
return str;
}
}
150. 逆波兰表达式求值
和上一道题思路很像啦,就是栈的应用,关键在于理解什么是后缀表达式吧,做着题还有一个问题是,Java中String类与其他类互相转换有忘了,待会儿总结一下。
代码:
class Solution {
/**
思路比较简单,就是利用栈来计算,遇到数字则进栈,遇到算术运算符则弹出两个栈顶元素进行计算,并将计算结果再次压入栈中,最终遍历完整个表达式后,弹出栈顶元素,即是结果
*/
public int evalRPN(String[] tokens) {
Stack<Integer> temp = new Stack<>();
int result = 0;
int a = 0;
int b = 0;
for(int i = 0; i < tokens.length; i++) {
switch(tokens[i]){
case "+":
{
b = temp.pop();
a = temp.pop();
result = a + b;
temp.push(result); // 别忘了把结果再压入栈中
continue;
}
case "-":
{
b = temp.pop();
a = temp.pop();
result = a - b;
temp.push(result);
continue;
}
case "*":
{
b = temp.pop();
a = temp.pop();
result = a * b;
temp.push(result);
continue;
}
case "/":
{
b = temp.pop();
a = temp.pop();
result = a / b;
temp.push(result);
continue;
}
default:break;
}
// 不是运算符,那么就是数字了,直接压入
temp.push(Integer.valueOf(tokens[i]));
}
// 最终只剩下栈顶元素,直接弹出即是最终结果
return temp.pop();
}
}
String类与其他类的相互转换:
String to Integer:
- 使用
Integer.parseInt()
或Integer.valueOf()
方法。 - 示例:
int num = Integer.parseInt("123");
String to Long:
- 使用
Long.parseLong()
或Long.valueOf()
方法。 - 示例:
long longNum = Long.parseLong("1234567890");
String to Float and Double:
- 使用
Float.parseFloat()
或Double.parseDouble()
方法。 - 示例:
float floatNum = Float.parseFloat("123.45");
String to Character:
- 对于单个字符的转换,通常使用String的
charAt(0)
方法。如果要获取整个字符串,可以创建一个字符数组,用String的toCharArray(str)
方法。 - 示例:
char[] ch = toCharArray("123");
或char ch = str.charAt(0);
其中str
是字符串。
String to BigInteger:
- 使用
new BigInteger(String)
的构造函数。 - 示例:
BigInteger bigInt = new BigInteger("12345678901234567890");
String to BigDecimal:
- 使用
new BigDecimal(String)
的构造函数。 - 示例:
BigDecimal bd = new BigDecimal("12345678901234567890.1234567890");
自动装箱与拆箱:当将String转换为基本数据类型时,可以直接使用自动装箱(autoboxing)。反之,将基本数据类型转换为String时,可以使用对象的toString()方法或String的valueOf()方法。例如:
Integer boxedInt = Integer.parseInt("123"); // Integer自动装箱
String intStr = boxedInt.toString(); // Integer对象的toString()方法返回String表示形式