蓝桥杯算法基础(35)贪心算法详解

动态规划和贪心算法都是一种推导算法
均用“局部最优解”来推导“全局最优解”

是对遍历解空间的一种优化

当问题具有最有子结构时,可用都动规,而贪心是动规的特例


什么是贪心策略

顾眼前-->长远
-遵循某种规则,不断(贪心地)选取当前最优策略,最终找到最优解
-难点:当前最优未必是整体最优

贪心策略例1:硬币支付问题

 

有1元,5元,10元,50元,100元,500元地硬币各c1,c5,c10,c50,c100,c500枚
现在要用这些硬币来支付A元,最少需要多少枚硬币?
假定本题至少存在一种支付方案
0<=ci<=10^9

0<=A<=10^5

输入:
第一行有六个数字,分别代表从小到大6种面值地硬币的个数

第二行为A,代表需支付的A元

样例:
输入
3 2 1 3 0 2
620




输出



保持一个分支
static int f(int A,int cur){

        if(A<=0)return 0;
        if(cur==0)return A;

        int coinValue=coins[cur];
        int x=A/coinValue;//金额有多少个coinValue
        int cnt=cnts[cur];//当前面值的硬币cnt个
        int t=min(x,cnt);//若是需要x个coinValue硬币,并且有足够数量,则取走x个
        //若是硬币数量不足,则只取走全部数量的硬币
        return t+f(A-t*coinValue,cur+1);//用t个当前面值,剩下的继续处理

}//有较小面值的硬币兜底


贪心策略例2:快速过河

一队人(N个人)期望跨河,有一条船,一次只能载2个人,过河之后需要有一个人划回来,所有人才能够跨河,每个人划船速度都不同,两个人一组整体速度是由划船速度较慢的决定的。问题:确定一种策略用最少的时间所有人都能过河。
输入:
方案数:T(1<=T<=20)
人数:N<1000
速度:<100s
输出:
最少的时间
样例:
输入:
1
4
1 2 5 10
输出
17


a,b,c,d
->a,b     ->a,d
<-a       <-a
->c,d     ->a,c
<-b       <-a
->a,b     ->a,b
a+3b+d   2a+b+c+d

public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int T=sc.nextInt();
        for(int i=0;i<T;i++){
            int n=sc.nextInt();
            int[] speed=new int[n];
            for(int j=0;j<n;j++){
                speed[j]=sc.nextInt();
            }
            //排序
            Arrays.sort(speed);
            f(n,speed);
        }
}

private static void f(int n,int[] speed){
        int left=n;
        int ans=0;
        while(left>0){
            if(left==1){//还剩一人
                ans+=speed[i];
                 break;
            }else if(left==2){//还剩两人
                ans+=speed[1];
                break;
            }else if(left==3){//还剩三人
            ans+=speed[2]+speed[0]+speed[1];
            break;

            }else{//大于三个,不断送两个过去,直到还剩下1或2或3个
            //先1,2过河,然后让1回来,再让最后两个最大的和倒数第二大的过去,然后2返回来,这样就1,2还在原地,但是最大的两个已经过去了
            //和用1作为引导最后两个过河的时间做比较,选最优策略。
            //每次都只送最大的那两个过和

            //1,2出发,1返回,最后两人出发,2返回
            int s1=speed[1]+speed[0]+speed[left-1]+speed[1];
            //1,3出发。1返回,1,4出发,1返回
            int s2=speed[left-1]+speed[left-2]+2*speed[0];
            ans+=min(s1,s2);
                left-=2;//左侧作为起点,left代表左侧的剩余人数

            }

        }
        System.out.println(ans);


}

贪心策略例题3:

区间调度问题

有n项工作,每项工作,分别在si时间开始,在ti时间结束
对于每项工作,你想可以选择参与否,如果选择了参与,那么自始至终都必须全程参与
此外,参与工作的时间段不能重复(即使是开始的瞬间的重叠也是不允许的)
你的目标是参与尽可能多的工作,那么最多能参与多少项工作呢?

1<=n<=100000
1<=si<=10^9

输入:

第一行:n
第二行:n个整数空格隔开,代表n个工作的开始时间
第三行:n个整数空格隔开,代表n个工作的结束时间

1--3 4---7 8--10
-------------------->
  2---5 6---9

找结束时间最早的
按ti结束时间排序,面向对象
排完序后,找下一个大于上一个结束时间的起始时间


public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int[] s=new int[n];
int[] t=nwe int[n];
    Job[] jobs=new Job[n];
    for(int i=0;i<n;i++){
        s[i]=sc.nextInt();
    }
    for(int i=0;i<n;i++){
        t[i]=sc.nextInt();
    }

    for(int i=0;i<n;i++){
        jobs[i]=new Job(s[i],t[i]);

    }
  //按结束时间排序
    Arrays.sort(jobs);
    int res=f(n,jobs);
    System.out.println(res);

}


//从结束时间较早的开始找,按序找下一个开始时间大于上一个的结束时间的元素
//找到后,更新结束时间,继续上述操作
private static int f(int n,Job[] jobs){
    int cnt=1;
    int y=jobs[0].t;
    for(int i=0;i<n;i++){
        if(jobs[i].s>y){
        cnt++;
        y=jobs[i].t;
        }

    }
    return cnt;

}

}

//必须实现排序规则
public static class Job implements Comparable<Job>{
        int s;
        int t;

        public  Job(int s,int t){
        this.s=s;
        this,t=t;
        }

        public int compareTo(Job other){

        int x=this.t-other.t;
        if(x==0)
            return this.s-other.s;

        }else{

            return x;
        }



}


区间选点问题

给你几个线段,每个线段必须至少有多少个点位穿过,问至少需要几个点位


public class Case_区间选点{
    public static void main(String[] args){
    Scanner sc=new Scanner(System.in);
    int n=sc.nextInt();
    interval[] intervals=new interval[n];
        for(int i=0;i<n;i++){
            intervals[i]=new Inteval(sc.nextInt(),sc.nextInt(),sc.nextInt());


        }

        Arrays.sort(intervals);//按区间右键点排序
    int max=interval[n-1].t//右键最大值
    int[] axis=new int[max-1];//标记数轴上的点是否已经被选中
    for(int i=0;i<=n;i++){
    //查阅区间中有多少个点
    int s=intervals[i].s;//起点
    int t=intervals[i].t;//终点

    int cnt=sum(axis,s,t);//sums[t]-sums[s-1];//效率低
    //2.如果不够,从区间右键开始标记,遇标记过的就跳过
    intervals[i].c-=cnt;//需要新增点的数量
    while(interval[i].c>0){
        if(axis[t]==0){//从区间终点开始选点
            axis[t]=1;
            //updateSums(t,sums);//更新前缀和

            intervals[i].c--;
            t--;
        }else{//已经有这个点
                //跳过这个点
             t--;



    }
   }


    }


   System.out.println(sum(axis,0,max));
 }




private static void updateSum(int t,int[] sums){
        for(int i=t;i<sums.length;i++){
            sums[i]++;

        }

}

}

private static class Interval implenments Compareble<Interval>{
        int s;
        int t;
        int c;


        public interval(int s,int t,int c){
        this.s=s;
        this.t=t;
        this.c=c;

        }


        public int compareTo(Interval ){
        int  x=this.x-other.x;
        if(x==0){
        return this.s-other.s;
        }else{
            retrun x;
        }

        }

}



//也可用前缀和来表示,类似于树状数组
//区间和,前缀和->树状数组

区间覆盖问题

覆盖线段最少需要多=少区间来进行覆盖

找小于这个线段起点的所有区间,在这些区间里找终点最长的那一个区间,然后更新线段的起点start=上一个end+1,继续之前的操作,直到完全覆盖这个线段

start       start     start
    |---------------------|
|----|
  |----|    end
   |--------|          end
  |----|  |-------------|
|-------||--------|   |---------|
                            最后+1就行




public class Case_区间覆盖{
        public static void main(){
            Scanner sc=new Scanner(System.in);
            int N=sc.nextInt();
            int T=sc.nextInt();

            Job[] jobs=new Job[N];
            for(int i=0;i<N;i++){
                jobs[i]=new Job(sc.nextInt(),sc.nextInt(),sc.nextInt());


            }

            Arrays.sort(jobs);
           int start=1;
           int end=1;
           int ans=1;
           for(int i=0;i<N;i++){
                int s=jobs[i].s;
                int t=jobs[i].t;

                if(i==0&&s>1)break;//没有覆盖线段前面一部分的区间


                if(s<=start){
                    end=max(x,end);//更新更右的端点

                }else{//开始下一个区间
                ans++;//上一个目标覆盖已经完成
                start=end+1;//更新起点,设置一个新的覆盖起点
                    if(s<=start){
                    end=max(t,end);//找最远右端点
                    }else{//若是区间之间有空白,即一个区间结束,找不到下一个小于起点的区间,则直接退出
                    break;
                    }


                }
                if(end>=T){//超越了,线段最右端,全部覆盖,直接退出
                break;
                }

           }
           //所有区间都遍历完成,为完全覆盖
           if(end<T)
           System.out.println(-1);
           else
            System.out.println(ans);


        }

}





private static class Job implements Compareble<Job>{

        int s;
        int t;
        public Job(int s,int t){
            this.s=s;
            this.t=s;


        }

        //按照区间起点排序
        public int compareTo(Job other){
            int x=this.x;
            int y=this.y;
        }
        if(x==0){
        return this.t-other.t;
        }else{
        return x;

        }



}

贪心策略例4:字典序最小问题

给定一个定长为N的字符串S,构造一个字符串T.长度也为N
起初,T是一个空串,随后反复进行下列任意操作
1,从S的头部删除一个字符,加到T的尾部
2,从S的尾部删除一个字符,加到T的尾部

目标是最后生成的字符串T的字典序尽可能小
1<=N<2000
字符串S只包含大写英文字母
输出:字符串T

要求每80个字符换行输出

//ACDBCB-->ABCBCD

//ACDBCB
//BCBDCA

//这题唯一要考虑的就是如果两端相同,选哪端
public class Case_最小最小字典序{
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int N=sc.nextInt();
        StringBuilder ss=new StringBuilder();

        for(int i=0;i<N;i++){
            ss.append(sc.next());
        }
        //String s=sc.nextLine();
        f(ss.toString);

    }

    private static void f(String s){
        String s1=new StringBuilder(s).reverse().toString();
        int N=s.length();
        StringBuilder rs=new StringBuilder();
        int cnt=0;

        while(rs.length()<N){
         if(s.compareTo(s1)<=0){
         rs.append(s.charAt(0));
         s=s.substring(1);
         }else{
            rs.append(s1.charAt(0));
            s1=s1.subString(1);

         }
        //字符满80个就换行
         if(rs.length()%80==0){
           System.out.println(rs.substring(cnt*80),(cnt+1)*80);
           cnt++;
         }
        }

        //余数部分
        if(rs.length()>cnt*80){
        System.out.println(rs.substring(cnt*80));

        }

    }



}

背包问题及相关问题(最优装在问题,部分背包问题,乘船问题)

给出n个物体,第i个物体重量vi,选择尽量多的物体,使得总重量不超过C
public class Case_最有装载问题{
        public static void main(String[] args){
            Scanner sc=new Scanner();
            int n=sc.nextInt();
            int[] w=new int[n];
            for(int i=0;i<n;i++){
                w[i]=sc.nextInt();

            }
            int C=sc.nextInt();

            Arrays.sort(w);
            int ans=f(n,w,C);
            System.out.println(ans);
        }

       private static int f(int n,int[] w,int c){
       int sum=0;
       int cnt=0;
       for(int i=0;i<n;i++){
       sum+=w[i];
       if(sum<=c){
       cnt++;
       }else{
       break;
       }


       }
       return cnt;

       }


}

 

部分背包问题
给出n个物体,第i个物体重量wi,价值为vi,在总重量不超过C的情况下让总价值尽量高

每个物体不可以只取走有部分,价值和重量按比例计算

求最大总价值

注意:每个物体可以只拿一部分,因此一定可以让总重量恰好为C


public class Case_部分背包问题{
    //自己定int C;


   public static void main(String[] args){
            int[] w={1,2,3,4,5};
            int[] v={3,4,3,1,4};

            int n=w.length();
            Obj[] objs=new Obj[n];
            for(int i=0;i<n;i++){
            objs[i]=new Obj(w[i],v[i]);
            }

            Arrays.sort(objs);

            int c=C;
            int maxValue=0;
            for(int i=n-1;i>=0;i--){
                if(objs[i].w<=c){
                    maxValue+=obj[i].v;
                    c-=objs[i].w;
                }else{
                    maxValue+=objs[i].v*(c/pbjs[i].w);
                    break;
                }

            }

   }

   private static class Obj implements Comparable<Obj>{
        int w;
        int v;
        public Obj(int w,int v){
            this.w=w;
            this.v=v;

        }

        public double getPrice(){
            return v/(double)w;
        }

        public int compareTo(Obj o){
            if(this.getPrice()==o.getPrice())return 0;
            else if(this.getPrice()<o.getPrice())return -1;
            else return 1;
        }


   }


}

 

乘船问题
有n个人,第i个人重量为wi,每艘船的最大载重量均为C,且最多只能乘两人。用最少的船装在所有人。

贪心策略,考虑最轻的人i,如果每个人都无法和他一起坐船(重量和超过C),则唯一的方案是每个人坐一艘

求需要船的数量

public class Case_乘船问题{
    public static void main(String[] args){
    int[] w={1,2,3,4,5,6,7,8,9,10};

    int n=w.length;
    int c=10;
    Arrays.sort(w);

    int cntOfperson=n;
    int cntOfBoot=0;
    int p1=0;
    int p2=n-1;
    while(cntOfperson>0){
       if(p1+p2>c){//大的自己一艘,先走
       p2--;//大指针左移
       cntOfperson--;//剩余的人数-1
       cntOfBoot++;//船数+1
       }else{//小的加大的可以一起走
       p1++;//小指针右移
       p2--;//大指针左移
       cntOfperson-=2;//剩余的人数-2
        cntOfBoot++;//船数+1
       }
    }

    System.out.println(cntOfBoot);//最后输出船数

    }


}

 

贪心策略小结

-最优子结构:对比dfs,不是进行各种可选支路的是试探,而是当下
就可用某种策略决定选择,无需考虑未来(未来情况的演变也影响不了当下的选择)
-只要一直这么选下去,就能得出最优的解,每一步都是当下(子问题)的最优解,
结果是原问题的最有解,这叫做最优子结构
-更书面的说法:如果问题的一个最优解中包含了子问题的最有解,则该问题具有最优子结构
-具备这类结构的问题,可以用局部最优解来推导全局最有解,可以认为是一种剪枝法,是对"df遍历法"的优化
贪心:由上一步的最优解推导下一步的最优解,而上一步之前的(历史)最优解则不作保留//(选当下最优的选项,然后继续如此)

相关推荐

  1. 算法基础35贪心算法详解

    2024-04-03 22:28:01       15 阅读
  2. 算法基础36)动态规划dp经典问题详解

    2024-04-03 22:28:01       15 阅读
  3. 算法基础37)BFS与DFS

    2024-04-03 22:28:01       12 阅读
  4. 算法基础38)c++ STL

    2024-04-03 22:28:01       18 阅读
  5. 基础算法(二)#

    2024-04-03 22:28:01       23 阅读

最近更新

  1. Docker

    2024-04-03 22:28:01       0 阅读
  2. Vue中v-for和v-if优先级(2、3)

    2024-04-03 22:28:01       0 阅读
  3. 晚上定时编译android系统

    2024-04-03 22:28:01       1 阅读
  4. 摒弃传统分页:移动端开发中的无限滚动实现

    2024-04-03 22:28:01       1 阅读
  5. 程序人生 - (002)

    2024-04-03 22:28:01       1 阅读
  6. MacOS隐藏文件打开指南

    2024-04-03 22:28:01       1 阅读

热门阅读

  1. 初识Spring Cloud

    2024-04-03 22:28:01       15 阅读
  2. C++引用python代码

    2024-04-03 22:28:01       17 阅读
  3. 信奥赛一本通 【例4.2】天安门广场的面积

    2024-04-03 22:28:01       16 阅读
  4. pygame--坦克大战(二)

    2024-04-03 22:28:01       13 阅读
  5. 供应商管理软件:供应商绩效评估实用清单

    2024-04-03 22:28:01       12 阅读
  6. ChatGPT学术写作攻略:让论文更具深度

    2024-04-03 22:28:01       15 阅读
  7. 宝塔面板无法访问 404 not found

    2024-04-03 22:28:01       16 阅读
  8. Memcached 教程之 Memcached add 命令(六)

    2024-04-03 22:28:01       19 阅读