每日算法总结——前缀树、对数器的补充、贪心算法题目:最多可以参加的会议数量、最小字典序

因为甲流耽搁了几天,今天继续记录。

一、前缀树

  • 什么是前缀树?

    Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。
    典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
    它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较。

  • 前缀树的3个基本性质

    1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
    2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
    3. 每个节点的所有子节点包含的字符都不相同。

【例题】一个字符串类型的数组arr1,另一个字符串类型的数组arr2。arr2中有哪些字符,是arr1中出现的?请打印。arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印。arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印arr2中出现次数最大的前缀。

public class TrieTree {
    /**
     * 前缀树节点
     */
    public static class TrieNode {
        // 单词经过该节点的次数,以该节点之前字符为前缀的字符数量
        public int pass;
        // 以该节点为结尾的单词个数
        public int end;
        // 当字符种类多于26个字母 HashMap<char, TrieNode>或TreeMap
        public TrieNode[] nexsts;

        public TrieNode() {
            pass = 0;
            end = 0;
            /*
                nexts[0] == null 没有走向‘a’的路
                nexts[0] != null 有走向‘a’的路
                ···
                nexts[25] != null 有走向‘z’的路
             */
            nexsts = new TrieNode[26];
        }
    }

    /**
     * 前缀树类
     */
    public static class Trie {
        private TrieNode root;

        public Trie() {
            root = new TrieNode();
        }

        /**
         * 添加单词
         */
        public void insert(String word) {
            if (word == null) {
                return;
            }
            char[] chs = word.toCharArray();
            // 从根节点出发
            TrieNode node = root;
            node.pass++;
            int index = 0;
            for (int i = 0; i < chs.length; i++) {
                index = chs[i] - 'a';
                if (node.nexsts[index] == null) {
                    // ‘a’字符位置为null,说明还没有前缀为a的路,需要新建出来
                    node.nexsts[index] = new TrieNode();
                }
                node = node.nexsts[index];
                node.pass++;
            }
            node.end++;
        }

        /**
         * 查询word这个单词之前加入过几次
         * 就是每个节点end的含义
         */
        public int search(String word) {
            if (word == null) {
                return 0;
            }
            char[] chars = word.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (char curChar : chars) {
                index = curChar - 'a';
                if (node.nexsts[index] == null) {
                    return 0;
                }
                node = node.nexsts[index];
            }
            return node.end;
        }

        /**
         * 所有加入的字符串中,有几个是以pre这个字符串作为前缀的
         * 就是每个节点pass的含义
         */
        public int prefixNumber(String pre) {
            if (pre == null) {
                return 0;
            }
            char[] chars = pre.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (char curChar : chars) {
                index = curChar - 'a';
                if (node.nexsts[index] == null) {
                    return 0;
                }
                node = node.nexsts[index];
            }
            return node.pass;
        }

        /**
         * 删除单词word
         * 需要对沿途节点的pass修改,同时修改终点的end
         */
        public void delete(String word) {
            // 先进行判断当前树中是否有word
            if (search(word) != 0) {
                char[] chars = word.toCharArray();
                TrieNode node = root;
                int index = 0;
                for (char curChar : chars) {
                    index = curChar - 'a';
                    if (--node.nexsts[index].pass == 0) {
                        // java可以这样释放空间,但C++需要遍历到底手动释放
                        node.nexsts[index] = null;
                        return;
                    }
                    node = node.nexsts[index];
                }
                node.end--;
            }
        }
    }
}

二、贪心算法

在某一个标准下,优先考虑最满足标准的样本,最后考虑最不满足标准的样本,最终得到一个答案的算法,叫作贪心算法。
也就是说,不从整体最优上加以考虑,所做出的是在某种意义上的局部最优解

  • 关键是判断为 “” 的标准

  • 左神说,目前贪心算法的题无法准确判断出哪个是最优标准,只能利用对数器一个一个试,好在贪心算法的代码很简洁,也很好写,明确了判定标准后,可以很快写出。

    • 不过想我这种菜鸡🐔连一个标准都可能想不出来 _(:3」∠)_
贪心算法的在笔试时的解题套路
  1. 实现一个不依靠贪心策略的解法X,可以用最暴力的尝试
  2. 脑补出贪心策略A、贪心策略B、贪心策略C…
  3. 用解法X和对数器,去验证每一个贪心策略,用实验的方式得知哪个贪心策略正确
  4. 不要去纠结贪心策略的证明
对数器的补充

对数器的概念和使用

  1. 有一个你想要测的方法a
  2. 实现复杂度不好但是容易实现的方法b
  3. 实现一个随机样本产生器
  4. 把方法a和方法b跑相同的随机样本,看看得到的结果是否一样。
  5. 如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a或者方法b
  6. 当样本数量很多时比对测试依然正确,可以确定方法a已经正确。

下面题目二有对数器案例

题目一:最多可以参加的会议数量

一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。
给你每一个项目开始的时间和结束的时间[startTime, endTime](给你一个数组,里面是一个个具体的项目),你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。返回这个最多的宣讲场次。

  • 解题思路:首先要选择先安排的会议(最优的会议),什么样的会议优先安排?有多个标准:开始时间最早的会议会议持续时间最短的会议结束时间最短的会议······等等。
    这道题应该选择的标准为:结束时间最短的会议。如何证明?利用数学方法。
public class MeetingArrange {
    public static class Program {
        public int start;
        public int end;

        public Program(int start, int end) {
            this.start = start;
            this.end = end;
        }
    }

    public static class ProgramComparator implements Comparator<Program> {

        @Override
        public int compare(Program o1, Program o2) {
            return o1.end - o2.end;
        }
    }

    /**
     * 会议安排
     * @param programs 所有的会议
     * @param timePoint 开始的时间
     * @return 所能安排的会议的最大数量
     */
    public static int bestArrange(Program[] programs, int timePoint) {
        Arrays.sort(programs, new ProgramComparator());
        int result = 0;
        // 从左往右依次遍历所有的会议
        for (Program program : programs) {
            if (timePoint <= program.start) {
                result++;
                timePoint = program.end;
            }
        }
        return result;
    }
}
题目二:最小字典序

给定一个字符串数组S,返回一个由S中所有字符串拼接而成的字符串,要求拼接后的字符串字典序最小。

解题思路

  • 贪心策略的选择,主要就是决定数组中那个字符串放在第一位、第二位、…、第N位,从而使得整个数组拼接起来是字典序最小的,其实就是一个字符串排序问题,排序策略就是贪心策略。

  • 排序策略其实也很好想,比如字典序小的排前面(显然不行,可以举出反例:bbba

  • 本题的排序策略应该是:(a+b)>(b+a)?a放前:b放前,主要利用了拼接的传递性:由a+b>b+ab+c>c+b推出a+c>c+a,具体证明比较麻烦,但这就是正确答案(管你看没看懂,写就完了🤯)

public class LowestLexicography {
    /**
     * 比较策略
     */
    public static class MyComparator implements Comparator<String> {
        @Override
        public int compare(String a, String b) {
            return (a + b).compareTo(b + a);
        }
    }

    public static String lowestString(String[] strs) {
        if (strs == null || strs.length == 0) {
            return "";
        }
        Arrays.sort(strs, new MyComparator());
        String res = "";
        for (String str : strs) {
            res += str;
        }
        return res;
    }
}

对数器验证:

/**
 * 枚举方法求最小字典序
 */
public static String enumString(String[] strs) {
    ArrayList<String> list = new ArrayList<>(Arrays.asList(strs));
    return process(list);
}

public static String process(ArrayList<String> list) {
    if (list.size() == 0) {
        return "";
    }
    String min = "~";
    for (int i = 0; i < list.size(); i++) {
        String temp = list.remove(i);
        String s = temp + process(list);
        min = min.compareTo(s) < 0 ? min : s;
        list.add(i, temp);
    }
    return min;
}

// 对数器测试
public static void main(String[] args) {
    int testTime = 1000;
    int maxSize = 10;
    int maxValue = 10;
    boolean successed = true;
    for (int i = 0; i < testTime; i++) {
        String[] arr1 = generateRandomArray(maxSize, maxValue);
        String[] arr2 = Arrays.copyOf(arr1, arr1.length);
        String s1 = enumString(arr1);
        String s2 = lowestString(arr2);
        if (!s1.equals(s2)) {
            System.out.println(s1);
            System.out.println(s2);
            successed = false;
            break;
        }
    }
    System.out.println(successed?"nice!":"Fucking fucked!");
    // System.out.println(enumString(new String[]{"b", "bba"}));
}

/**
 * 生成随机数组
 */
public static String[] generateRandomArray(int maxSize, int maxLength) {
    // 长度随机
    String[] arr = new String[(int) (Math.random() * (maxSize + 1))];
    for (int i = 0; i < arr.length; i++) {
        // 值随机
        arr[i] = "";
        for (int j = 0; j < (int) (maxLength * Math.random()) + 1; j++) {
            arr[i] += (char) (('z' - 'a' + 1) * Math.random() + 'a');
        }
    }
    return arr;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值