暴力递归:将栈逆序,不要使用额外空间

暴力递归:将栈逆序,不要使用额外空间

提示:本节是系列暴力递归的题目,目标是为了尝试暴力递归,然后改进动态规划的代码

暴力递归:问题可以分为若干相同模式的子问题,先求更小规模上的解,然后整理返回。


题目

给你一个栈,数据结构:栈
请你想办法把栈逆序了,注意:不要使用额外空间


一、审题

示例:栈从上到下放了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,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值