乐趣区

关于贪心算法:贪心算法思想与练习

文章和代码曾经归档至【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:

6
7 1 5 3 6 4

输入样例 1:

7

输出样例 2:

5
1 2 3 4 5

输入样例 2:

4

输出样例 3:

5
7 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$

输出样例:

4
6 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$,
数据保障肯定有解。

输出样例:

4
1
2
5
4

输入样例:

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 2
1 2
-3 1
2 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;
}
退出移动版