暴力递归:将栈逆序,不要使用额外空间
提示:本节是系列暴力递归的题目,目标是为了尝试暴力递归,然后改进动态规划的代码
暴力递归:问题可以分为若干相同模式的子问题,先求更小规模上的解,然后整理返回。
题目
给你一个栈,数据结构:栈
请你想办法把栈逆序了,注意:不要使用额外空间
一、审题
示例:栈从上到下放了1 2 3
请你逆序为从上到下3 2 1
这就是栈的逆序
用栈逆序一个栈,需要耗费o(n)额外空间
栈本身就可以逆序,因为栈可以先进后出:
咱们外部申请一个栈,s2,把s全部弹出,放入s2,然后再弹出s2就是s的逆序了,这老掉牙了
但是这样就会耗费额外空间,这不符合本题的要求
用暴力递归,直接逆序栈,o(1)空间复杂度
这比较困难,但是递归的本质,就是不断开辟缓存,利用缓存放某一个递归函数的值
比如:将f定义为求1–N上的累加和,咱们可以先去1–N/2范围上求累加和left,然后再去求N/2+1–N范围内的累加和right
ans=left+right
1–N/2范围上,又有它自己的l+r,N/2+1–N范围上,也有它自己的l+r
公式都一样,但是所有这些l和r都是不同递归函数进程的缓存下的,他们名字相同,但是不冲突。
因此,我们在这个题中也可以用类似的思想:
(1)定义一个递归函数f(stack),给它一个栈,它f能做什么呢?清除栈底那个元素down,然后返回栈底down;
我们为啥要返回栈底,因为我们想把栈底翻上来,放在栈顶呗
先不说(1)咋实现的,你知道它有着功能就行
先说(2):
(2)定义一个逆序函数reverse(stack),给它一个栈,让他完成逆序过程;
这reverse就是咱们本题要求的函数
里面用到了谁?用到了f,为啥
这么想:reverse(stack)里面时这么操作的
0)如果栈来了就是一个空的,那不好意思,返回就行;啥也不干
1)我们这样搞,先把stack的栈底清除,暂存这个栈底down;调用f(stack)函数就能搞定
2)然后递归把stack剩下的元素逆序好:调用reverse(stack)【这个栈少了down哦】
3)好,此时少了down的栈stack已经全部逆序好了,咱们再压入down放到stack的最顶端;
4)现在岂不是stack就是我们要的逆序好的栈?
——这就是reverse的实现,思想很明确吧,就是清除栈底,先逆序剩下的栈,然后放栈底到顶部,岂不就是完成了整体的逆序;
okay,那么上面(1)咋做呢???
咱们之前说了定义一个递归函数f(stack),给它一个栈,它f能做什么呢?清除栈底那个元素down,然后返回栈底down;
我们为啥要返回栈底,因为我们想把栈底翻上来,放在栈顶呗
怎么实现呢?这样做
0)先直接弹出栈顶peek
1)如果此时栈空了,那直接返回那个peek,因为刚刚那个peek就是栈底元素,要清除它,返回它
2)如果栈不是空的,那说明刚刚那个peek,它不是栈底,回头,咱还需要把它放回stack里面;
现在,继续递归调用f(stack)【现在stack是少了一个栈顶peek的stack哦】,得到它的栈底down–当然这个栈底一直都是原始栈的栈底;缓存好
3)此时,将0)中的那个原始栈的栈顶peek放回栈stack里面
4)然后返回down;最终完成清楚并返回原始stack的栈底down。
说白了这个f就是要清除原始栈的栈底,并返回这个栈底,其他的元素原封不动,中途抽出来的栈顶peek,临时放好,等拿到栈底之后,挨个还原。
所以f的实现代码(1):
//复习逆序栈
//复习:先清除栈底,然后返回栈底down
public static int f(Stack<Integer> stack){
//来了就弹
int peek = stack.pop();
//递归去把剩下的这个栈的栈底清除,返回来,本来也就是原始栈的栈底
if (stack.isEmpty()) return peek;//就一个元素,返回去就行
else {
int down = f(stack);
//然后我们要把刚刚顶部那个放回去
stack.push(peek);//除了栈底,其他原封不动
return down;
}
}
特别巧吧,就是递归实现子问题,然后处理本问题。搞定了
需要你宏观上把握这个递归代码的思想,反正清除栈底,暂存,继续递归拿栈底,然后原来的顶部那个放入栈不动,返回栈底。
有了f,我们就可以实现(2)的reverse了
也是本题的代码:
//复习:本题要的逆序过程
public static void reviewReverse(Stack<Integer> stack){
if (stack.isEmpty()) return;//空了就算了
//正常情况下,先清除栈底然后拿到栈底,再把stack余下的完成逆序,最后把栈底放在这个逆序上,就是整体逆序
int down = f(stack);
reverse(stack);//剩下的逆序
stack.push(down);//逆序放栈底,就是整体逆序了
}
需要你宏观上把握这个递归代码的思想,先清除栈底,拿到栈底缓存,然后逆序栈剩下的元素,最后把这个栈底放到已经逆序好的栈上,完成整体的栈逆序。
美滋滋
测试一把:
public static void test(){
Stack<Integer> stack = new Stack<>();
stack.push(3);
stack.push(2);
stack.push(1);
reviewReverse(stack);
while (!stack.empty()){
System.out.println(stack.pop());
}
reverse(stack);
while (!stack.empty()){
System.out.println(stack.pop());
}
}
public static void main(String[] args) {
test();
}
}
3
2
1
总结
提示:重要经验:
1)reverse的实现,思想很明确吧,就是清除栈底,先逆序剩下的栈,然后放栈底到顶部,岂不就是完成了整体的逆序;
2)清除栈底并返回栈底的函数f,很简单,先弹出栈顶,把剩下的栈,继续递归拿栈底之后,刚刚弹出的栈顶,放回栈原封不动。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。