模版:贪心算法

125.耍杂技的牛

https://www.acwing.com/solution/acwing/content/845/

import java.util.*;


public class Main{

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        List list = new ArrayList();
        int n = scan.nextInt();
        for(int i = 0;i < n;i++)
        {
            int w = scan.nextInt();
            int s = scan.nextInt();
            list.add(new PIIs(w + s,w));
        }
        Collections.sort(list);
        int res = Integer.MIN_VALUE;
        int sum = 0;//记录危险值
        for(PIIs item : list)
        {
            int w = item.getSecond();
            int s = item.getFirst() - w;
            res = Math.max(res, sum - s);
            sum += w;
        }
        System.out.println(res);

    }

}



class PIIs implements Comparable{
    private int first;
    private int second;

    public int getFirst()
    {
        return this.first;
    }
    public int getSecond()
    {
        return this.second;
    }
    public PIIs(int first,int second)
    {
        this.first = first;
        this.second = second;
    }
    @Override
    public int compareTo(PIIs o) {
        // TODO 自动生成的方法存根
        return Integer.compare(first, o.first);
    }
}

905.区间选点

题目描述:
给定N个闭区间[ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。

输出选择的点的最小数量。

位于区间端点上的点也算作区间内。

算法分析

  • 1、将每个区间按右端点从小到大进行排序

  • 2、从前往后枚举每个区间,初始选定end值为无穷小

    • 若当前区间中包含该点end,则直接跳过
    • 否则,选择当前区间的右端点
  • 证明:

    • (1)找到cnt个点,满足题意情况,则最优解Ans <= cnt
    • (2)找到cnt个点,即找到cnt个区间,且区间从左到右依次排好,且没有相同的交集,则说明可能有区间没有被这cnt个点覆盖过,所以最优解Ans >= cnt
    • Ans == cnt,证毕

时间复杂度

Java 代码


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        List list = new ArrayList();
        int n = scan.nextInt();
        for(int i = 0;i < n;i++)
        {
            int L = scan.nextInt();
            int R = scan.nextInt();
            list.add(new PIIs(L,R));
        }
        //按右端点进行排序
        Collections.sort(list);
        int count = 0;
        int end = Integer.MIN_VALUE;
        for(PIIs item:list)
        {
            
            if(item.getFirst() > end) 
            {
                count ++;
                end = item.getSecond();
            }
            
        }
        System.out.println(count);
    }

}

class PIIs implements Comparable{
    private int first;
    private int second;
    
    public int getFirst()
    {
        return this.first;
    }
    public int getSecond()
    {
        return this.second;
    }
    public PIIs(int first,int second)
    {
        this.first = first;
        this.second = second;
    }
    
    @Override
    public int compareTo(PIIs o) {
        // TODO 自动生成的方法存根
        return Integer.compare(second, o.second);
    }
}

906. 区间分组

题目描述:
给定N个闭区间[ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。

输出最小组数。

算法分析

  • 1、将所有区间按左端点从小到大排序
  • 2、从前往后枚举每个区间,判断能否将其放到某个现有的组中,即是否存在区间的的左端点L > 所有组中右端点的最小值

    • 如果不存在这样的组,则开新组,然后再将其放进组中
    • 如果存在这样的组,则将其放在符合条件的组中,并更新当前组的右端点的值
  • 3、为了不用每次选择组时都遍历所有组,可以通过小根堆来维护所有组中的尾端

证明:

  • 1、按照上述存放,一定是一种合法的方案,则Ans <= cnt
  • 2、由于分了cnt个组,则说明一定存在cnt个区间含有公共点,则一定至少开cnt个组,则Ans >= cnt
    综合1、2可推出Ans == cnt

时间复杂度

Java 代码

import java.util.*;
public class Main{
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        List list = new ArrayList();
        int n = scan.nextInt();
        for(int i = 0;i < n;i++)
        {
            int L = scan.nextInt();
            int R = scan.nextInt();
            list.add(new PIIs(L,R));
        }
        //按左端点排序
        Collections.sort(list);
        //通过小根堆维护每组的尾端
        Queue minheap = new PriorityQueue();
        for(PIIs item : list)
        {
            if(minheap.isEmpty() || minheap.peek() >= item.getFirst())
                minheap.add(item.getSecond());
            else 
            {
                //更新小根堆
                minheap.poll();
                minheap.add(item.getSecond());
            }
        }
        System.out.println(minheap.size());
        
    }

}



class PIIs implements Comparable{
    private int first;
    private int second;
    
    public int getFirst()
    {
        return this.first;
    }
    public int getSecond()
    {
        return this.second;
    }
    public PIIs(int first,int second)
    {
        this.first = first;
        this.second = second;
    }
    
    @Override
    public int compareTo(PIIs o) {
        // TODO 自动生成的方法存根
        return Integer.compare(first, o.first);
    }
}


803.区间合并

给定 n 个区间 [li,ri],要求合并所有有交集的区间。

注意如果在端点处相交,也算有交集。

输出合并完成后的区间个数。

算法分析

  • 1、将每个区间按左端点从小到大进行排序
  • 2、如图所示,可分3种情况
    • 情况一:当前区间完全被上一区间覆盖,直接跳过
    • 情况二:将当前区间的右端点更新为上一区间的右端点,达到区间延长的效果
    • 情况三:当前区间的左端点严格大于上一区间的右端点,则表示该区间不能合并,更新区间且count++
模版:贪心算法_第1张图片
2df0af743fbf357f2e071728365db89.png

时间复杂度

Java 代码


import java.util.*;


public class Main{

    public static void main(String[] args) {
        List list = new ArrayList();
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        for(int i = 0;i < n;i++)
        {
            int L = scan.nextInt();
            int R = scan.nextInt();
            list.add(new PIIs(L,R));
        }
        //按左端点进行排序
        Collections.sort(list);
        int count = 0;
        int start = Integer.MIN_VALUE;
        int end = Integer.MIN_VALUE;
        for(PIIs item : list)
        {
            
            if(item.getFirst() > end)
            {
                count ++;
                start = item.getFirst();
                end = item.getSecond();
            }
            else
            {
                end = Math.max(end, item.getSecond());
            }
        }
        System.out.println(count);
    }

}



class PIIs implements Comparable{
    private int first;
    private int second;
    
    public int getFirst()
    {
        return this.first;
    }
    public int getSecond()
    {
        return this.second;
    }
    public PIIs(int first,int second)
    {
        this.first = first;
        this.second = second;
    }
    
    @Override
    public int compareTo(PIIs o) {
        // TODO 自动生成的方法存根
        return Integer.compare(first, o.first);
    }
}

148. 合并果子

题目描述:

在一个果园里,达达已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。

达达决定把所有的果子合成一堆。

每一次合并,达达可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。

可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。

达达在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以达达在合并果子时要尽可能地节省体力。

假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使达达耗费的体力最少,并输出这个最小的体力耗费值。

例如有3种果子,数目依次为1,2,9。

可以先将1、2堆合并,新堆数目为3,耗费体力为3。

接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。

所以达达总共耗费体力=3+12=15。

可以证明15为最小的体力耗费值。

算法分析

  • 典型的构造哈夫曼树的问题,只需每次找出最小的两个值合并成一个元素,该做法消耗体力总和最低

时间复杂度

Java 代码

import java.util.*;


public class Main{

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        Queue minheap = new PriorityQueue();
        int n = scan.nextInt();
        for(int i = 0;i < n;i++)
        {
            minheap.add(scan.nextInt());
        }
        int res = 0;
        while(minheap.size() >= 2)
        {
            int x = minheap.poll();
            int y = minheap.poll();
            int value = x + y;
            res += value;
            minheap.add(value);
        }
        System.out.println(res);
        
    }

}

907. 区间覆盖

题目描述:
给定N个闭区间[ai,bi]以及一个线段区间[s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。

输出最少区间数,如果无法完全覆盖则输出-1。

算法分析

  • 1、将所有区间按左端点从小到大进行排序
  • 2、从前往后枚举每个区间,在所有能覆盖start的区间中,选择右端点最大的区间,然后将start更新成右端点的最大值

证明

  • 在剩下所有能覆盖start的区间中,选择右端点最大的区间,则一定会比前面的选择最优,更快达到end,所以该做法一定是最优

时间复杂度

Java 代码


import java.util.*;


public class Main{

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        List list = new ArrayList();
        int start = scan.nextInt();
        int end = scan.nextInt();
        int n = scan.nextInt();
        for(int i = 0;i < n;i++)
        {
            int L = scan.nextInt();
            int R = scan.nextInt();
            list.add(new PIIs(L,R));
        }
        //按左端点排序
        Collections.sort(list);
        int res = 0;
        for(int i = 0;i < n;i++)
        {
            int j = i;
            int r = Integer.MIN_VALUE;//标识是否可以完全覆盖
            while(j < n && list.get(j).getFirst() <= start)
            {
                r = Math.max(r, list.get(j).getSecond());
                j ++;
            }
            //若不能完全覆盖
            if(r < start)
            {
                res = -1;
                break;
            }
            res ++;
            if(r >= end)
            {
                break;
            }
            start = r;
            i = j - 1;
        }
        System.out.println(res);
        
    }

}

class PIIs implements Comparable{
    private int first;
    private int second;
    
    public int getFirst()
    {
        return this.first;
    }
    public int getSecond()
    {
        return this.second;
    }
    public PIIs(int first,int second)
    {
        this.first = first;
        this.second = second;
    }
    
    @Override
    public int compareTo(PIIs o) {
        // TODO 自动生成的方法存根
        return Integer.compare(first, o.first);
    }
}


908. 最大不相交区间数量

题目描述:
给定N个闭区间[ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。

输出可选取区间的最大数量。

算法分析

(与AC905一样)https://www.acwing.com/solution/AcWing/content/5887/

  • 1、将每个区间按右端点从小到大进行排序

  • 2、从前往后枚举每个区间,初始选定end值为无穷小

    • 若当前区间中包含该点end,则直接跳过
    • 否则,选择当前区间的右端点
  • 证明:

    • (1)找到cnt个点,满足题意情况,则最优解Ans <= cnt
    • (2)找到cnt个点,即找到cnt个区间,且区间从左到右依次排好,且没有相同的交集,则说明可能有区间没有被这cnt个点覆盖过,所以最优解Ans >= cnt
    • Ans == cnt,证毕

时间复杂度

Java 代码


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        List list = new ArrayList();
        int n = scan.nextInt();
        for(int i = 0;i < n;i++)
        {
            int L = scan.nextInt();
            int R = scan.nextInt();
            list.add(new PIIs(L,R));
        }
        //按右端点进行排序
        Collections.sort(list);
        int count = 0;
        int end = Integer.MIN_VALUE;
        for(PIIs item:list)
        {
            
            if(item.getFirst() > end) 
            {
                count ++;
                end = item.getSecond();
            }
            
        }
        System.out.println(count);
    }

}

class PIIs implements Comparable{
    private int first;
    private int second;
    
    public int getFirst()
    {
        return this.first;
    }
    public int getSecond()
    {
        return this.second;
    }
    public PIIs(int first,int second)
    {
        this.first = first;
        this.second = second;
    }
    
    @Override
    public int compareTo(PIIs o) {
        // TODO 自动生成的方法存根
        return Integer.compare(second, o.second);
    }
}

913. 排队打水

题目描述:
有 n 个人排队到 1 个水龙头处打水,第 i 个人装满水桶所需的时间是 ti,请问如何安排他们的打水顺序才能使所有人的等待时间之和最小?

算法分析

  • 安排他们的打水顺序才能使所有人的等待时间之和最小,则需要将打水时间最短的人先打水

证明:

不妨设

  • (1) ≠ ≠ ≠ ... ≠
  • (2)~属于
  • (3) < < <... < ,

1、由i的任意性,打水的时间总和为 * (n - 1) + * (n - 2) + ... + * (n - n)
=n * ( + +... + ) - ( * 1 + * 2 + ... + * n)

2、即相当于求 * 1 + * 2 + ... + * n 的最大值

3、假设 , ,... , 是按自然顺序排好序时是最大值,即 = * 1 + * 2 + ... +

4、任意选择两项,,且 < ,c > 0,交换,位置得到, ,同时交换后不会对其他项造成影响
由于 * x + * (x + c) = * x + * x + * c > * x + * x + * c = * x + * (x + c),即交换之后比原来的值更小.由于选取的任意性可得假设成立.

时间复杂度

Java 代码

import java.util.*;


public class Main{

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        
        int n = scan.nextInt();
        int[] a = new int[n + 1];
        for(int i = 1;i <= n;i++)
        {
            a[i] = scan.nextInt();
        }
        Arrays.sort(a);
        long res = 0;
        for(int i = 1;i <= n;i++)
        {
            res += (a[i] * (n - i));
        }
        System.out.println(res);
        
    }

}

你可能感兴趣的:(模版:贪心算法)