首页 > 编程语言 >【算法】007_前缀树和贪心算法

【算法】007_前缀树和贪心算法

时间:2024-02-18 13:11:06浏览次数:34  
标签:node index 结点 return int 算法 007 public 贪心

1. 前缀树

一个字符串类型的数组arr1,另一个字符串类型的数组arr2.arr2中有哪些字符,是arr1中出现的?arr2中有哪些字符是作为arr1中某个字符串前缀出现的?arr2中有哪些字符是作为arr1中某个字符串前缀出现的?

image-20240216122039031

  • pass:表示当前这个结点被经过的次数

  • end:这个结点作为结尾的次数

  • 例如将abc转换为前缀树:

    • 初始p=e=0,当要插入a的时候p=1,表示到达a要经过根结点

      image-20240216122252953

    • 插入a,a的p++

      image-20240216122518163

    • 插入b,b的p++

      image-20240216122614125

    • 插入c

      image-20240216122629657

    • c是最后一个点,修改c的e=1

      image-20240216122812252

  • 插入ab

    • 来到头结点,p变成2

    • 有走向a的路,所以a的p=2

    • 有走向b的路,所以b的p=2

    • b就结尾了,所以b结点的e=1

      image-20240216123014422

  • 当查找ab字符串时

    • 有走向a的路
    • 有走向b的路
    • b的end=1,说明b已经结束了,找到了ab
    • 如果b的end=0,说明没有以b作为结尾的字符串,找不到ab
public class Code01_TrieTree {

    public class TrieNode{
        public int pass;//经过了这个结点pass+1
        public int end;//以这个结点结尾end+1
        public TrieNode[] nexts;

        public TrieNode() {
            pass = 0;
            end = 0;
            //nexts[0] == null   没有走向a的路
            //nexts[0] != null   有走向a的路
            nexts = new TrieNode[26];
        }
    }

    public 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从根结点开始
             node.pass++;//经过了根结点
             int index = 0;
             for (int i = 0; i < chs.length; i++) {//从左往右遍历字符
                 index = chs[i] - 'a';//由字符对应走向哪条路
                 if (node.nexts[index] == null){//当前的树上没有这个结点
                     node.nexts[index] = new TrieNode();//新建
                 }
                 node = node.nexts[index];//有这个结点的话就直接到这个结点
                 node.pass++;//经过这个结点
             }
             node.end++;//结尾
         }

         public void delete(String word){
             //确定树中确实加入过word,才删除
             if (search(word) == 0){
                 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';
                 // node = node.nexts[index];
                 // node.pass--;
                 // //删除了字符之后node的pass为0,说明没有经过node的路径,就该把node从前缀树中删除。之前插入的时候,有经过结点的路径,才会创建结点
                 // if (node.pass == 0){
                 //     node = null;
                 //     return;
                 // }
                 index = chs[i] - 'a';
                 if (--node.nexts[index].pass == 0){
                     node.nexts[index] = null;//这里为null了之后,node之后的结点也会被释放,即使后面还有p!=0的结点,只是还没循环到,循环到了之后p依然会变成0,可以自己画图理解
                     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';
                 //word的字符还没有比较完,但是前缀树已经没有路了,说明word没有被加入过前缀树
                 if (node.nexts[index] == null){
                     return 0;
                 }
                 node = node.nexts[index];
             }
             return node.end;//node被作为结尾的次数,就说明这个字符串有几个
         }

        //所有加入的字符串中,有几个是以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';
                //pre中的字符,没有出现在路径中
                if (node.nexts[index] == null){
                    return 0;
                }
                node = node.nexts[index];
            }
            return node.pass;//node被路过的次数
        }
    }
}

2. 贪心算法

题目一

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

  • 按照结束时间从早到晚进行排序

  • 按排序好的顺序进行遍历

  • 如果遍历到的会议开始时间没有早于当前时间点,就可以安排这个会议

    • 第一个被安排的会议是①,①最早结束

    • 第二个被安排的会议是⑤,因为①结束之后,⑤是最早开始的

    • 第三个被安排的会议是Ⅵ,因为⑤结束之后,⑥是最早开始的

image-20240216151453713

题目二

给定一个字符串类型的数组strs,找到一种拼接方式,使得把所有字符串拼起来之后形成的字符串具有最小的字典序。

  • 通常想到的思路是,将字符串找到字典序排序,但是如果遇到ba和b:

    按字典序排序:b,ba,拼接之后就会得到bba。但正确答案应该是bab。所以这种方式不对

  • 正确的是,两两字符串拼接之后比较字典序

    证明听不懂,算了,不为难自己

public class Code03_LowestLexicography {
    public class MyComparator implements Comparator<String>{
        @Override
        public int compare(String o1, String o2) {
            return (o1 + o2).compareTo(o2 + o1);
        }
    }

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

题目三

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

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

image-20240218110145177

  • 60先分成30和30,花费60
  • 再把30分成10和20,花费30
  • 哈夫曼编码,从底部向上
public class Code04_LessMoneySplitGold {
    public static void main(String[] args) {
        int[] arr = {2,3,4,7,9,2};
        Code04_LessMoneySplitGold mon = new Code04_LessMoneySplitGold();
        System.out.println(mon.lessMoney(arr));
    }

    public int lessMoney(int[] arr){
        PriorityQueue<Integer> queue = new PriorityQueue<>(new MinHeapComparator());
        for (int i = 0; i < arr.length; i++) {
            queue.add(arr[i]);
        }
        int sum = 0;
        int cur = 0;
        while (queue.size() > 1){
            cur = queue.poll() + queue.poll();
            sum += cur;//加的是中间结点,没有加叶子结点
            queue.add(cur);
        }
        return sum;
    }

    public class MinHeapComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1- o2;
        }
    }
}

题目四

输入:

正数数组costs,正数数组profits,正数k,正数m

含义:

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

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

k表示你只能串行的最多做k个项目

m表示你初始的资金

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

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

假设m=1,k=4

image-20240218111345345

由于初始资金为1,所以只能先做第二个项目。做完之后资金变为3。

做第一个项目,资金变为4

做第三个项目,资金变为7

之后的项目都做不了,资金不够。

  • 小根堆,按照花费从小到大进行排序

  • 将花费小于等于资金的项目弹出,进入大根堆

  • 从大根堆中弹出一个项目,这就是要做的项目

  • 更新资金,继续将小根堆中花费小于等于资金的项目弹出,进入大根堆

  • 这样操作的结果就是,每次做的项目都是花费小于等于资金的所有项目中选择一个利润最高的项目

public class Code05_IPO {
    public class Node{
        public int profit;
        public int cost;
        public Node(int profit, int cost){
            this.profit = profit;
            this.cost = cost;
        }
    }

    public class MinCostComparator implements Comparator<Node>{
        @Override
        public int compare(Node o1, Node o2) {
            return o1.cost - o2.cost;
        }
    }
    public class MaxProfitComparator implements Comparator<Node>{
        @Override
        public int compare(Node o1, Node o2) {
            return o2.profit - o1.profit;
        }
    }

    public int findMaximizedCapital(int k, int m, int[] Profits, int[] Costs){
        PriorityQueue<Node> minQueue = new PriorityQueue<>(new MinCostComparator());
        PriorityQueue<Node> maxQueue = new PriorityQueue<>(new MaxProfitComparator());

        //按照花费从小到大进入小根堆
        for (int i = 0; i < Profits.length; i++) {
            minQueue.add(new Node(Profits[i], Costs[i])); 
        }

        //最多做k个项目
        for (int i = 0; i < k; i++) {
            //将花费小于等于资金的项目弹出到大根堆中
            while (!minQueue.isEmpty() && minQueue.peek().profit <= m){
                maxQueue.add(minQueue.poll());
            }
            //大根堆为空,说明当前资金已经不够做项目了
            if (maxQueue.isEmpty()){
                return m;
            }
            m += maxQueue.poll().profit;
        }
        return m;
    }
}

题目五

一个数据流中,随时可以取得中位数

  • 一个大根堆和一个小根堆
  • 第一个数字进入大根堆
  • 后来的数字,判断是否小于等于大根堆的堆顶
    • 是,则进入大根堆
    • 否,则进入小根堆
    • 小根堆中的元素总是大于大根堆中的元素,于是两个堆顶的元素就是大小相邻的元素
  • 比较大根堆和小根堆的大小,如果差值大于1,就将多的堆弹出一个元素到少的堆
  • 最后,最小的一半数在大根堆中,最大的一半数在小根堆中。
public class Code06_MedianQuick {

    public static class MedianHolder{
        private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new MaxHeapComparator());
        private PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>(new MinHeapComparator());

        public Integer getMedian(){
            int maxHeapSize = this.maxHeap.size();
            int minHeapSize = this.minHeap.size();

            Integer maxHeapHead = this.maxHeap.peek();
            Integer minHeapHead = this.minHeap.peek();

            //数字一共是偶数个,中位数是中间两个数的平均数
            if (maxHeapSize == minHeapSize){
                return (maxHeapHead + minHeapHead) / 2;
            }
            //奇数个数字,中位数是多出的那一个
            return maxHeapSize > minHeapSize ? maxHeapHead : minHeapHead;
        }

        public void addNumber(int num){
            //第一个数字进入大根堆
            if (maxHeap.isEmpty()){
                maxHeap.add(num);
            }
            if (num <= maxHeap.peek()){
                maxHeap.add(num);
            }else {
                minHeap.add(num);
            }
            modifyTwoHeapsSize();

        }

        private void modifyTwoHeapsSize(){
            if (this.minHeap.size() - this.maxHeap.size() == 2){
                maxHeap.add(minHeap.poll());
            }
            if (this.maxHeap.size() - this.minHeap.size() == 2){
                minHeap.add(maxHeap.poll());
            }
        }
    }

    public static class MaxHeapComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            if (o2 > o1) {
                return 1;
            } else {
                return -1;
            }
        }
    }

    public static class MinHeapComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            if (o2 < o1) {
                return 1;
            } else {
                return -1;
            }
        }
    }
}

标签:node,index,结点,return,int,算法,007,public,贪心
From: https://www.cnblogs.com/jyyyy/p/18019098

相关文章

  • 路由选择算法简要介绍
    本文仅对LS和DV进行简单的介绍,由于作者初学计算机网络,同时也没有学习图论的知识,若有不妥之处还请指出.一、链路状态算法(LS)特殊量:D(v):直到本次迭代,从源节点到节点v的最低路径开销p(v):从源到v沿着当前最低开销路径的前一节点N':已确定最短路径的节点集c(a,b):两......
  • day28 回溯算法part4 代码随想录算法训练营 78. 子集
    题目:78.子集我的感悟:看见弹幕是秒了,我有点不敢相信,自己试了试,没有通过,再看了一眼文字讲解。感觉懂了点理解难点:这题可以没有终止条件,开始我就疑惑这个终止条件怎么写注意这个nums[i]要添加进入是可以不写终止的,不会出现无线递归的,因为是从i+1开始,那会不会越界??,不会,最......
  • 今天练习2-回溯算法-93. 复原 IP 地址
    注意点&感悟:加油!题目链接:93.复原IP地址自己独立写的代码:classSolution:defrestoreIpAddresses(self,s:str)->List[str]:res=[]self.backtracking(s,0,[],res)returnresdefbacktracking(self,s,start_index,path,res......
  • 今天练习-回溯算法-93. 复原 IP 地址
    注意点&感悟:难吗?不难。难的是克服畏难的心里。题目链接:93.复原IP地址自己独立写的代码:fromtypingimportListclassSolution:defrestoreIpAddresses(self,s:str)->List[str]:res=[]self.backtracking(s,0,[],res)return......
  • day28 回溯算法part4 代码随想录算法训练营 93. 复原 IP 地址
    题目:93.复原IP地址我的感悟:加油!理解难点:开始没理解,start_index的含义start_index是切割后的位置信息。代码难点:代码示例:fromtypingimportListclassSolution:defrestoreIpAddresses(self,s:str)->List[str]:#找3个分割点?#最后......
  • 算法入门:搜索算法
    文章目录1.二分查找2.深度优先搜索(DFS)3.广度优先搜索(BFS)4.DFS与BFS区别 1.二分查找思想:二分查找是一种高效的查找算法,它基于分治思想,适用于已排序的数组。确定搜索范围:首先确定整个数组的搜索范围,通常是从数组的起始位置到结束位置。计算中间位置:计算搜索范围的中......
  • 排序算法总结
    冒泡排序稳定排序时间复杂度o(n2)空间复杂度o(1)点击查看代码staticvoidBubbleSort(){int[]data={1,8,5,7,9,4,6,99,88,74};inti,j,flag;//岗哨模式的冒泡排序for(i=data.Length-1;i>0......
  • Python 机器学习 逻辑回归算法
    ​ 1、理解逻辑回归逻辑回归建立在线性回归之上。在线性回归中,模型预测的是一个连续的数值。而在逻辑回归中,线性回归的输出被输入到Sigmoid函数中,用于预测某个类别的概率。Sigmoid函数是一个S形的曲线,它将任意实数映射到(0,1)区间,适合用来表达概率。逻辑回归广泛应用于各种......
  • P6354 [COCI2007-2008#3] TAJNA
    题目描述使用一种加密算法。设字符串的长度为nn,则构造一个矩阵,使得r×c=nr×c=n且在r≤cr≤c的情况下使得rr尽量大。然后把给定的明文按照由上到下,从左到右的顺序填充这个r×cr×c的矩阵。得到的密文就是把矩阵按照从左到右,从上到下的顺序输出的字符串。给定你明文,......
  • 【机器学习】机器学习常见算法详解第4篇:KNN算法计算过程(已分享,附代码)
    本系列文章md笔记(已分享)主要讨论机器学习算法相关知识。机器学习算法文章笔记以算法、案例为驱动的学习,伴随浅显易懂的数学知识,让大家掌握机器学习常见算法原理,应用Scikit-learn实现机器学习算法的应用,结合场景解决实际问题。包括K-近邻算法,线性回归,逻辑回归,决策树算法,集成学习,聚......