文章和代码曾经归档至【Github仓库:algorithms-notes】或者公众号【AIShareLab】回复 算法笔记 也可获取。

贪婪的核心思想:最优解,短视。

依照数据规模猜想贪婪,个别在$10 ^ 5$是排序,$10 ^ 6$或$10 ^ 7$是O(n)的做法,扫描一边,1000左右是两重循环,100左右是三重循环。

股票买卖 II

给定一个长度为 N 的数组,数组中的第 i 个数字示意一个给定股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你能够尽可能地实现更多的交易(屡次交易一支股票)。

留神:你不能同时参加多笔交易(你必须在再次购买前发售掉之前的股票)。

输出格局

第一行蕴含整数 N,示意数组长度。

第二行蕴含 N 个不大于 10000 的正整数,示意残缺的数组。

输入格局

输入一个整数,示意最大利润。

数据范畴

$1≤N≤10^5$

输出样例1:

67 1 5 3 6 4

输入样例1:

7

输出样例2:

51 2 3 4 5

输入样例2:

4

输出样例3:

57 6 4 3 1

输入样例3:

0

样例解释

样例1:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能取得利润 = 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能取得利润 = 6-3 = 3 。共得利润 4+3 = 7。

样例2:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能取得利润 = 5-1 = 4 。留神你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参加了多笔交易,你必须在再次购买前发售掉之前的股票。

样例3:在这种状况下, 不进行任何交易, 所以最大利润为 0。

贪婪思路:

任何跨度大于一天的交易都能够拆分成跨度等于一天的交易(两头局部的买和卖互相对消了)。所以最优解只须要聚焦在这跨度为1的交易上即可,那么基本思路就是如果后一天价格大于前一天,则交易一次。

code

#include <cstdio>#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 100010;int n;int price[N];int main(){    scanf("%d", &n);    for(int i = 0; i < n; i ++ )scanf("%d", &price[i]);    int res = 0;    for(int i = 0; i + 1 < n; i ++ )    {        int dt = price[i + 1] - price[i];        if(dt > 0) res += dt;    }    printf("%d", res);}

货仓选址

在一条数轴上有 N 家商店,它们的坐标别离为 $A_1$∼$A_N$。

当初须要在数轴上建设一家货仓,每天凌晨,从货仓到每家商店都要运送一车商品。

为了提高效率,求把货仓建在何处,能够使得货仓到每家商店的间隔之和最小。

输出格局

第一行输出整数 N。

第二行 N 个整数 $A_1$∼$A_N$。

输入格局

输入一个整数,示意间隔之和的最小值。

数据范畴

$1≤N≤100000$,
$0≤A_i≤40000$

输出样例:

46 2 9 1

输入样例:

12

思路:

中位数有十分优良的性质,比如说在这道题目中,每一个点到中位数的间隔,都是满足全局的最有性,而不是部分最优性。

具体的来说,咱们设在仓库右边的所有点,到仓库的间隔之和为p,左边的间隔之和则为q,那么咱们就必须让p+q的值尽量小。

当仓库向左挪动的话,p会缩小x,然而q会减少n−x,所以说当为仓库中位数的时候,p+q最小。

每次只关注部分最优解,即可推出全局最优解。

code

#include<bits/stdc++.h>using namespace std;typedef long long LL;const int N = 100010;int x[N];int n;int main(){    scanf("%d", &n);    for(int i = 0; i < n; i ++ ) scanf("%d", &x[i]);    // 首先须要进行排序    sort(x, x + n);    // 这里求中点采纳下取整的形式,    // 能够举例eg:n = 3,则0 1 2 3,中点为[3/2]=1即可(在两者之间即为最短)。    int c = x[n / 2];    LL res = 0;    for (int i = 0; i < n; i ++ ) res += abs(x[i] - c);    printf("%lld", res);    return 0;}

糖果传递

有 n 个小朋友坐成一圈,每人有 a[i] 个糖果。

每人只能给左右两人传递糖果。

每人每次传递一个糖果代价为 1。

求使所有人取得均等糖果的最小代价。

输出格局

第一行输出一个正整数 n,示意小朋友的个数。

接下来 n 行,每行一个整数 a[i],示意第 i 个小朋友初始失去的糖果的颗数。

输入格局

输入一个整数,示意最小代价。

数据范畴

$1≤n≤1000000$,
$0≤a[i]≤2×10^9$,
数据保障肯定有解。

输出样例:

41254

输入样例:

4

思路:

题目能够绘图如下:

其中:

那么能够看到:

由上式,能够演绎出:

$$c_{i}=c_{i+1}+\bar{a}-a_{i}$$

那么原指标函数能够化简为:

这样就转换成与上一题一样的思维了。

code

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;typedef long long LL;const int N = 1000010;int n;int a[N];LL c[N];int main(){    scanf("%d", &n);    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);    LL sum = 0;    for (int i = 1; i <= n; i ++ ) sum += a[i];    LL avg = sum / n;    for (int i = n; i > 1; i -- )    {        c[i] = c[i + 1] + avg - a[i];    }    c[1] = 0;    sort(c + 1, c + n + 1);    LL res = 0;    // 因为i从1开始,因而取中点时,c[(n + 1) / 2],相当于上取整    for (int i = 1; i <= n; i ++ ) res += abs(c[i] - c[(n + 1) / 2]);    printf("%lld\n", res);    return 0;}

雷达设施

假如海岸是一条有限长的直线,海洋位于海岸的一侧,陆地位于另外一侧。

每个小岛都位于陆地一侧的某个点上。

雷达安装均位于海岸线上,且雷达的监测范畴为 d,当小岛与某雷达的间隔不超过 d 时,该小岛能够被雷达笼罩。

咱们应用笛卡尔坐标系,定义海岸线为 x 轴,海的一侧在 x 轴上方,海洋一侧在 x 轴下方。

当初给出每个小岛的具体坐标以及雷达的检测范畴,请你求出可能使所有小岛都被雷达笼罩所需的最小雷达数目。

输出格局

第一行输出两个整数 n 和 d,别离代表小岛数目和雷达检测范畴。

接下来 n 行,每行输出两个整数,别离代表小岛的 x,y 轴坐标。

同一行数据之间用空格隔开。

输入格局

输入一个整数,代表所需的最小雷达数目,若没有解决方案则所需数目输入 −1。

数据范畴

$1≤n≤1000$,
$−1000≤x,y≤1000$

输出样例:

3 21 2-3 12 1

输入样例:

2

思路:

给定若干区间,起码抉择多少个点,能够使每个区间上起码选一个点?

贪婪策略:

  • 将所有区间按右端点从小到大排序;
  • 顺次思考每个区间:

    • 如果以后区间蕴含最初一个抉择的点,则间接跳过;
    • 如果以后区间不蕴含最初一个抉择的点,则在以后区间的右端点的地位选一个新的点;

证实:

cnt:算法失去的后果

opt:最优解

最优解示意所有办法的最小值,因而 $opt \le cnt$ 。

再证实 $opt \ge cnt$ :

  • 所有可行解必然都大于等于cnt:选了cnt个点,则意味着必然存在cnt个互不相交的区间。

首先上述做法肯定能够保障所有区间都至多蕴含一个点。

而后咱们再证实这样选出的点的数量是起码的,无妨设选出的点数是 m:

依照上述做法,咱们抉择的点都是某个区间的右端点,而且因为区间按右端点排好序了,所以咱们抉择的点也是排好序的;

只有在以后区间和上一个点所对应的区间是没有交加时,咱们才会抉择一个新点,所以所有选出的点所对应的区间是如下图所示的状况,两两之间没有交加。

所以咱们找到了 m 个两两之间没有交加的区间,因而咱们至多须要选 m 个点。而且通过上述做法,咱们能够只选 m 个点。因而最优解就是 m。

工夫复杂度

  • 计算每个坐标所对应的区间,须要 O(n)的计算量;
  • 将所有区间排序须要 O(nlogn) 的计算量;
  • 扫描所有区间须要 O(n)的计算量;

所以总共的工夫复杂度是 O(nlogn)。

code:

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>#include <cmath>using namespace std;const int N = 1010;int n, d;// 用stl的pair也能够,这里本人定义构造 segment 了struct Segment{    double l, r;    // 重载比拟符    bool operator< (const Segment& t) const    {        return r < t.r;    }}seg[N];int main(){    scanf("%d%d", &n, &d);    bool failed = false;    for (int i = 0; i < n; i ++ )    {        int x, y;        scanf("%d%d", &x, &y);        // 离海岸线大于d,则不可能存在,因而失败        if (y > d) failed = true;        else        {            // 计算线段长度            double len = sqrt(d * d - y * y);            // 左右端点            seg[i].l = x - len, seg[i].r = x + len;        }    }    if (failed)    {        puts("-1");    }    else    {        sort(seg, seg + n);        int cnt = 0;        double last = -1e20;        for (int i = 0; i < n; i ++ )            // last 小于左边界,则减少右边界的点            if (last < seg[i].l)            {                cnt ++ ;                last = seg[i].r;            }        printf("%d\n", cnt);    }    return 0;}