暴力递归--题目集

目录

一 . 打印字符串序列

二. 打印字符串的全排列

三. 拿纸牌,得分数

四. 逆序栈(不可以使用额外空间)

五. 背包问题

六. 数字数组转换成字符


一 . 打印字符串序列

总体思路:将字符串按照深度优先遍历,将不要当前字符的跑一遍;再将要当前字符的跑一遍,再加上当前字符;形成一个二叉树结构的样子。代码实现较为简单,主要是理解起来,如果理解的太深,反而会迷惑,所以对于递归这一类的,就去给局部的流程整理清楚,然后放大到全局都是这个流程,就是正确的。以下是代码实现。分为重复和无重复的实现。

    //一、打印字符串的子序列  包含重复的
    //参数:字符串  起始位置  存储结果的集合  结果串
    public void printSequences(char[] arr, int index, List<String> result, String path) {
        //递归出口  如果当前index等于arr的长度了  就将这个path结果存到result集合中
        if (index == arr.length) {
            result.add(path);
            return;
        }
        //将要和不要的字符 都通通跑一遍  然后将需要的字符加到path上面
        //不能去太深的理解  容易乱
        //不要当前字符的  将其深度跑一遍
        printSequences(arr, index + 1, result, path);
        //要当前字符的  将其深度跑一遍 并加上当前字符
        printSequences(arr, index + 1, result, path + arr[index]);
    }

    //二、打印字符串序列  不包含重复元素  直接使用set去重即可
    public void printSequences(char[] arr, int index, HashSet<String> result, String path) {
        if (index == arr.length) {
            result.add(path);
            return;
        }
        //不要当前位置上的元素
        printSequences(arr, index + 1, result, path);
        //需要当前位置上的元素
        printSequences(arr, index + 1, result, path + arr[index]);
    }

二. 打印字符串的全排列

总体思路:循环字符串,每次交换相邻两个元素,然后递归去操作他(继续交换,或者将其放入到结果集合中),操作完毕之后,将元素位置调换回来。分为去重复和不去重复两个实现。

不去重复版:

    public static List<String> per1(char[] arr){
        List<String> result = new ArrayList<>();
        permutation1(arr,result,0);
        return result;
    }
    //未去重复版
    //参数:字符数组   结果集合   每次开始的位置
    public static void permutation1(char[] arr, List<String> result, int index) {
        //递归出口  如果index等于数组长度了 就将其添加到result中
        if(index == arr.length){
            result.add(String.valueOf(arr));
        }else {
            //循环这个字符串  将这个字符串的邻近两个元素交换位置  去处理它  然后再将其位置还原 进行下一次交换并处理
            //注意:i的位置应该是从index开始 进行每一个局部的相邻字符的交换  最终形成整体
            for (int i = index; i < arr.length; i++) {
                swap(arr, i, index);
                //处理它
                permutation1(arr, result, index + 1);
                //将原顺序调整回来
                swap(arr, i, index);
            }
        }
    }

去重复版:

    //去重复版
    public static List<String> per2(char[] arr){
        List<String> result = new ArrayList<>();
        permutation2(arr,result,0);
        return result;
    }
    //参数:字符数组  结果集合  每次开始的位置
    public static void permutation2(char[] arr,List<String> result,int index){
        if(index == arr.length){
            result.add(String.valueOf(arr));
        }else{
            //去重复 给每一个字符做一个标记 如果是true就是已经存在
            boolean[] flag = new boolean[256];
            for(int i = index;i < arr.length;i++){
                if(!flag[i]) {  //如果是默认值false 就进来处理这个字符串  如果是true就不处理
                    flag[i] = true;
                    swap(arr, i, index);
                    permutation2(arr, result, index + 1);
                    swap(arr, i, index);
                }
            }
        }
    }

三. 拿纸牌,得分数

题目:给定一个整型数组arr,代表数值不同的纸牌排成一条线 ,玩家A和玩家B依次拿走每张纸牌, 规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌, 玩家A和玩家B都绝顶聪明,请返回最后获胜者的分数。

代码实现:

    public int win(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        //先手先拿
        int firstValue = first(arr,0,arr.length - 1);
        int lastValue = last(arr,0,arr.length - 1);
        return Math.max(firstValue,lastValue);
    }

    //参数:起始位置  终止位置  在这个范围内去拿纸牌
    public int first(int[] arr, int left, int right) {
        //如果只剩下一个牌  肯定是我先拿
        if(left == right){
            return arr[left];
        }
        //两种情况:
        //情况一:我先拿左边的  拿完之后我的每次都变成了后手 但是我多了一个arr[left]值
        //范围变成了  left + 1 ~ right
        int leftValue = arr[left] + last(arr, left + 1, right);
        //情况二:先拿右边的 拿完之后我变成了后手  同时我也多了一个arr[right]
        int rightValue = arr[right] + last(arr, left, right - 1);
        //我每次获得两个之中的最大值
        return Math.max(leftValue,rightValue);
    }

    //后手拿牌
    public int last(int[] arr, int left, int right) {
        //如果只剩下一个牌  肯定是属于先手  我什么也得不到
        if(left == right){
            return 0;
        }
        //两种情况:
        //情况一:左边的牌已经被先手拿了  那我只剩下右边的牌 即范围变成了left + 1 ~ right
        int rightValue = first(arr, left + 1, right);
        //情况二:右边的牌被先手拿了  我只剩下左边的牌  范围变成了 left ~ right - 1;
        int leftValue = first(arr, left, right - 1);
        //每次获得最小的那个
        return Math.min(rightValue,leftValue);
    }

四. 逆序栈(不可以使用额外空间)

总体思路:每次先从栈低拿出一个数,然后将原数压入栈中,保持栈的顺序,栈空之后,使用递归将其反向压入栈中。

代码实现:

//将一个栈逆序 不能使用额外的存储空间
    public void reverseStack(Stack<Integer> stack){
        //递归出口  如果栈为空  停止
        if(stack.isEmpty()){
            return ;
        }
        //我需要一个方法  每次调用他 给我返回一个栈低元素
        //每次得到一个栈低元素
        int base = returnBase(stack);
        //使用递归 将获取到的栈低元素按照反方向存入
        reverseStack(stack);
        //将这个元素压入栈
        stack.push(base);
    }

    //返回栈低元素 并保持原栈顺序
    public int returnBase(Stack<Integer> stack){
        //先拿到栈顶元素  当最后一个栈顶元素被弹出时  栈为空  进入到if里面  且top不会记录最后一个元素的值 即不会将其压入栈 会直接返回
        int top = stack.pop();
        //拿完之后 判断栈是否为空 如果为空 直接返回这个top
        if(stack.isEmpty()){
            return top;
        }else{
            //如果不是空  继续执行此操作  并记录栈低的值
            int last = returnBase(stack);
            //栈为空后 依次返回  将上面的元素重新压回栈中  保持原栈顺序
            stack.push(top);
            return last;
        }
    }

五. 背包问题

题目描述:给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表,i号物品的重量和价值 ,给定一个正数bag,表示一个载重bag的袋子,装的物品不能超过这个重量,返回能装下的最大价值。

总体思路:使用暴力递归,将不要当前货物的路线跑一遍,再跑一遍要当前货物的路线,得到两者的最大值,就是最大价值。

//参数:背包数组  价值数组  起始位置(i号物品) 载重袋子  已经装了多少货物
    //返回值:能装下的最大价值
    public int bagMaxCapacity(int[] weights,int[] values,int i,int bag,int alreadyWeight){
        //递归出口
        //1. 如果背包中的物品已经大于最大载重了 返回0
        if(alreadyWeight > bag){
            return 0;
        }
        //2. 如果物品数组已经空了 什么也拿不到 返回0
        if(i == weights.length){
            return 0;
        }
        //将要当前物品的和不要当前物品的都跑一遍 然后返回其最大值
        //                 不要当前物品的
        return Math.max(bagMaxCapacity(weights,values,i + 1,bag,alreadyWeight),
        //                 要当前物品的  当前物品的价值 加上下一个要的物品的价值
                        values[i] + bagMaxCapacity(weights,values,i + 1,bag,alreadyWeight + weights[i]));
    }

暴力递归改动态规划版本

//暴力递归法
    //index  是w中的第index个货物
    //rest   是背包还能装入的货物量
    public static int bag1(int[] w, int[] v, int index, int rest) {
        //如果rest被撑爆了 则不能装了
        if (rest < 0) {
            return -1;
        }
        //如果走到了最后一个货物 则也没有了
        if (index == w.length) {
            return 0;
        }
        //先找不要当前货物的
        int p1 = bag1(w, v, index + 1, rest);
        //找要当前货物的
        int p2 = 0;
        int next = bag1(w, v, index + 1, rest - w[index]);
        //如果要当前货物了,并且背包不被撑爆的前提下  将后面一个货物临时接收
        if (next != -1) {
            p2 = v[index] + next;
        }
        //返回最大价值
        return Math.max(p1, p2);
    }

    //背包问题改动态规划
    public static int dpBag(int[] w, int[] v, int bag) {
        //如果bag被撑爆了 则不能装了
        //循环从0开始  所以没有小于0一说
        /*if (bag < 0) {
            return -1;
        }*/
        //如果走到了最后一个货物 则也没有了
        //因为二维表初始每个值都是0  所以可以省去这一步
        /*if (index == w.length) {
            return 0;
        }*/
        //去掉无效数据
        if (w == null || v == null || w.length != v.length || w.length == 0) {
            return -1;
        }
        //二维数组表
        //只有遍历货物和背包是可变的
        //得到货物的个数
        int N = w.length;
        //N 存储货物的个数  bag存储背包的容量
        int[][] dp = new int[N + 1][bag + 1];
        //双循环  填表
        //从下往上 从左往右
        for (int index = N - 1; index >= 0; index--) {
            for (int rest = 0; rest <= bag; rest++) {
                //先找不要后面一个货物的
                int p1 = dp[index + 1][rest];
                //找要要后面一个货物的
                int p2 = 0;
                //要后面一个货物的前提是背包能不能装下
                //如果能装下 则后面一个货物的值就是表中的对应位置上的值
                int next = rest - w[index] < 0 ? -1 : dp[ index + 1 ][ rest - w[index] ];
                //如果要当前货物了,并且背包不被撑爆的前提下  将后面一个货物临时接收
                if (next != -1) {
                    p2 = v[index] + next;
                }
                //将p1和p2较大价值的装入到表中
                dp[index][rest] = Math.max(p1,p2);
            }
        }
        //因为是从下往上开始填表 所以返回最大价值是第一行的最后一个
        return dp[0][bag];
    }

六. 数字数组转换成字符

题目描述:规定1和A对应、2和B对应、3和C对应...26和Z对应,那么一个数字字符串比如"111”就可以转化为: "AAA"、"KA"和"AK",给定一个只有数字字符组成的字符串str,返回有多少种转化结果。

总体思路:暴力递归 尝试每一中可能。分情况讨论

//参数:字符串数组  起始位置
    //返回值:返回N中转化结果
    public static int convertNumToString(char[] str, int i) {
        //如果已经走到头了 则解法加一
        if (i == str.length) {
            return 1;
        }
        //如果碰到0了 直接跳过

        //如果碰到了1 就正常转
        if (str[i] == '1') {
            //将当前的i进行单转
            int res = convertNumToString(str, i + 1);
            //在不越界的条件下  将当前i和其i+1的位置进行组合转
            if (i + 1 < str.length) {
                res += convertNumToString(str, i + 2);
            }
            //返回i在1时的转换结果
            return res;
        }
        //如果碰到了2 单转正常转
        if (str[i] == '2') {
            //单转
            int res = convertNumToString(str, i + 1);
            //如果i+1的位置不越界 并且在0~6的范围内(字母只有26个)
            //将当前i和i+1进行组合转
            if (i + 1 < str.length && (str[i + 1] >= '0' && str[i + 1] <= '5')) {
                res += convertNumToString(str, i + 2);
            }
            //返回i==2时的转换结果
            return res;
        }
        //如果i在 3~9 的范围内 则直接单转
        return convertNumToString(str, i + 1);
    }

七.N皇后问题

题目描述:

N皇后问题是指在N*N的棋盘上要摆N个皇后,

* 要求任何两个皇后不同行、不同列, 也不在同一条斜线上

* 给定一个整数n,返回n皇后的摆法有多少种。 n=1,返回1

* n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0

* n=8,返回92

采用暴力递归的方式

//给定一个N  返回能找出的有效摆法数
    public static int num(int N) {
        if (N < 1) {
            return 0;
        }
        if (N == 1) {
            return 1;
        }
        //记录行列的数组  索引代表行,该索引位置上的值代表列
        int[] record = new int[N];
        int result = process(N, 0, record);
        return result;
    }
    //给定N  当前行  以及记录摆入皇后的列数组(按照深度优先进行遍历,不会出现行重复的情况,所以只需要记录列数组)
    public static int process(int N, int curRow, int[] record) {
        //递归出口
        //curRow代表行  当皇后数和curRow数相等了 再停止
        if (N == curRow) {
            return 1;
        }
        int result = 0;
        //循环每一个皇后  列索引
        for (int j = 0; j < N; j++) {
            //判断 是否有共列 或者共斜线的情况
            if (isValid(record, curRow, j)) {
                //可以存
                //将对应的列索引值存到当前位置上  即 第0行 存入j=0
                record[curRow] = j;
                //继续找别的皇后 并加入解法中 继续从下一行开始找
                result += process(N, curRow + 1, record);
            }
        }
        return result;
    }
    //判断是否有共列 共斜线的情况  只需要看 i - 1 之前的那些行  i及其之后的还没有放入过值
    //参数:当前数组   处理到了第几行  当前列值多少
    public static boolean isValid(int[] record, int curRow, int j) {
        //依次查看每一列的每一个值 是否等于 当前位置(因为存储的时候是将对应的位置索引存入到对应的位置)
        for (int k = 0; k < curRow; k++) {
            //条件一:比较两个皇后是否共列  即:record[k] 代表的列值 是否等于 j代表的列值  如果共列  不能放
            //条件二:是否是同斜线的  计算方法:如果行-行 == 列-列 就代表是同一条斜线上面的
            //如果当前不是同一条斜线上面的 就往里面缩小范围  k代表了行  record[k]就是列
            //缩小范围理解: 因为k和curRow这两个行形成了一个范围
            //             在这个范围内去逐步缩小范围去比较是否有共斜线的现象
            if (record[k] == j || Math.abs(curRow - k) == Math.abs(record[k] - j)) {
                //不能存
                return false;
            }
        }
        return true;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值