算法学习--动态规划与贪心算法

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

动态规划

1. 动态规划的思想

  • 动态规划(DP)其实是运筹学的一种最优化方法,只不过在计算机问题上应用比较多,比如说让你求最长递增子序列呀,最小编辑距离等等。
  • 动态规划问题一般形式是求最值,即求最优子结构,求解动态规划的核心问题是穷举;
  • 但是动态规划的穷举有其自身的特点,因为这类问题通常存在重复子问题,直接穷举会照成大量的资源浪费,效率也会很低;
  • 对于重复的子问题,我们可以使用一张表DP table来优化穷举过程,避免不必要的计算
  • 动态规划实际上就是填表的问题,填表就需要列出正确的状态转移方程

2. 如何求解动态规划

2.1 首先从斐波那契数列开始

  1. 暴力递归的形式
int f(int N) {
    if (N == 1 || N == 2) return 1;
    return f(N - 1) + f(N - 2);
}
  • 这种方式就会如上文所说造成大量的重复计算,如求f(20)就必须先求出f(19)f(18),而求解f(19)又需要求f(18)f(17),由此可以看出,f(18)就需要求2次,依次往下递归,直到遇到f(1)f(2)才会返回结果,显然子问题的的规模是O(2^n),指数级增长
  1. 带DP table的解法
  • 带DP table的解法一般是自顶向下的,有小的开始往上推出大的,直到推出想要的结果,于是,斐波那契数列可以改写成如下:
public static int Fib(int n){
    int[] dp = new int[n+1];
    dp[0] = 0;
    dp[1] = 1;
    dp[2] = 1;
    int i = 3;
    while (i <= n){
        dp[i] = dp[i-1] + dp[i-2];
        i++;
    }
    return dp[n];
}
  • 这里可以看出,其状态方程为:
    f(n) = 0 ; n = 0
    f(n) = 1 ; n = 1,2
    f(n) = f(n-1) + f(n-2) ;n > 2
  • 什么是状态转移方程;简单来说,就是当我们需要得到状态n,这个状态n又是有状态n-1和状态n-2相加转移而来,这就叫状态转移,转移的方式就叫状态转移方程
  • 经过使用DP table求解可以看出,问题规模降为了O(n)
  1. 最优解法
  • 有状态方程可以看出,当前状态仅由前两个之和维护,所以其实并不需要维护一整个DP table,只需要存储之前的两个状态即可,进一步将空间复杂度将为了O(1)
public static int Fibonacci(int n) {
    if(n == 0) return 0;
    int a = 1;
    int b = 1;
    while (n > 2){
        a = a + b;
        b = a - b;
        n--;
    }
    return a;
}
  • 这个例子没有涉及到求最值,严格来说并不算动态规划,旨在演示算法设计螺旋上升的过程

2.2 0-1背包问题

  • 有n个重量和价值分别为wi和vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值
  • 因为对每个物品只有拿(1)或不拿(0)两种情况,所以又叫0-1背包问题
  1. 用递归的思想去解决
public class Q1 {
    public static int[] w = {2, 1, 3, 2}; //物品重量
    public static int[] v = {3, 2, 4, 2}; //物品价值
    public static int n = 4; //物品数量
    public static int W = 5; //背包容量

    public static void main(String[] args) {
        int ww = W;
        int ans = dfs(0, ww);
        System.out.println(ans);
    }

    public static int dfs(int i, int W){
        if(W <= 0) return 0;
        if(i == n) return 0;
        int v2 = dfs(i+1, W); //不选当前物品
        if(W >= w[i]){
            int v1 = v[i] + dfs(i+1, W-w[i]); //选当前物品
            return max(v1, v2);
        }else{
            return v2;
        }
    }
}

  • dfs算法可以求出结果,但存在大量的重叠子问题,求f(x,y);我们可以维护一张二维数组,来表示f(x,y)对应的值,这样再下次需要值的时候直接去二维数组拿,而不是再去求解
  • 主要就是在返回之前先记录
public class Q1 {
    public static int[] w = {2, 1, 3, 2}; //物品重量
    public static int[] v = {3, 2, 4, 2}; //物品价值
    public static int n = 4; //物品数量
    public static int W = 5; //背包容量
    public static int[][] rec; //用一张表维护状态

    public static void main(String[] args) {
       rec = new int[n][W+1];
        //将每一个值都先填为-1
        for (int i = 0; i < rec.length; i++) {
            Arrays.fill(rec[i], -1);
        }
        ww = W;
        ans = m(0, ww);
        System.out.println(ans);
    }

    public static int m(int i, int ww){
        if(ww <= 0) return 0;
        if(i == n) return 0;
        //先查表
        if(rec[i][ww] >= 0){
            return rec[i][ww];
        }
        int v2 = m(i+1, ww); //不选
        int ans;
        if(ww >= w[i]){
            int v1 = v[i] + m(i+1, ww-w[i]); //选
            ans = max(v1, v2);
        }else{
            ans = v2;
        }
        //记录
        rec[i][ww] = ans;
        return ans;
    }
}
/*
[-1, -1, -1, -1, -1, 7]
[-1, -1, -1, 4, -1, 6]
[-1, -1, 2, 4, 4, 6]
[-1, 0, 2, 2, 2, 2]
*/
  1. DP思想去解决
public class Q1 {
    public static int[] w = {2, 1, 3, 2}; //物品重量
    public static int[] v = {3, 2, 4, 2}; //物品价值
    public static int n = 4; //物品数量
    public static int W = 5; //背包容量

    public static void main(String[] args) {
        System.out.println(dp());
    }

    public static int dp(){
        int[][] dp = new int[n][W+1];
        //第0列全是0
        for (int i = 0; i < dp.length; i++) {
            dp[i][0] = 0;
        }
        //初始化第0行
        for (int i = 0; i < dp[0].length; i++) {
            if(i >= w[0]){
                dp[0][i] = v[0];
            }else{
                dp[0][i] = 0;
            }
        }
        //从第1行开始
        for (int i = 1; i < dp.length; i++) {
            //从第一列开始,j代表列,也是背包的剩余容量
            for (int j = 1; j < dp[0].length; j++) {
                //如果能拿当前的物品
                if(j >= w[i]){
                    int v1 = v[i] + dp[i-1][j-w[i]]; //选了当前物品,则需要去dp表找剩余容量可容纳的值
                    int v2 = dp[i-1][j]; //不选当前物品,直接是上一行的数字
                    dp[i][j] = max(v1, v2);
                }else{ //当前物品直接大于了剩余容量,拿不起该物品
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[dp.length-1][dp[0].length-1];
    }
}
/*
[0, 0, 3, 3, 3, 3]
[0, 2, 3, 5, 5, 5]
[0, 2, 3, 5, 6, 7]
[0, 2, 3, 5, 6, 7]
*/

2.3 钢条切割问题

  • 不同长度的钢条的售价不同,现有一根长度为n的钢条和一个价格表,求怎么切能卖出最多钱
  1. 递归实现
public class Q2 {
    public static int[] price = {1,5,8,16,10,17,17,20,34,30};
    public static int length = 10;

    public static void main(String[] args) {
        System.out.println(r(length));
    }
    private static int r(int x){
        if(x == 0) return 0;
        int ans = 0;
        for (int i = 1; i <= x; i++) {
            int v = price[i-1] + r(x-i);
            ans = Math.max(v, ans);
        }
        return ans;
    }
}
  • 用一个表记录
public class Q2 {
    public static int[] price = {1,5,8,16,10,17,17,20,34,30};
    public static int length = 10;
    public static int[] p = new int[length+1];

    public static void main(String[] args) {
        System.out.println(r(length));
    }
    private static int r(int x){
        if(x == 0) return 0;
        int ans = 0;
        Arrays.fill(p, -1);
        for (int i = 1; i <= x; i++) {
            if(p[x-i] == -1){
                p[x-i] = r(x-i);
            }
            int v = price[i-1] + p[x-i];
            ans = Math.max(v, ans);
        }
        p[x] = ans;
        return ans;
    }
}
  1. dp解决
public class Q2 {
    public static int[] price = {1,5,8,16,10,17,17,20,34,30};
    public static int length = 10;
    public static int[] vs = new int[length+1]; //每一个长度的最佳切割方案

    public static void main(String[] args) {
        System.out.println(dp());
    }
    
    public static int dp(){
        vs[0] =0;
        for (int i = 1; i <= length; i++) { //拥有钢条的长度
            for (int j = 1; j <= i; j++) { //保留j为整段
            	//最佳方案要么不切,要么切成其他整数段
                vs[i] = Math.max(price[j-1]+vs[i-j], vs[i]);
            }
        }
        System.out.println(Arrays.toString(vs));
        return vs[length];
    }
}

2.4 三角形路径

/*
数字三角形:
         7
       3   8
     8   1   0
   2   7   4   4
 4   5   2   6   5
 求最大的路径
* */
public class Q3 {
    public static int[][] triangle = {
                 {7},
                {3,8},
               {8,1,0},
              {2,7,4,4},
             {4,5,2,6,5},
    };

    public static int[][] dp = new int[triangle.length][triangle.length];

    public static void main(String[] args) {
        int dfs = dfs(triangle, 0, 0);
        System.out.println(dfs);
        int dp = dp();
        System.out.println(dp);
    }

    private static int dp(){
        for (int i = 0; i < dp[0].length; i++) {
            dp[dp.length-1][i] = triangle[dp.length-1][i];
        }
        for (int i = dp.length-2; i >= 0; i--) {
            for (int j = 0; j < triangle[i].length; j++) {
                dp[i][j] = Math.max(triangle[i][j]+dp[i+1][j], triangle[i][j]+dp[i+1][j+1]);
            }
        }
        for (int[] ints : dp) {
            System.out.println(Arrays.toString(ints));
        }
        return dp[0][0];
    }

    private static int dfs(int[][] triangle, int i, int j){
        if(i == triangle.length-1) {
            return triangle[i][j];
        }else{
            return triangle[i][j] + Math.max(dfs(triangle, i+1, j), dfs(triangle, i+1, j+1));
        }
    }
}

2.5 最长公共子串

/*
最长公共子序列
* */
public class Q4 {
    private static String s1 = "ab24c";
    private static String s2 = "a1b2c3";

    private static int[][] dp = new int[s1.length()+1][s2.length()+1];

    public static void main(String[] args) {
        System.out.println(dfs(s1, s2).toString());
        String s = dp();
        System.out.println(s);
    }

    private static String dp(){
        int flag = 0;
        //初始化dp数组的第一列
        for (int i = 1; i < dp.length; i++) {
            if(flag == 1){
                dp[i][1] = 1;
            }else if(s1.charAt(i-1) == s2.charAt(0)){
                dp[i][1] = 1;
                flag = 1;
            }else{
                dp[i][1] = 0;
            }
        }
        //初始化第一行
        flag = 0;
        for (int i = 1; i < dp[0].length; i++) {
            if(flag == 1){
                dp[1][i] = 1;
            }else if(s2.charAt(i-1) == s1.charAt(0)){
                dp[1][i] = 1;
                flag = 1;
            }else{
                dp[1][i] = 0;
            }
        }
        for (int i = 2; i < dp.length; i++) {
            for (int j = 2; j < dp[0].length; j++) {
                int maxOfLeftAndUp = Math.max(dp[i-1][j], dp[i][j-1]);
                if(s1.charAt(i-1) == s2.charAt(j-1)){
                    dp[i][j] = Math.max(maxOfLeftAndUp, dp[i-1][j-1]+1);
                }else{
                    dp[i][j] = maxOfLeftAndUp;
                }
            }
        }
        for (int[] ints : dp) {
            System.out.println(Arrays.toString(ints));
        }
        //得到了dp数组,还需要解析数组得到子序列
        //从右下角开始看,如果当前得到的值是有左对角+1得来,则表示该点在子串中
        int m = s1.length();
        int n = s2.length();
        StringBuilder stringBuilder = new StringBuilder();
        while (m > 0 && n > 0){
            //比左和上大,则只会来自于对角+1
            if(dp[m][n] > Math.max(dp[m-1][n], dp[m][n-1])){
                stringBuilder.insert(0, s1.charAt(m-1));
                m--;
                n--;
            }else{ //该点来自左或上的大者,向那边走
                if(dp[m-1][n] > dp[m][n-1]){
                    m--;
                }else{
                    n--;
                }
            }
        }
        return stringBuilder.toString();
    }

    private static ArrayList<Character> dfs(String s1, String s2){
        int len1 = s1.length();
        int len2 = s2.length();
        ArrayList<Character> res = new ArrayList<>();
        for (int i = 0; i < len1; i++) {
            ArrayList<Character> list = new ArrayList<>();
            for (int j = 0; j < len2; j++) {
                if(s1.charAt(i) == s2.charAt(j)){ //找到相同字符
                    list.add(s1.charAt(i));
                    list.addAll(dfs(s1.substring(i+1), s2.substring(j+1)));
                    break;
                }
            }
            if(list.size() > res.size()){
                res = list;
            }
        }
        return res;
    }
}

2.6 完全背包问题

  • 相较于01背包问题,现在每种物品可以拿多个,求能拿的最大价值
/*
相较于01背包一个物品只能拿一个,现在每个物品可以拿多个
求最大价值
* */
public class Q5_完全背包问题 {
    public static int[] w = {2, 2, 3, 2}; //物品重量
    public static int[] v = {3, 2, 4, 2}; //物品价值
    public static int n = 4; //物品数量
    public static int W = 11; //背包容量
    public static int[][] rec = new int[n][W+1]; //用一张表维护状态

    public static void main(String[] args) {
        System.out.println(dp());
    }

    private static int dp(){
        //初始化第一列都为0;
        for (int i = 0; i < rec.length; i++) {
            rec[i][0] = 0;
        }
        //初始化第一行
        for (int i = 0; i < rec[0].length; i++) {
            rec[0][i] = (i / w[0]) * v[0];
        }
        for (int i = 1; i < rec.length; i++) {
            for (int j = 1; j < rec[0].length; j++) {
                if(j >= w[i]) {
                    int v1 = v[i] + rec[i][j - w[i]];
                    int v2 = rec[i - 1][j];
                    rec[i][j] = Math.max(v1, v2);
                }else{
                    rec[i][j] = rec[i-1][j];
                }
            }
        }
        for (int[] ints : rec) {
            System.out.println(Arrays.toString(ints));
        }
        return rec[n-1][W];
    }
}

2.7 最长递增子序列

/*
求数组中最长的递增子序列
如:4,2,3,1,5的最长递增子序列为2,3,5
* */
public class Q6_最长递增子序列 {
    public static void main(String[] args) {
        /*int n = 10;
        int[] arr = new int[n];
        Random random = new Random();
        for (int i = 0; i < n; i++) {
            arr[i] = random.nextInt();
        }*/
        int[] arr = {4,2,3,1,5,6,4,8,5,9};
        System.out.println(violent(arr));
        System.out.println(dp(arr));
        System.out.println(dp2(arr));
    }

    //暴力求解
    private static int violent(int[] arr){
        int max = 0;
        for (int i = 0; i < arr.length; i++) {
            int curMax = arr[i];
            int nowMax = 0;
            for (int j = i; j < arr.length; j++) {
                if(arr[j] >= curMax){
                    nowMax++;
                    curMax = arr[j];
                }
            }
            max = Math.max(max, nowMax);
        }
        return max;
    }

    //动态规划求解
    public static int dp(int[] arr){
        int[] dp = new int[arr.length];
        dp[0] = 1;
        for (int i = 1; i < dp.length; i++) {
            int count = 1;
            for (int j = i-1; j >= 0; j--) {
                if(arr[j] < arr[i]){
                    count = Math.max(count, dp[j]+1);
                }
            }
            dp[i] = count;
        }
        System.out.println(Arrays.toString(dp));
        Arrays.sort(dp);
        return dp[dp.length-1];
    }

    //DP解法2
    public static int dp2(int[] arr){
        int[] dp = new int[arr.length+1];
        dp[1] = arr[0]; //初始化长度为1的递增子序列,初始化为第一个元素
        int p = 1; //记录dp更新的最后位置
        for (int i = 1; i < arr.length; i++) {
            if(arr[i] > dp[p]){ //当前数字比dp前面的数字都要大,直接将这个数加入dp数组
                dp[p+1] = arr[i];
                p++;
            }else{
                //替换dp中比这个值的大的第一个数
                for (int j = 0; j <= p; j++) {
                    if(dp[j] > arr[i]){
                        dp[j] = arr[i];
                        break;
                    }
                }
            }
        }
        return p;
    }
}

贪心算法

1. 贪心策略

  • 遵循某种规则,不断地选取当前最优策略,最终的解作为最优解
  • 当前最优并不一定是全局最优

2. 例题

2.1 支付问题

  1. 题目描述
  • 现有1,5,10,50,100,500面值的硬币各c1,c5,c10,c50,c100,c500枚,现需要用这些硬币来支付A元,最少需要多少枚硬币
  • 输入:第一行为一个数组,分别代表各面值硬币的数量;第二行为一个数字,表示需要支付的金额A
  • 输出:一个数字,表示最少需要支付的硬币个数
  1. 策略:每次支付可以支付的最大面额
  2. 代码实现
public class Q1 {
    private static int[] coins = {1, 5, 10, 50, 100, 500};
    private static int[] cnts = new int[6]; //面值对应的个数
    private static int A;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int i = 0;
        while (i < 6){
            cnts[i] = scanner.nextInt();
            i++;
        }
        A = scanner.nextInt();
        System.out.println(f(A, 5));
    }
    private 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; 
        //查看当前的该面值的个数
        int cnt = cnts[cur];
        //如果个数足够,直接拿出这么多个,不够这么多个,全部拿出
        int t = Math.min(x, cnt); 
        //处理剩下的,剩下的金额为减去最大的金额*个数后的金额,最大的面值变成了第二大的
        return t + f(A-t*coinValue, cur-1); 
    }
}

2.2 过河问题

  1. 题目描述
  • 有n个人需要过河,只有一艘船,每次只能坐2个人,每个人划船的速度又不同,每次过河的时间以慢的人为准,求如何让这群人以最快的速度过河
  • 输入:第一行一个数字,表示人数;第二行一个数组,表示每个人的速度
  • 输出:一个数字,表示最短时间
  1. 策略
  • 假设4个人过河,速度分别为a,b,c,d,设a<b<c<d;
  • 方法1:每次都让最快的带,耗时为b+a+c+a+d=2a+b+c+d
  • 方法2:先让最快的两个过去,最快的回来,然后让最慢的2个过去,第一趟过去的第二快的回来,再回去,耗时为b+a+d+b+b=a+3b+d
  • 两种比较,则最终会比较a+c?2b,可以看出,谁更小就选谁
  1. 代码实现
public class Q2 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] speed = new int[n];
        for (int i = 0; i < n; i++) {
            speed[i] = scanner.nextInt();
        }
        //先排序
        Arrays.sort(speed);
        System.out.println(f(n, speed));
    }

    private static int 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){ //还剩两个人,最后只剩2个人只可能剩1,2
                ans += speed[1];
                break;
            }else if(left == 3){ //还剩3个人,只可能是这个结果
                ans += speed[0] + speed[1] + speed[2];
                break;
            }else{ //超过3个人,就要分情况
            	//这里都没有加speed[1]是因为left==1时加了
                //1,2出发,1返回,最后两个出发,2返回
                int s1 = speed[1] + speed[0] + speed[left-1] + speed[1];
                //每次让1返回
                int s2 = speed[left-1] + speed[left-2] + 2*speed[0];
                ans += Math.min(s1,s2);
                left -= 2;
            }
        }
        return ans;
    }
}

2.3 区间调度问题

  1. 题目描述
  • 有n项工作,每项工作分别在si时间开始,在ti时间结束;对于每项工作,都可以选是否参与,只要参与便必须全程参与,而且同一时间内只能参与一项工作,问最多能参与几项工作
  • 输入:第一行一个数字n,代表工作总数,第二行一个数组,表示n个工作的开始时间,第三行一个数组,表示工作的结束时间
  • 输出:一个数字,代表最多项目数
  1. 策略
  • 总是选结束时间最早的
  1. 代码实现
public class Q3 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] s = new int[n];
        int[] t = new int[n];
        for (int i = 0; i < n; i++) {
            s[i] = scanner.nextInt();
        }
        for (int i = 0; i < n; i++) {
            t[i] = scanner.nextInt();
        }
        Job[] jobs = new Job[n];
        for (int i = 0; i < n; i++) {
            jobs[i] = new Job(s[i], t[i]);
        }
        //根据结束时间排序
        Arrays.sort(jobs);
        System.out.println(f(n, jobs));
    }

    private static int f(int n, Job[] jobs){
        int cnt = 1;
        //最早结束的项目
        int job = jobs[0].t;
        for (int i = 0; i < n; i++) {
            //开始时间比上一个项目结束时间晚的项目,
            //由于已经排过序,所以找到的第一个一定是符合条件的项目中,结束时间最早的
            if(jobs[i].s > job){
                cnt++;
                job = jobs[i].t;
            }
        }
        return cnt;
    }

    //通过一个内部类来绑定开始时间和结束时间
    private static class Job implements Comparable<Job>{
        public int s;
        public int t;

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

        @Override
        public int compareTo(Job o) {
            return (this.t - o.t == 0) ? (this.s - o.s) : (this.t - o.t);
        }
    }
}

2.4 区间选点问题

  1. 题目描述
  • 有若干时间区间,求出最少需要几个点可以覆盖全部区间
  1. 策略
  • 按结束时间排序,然后选结束时间早的,如果某个结束时间在另一个区间内,跳过该区间
  1. 代码实现
public class Q4 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] start = new int[n];
        int[] end = new int[n];
        for (int i = 0; i < n; i++) {
            start[i] = scanner.nextInt();
        }
        for (int i = 0; i < n; i++) {
            end[i] = scanner.nextInt();
        }
        Time[] times = new Time[n];
        for (int i = 0; i < n; i++) {
            times[i] = new Time(start[i], end[i]);
        }
        Arrays.sort(times);
        int count = 1;
        for (int i = 1; i < n; i++) {
            if(times[i-1].end >= times[i].start && times[i-1].end <= times[i].end){
                i++;
            }
            count++;
        }
        System.out.println(count);
    }


    public static class Time implements Comparable<Time>{
        public int start;
        public int end;

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

        @Override
        public int compareTo(Time o) {
            return (this.end - o.end == 0) ? (this.start - o.start) : this.end - o.end;
        }
    }
}

2.5 区间选点加强版

  1. 题目描述
  • 有n个区间,每个区间上必须要有m个点,求最少需要多少个点满足条件
  1. 策略
  • 按结束时间排序,选最后一个开始,每个区间要先判断已选过的点数
  1. 代码实现
public class Q5 {
    public static void main(String[] args) {
        /*Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        Time[] times = new Time[n];
        for (int i = 0; i < n; i++) {
            int start = scanner.nextInt();
            int end = scanner.nextInt();
            int num = scanner.nextInt();
            times[i] = new Time(start, end, num);
        }*/
        int n = 5;
        Time[] times = new Time[n];
        times[0] = new Time(3,7,3);
        times[1] = new Time(8,10,3);
        times[2] = new Time(6,8,1);
        times[3] = new Time(1,3,1);
        times[4] = new Time(10,11,1);
        Arrays.sort(times);
        ArrayList<Integer> list = new ArrayList<>();
        int max = times[n-1].end;
        int[] axis = new int[max+1]; //标记数轴上的点是否被选中
        for (int i = 0; i < n; i++) {
            int s = times[i].start; //起点
            int t = times[i].end;  //终点
            int cnt = sum(axis, s, t); //找到这个区间已经选点的数量
            times[i].num -= cnt; //需要新增的点的数量
            while (times[i].num > 0){
                if(axis[t] == 0){//从区间终点开始选值
                    axis[t] = 1; //将该点置为1,表示已选中
                    times[i].num--; //需要标记的点变少一个
                    t--;
                }else { //这个点是1,本来就选值过
                    t--;
                }
            }
        }
        System.out.println(sum(axis, 0, max));
        System.out.println(Arrays.toString(axis));
    }

    //统计数轴s-t上已经有的点数,因为只有0和1,统计1的个数只需要全部就相加
    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;
    }

    public static class Time implements Comparable<Time>{
        public int start;
        public int end;
        public int num;

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

        @Override
        public int compareTo(Time o) {
            return (this.end - o.end == 0) ? (this.start - o.start) : this.end - o.end;
        }
    }
}

2.6 区间覆盖问题

/*
区间覆盖问题
输入:
    数字n,表示有几个时段,数字t,表示需要覆盖[1-t]时间段
    n个数组,一个数组2个数字,表示开始与结束时间
输出:
    最少选几个时间段可以覆盖[1-t]
* */
public class Q6 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m =scanner.nextInt();
        Job[] jobs = new Job[n];
        for (int i = 0; i < n; i++) {
            int start = scanner.nextInt();
            int end = scanner.nextInt();
            jobs[i] = new Job(start, end);
        }
        //按起点排序
        Arrays.sort(jobs);
        int start = 1; //初始化起点
        int end = 1; //初始化终点
        int ans = 1;
        for (int i = 0; i < n; i++) {
            int s = jobs[i].start;
            int t = jobs[i].end;
            //最开始的时间的起点>0,不可能覆盖完全了
            if(i == 0 && s > 1){
                break;
            }
            //找起点不大于start的区间中,end最大的
            if(s <= start){
                end = max(t, end);
            }else{
                ans++;
                start = end + 1;
                if(s <= start){
                    end = max(t, end);
                }else{
                    break;
                }
            }
            if(end >= m){
                break;
            }
        }
        if(end < m){
            System.out.println(-1);
        }else{
            System.out.println(ans);
        }
    }


    public static class Job implements Comparable<Job>{
        public int start;
        public int end;

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

        //按区间的起点来排序
        @Override
        public int compareTo(Job o) {
            return (this.start-o.start==0)?(this.end-o.end):(this.start-o.start);
        }
    }
}

2.7 字典序最小问题

/*
输入一个长度为N字符串S,构造一个长度也为N的字符串T;
起初,T是空串,随后反复进行下列任意操作:
    1. 从S的头部删除一个元素加到T的尾部
    2. 从S的尾部删除一个元素加到T的头部
目标是要求最后生成的T字典序最小
* */
public class Q7 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String s = scanner.nextLine();
        String f = f(s);
        System.out.println(f);
    }

    private static String f(String s){
        String s1 = new StringBuilder(s).reverse().toString();
        int N = s.length();
        StringBuilder res = new StringBuilder();
        while (res.length() < N){
            if(s.compareTo(s1) <= 0){
                res.append(s.charAt(0));
                s = s.substring(1);
            }else{
                res.append(s1.charAt(0));
                s1 = s1.substring(1);
            }
        }
        return res.toString();
    }
}

2.8 背包问题

  1. 求容量为m的背包最多能装几件物品
/*
有n个物品,重量分别为wi,先有一个容量为m的背包
求最多可以装几件物品
* */
public class Q8 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String[] strings = scanner.nextLine().split(" ");
        int[] w = new int[strings.length];
        for (int i = 0; i < strings.length; i++) {
            w[i] = Integer.parseInt(strings[i]);
        }
        int m = scanner.nextInt();
        int count = 0;
        int sum = 0;
        Arrays.sort(w);
        for (int i = 0; i < w.length; i++) {
            if(sum + w[i] <= m){
                count++;
                sum += w[i];
            }else{
                break;
            }
        }
        System.out.println(count);
    }
}
  1. 尽量让装进背包的物品价值最高
/*
n个物品,重量为wi,价值为vi,背包总容量为c
每个物体可以只取走一部分,价值和重量按比例计算
求能装进背包的最大总价值
(因为可以只取走一部分,所有总重量一定为c)
* */
public class Q9 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt(); //物品的总数
        double[] w = new double[n]; //每个物品的重量
        double[] v = new double[n]; //每个物品对应的价值
        Good[] goods = new Good[n];
        for (int i = 0; i < n; i++) {
            w[i] = scanner.nextDouble();
        }
        for (int i = 0; i < n; i++) {
            v[i] = scanner.nextDouble();
        }
        for (int i = 0; i < n; i++) {
            goods[i] = new Good(w[i], v[i]);
        }
        //按单位价格排序
        Arrays.sort(goods);
        /*for (Good good : goods) {
            System.out.println(good);
        }*/
        double weight = scanner.nextDouble();
        double remain = weight;
        double price = 0;
        for (int i = 0; i < n; i++) {
            if(remain == 0){
                break;
            }
            if(remain >= goods[i].weight){
                remain = remain - goods[i].weight;
                price = price + goods[i].value;
            }else{
                price += remain*goods[i].price;
                remain = 0;
            }
        }
        System.out.println(price);
    }


    public static class Good implements Comparable<Good>{
        public double weight;
        public double value;
        public double price;

        public Good(double weight, double value) {
            this.weight = weight;
            this.value = value;
            this.price = value / weight;
        }

        @Override
        public int compareTo(Good o) {
            if(this.price > o.price){
                return -1;
            }else if(this.price < o.price){
                return 1;
            }else{
                if(this.weight > o.weight){
                    return -1;
                }
            }
            return 0;
        }

        @Override
        public String toString() {
            return "Good{" +
                    "weight=" + weight +
                    ", value=" + value +
                    ", price=" + price +
                    '}';
        }
    }
}

2.9 乘船问题

/*
乘船问题,有n个人,体重为wi;
现又无数个载重为k的船,每个船最多只能坐2个人
问最少几辆船可以让所有人过河
* */
public class Q10 {
    public static void main(String[] args) {
        Random random = new Random();
        int n = 10;
        int[] weight = new int[n];
        for (int i = 0; i < n; i++) {
            weight[i] = random.nextInt(40) + 80;
        }
        /*int[] weight = {88,89,103,106,107,109,110,112,115,119};
        int n = weight.length;*/
        Arrays.sort(weight);
        System.out.println(Arrays.toString(weight));
        int k = 200; //船的载重
        int left = 0;
        int right = n-1;
        int count = 0;
        while (left <= right){
            if(weight[left]+ weight[right] <= k){
                count++;
                System.out.println(weight[left] + "+" + weight[right] + "=" + (weight[left] + weight[right]));
                left++;
                right--;
            }else{
                count++;
                System.out.println(weight[right]);
                right--;
            }
        }
        System.out.println(count);
    }
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值