算法26:暴力递归

news2024/11/17 7:23:23

目录

题目1:给你一个字符串,要求打印打印出这个字符串的全部子序列(子序列不能重复)

题目2:打印一个字符串的全部排列。

题目3:针对题目2,要求去除重复元素

题目4:给定一个字符串,要求打印这个字符串中字符能够能够组成的全部子集,子集无重复值。

题目5:给你一个栈,请你逆序这个栈,不能申请额外的数据结构, 只能使用递归函数。 


递归是一个过渡,为动态规划做准备。递归写好了,可以大量的优化代码结构,但是缺点是逻辑不好懂。一个号的递归,参数设计尤为重要。

题目1:给你一个字符串,要求打印打印出这个字符串的全部子序列(子序列不能重复)

这一题需要注意的是,给定了一个字符串,而字符串肯定是有顺序的,比如字符串为abc, 那么他的子序肯定不会出现cba。因为这样的顺序完全被颠倒了。所有,子序是不能改变原字符串的整体顺序的,在保持整体顺序的情况下自由组合。

package code03.递归_06;

import java.util.ArrayList;
import java.util.List;

/**
 * 给你一个字符串,要求打印打印出这个字符串的全部子序列(子序列不能重复)
 */
public class StringSequence_02
{

    public static List<String> getChildSuquence(String str)
    {
        char[]  chars = str.toCharArray();
        List<String> result = new ArrayList<>();
        //path这个参数很重要,它就是一个逐层拼接完整的字符串
        //根据递归遍历的层数,组成不同的字符串,非常灵巧
        String path = "";
        func(chars, result, 0, path);
        return result;
    }

    /**
     * 字符串是有序的,所以子序列必须要按照字符串的整体序列进行。 比如abc, 子序列肯定不能
     * 出现ba或者ca这种情况。
     *
     * 当前递归的逻辑是从后往前,就是找到最后一个元素。 下标依次是:
     *  N-2... N
     *  N-3...N
     *  N-4...N
     *  0...N
     *  N代表的是数组最后一个元素的下标。 而 N+1 就是代表的是数组的长度,
     *  此时,已经没有对应的下标了,所以认为是出口
     *
     *  假设字符串为abc, 那么它的子集为
     *  1.空字符串
     *  2.c
     *  3.b
     *  4.bc
     *  5.a
     *  6. 52组合得到ac,53z组合河道ab,54组合得到abc
     */
    static void func (char[] chars, List<String> list, int index, String path)
    {
        /**
         * 递归的出口。
         * index是最后一个字符的下标。而index+1就是数组的长度
         * 所以就需要跳出递归了
         */
        if (index == chars.length) {
            //排除重复元素
            if (!list.contains(path)) {
                list.add(path);
            }
            return;
        }
        //一直往下找,直到数组的尽头,默认给个空字符串
        func(chars, list, index + 1, path); //此处index+1用的很灵巧,因为index值本身是没有改变的

        //此处会改变path的值,用的很灵巧
        //每一个方法对弈一个方法栈,而变量path存在本地变量表,方法结束,本地变量表销毁
        //因此,此处改变的path只是在当前方法中生效。不会影响其他方法中相同名称的path值
        path = path + String.valueOf(chars[index]);

        //此处开始根据路径进行拼接。
        func(chars, list, index + 1, path);
    }

    public static void main(String[] args) {
        String test = "abc";
        //String test = "abcc";
        List<String>  result = getChildSuquence(test);

        for (String str : result) {
            System.out.println(str);
        }
    }
}

这一题解释一下:

设计思路是根据数组的下标索引进行的,而递归是一直找到下标最后一个元素为止。

此时,如果最后一个元素为c。如果c不被包括,那么path为空字符串,func搜集到空字符串。如果c被包括,那就 func 搜集到的就是 空字符串与c的拼接。 即c。递归结束,返回上一层

此时,返回到元素b处,此时不包括b的元素搜集完毕。即搜集到空字符串和c。path重新组合。 此时path为空字符串和b的组合。再次func操作。而func内部又分为不包含path和包含path两种逻辑,此时针对的是c。如果不包含c,那就搜集到b。如果包含c,那就收集到了b和c的组合,即bc

以此类推.......

整个流程图如下:

搜集结果为: 空字符串、c、b、bc、a、ac、ab、abc 

题目2:打印一个字符串的全部排列

这个字符串的排序指的是可以任意变换字符串的顺序,而不是子字符串。 比如: abc 其中一个就是cba 但是ab a ac等都不属于全部排序

package code03.递归_06;

import java.util.ArrayList;
import java.util.List;

/**
 * 给定一个字符串,要求打印这个字符串的全部排序。
 * 注意:这个字符串的排序指的是可以任意变换字符串的顺序,而不是子字符串
 * 比如: abc 其中一个就是cba   但是ab a ac等都不属于全部排序
 */
public class PrintAllFullStr_03
{
    public static List<String> getAllStr(String str)
    {
        List<String> result = new ArrayList<>();
        String path = "";
        char[] chars = str.toCharArray();
        List<Character> list = new ArrayList<>();
        for (char cha : chars) {
            list.add(cha);
        }
        func(list, result, path);
        return result;
    }

    static void func (List<Character> chars, List<String> result, String path)
    {
        if (chars.isEmpty()) {
            result.add(path);
        }
        else {
            for (int i = 0; i < chars.size(); i++) {
                char cur = chars.get(i);
                //清除现场
                chars.remove(i);
                func(chars, result,  path + cur);
                //恢复现场
                chars.add(i, cur);
            }
        }
    }

    public static void main(String[] args) {
        String test = "abc";
        //String test = "acc";
        List<String> result = getAllStr(test);
        for (String str : result) {
            System.out.println(str);
        }
    }
}

其实,这一道题,还有另一种比较通用的解法,思路与题目1基本一样。

package code03.递归_06;

import java.util.ArrayList;
import java.util.List;

/**
 * 给定一个字符串,要求打印这个字符串的全部排序。
 * 注意:这个字符串的排序指的是可以任意变换字符串的顺序,而不是子字符串
 * 比如: abc 其中一个就是cba   但是ab a ac等都不属于全部排序
 */
public class PrintAllFullStr_03_opt
{
    public static List<String> getAllStr(String str)
    {
        char[] chars = str.toCharArray();
        List<String> result = new ArrayList<>();
        String path = "";
        func(chars, result, 0, path);
        return result;
    }

    static void func (char[]  chars, List<String> result, int index, String path)
    {
        if (index == chars.length) {
            result.add(path);
        }
        else {
            for (int i = index; i < chars.length; i++) {
                swap(chars, index, i);
                func(chars, result, index + 1, path + chars[index]);
                swap(chars, index, i);
            }
        }
    }

    public static void swap(char[] chs, int i, int j) {
        char tmp = chs[i];
        chs[i] = chs[j];
        chs[j] = tmp;
    }

    public static void main(String[] args) {
        String test = "abc";
        //String test = "acc";
        List<String> result = getAllStr(test);
        for (String str : result) {
            System.out.println(str);
        }
    }
}

针对通用解法,我们发现,既然chs始终是动态排序的,但是内部元素并不会减少。因此,可以稍微优化一下参数:

package code03.递归_06;

import java.util.ArrayList;
import java.util.List;

/**
 * 给定一个字符串,要求打印这个字符串的全部排序。
 * 注意:这个字符串的排序指的是可以任意变换字符串的顺序,而不是子字符串
 * 比如: abc 其中一个就是cba   但是ab a ac等都不属于全部排序
 */
public class PrintAllFullStr_03_opt2
{
    public static List<String> getAllStr(String str)
    {
        char[] chars = str.toCharArray();
        List<String> result = new ArrayList<>();
        func(chars, result, 0);
        return result;
    }

    static void func (char[]  chars, List<String> result, int index)
    {
        if (index == chars.length) {
            result.add(String.valueOf(chars));
        }
        else {
            for (int i = index; i < chars.length; i++) {
                swap(chars, index, i);
                func(chars, result, index + 1);
                swap(chars, index, i);
            }
        }
    }

    public static void swap(char[] chs, int i, int j) {
        char tmp = chs[i];
        chs[i] = chs[j];
        chs[j] = tmp;
    }

    public static void main(String[] args) {
        String test = "abc";
        //String test = "acc";
        List<String> result = getAllStr(test);
        for (String str : result) {
            System.out.println(str);
        }
    }
}

其实,这一道题的解题思路就是逐步确定每个位置,而记录位置的参数是index。然后for循环,把剩余的数组元素,依次填补当前位置。以此类推,典型的深度优先遍历算法。流程图如下:

题目3:针对题目2,要求去除重复元素

 去除重复元素,其实分为结果过滤和条件过滤。结果过滤就是走完所有的流程,到最后搜集结果的时候通过结果集去重。而条件过滤则是从源头排除,性能更高。以题目2为例。如果字符串为acc. 下标为1的时候,c出现一次,下一个c再次出现的时候,直接排除掉。

结果过滤:

package code03.递归_06;

import java.util.ArrayList;
import java.util.List;

/**
 * 给定一个字符串,要求打印这个字符串的全部排序, 并且
 * 这些排完序的字符串无重复数据
 *
 * 结果过滤: 执行完流程,待放入集合中时判断是否重复进行过滤。
 * 缺点很明显, 不管是否重复,所有流程都会走一遍,性能低
 *
 */
public class PrintAllFullStrNoRepeat_04
{
    public static List<String> getAllStr(String str)
    {
        char[] chars = str.toCharArray();
        List<String> result = new ArrayList<>();
        String path = "";
        func(chars, result, 0);
        return result;
    }

    static void func (char[]  chars, List<String> result, int index)
    {
        if (index == chars.length) {
            /**
             * 结果过滤,可以直接使用set
             * 此处使用的是list,需要判断集合是否存在重复元素
             */
            if (!result.contains(String.valueOf(chars))) {
                result.add(String.valueOf(chars));
            }
        }
        else {
            /**
             * index代表当前位置开头。
             * 比如index为0, 就是锁定0位置的开头数组。 for循环
             * 是找到 a b c 分别占据0的位置作为开头。 比如 a**. b**, c**
             *
             * 当index为1时,代表分别找到所有数据作为1下标位置开头。 即*a*, *b*, *c*
             *
             */
            for (int i = index; i < chars.length; i++) {
                swap(chars, index, i);
                func(chars, result, index + 1);
                swap(chars, index, i);
            }
        }
    }

    public static void swap(char[] chs, int i, int j) {
        char tmp = chs[i];
        chs[i] = chs[j];
        chs[j] = tmp;
    }

    public static void main(String[] args) {
        //String test = "abc";
        String test = "acc";
        List<String> result = getAllStr(test);
        for (String str : result) {
            System.out.println(str);
        }
    }
}

条件过滤:

package code03.递归_06;

import java.util.ArrayList;
import java.util.List;

/**
 * 给定一个字符串,要求打印这个字符串的全部排序, 并且
 * 这些排完序的字符串无重复数据
 *
 * 结果过滤: 执行完流程,待放入集合中时判断是否重复进行过滤。
 * 缺点很明显, 不管是否重复,所有流程都会走一遍,性能低
 *
 */
public class PrintAllFullStrNoRepeat_04_opt
{
    public static List<String> getAllStr(String str)
    {
        char[] chars = str.toCharArray();
        List<String> result = new ArrayList<>();
        String path = "";
        func(chars, result, 0);
        return result;
    }

    static void func (char[]  chars, List<String> result, int index)
    {
        if (index == chars.length) {
            result.add(String.valueOf(chars));
        }
        else {
            //ASCII码的范围是0-255, 它可以表示所有的字符
            //每一次递归都new一个,用的很巧妙。需要仔细揣摩
            boolean[] visited = new boolean[256];
            for (int i = index; i < chars.length; i++) {
                /**
                 * index是当前位置。第一次进入肯定是通过的。占据当前位置
                 *
                 * 原始字符数组为 a b c        原始数组为acc
                 * 1. a b c                   a c c   原始的c c位置没变。 也就是说第二个位置被第一个c占据了一遍
                 * 2. a c b                   a c c 此时的 c c是交换后的位置。也就是第二c想要再次来占据第二个位置作为后续数组的开头,重复占领该位置,直接pass掉
                 * 3. b a c                   c a c
                 * 4. b c a                   c c a
                 * 5. c a b                   c a c  同理
                 * 6  c b a                   c c a  同理
                 */
                if (!visited[chars[i]]) {
                    visited[chars[i]] = true;
                    swap(chars, index, i);
                    func(chars, result, index + 1);
                    swap(chars, index, i);
                }
            }
        }
    }

    public static void swap(char[] chs, int i, int j) {
        char tmp = chs[i];
        chs[i] = chs[j];
        chs[j] = tmp;
    }

    public static void main(String[] args) {
        //String test = "abc";
        String test = "accb";
        List<String> result = getAllStr(test);
        for (String str : result) {
            System.out.println(str);
        }
    }
}

题目4:给定一个字符串,要求打印这个字符串中字符能够能够组成的全部子集,子集无重复值。

弄懂了题目2,这一题其实就很简单了。

package code03.递归_06;

import java.util.ArrayList;
import java.util.List;

/**
 * 给定一个字符串,要求打印这个字符串的全部排序。
 * 注意:这个字符串的排序指的是可以任意变换字符串的顺序,而不是子字符串
 * 比如: abc 其中一个就是cba   但是ab a ac等都不属于全部排序
 */
public class PrintAllStr_05_opt
{
    public static List<String> getAllStr(String str)
    {
        char[] chars = str.toCharArray();
        List<String> result = new ArrayList<>();
        String path = "";
        func(chars, result, 0, path);
        return result;
    }

    static void func (char[]  chars, List<String> result, int index, String path)
    {
        if (index == chars.length) {
            result.add(path);
        }
        else {
            boolean[] visited = new boolean[256];
            for (int i = index; i < chars.length; i++) {
                visited[chars[i]] = true;
                swap(chars, index, i);
                func(chars, result, index + 1, path + chars[index]);
                if (index < chars.length-1) {
                    result.add(path + chars[index]);
                }
                swap(chars, index, i);
            }
        }
    }

    public static void swap(char[] chs, int i, int j) {
        char tmp = chs[i];
        chs[i] = chs[j];
        chs[j] = tmp;
    }

    public static void main(String[] args) {
        String test = "abc";
        //String test = "acc";
        List<String> result = getAllStr(test);
        System.out.println("length is " + result.size());
        for (String str : result) {
            System.out.println(str);
        }
    }
}

题目5:给你一个栈,请你逆序这个栈,不能申请额外的数据结构, 只能使用递归函数。 

package code03.递归_06;

import java.util.Stack;

/**
 * 题目:
 * 给你一个栈,请你逆序这个栈,
 * 不能申请额外的数据结构,
 * 只能使用递归函数。 如何实现?
 */
public class StackReverse_06 {

    public static void reverse (Stack<Integer> stack)
    {
        if (stack == null || stack.isEmpty()) {
            return;
        }
        //获取栈底元素
        int result = func(stack);
        /**
         * 继续逆转栈中剩余的元素
         * 例如栈中从上到下为 3 2 1
         * 第一次 获取result 1
         * 第二次 获取result 2
         * 第三次 获取result 3
         */
        reverse(stack);
        /**
         * 那么最后一次递归结束后,放入元素
         * 顺序是 放入 1 2 3. 顺序变为  1  2 3
         */
        stack.push(result);
    }

    /**
     * 当前递归,每次结束的时候找到的都是栈底元素
     * @param stack
     * @return
     */
    public static int func(Stack<Integer> stack)
    {
        //如果cur是栈底元素,那么当前pop以后栈就空了
        //直接返回cur
        int cur = stack.pop();
        if (stack.isEmpty()) {
            return cur;
        }
        else {
            //栈底元素
            int last = func(stack);
            //倒数第二个放入栈底
            stack.push(cur);
            return last;
        }
    }


    public static void main(String[] args) {
        Stack<Integer> test = new Stack<Integer>();
        test.push(1);
        test.push(2);
        test.push(3);
        test.push(4);
        test.push(5);
        System.out.println("逆转前顺序为: 5 4 3 2 1");
        reverse(test);
        System.out.print("逆转后顺序为: ");
        while (!test.isEmpty()) {
            System.out.print(test.pop());
            System.out.print(" ");
        }

    }
}

这一题很难,难在不允许使用其他的结构来辅助。只能在栈中自己折腾。既然是逆序,那么放置的顺序就是要反过来,栈的特性是先进后出,这就复杂了起来,下面看下整个流程:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/548745.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【靶机】vulnhub靶机billu

靶机下载地址&#xff1a;https://download.vulnhub.com/billu/Billu_b0x.zip 一、环境搭建 1.1 实验环境设计 使用vmware导入靶机&#xff0c;模式为nat模式即可&#xff0c;同时开启kali作为攻击机&#xff0c;对靶机进行渗透&#xff0c;要确定两台主机在同一网段。 Kali…

[Python从零到壹] 六十六.图像识别及经典案例篇之基于机器学习的图像分类

五月太忙&#xff0c;还是写一篇吧&#xff01; 欢迎大家来到“Python从零到壹”&#xff0c;在这里我将分享约200篇Python系列文章&#xff0c;带大家一起去学习和玩耍&#xff0c;看看Python这个有趣的世界。所有文章都将结合案例、代码和作者的经验讲解&#xff0c;真心想把…

opencv_c++学习(十五)

一、图像的模板匹配 顾名思义&#xff0c;模板匹配是在一幅图像中寻找一个相同或相似的对象&#xff0c;如上图所示。 matchTemplate(lnputArray image, lnputArray templ,OutputArray result, int method, lnputArray mask noArray())image:待模板匹配的原图像&#xff0c;图…

【Spring/MySQL数据库系列】数据库事务的特点与隔离级别

⭐️前面的话⭐️ 本文已经收录到《Spring框架全家桶系列》专栏&#xff0c;本文将介绍有关数据库事务的特点以及隔离级别。 &#x1f4d2;博客主页&#xff1a;未见花闻的博客主页 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4…

数值计算 - 利用机器计算的基本方式

离散化方法 设f(x)是定义在[a,b]上的连续函数&#xff0c;当它们的表达式很复杂&#xff0c;甚至写不出来时&#xff0c;我们可以选择若干个离散点 求出f(x)在这些点处的函数值或函数值的近似值 从而得到一个如下的函数值列表&#xff1a; ⚠️提示&#xff1a;对于一个实际的…

Android源码环境搭建

Android源码环境搭建 参考&#xff1a; Android源码环境搭建 1.安装Ubuntu16.4 系统 2.openjdk 8 的安装 sudo apt-get install openjdk-8-jdk使用java -version检查版本 3.安装所有的软件包 sudo apt-get install git-core gnupg flex bison gperf build-essential zip c…

Shell基础学习---3、Read读取控制台输入、函数、正则表达式入门

1、Read读取控制台输入 1、基本语法 read &#xff08;选项&#xff09; &#xff08;参数&#xff09; 选项说明-p指定读取值的提示符-t指定读取值等待的时间(秒) 如果-t不加表示一直等待 参数说明变量指定读取值的变量名 2、案例实操 2、函数 2.1 系统函数 2.1.1 bas…

【数据库】SQLServer报修表,维修表,反馈表三表连查

大家好&#xff0c;我是雷工&#xff01; 最近参与的一个SCADA项目&#xff0c;客户要求增加设备维保的功能&#xff0c;对设备的报修&#xff0c;维修&#xff0c;反馈过程进行记录查询&#xff0c;进一步提升企业的信息化能力。 该过程的实现是通过创建三个表分别记录报修-维…

uniapp 用css画五边形(app 小程序),长方形中间斜线分割成两部分

效果图 css .scoreLabel{ background: $yxs-theme-color; width: 64rpx; height: 69rpx; line-height: 32rpx; font-size: 28rpx; font-family: DINPro; f…

chatgpt赋能Python-python3_9安装scrapy

Python3.9安装Scrapy——加速数据抓取的利器 在现代数字化时代&#xff0c;数据抓取和数据挖掘的重要性越来越受到重视。作为一种高效的爬虫框架&#xff0c;Scrapy能够实现快速的页面抓取和数据解析&#xff0c;并帮助我们快速获取所需数据。本篇文章将会为大家介绍如何在Pyt…

【零基础学web前端】CSS学习,字体属性,文本属性,背景属性,圆角矩形属性

前言: 大家好,我是良辰丫,在上一篇文章中我们了解了CSS引入方式,CSS基础选择器,CSS复合选择器,今天我们继续学习CSS的相关知识点.&#x1f49e;&#x1f49e; &#x1f9d1;个人主页&#xff1a;良辰针不戳 &#x1f4d6;所属专栏&#xff1a;零基础学web前端 &#x1f34e;励志…

其利天下技术居于32位MCU推出11万转无刷高速吹风筒方案--【高速吹风筒PCBA】

大家都知道高速吹风筒的兴起是因为戴森的产品体验&#xff0c;从另一角度赋予了吹风筒全新的产品形态和灵魂&#xff0c;于是产品有了智能和品质的体验感。 无刷电机的技术瓶颈在大家的共同努力下&#xff0c;从结构到驱动上都有了新的突破&#xff0c;所以近年来&#xff0c;高…

在疯狂三月之后,深入浅出分析AIGC的核心价值 (下篇)|【AI行研商业价值分析】

Rocky Ding 公众号&#xff1a;WeThinkIn 写在前面 【AI行研&商业价值分析】栏目专注于分享AI行业中最新热点/风口的思考与判断。也欢迎大家提出宝贵的优化建议&#xff0c;一起交流学习&#x1f4aa; 大家好&#xff0c;我是Rocky。 本文是《在疯狂三月之后&#xff0c;深…

openwrt-安装NGINX

openwrt-安装NGINX 介绍 OpenWrt 是一个用于嵌入式设备的开源操作系统。它基于 Linux 内核&#xff0c;并且主要被设计用于路由器和网络设备。 OpenWrt 的主要特点包括&#xff1a; 完全可定制&#xff1a;OpenWrt 提供了一个完全可写的文件系统&#xff0c;用户可以自定义设…

目前账号矩阵系统源码有几种框架

目前账号矩阵系统源码主要有三种框架&#xff1a;Spring、Struts和Hibernate。Spring框架是一个全栈式的Java应用程序开发框架&#xff0c;提供了IOC容器、AOP、事务管理等功能。Struts框架是一个MVC架构的Web应用程序框架&#xff0c;用于将数据模型、Web应用程序的用户界面和…

纯css实现手风琴效果

今天在网上看到了一个纯css实现的手风琴&#xff0c;很巧妙 效果如下&#xff1a; 具体代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content…

跨平台应用开发进阶(六十五):小程序分包策略及实战讲解

文章目录 一、前言二、为什么要使用分包&#xff1f;三、分包大小查看四、如何使用分包&#xff1f;五、独立分包六、分包预下载七、项目实战八、拓展阅读 一、前言 微信小程序开发过程中&#xff0c;随着业务不断迭代&#xff0c;程序包的体积越来越大&#xff0c;使用分包加…

HTTP协议报文格式详解和抓包那些事

文章目录 HTTP协议是什么HTTP报文格式抓包工具fiddler HTTP请求请求行HTTP方法URL版本号 请求头HOSTContent-Type&Content-LengthUser-Agent(简称UA)RefererCookie HTTP响应状态行版本号状态码 HTTP协议是什么 HTTP协议全称为超文本传输协议&#xff0c;是一个被广泛使用的…

【dfs之 迭代加深】【dfs层序遍历】【dfs和bfs的缺点结合解决 就是 迭代加深】加成序列

迭代加深 DFS&#xff0c;BFS和迭代加深的联系与区别例题1. 加成序列普通思想&#xff1a;优化方法&#xff1a; DFS&#xff0c;BFS和迭代加深的联系与区别 DFS&#xff1a; DFS算法是沿着搜索树的根节点&#xff0c;一直遍历完该搜索树之后再回溯继续搜索的一种算法。缺点是…

python读取excel数据并用双y轴绘制柱状图和折线图,柱子用渐变颜色填充

python绘图系列文章目录 往期python绘图合集: python绘制简单的折线图 python读取excel中数据并绘制多子图多组图在一张画布上 python绘制带误差棒的柱状图 python绘制多子图并单独显示 python读取excel数据并绘制多y轴图像 python绘制柱状图并美化|不同颜色填充柱子 python随机…