算法学习 暴力递归

暴力递归

1.把问题转化为规模缩小了的同类问题的子问题
2.有明确的不需要继续进行递归的条件(base case)
3.有当得到了子问题的结果之后的决策过程
4.不记录每一个子问题的解

动态规划的基础

Q1:汉诺塔问题

3个柱子,其中一个柱子摆放n个盘子,将n个盘子放另一个柱子上,金字塔->金字塔

抽象成form,to,other
问题转为
1到i-1个盘子from-other
第i个盘子 form-to
第1到i-1个盘子other-to
只需要满足第i个盘子局部决策正确即可,不用理会全局是否还是初始状态,其余状态在递归中自动满足。
***考虑局部的决策正确,全局交给递归满足 只需要解决递归的子问题即可
只要最底下的盘子满足条件即可,其余盘子移动到正确盘子时也会满足移动的条件
问题逐渐转化为,摆放好第n个,摆放好第n-1个...摆放好第一个

  public static void hanoi(int n){
        if(n>0){
            func(n,'左','右','中');
        }
    }
    //从start移到end,借助other
    public static void func(int i,int start,int end,int other){
        if(i==1){
            System.out.println("Move 1 from"+start+"to"+end);
        }else{
            func(i-1,start,other,end);
            System.out.println("Move "+i+" from"+start+"to"+end);
            func(i-1,other,end,start);
        }
    }

Q2:打印字符串的所有子串,含空串

二叉树,从左往右,每个字符分支为含or不含

    public static void func(String str){
       char[] chs=str.toCharArray();
       process(chs,0,new ArrayList<Character>());
    }
    public static void process(char[] str,int i,List<Character> res){
        if(i==str.length){
            System.out.println(res);
            return;
        }
        //加入该字符的路
        List<Character> resKeep= copyList(res);
        process(str,i+1,resKeep);
        //不加入该字符的路
        List<Character> resNotKeep= copyList(res);
        process(str,i+1,resNotKeep);
    }

    //省空间做法,原地修改str再复原
    public static void process1(char[] str,int i){
        if(i==str.length){
            System.out.println(str);
            return;
        }
        char temp=str[i];
        //加入该字符的路
        process1(str,i+1);
        str[i]=' ';
        //不加入该字符的路
        process1(str,i+1);
        str[i]=temp;
    }

Q3:打印字符串全部排列,要求不出现重复排列

    //str[i,,,,n]未排,且后续的都可在i位置进行尝试
    //str[0,,,i-1]已经排序
    //res收集排序的结果
    public static void process(char[] str,int i,ArrayList<String> res){
        if(i==str.length){
            res.add(String.valueOf(str));
            return;
        }
        //用于去重
        boolean[]visit =new boolean[26];
        for(int j=i;j<str.length;j++){
            if(!visit[str[j]-'a']){
                visit[str[j]-'a']=true;//剪枝
                swap(str,i,j);
                process(str,i+1,res);
                swap(str,j,i);
            }
        }
    }

Q4:给定一组标有权值的卡牌,A,B轮流拿,只能拿最左或最右

,A,B不是笨蛋,卡牌为(1,2,100,4)如A先拿,只会拿1,而不会拿4把100留给B而输掉比赛,问最高得分。

    //f和s都是对于同一张牌而言
    //先手 返回在i,j范围的最大分数
    public static int f(int[] arr,int i,int j){
        if(i==j) return arr[i];
        //在[i,j]先手 在[i+1,j]后手
        return Math.max(arr[i]+s(arr,i+1,j),arr[j]+s(arr,i,j-1));
    }
    //后手 先手已经拿掉最大的了,后手只能拿最小(相对最小)
    public static int s(int[] arr,int i,int j){
        if(i==j) return 0;//一张牌的时候,先手拿掉后后手拿不到为0
        return Math.min(arr[i]+f(arr,i+1,j),arr[j]+f(arr,i,j-1));
    }
    public static int win(int[] arr){
        if(arr==null||arr.length==0){
            return 0;
        }
        return Math.max(f(arr,0,arr.length-1),s(arr,0,arr.length-1));
    }

Q5:只递归,不用额外的辅助,逆序一个栈

    //将堆低弹出,其余不变
    //123 ->23 1 ->3 2 -> null 3
    public static int f(Stack<Integer> stack){
        //拿到堆顶元素并保留
        int result=stack.pop();
        if(stack.isEmpty()){
            //最后一个元素是底也是顶 base case
            return result;
        }else{
            //拿掉堆顶的情况下继续拿下一个元素,直至栈空、
            //拿堆底
            int last=f(stack);
            //恢复栈
            stack.push(result);
            return last;
        }
    }

    public static void reverse(Stack<Integer> stack){
        if(stack.isEmpty()){//base case
            return;
        }
        //stack[1,n]
        //拿堆底n,n-1,n-2,,,1
        int i=f(stack);
        reverse(stack);
        //将堆底逆序压回去
        //最后一次拿堆底为1后,递归返回重复执行下一句
        stack.push(i);
    }

Q6:解码问题

规定1-a,2-b,3-c..11-k...26-z

解数字组成的字符串一共可解出多少种组合 如111->(aaa/ak/ka)

已决定[0,i-1],待决定[i,n]
待决定有3种可能,
1.i为0,后续无法决定
2.i大于3 只有一种决定+[i+1,n]种决定
3.i小于3 “有一种+[i+1,n]”种 和“ 一种+[i+2,n]种 ”两个可能

public static int process(char[] str,int i){
        if(i==str.length) return 1; //收集结果
        if(str[i]=='0') return 0; //后续不可能转成功
        if(str[i]=='1'){
            //只转一个数
            int res=process(str,i+1);
            //转两个数
            if(i+1<str.length){
                res+=process(str,i+2);
            }
            return res;
        }
        if(str[i]=='2'){
            //只转一个数
            int res=process(str,i+1);
            //转两个数,这时对第二个数有要求,转化范围只有1-26
            if(i+1<str.length&&str[i+1]>'0'&&str[i+1]<'6'){
                res+=process(str,i+2);
            }
            return res;
        }
        //只转一个数
        return process(str,i+1);
    }

Q7: 01背包问题

要or不要,暴力递归树类似Q2

数组weight[i],values[i]表示物品i的重量和价值,给定背包大小bag,求怎么装价值最大。

    //i...的货物随便选,返回选择后的最大价值
    //不理会i之前的
    public static int process(int[] weights,int[] values,int i,int alreadyWeight,int bag){
        //前两个if是base case 可理解为选后违规,可新加的价值为0
        //选完超重
        if(alreadyWeight>bag) return 0;
        //已经没货可选了,还未选择
        if(i==weights.length) return 0;
        //拿i物品和不拿i物品
        return Math.max(process(weights,values,i+1,alreadyWeight,bag),
                values[i]+process(weights,values,i+1,weights[i]+alreadyWeight,bag));
    }

 

 

<think>嗯,用户想了解递归与分治算法的概念、实现以及例题解释。首先,我需要先回顾一下递归和分治的基本定义,确保自己理解正确。递归是指函数调用自身的过程,通常用来将问题分解成更小的子问题。分治算法则是将一个大问题分解成多个小问题,分别解决后再合并结果。这两者经常结合使用,比如在快速排序、归并排序中。 接下来,用户可能需要知道两者的具体区别和联系。比如,分治算法通常包含三个步骤:分解、解决、合并。而递归是实现分治的一种常见方法,但分治也可以不用递归,比如用迭代的方式,不过递归更自然。需要举一些例子来说明,比如归并排序,分解数组到单个元素,再合并排序。 然后,用户提到了实现和例题,这部分需要给出具体的例子,比如LeetCode中的题目,如恢复IP地址、x的平方根等。这些例题需要说明如何应用递归和分治来解决,比如恢复IP地址时如何分割字符串并验证有效性,分治处理每一段。 还要注意引用用户提供的参考资料,比如引用[1]中的算法系列文章,引用[2]中的分治算法要素,引用[3]中的假币问题。这些引用需要自然地融入回答中,帮助用户找到更多信息。 用户可能还关心递归与分治的优缺点,比如递归可能导致栈溢出,分治的时间复杂度分析,如主定理的应用。不过用户的问题主要集中在概念、实现和例题,可能暂时不需要深入分析复杂度,但可以简要提及。 需要检查是否有常见的误解,比如认为所有递归都是分治,其实分治是策略,递归是手段。还要确保例题的解释清晰,比如pow(x,n)问题,用分治将问题分解为n/2次方的平方,减少计算次数。 最后,生成的相关问题要围绕用户的需求,比如性能优化、应用场景等,帮助用户进一步学习。同时,确保回答结构符合要求,分点明确,使用中文,数学公式正确使用$和$$格式。</think>### 递归与分治算法详解 #### 一、基本概念 1. **递归** 递归是一种通过函数调用自身来解决问题的编程技巧,其核心是将复杂问题分解为结构相似的子问题。例如,计算阶乘 $n!$ 的递归实现: $$n! = n \times (n-1)! \quad (n \ge 1)$$ 递归需满足两个条件: - **基线条件**(终止条件) - **递归调用**(缩小问题规模)[^1] 2. **分治算法** 分治算法通过以下三步解决问题: - **分解**:将原问题划分为多个子问题(如归并排序中将数组分为两半) - **解决**:递归或直接求解子问题 - **合并**:将子问题的解组合为原问题的解 分治适用于子问题相互独立且合并成本较低的场景[^2]。 --- #### 二、实现与例题 1. **LeetCode例题:恢复IP地址(restore-ip-addresses)** **问题**:将字符串分割为合法的IPv4地址(如`"25525511135"`→`["255.255.11.135", "255.255.111.35"]`)。 **分治思路**: - 将字符串分为4段,每段长度为1-3且值≤255 - 递归处理剩余字符串,确保所有可能被枚举 ```python def restoreIpAddresses(s): def backtrack(s, segments, result): if len(segments) == 4: if not s: result.append(".".join(segments)) return for i in range(1, 4): if i > len(s): break segment = s[:i] if (len(segment) > 1 and segment[0] == '0') or int(segment) > 255: continue backtrack(s[i:], segments + [segment], result) result = [] backtrack(s, [], result) return result ``` 2. **快速幂算法(powx-n)** **问题**:高效计算 $x^n$。 **分治实现**: $$x^n = \begin{cases} (x^{n/2})^2 & \text{if } n \text{ even} \\ x \times (x^{(n-1)/2})^2 & \text{if } n \text{ odd} \end{cases}$$ 时间复杂度为 $O(\log n)$,显著优于暴力法 $O(n)$。 --- #### 三、应用场景与优化 1. **典型应用** - 排序算法(快速排序、归并排序) - 数学计算(大整数乘法、矩阵乘法) - 搜索问题(二分查找、最近点对问题) 2. **优化方向** - **尾递归优化**:避免栈溢出(如将递归改写为迭代) - **剪枝策略**:减少无效子问题(如IP地址分割时跳过前导零) - **并行计算**:独立子问题可并行处理[^2] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值