手撸代码系列(十九)--贪心专题

本文通过实例探讨了动态规划与贪心算法在硬币支付、快速渡河、区间调度和区间选点问题中的应用,展示了如何利用局部最优解求解全局最优解的过程。通过解决这些问题,读者将理解这两种算法在实际场景中的策略选择和优化效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  动态规划和贪心算法都是一种递推算法,均用局部最优解来推导全局最优解;是对遍历解空间的一种优化,当问题具有最优子结构时,可用动规,而贪心是动规的的特例。

贪心:遵循某种规则,不断(贪心地)选取当前最优策略,最终找到最优解。

题目一: 硬币问题

问题描述:

    有1元,5元,10元,50元,100元,500元的硬币各 c 1 c_1 c1, c 5 c_5 c5, c 10 c_{10} c10, c 50 c_{50} c50, c 100 c_{100} c100, c 500 c_{500} c500枚;现在要使用这些硬币来支付A元,最少需要多少枚硬币?假定本题至少存在一种支付方案。

输入:

第一行有6个数字,分别代表从小到大6种面值的硬币的个数
第二行为A,代表需支付的A元

    0 = < c i = < 1 0 9 0=< c_i =< 10^9 0=<ci=<109
    0 = < A = < 1 0 9 0=< A =<10^9 0=<A=<109

输出:

最后的结果,一个整数

样例1:

输入:
	3 2 1 3 0 2
	620
输出:
	6

完整java代码

public class Main {
    static int[] cnts = new int[6]; // 存各种面值硬币对应的数量
    static int[] coins = {1, 5, 10, 50, 100, 500};
    public static void main(String[] args) {
        input();
    }
    private static void input() {
        Scanner scanner = new Scanner(System.in);
        for (int i = 0; i < cnts.length; i++) {
            cnts[i] = scanner.nextInt();
        }
        int A = scanner.nextInt();
        int result = f(A, 5);
        System.out.println(result);
        scanner.close();
    }
    private static int f(int A, int cur) {
        // A本身不合理了。
        if (A <= 0){
            return 0;
        }
        // 用的是1块的。
        if (cur == 0){
            return A;
        }
        int coinValue = coins[cur]; // 获取当前的面值大小
        int x = A / coinValue; // 金额里有多少个coinValue,理想值
        int cnt = cnts[cur]; // 当前面值的硬币有cnt个,实际值
        int temp = Math.min(x, cnt);
        return temp+f(A-temp*coinValue, cur-1);
    }
}
题目二:快速渡河

问题描述:

    N位旅行者乘一条船穿过一条河,但该船最多只能载两个人。因此,必须安排某种策略(需要人来回),以便所有人都能过河。每个人都有不同的划船速度;船中两个人的划船速度取决于较慢者的速度。问,最短时内完成所有人过河,最短时间是多少。

输入:

输入的第一行包含一个整数T(1 <= T <= 20),即测试用例的数量。
然后是T例。每个案例的第一行包含N,第二行包含N个整数,给出每个人过河的时间。
每个案例前面都有一个空白行。
人数不会超过1000,而且没有人花费超过100秒的时间穿越。

输出:

对于每个测试用例,打印一行,其中包含所有N个人穿过河流所需的总秒数。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

完整java代码

import java.util.Arrays;
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        input();
    }
    private static void input() {
        Scanner scanner = new Scanner(System.in);
        int T = scanner.nextInt(); // T 个测试用例
        for (int i = 0; i < T; i++) {
            int n = scanner.nextInt(); // 每个测试用例对应多少个数据
            int[] speed = new int[n];
            for (int j = 0; j < speed.length; j++) {
                speed[j] = scanner.nextInt();
            }
            // 排序
            Arrays.sort(speed);
            f(n, speed);
        }
        scanner.close();
    }
    private static void f(int n, int[] speed) {
        int left = n;
        int ans = 0;
        while (left > 0){
            // 如果仅有一个人,则直接相加
            if (left == 1){
                ans += speed[0];
                break;
            }else if (left == 2){
                // 如果仅有两个人,由于排过序,取大值即可
                ans += speed[1];
                break;
            }else if (left == 3){
                // 如果有三个人:1,2,3, 12->;  1<-;  13->;
                ans += speed[2] + speed[0] + speed[1];
            }else {
                // 12出发,1返回, 34出发,2返回, 12再出发
                int s1 = 2*speed[1] + speed[0] + speed[left-1]; // 两个最慢的人凑一组
                // 13出发,1返回,14出发,1返回,12再出发
                int s2 = speed[left-1] + speed[left-2] + 2*speed[0];  // 速度快的人带速度最慢的两个人。
                ans += Math.min(s1, s2);
                left -= 2; // 左侧的人数
            }
        }
        System.out.println(ans);
    }
}
题目三:区间调度

问题描述:

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

1 = < n = < 100000 1 =< n =< 100000 1=<n=<100000
1 = < s i = < t i < = 1 0 9 1 =< s_i =< t_i <= 10^9 1=<si=<ti<=109

输入:

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

输出:

	最后的结果,一个整数

样例:

输入:
	5
	1 2 4 6 8
	3 5 7 9 10
输出:
	3

完整java代码

import java.util.Arrays;
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        input();
    }
    private static void input() {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] start = new int[n];
        int[] end = new int[n];
        Jobs[] jobs = new Jobs[n];
        for (int i = 0; i < start.length; i++) {
            start[i] = scanner.nextInt();
        }
        for (int i = 0; i < end.length; i++) {
            end[i] = scanner.nextInt();
        }
        // 实现打包
        for (int i = 0; i < jobs.length; i++) {
            jobs[i] = new Jobs(start[i], end[i]);
        }
        Arrays.sort(jobs);
        int result = f(n, jobs);
        System.out.println(result);
        scanner.close();
    }
    private static int f(int n, Jobs[] jobs) {
        int cnt = 1;
        int y = jobs[0].end; // 第一个结束的任务,选
        for (int i = 0; i < n; i++) {
            if (jobs[i].start > y){
                cnt++; // 开始时间大于结束时间,选
                y = jobs[i].end;
            }
        }
        return cnt;
    }
    // 必须实现排序规则
    private static class Jobs implements Comparable<Jobs>{
        int start;
        int end;
        public Jobs(int start, int end){
            this.start = start;
            this.end = end;
        }@Override
        public int compareTo(Jobs o) {
            int x = this.end - o.end;
            if (x == 0){
                return this.start - o.start;
            }else {
                return x;
            }
        }
    }
}
题目四:区间选点

问题描述:
    给定 n n n 个闭合的整数区间 [ a i , b i ] [a_i,b_i] [aibi] n n n 个整数 c 1 , . . . , c n c _1,...,c_n c1...cn

    编写一个程序:从标准输入读取间隔数,它们的终点和整数 c 1 , . . . , c n c _1,...,c_n c1...cn, 计算整数集合 Z Z Z 的最小尺寸,它至少具有 c i c_i ci 公共元素的间隔 [ a i , b i ] [a_i,b_i] [aibi] ,对于每个 i = 1 , 2 , 3 , . . . , n i = 1, 2, 3, ... , n i=1,2,3,...,n ;将答案写入标准输出。

输入:
    输入的第一行包含整数 n ( 1 < = n < = 50000 ) n(1 <= n <= 50000) n1<=n<=50000 ,表示 间隔数。以下 n n n 行描述了间隔。输入的第 ( i + 1 ) (i + 1) i+1行包含由单个空格分隔的三个整数 a i a_i ai b i b_i bi c i c_i ci,并且使得 0 < = a i < = b i < = 50000 0 <= a_i <= b_i <= 50000 0<=ai<=bi<=50000 并且 1 < = c i < = b i − a i + 1 1 <= c_i <= b_i-a_i + 1 1<=ci<=biai+1 产量。

输出:

    输出恰好包含一个等于集合 Z Z Z 的最小尺寸的整数,该集合 Z Z Z 至少与区间 [ a i , b i ] [a_i,b_i] [aibi] 共享 c i c_i ci 元素。

样例:

输入:
	5
	3 7 3
	8 10 3
	6 8 1
	1 3 1
	10 11 1
输出:
	6
思路:根据区间的终点排序,从左到右遍历,尽量选择靠右的点。注意不能重复选点。

完整java代码

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

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        Interval[] intervals = new Interval[n];
        for (int j = 0; j < n; j++) {
            intervals[j] = new Interval(scanner.nextInt(), scanner.nextInt(), scanner.nextInt());
        }
        Arrays.sort(intervals);
        int max = intervals[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 count = sum(axis, s, t);
            intervals[i].c -= count;
            while (intervals[i].c > 0){
                if (axis[t]==0){
                    axis[t] = 1;
                    intervals[i].c--;
                    t--;
                }else {
                    t--;
                }
            }
        }
        System.out.println(sum(axis,0, max));
    }
    private static int sum(int[] axis, int s, int t) {
        int sum = 0;
        for (int i = s; i <= t; i++) {
            sum += axis[i];
        }
        return sum;
    }
    private static class Interval implements Comparable<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 other) {
            int x = this.t - other.t;
            if (x==0) {
                return this.s-other.s;
            }else {
                return x;
            }
        }
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值