Violent recursion: the prototype of dynamic programming

1. The basic concept of violent recursion:

  1. What is violent recursion? In short, violent recursion is trying, and at the same time, violent recursion is the predecessor of dynamic programming, in other words: dynamic programming is the optimization of violent recursion.

  1. Basic steps on solving violent recursion:

  1. Transform the problem into subproblems of the same kind reduced in size

  1. There are clear conditions that do not need to continue recursion (base case)

  1. There is a decision-making process after the results of the sub-problems are obtained

  1. does not record the solution of each subproblem

2. Combining examples to effectively understand the process of violent recursion:

2.1 Print all subsequences (subsets) of a string, including empty strings

Title description:
Given a string, list all subsequences of this string: You need to pay attention to the difference between a subsequence and a substring: A substring is a sequence of consecutive characters in a string, and the emphasis is on continuity. For example, "1AB2345CD", then "1AB23", "5CD" are the corresponding substrings, but "12345CD" is not, it is no longer in a continuous state, a subsequence of the string is the original string to delete some (or not to delete ) characters without changing the relative position of the remaining characters to form a new string, then obviously, the biggest difference between a subsequence and a substring is that it can be discontinuous. For example, "1AB2345CD", "12345CD" is a subsequence of it.

Let's describe our idea of ​​solving the problem: According to the idea of ​​violent recursion we summarized above, we solve this problem in a personalized way: for each character of the string, there are strings and no strings There are two options , if characters are added to the string, the next recursive string will be modified (plus the current character) , otherwise the original string will remain unchanged and enter the next recursion until the last element is reached, and the recursion ends.

code show as below:

public class Subsequence {
    public static void main(String[] args) {
        String s="abcde";
        System.out.println(allSubsequence(s));
    }
    public static List<String> allSubsequence(String target){
        //将字符串转化为字符数组(便于遍历)
        char[]chars=target.toCharArray();
        //创建结果集
        LinkedList<String> end = new LinkedList<>();
        int index=0;//定义遍历位置
        process(chars,end,index,"");
        return end;
    }

    private static void process(char[] chars, LinkedList<String> end, int index,String path) {
        //确认终止条件
        if(index==chars.length){
            //将结果加入到结果集
            end.add(path);
            return ;
        }
        //对于当前结点有两种处理策略:1.当前结点不加入结果集2.当前结点加入结果集
        //当前结果不加入结果集
        String no=path;
        //进入下一层递归
        process(chars, end, index+1, no);
        //当前结果加入结果集
        String yes=path+String.valueOf(chars[index]);
        //进入下一层递归
        process(chars, end, index+1, yes);
    }
}

Let's think about another question on this basis: What if each character in the string is required not to be repeated?

The answer is relatively simple. Just adjust the container used to collect the results from a queue to a hashmap. The code is as follows:

public class SubsequenceForNoRepeat {
    public static void main(String[] args) {
        String s="aaaaa";
        System.out.println(allSubsequence(s));
    }

    public static List<String> allSubsequence(String target){
        //将字符串转化为字符数组(便于遍历)
        char[]chars=target.toCharArray();
        //创建结果集
        HashSet<String> end = new HashSet<>();
        LinkedList<String> list = new LinkedList<>();
        int index=0;//定义遍历位置
        process(chars,end,index,"");
        //将hashset遍历进list
        for (String individual:end) {
            list.add(individual);
        }
        return list;
    }

    private static void process(char[] chars, HashSet<String> end, int index, String path) {
        //确认终止条件
        if(index==chars.length){
            //将结果加入到结果集
            end.add(path);
            return ;
        }
        //对于当前结点有两种处理策略:1.当前结点不加入结果集2.当前结点加入结果集
        String no=path;
        process(chars, end, index+1, no);
        String yes=path+String.valueOf(chars[index]);
        process(chars, end, index+1, yes);
    }
}

2.2 Print all permutations of a string

Problem description: Enter a string without repeating characters, and print out all the permutations of the characters in the string. For example, input the string abc, then print out all the strings abc, acb, bac, bca, cab, and cba that can be arranged by the characters a, b, and c.

Problem-solving ideas: Convert a string into a character array. For a character at a position, it can be exchanged with the element behind it (using a loop). After the exchange, recurse and enter the arrangement of the next array element. The exit of the recursion is When the last element of the character array is arranged, it should be noted that: since we operate on the original data (the original character array) during the arrangement, it means that when we jump out of When layer elements, we need to restore the original data

code show as below:

package violencerecursion;

import java.util.LinkedList;
import java.util.List;

/**
 * @author tongchen
 * @create 2023-03-15 17:17
 */
public class FullArrangement {
    public static void main(String[] args) {
        String s="abc";
        System.out.println(arrange(s));
    }
    public static List<String> arrange(String s){
        //将字符串转化为字符数组
        char []chars=s.toCharArray();
        //创建结果集
        LinkedList<String> list = new LinkedList<>();
        //递归处理
        process(chars,0,list);
        return list;
    }

    private static void process(char[] chars, int i, LinkedList<String> strings) {
        //递归的出口
        if(i== chars.length){
            strings.add(String.valueOf(chars));
            return ;
        }
        //循环递归(思路:从当前结点开始往下的下标都能进行全排列,之前的结点都已经排列好了)
        for(int j=i;j< chars.length;++j){
            //交换其中两个
            swap(chars,i,j);
            //从此往下继续递归
            process(chars, i+1, strings);
            //恢复
            swap(chars, i,j );
        }
    }

    private static void swap(char[] chars, int i, int j) {
        char temp=chars[i];
        chars[i]=chars[j];
        chars[j]=temp;
    }
}

In the same way, we add further conditional restrictions to this problem: repeated elements are not allowed in the final output result: there are two ways to solve the problem here: ① use hashmap to wait until the final data is added to the result set to determine whether the current result set contains this element, the code is as follows:

package violencerecursion;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

/**
 * @author tongchen
 * @create 2023-03-15 23:01
 */
public class FullArrangementNoRepeat {
    //思路:第一种思路很简单,把存放结果的容器由list转化为set,解决重复问题,但这种效率相对较低
    public static void main(String[] args) {
        String s="aaa";
        System.out.println(arrange(s));
    }
    public static List<String> arrange(String s){
        //将字符串转化为字符数组
        char []chars=s.toCharArray();
        //创建map
        HashSet<String> set = new HashSet<>();
        //创建结果集
        LinkedList<String> list = new LinkedList<>();
        //递归处理
        process(chars,0,set);
        //最后将结果复制到list
        for (String s1: set) {
            list.add(s1);
        }
        return list;
    }

    private static void process(char[] chars, int i, HashSet<String> strings) {
        //递归的出口
        if(i== chars.length){
            strings.add(String.valueOf(chars));
            return ;
        }
        //循环递归(思路:从当前结点开始往下的下标都能进行全排列,之前的结点都已经排列好了)
        for(int j=i;j< chars.length;++j){
            //交换其中两个
            swap(chars,i,j);
            //从此往下继续递归
            process(chars, i+1, strings);
            //恢复
            swap(chars, i,j );
        }
    }

    private static void swap(char[] chars, int i, int j) {
        char temp=chars[i];
        chars[i]=chars[j];
        chars[j]=temp;
    }
}

②By using branch and bound, it is necessary to judge whether there is such a situation in the result when each element is added. Once it exists, directly abandon this recursion and join the next cycle

package violencerecursion;

import java.util.LinkedList;
import java.util.List;

/**
 * @author tongchen
 * @create 2023-03-15 23:12
 */
public class FullArrangementBranchGauge {
    public static void main(String[] args) {
        String s="aaa";
        System.out.println(fullArrangement(s));
    }
    public static List<String> fullArrangement(String s){
        //创建新list
        LinkedList<String> list = new LinkedList<>();
        //将数组转化为字符串
        char[] chars = s.toCharArray();
        //从开头的位置不断向后递归
        process(chars,0,list);
        return list;
    }

    private static void process(char[] chars, int i, LinkedList<String> list) {
        //设置终止条件
        if(i== chars.length){
            //将排列的结果加入字符数组
            list.add(String.valueOf(chars));
        }
        //创建一个负责检查当前位置是否重复的哈希表,如果当前位置重复,直接不走当前路径
        boolean [] checks=new boolean[26];
        //从当前位置开始与往后的字符交换生成结果
        for (int j = i; j <chars.length ; j++) {
            if(!checks[chars[j]-'a']){
                checks[chars[j]-'a']=true;
                swap(chars, i,j );
                //进入下一个位置
                process(chars, i+1, list);
                //恢复
                swap(chars,j,i);
            }
            //直接跳过
        }
    }
    private static void swap(char[] chars, int i, int j) {
        char temp=chars[i];
        chars[i]=chars[j];
        chars[j]=temp;
    }
}

Let's analyze these two different ways of solving the problem: one is to deduplicate the final result, and the other is to check every element, and exclude this situation once duplicates are found. , obviously the latter is more efficient.

2.3 Given an integer array arr, cards with different values ​​are arranged in a line.

Leetcode connection: https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/

Problem-solving ideas: For the number 1, we can divide it into two cases: ①The current number is directly converted into a letter; Directly converted into letters ② If the following number is 0-5, it can be converted into a letter together with the following number; if the number is 3, it can only be converted into a letter by itself. There is only one case, if it is 0, it can only be combined with the following elements There is only one case for one letter. The recursive exit is when the last element is exceeded, and the current number is solved to recurse to the next number

code show as below:

package violencerecursion;

/**
 * @author tongchen
 * @create 2023-03-15 23:40
 */
public class NumConvert {
    public static void main(String[] args) {
        String s="12345";
        System.out.println(convert(s));

    }
    //创建转化函数
    public static int convert(String s){
        //将字符串转化为字符数组
        char[] chars = s.toCharArray();
        //进行递归
      return  process(chars,0);
    }

    private static int process(char[] chars, int i) {
        //检查递归的出口
        if(i== chars.length){
            return 1;
        }
        //如果是1和2,可能存在两种情况
        if(chars[i]=='1'){
            int res=process(chars, i+1);
            //判断第二种情况,结果直接在1的基础上进行相加即可
            if((i+1)< chars.length){
                res+=process(chars, i+2);
            }
            return res;
        }
        //如果是2,也要进行分类讨论
       else if(chars[i]=='2'){
            int res=process(chars, i+1);
            if((i+1)< chars.length&&chars[i+1]<='5'){
                res+=process(chars, i+2);
            }
            //注意要将结果直接返回,不继续向下遍历了
            return res;
        }
        //如果是3-9,就只有一种情况了
        else{
            return process(chars, i+1);
        }
    }
}

2.4 Knapsack problem:

Given two arrays weights and values, both of length N, weights[i] and values[i] represent the weight and value of item i, respectively. Given a positive number bag, which represents a bag with a load, the items you can hold cannot exceed this weight. Return What is the most value you can hold?

Problem-solving ideas: For the knapsack problem, our solution is as follows: For each item with a serial number, it is marked with a certain weight. We mark the elements already in the existing knapsack. When the weight of the current knapsack is greater than the maximum capacity of the knapsack Or when we reach the last element, the recursion ends. Similarly, for each item that exists, there are two options of putting it in the backpack or not putting it in the backpack. Before each recursive backtracking, it will return to the current different options and go to the backpack. The maximum storage capacity.

package violencerecursion;

/**
 * @author tongchen
 * @create 2023-03-16 23:52
 */
public class Bag {
    public static void main(String[] args) {
        int[]w={3,4,5};
        int[]v={6,4,8};
        System.out.println(bestBag(w, v, 10));
    }
    public static int bestBag(int []w,int []v,int weight){
        //检查有效性
        if(w.length==1&&w[0]>weight){
            return 0;
        }
       return process(w,v,0,0,weight);
    }
    //递归过程
    private static int process(int[] w, int[] v, int index, int alWeight, int weight) {
        //判断出口
        if(alWeight>weight){
            return -1;
        }
        if(index==w.length){
            return 0;//成功
        }
        //第一种情况:当前节点上不放值
        int value1=process(w, v, index+1, alWeight, weight);
        //第二种情况:当前结点放入值
        int value2Next=process(w, v, index+1, alWeight+w[index], weight);
        int value2=-1;
        if(value2Next!=-1){
            value2=value2Next+v[index];
        }
        //返回两种结果的最大值
        return Math.max(value1, value2);
    }
}

Guess you like

Origin blog.csdn.net/m0_65431718/article/details/129604874