左神算法笔记05:前缀树、贪心算法、N皇后问题

前缀树

何为前缀树?如何生成前缀树?
例子:
一个字符串类型的数组arr1,另一个字符串类型的数组arr2。

  1. arr2中有哪些字符,是arr1中出现的?请打印。
  2. arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印。
  3. arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印arr2中出现次数最大的前缀。
    在这里插入图片描述

public class TrieTree {
    public static class TrieNode{
    	//表示添加前缀树的时候,当前节点到达过多少次
        public int pass;
        //表示到当前节点就结尾的单词有多少个
        public int end;
        //字符特别多的情况下,用HashMap<Character,Node> nexts;
        public TrieNode[] nexts;
        
        public TrieNode(){
            pass= 0;
            end = 0;
            //nexts[0] == null 没有走向'a'的路
            //nexts[0] != null 有走向'a'的路
            //...
            //next[25] != null 有走向'z'的路
            nexts = 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[] chars = word.toCharArray();
            TrieNode node = root;
            node.pass++;
            int index = 0;
            //从左往右遍历字符串
            for (int i=0; i<chars.length; i++){
            	//由字符得出对应该走哪条路
                index = chars[i] - 'a';
                if (node.nexts[index] == null){
                    node.nexts[index] = new TrieNode();
                }
                node = node.nexts[index];
                node.pass++;
            }
            node.end++;
        }
        //查询word这个单词出现过几次
        public int search(String word){
            if (word == null){
                return 0;
            }
            char[] chars = word.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (int i=0; i<chars.length; i++){
                index = chars[i] - 'a';
                if (node.nexts[index] == null){
                   return 0;
                }
                node = node.nexts[index];
            }
            return node.end;
        }
        //删除元素
        public void delete(String word){
            if (search(word) != 0){
                char[] chars = word.toCharArray();
                TrieNode node = root;
                int index = 0;
                for (int i=0; i<chars.length; i++){
                    index = chars[i] - 'a';
                    if (--node.nexts[index].pass== 0){
                        node.nexts[index] = null;
                        return;
                    }
                    node = node.nexts[index];
                }
                node.end--;
            }
        }
        //查找以pre为前缀的单词有几个
        public int prefixNumber(String pre) {
            if (pre == null) {
                return 0;
            }
            char[] chars = pre.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (int i = 0; i < chars.length; i++){
                index = chars[i] - 'a';
                if (node.nexts[index] == null){
                    return 0;
                }
                node = node.nexts[index];
            }
            return node.pass;
        }

        public static void main(String[] args) {
            Trie trie = new Trie();
            System.out.println(trie.search("zuo"));
            trie.insert("zuo");
            System.out.println(trie.search("zuo"));
            trie.delete("zuo");
            System.out.println(trie.search("zuo"));
            trie.insert("zuo");
            trie.insert("zuo");
            trie.delete("zuo");
            System.out.println(trie.search("zuo"));
            trie.delete("zuo");
            System.out.println(trie.search("zuo"));
            trie.insert("zuoa");
            trie.insert("zuoac");
            trie.insert("zuoab");
            trie.insert("zuoad");
            trie.delete("zuoa");
            System.out.println(trie.search("zuoa"));
            System.out.println(trie.prefixNumber("zuo"));

        }

    }
}

贪心算法

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

笔试的解题套路

  1. 实现一个不依靠贪心策略的解法x,可使用最暴力的尝试
  2. 脑补出贪心策略a,贪心策略b。。。
  3. 用解法x和对数器,去验证每一个贪心策略,用实验的方式得知哪个贪心策略正确
  4. 不要纠结贪心策略的证明

技巧:

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

安排会议问题

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

import java.util.Arrays;
import java.util.Comparator;

public class BestArrange {
    public static class Program{
        private int start;
        private 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;
        }
    }

    public static int bestArray (Program[] programs,int start) {
        Arrays.sort(programs,new ProgramComparator());
        int result = 0;
        //从左往右遍历所有的会议,当前开始时间start要是小于或等于当前会议的开始时间,就代表当前会议可以安排
        for(int i = 0; i < programs.length; i++){
            if(start <= programs[i].start){
                result++;
                start = programs[i].end;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        Program pro1 = new Program(0,1);
        Program pro2 = new Program(1,3);
        Program pro3 = new Program(1,2);
        Program pro4 = new Program(2,3);
        Program[] programs = {pro1,pro2,pro3,pro4};
        System.out.println(bestArray(programs, 0));
    }
}

字符串拼接问题

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

将字符串等同于k进制的树,则a.b <= b.a 相当于 a * m(b) +b (m(b)为b字符串的长度,相当于把a往左移m(b)位) << b * m(a) +a

在这里插入图片描述

在这里插入图片描述
由此可见,贪心策略证明非常麻烦,所以做题时别证,套模板,用对数器来实验比较结果是否正确

import java.util.Arrays;
import java.util.Comparator;

public class LowestLexicography {
    public static class MyComparator implements Comparator<String> {
        @Override
        public int compare(String a, String b) {
        	//a结合b之后比较b结合a,实际上就是比较字典序
            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 (int i = 0;i<strs.length;i++){
            res += strs[i];
        }
        return res;
    }

    public static void main(String[] args) {
        String[] strs1 =  { "jibw", "ji", "jp", "bw", "jibw" };
        System.out.println(lowestString(strs1));
    }

}

金条问题

一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为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 class Less_Money {
    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;
    }

    public static void main(String[] args) {
        int[] arr = {6,7,8,9};
        System.out.println(lessMoney(arr));
    }
}

最大钱数问题

输入:
参数1,正数数组costs
参数2,正数数组profits
参数3,正数k
参数4,正数m
含义:
costs[i]表示i号项目的花费,
profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润),
k表示你不能并行、只能串行的最多做k个项目,
m表示你初始的资金
说明:
你每做完一个项目,马上获得的收益,可以支持你去做下一个 项目。
输出:
你最后获得的最大钱数。
在这里插入图片描述

import java.util.Comparator;
import java.util.PriorityQueue;

public class IPO {
    public static class Node{
        public int p;
        public int c;
        public Node( int p,int c){
            this.p = p;
            this.c = c;
        }
    }
    //最小花费比较器
    public static class MinCostComparator implements Comparator<Node>{
        @Override
        public int compare(Node o1, Node o2) {
            return o1.c-o2.c;
        }
    }
    //最大利润比较器
    public static class MaxProfitComparator implements Comparator<Node>{

        @Override
        public int compare(Node o1, Node o2) {
            return o2.p-o1.p;
        }
    }
    public static int findMaximizedCapital(int k,int w,int[] Profits,int[] Capital){
        PriorityQueue<Node> minCostQ = new PriorityQueue<>(new MinCostComparator());
        PriorityQueue<Node> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
        //所有项目被扔进被锁池中——花费组织的小根堆
        for (int i=0;i<nodes.length; i++){
            minCostQ.add(new Node(Profits[i],Capital[i]));
        }
        //进行k轮
        for (int i=0; i<k; i++){
        	//能力所及的项目,全解锁
            while (!minCostQ.isEmpty() && minCostQ.peek().c <= w){
                maxProfitQ.add(minCostQ.poll());
            }
            if (maxProfitQ.isEmpty()){
                return w;
            }
            w += maxProfitQ.poll().p;
        }
        return w;
    }

    public static void main(String[] args) {
        int[] profits = {4,5,6};
        int[] capical = {10,13,30};
        System.out.println(findMaximizedCapital(3, 15, profits, capical));
    }
}

一个数据流中,随时可以取得中位数(与贪心无关)

在这里插入图片描述

import java.util.Comparator;
import java.util.PriorityQueue;

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

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

        public void addNumber(int num) {
            if (this.maxHeap.isEmpty()) {
                this.maxHeap.add(num);
                return;
            }
            if (this.maxHeap.peek() >= num) {
                this.maxHeap.add(num);
            } else {
                if (this.minHeap.isEmpty()) {
                    this.minHeap.add(num);
                    return;
                }
                if (this.minHeap.peek() > num) {
                    this.maxHeap.add(num);
                } else {
                    this.minHeap.add(num);
                }
            }
            modifyTwoHeapsSize();
        }

        public Integer getMedian() {
            int maxHeapSize = this.maxHeap.size();
            int minHeapSize = this.minHeap.size();
            if (maxHeapSize + minHeapSize == 0) {
                return null;
            }
            Integer maxHeapHead = this.maxHeap.peek();
            Integer minHeapHead = this.minHeap.peek();
            if (((maxHeapSize + minHeapSize) & 1) == 0) { //偶数
                return (maxHeapHead + minHeapHead) / 2;
            }
            return maxHeapSize > minHeapSize ? maxHeapHead : minHeapHead;
        }

    }
    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;
            }
        }
    }

}

N皇后问题

N皇后问题是指在N*N的棋盘上要摆N个皇后,要求任何两个皇后不同行、不同列,也不在同一条斜线上。
给定一个整数n,返回n皇后的摆法有多少种
n = 1,返回1
n = 2或3,2皇后和3皇后问题怎么摆都不行,返回0
n = 8,返回92

public static int num1(int n){
	if(n < 0){
		return 0;
	}
	//record[0]代表第0行的皇后放在哪一列
	int[] record = new int[n];
	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;
	//当前行在i行,尝试所有的列j
	for(int j = 0;j < n;j++){
		//当前i行的皇后,放在j列,会不会和之前的(0...i-1)的皇后,共行共列或者共斜线
		//如果是,认为无效
		//如果不是,认为有效
		if(isValid(record,i,j){
			record[i] = j;
			res += process1(i+1,record,n);
		}
	}
	return res;
}
//record[0...i-1]需要看,后续的不需要
//返回i行皇后,放在j列,是否有效
public static boolean isValid(int[] record,int i,int j){
	for(int k = 0; k < i; k++){
		//共列和共斜线返回false
		if(j == record[k] || Math.abs(record[k] - j) == Math.abs(i-k)){
			return false;
		}
	}
	return true;
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值