前缀树和贪心算法

目录

1 前缀树

2 贪心算法

2.1 贪心算法在笔试时的解题套路

2.2 会议室宣讲

2.3 金条分割

2.4 获取最大钱数

2.5 N皇后问题


1 前缀树

何为前缀树?

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

如何生成前缀树?

pass:加前缀树时经过该节点的次数

end:这个节点为多少个字符串的终结的点

 代码:

public static class TrieNode{
    public int pass;
    public int end;
    public TrieNode[] nexts; 
    //HashMap<Char, Node> nexts;
    //TreeMap<Char, Node> nexts;
    public TrieNode(){
        pass = 0;
        end = 0;
        //nexts[0] == null 没有走向'a'的路
        //nexts[0] != null 有走向'a'的路
        // ...
        //nexts[25] != null 有走向'z'的路
        next = 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'; //由字符对应成走向哪条路,index为两个字符相减得ASCII值
            if( node.nexts[index] == null){
                node.nexts[index] = new TrieNode();
            }
            node = node.nexts[index];
            node.pass++;
        }
        node.end++;
    }

    public void delete(String word){
        if(search(word) != 0){ //确定树中确实加入过word,才删除
            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.nexts[index].pass == 0){
                    // java可以这么做 c++ 要遍历到底去析构
                    node.nexts[index] = null;
                    // ...
                    return;
                }
                node = node.nexts[index];
            }
            node.end--;
        }
    }

    
    //word 这个单词之前加入过几次
    public int search(String word){
        if(word == null){
            return 0;
        }
        char[] chs = word.toCharArray();
        TrieNode node = root;
        int index = 0;
        for(int i = 0; i < chs.length; i++){
            index = chs[i] - 'a';
            if(node.nexts[index] == null){
                return 0;
            }
            node = node.nexts[index];
        }
        return node.end;
    }

    //所有加入的字符串中,有几个是以pre这个字符串作为前缀的
    public int preFixNumber(String pre){
        if(pre == null){
            return 0;
        }
        char[] chs = pre.toCharArray();
        TrieNode node = root;
        int index = 0;
        for(int i = 0; i < chs.length; i++){
            index = chs[i] - 'a';
            if(node.nexts[index] == null){
                return 0;
            }
            node = node.nexts[index];
        }
        return node.pass;
    }
}
    



例子:

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

2 贪心算法

在某一个标准下,优先考虑最满足标准的样本,最后考虑最不满足标准的样本,最终得到一个答案的算法,叫作贪心算法。

也就是说,不从整体最优上加以考虑,所做出的是在某种意义上的局部最优解。

局部最优 ——?——> 整体最优

2.1 贪心算法在笔试时的解题套路

1. 实现一个不依靠贪心策略的解法X,可以用最暴力的尝试

2. 脑补出贪心策略A、贪心策略B、贪心策略C...

3. 用解法X和对数器,去验证每一个贪心策略,用实验的方式得知哪个贪心策略正确

4. 不要去纠结贪心策略的证明

贪心策略在实现时,经常使用到的技巧:

1. 根据某标准建立一个比较器来排序

2. 根据某标准建立一个比较器来组成堆

2.2 会议室宣讲

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

解题思路:取最早结束的会议进行安排,每次安排完后将时间冲突的会议删去,继续在剩余的会议里选择最早结束的会议继续安排。

public static calss 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>{
    public int compare(Program o1, Program o2){
        return o1.end - o2.end;
    }
}

public static int bestArrange(Program[] programs, int timePoint){
    Arrays.sort(programs, new ProgramComparator());
    int result = 0;
    //从左往右依次遍历所有的会议
    for(int i = 0; i < programs.length; i++){
        if(timePoint <= programs[i].start){
            result++;
            timePoint = program[i].end;
        }
    }
    return result;
}

2.3 金条分割

题目:一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板

一群人想整分整块金条,怎么分最省铜板?

例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60。金条要分成10,20,30三个部分。 如果先把长度60的金条分成10和50,花费60;再把长度50的金条分成20和30,花费50;一共花费110铜板。

但是如果先把长度60的金条分成30和30,花费60;再把长度30金条分成10和20,花费30;一共花费90铜板。

输入一个数组,返回分割的最小代价

解题思路:

①将数组中所有数放入小根堆

②每次弹出两个数做结合

③将结合的数再次放入小根堆里

④回到②直到所有数弹出

public static int lessMoney(int[] arr){
    PriorityQueue<Integer> pQ = new PriorityQueue<>();
    for( int i = 0; i < arr.length; i++)
        pQ.add(arr[i]);
    }
    int sum = 0;
    int cur = 0;
    while (pQ.size() > 1 ){
        cur = pQ.poll() + pQ.poll();
        sum += cur;
        pQ.add(cur);
    }
    return sum;
}

 2.4 获取最大钱数

输入:

正数数组costs

正数数组profits

正数k

正数m

含义:

costs[i]表示i号项目的花费

profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润)

k表示你只能串行的最多做k个小牧

m表示你初始的资金

说明:你每做完一个项目,马上获得的收益,可以支持你去做下一个项目

输出:你最后获得的最大钱数

解题思路:

①准备一个花费小根堆和一个利润大根堆

②将所有能完成项目先放入花费小根堆中

③然后将能够完成的项目逐渐弹出至大根堆

④从大根堆弹出当前资金下利润最大的项目

⑤回到②直到小根堆中没有项目

 

2.5 N皇后问题

N皇后问题是指在N*N的棋盘上要摆N个皇后,要求任何两个皇后不同行不同列,也不在同一条斜线上。

给定一个整数n,返回N皇后的摆法有多少种

n=1,返回1

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

n=8,返回92

解题思路:

①首先建立一个N*N的list(棋盘)

②首先在第一行放置一个皇后

③接着在第二行放置一个皇后,然后依次判断列和的斜线要求是否满足

④依次完成

 代码

public static int num1(int n){
    if( n < 1){
        return 0;
    }
    int[] record = new int[n]; //record[i] -> i行的皇后,放在了第几列  用一个数组代替了list
    return process1(0, record, n);
}

// record[0,...,i-1]的皇后,任何两个皇后都一定不共行、不共列、不共斜线
// 目前来到了第i行
// record[0,...,i-1]表示之前的行放过的皇后位置
// n代表整体一共有多少行
// 返回值是,摆完所有的皇后,合理的摆法有多少种
public static int process1(int i, int[] record, int n){
    if( i == n){ //终止行
        return 1;
    }
    int res = 0;
    for(int j = 0; j < n; j++{
        //当前i行的皇后,放在j列,会不会和之前(0,...,i-1)的皇后,共行共列或者共斜线
        //如果是, 认为有效
        //如果不是 认为无效
        if(isValid(record, i, j)){ // 当前行在i行,尝试i行所有的列 -> j
            record[i] = j;
            res += process1(i + 1, record, n);
        }
    }
    return res;
}


// record[0,...,i-1]你需要看,record[i...]不需要看
// 返回i行皇后,放在了j列,是否有效
public static boolean isValid(int[] record, int i, int j){
    for(int k = 0; k < i; k++){ //之前的某个k行皇后
        if( j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)){
            return false;
        }
    }
    return true;
}
            

常数优化版本(用位运算去加速计算)

// 请不要超过32皇后问题
public static int num2(int n){
    if(n < 1 || n > 32){
        return 0;
    }
    int limit = n == 32 ? -1 : (1 << n) - 1;
    return process2(limit, 0, 0, 0);
}

// colLim 列的限制,1的位置不能放皇后,0的位置可以
// leftDiaLim 左斜线的显示, 1的位置不能放皇后,0的位置可以
// rightDiaLim 右斜线的显示, 1的位置不能放皇后,0的位置可以
public static int process2(int limit, int colLim, int leftDiaLim, int rightDiaLim){
    if(colLim == limit){ //说明该行上所有都填满皇后
        return 1;
    }
    int pos = 0;
    int mostRightOne = 0;

    //所有候选皇后的位置
    pos = limit & (~(colLim | leftDiaLim | rightDiaLim));

    int res = 0;

    while(pos != 0){
        mostRightOne = pos & (~pos + 1); // 提取最右侧的1
        pos = pos - mostRightOne;  // 减去最右侧1后的用于下一个循环的pos
        res += process2(limit, colLim | mostRightOne, 
                        (leftDiaLim | mostRightOne) << 1,
                        (rightDiaLim | mostRightOne) >> 1);
    }
    return res;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KevinJune

希望我的内容对你们有所帮助

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值