程序 = 数据结构 + 算法
力扣解题思路2
148.排序链表
- 要求复杂度为nlogn则想到归并排序。
- 通过快慢指针找到链表中点进行拆分,直到拆分到长度为1。
- 然后不断合并。
public ListNode sortList(ListNode head) {
return split(head,null);
}
public ListNode split(ListNode head,ListNode tail){
if(head==tail||head.next==tail) return head;
ListNode slow = head;
ListNode fast = head.next;
while(fast!=tail&&fast.next!=tail){
fast = fast.next.next;
slow = slow.next;
}
ListNode mid = slow.next;
slow.next = null;
ListNode left = split(head,null);
ListNode right = split(mid,tail);
return merge(left,right);
}
public ListNode merge(ListNode left,ListNode right){
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
while(left!=null&&right!=null){
if(left.val<right.val){
p.next = new ListNode(left.val);
left = left.next;
}else{
p.next = new ListNode(right.val);
right = right.next;
}
p = p.next;
}
p.next = left==null?right:left;
return dummy.next;
}
151.翻转字符串里的单词
- 模拟
public String reverseWords(String s) {
StringBuilder sb = new StringBuilder();
char[] cs = s.toCharArray();
int n=s.length();
boolean flag = true;
int j = n-1,i=j;
while(j>=0){
if(cs[j]==' '){
j--;
}else{
i = j-1;
while(i>=0&&cs[i]!=' '){
i--;
}
if(flag){
flag = false;
}else{
sb.append(" ");
}
sb.append(s.substring(i+1,j+1));
j = i;
}
}
return sb.toString();
}
152.乘积最大子数组
- 动态规划
- 定义两个数组,max[i]记录算上当前位置数组中的最大乘积,min[i]记录最小乘积。
public int maxProduct(int[] nums) {
int n = nums.length;
int max[] = new int[n];
int min[] = new int[n];
int res = nums[0];
max[0] = nums[0];
min[0] = nums[0];
for(int i=1;i<n;i++){
max[i] = Math.max(nums[i],Math.max(nums[i]*max[i-1],nums[i]*min[i-1]));
min[i] = Math.min(nums[i],Math.min(nums[i]*max[i-1],nums[i]*min[i-1]));
res = Math.max(res,max[i]);
}
return res;
}
155.最小栈
- 投机方法(两个栈是同步的):用两个栈,一个普通栈,一个最小栈。
Deque<Integer> stack;
Deque<Integer> min;
public MinStack() {
stack = new ArrayDeque<>();
min = new ArrayDeque<>();
}
public void push(int val) {
stack.push(val);
if(min.isEmpty()){
min.push(val);
}else{
min.push(Math.min(val,min.peek()));
}
}
public void pop() {
stack.pop();
min.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return min.peek();
}
- 单调栈方法(两个栈不同步):辅助栈只在加入的数比栈顶小入栈,普通栈出栈时如果等于单调栈,则同时出栈单调栈。
public void push(int val) {
stack.push(val);
int minPeek = min.isEmpty()?Integer.MAX_VALUE:min.peek();
if(val<=minPeek){
min.push(val);
}
}
public void pop() {
int val = stack.pop();
if(min.peek()==val){
min.pop();
}
}
162.寻找峰值
- 因为复杂度要求logn,所以要用二分查找。
- nums[m]<nums[m+1]时m+1右侧必定包含峰值。
- nums[m】>nums[m+1]时,m左侧必定包含峰值。
public int findPeakElement(int[] nums) {
int l = 0,r = nums.length-1;
while(l<r){
int m = (l+r)/2;
if(nums[m]<nums[m+1]){
l = m+1;
}else{
r = m;
}
}
return l;
}
165.比较版本号
169.多数元素
-
- 排序后去中值,复杂度为nlogn不符合。
-
- 定义一个变量res和个数t,遍历所有元素x,如果x与res相等,则t++,如果x与res不想等,则t--。如果t减少为0,则替换x。最后留下的x就是出现次数大于n/2的元素。复杂度为n
public int majorityElement(int[] nums) {
int res = nums[0],t = 1;
for(int i=1;i<nums.length;i++){
if(nums[i]==res){
t++;
}else{
t--;
}
if(t==0) {
res = nums[i];
t = 1;
}
}
return res;
}
198.打家劫舍
- 动态规划
- dp[i]定义为当前能偷窃到的最高金额。
- 不偷为dp[i-1]
- 偷为dp[i-2]+nums[i]
- 取上面两者最大值
public int rob(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
dp[0] = nums[0];
for(int i=1;i<n;i++){
int x1 = dp[i-1];
int x2 = (i-2>=0?dp[i-2]:0)+nums[i];
dp[i] = Math.max(x1,x2);
}
return dp[n-1];
}
- 由于dp[i]只与i-1和i-2有关,可以用三个变量来代替dp数组。
public int rob(int[] nums) {
int n = nums.length;
if(n==1) return nums[0];
int a = nums[0];
int b = Math.max(nums[0],nums[1]);
for(int i=2;i<n;i++){
int c = Math.max(a+nums[i],b);
a = b;
b = c;
}
return b;
}
207.课程表
- 拓扑
- 广度优先算法:需要计算每个节点的入度,当入度为0就插入队列,然后队列出队后在重新更新相关节点的入度
- 用List<List<>>存储边,用数组存储入度个数。
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<List<Integer>> edges = new ArrayList<>();
int[] in = new int[numCourses];
int res = 0;
for(int i=0;i<numCourses;i++){
edges.add(new ArrayList<>());
}
//计算每个节点的入度,并构建有向图
for(int[] info:prerequisites){
edges.get(info[1]).add(info[0]);
in[info[0]]++;
}
Queue<Integer> queue = new LinkedList<>();
for(int i=0;i<numCourses;i++){
if(in[i]==0){
queue.add(i);
}
}
while(!queue.isEmpty()){
int x = queue.poll();
res ++ ;
for(int edge:edges.get(x)){
in[edge]--;
if(in[edge]==0){
queue.add(edge);
}
}
}
return res==numCourses;
}
- 深度优先算法:需要记录每个节点的状态,0为未搜索,1为正在搜索,2为已搜索。
- 如果在dfs的过程中遇到正在搜索则说明存在有环。
- 由于深度优先算法的特性,他并不需要记录每个节点的入度,会自动找到没有出度的叶子结点,找到叶子结点定义为已搜索,然后不断回退继续深度优先搜索。直到遇到环或者搜索结束。
List<List<Integer>> edges;
boolean hasLoop ;
int[] state;
public boolean canFinish(int numCourses, int[][] prerequisites) {
edges = new ArrayList<>();
for(int i=0;i<numCourses;i++){
edges.add(new ArrayList<>());
}
hasLoop = false;
state = new int[numCourses];
for(int[] info:prerequisites){
edges.get(info[1]).add(info[0]);
}
for(int i=0;i<numCourses&&!hasLoop;i++){
if(state[i]==0){
dfs(i);
}
}
return !hasLoop;
}
public void dfs(int i){
state[i] = 1;
for(int t:edges.get(i)){
if(state[t]==0){
dfs(t);
}else if (state[t]==1){
hasLoop = true;
return;
}
}
state[i] = 2;
}
208.实现Trie(前缀树)
- 由于是树形结构,每个节点Trie应该持有一组子节点Trie[],并且判断当前节点是否为叶子结点的isLeaf。
- 对于初始化,每个节点的子节点应该对应26个字母。
- 对于插入,即遍历字符串的每一位,从树的根节点开始不断向下新建节点。
- 根节点不存储数据,从子节点开始插入和检索。
- 注意只有小写字母,c-'a'而不是c-'0'
class Trie {
Trie[] child;
boolean isLeaf;
public Trie() {
child = new Trie[26];
isLeaf = false;
}
public void insert(String word) {
Trie node = this;
for(int i=0;i<word.length();i++){
char c = word.charAt(i);
int index = c-'a';
if(node.child[index]==null){
node.child[index] = new Trie();
}
node = node.child[index];
}
node.isLeaf = true;
}
public boolean search(String word) {
Trie node = this;
for(int i=0;i<word.length();i++){
char c = word.charAt(i);
int index = c-'a';
if(node.child[index]==null) return false;
node = node.child[index];
}
return node.isLeaf;
}
public boolean startsWith(String prefix) {
Trie node = this;
for(int i=0;i<prefix.length();i++){
char c = prefix.charAt(i);
int index = c-'a';
if(node.child[index]==null) return false;
node = node.child[index];
}
return true;
}
}
209.长度最小的子数组
- 滑动窗口
- 定义窗口为左闭右开。
- i和j都设置为0
public int minSubArrayLen(int target, int[] nums) {
int n = nums.length;
if(n==0) return 0;
int i=0,j=0;
int sum = 0;
int res = Integer.MAX_VALUE;
while(j<n){
sum+=nums[j];
while(sum>=target){
res = Math.min(res,j-i+1);
sum-=nums[i];
i++;
}
j++;
}
return res==Integer.MAX_VALUE?0:res;
}
210.课程表2
- 相比较1增加输出数组 public int[] findOrder(int numCourses, int[][] prerequisites) { //广度优先遍历 List<List> edges = new ArrayList<>(); int in[] = new int[numCourses]; int res[] = new int[numCourses]; Queue queue = new LinkedList<>(); int size = 0; for(int i=0;i<numCourses;i++){ edges.add(new ArrayList<>()); } for(int[] info:prerequisites){ edges.get(info[1]).add(info[0]); in[info[0]]++; } for(int i=0;i<numCourses;i++){ if(in[i]==0){ queue.add(i); } } while(!queue.isEmpty()){ Integer x = queue.poll(); res[size++] = x; for(int edge:edges.get(x)){ in[edge]--; if(in[edge]==0){ queue.add(edge); } } } if(size==numCourses) return res; return new int[0]; }
数组中的第k个最大元素
- 快排
- 用随机函数选定下标p,p的左边全是大于nums[p],p的右边全是小于nums[p]
- 通过p和k的比较进行左右两边的选择,也就是剪纸
public int findKthLargest(int[] nums, int k) {
return quickSort(nums,0,nums.length-1,k-1);
}
public int quickSort(int[] nums,int l,int r,int k){
int p = partation(nums,l,r);
if(p==k) {
return nums[p];
}
return p<k?quickSort(nums,p+1,r,k):quickSort(nums,l,p-1,k);
}
public int partation(int[] nums,int l,int r){
int rand = new Random().nextInt(r-l+1)+l;
swap(nums,rand,l);
int k = nums[l];
while(l<r){
while(l<r&&nums[r]<=k) r--;
if(l<r){
nums[l] = nums[r];
l++;
}
while(l<r&&nums[l]>=k) l++;
if(l<r){
nums[r] = nums[l];
r--;
}
}
nums[l] = k;
return l;
}
public void swap(int[] nums,int i,int j){
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
221.最大正方形
- dp算法
- 上,左,左上的最小值+1
public int maximalSquare(char[][] matrix) {
int n = matrix.length;
int m = matrix[0].length;
int[][] dp = new int[n][m];
int res = 0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
int c = matrix[i][j] - '0';
if(i==0||j==0){
dp[i][j] = c;
}else if(c==0){
dp[i][j] = 0;
}else{
dp[i][j] = Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]))+1;
}
res = Math.max(res,dp[i][j]);
}
}
return res*res;
}
224.基本计算器
- 一个栈存储数字nums,一个栈存储运算符ops
- 定义一个计算函数,取nums两位,取ops一位,根据ops来进行计算,计算结果再插入nums中
- 将s中的" "全部置换成""
- 循环遍历s,
-
- 如果为'('则入ops栈。
-
- 如果为')'则不断计算,直到遇到(将其弹出。
-
- 如果为数字,则计算当前数字之后的所有连续数字,组成一个整数入nums栈。
-
- 如果为运算符,则先计算能计算的(不断计算直到遇到优先级比当前运算符还要低的运算符),再入ops栈。
- 运算符的优先级存储在map中
public int calculate(String s) {
Map<Character,Integer> map = new HashMap<>(){{
put('+',1);
put('-',1);
put('*',2);
put('/',2);
put('%',2);
put('^',3);
}};
Deque<Integer> nums = new ArrayDeque<>();
Deque<Character> ops = new ArrayDeque<>();
s = s.replaceAll(" ","");
char[] cs = s.toCharArray();
nums.addLast(0);
for(int i=0;i<cs.length;i++){
if(cs[i]=='('){
ops.addLast(cs[i]);
}else if(cs[i]==')'){
while(!ops.isEmpty()){
Character c = ops.peekLast();
if(c=='('){
ops.pollLast();
break;
}else{
cal(nums,ops);
}
}
}else{
if(Character.isDigit(cs[i])){
int u = 0;
int j = i;
while(j<cs.length&&Character.isDigit(cs[j])){
u = u * 10 + cs[j] - '0';
j++;
}
nums.addLast(u);
i = j-1;
}else{
if(i>0&&(cs[i-1]=='(')){
nums.addLast(0);
}
while(!ops.isEmpty()&&ops.peekLast()!='('){
if(map.get(ops.peekLast())>=map.get(cs[i])){
cal(nums,ops);
}else break;
}
ops.addLast(cs[i]);
}
}
}
while(!ops.isEmpty()) cal(nums,ops);
return nums.peekLast();
}
void cal(Deque<Integer> nums,Deque<Character> ops){
if(nums.isEmpty()||nums.size()<2) return ;
if(ops.isEmpty()) return ;
int num1 = nums.pollLast();
int num2 = nums.pollLast();
char c = ops.pollLast();
int tmp = 0;
if(c=='+'){
tmp = num1 + num2;
}else if(c=='-'){
tmp = num2 - num1;
}else if(c=='*'){
tmp = num1 * num2;
}else if(c=='/'){
tmp = num2 / num1;
}else if(c=='^'){
tmp = (int)Math.pow(num2,num1);
}
nums.addLast(tmp);
}
230.二叉搜索树中第k小的元素
- 可能面临的情况:
-
- 无:递归或非递归的中序遍历。
-
- 这个树经常要被访问:将子树的结点个数存储在hash表中(复杂度为H树的高度)或者放到List中(复杂度为1)
-
- 这个树经常要被修改
- 中序搜索(递归方法),找到第k个返回。
int res = -1;
int num = 0;
public int kthSmallest(TreeNode root, int k) {
dfs(root,k);
return res;
}
public void dfs(TreeNode root,int k){
if(root==null) return ;
dfs(root.left,k);
num++;
if(num==k){
res = root.val;
return ;
}
dfs(root.right,k);
}
- 中序遍历(非递归方法)
public int kthSmallest(TreeNode root, int k) {
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode p = root;
while(p!=null||!stack.isEmpty()){
while(p!=null){
stack.addLast(p);
p = p.left;
}
if(!stack.isEmpty()){
p = stack.pollLast();
k--;
if(k==0){
return p.val;
}
p = p.right;
}
}
return 0;
}
234.回文链表
- 快慢指针,慢指针将前半部分倒置。
public boolean isPalindrome(ListNode head) {
if(head==null||head.next==null) return true;
ListNode pre = null;
ListNode slow = head;
ListNode fast = head;
ListNode next = null;
while(fast!=null&&fast.next!=null){
fast = fast.next.next;
next = slow.next;
slow.next = pre;
pre = slow;
slow = next;
}
if(fast!=null) slow = slow.next;
while(pre!=null&&slow!=null){
if(pre.val!=slow.val) return false;
pre = pre.next;
slow = slow.next;
}
return true;
}
236.二叉树的最近公共祖先
- 重点在于是二叉树,不是搜索二叉树
- 利用后序遍历,定义三个boolean值,分别为当前根节点,左子树,右子树是否有目标结点。
TreeNode res = null;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
dfs(root,p,q);
return res;
}
public boolean dfs(TreeNode root,TreeNode p,TreeNode q){
if(root==null) return false;
boolean incurrent = root.val==p.val||root.val==q.val;
boolean inleft = dfs(root.left,p,q);
boolean inright = dfs(root.right,p,q);
if((inleft&&inright)||((inright||inleft)&&incurrent)){
res = root;
}
return incurrent||inleft||inright;
}
239.滑动窗口最大值
- 滑动窗口
- 维持一个双端队列并且是单调递减队列。
- 每次按照单调递减入队列,取队列头为最大值。
- 双端队列中存储的是元素下标,用于判断是否还在滑动窗口内。
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] res = new int[n-k+1];
int idx = 0;
Deque<Integer> deque = new ArrayDeque<>();
for(int i=0;i<k;i++){
while(!deque.isEmpty()&&nums[deque.peekLast()]<nums[i]){
deque.pollLast();
}
deque.offerLast(i);
}
res[idx++] = nums[deque.peekFirst()];
int j = k;
while(j<n){
while(!deque.isEmpty()&&nums[deque.peekLast()]<nums[j]){
deque.pollLast();
}
deque.offerLast(j);
while(!deque.isEmpty()&&deque.peekFirst()<=j-k){
deque.pollFirst();
}
res[idx++] = nums[deque.peekFirst()];
j++;
}
return res;
}
264.丑数
- dp[i]表示第i个丑数,
- 分别定义三个下标变量x2,x3,x5。表示当前下标需要乘上相应的数(如x2下标对应的值需要乘上2)。
- dp[i]就是三者的最小值
- dp[i]=min(dp[x2]*2,dp[x3]*3,dp[x5]*5)
- 然后比较dp[i]和三者,如果相等则说明该下标以及有了对应的丑数了,需要后移。
public int nthUglyNumber(int n) {
int[] dp = new int[n+1];
dp[1] = 1;
int x2 = 1,x3 = 1,x5 = 1;
for(int i=2;i<=n;i++){
dp[i] = Math.min(dp[x2]*2,Math.min(dp[x3]*3,dp[x5]*5));
if(dp[i]==dp[x2]*2){
x2++;
}
if(dp[i]==dp[x3]*3){
x3++;
}
if(dp[i]==dp[x5]*5){
x5++;
}
}
return dp[n];
}
279.完全平方数
- dp[i]表示构成整数i需要完全平方数的最少数量。
- dp[1] = 1;
- 从后往前遍历取最小值
public int numSquares(int n) {
int[]dp = new int[n+1];
dp[1] = 1;
for(int i=2;i<=n;i++){
dp[i] = i;
for(int j=1;i-j*j>=0;j++){
dp[i] = Math.min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
287.寻找重复数
- 将数组看成链表
- 快慢指针找到环的入口
public int findDuplicate(int[] nums) {
int slow = nums[0],fast = nums[0];
while(true){
slow = nums[slow];
fast = nums[nums[fast]];
if(slow == fast) break;
}
fast = nums[0];
while(fast!=slow){
fast = nums[fast];
slow = nums[slow];
}
return slow;
}
300.最长递增子序列
- 动态规划
- dp[i]存储着当前的递归子序列,如果要加入的数比dp[i]还大则加入到最后,并且maxlen+1,如果比dp[i]小则通过二分查找的方式找到该数应该在的地方。
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
int maxLen = 0;
for(int num:nums){
int l = 0,r = maxLen;
while(l<r){
int m = (l+r)/2;
if(dp[m]<num){
l = m+1;
}else {
r = m;
}
}
dp[l] = num;
if(l==maxLen) maxLen++;
}
return maxLen;
}
309.最佳买卖股票时机含冷冻期
- 动态规划,由于每个dp都只与前两位有关,可改成状态。
- 定义三个状态,has_stock,no_stock,freeze
public int maxProfit(int[] prices) {
int n = prices.length;
if(n<=1) return 0;
int hasStock = -prices[0];
int noStockNotFreeze = 0;
int noStockButFreeze = 0;
for(int i=1;i<prices.length;i++){
int x1 = Math.max(hasStock,noStockNotFreeze-prices[i]);
int x2 = Math.max(noStockNotFreeze,noStockButFreeze);
int x3 = hasStock + prices[i];
hasStock = x1;
noStockNotFreeze = x2;
noStockButFreeze = x3;
}
return Math.max(noStockButFreeze,noStockNotFreeze);
}
322.零钱兑换
- 动态规划,dp[i][j]表示前i个硬币中最少构成总金额为j的硬币个数。
- 定义dp[n+1][m+1],第一列全部为0,表示构成总金额为0需要0个硬币,第一行全为最大值,表示0硬币构成任何金额都需要无穷大个硬币。
- 如果nums[i]>=j表示可以选择是否参与组成,如果不参与则为dp[i-1][j],如果参与则为dp[i][j-nums[i]],取两者最小值作为dp[i][j]。
public int coinChange(int[] coins, int amount) {
if(amount==0) return 0;
int n = coins.length;
int[][] dp = new int[n+1][amount+1];
for(int i=0;i<=amount;i++){
dp[0][i] = amount+1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=amount;j++){
dp[i][j] = dp[i-1][j];
if(coins[i-1]<=j){
dp[i][j] = Math.min(dp[i-1][j],dp[i][j-coins[i-1]]+1);
}
}
}
return dp[n][amount]==amount+1?-1:dp[n][amount];
}
- 优化成一阶,因为dp只与当前行和上一行有关。
public int coinChange(int[] coins, int amount) {
if(amount==0) return 0;
int n = coins.length;
int[] dp = new int[amount+1];
for(int i=0;i<=amount;i++){
dp[i] = amount+1;
}
dp[0] = 0;
for(int i=1;i<=n;i++){
for(int j=coins[i-1];j<=amount;j++){
dp[j] = Math.min(dp[j],dp[j-coins[i-1]]+1);
}
}
return dp[amount]==amount+1?-1:dp[amount];
}
337.打家劫舍3
- 二叉树类型
- dfs,每个节点获取左右子节点的能够盗取的最高金额(分为偷和不偷)。
public int rob(TreeNode root) {
int[] res = dfs(root);
return Math.max(res[0],res[1]);
}
public int[] dfs(TreeNode root){
if(root==null) return new int[]{0,0};
//定义0为偷,1为不偷
int[] left = dfs(root.left);
int[] right = dfs(root.right);
int[] res = new int[2];
res[0] = root.val + left[1] + right[1];
res[1] = Math.max(left[0],left[1])+Math.max(right[0],right[1]);
return res;
}
338.比特位记数
- dp数组
- 如果当前数是偶数的话,dp[i] = dp[i/2],否则dp[i-1]+1
- 判断奇偶性可以采用位运算。
public int[] countBits(int n) {
if(n==0)return new int[]{0};
if(n==1)return new int[]{0,1};
int[] dp = new int[n+1];
dp[0] = 0;
dp[1] = 1;
for(int i=2;i<=n;i++){
if(i%2==0){
dp[i] = dp[i/2];
}else {
dp[i] = dp[i-1]+1;
}
}
return dp;
}
前k个高频元素
- 利用堆排序
- 先利用map存储每个元素的频数,再利用小顶堆(优先队列)遍历存放进去,然后取k个到res数组中。
- 注意要从小到大排序,因为要比较peek当前队列中最小的那个。
public int[] topKFrequent(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
// System.out.println(map);
PriorityQueue<Map.Entry<Integer,Integer>> queue = new PriorityQueue<>((e1,e2)-> e1.getValue()-e2.getValue());
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
if(queue.size()==k){
if(queue.peek().getValue()<entry.getValue()){
queue.poll();
queue.offer(entry);
}
}else{
queue.offer(entry);
}
}
int[] res = new int[k];
for(int i=0;i<k;i++){
res[i] = queue.poll().getKey();
}
return res;
}
394.字符串解码
- 定义当前字符串,当前数字,栈中字符串,栈中数字。
- 当遇到左括号,将当前数字和当前字符串分别入栈。
- 当遇到右括号时,取出数字栈顶num1,取出字符串栈顶str1,,str1+遍历累加num1次的cur,并赋值给res。
public String decodeString(String s) {
StringBuilder res = new StringBuilder();
int num = 0;
Deque<StringBuilder> resStack = new ArrayDeque<>();
Deque<Integer> numStack = new ArrayDeque<>();
char[] cs = s.toCharArray();
for(char c:cs){
if(Character.isDigit(c)){
num = num * 10 + c - '0';
}else if(c=='['){
resStack.offerFirst(res);
numStack.offerFirst(num);
num = 0;
res = new StringBuilder();
}else if(c==']'){
StringBuilder tempStr = resStack.pollFirst();
int tempNum = numStack.pollFirst();
for(int i=0;i<tempNum;i++){
tempStr.append(res);
}
res = tempStr;
}else{
res.append(c);
}
}
return res.toString();
}