【数据结构与算法(十一)】——动态规划与贪婪算法

又一个周末

动态规划与贪婪算法

使用动态规划的情况
1、使用动态规划的情况:求最优解最大值最小值)的问题,并且要求这个问题能分解成若干个子问题,并且子问题之间还有重叠的更小的子问题。
2、在应用动态规划之前要分析能否把大问题分解成小问题,分解后的每个小问题也存在最优解,之后把小问题的最优解组合起来能够得到整个问题的最优解,就用动态规划解决这个问题
动态规划的特点
1、求最优解
2、整体问题的最优解是依赖各个子问题的最优解
3、把大问题分解成若干个小问题,这些小问题之间还有相互重叠的更小的子问题
4、由于子问题在分解大问题的过程中重复出现,为了避免重复求解子问题,可以用从下往上的顺序计算小问题的最优解并存储下来,再以此为基础求取大问题的最优解。从上往下分析问题,从下往上解决问题。在应用动态规划解决问题的时候,要从解决最小问题开始,并把已经解决的子问题的最优解存储下来【在一维数组或二维数组中】,并把子问题的最优解组合起来逐步解决大问题。
贪婪算法:每步都采取最优解,最终得到局部的最优解

题目

剪绳子

给你一根长度为n的绳子,请把绳子剪成m段(m和n都是int,n>1并且m>1),每段绳子的长度记为k[0],k[1]……k[m]。请问k[0]*k[1]*……*k[m]可能的最大乘积是多少?

动态规划:时间O( n 2 ),空间O(n)

f ( n ) = m a x ( f ( i ) f ( n i ) ) , 0 < i < n ,这是一个至上而下的递归公式,如果用递归的话会需要重复计算很多子问题,所以我们就按照自下而上来进行计算,先得到 f ( 2 ) , f ( 3 ) ,接着 f ( 4 ) , f ( 5 ) ,直到计算到 f ( n )
时间O( n 2 ),是因为有双重循环;空间O(n)是因为使用了一个新的长为n+1的数组

//f(2)=1*1;f(3)=1*2>1*1*1?1*2:1*1*1
int maxProductAfterCutting1(int length)
{
    if (length < 2)//0||1
        return 0;
    if (length == 2)
        return 1;
    if (length == 3)
        return 2;

    //为什么是n+1?因为要计算的是第n个值,在数组中有第n个值的数组,最少要n+1长(从第0开始算)
    int *products = new int[length + 1];
    int i;
    for (i = 0; i <= 3; i++)
        products[i] = i;
    int max = 0;
    //i=4,下面的算法类似于斐波那契数列
    for (; i <= length; i++) {
        max = 0;
        for (int j = 1; j <= i / 2; j++) {
            int product = products[j] * products[i - j];
            if (max < product)
                max = product;

            products[i] = max;
        }
    }
    max = products[length];
    delete[] products;
    return max;
}

贪婪算法:时间和空间O(1)

剪绳子的时候,按照下面策略剪会使得到的各段绳子的长度的乘积最大(这要用数学方法进行证明):当 n >= 5 时,尽可能多的剪长度为3的绳子;当剩下的绳子长度为4时,把绳子剪成两段长度为2的绳子。

//贪婪算法
int maxProductAfterCutting2(int length)
{
    if (length < 2)//0||1
        return 0;
    if (length == 2)
        return 1;
    if (length == 3)
        return 2;

    //尽可能剪去长度为3的绳子
    int timeOf3 = length / 3;
    //当绳子最后剩下的长度为4时,不再剪去3,而是2*2;只有4才需要-1
    if (length - timeOf3 * 3 == 1)//表示最后剩下的是3+1=4
        timeOf3--;
    //因为倒数两段必须>=5才能继续剪3,所以倒数最后一段只能为4,3,2;
    //如果最后一段是4,则2;3,则0,但timeOf3没有减1;2,则1,反正最后也是等于2;
    //最后一段是1,2,3就不需要再剪了,保持就好
    int timeOf2 = (length - timeOf3 * 3) / 2;
    return (int)(pow(3, timeOf3))*(int)(pow(2, timeOf2));
}

抽象建模能力的锻炼

把现实问题抽象成数学模型并用计算机的编程语言表达出来:选定数据结构分析内在规律(列出公式,使用各种数学方法)

扑克牌中的顺子

从扑克牌中随机抽出5张牌,判断是不是顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大王、小王可以看成任意数字。

思路

1、将大王、小王抽象为0
2、将抽出来的5个数进行排序
3、统计数组中0的个数
4、判断是否有对子,有对子则一定不是顺子
5、统计排序之后的数组中相邻数字之间的空缺总数,判断孔雀的总数是否小于或等于0的个数

#include<stdlib.h>  //qsort
int compare(const void* arg1, const void* arg2)
{
    return *(int*)arg1 - *(int*)arg2;
}

bool isContinuous(int *numbers, int length)
{
    //先排除不按理出牌的情况
    if (numbers == nullptr || length < 1)
        return false;
    //void qsort (void* base, size_t num, size_t size,int(*compar)(const void*, const void*));
    /*这个函数被qsort重复调用来比较两个元素。 它应遵循以下原型:int compar (const void* p1, const void* p2);
    时间复杂度O(nlogn)*/
    qsort(numbers, length, sizeof(int), compare);

    int numberOfZero = 0;
    int numberOfGap = 0;
    //统计0的个数,这是在排好序后再统计的,所以0是连在一起的
    for (int i = 0; i < length&&numbers[i] == 0; i++)
        numberOfZero++;
    //统计数组中的间隔数目,small是前面那个数的坐标,big是后面那个数的坐标
    int small = numberOfZero;
    int big = small + 1;
    while (big < length) {
        //两个数相等,有对子,所以就直接over了
        if (numbers[small] == numbers[big])
            return false;
        numberOfGap += numbers[big] - numbers[small] - 1;
        small++;
        big++;
    }
    return (numberOfGap <= numberOfZero);
}

股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?例如,一只股票在某些时间节点的价格为{9,11,8,5,7,12,16,14}。如果我们能在价格为5的时候买入并在价格为16时卖出,则能收获最大利润为11

思路:

1、数据结构:数组
2、股票交易的利润来自股票买入和卖出的差价。只能在买入之后才能卖出,所以把股票的买入和卖出两个价组成数对,利润就是数对的差值。所以最大的利润就是其中某一个数对的差值。
3、蛮力法:找出数组中所有数对,并计算它们的差值。由于长度为n的数组中存在O( n 2 )个数对,所以算法复杂度也就有O( n 2 )
4、更好的方法:在卖出价固定时,买入价越低获得的利润越大。也就是说,如果扫描到数组中的第i个数字时,只要我们能够记住之前的i-1个数字的最小值,就能算出在当前价位卖出时能得到的最大利润。所以i也要不断变化

//时间复杂度O(n),不用说组所有的数对,因为现实问题也只能用前面的当买入价,后面的当卖出价
//关于股票的买卖问题
int MaxDiff(const int* numbers, unsigned length)
{
    if (numbers == nullptr || length < 2)
        return 0;

    int min = numbers[0];
    int maxDiff = numbers[1] - min;

    //每个i都当一次卖出价,每一轮中i前面的又都当一次买入价
    for (int i = 2; i < length; i++)
    {
        //因为i增加1,那它前面的数也只增加了1个就是i-1
        if (numbers[i - 1] < min)
            min = numbers[i - 1];

        int currentDiff = numbers[i] - min;
        if (currentDiff > maxDiff)
            maxDiff = currentDiff;
    }
    return maxDiff;
}

记得前阵子笔试的时候有一道这道题的拓展题,但是现在找不到原题了,(⊙﹏⊙)

猜你喜欢

转载自blog.csdn.net/Laon_Chan/article/details/80379711