共计 4085 个字符,预计需要花费 11 分钟才能阅读完成。
文章和代码曾经归档至【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;
}