我要进大厂-算法-第八天(贪心~好难呀)

阅读该文章预计90分钟

一:前缀树

1.1:举个例子说明前缀树
举个例子:

"abc" 加到头节点
如果头节点没有走向a的路,就创建一个a,依次建立a、b、c

“bce" 加到头节点
如果头节点没有走向b的路,就创建b,如果没有b-c的路,依次加入c、e

”abd“ 加入到头节点
头节点有走向a的路,那么来到a,有a-》b,来到b,没有b-d,则b的下面创建一个d

总的来说,依次看看有没有头节点到这个路,如果没有则创建出来
1.2:作用

判断某个字符串,是不是以某个字符串开头的

给出n个字符串,生成一个前缀树
然后再给一个字符串,判断是否存在某个开头(前缀)     ---满足
然后再给一个字符串,判断是否存在该字符串	       ---不满足,需要改造点1
然后再给一个字符串,判断有多少个字符串以它作为前缀   ---不满足,需要改造点2

改造点1:需要增加每个节点有多少个以该节点的字符串结尾的。如果超过0的,那就肯定存在该字符串
改造点2:需要增加每个节点被经过多少次,如果经历过n次,则有n个字符串以它作为前缀
1.3: Java代码

package xyz.fudongyang.basic.class_07.my;

public class Code_01_TrieTree {

    /**
     * 前缀树的节点的数据结构
     */
    public static class TrieNode {
        int path;   // 有多少个节点来过此路径
        int end;    // 有多少个节点以它为结尾
        TrieNode[] next;  // 我的下一个路径有26条

        public TrieNode() {
            // 默认我有26条路 a~z
            this.next = new TrieNode[26];
        }
    }

    /**
     * 前缀树节点
     */
    public static class Trie {
        // 前缀树拥有一个头节点
        TrieNode head;

        public Trie() {
            this.head = new TrieNode();
        }

        // 插入一个字符串
        public void insert(String req) {
            if (req == null) {
                return;
            }
            TrieNode node = head;
            char[] charArray = req.toCharArray();
            for (char c : charArray) {
                // 求出该字符串的字符在a~z的哪一个位置上
                int index = c - 'a';
                if (node.next[index] == null) {
                    // 如果从来没来过,则创建数据
                    node.next[index] = new TrieNode();
                }
                node = node.next[index];
                // 路过该节点的都+1
                node.path++;
            }
            // 以它结尾的都+1
            node.end++;
        }

        // 删除一个字符串
        public void delete(String req) {
            // 当这个字符串存在才删除
            if (search(req) != 0){
                TrieNode node = head;
                char[] charArray = req.toCharArray();
                for (char c : charArray) {
                    int index = c - 'a';
                    // 如果是最后一个来过该元素的,则后序元素也是只来过一次,则删除掉吧
                    if (--node.next[index].path == 0){
                        node.next[index] = null;
                        return;
                    }
                    node = node.next[index];
                }
                // 以它结尾的就要被删掉一次啦
                node.end--;
            }
        }


        // 判断当前字符串是否存在过几次
        public int search(String req) {
            if (req == null){
                return 0;
            }
            TrieNode node = head;
            char[] charArray = req.toCharArray();
            for (char c : charArray) {
                int index = c - 'a';
                if (node.next[index] == null){
                    return 0;
                }
                node = node.next[index];
            }
            return node.end;
        }

        // 判断当前字符串以它开头的有几次
        public int prefixNumber(String req){
            if (req == null){
                return 0;
            }
            TrieNode node = head;
            char[] charArray = req.toCharArray();
            for (char c : charArray) {
                int index = c - 'a';
                if (node.next[index] == null){
                    return 0;
                }
                node = node.next[index];
            }
            return node.path;
        }

    }


    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"));

    }

}

二:贪心策略

2.1:前提知识
贪心策略不要证明
对数器来验证贪心测略的正确性,小样本测试对则大样本也对

看下面几道题

三:拼接字符串数组,求最低的字典序

3.1:题目描述

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

3.2:思路讲解
首先明白字典序
“b” “bc” “b”肯定在前面,在字典中的顺序叫做字典序,我相信你会懂!

我们需要将我们的字符串数组进行排序,排完序后,我们拼起来就是最低的字典序
如何排序?
不能根据比较前一个和后一个的字典序的排序(eg:a < b)
比如 “b” “ba“  如果按照每条数据的字典序排,则不对
那应该如何排序?
我们将贪心策略运行到排序中
如果(a+b <= b+a),则按照这个规矩排序
我们的根据这个策略排序,就会得到,完全的符合题意的哪个参数作为前缀最小就放前面
为什么对????
排序策略要有传递性,a.b > b.a  b.c > c.b 则 a.c > c.a  有传递性,才能说明我们的排序是对的。

看code
3.3:Java代码
package xyz.fudongyang.basic.class_07.my;

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

public class Code_05_LowestLexicography {

    private static class Sort implements Comparator<String> {

        @Override
        public int compare(String a, String b) {
            // compareTo 就是比较两个字符串的字典序
            // 利用a+b是否大于b+a进行排序,就是贪心的过程
            return (a + b).compareTo(b + a);
        }
    }

    private static String lowestString(String[] strs) {
        if (strs == null || strs.length == 0) {
            return null;
        }
        // 系统排序就是贪心的过程
        Arrays.sort(strs, new Sort());
        StringBuilder res = new StringBuilder();
        for (String str : strs) {
            res.append(str);
        }
        return res.toString();
    }

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

        String[] strs2 = {"ba", "b"};
        System.out.println(lowestString(strs2));

    }

}

四:切割金条且最小代价

4.1:题目介绍

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

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

4.2:思路讲解
我的第一个思路:
先把需求数组排序 incr
然后按照数组的从大到小进行切割

我的第二个思路:
利用哈夫曼编码,所有代价就是所有的非叶节点的和
把所有的数加入到小根堆中,每一次从小根堆中拿出两个最小值,两个最小值的和再扔回小根堆
将所有两个最小值的和累加起来就是结果,其实就是所有非叶节点的和,你品把!
利用堆解决贪心问题是个好注意,最合适!

嘻嘻!看code吧
4.3:Java代码
package xyz.fudongyang.basic.class_07.my;

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

public class Code_02_Less_Money {

	public static int lessMoney(int[] arr) {
		if (arr == null || arr.length == 0){
			return 0;
		}
		PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
		for (int i : arr) {
			priorityQueue.add(i);
		}
		int result = 0;
		while (priorityQueue.size()>1){
			int sum = priorityQueue.poll() + priorityQueue.poll();
			result += sum;
			priorityQueue.add(sum);
		}
		return result;
	}

	public static class MinheapComparator implements Comparator<Integer> {

		@Override
		public int compare(Integer o1, Integer o2) {
			return o1 - o2; // < 0  o1 < o2  负数
		}

	}

	public static class MaxheapComparator implements Comparator<Integer> {

		@Override
		public int compare(Integer o1, Integer o2) {
			return o2 - o1; // <   o2 < o1
		}

	}

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

		int[] arrForHeap = { 3, 5, 2, 7, 0, 1, 6, 4 };

		// min heap
		PriorityQueue<Integer> minQ1 = new PriorityQueue<>();
		for (int i = 0; i < arrForHeap.length; i++) {
			minQ1.add(arrForHeap[i]);
		}
		while (!minQ1.isEmpty()) {
			System.out.print(minQ1.poll() + " ");
		}
		System.out.println();

		// min heap use Comparator
		PriorityQueue<Integer> minQ2 = new PriorityQueue<>(new MinheapComparator());
		for (int i = 0; i < arrForHeap.length; i++) {
			minQ2.add(arrForHeap[i]);
		}
		while (!minQ2.isEmpty()) {
			System.out.print(minQ2.poll() + " ");
		}
		System.out.println();

		// max heap use Comparator
		PriorityQueue<Integer> maxQ = new PriorityQueue<>(new MaxheapComparator());
		for (int i = 0; i < arrForHeap.length; i++) {
			maxQ.add(arrForHeap[i]);
		}
		while (!maxQ.isEmpty()) {
			System.out.print(maxQ.poll() + " ");
		}

		System.out.println();
		PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
		System.out.println(priorityQueue.poll());
	}

}

五:最大收益(IPO)

5.1:题目介绍

我有两个数组,一个是代价数组,一个是利润数组。

输入: 参数1,正数数组costs 参数2,正数数组profits 参数3,正数k 参数4,正数m。

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

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

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

m表示你初始的资金
说明:你每做完一个项目,马上获得的收益,可以支持你去做下一个 项目。
输出: 你最后获得的最大钱数。

5.2:思路讲解
我肯定想,在刚好满足代价的情况下,收益最大的那一个!你品吧

维护一个收益的大根堆
维护一个代价的小根堆

怎么维护这两个堆?
步骤1:在代价小根堆中,是所有的数据。
步骤2:代价如果比本次启动资金小,则弹出来,放到大根堆中。
步骤3:弹出大根堆,我的k-1,
步骤4:并且我的启动资金增加了,看看我还能做哪些项目,如果能做的项目,从小根堆弹出,放入大根堆(循环步骤2-3)
说明:如果步骤2不执行,可以直接执行步骤3
结束条件:大根堆没有东西了,或者k用完了

最后的结果就是:最后一个的启动资金。

看我code表演
5.3:Java代码
package xyz.fudongyang.basic.class_07.my;

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

public class Code_03_IPO {

    public static class Node {
        // 做完后得到的利润
        private int p;
        // 做的代价(需要的启动资金)
        private int c;

        public Node(int p, int c) {
            this.p = p;
            this.c = c;
        }
    }

    /**
     * 获取到最大收益值
     *
     * @param k       只允许做k次项目
     * @param w       启动资金
     * @param profits 利润
     * @param capital 代价
     * @return int    最大收益值
     */
    public static int findMaximizedCapital(int k, int w, int[] profits, int[] capital) {
        // 首先先声明一个结构,用于组装信息
        // 1.初始化数据,将数据封装到自定义结构中
        int length = profits.length;
        Node[] nodeArray = new Node[length];
        for (int i = 0; i < profits.length; i++) {
            nodeArray[i] = new Node(profits[i], capital[i]);
        }
        // 2.维护一个代价最小堆,数据来源于所有的项目
        PriorityQueue<Node> maxProfitsQueue = new PriorityQueue<>(new MaxProfitsComparator());
        PriorityQueue<Node> minCapitalQueue = new PriorityQueue<>(new MinCapitalComparator());
        minCapitalQueue.addAll(Arrays.asList(nodeArray));
        // 4.边界条件,最多只能做k个项目,或者启动资金没办法做项目了
        for (int i = 0; i < k; i++) {
            // 3.维护一个利润最大堆,数据来源于当前启动资金可以做的项目
            while (!minCapitalQueue.isEmpty() && w >= minCapitalQueue.peek().c){
                maxProfitsQueue.add(minCapitalQueue.poll());
            }
            // 5.弹出最大堆堆顶,使得我的k减一,我的w增加对应的利润数
            if (maxProfitsQueue.isEmpty()){
                return w;
            }
            w += maxProfitsQueue.poll().p;
        }
        // 6.返回值就是我最后的启动资金
        return w;
    }

    private static class MinCapitalComparator implements Comparator<Node> {

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

    private static class MaxProfitsComparator implements Comparator<Node> {

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


}


六:最多的宣讲场次

6.1:题目描述

一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。 给你每一个项目开始的时间和结束的时间(给你一个数组,里面 是一个个具体的项目),你来安排宣讲的日程。

要求:会议室进行 的宣讲的场次最多。返回这个最多的宣讲场次。

6.2:思路讲解
我不能让 最近一个的最短时间的场次进行 因为我不确定会不会错过更小的
我也不能让 按照持续时间短,也得不到最优解
不能按照开始时间,和持续时间进行贪心
所以啊 想想这个问题
所以我应该按照结束时间贪心

仔细想想 这就是个时间轴,我应该用结束时间贪心
我求出最近一个结束时间早的,然后我做这个项目,但是会因为这个项目而导致其他项目做不了
也就是我的时间轴根据最近一个项目结束时间近的进行时间轴的推进

看code
6.3:Java代码
package xyz.fudongyang.basic.class_07.my;

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

public class Code_06_BestArrange {

    private static class Project {
        private int start;
        private int end;

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

    private static class EndTimeComparator implements Comparator<Project> {

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


    private static int bestArrange(Project[] projects, int curTime) {
        // 根据结束时间进行排序,贪心
        Arrays.sort(projects,new EndTimeComparator());
        // 依次选出结束时间最早的,执行,然后推进时间轴
        int res = 0;
        for (int i = 0; i < projects.length; i++) {
            if (curTime <= projects[i].start){
                res++;
                curTime = projects[i].end;
            }
        }
        return res;
    }


    public static void main(String[] args) {

    }

}

七:随时获取一个流中的中位数

7.1:思路讲解
随时获取一个流中的中位数

可以用我们的堆

堆只保证堆顶是最大值或者最小值,我们获取中位数也是只获取中间的数,或者中间两个数的平均值

我们可以维护一个最大堆,用于放整体比较小的数
我们可以维护一个最小堆,用于放整体比较大的数

那么,最大堆的堆顶、最小堆的堆顶 这两个数其中一个是中位数,或者中位数就是这两个数的平均值

看code
7.2:Java代码
package xyz.fudongyang.basic.class_07.my;

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

public class Code_04_MadianQuick {

    public static class MedianHolder {
        // 维护一个总体较小的堆
        private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new MaxHeapComparator());
        // 维护一个总体较大的堆
        private PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>(new MinHeapComparator());

        private void modifyTwoHeapsSize() {
            if (this.maxHeap.size() == this.minHeap.size() + 2) {
                this.minHeap.add(this.maxHeap.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;
            }
        }
    }

    // for test
    public static int[] getRandomArray(int maxLen, int maxValue) {
        int[] res = new int[(int) (Math.random() * maxLen) + 1];
        for (int i = 0; i != res.length; i++) {
            res[i] = (int) (Math.random() * maxValue);
        }
        return res;
    }

    // for test, this method is ineffective but absolutely right
    public static int getMedianOfArray(int[] arr) {
        int[] newArr = Arrays.copyOf(arr, arr.length);
        Arrays.sort(newArr);
        int mid = (newArr.length - 1) / 2;
        if ((newArr.length & 1) == 0) {
            return (newArr[mid] + newArr[mid + 1]) / 2;
        } else {
            return newArr[mid];
        }
    }

    public static void printArray(int[] arr) {
        for (int i = 0; i != arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        boolean err = false;
        int testTimes = 200000;
        for (int i = 0; i != testTimes; i++) {
            int len = 30;
            int maxValue = 1000;
            int[] arr = getRandomArray(len, maxValue);
            MedianHolder medianHold = new MedianHolder();
            for (int j = 0; j != arr.length; j++) {
                medianHold.addNumber(arr[j]);
            }
            if (medianHold.getMedian() != getMedianOfArray(arr)) {
                err = true;
                printArray(arr);
                break;
            }
        }
        System.out.println(err ? "Oops..what a fuck!" : "today is a beautiful day^_^");

    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值