刷刷笔试题--贪心算法

贪心算法思想:

顾名思义,贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。

当然,希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。

单源最短路经问题最小生成树问题等。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。

贪心算法的基本要素:

1.贪心选择性质
所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。
这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。

动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。

对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。

2. 当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。

问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。

贪心算法的基本思路:

从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。

当达到算法中的某一步不能再继续前进时,算法停止。

该算法存在问题:
1. 不能保证求得的最后解是最佳的;
2. 不能用来求最大或最小解问题;
3. 只能求满足某些约束条件的可行解的范围。

实现该算法的过程:
从问题的某一初始解出发;
while 能朝给定总目标前进一步 do
   求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;


1.[编程题]最大差值

有一个长为n的数组A,求满足0≤a≤b<n的A[b]-A[a]的最大值。

给定数组A及它的大小n,请返回最大差值。

测试样例:

[10,5],2
返回:0

解析:

要求按顺序从左到右扫,很多题都适用这种方法

import java.util.*;

public class LongestDistance {
    public int getDis(int[] A, int n) {
        // write code here
        int maxDiff=0;//初始化最大差值
        int min=A[0];//初始化最小值
        for(int i=1;i<n;i++){
            if(A[i]<min)
                min=A[i];//更新最小值
            if(A[i]-min>maxDiff)
                maxDiff=A[i]-min;//更新最大差值
        }
        return maxDiff;
    }
}

2.股票交易日

在股市的交易日中,假设最多可进行两次买卖(即买和卖的次数均小于等于2),规则是必须一笔成交后进行另一笔(即买-卖-买-卖的顺序进行)。给出一天中的股票变化序列,请写一个程序计算一天可以获得的最大收益。请采用实践复杂度低的方法实现。

给定价格序列prices及它的长度n,请返回最大收益。保证长度小于等于500。

测试样例:

[10,22,5,75,65,80],6
返回:87

解析:

以第i天为分界线,计算第i天之前进行一次交易的最大收益profitBefore[i]

和第i天之后进行一次交易的最大收益profitAfter[i]

最后遍历一遍max{profit[i[+profitAfter[i]}

因为一共有两次交易‘分别找出第一次最大值,和第二次最大值

先从前往后遍历

因为要先买股票,找到花钱最少的

再一起找可以得到的最大收益,profitBefore[i],

用前一天的和今天新买股票进行比较


import java.util.*;

public class Stock {
    public int maxProfit(int[] prices, int n) {
        if(n==0)
            return n;
        int[] profitBefore=new int[n];
        int minBuy=prices[0];
        profitBefore[0]=0;
        for(int i=1;i<n;i++){
            minBuy=Math.min(prices[i],minBuy);
            profitBefore[i]=Math.max(profitBefore[i-1],prices[i]-minBuy);
        }
        
        int[] profitAfter=new int[n];
        int maxSell=prices[n-1];
        profitAfter[n-1]=0;
        for(int i=n-2;i>=0;i--){
            maxSell=Math.max(maxSell,prices[i]);
            profitAfter[i]=Math.max(profitAfter[i+1],maxSell-prices[i]);
        }
        int max=0;
        for(int i=0;i<n;i++){
            max=Math.max(max,profitBefore[i]+profitAfter[i]);
        }
        return max;
    }
}

3.[编程题]直方图内最大矩形

有一个直方图,用一个整数数组表示,其中每列的宽度为1,求所给直方图包含的最大矩形面积。比如,对于直方图[2,7,9,4],它所包含的最大矩形的面积为14(即[7,9]包涵的7×2的矩形)。

给定一个直方图A及它的总宽度n,请返回最大矩形面积。保证直方图宽度小于等于500。保证结果在int范围内。

测试样例:

[2,7,9,4,1],5
返回:14

解析:

求直方图矩形面积的最大值就是求数组中相邻元素(单个元素,两两相邻,三三相邻。。。 )中最小值乘以他们的数量得到面积,

再取最大值,通过分析可以发现实际第一个元素比较一次,第二个元素比较两次,第三个元素比较三次。。。

以此类推就可,对于A[n]只需记录A[n-1]之前最大面积的即可,所以不需要设置额外的数组,

只需设置两个int变量即可求出面积最大值。


import java.util.*;

public class MaxInnerRec {
    public int countArea(int[] A, int n) {
        // 初始化最大面积
        int max=0;
        for(int i=0;i<n;i++){
            int min=A[i];//每次循环,设定第一个数位最小的
            for(int j=i;j<n;j++){//第二次循环要从i后面的数开始
                if(A[j]<min)
                    min=A[j];
                max=Math.max(max,min*(j-i+1));
            }
        }
        return max;
    }
}


4.[最大整数]设有n个正整数,将它们连接成一排,组成一个最大的多位整数。
例如:n=3时,3个整数13,312,343,连成的最大整数为34331213。
又如:n=4时,4个整数7,13,4,246,连成的最大整数为7424613。
输入:n
N个数
输出:连成的多位数

算法分析:此题很容易想到使用贪心法,在考试时有很多同学把整数按从大到小的顺序连接起来,测试题目的例子也都符合,但最后测试的结果却不全对。

按这种标准,我们很容易找到反例:12,121应该组成12121而非12112,那么是不是相互包含的时候就从小到大呢?

也不一定,如12,123就是12312而非12123,这种情况就有很多种了。是不是此题不能用贪心法呢?

贪心算法所作的选择可以依赖于以往所作过的选择,但决不依赖于将来的选择,也不依赖于子问题的解,因此贪心算法与其他算法相比具有一定的速度优势。

如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。

其实此题可以用贪心法来求解,只是刚才的标准不对

正确的标准是:先把整数转换成字符串,使用冒泡排序

相信这种冒泡已经很熟悉了,注意看程序中最核心的比较规则是什么,是这一句if(array[j] > array[j+1] ) 他是以数字大小作为比较准则来返回true或者是false,

那么我们完全可以改变一下这个排序准则,比如23,123,这两个数字,在我们这个题中它可以组成两个数字 23123和12323,分明是前者大些,所以我们可以说

23排在123前面,也就是23的优先级比123大,123的优先级比23小,用compareTo函数来比较


import java.util.*;
public class BiggestNum {
	public static void main(String[] args){
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt();
		int[] a=new int[n];
		String[] b=new String[n];
		for(int i=0;i<n;i++){
			a[i]=sc.nextInt();
			b[i]=Integer.toString(a[i]);
		}
		for(int i=0;i<n;i++){
			for(int j=0;j<n-i-1;j++){
				if(compare(b[j],b[j+1])<0){
					String temp=b[j];
					b[j]=b[j+1];
					b[j+1]=temp;
				}
			}
		}
		String max="";
		for(int i=0;i<n;i++){
			max+=b[i];
		}
		System.out.println(max);
		
		
	}
	public static int compare(String a,String b){//重新制定规则
		return Integer.parseInt(a+b)-Integer.parseInt(b+a);
	}
}

5.[编程题]连续子数组的最大和

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?

解析:

两个变量,一个变量tempsum来记录一段一段的和,只要tempsum>0,它就可以继续往下加,因为无论array[i]大于0还是小于0,只要加上一个比0大的数,都比自己原来更大,但是如果tempsum<0,就另起一个头,以array[i]为头,它不被之前的tempsum影响,有了一个新的tempsum

每次求出新的tempsum都和sum比较一下,如果当前的tempsum>sum,则替换掉原来的,否则维持sum不变


public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array.length==0||array==null)
            return 0;
        int tempsum=array[0];
        int sum=array[0];
        for(int i=1;i<array.length;i++){
            tempsum=tempsum>0?tempsum+array[i]:array[i];
            sum=tempsum>sum?tempsum:sum;
        }
        return sum;
    }
}






    原文作者:贪心算法
    原文地址: https://blog.csdn.net/nanamasuda/article/details/52489508
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞