题目## 题目## 题目
题目的主要信息:
- 写一个支持+ - *三种符号的运算器,其中优先级+ - 是一级,*更高一级
- 支持括号运算
举一反三:
方法:栈 + 递归(推荐使用)
知识点:栈
栈是一种仅支持在表尾进行插入和删除操作的线性表,这一端被称为栈顶,另一端被称为栈底。元素入栈指的是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;元素出栈指的是从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
思路:
对于上述两个要求,我们要考虑的是两点,一是处理运算优先级的问题,二是处理括号的问题。
处理优先级问题,那必定是乘号有着优先运算的权利,加号减号先一边看,我们甚至可以把减号看成加一个数的相反数,则这里只有乘法和加法,那我们优先处理乘法,遇到乘法,把前一个数和后一个数乘起来,遇到加法就把这些数字都暂时存起来,最后乘法处理完了,就剩余加法,把之前存起来的数字都相加就好了。
处理括号的问题,我们可以将括号中的部分看成一个新的表达式,即一个子问题,因此可以将新的表达式递归地求解,得到一个数字,再运算:
- 终止条件: 每次遇到左括号意味着进入括号子问题进行计算,那么遇到右括号代表这个递归结束。
- 返回值: 将括号内部的计算结果值返回。
- 本级任务: 遍历括号里面的字符,进行计算。
具体做法:
- step 1:使用栈辅助处理优先级,默认符号为加号。
- step 2:遍历字符串,遇到数字,则将连续的数字字符部分转化为int型数字。
- step 3:遇到左括号,则将括号后的部分送入递归,处理子问题;遇到右括号代表已经到了这个子问题的结尾,结束继续遍历字符串,将子问题的加法部分相加为一个数字,返回。
- step 4:当遇到符号的时候如果是+,得到的数字正常入栈,如果是-,则将其相反数入栈,如果是*,则将栈中内容弹出与后一个元素相乘再入栈。
- step 5:最后将栈中剩余的所有元素,进行一次全部相加。
图示:
Java实现代码:
import java.util.*;
public class Solution {
public ArrayList<Integer> function(String s, int index){
Stack<Integer> stack = new Stack<Integer>();
int num = 0;
char op = '+';
int i;
for(i = index; i < s.length(); i++){
//数字转换成int数字
//判断是否为数字
if(s.charAt(i) >= '0' && s.charAt(i) <= '9'){
num = num * 10 + s.charAt(i) - '0';
if(i != s.length() - 1)
continue;
}
//碰到'('时,把整个括号内的当成一个数字处理
if(s.charAt(i) == '('){
//递归处理括号
ArrayList<Integer> res = function(s, i + 1);
num = res.get(0);
i = res.get(1);
if(i != s.length() - 1)
continue;
}
switch(op){
//加减号先入栈
case '+':
stack.push(num);
break;
case '-':
//相反数
stack.push(-num);
break;
//优先计算乘号
case '*':
int temp = stack.pop();
stack.push(temp * num);
break;
}
num = 0;
//右括号结束递归
if(s.charAt(i) == ')')
break;
else
op = s.charAt(i);
}
int sum = 0;
//栈中元素相加
while(!stack.isEmpty())
sum += stack.pop();
ArrayList<Integer> temp = new ArrayList<Integer>();
temp.add(sum);
temp.add(i);
return temp;
}
public int solve (String s) {
ArrayList<Integer> res = function(s, 0);
return res.get(0);
}
}
C++实现代码:
class Solution {
public:
vector<int> function(string s, int index){
stack<int> stack;
int num = 0;
char op = '+';
int i;
for(i = index; i < s.length(); i++){
//数字转换成int数字
if(isdigit(s[i])){
num = num * 10 + s[i] - '0';
if(i != s.length() - 1)
continue;
}
//碰到'('时,把整个括号内的当成一个数字处理
if(s[i] == '('){
//递归处理括号
vector<int> res = function(s, i + 1);
num = res[0];
i = res[1];
if(i != s.length() - 1)
continue;
}
switch(op){
//加减号先入栈
case '+':
stack.push(num);
break;
case '-':
//相反数
stack.push(-num);
break;
//优先计算乘号
case '*':
int temp = stack.top();
stack.pop();
stack.push(temp * num);
break;
}
num = 0;
//右括号结束递归
if(s[i] == ')')
break;
else
op = s[i];
}
int sum = 0;
//栈中元素相加
while(!stack.empty()){
sum += stack.top();
stack.pop();
}
return vector<int> {sum, i};
}
int solve(string s) {
return function(s, 0)[0];
}
};
Python代码实现:
class Solution:
def solve(self , s ):
s = s.strip()
stack = []
res = 0
num = 0
sign = '+'
index = 0
while index < len(s):
if s[index] == ' ':
index += 1
continue
# 遇到左括号
if s[index] == '(':
end = index + 1
lens = 1
while lens > 0:
if s[end] == '(':
lens += 1
if s[end] == ')':
lens -= 1
end += 1
#将括号视为子问题进入递归
num = self.solve(s[index + 1: end - 1])
index = end - 1
continue
#字符数字转换成int数字
if '0' <= s[index] <= '9':
num = num * 10 + int(s[index])
#根据符号运算
if not '0' <= s[index] <= '9' or index == len(s) - 1:
#加
if sign == '+':
stack.append(num)
#减,加相反数
elif sign == '-':
stack.append(-1 * num)
#乘优先计算
elif sign == '*':
stack.append(stack.pop() * num)
num = 0
sign = s[index]
index += 1
#栈中元素相加
while stack:
res += stack.pop()
return res
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n), n n n为字符串长度,相当于遍历一遍字符串全部元素
- 空间复杂度: O ( n ) O(n) O(n),辅助栈和递归栈的空间
题目的主要信息:
- 实现一个函数判断字符串是否表示数字
- 包括科学记数法的数字:一个整数或小数,后接一个可选的大小写字母e,后接一个可正可负的整数
- 小数,可选的正负号,小数点前后整数任意有一个即可
- 整数,可选的正负号,加上后面的整数
- 字符串可能包含前导、后导空格
举一反三:
学习完本题的思路你可以解决如下题目:
方法一:遍历(推荐使用)
思路:
既然是字符串,那我们使用一个遍历字符串的全局变量作为下标,然后遍历字符串依次检查每个字符,根据字符属于的类型进行判断。
具体做法:
- step 1:先判断空串的情况。
- step 2:遍历字符前面的空格,将下标移到第一个不是空格的位置。遍历字符串后面的空格,将长度限制在最后一个空格。若是长度小于下标,说明全是空格。
- step 3:剩余部分判断,开始找数字,判断是不是一个有符号的整数,优先判断符号,直到遇到非数字停止。
- step 4:如果有小数点,那么开始判断小数点后是不是一个无符号的整数,也是遍历直到遇到非数字为止,出现小数点的话,小数点前和小数点后的数字任意有一即可。
- step 5:若是出现字母e或者E,那么需要判断后面是不是一个有符号的整数,,也是遍历直到遇到非数字为止,e前后都要数字。
- step 6:最后检查下标是不是遍历到了刚刚限制的长度,若是没有,说明后面还有非空格,不符合要求。
图示:
Java实现代码:
import java.util.*;
public class Solution {
//遍历字符串的下标
private int index = 0;
//有符号判断
private boolean integer(String s){
if(index < s.length() && (s.charAt(index) == '-' || s.charAt(index) == '+'))
index++;
return unsigned_integer(s);
}
//无符号数判断
private boolean unsigned_integer(String s){
int temp = index;
while(index < s.length() && (s.charAt(index) >= '0' && s.charAt(index) <= '9'))
index++;
return index > temp;
}
public boolean isNumeric (String str) {
//先判断空串
if(str == null || str.length() == 0)
return false;
//去除前面的空格
while(index < str.length() && str.charAt(index) == ' ')
index++;
int n = str.length() - 1;
//去除字符串后面的空格
while(n >= 0 && str.charAt(n) == ' ')
n--;
//限制的长度比下标1
n++;
//全是空格情况
if(n < index)
return false;
//判断前面的字符是否是有符号的整数
boolean flag = integer(str);
//如果有小数点
if(index < n && str.charAt(index) == '.'){
index++;
//小数点前后有无数字可选
flag = unsigned_integer(str) || flag;
}
//如果有e
if(index < n && (str.charAt(index) == 'e' || str.charAt(index) == 'E')){
index++;
//e后面必须全是整数
flag = flag && integer(str);
}
//是否字符串遍历结束
return flag && (index == n);
}
}
C++实现代码:
class Solution {
public:
//遍历字符串的下标
int index = 0;
//有符号判断
bool integer(string& s){
if(index < s.length() && (s[index] == '-' || s[index] == '+'))
index++;
return unsigned_integer(s);
}
//无符号数判断
bool unsigned_integer(string& s){
int temp = index;
while(index < s.length() && (s[index] >= '0' && s[index] <= '9'))
index++;
return index > temp;
}
bool isNumeric(string str) {
//先判断空串
if(str.length() == 0)
return false;
//去除前面的空格
while(index < str.length() && str[index] == ' ')
index++;
int n = str.length() - 1;
//去除字符串后面的空格
while(n >= 0 && str[n] == ' ')
n--;
//限制的长度比下标1
n++;
//全是空格情况
if(n < index)
return false;
//判断前面的字符是否是有符号的整数
bool flag = integer(str);
//如果有小数点
if(index < n && str[index] == '.'){
index++;
//小数点前后有无数字可选
flag = unsigned_integer(str) || flag;
}
//如果有e
if(index < n && (str[index] == 'e' || str[index] == 'E')){
index++;
//e后面必须全是整数
flag = flag && integer(str);
}
//是否字符串遍历结束
return flag && (index == n);
}
};
Python实现代码:
class Solution:
def __init__(self):
#遍历字符串的下标
self.index = 0
#有符号判断
def integer(self, s: str) -> bool:
if self.index < len(s) and (s[self.index] == '-' or s[self.index] == '+'):
self.index += 1
return self.unsigned_integer(s)
#无符号数判断
def unsigned_integer(self, s: str) -> bool:
temp = self.index
while self.index < len(s) and (s[self.index] >= '0' and s[self.index] <= '9'):
self.index += 1
return self.index > temp
def isNumeric(self , str: str) -> bool:
#先判断空串
if len(str) == 0:
return False
#去除前面的空格
while self.index < len(str) and str[self.index] == ' ':
self.index += 1
n = len(str) - 1
#去除字符串后面的空格
while n >= 0 and str[n] == ' ':
n -= 1
#限制的长度比下标1
n += 1
#全是空格情况
if n < self.index:
return False
#判断前面的字符是否是有符号的整数
flag = self.integer(str)
#如果有小数点
if self.index < n and str[self.index] == '.':
self.index += 1
#小数点前后有无数字可选
flag = self.unsigned_integer(str) or flag
#如果有e
if self.index < n and (str[self.index] == 'e' or str[self.index] == 'E'):
self.index += 1
#e后面必须全是整数
flag = flag and self.integer(str)
#是否字符串遍历结束
return flag and (self.index == n)
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n为字符串长度,最坏遍历字符串每个字符一次
- 空间复杂度: O ( 1 ) O(1) O(1),常数级变量,无额外辅助空间
方法二:正则表达式(扩展思路)
思路:
可以使用正则表示匹配直接匹配字符串是否表示数字。其中C++和Java中使用双斜杠,Python使用单斜杠。
具体做法:
- step 1:用\s表示匹配空格,*表示匹配0个或多个,写在正则表达式首尾。
- step 2:正负号可选,因此使用?匹配正负号。
- step 3:整数用\d匹配,小数点前有无数字可以用|表示:小数点前有数字,匹配小数点,后面的数字可有可无;小数前没有数字,匹配小数点后必须有数字。
- step 4:匹配大小写字母[Ee],以及后面可有可无的正负号,后面必须匹配一个整数。
Java实现代码:
import java.util.regex.Pattern;
public class Solution {
public boolean isNumeric (String str) {
//正则表达式匹配
String pattern = "(\\s)*[+-]?((\\d+(\\.(\\d+)?)?)|(\\.\\d+))([Ee][+-]?\\d+)?(\\s)*";
//根据匹配值返回
return Pattern.matches(pattern, str);
}
}
C++实现代码:
#include<regex>
class Solution {
public:
bool isNumeric(string str) {
//正则表达式匹配
string pattern = "(\\s)*[+-]?((\\d+(\\.(\\d+)?)?)|(\\.\\d+))([Ee][+-]?\\d+)?(\\s)*";
regex re(pattern);
//根据匹配值返回
return regex_match(str, re);
}
};
Python实现代码:
import re
class Solution:
def isNumeric(self , str: str) -> bool:
#正则表达式匹配
pattern = "^(\s)*[+-]?((\d+(\.(\d+)?)?)|(\.\d+))([Ee][+-]?\d+)?(\s)*$"
#根据匹配值返回
if re.match(pattern, str):
return True
else:
return False
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n为字符串长度,正则表达式最坏遍历每个字符一次
- 空间复杂度: O ( 1 ) O(1) O(1),常数级空间
题目主要信息:
- 题目给定一个 n ∗ m n*m n∗m的矩阵,需要将其螺旋输出
举一反三:
学习完本题的思路你可以解决类似的矩阵遍历的问题。
方法:边界模拟法(推荐使用)
思路:
这道题就是一个简单的模拟,我们想象有一个矩阵,从第一个元素开始,往右到底后再往下到底后再往左到底后再往上,结束这一圈,进入下一圈螺旋。
具体做法:
- step 1:首先排除特殊情况,即矩阵为空的情况。
- step 2:设置矩阵的四个边界值,开始准备螺旋遍历矩阵,遍历的截止点是左右边界或者上下边界重合。
- step 3:首先对最上面一排从左到右进行遍历输出,到达最右边后第一排就输出完了,上边界相应就往下一行,要判断上下边界是否相遇相交。
- step 4:然后输出到了右边,正好就对最右边一列从上到下输出,到底后最右边一列已经输出完了,右边界就相应往左一列,要判断左右边界是否相遇相交。
- step 5:然后对最下面一排从右到左进行遍历输出,到达最左边后最下一排就输出完了,下边界相应就往上一行,要判断上下边界是否相遇相交。
- step 6:然后输出到了左边,正好就对最左边一列从下到上输出,到顶后最左边一列已经输出完了,左边界就相应往右一列,要判断左右边界是否相遇相交。
- step 7:重复上述3-6步骤直到循环结束。
图示:
Java代码实现:
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> spiralOrder(int[][] matrix) {
ArrayList<Integer> res = new ArrayList<>();
//先排除特殊情况
if(matrix.length == 0) {
return res;
}
//左边界
int left = 0;
//右边界
int right = matrix[0].length - 1;
//上边界
int up = 0;
//下边界
int down = matrix.length - 1;
//直到边界重合
while(left <= right && up <= down){
//上边界的从左到右
for(int i = left; i <= right; i++)
res.add(matrix[up][i]);
//上边界向下
up++;
if(up > down)
break;
//右边界的从上到下
for(int i = up; i <= down; i++)
res.add(matrix[i][right]);
//右边界向左
right--;
if(left > right)
break;
//下边界的从右到左
for(int i = right; i >= left; i--)
res.add(matrix[down][i]);
//下边界向上
down--;
if(up > down)
break;
//左边界的从下到上
for(int i = down; i >= up; i--)
res.add(matrix[i][left]);
//左边界向右
left++;
if(left > right)
break;
}
return res;
}
}
C++代码实现
class Solution {
public:
vector<int> spiralOrder(vector<vector<int> > &matrix) {
vector<int> res;
int n = matrix.size();
//先排除特殊情况
if(n == 0)
return res;
//左边界
int left = 0;
//右边界
int right = matrix[0].size() - 1;
//上边界
int up = 0;
//下边界
int down = n - 1;
//直到边界重合
while(left <= right && up <= down){
//上边界的从左到右
for(int i = left; i <= right; i++)
res.push_back(matrix[up][i]);
//上边界向下
up++;
if(up > down)
break;
//右边界的从上到下
for(int i = up; i <= down; i++)
res.push_back(matrix[i][right]);
//右边界向左
right--;
if(left > right)
break;
//下边界的从右到左
for(int i = right; i >= left; i--)
res.push_back(matrix[down][i]);
//下边界向上
down--;
if(up > down)
break;
//左边界的从下到上
for(int i = down; i >= up; i--)
res.push_back(matrix[i][left]);
//左边界向右
left++;
if(left > right)
break;
}
return res;
}
};
Python实现代码:
class Solution:
def spiralOrder(self , matrix: List[List[int]]) -> List[int]:
res = list()
n = len(matrix)
#先排除特殊情况
if n == 0:
return res
#左边界
left = 0
#右边界
right = len(matrix[0]) - 1
#上边界
up = 0
#下边界
down = n - 1
#直到边界重合
while left <= right and up <= down:
#上边界的从左到右
for i in range(left, right+1):
res.append(matrix[up][i])
#上边界向下
up += 1
if up > down:
break
#右边界的从上到下
for i in range(up,down+1):
res.append(matrix[i][right])
#右边界向左
right -= 1
if left > right:
break
i = right
#下边界的从右到左
while i >= left:
res.append(matrix[down][i])
i -= 1
#下边界向上
down -= 1
if up > down:
break
i = down
#左边界的从下到上
while i >= up:
res.append(matrix[i][left])
i -= 1
#左边界向右
left += 1
if left > right:
break
return res
复杂度分析:
- 时间复杂度: O ( m n ) O(mn) O(mn),相当于遍历整个矩阵
- 空间复杂度: O ( 1 ) O(1) O(1),res属于必要空间,没有使用额外辅助空间