贪心算法

使用递归套路写贪心对数器

贪心算法

贪心算法就是一种你在某个情况下选择一种当时来说最好的标准来完成整个事情(局部最优,全局来看未必最优),不过如果你的贪心并没有得到全局的最优解的时候就说你这个贪心算法是错误的。

我个人理解,需要使用贪心的时候就是那种给你很多种可能性,然后让你返回一个最好的情况的题目。

面试题

给定一个由字符串组成的数组strs,
必须把所有的字符串拼接起来,
返回所有可能的拼接结果中,字典序最小的结果

使用递归套路写贪心对数器

将某一个时刻的所有情况看成一个r叉树,向每种情况下的子树要信息,然后和自己的信息做整合返回。不一定是要返回信息,可以将一个容器传给子树,然后它将容器填好返回给你也算是拿到信息了。

递归套路好处,只用考虑base case和一般情况,至于怎么递归的,我们不关心,站着某个中间时刻,设能够拿到所有情况下的信息,然后我整合返回就可以了。

就是数学中的不完全归纳法的意思,现在n=1的情况也就是base case处理是正确的,也就是n=1的时候符合要求,命题成立。假设n=m的时候,符合要求,也就是向父树提供了数据,而且是正确的。是否能够推出n=m+1的情况,我还是能够推出符合要求的数据呢?答案是肯定的,只要我整合n=m情况下的数据再结合我自身的数据就能够推出。所以,这种方法是正确的。这个强大的功能依赖于递归序,二叉树时能够来到这个方法三次,r叉树就能回来r+1次,那么我就能够整合子树的数据向上层提交。

注意暴力解的时候向递归里丢的容器是用的同一个还是每次都使用新的,如果是用的同一个,那么回来要恢复现场,因为控制变量法嘛,要除了决策以外其他条件完全一模一样,就像是你现在在减肥,你想要测量每种食物的热量。在理想情况下,你记住了你现在身体的数据,你先吃了一个馒头,然后测量出来了一个热量数据。然后该测鸡蛋的热量了,那么你得返回刚才还没吃的状态下,就是恢复身体数据,然后再吃鸡蛋再测量。如果现在问题是想知道什么食物的热量最少,那么就是你反复的恢复身体数据,然后吃一种得到一个热量数据,然后Math.min(min,新数据)一下就可以了。

java解决题目

方法1是暴力解,方法2是贪心算法

/**
 * author waigo
 * create 2021-02-24 13:20
 */

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;

/**
 * 面试题:
 * 给定一个由字符串组成的数组strs,
 * 必须把所有的字符串拼接起来,
 * 返回所有可能的拼接结果中,字典序最小的结果
 */
public class Code01_LowestDictionaryOrderMerge {
    //对数器:暴力递归
    public static String getLowestMerge1(String[] strs) {
        if (strs == null || strs.length == 0) return null;
        ArrayList resultList = new ArrayList<>();
        //使用List的时候需要注意,千万不要在一个迭代中使用remove,remove后数组会进行一个压缩,即使是
        //你后来又add回去了,元素位置乱了就出问题了
        ArrayList useSet = new ArrayList<>();
        process("", strs, resultList, useSet);
        try{
            String lowest = resultList.get(0);
            for (int i = 1; i < resultList.size(); i++)
                if (resultList.get(i).compareTo(lowest) < 0) lowest = resultList.get(i);
            return lowest;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param s          当前拼接到的字符串
     * @param useSet    拼接过的字符串
     * @param resultList 已经尝试过的序列集合
     */
    private static void process(String s, String[] strs, ArrayList resultList, ArrayList useSet) {
        if (useSet.size() >= strs.length) {
            resultList.add(s);//这里存储已经使用过的字符串不能存储值,不然出现相同字符串的时候,比如
            //"k","k","k",一上来选了k,然后发现有了,就无法再次选下一个K,导致字符串数量无法到达strs.length
            return;
        }
        for (int i = 0;i < strs.length;i++) {
            if (!useSet.contains(i)) {
                useSet.add(i);//选了str,存它下标
                process(s + strs[i], strs, resultList, useSet);
                useSet.remove((Integer)i);//恢复现场,不选str,去选其他的
            }
        }
    }

    //贪心算法
    public static String getLowestMerge2(String[] strs) {
        if (strs == null || strs.length == 0) return null;
        Arrays.sort(strs, (o1, o2) -> (o1+o2).compareTo(o2+o1));
        StringBuilder result = new StringBuilder();
        for (String str : strs) {
            result.append(str);
        }

        return result.toString();
    }
    public static String[] generalStringAry(int maxSize, int maxStrLen) {
        if (maxSize < 0) return null;
        int size = (int) (Math.random() * (maxSize + 1));//0~maxSize
        String[] ary = new String[size];
        for (int i = 0; i < size; i++) {
            ary[i] = generalRandomString(maxStrLen);
        }
        return ary;
    }

    public static String generalRandomString(int maxStrLen) {
        char[] strChars = new char[(int) (Math.random() * maxStrLen)+1];//1~maxStrLen-1
        for (int i = 0; i < strChars.length; i++) {
            strChars[i] = (char) ((int) (Math.random() * 26) + 'a');
        }
        return String.valueOf(strChars);
    }

    public static void main(String[] args) {
        int maxSize = 6, maxStrLen = 3, testTimes = 1000000;
        int i = 0;
        String lowestMerge2="";
        String lowestMerge1="";
        String[] strs = null;
        for (; i < testTimes; i++) {
            strs = generalStringAry(maxSize, maxStrLen);
            lowestMerge2 = getLowestMerge2(strs);
            lowestMerge1 = getLowestMerge1(strs);
            if (!Objects.equals(lowestMerge2, lowestMerge1)) break;
        }
        System.out.println(i == testTimes ? "finish!!!" : "oops!!!");

    }
}

如何判断一个贪心策略是否正确呢?

这个是一个无法用正向思维来搞定的,不切实际,因为你很难论证,而且没有方法,每次都得根据业务需求重新证明,非常麻烦,所以这个时候就可以考虑暴力对数器了。我把业务下的最优情况搞出来,然后不断测试我的贪心策略,如果1000000次都能相同,那有理由相信正确了。

像这道题,我直接将所有可能性给罗列出来,然后找到了最好的情况,非常暴力。然后怎么贪心呢?我们看看题目,要求的是所有字符串合并后字典序最小的情况,那么我们先来考虑,字典序怎么比较的?

a

这个时候你将你这个想法实现,然后跑一边对数器,发现错误了。看看啥例子没跑过,发现出现类似这样的东西["nb","n","a"],按照我们之前的想法返回的是"annb",你会发现"anbn"会更优,问题出现在哪呢?

就是两个字符串可能按照字典序排好的情况下不一定能够拿到最优,问题是一个长一个短,而长的那个后面的是一串比较小的字符串。那我直接判断的时候,看两个字符串到底谁在前合并起来要小行不行?然后bla...bla就实现了。

就是这么个套路,写对数器,然后将你野马般的思绪付诸实践,慢慢把最优解试出来。

你可能感兴趣的:(贪心算法)