第八章(上) 贪心策略与动态规划

动态规划和贪心算法都属于递推算法,但是与dfs求解的个数或者是全部解不同的是,他们俩是用来求最优解,且都是用局部最优来推导全局最优解,是对遍历解空间的一种优化。当问题具有最优子结构时,可用动归,而贪心是动归的特例。贪心只不过是只要顾及眼前的最优就可以求得全部的最优,而动归不是。

1、

硬币问题

有1元,5元,10元,50元,100元,500元的硬币各c1,c5,c10,c50,c100,c500枚.

现在要用这些硬币来支付A元,最少需要多少枚硬币?

假定本题至少存在一种支付方案.

0≤ci≤10^9

0≤A≤10^9

输入:

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

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

样例:

输入

3 2 1 3 0 2
620

输出

6
*/

import java.util.Scanner;

class Main{
    static int[] cnts = new int[6];
    static int[] coins = {1, 5, 10, 50, 100, 500};
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        for (int i = 0; i < 6; i++) {
            cnts[i] = sc.nextInt();
        }
        int A = sc.nextInt();
        int res = f(A, 5);
        System.out.println(res);
    }
    static int f(int A,int cur){
        if(A<=0) return 0;
        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、http://poj.org/problem?id=1700

一群N人希望过一条只有一条船的河,最多可以载两个人。因此,必须安排某种穿梭布置,以便来回划船,以便所有人都可以穿越。每个人都有不同的划船速度; 一对夫妇的速度取决于较慢的速度。您的工作是确定一种策略,以最大限度地缩短这些人的时间。

输入的第一行包含单个整数T(1 <= T <= 20),即测试用例的数量。然后是T案例。每个案例的第一行包含N,第二行包含N个整数,给每个人过河的时间。每个案例前面都有一个空行。人数不会超过1000人,没有人需要超过100秒才能完成。

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

样本输入

1
4
1 2 5 10

样本输出

17
import java.util.Arrays;
import java.util.Scanner;

public class Main {
    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);
        }
    }
    static void f(int n,int[] speed){
        int left =n;
        int ans =0;
        while (left>0){
            if(left ==1){
                ans +=speed[0];
                break;
            }
            if(left ==2){
                ans+= speed[1];
                break;
            }
            if(left ==3){
                ans+= speed[0]+speed[1]+speed[2];
                break;
            }
            else {
                //1,2出发,1返回,最后两名出发,从小到大排 思路就是前面走俩,后面走俩
                int s1 = speed[1] + speed[0] + speed[left - 1] + speed[1];
                //1,3出发,1返回,1,4出发,1返回,1,2过河 思路是用小的去带慢的,这儿举个例子就能感觉出来 带慢的带哪一个都可以,结果是一样的
                int s2 = speed[left - 1] + speed[left - 2] + 2 * speed[0];
                ans+= Math.min(s1,s2);
                left-=2;

            }
        }
        System.out.println(ans);
    }

}

三个贪心问题:区间问题:包括区间调度,区间选点,区间覆盖等问题  这就是很明显的最值问题了

3、区间调度问题:

有n项工作,每项工作分别在si时间开始,在ti时间结束.

对于每项工作,你都可以选择参与与否.如果选择了参与,那么自始至终都必须全程参与.

此外,参与工作的时间段不能重复(即使是开始的瞬间和结束的瞬间的重叠也是不允许的).

你的目标是参与尽可能多的工作,那么最多能参与多少项工作呢?

1≤n≤100000

1≤si≤ti≤10^9

输入:

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

样例输入:

5
12468
357910

样例输出:

3

说明:选取工作1,3,5

tip:

这个题要引入面向对象的思想,

comparable接口:

  1. 实现Comparable<T>接口后,还要覆盖public int compareTo(<T> object)方法

  2. * 如果两个对象相等返回0

  3. * 当前对象大于方法传过来的对象时,返回一个正整数

  4. * 当前对象小于方法传过来的对象时,返回一个负整数

考虑策略:考虑开始时间最早的,可能有一个从头到尾不可取,,,考虑时间短的,可能有一个在中间,选了他边上两个度不能选,不可取, 最后的策略是选结束最早的,选完一个后在起点大于结束点的情况中选结束最早的,因为要避免交叉问题

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

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] s = new int[n];
        int[] t = new 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);

    }
    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;
    }
    static class Job implements Comparable<Job>{
        int s;
        int t;

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

        @Override
        public int compareTo(Job other) {
            int x = this.t-other.t;
            if(x==0){
                return this.s - other.s; //因为按开始时间拍和安结束时间拍都应该从小到大,从下到大就不需要在换,若从大到小,还得返回-1

            }else {
                return x;
            }


        }
    }
}

4、区间选点:

这个问题的原始问题是:一段里有很多小区间,有的室友重复的,如何放一个点使他能够命中尽可能多的点。思路也是对结束时间进行排序,然后直接去选每个区间结束时的点,如果这个区间结束时的点命中了下一个区间,那就下一个区间就不用在选点了。那么如何判断这一个点是否命中了下一区间呢,就是选择下一区间开始时间大于上一结束时间的点,命中的就已经跳过了。

这个题的变体;难的地方在于不仅仅是需要命中,,而是需要达到这个区间的命中数。

Intervals
You are given n closed, integer intervals [ai, bi] and n integers c1, ..., cn.
Write a program that:
reads the number of intervals, their end points and integers c1, ..., cn from the standard input,
computes the minimal size of a set Z of integers which has at least ci common elements with interval [ai, bi], for each i=1,2,...,n,
writes the answer to the standard output.

Input
The first line of the input contains an integer n (1 <= n <= 50000) -- the number of intervals.
The following n lines describe the intervals. The (i+1)-th line of the input contains three integers ai,
bi and ci separated by single spaces and such that 0 <= ai <= bi <= 50000 and 1 <= ci <= bi - ai+1.

Output
The output contains exactly one integer equal to the minimal size of set Z
sharing at least ci elements with interval [ai, bi], for each i=1,2,...,n.
Sample Input
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
Sample Output
6
 */
//POJ1201

tip:此题我用的是数组,思路和答案没错,但是poj过不了,原始是超时,要用一种叫树状数组的东西就可以ac了,,总结:求区间和和前缀和都可以用树状数组做,空间换时间,效率非常高

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

public class test {
    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 Interval(sc.nextInt(), sc.nextInt(), sc.nextInt());
        }
        Arrays.sort(intervals);//按区间右端点排序
        int max = intervals[n-1].t;
        int[] axis = new int[max + 1];//标记数轴上的点是否已经被选中,从1开始,所以要加一
        for(int i=0;i<n;i++){
            int s = intervals[i].s;
            int t = intervals[i].t;//终点
            int cnt = sum(axis, s, t);//找到这个区间已经选点的数量,
            intervals[i].c -= cnt;//需要新增的点的数量
            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));


    }
    static int sum(int[] axis, int s, int t){
        int sum = 0;
        for (int i = s; i <= t; i++) {
            sum += axis[i];
        }
        return sum;
    }
    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;
        }

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

5、区间覆盖

这个原问题是去找用最少的别的区间去覆盖这个区间,覆盖个头或者尾巴也叫覆盖,策略就是所有结束小于所找区间开始的不考虑,所有开始大于缩招结束的不考虑,剩余全部区间进行排序,找所有开头小于所给区间的,然后更新右边为右边最大的区间,然后把start更新为最大右边区间的结尾位置,然后再去找开头小于这个start的,如果没有,就出现了断层,输出无解,,注意这个排序是按开始时间排序的哦

下面这个题是他的变形,每个牛丢都有自己的工作区间,用最少的牛去干完活就是最少的区间去覆盖要工作的区间

Farmer John is assigning some of his N (1 <= N <= 25,000) cows to do some cleaning chores around the barn.

 He always wants to have one cow working on cleaning things up and has divided the day into T shifts (1 <= T <= 1,000,000),

 the first being shift 1 and the last being shift T.

 Each cow is only available at some interval of times during the day for work on cleaning.

 Any cow that is selected for cleaning duty will work for the entirety of her interval.

 Your job is to help Farmer John assign some cows to shifts so that (i) every shift has at least one cow assigned to it,

 and (ii) as few cows as possible are involved in cleaning. If it is not possible to assign a cow to each shift, print -1.

 Input
 Line 1: Two space-separated integers: N and T

 Lines 2..N+1: Each line contains the start and end times of the interval during which a cow can work.

 A cow starts work at the start time and finishes after the end time.
 Output
 Line 1: The minimum number of cows Farmer John needs to hire or -1 if it is not possible to assign a cow to each shift.
 Sample Input
 3 10
 1 7
 3 6
 6 10
 Sample Output
 2    //1-7  6-10就可以覆盖一到十

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

public class test {
    public static void main(String[] args) {
        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());
        }
        Arrays.sort(jobs);
        int start = 1;//要覆盖的目标点,end覆盖该点的所有区间中右端点最右
        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; //大于1break,,按点包含的
            if(s<=start){
                end = Math.max(end,t);
            }else {
                ans++;
                start = end +1;
                if(s<=start){
                    end = Math.max(t, end);

                }else {
                    break;
                }
            }
            if(end>=T){
                break;//当前的end超越了线段的右侧
            }
        }
        if(end<T){
            System.out.println(-1);
        }
        else {
            System.out.println(ans);
        }

    }
    private static class Job implements Comparable<Job> {
        int s;
        int t;

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

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

6、字典序最小问题

字典序最小问题

给一个定长为N的字符串S,构造一个字符串T,长度也为N。

起初,T是一个空串,随后反复进行下列任意操作

1. 从S的头部删除一个字符,加到T的尾部
2. 从S的尾部删除一个字符,加到T的尾部

目标是最后生成的字符串T的字典序尽可能小

1≤N≤2000
字符串S只包含大写英文字母

输入:字符串S
输出:字符串T

POJ - 3617 要求   !!!!这个要求,,每80个字符换行输出

http://poj.org/problem?id=3617

import java.util.Scanner;

public class test {
    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());
        }
        f(ss.toString());
    }
    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){//compareto函数用在字符串上进行比较,输出的是两个字符串的首字母的ascall吗的差值。
                rs.append(s.charAt(0));
                s = s.substring(1);
            }else {
                rs.append(s1.charAt(0));
                s1 = s1.substring(1);
            }
            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));
        }

    }

}

7、终于到了背包问题了:::(最优装载问题,部分背包问题,乘船问题)

 最优装载问题: * 给出n个物体,第i个物体重量为wi。选择尽量多的物体,使得总重量不超过C。

这个贪心问题很简单,没有价值问题,只需要重量排序,然后从小到大排个序就OK啦

public class Case07_最优装载问题 {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    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。

为什么叫部分背包呢,是因为物体可以只拿走一部分,因此我们可以去求价值比,求出价值比最大的即可

import java.util.Arrays;

public class test{
    public static void main(String[] args) {
        int[] w = {1, 2, 3, 4, 5};
        int[] v = {3, 4, 3, 1, 4};
        int n = w.length;
        double C = 10;
        Obj[] objs = new Obj[n];
        for (int i = 0; i < n; i++) {
            objs[i] = new Obj(w[i], v[i]);
        }

        Arrays.sort(objs);
        double c = C;
        double maxValue = 0;
        for (int i = n - 1; i >= 0; i--) {
            if (objs[i].w <= c) {
                maxValue += objs[i].v;
                c -= objs[i].w;
            } else {
                maxValue += objs[i].v * (c / objs[i].w);
                break;
            }
        }
        System.out.println(maxValue);
    }

    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;
        }

        @Override
        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),则唯一的方案是每个人坐一艘
 否则,他应该选择能和他一起坐船的人中最重的一个j

 求需要船的数量

import java.util.Arrays;

public class test {
    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 cntOfBoat = 0;
        int p1 = 0;
        int p2 = n - 1;
        while (cntOfPerson > 0) {
            if (p1 + p2 > c) {
                p2--;
                cntOfPerson--;
                cntOfBoat++;
            } else {
                p1++;
                p2--;
                cntOfPerson -= 2;
                cntOfBoat++;
            }
        }
        System.out.println(cntOfBoat);
    }
}

做个小补充(补充一些概念):

1、最优子结构:只要这么一直选下去,就能得出最优的解,每一步都是当下的最优解,结果是原问题的最优解

2、贪心:由上一步最优推导下一步最优,上一步之前的历史(最优解)不做保留

动态规划问题:

写出dp的顺序就是 递归,记忆性递归,dp

所以dp求解其实就是一个依赖关系的问题,比如01背包,背包重量为5依赖背包重量为4,的结果,,,,比如钢条问题,钢条长度为10的情况依赖于钢条长度为9的情况,,,比如三角形,上面的三角形最优依赖于下面的三角形最优,所以得出问题的求解顺序,即打表的先后顺序

也就是大佬们口中的打表法,,分析依赖去想先算出啥打表,,后算出啥来打表

动态规划解决的问题是:最优子结构或者说是子问题的最优性的问题

本质是递推

他擅长去解决重叠子问题

比如dfs中解决的问题是一颗大树的样子,每一层的问题是同等级别的,而动归可以处理同一根节点的左右孩子问题有交叉的情况

比如斐波那契数,,fn需要f(n-1)和f(n-2) 他俩是同一级别的,,但是求f(n-1)还需要求f(n-2) 这就叫重叠子问题

动归的形式呢? 一般就是递推 和 记忆性递归

01背包问题

有n个重量和价值分别为wi,vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。

    1≤n≤100

    1≤wi,vi≤100

    1≤W≤10000

输入:

    n=4
    (w,v)={(2,3),(1,2),(3,4),(2,2)}
    W=5

输出:

    7(选择第0,1,3号物品)

因为对每个物品只有选和不选两种情况,所以这个问题称为01背包。

tip:这儿其实并没有去把他们用类封装,更简单一些,因为价值和重量都可以用同一下标,所以不用封装是可以的

dp法:这里的dp打表打的是二维的表,原因是两个变量,横向是空间的量,我这里横向多了一个0表示的是空间为0的情况,而纵向

表示的是物品,标注空间和价值

dfs法:从高到低的dfs,,每个都选和不选,最优解经历了回溯的情况,把原来的dfs改成记忆话搜索就两步啊,,

import java.util.Arrays;

public class test {
    static int[] w = {2, 1, 3, 2};//重量表
    static int[] v = {3, 2, 4, 2};//价值表
    static int n = 4;//物品数量
    static int W = 5;//背包的承重极限
    static int [][]rec = new int[n][W + 1];
    public static void main(String[] args) {
        int ww = W;
        int ans = dfs(0, ww);//0是从0开始,ww是背包的承重极限,还有着两个是变化的,为什么要有从0开始呢,因为每个都有选和两种情况,回溯的时候有游标就可以回溯了
        System.out.println(ans);

//下面这个是dp的开始的代码

        for (int i = 0; i < n; i++) {
            Arrays.fill(rec[i], -1);
        }
        ww = W;
        ans = m(0, ww);
        System.out.println(ans);

        System.out.println(dp());
    }
    // 2的n的复杂度
    static int dfs(int i, int ww) {
        if (ww <= 0) return 0;//装不进去
        if (i == n) return 0;//没东西可选了,,这个return 0不知道为啥

        int v2 = dfs(i + 1, ww);//不选择当前物品,直接有游标到下一个去了,重量不变
        if (ww >= w[i]) {
            int v1 = v[i] + dfs(i + 1, ww - w[i]);//选择当前物品
            return Math.max(v1, v2);
        } else {
            return v2;
        }
    }

    static int m(int i,int ww){
        if (ww <= 0) return 0;//装不进去
        if (i == n) return 0;//没东西可选了,,这个return 0不知道为啥
        int ans=0;
        if (rec[i][ww]>=0){
            return rec[i][ww];   //计算之前先查询
        }
        int v2 = dfs(i + 1, ww);//不选择当前物品
        if (ww >= w[i]) {
            int v1 = v[i] + dfs(i + 1, ww - w[i]);//选择当前物品
            ans =  Math.max(v1, v2);
        } else {
            ans = v2;
        }
        //返回的时候计算之后做记录
        rec[i][ww] = ans;
        return ans;
    }
//dp解法
    static int dp(){
        int[][] dp = new int[n][W + 1];
        for(int i=0;i<W+1;i++){
            if(i>=w[0]){
                dp[0][i] = v[0]; //这个是初始化表格的第一行,比较简单,因为能放下就是能放下,放不下就是放不下,不用去找
            }else {
                dp[0][i] = 0;
            }
        }
        for(int i=1;i<n;i++){ //从第二行开始
            for(int j=0;j<W+1;j++){
                if(j>=w[i]){
                    int i1 = v[i]+dp[i-1][j-w[i]];//一定是上一行、,但是列要看减完是啥
                    int i2 = dp[i-1][j];
                    dp[i][j] =  Math.max(i1,i2);
                }else {
                    dp[i][j] = dp[i-1][j]; //如果空间没有这个w【i】大,那就不放他,直接就是上面那个价值
                }
            }
        }
        return dp[n-1][W];
    }



}

8、

Serling公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。
假定我们知道Serling公司出售一段长为i英寸的钢条的价格为pi(i=1,2,…,单位为美元)。钢条的长度均为整英寸。

| 长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| - | - | - | - | - | - | - | - | - | - |
价格pi | 1 | 5 | 8 | 16 | 10 | 17 | 17 | 20 | 24 | 30 |

钢条切割问题是这样的:给定一段长度为n英寸的钢条和一个价格表pi(i=1,2,…n),求切割钢条方案,使得销售收益rn最大。
注意,如果长度为n英寸的钢条的价格pn足够大,最优解可能就是完全不需要切割。
 

import java.util.Arrays;

class Main{
    static int n = 10;
    static int[] p = {1, 5, 8, 16, 10, 17, 17, 20, 24, 30};
    static int[] vs = new int[n + 1];

    public static void main(String[] args) {
        int ans = r(n);
        System.out.println(ans);
        Arrays.fill(vs, -1);
        ans = memo(n);
        System.out.println(ans);
        ans = dp();
        System.out.println(ans);
    }
    //递归解法
    static int r(int x){
        if(x==0){
            return 0;
        }
        int ans = 0;
        for(int i=1;i<=x;i++){
            int v = p[i-1]+r(x-i);
            ans = Math.max(v,ans);
        }
        return ans;
    }

    //记忆化解法
    static int memo(int x){
        if(x==0){
            return 0;
        }
        int ans = 0;
        for(int i=1;i<=x;i++){
            if(vs[x-i] ==-1){  //这里先查,没有的话就递归
                vs[x-i] = r(x-i);
            }
            int v = p[i-1]+vs[x-i];
            ans = Math.max(v,ans);
        }
        vs[x] = ans;
        return ans;
    }

    //dp解法 ,变化的只有剩余钢条的长度,所以用一位数组就可以来打表,那价格呢?为什么不想上面那个o1背包一样?
    static int dp(){
        vs[0] = 0;
        for(int i=1;i<=n;i++){//拥有的钢条长度
            for(int j=1;j<=i;j++){//保留j为整段
                vs[i] = Math.max(p[j - 1]+vs[i-j],vs[i]);//这里j-1是因为原来的数组是从0开始
            }
        }
        return vs[n];
    }
}

9、 * 数字三角形(POJ1163)<br>
 *
 * 在数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。<br>
 * 路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。<br>
 * 三角形的行数大于1小于等于100,数字为 0 - 99<br>
 * 输入格式:<br>
 * 5 //表示三角形的行数 接下来输入三角形<br>
 *      7<br>
 *     3 8<br>
 *    8 1 0<br>
 *   2 7 4 4<br>
 *  4 5 2 6 5<br>
 * 要求输出最大和<br>

import java.util.Scanner;

class Main{
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[][] triangle = new int[n][];
        for (int i = 0; i < n; i++) {
            triangle[i] = new int[i + 1];
            for (int j = 0; j < i + 1; j++) {
                triangle[i][j] = sc.nextInt();
            }
        }
        System.out.println(maxSumUsingRecursive(triangle, 0, 0));

    }
    //递归解法
    static int maxSumUsingRecursive(int[][] triangle,int i,int j){
        int rowIndex = triangle.length;
        if (i == rowIndex - 1) {
            return triangle[i][j]; 
        } else {
            //顶点的值+max(左侧支线的最大值,右侧支路的最大值)
            return triangle[i][j] + Math.max(maxSumUsingRecursive(triangle, i + 1, j), maxSumUsingRecursive(triangle, i + 1, j + 1));
        }
    }

    //dp解法,起始就是记忆化解法
    public static int maxSumUsingDp(int[][] triangle, int i, int j) {
        int rowCount = triangle.length;//行数
        int columnCount = triangle[rowCount - 1].length;//最后一行的列数
        int[][] dp = new int[rowCount][columnCount];
        for (int k = 0; k < columnCount; k++) {
            dp[rowCount-1][k] = triangle[rowCount - 1][k];//初始化最后一行
        }

        for (int k = rowCount - 2; k >= 0; k--) {
            for (int l = 0; l <= k; l++) {
                dp[k][l] = triangle[k][l] + Math.max(dp[k+1][l], dp[k + 1][l+1]);
            }
        }
        return dp[0][0];
    }

    //滚动数组,(一纬数组),比打表法更省地方,因为他
    public static int rollDp(int[][] triangle, int i, int j) {
        int rowCount = triangle.length;//行数
        int columnCount = triangle[rowCount - 1].length;//最后一行的列数
        int[] dp = new int[columnCount];
        for (int k = 0; k < columnCount; k++) {
            dp[k] = triangle[rowCount - 1][k];//初始化最后一行
        }

        for (int k = rowCount - 2; k >= 0; k--) {
            for (int l = 0; l <= k; l++) {
                dp[l] = triangle[k][l] + Math.max(dp[l], dp[l + 1]);
            }
        }
        return dp[0];
    }

}

10、最长公共子序列(子序列是可以跳空的,而子数组不可以)

 * 求最大公共子序列问题
 * AB34C
 * A1BC2 结果为 ABC
 * 更多案例请看测试用例

思路:这个题比较麻烦不仅要得出长度,还要求出来是啥得出的最长

package org.lanqiao.algo.elementary._08_dp;

import org.assertj.core.api.Assertions;

import java.util.ArrayList;

/**
 * 求最大公共子序列问题
 * AB34C
 * A1BC2 结果为 ABC
 * 更多案例请看测试用例
 * */
public class Case13_LCS {
  public static void main(String[] args) {
    Case13_LCS obj = new Case13_LCS();
    // System.out.println(obj.solution("AB34C", "A1BC2"));
    // Assertions.assertThat(obj.solution("3563243", "513141")).isEqualTo("534");
    // Assertions.assertThat(obj.solution("3069248", "513164318")).isEqualTo("3648");
    ArrayList ans = obj.dfs("AB34C", "A1BC2");
    System.out.println(ans);
    System.out.println(obj.dfs("3563243", "513141"));
    System.out.println(obj.dfs("3069248", "513164318"));
    System.out.println(obj.dfs("123", "456"));

  }

  ArrayList<Character> dfs(String s1, String s2) {
    int len1 = s1.length();
    int len2 = s2.length();
    ArrayList<Character> ans = new ArrayList<>();
    for (int i = 0; i < len1; i++) {
      //求以i字符开头的公共子序列
      ArrayList<Character> list = new ArrayList<>();
      //和s2的每个字符比较
      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() > ans.size()) {
        ans = list;
      }
    }
    return ans;
  }
  /**
   * 生成动规表
   * @param s1
   * @param s2
   * @return
   */
  String solution(String s1, String s2) {
    int len1 = s1.length();
    int len2 = s2.length();
    int[][] dp = new int[len1 + 1][len2 + 1]; // 动规数组
    int flag = 0;
    // 初始化第一列
    //O(M)
    for (int i = 1; i <= len1; i++) { //flag用的太巧妙了
      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;
    //初始化第一行
    //O(N)
    for (int j = 1; j <= len2; j++) {
      if (flag == 1) {
        dp[1][j] = 1;
      } else if (s2.charAt(j - 1) == s1.charAt(0)) {
        dp[1][j] = 1;
        flag = 1;
      } else {
        dp[1][j] = 0;
      }
    }
    //O(M*N)
    for (int i = 2; i <= len1; i++) {  // M
      for (int j = 2; j <= len2; j++) {  // N
        int maxOfLeftAndUp = Math.max(dp[i - 1][j], dp[i][j - 1]);//这儿其实不用考虑左上角,因为上和左一定大于左上角,因为他们俩都比较长啊
        if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
          
          dp[i][j] =  maxOfLeftAndUp + 1;
        } else {
          dp[i][j] = maxOfLeftAndUp;
        }
      }
    }
    // Util.printMatrix(dp);
    return parseDp(dp, s1, s2);
  }

  /**
   * 解析动态规划表,得到最长公共子序列
   * @param dp
   * @param s1
   * @param s2
   * @return
   */
  private String parseDp(int[][] dp, String s1, String s2) {
    int M = s1.length();
    int N = s2.length();
    StringBuilder sb = new StringBuilder();
    while (M > 0 && N > 0) {
      // 比左和上大,一定是当前位置的字符相等
      if (dp[M][N] > Math.max(dp[M - 1][N], dp[M][N - 1])) {
        sb.insert(0, s1.charAt(M - 1)); //这儿一直往0出插入就能保证后来的都插在最前面而不需要知道长度是多少再去定义字符串的长度
        M--;
        N--;
      } else {  // 一定选择的是左边和上边的大者
        if (dp[M - 1][N] > dp[M][N - 1]) {
          M--;  //往上移
        } else {
          N--; // 往左移
        }
      }
    }

    return sb.toString();
  }


}

11、 * 物品数量无限
 * 完全背包问题
 * 首先在初始化最后一行的时候有所不同:初始化时,当只考虑一件物品a时,state[row][j] = values[row]*j/weight[row]
 * 然后在递推的时候有些不同:state[row][j] = max{state[row+1][j],state[row][j-weight[row]]+values[row]},即不抓时用现在的容量去匹配下面行
 * 要抓的时候,先抓到这个物品的价值,然后用剩下的容量去匹配同一行,为什么匹配同一行,这是因为剩下的容量可以重复抓当前物品(不限数量)
 *
 * 同时必须理解,抓一个之后用剩余的容量重新考虑当前可选的所有物品其实包含了抓2个甚至更多的情况!!!

tip:主要就是把每一行这个物品抓一次去找剩余的和抓两次去找剩余的,抓三次去找剩余的这个比较给换成了,考虑本物品:直接拿同一行的上一个加上本身 和直接不考虑本物品:直接去找上一行的 进行比较就可以了

class Main{
    static int[] values = {9, 5, 3, 1};
    static int[] weights = {7, 4, 3, 2};
    static int n = 4;
    static int total = 10;

    public static void main(String[] args) {
        dp();
    }
    static void dp(){
        int[][] dp =new int[4][11];
        for(int i=1;i<11;i++){
            if(i>weights[0]){
                dp[0][i] = values[0]*(i/weights[0]);
            }else {
                dp[0][i] = 0;
            }
        }
        for(int i=1;i<4;i++){
            for(int j=0;j<11;j++){
                if(j>=weights[1]){
                    int i1 = dp[i][j-1]+values[i];
                    int i2 = dp[i-1][j];
                    dp[i][j] = Math.max(i1,i2);
                }else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        System.out.println(dp[3][10]);
    }
}

12、最长上升子序列

public class Case15_LIS {
  static int[] arr = {4, 2, 3, 1, 5, 6, 4, 8, 5, 9};

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

  /*在优化之后,可以达到O(NlgN)*/
  private static int dp1(int[] arr) {
    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[p + 1] = arr[i];
        p++;
      } else {
        //扫描dp数组,替换第一个比arr[i]大的dp
        // for (int j = 0; j <= p; j++) {
        //   if (dp[j]>arr[i]){
        //     dp[j]=arr[i];
        //   }
        // }
        int indexOfFirstBigger = Util.indexOfFirstBigger(dp, arr[i], 0, p);
        if (indexOfFirstBigger != -1)
          dp[indexOfFirstBigger] = arr[i];
      }
    }

    return p;
  }

  private static int f(int[] arr) {
    int maxCnt = 0;
    for (int i = 0; i < arr.length; i++) {
      int p = i;
      int cnt = 1;
      for (int j = i + 1; j < arr.length; j++) {
        if (arr[j] > arr[p]) {
          cnt++;
          p = j;
        }
      }
      // if (cnt>maxCnt){
      //   maxCnt=cnt;
      // }
      maxCnt = max(maxCnt, cnt);
    }
    return maxCnt;

  }

  static int[] dp = new int[arr.length];

  private static int dp(int[] arr) {
    dp[0] = 1;

    for (int i = 1; i < arr.length; i++) {
      int cnt = 1;
      for (int j = i - 1; j >= 0; j--) {
        if (arr[i] > arr[j]) {
          cnt = max(cnt, dp[j] + 1);
        }
      }
      dp[i] = cnt;

    }
    int ans = -1;
    for (int i = 0; i < dp.length; i++) {
      ans = max(ans, dp[i]);
    }
    return ans;
  }
}

猜你喜欢

转载自blog.csdn.net/h_666666/article/details/86768854
今日推荐