贪心算法
在某一个标准下,优先考虑最满足标准的样本,最后考虑最不满足标准的样本,最终得到一个答案的算法,叫做贪心算法
不从整体最优上加以考虑,所做出的是在某种意义上的局部最优解
如何从局部最优推到整体最优,这个点有时候不需要去证明(证明方式难)
贪心算法在笔试时的解题套路
- 实现一个不依靠贪心策略的解法X,可以用暴力尝试
- 脑补出贪心策略A、B…
- 用解法X和对数器去验证每一个贪心策略,用实验的方式得知哪个贪心策略正确(或能举出反例)
- 不要去纠结贪心策略的证明
注:贪心算法基本只会出现在笔试(淘汰率),面试很少出现(考察不了coding能力,没有区分度)
贪心策略在实现时,经常使用到的技巧
- 根据某标准建立一个比较器来排序
- 根据某标准建立一个比较器来组成堆
会议安排问题
一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲,给你每一个项目开始的时间和结束的时间(一个数组,里面是一个个具体的项目),请你安排宣讲的日程,要求会议室进行的宣讲的次数最多,返回这个最多的宣讲次数
贪心策略:
- 开始时间最早先开始(不行)(如果有一个会议最早开始并且开一天)
- 持续时间少的先安排(不行)(中间有个时间短的同时与前后两个冲突)
- 会议结束的早先安排(可以)
能使这一天接下来的剩余时间最多
public class Program{
public int start;
public int end;
public Program(int start,int end){
this.start=start;
this.end=end;
}
}
public class ProgramComparator implements Comparator<Program>{
@Override
public int compare(Program o1,Program o2){
return o1.end-o2.end;
}
}
//timePoint 开始时间
public int bestArrange(Program[] programs,int timePoint){
Arrays.sort(programs,new ProgramComparator());
int res=0;
for(int i=0;i<program.length;i++){
if(timePoint<=program[i].start){
res++;
timePoint=programs[i].end;
}
}
return res;
}
字典序最小问题
比较两个字符串的字典序,短的字符串先补0变成和长的字符串一样长,然后从第一个字符开始依次比较
题目:给定一个字符串数组,要求将数组中所有字符串进行拼接,返回拼接后字典序最小的拼接字符串
思路一:按字典序从小到大排序并拼接(不行)
思路二:字符串a与字符串b拼接起来的字典序是否小于等于字符串b与字符串a拼接起来的字典序(可以)
有效的比较策略+算法的有效性
注:一个有效的比较策略要具有传递性,不要形成环
public class MyComparator implements Comparator<String>{
@Override
public int compare(String a,String b){
return (a+b).compareTo(b+a);//在java中compareTo返回的是字典序的比较结果
}
}
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;
}
证明过程:很难!不要碰!
切金条问题(经典哈夫曼编码问题)
先把所有数放到小根堆中,弹出两个数,做结合,把结合(加起来的数)放到小根堆中,再拿出两个数做结合,依次循环直到小根堆为空
public class MinheapComparator implements Comparator<Integer>{
@Override
public int compare(Integer o1,Integer o2){
return o1-o2;
}
}
public int lessMoney(int[] arr){
PriorityQueue<Integer> pq=new PriorityQueue<>(new MinheapComparator());
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;
}
项目花费、利润问题
注:项目不能重复做
M:初始资金 k:可做的项目数
准备一个小根堆(用花费的金额来存)(锁住)和一个大根堆(利润)(解锁)
public class Node{
public int p;
public int c;
public Node(int p,int c){
this.p=p;
this.c=c;
}
}
public class MinCostComparator implements Comparator<Node>{
@Override
public int compare(Node o1,Node o2){
return o1.c-o2.c;
}
}
public class MaxProfitComparator implements Comparator<Node>{
@Override
public int compare(Node o1,Node o2){
return o2.p-o1.p;
}
}
public 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<Profits.length;i++){
minCostQ.add(new Node(Profits[i],Capital[i]));
}
for(int i=0;i<k;i++){//i<k
while(!minCostQ.isEmpty()&&minCostQ.peek().c<=w){
maxProfitQ.add(minCostQ.poll());
}
if(maxProfitQ.isEmpty()){
return w;
}
w+=maxProfitQ.poll().p;
}
return w;
}
补充:一个数据流中如何做到随时可以取得中位数
与贪心无关
准备一个大根堆一个小根堆,第一个数先放进大根堆,第二个数进来的时候
- 判断当前数是否小于等于大根堆的堆顶,如果是则放入大根堆,否则放入小根堆
- 判断大根堆和小根堆的数量,如果相差等于2,则调整,堆顶的数到数量较少的堆中
这样较小的N/2个数在大根堆堆顶,较大的N/2个数在小根堆堆顶
时间复杂度低:调整时间都是O(logN)
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;
}
}
}
public class MedianHolder{
public PriorityQueue<Integer> maxHeap=new PriorityQueue<>(new MaxHeapComparator());
public PriorityQueue<Integer> minHeap=new PriorityQueue<>(new MinHeapComparator());
public void modifyTwoHeapSize(){
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(maxHeap.isEmpty()||num<=maxHeap.peek()){
maxHeap.add(num);
}else{
minHeap.add(num);
}
modifyTwoHeapSize();//z
}
public Integer getMidian(){
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)>>1;
}
return maxHeapSize>minHeapSize?maxHeapHead:minHeapHead;
}
}