9. 貪心演算法
阿新 • • 發佈:2019-01-07
一. 貪心基礎 Assign Cookies
假設你是一位很棒的家長,想要給你的孩子們一些小餅乾。但是,每個孩子最多隻能給一塊餅乾。對每個孩子 i ,都有一個胃口值 g(i) ,這是能讓孩子們滿足胃口的餅乾的最小尺寸;並且每塊餅乾 j ,都有一個尺寸 s(j) 。如果 s(j) >= g(i) ,我們可以將這個餅乾 j 分配給孩子 i ,這個孩子會得到滿足。你的目標是儘可能滿足越多數量的孩子,並輸出這個最大數值。 注意: 你可以假設胃口值為正。 一個小朋友最多隻能擁有一塊餅乾。 示例 1: 輸入: [1,2,3], [1,1] 輸出: 1 解釋: 你有三個孩子和兩塊小餅乾,3個孩子的胃口值分別是:1,2,3。 雖然你有兩塊小餅乾,由於他們的尺寸都是1,你只能讓胃口值是1的孩子滿足。 所以你應該輸出1。 示例 2: 輸入: [1,2], [1,2,3] 輸出: 2 解釋: 你有兩個孩子和三塊小餅乾,2個孩子的胃口值分別是1,2。 你擁有的餅乾數量和尺寸都足以讓所有孩子滿足。 所以你應該輸出2.
解決思路
先把餅乾按大小排序, 小朋友按胃口大小排序
如果最大的餅乾能滿足胃口最大的小朋友, 就把最大的餅乾給他,
否則就把最大的餅乾給胃口第二大的小朋友....以此類推
程式碼
#include <iostream> #include <vector> #include <algorithm> using namespace std; /// 455. Assign Cookies /// https://leetcode.com/problems/assign-cookies/description/ /// 先嚐試滿足最貪心的小朋友 /// 時間複雜度: O(nlogn) 主要體現在排序中 /// 空間複雜度: O(1) class Solution { public: int findContentChildren(vector<int>& g, vector<int>& s) { // s表示餅乾 g表示小朋友 sort(g.begin(), g.end(), greater<int>()); // 從大到小排序, greater表示從大到小, 否則預設從小到大 sort(s.begin(), s.end(), greater<int>()); int gi = 0, si = 0; int res = 0; while(gi < g.size() && si < s.size()){ if(s[si] >= g[gi]){ res ++; si ++; gi ++; } else gi ++; } return res; } };
二. 貪心演算法和動態規劃的關係 Non-overiapping Intervals
給定一個區間的集合,找到需要移除區間的最小數量,使剩餘區間互不重疊。 注意: 可以認為區間的終點總是大於它的起點。 區間 [1,2] 和 [2,3] 的邊界相互“接觸”,但沒有相互重疊。 示例 1: 輸入: [ [1,2], [2,3], [3,4], [1,3] ] 輸出: 1 解釋: 移除 [1,3] 後,剩下的區間沒有重疊。 示例 2: 輸入: [ [1,2], [1,2], [1,2] ] 輸出: 2 解釋: 你需要移除兩個 [1,2] 來使剩下的區間沒有重疊。 示例 3: 輸入: [ [1,2], [2,3] ] 輸出: 0 解釋: 你不需要移除任何區間,因為它們已經是無重疊的了。
解題思路
暴力解法: 先要排序, 方便判斷不重疊,找出所有子區間的組合, 之後判斷它不重疊。 O((2^n)*n)
動態規劃: 先要排序, 方便判斷不重疊,使用最長上升子序列 的解法
動態規劃程式碼
#include <iostream>
#include <vector>
using namespace std;
/// Definition for an interval.
struct Interval {
int start;
int end;
Interval() : start(0), end(0) {}
Interval(int s, int e) : start(s), end(e) {}
};
bool compare(const Interval &a, const Interval &b){
if(a.start != b.start)
return a.start < b.start;
return a.end < b.end;
}
/// 435. Non-overlapping Intervals
/// https://leetcode.com/problems/non-overlapping-intervals/description/
/// 動態規劃
/// 時間複雜度: O(n^2)
/// 空間複雜度: O(n)
class Solution {
public:
int eraseOverlapIntervals(vector<Interval>& intervals) {
if(intervals.size() == 0)
return 0;
sort(intervals.begin(), intervals.end(), compare);
// memo[i]表示以intervals[i]為結尾的區間能構成的最長不重疊區間序列 個數, 開始時 都為1
vector<int> memo(intervals.size(), 1);
for(int i = 1 ; i < intervals.size() ; i ++)
// memo[i]
for(int j = 0 ; j < i ; j ++) // 掃描i前面的 所有區間
if(intervals[i].start >= intervals[j].end) // 滿足條件, 則計算 到第i個區間時, 可以連起來的區間數
memo[i] = max(memo[i], 1 + memo[j]);
int res = 0;
for(int i = 0; i < memo.size() ; i ++)
res = max(res, memo[i]);
return intervals.size() - res; // 得到要去除的 最少區間數
}
};
貪心演算法
按照區間的結尾排序,
每次選擇結尾最早的, 且和前一個區間不重疊的區間
程式碼
#include <iostream>
#include <vector>
using namespace std;
/// Definition for an interval.
struct Interval {
int start;
int end;
Interval() : start(0), end(0) {}
Interval(int s, int e) : start(s), end(e) {}
};
bool compare(const Interval &a, const Interval &b){ // 結尾早的排前面, 結尾一樣 選區間長的排前面
if(a.end != b.end)
return a.end < b.end;
return a.start < b.start;
}
/// 435. Non-overlapping Intervals
/// https://leetcode.com/problems/non-overlapping-intervals/description/
/// 貪心演算法
/// 時間複雜度: O(n)
/// 空間複雜度: O(n)
class Solution {
public:
int eraseOverlapIntervals(vector<Interval>& intervals) {
if(intervals.size() == 0)
return 0;
sort(intervals.begin(), intervals.end(), compare);
int res = 1; // 當前可以保留的 區間數
int pre = 0;
for(int i = 1 ; i < intervals.size() ; i ++)
if(intervals[i].start >= intervals[pre].end){
res ++;
pre = i;
}
return intervals.size() - res;
}
};
三. 貪心選擇性質的證明
一般過程如下:
假設貪心演算法為A 不是最優解,
則存在一個最優演算法O
只要證明A完全可以代替O,且不影響求出最優解 即可
上一節中貪心選擇性質的證明
題目: 給定一組區間, 問最多保留多少個區間, 可以讓這些區間之間相互不重疊。
貪心演算法: 按照區間的結尾排序, 每次選擇結尾最早的, 且和前一個區間不重疊的區間
反證法證明:
貪心演算法的 某次選擇的是[s(i), f(i)] 這個區間; 其中f(i)是當前所有選擇中結尾最早的
假設這個選擇不是最優解。 也就是說, 如果這個問題的最優解為k, 則這個問題得到的解, 最多為k-1
假設最優解在這一步選擇的是[s(j), f(j)], f(j)>f(i)
顯然 可以將[s(i),f(i)]替換[s(j),f(j)], 而不影響後序的區間選擇:
f(j)>f(i) 顯然不會影響後面選擇
而s(i)和s(i), 則隱藏在條件中:
前面的m次選擇是一樣的, 到這一次 使用'貪心選擇'還是 '最優選擇', 都不會對全面已經選擇過的區間 產生影響了.