用栈访问最后若干元素
682.棒球比赛(简单)
现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。
比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则:
整数 x - 表示本回合新获得分数 x
"+" - 表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。
"D" - 表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。
"C" - 表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。
请你返回记录中所有得分的总和。
输入:ops = ["5","2","C","D","+"]
输出:30
解释:
"5" - 记录加 5 ,记录现在是 [5]
"2" - 记录加 2 ,记录现在是 [5, 2]
"C" - 使前一次得分的记录无效并将其移除,记录现在是 [5].
"D" - 记录加 2 * 5 = 10 ,记录现在是 [5, 10].
"+" - 记录加 5 + 10 = 15 ,记录现在是 [5, 10, 15].
所有得分的总和 5 + 10 + 15 = 30
使用链表模拟栈
class Solution {
public int calPoints(String[] operations) {
List<Integer> list = new ArrayList<>();
int index = 0;
for(String ops : operations){
if("C".equals(ops)){
index--;
}else if("D".equals(ops)){
list.add(index, list.get(index-1) * 2);
index++;
}else if("+".equals(ops)){
list.add(index, list.get(index-1) + list.get(index-2));
index++;
}else{
list.add(index, Integer.parseInt(ops));
index++;
}
}
int sum = 0;
for(int i = 0; i < index; i++){
sum += list.get(i);
}
return sum;
}
}
使用栈
class Solution {
public int calPoints(String[] operations) {
Stack<Integer> stack = new Stack<>();
int sum = 0;
for(String str : operations){
switch(str){
case "+":
int a = stack.pop();
int b = stack.peek();
int temp = a + b;
sum += temp;
stack.push(a);
stack.push(temp);
break;
case "C":
int last = stack.pop();
sum -= last;
break;
case "D":
int newnum = stack.peek() * 2;
stack.push(newnum);
sum += newnum;
break;
default:
sum += stack.push(Integer.parseInt(str));
break;
}
}
return sum;
}
}
71.简化路径(中等)★
给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/' 开头),请你将其转化为更加简洁的规范路径。
在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,'//')都被视为单个斜杠 '/' 。 对于此问题,任何其他格式的点(例如,'...')均被视为文件/目录名称。
请注意,返回的 规范路径 必须遵循下述格式:
始终以斜杠 '/' 开头。
两个目录名之间必须只有一个斜杠 '/' 。
最后一个目录名(如果存在)不能 以 '/' 结尾。
此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 '.' 或 '..')。
输入: path = "/a/./b/../../c/"
输出: "/c"
class Solution {
public String simplifyPath(String path) {
//使用split时,分割符在头尾会产生空格
String[] str = path.split("/");
//使用双端队列比较方便
Deque<String> deque = new ArrayDeque<>();
for(String s : str){
if("..".equals(s)){
if(!deque.isEmpty()){
deque.pollLast();
}
} else if(s.length() > 0 && !".".equals(s)){ //空格或.不需要处理
deque.offerLast(s);
}
}
StringBuffer sb = new StringBuffer();
if(deque.isEmpty()){
sb.append("/");
}else{
while(!deque.isEmpty()){
String s = deque.pollFirst();
sb.append("/" + s);
}
}
return sb.toString();
}
}
388.文件的最长绝对路径(中等)★
/n为换行符,分割两个文件夹,/t为制表符,表示当前元素在文件系统树中的深度
class Solution {
//首先,文件系统为一棵树,\t表示当前文件夹或文件的深度
//我们将遍历到的文件夹和文件加入到栈中
//若当前文件或文件夹的深度小于栈顶的元素,则栈必须回退至符合条件
//由于只求长度,故在栈中需要记录文件的长度即可,然后不断更新最大值
//注意含有.的才是文件
public int lengthLongestPath(String input) {
int len = input.length();
int index = 0;
int ans = 0;
Deque<Integer> deque = new ArrayDeque<>();
while(index < len){
//先求出当前元素的深度
int depth = 1;
while(index < len && input.charAt(index) == '\t'){
depth++;
index++;
}
//记录当前元素长度,判断是否为文件
boolean isFile = false;
int length = 0;
while(index < len && input.charAt(index) != '\n'){
if(input.charAt(index) == '.'){
isFile = true;
}
index++;
length++;
}
index++; //跳过当前换行符
while(deque.size() >= depth){ //若当前栈的深度大于等于当前元素的深度,则必须出栈直到小于
deque.pop();
}
if(!deque.isEmpty()){
length += deque.peek()+1; //当前栈顶元素的长度加文件本身长度加1
}
if(isFile){
ans = Math.max(ans, length); //是文件时比较大小
}else{
deque.push(length);
}
}
return ans;
}
}
class Solution {
//使用hashMap存储最新的文件夹路径,等遍历到文件时长度为map[n-1]+len
static int[] hashMap = new int[10010];
public int lengthLongestPath(String input) {
Arrays.fill(hashMap, -1);
int ans = 0;
int n = input.length();
for(int i = 0; i < n; ){ //相当于while循环
int depth = 0;
//计算当前文件/文件夹深度
while(i < n && input.charAt(i) == '\t'){
depth++;
i++;
}
//计算当前元素长度
int len = 0;
boolean isDir = true;
while(i < n && input.charAt(i) != '\n'){
if(input.charAt(i) == '.'){
isDir = false;
}
i++;
len++;
}
int prev = depth - 1 >= 0 ? hashMap[depth - 1] : -1; //当前文件夹或文件上一级文件夹的长度
int path = prev + len + 1; //当前文件夹或文件长度
if(isDir){
hashMap[depth] = path; //将hashmap的值修改为当前的路径长度
}else if(path > ans){
ans = path;
}
i++;
}
return ans;
}
}
150.逆波兰表达式求值(中等)
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
class Solution {
//逆波兰表达式即后缀表达式,使用一个数字栈,遇到数字时进栈,遇到运算符时出栈两个元素,最后剩下的元素即为结果,使用数组模拟栈即可
public int evalRPN(String[] tokens) {
int[] stack = new int[tokens.length/2 + 1];//表达式中运算数的数量为运算符数量加一
int index = 0;
for(String s : tokens){
switch(s){
case "+":
stack[index - 2] += stack[index - 1];
index--;
break;
case "-":
stack[index - 2] -= stack[index - 1];
index--;
break;
case "*":
stack[index - 2] *= stack[index - 1];
index--;
break;
case "/":
stack[index - 2] /= stack[index - 1];
index--;
break;
default:
stack[index] = Integer.parseInt(s);
index++;
break;
}
}
return stack[0];
}
}
227.基本计算器II(中等)★
给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。 整数除法仅保留整数部分。 你可以假设给定的表达式总是有效的。所有中间结果将在 [-2^31, 2^31 - 1] 的范围内。 注意操作符前后有空格
输入:s = "3+2*2"
输出:7
//由于乘除法的优先级高于加减法,故先对乘除法进行运算,遍历字符串,遇到数字时求出完整的数字再入栈
//遇到加法时后续数字直接入栈,遇到减法时,后续数字取相反数后入栈
//遇到乘除法时后续数字与当前栈顶元素进行操作,结果入栈,最后将栈内数字全部相加即可
class Solution {
public int calculate(String s) {
Deque<Integer> deque = new ArrayDeque<>();
char perSign = '+'; //默认符号为加号
int len = s.length();
int num = 0;
for(int i = 0; i < len; i++){
if(Character.isDigit(s.charAt(i))){
num = num * 10 + s.charAt(i) - '0';
}
if(!Character.isDigit(s.charAt(i)) && s.charAt(i) != ' ' || i == len-1){ //注意当i=len-1,当前为数字,需要将前一个存储起来的运算符用掉
switch(perSign){
case '+':
deque.push(num);
break;
case '-':
deque.push(-num);
break;
case '*':
deque.push(num * deque.pop());
break;
default:
deque.push(deque.pop() / num);
break;
}
perSign = s.charAt(i);
num = 0;
}
}
int ans = 0;
while(!deque.isEmpty()){
ans += deque.pop();
}
return ans;
}
}
使用双栈实现一个完全计算器
class Solution {
//预处理运算符优先级
Map<Character, Integer> map = new HashMap<>(){{
put('-', 1);
put('+', 1);
put('*', 2);
put('/', 2);
put('%', 2);
put('^', 3);
}};
public int calculate(String s) {
s = s.replaceAll(" ", ""); // 去除所有空格
char[] cs = s.toCharArray();
int n = s.length();
Deque<Integer> nums = new ArrayDeque<>(); //数字栈
nums.addLast(0); //防止第一个数为负数,先往nums中添加0
Deque<Character> ops = new ArrayDeque<>(); //操作符栈
for (int i = 0; i < n; i++) {
char c = cs[i];
if (c == '(') {
ops.addLast(c);
} else if (c == ')') {
// 计算到最近一个左括号为止
while (!ops.isEmpty()) {
if (ops.peekLast() != '(') {
calc(nums, ops);
} else {
ops.pollLast();
break;
}
}
} else {
if (isNumber(c)) {
int u = 0;
int j = i;
// 将从 i 位置开始后面的连续数字整体取出,加入 nums
while (j < n && isNumber(cs[j])) u = u * 10 + (cs[j++] - '0');
nums.addLast(u);
i = j - 1; //i指向数字的最后一位,方便继续遍历
} else {
if (i>0 && (cs[i - 1] == '(' || cs[i - 1] == '+' || cs[i - 1] == '-')) {
nums.addLast(0); //上个位置为左括号或加号或减号时,加入0避免边界条件判断
}
while (!ops.isEmpty() && ops.peekLast() != '(') {
char prev = ops.peekLast();
if (map.get(prev)>=map.get(c)) {//栈内优先级大于等于当前运算符的都计算
calc(nums, ops);
} else {
break;
}
}
ops.addLast(c); //将当前运算符入栈
}
}
}
// 将剩余的计算完
while (!ops.isEmpty()) calc(nums, ops);
return nums.peekLast();
}
void calc(Deque<Integer> nums, Deque<Character> ops) {
if (nums.isEmpty() || nums.size() < 2) return;
if (ops.isEmpty()) return;
int b = nums.pollLast(), a = nums.pollLast();
char op = ops.pollLast();
int ans = 0;
if (op == '+') ans = a + b;
else if (op == '-') ans = a - b;
else if (op == '*') ans = a * b;
else if (op == '/') ans = a / b;
else if (op == '^') ans = (int)Math.pow(a, b);
else if (op == '%') ans = a % b;
nums.addLast(ans);
}
boolean isNumber(char c) {
return Character.isDigit(c);
}
}
224.基本计算器(困难)
解法同上
20.有效的括号(简单)
给定一个括号序列字符串,要求判断括号是否全部能够匹配。
输入: s = "()[]{}"
输出: true
//使用栈
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new ArrayDeque<>();
char[] ch = s.toCharArray();
for(char c : ch){
if(c =='(' || c == '[' || c == '{'){
deque.addLast(c);
}else{
if(!deque.isEmpty()){
if(c == ')'){
if(deque.pollLast() != '(') return false;
}else if(c == ']'){
if(deque.pollLast() != '[') return false;
}else{
if(deque.pollLast() != '{') return false;
}
}else{
return false;
}
}
}
return deque.isEmpty();
}
}
636.函数的独占时间(中等)
有一个 单线程 CPU 正在运行一个含有 n 道函数的程序。每道函数都有一个位于 0 和 n-1 之间的唯一标识符。
函数调用 存储在一个 调用栈 上 :当一个函数调用开始时,它的标识符将会推入栈中。而当一个函数调用结束时,它的标识符将会从栈中弹出。标识符位于栈顶的函数是 当前正在执行的函数 。每当一个函数开始或者结束时,将会记录一条日志,包括函数标识符、是开始还是结束、以及相应的时间戳。
给你一个由日志组成的列表 logs ,其中 logs[i] 表示第 i 条日志消息,该消息是一个按 "{function_id}:{"start" | "end"}:{timestamp}" 进行格式化的字符串。例如,"0:start:3" 意味着标识符为 0 的函数调用在时间戳 3 的 起始开始执行 ;而 "1:end:2" 意味着标识符为 1 的函数调用在时间戳 2 的 末尾结束执行。注意,函数可以 调用多次,可能存在递归调用 。
函数的 独占时间 定义是在这个函数在程序所有函数调用中执行时间的总和,调用其他函数花费的时间不算该函数的独占时间。例如,如果一个函数被调用两次,一次调用执行 2 单位时间,另一次调用执行 1 单位时间,那么该函数的 独占时间 为 2 + 1 = 3 。
以数组形式返回每个函数的 独占时间 ,其中第 i 个下标对应的值表示标识符 i 的函数的独占时间。
输入:n = 2, logs = ["0:start:0","0:start:2","0:end:5","1:start:6","1:end:6","0:end:7"]
输出:[7,1]
class Solution {
//栈中存储标志位start的项,存储其编号和时间戳,表示当前在运行或等待运行的函数
//注意栈顶元素的时间戳需要随着标志为end的项的到来而变化,表示当前的开始时间
//当有新函数入栈时,要计算前一个函数运行了多长时间并记录到数组中
public int[] exclusiveTime(int n, List<String> logs) {
Deque<int[]> deque = new ArrayDeque<>(); //存储每个函数的编号和时间戳
int[] ans = new int[n];
for(String log : logs){
int index = Integer.parseInt(log.substring(0, log.indexOf(":")));
String type = log.substring(log.indexOf(":")+1, log.lastIndexOf(":"));
int t = Integer.parseInt(log.substring(log.lastIndexOf(":") + 1));
if("start".equals(type)){
if(!deque.isEmpty()){
ans[deque.peek()[0]] += (t - deque.peek()[1]);
deque.peek()[1] = t;
}
deque.push(new int[]{index, t});
}else{
ans[index] += (t - deque.pop()[1] + 1);
if(!deque.isEmpty()){
deque.peek()[1] = t + 1;
}
}
}
return ans;
}
}
32.最长有效括号(困难)★
给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
输入: s = ")()())"
输出: 4
解释: 最长有效括号子串是 "()()"
方法1.动态规划
class Solution {
//使用动态规划,dp[i]表示以s.charAt(i)结尾的字符的有效括号长度
//若当前字符为'(',则以其结尾的字符串肯定不是有效括号,故为长度为0
//若当前字符为')',分两种情况
//1.前一个字符为'(',即"...()",则dp[i] = dp[i-2]+2
//2.前一个字符为')',即"...))",前一个')'能组成的有效括号长度为dp[i-1]
//若最后一个')'要有效,必须在s[i-dp[i-1]-1]的位置为'(',即"(...))",再加上'('前面的有效长度
//dp[i] = dp[i-dp[i-1]-2] + dp[i-1] + 2
public int longestValidParentheses(String s) {
int ans = 0;
int n = s.length();
int[] dp = new int[n];
for(int i = 1; i < n; i++){
if(s.charAt(i) == ')'){
if(s.charAt(i-1) == '('){
dp[i] = (i >= 2 ? dp[i-2] : 0) + 2;
}else if(i-dp[i-1] > 0 && s.charAt(i-dp[i-1]-1) == '('){
dp[i] = (i-dp[i-1]-2 >= 0 ? dp[i-dp[i-1]-2] : 0) + dp[i-1] + 2;
}
ans = Math.max(ans, dp[i]);
}
}
return ans;
}
}
方法2.栈
class Solution {
//使用栈,栈中存储的是每个左括号的下标,当遍历到右括号时,弹出当前栈顶左括号
//并用当前右括号下标减去当前栈顶左括号下标计算出有效括号长度
//需要注意的是,我们需要在栈底存储一个最后未被匹配的右括号的下标
//当我们遇到下一个右括号且栈中没有左括号与其匹配(即栈底的右括号成为栈顶)
//则本段匹配结束,当前右括号成为新的栈底
//为了防止第一个括号为左括号直接入栈的情况,我们预先在栈中加入元素-1
public int longestValidParentheses(String s) {
Deque<Integer> deque = new ArrayDeque<>();
deque.push(-1);
char[] ch = s.toCharArray();
int ans = 0;
for(int i = 0; i < ch.length; i++){
if(ch[i] == '('){
deque.push(i);
}else{
deque.pop();
if(deque.isEmpty()){
deque.push(i);
}else{
ans = Math.max(ans, i - deque.peek());
}
}
}
return ans;
}
}
左右双指针法
//从左到右计算左右括号的数量,当左右括号数量相等时,计算得出长度
//当右括号的数量大于左括号的数量时,左右括号数量都归零
//但这种计算无法应对"((()"的情况,故需要从右到左再遍历一遍,条件相反
class Solution {
public int longestValidParentheses(String s) {
int left = 0, right = 0, maxlength = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * right);
} else if (right > left) {
left = right = 0;
}
}
left = right = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * left);
} else if (left > right) {
left = right = 0;
}
}
return maxlength;
}
}
385.迷你语法分析器(中等)
给定一个字符串 s 表示一个整数嵌套列表,实现一个解析它的语法分析器并返回解析的结果 NestedInteger 。
列表中的每个元素只可能是整数或整数嵌套列表
输入: s = "324"
输出: 324
输入: s = "[123,[456,[789]]]"
输出: [123,[456,[789]]]
使用递归,深度优先搜索
class Solution {
int i = 0; //设置全局索引,保证按序处理s
public NestedInteger deserialize(String s) {
if(s.charAt(i) == '['){ //若遇到[,开启一个新的列表
i++;
NestedInteger ni = new NestedInteger();
while(s.charAt(i) != ']'){ //当前列表未结束,递归调用deserialize解析
ni.add(deserialize(s));
if(s.charAt(i) == ',') i++;
}
i++; //继续为下一次递归做准备,或者最后一次递归直接越界
return ni; //返回当前递归层次的ni
}else{
boolean negative = false;
if(s.charAt(i) == '-'){
i++;
negative = true;
}
int num = 0;
while(i < s.length() && Character.isDigit(s.charAt(i))){
num = num * 10 + s.charAt(i) - '0';
i++;
}
if(negative) num *= -1;
return new NestedInteger(num); //注意需要返回NestedInteger类型
}
}
}
使用栈模拟递归过程
class Solution {
//使用栈模拟递归
public NestedInteger deserialize(String s) {
if(s.charAt(0) != '[') return new NestedInteger(Integer.parseInt(s));//只有数字的情况
Deque<NestedInteger> deque = new ArrayDeque<>();
int num = 0;
boolean negative = false;
for(int i = 0; i < s.length(); i++){
char c = s.charAt(i);
if(c == '-'){
negative = true;
}else if(c == '['){
deque.push(new NestedInteger()); //入栈一个新的NestedInteger
}else if(Character.isDigit(c)){
num = num * 10 + c - '0';
}else if(c == ',' || c == ']'){
if(Character.isDigit(s.charAt(i-1))){
if(negative) num *= -1;
deque.peek().add(new NestedInteger(num)); //将数字加入栈顶的NestedInteger
}
num = 0;
negative = false;
if(c == ']' && deque.size() > 1){
NestedInteger ni = deque.pop(); //当前NestedInteger结束,将其加入下一个NestedInteger
deque.peek().add(ni);
}
}
}
return deque.pop();
}
}
42.接雨水(困难)★
按行遍历 时间复杂度较高,无法通过特定的测试用例
class Solution {
//按行更新雨水量num
//例如对于第一行,从遇到第一个大于等于1的数字开始,若当前位置数字小于1
//则temp_num+1,遇到下一个大于等于1的数时,说明前面累计的数量有效
//故num+temp_num,temp_num置0,继续累加
public int trap(int[] height) {
int max = getMax(height);
int ans = 0;
for(int i = 1; i <= max; i++){
int temp = 0;
boolean flag = false;
for(int h : height){
if(flag && h < i) temp++;
if(h >= i){
ans += temp;
temp = 0;
flag = true;
}
}
}
return ans;
}
public int getMax(int[] arr){
if(arr.length == 0) return 0;
int max = arr[0];
for(int num : arr){
if(num > max) max = num;
}
return max;
}
}
按列遍历,动态规划维护左右最大值数组 时间复杂度降为O(n)
class Solution {
//按列遍历,对于每一列,找出它左右最大的数,并得他们的最小值
//最小值就是当前水面高度,若当前数比水面高度小,则有水
//在求左右两边的最大值时可以使用动态规划,left_max[i]表示i左边最大的数,不包括i
public int trap(int[] height) {
int ans = 0;
int n = height.length;
int[] left_max = new int[n];
int[] right_max = new int[n];
for(int i = 1; i < n - 1; i++){
left_max[i] = Math.max(height[i-1], left_max[i-1]);
}
for(int i = n-2; i >= 0; i--){
right_max[i] = Math.max(height[i+1], right_max[i+1]);
}
for(int i = 1; i < n-1; i++){
int min = Math.min(left_max[i], right_max[i]);
if(height[i] < min) ans += min - height[i];
}
return ans;
}
}
//对于i,left_max数组只使用了前一个数,故可以将其转化为一个变量,从左到右遍历即可
//因此对于right_max需要从右到左遍历
//双指针遍历法可以将空间复杂度降低为O(1)
341.扁平化嵌套列表迭代器(中等)★
给你一个嵌套的整数列表 nestedList 。每个元素要么是一个整数,要么是一个列表;该列表的元素也可能是整数或者是其他列表。请你实现一个迭代器将其扁平化,使之能够遍历这个列表中的所有整数。
实现扁平迭代器类 NestedIterator :
NestedIterator(List<NestedInteger> nestedList) 用嵌套列表 nestedList 初始化迭代器。
int next() 返回嵌套列表的下一个整数。
boolean hasNext()如果仍然存在待迭代的整数,返回 true;否则返回 false 。
输入:nestedList = [[1,1],2,[1,1]]
输出:[1,1,2,1,1]
解释:通过重复调用 next 直到 hasNext 返回 false,next 返回的元素的顺序应该是: [1,1,2,1,1]。
在构造函数中提前扁平化整个嵌套列表(递归)
public class NestedIterator implements Iterator<Integer> {
private LinkedList<Integer> ans; //用链表作为容器
public NestedIterator(List<NestedInteger> nestedList) {
ans = new LinkedList<>();
DFS(nestedList);
}
@Override
public Integer next() {
return ans.removeFirst();
}
@Override
public boolean hasNext() {
return !ans.isEmpty();
}
public void DFS(List<NestedInteger> nestedList){
for(NestedInteger n : nestedList){
if(n.isInteger()){
ans.addLast(n.getInteger());
}else{
DFS(n.getList());
}
}
}
}
调用next或hasNext时扁平化当前的嵌套子列表(非递归)
public class NestedIterator implements Iterator<Integer> {
//在next和hasNext函数中扁平化对应的子列表,先把所有元素逆序放入栈中
//调用hasNext方法时,访问栈顶元素,若为数字则在next中弹出,若为列表则在hasNext中逆序入栈
private Deque<NestedInteger> deque; //使用队列作为容器,注意这里的泛型是NestedInteger
public NestedIterator(List<NestedInteger> nestedList) {
deque = new ArrayDeque<>();
for(int i = nestedList.size()-1; i >= 0; i--){
deque.addLast(nestedList.get(i)); //先把所有元素逆序入栈
}
}
@Override
public Integer next() {
NestedInteger cur = deque.removeLast(); //弹出栈顶元素
return cur.getInteger();
}
@Override
public boolean hasNext() {
while(!deque.isEmpty()){
NestedInteger top = deque.peekLast(); //访问栈顶元素
if(top.isInteger()){
return true;
}
deque.removeLast(); //栈顶元素时列表
for(int i = top.getList().size()-1; i >= 0; i--){ //对列表元素进行遍历
deque.addLast(top.getList().get(i));
}
}
return false;
}
}
394.字符串解码(中等)★
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
输入: s = "3[a2[c]]"
输出: "accaccacc"
非递归,使用栈
class Solution {
//非递归方式,使用数字栈和字符串栈实现
//遇到左括号时,将当前字符串和数字入栈,并初始化字符串和数字
//遇到右括号时,将当前字符串重复n次(从数字栈中取出),并与栈顶元素拼接
public String decodeString(String s) {
Deque<Integer> deque_num = new ArrayDeque<>();
Deque<String> deque_str = new ArrayDeque<>();
int num = 0;
StringBuilder res = new StringBuilder();
char[] ch = s.toCharArray();
for(char c : ch){
if(Character.isDigit(c)){
num = num * 10 + c - '0';
}else if(c == '['){
deque_num.addLast(num);
deque_str.addLast(res.toString());
num = 0;
res = new StringBuilder();
}else if(c == ']'){
StringBuilder temp = new StringBuilder();
int multi = deque_num.removeLast();
for(int i = multi; i > 0; i--){
temp.append(res);
}
res = new StringBuilder(deque_str.removeLast() + temp); //注意此时需要更新当前字符串
}else{
res.append(c);
}
}
return res.toString();
}
}
递归
class Solution {
public String decodeString(String s) {
return dfs(s, 0)[0]; //调用dfs,结果存储在数组第一个值中
}
private String[] dfs(String s, int i) {
StringBuilder res = new StringBuilder();
int multi = 0;
while(i < s.length()) {
if(Character.isDigit(s.charAt(i))){
multi = multi * 10 + s.charAt(i) - '0';
}else if(s.charAt(i) == '[') { //遇到左括号时更新指针位置
String[] tmp = dfs(s, i + 1); //并递归调用得到当前括号内的值(到右括号时返回)
i = Integer.parseInt(tmp[0]); //更新指针(移动到当前左括号对应的右括号)
while(multi > 0) { //重复字符串
res.append(tmp[1]);
multi--;
}
}else if(s.charAt(i) == ']') { //遇到右括号,返回指针位置和当前括号内累积的字符串
return new String[] { String.valueOf(i), res.toString() };
}else{
res.append(String.valueOf(s.charAt(i))); //遇到其他字符直接加入当前累积字符串
}
i++;
}
return new String[] { res.toString() }; //将结果放在字符串数组首位返回
}
}