Tag Archives: 贪心

LeetCode Container With Most Water

11. Container With Most Water

Given n non-negative integers a1a2, …, a, where each represents a point at coordinate (iai). n vertical lines are drawn such that the two endpoints of line i is at (iai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.

Note: You may not slant the container and n is at least 2.

The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.

Example:

Input: [1,8,6,2,5,4,8,3,7]
Output: 49

题意是x轴上立着一堆线段,从这些线段中找两根和x轴构成一个木桶,问哪个木桶能装最多的水。 暴力枚举的话,是$O(n^2)$的。我们可以用贪心的思路来解决:要使木桶装更多的水,短板越长越好,且两根线之间的距离越长也越好。 设置首尾指针i,j,计算由i,j夹到的木桶的体积(这里简单点,面积即可),如果height[i]<height[j],则i++,因为短板越长越好,所以这相当于抛弃了height[i],保留height[j],底板减1。
完整代码如下:

class Solution {
public:
    int maxArea(vector<int>& height)
    {
        int mA = 0, i = 0, j = height.size() – 1;
        while (i != j) {
            int cur = min(height[i], height[j]) * (j – i);
            if (cur > mA)
                mA = cur;
            if (height[i] < height[j])
                i++;
            else
                j--;
        }
        return mA;
    }
};

本代码提交AC,用时24MS。
二刷。
证明贪心方法的正确性一般用反证法,推出矛盾即可,证明过程见讨论区:https://discuss.leetcode.com/topic/503/anyone-who-has-a-o-n-algorithm

三刷。反证法过程:刚开始的时候l=0,r=n-1,假设此时height[l]<height[r],则此时的组合对l来说是它能形成最大面积的情况。因为x轴长度最大,当r往左边走的时候,x肯定会变小;如果r往左走遇到比l更短的y,则总的y变短,面积变小;如果r往左走遇到比l更长的y,则依然是l作为y,但因为x已经变小了,所以面积依然变小。总之,每一次组合,都是对更短的那个柱子的最大面积,而更长的柱子还有可能和其他柱子组成更大的面积,所以我们每次丢掉短柱子,保留长柱子。

hihoCoder 1051-补提交卡

hihoCoder 1051-补提交卡 #1051 : 补提交卡 时间限制:2000ms 单点时限:1000ms 内存限制:256MB 描述 小Ho给自己定了一个宏伟的目标:连续100天每天坚持在hihoCoder上提交一个程序。100天过去了,小Ho查看自己的提交记录发现有N天因为贪玩忘记提交了。于是小Ho软磨硬泡、强忍着小Hi鄙视的眼神从小Hi那里要来M张”补提交卡”。每张”补提交卡”都可以补回一天的提交,将原本没有提交程序的一天变成有提交程序的一天。小Ho想知道通过利用这M张补提交卡,可以使自己的”最长连续提交天数”最多变成多少天。 输入 第一行是一个整数T(1 <= T <= 10),代表测试数据的组数。 每个测试数据第一行是2个整数N和M(0 <= N, M <= 100)。第二行包含N个整数a1, a2, … aN(1 <= a1 < a2 < … < aN <= 100),表示第a1, a2, … aN天小Ho没有提交程序。 输出 对于每组数据,输出通过使用补提交卡小Ho的最长连续提交天数最多变成多少。 样例输入 3 5 1 34 77 82 83 84 5 2 10 30 55 56 90 5 10 10 30 55 56 90 样例输出 76 59 100


这一题想到那个点上就不难了,题目要求将补提交卡插入空缺的某天中,使连续提交天数最长。要使连续提交天数最长,补提交卡插入的位置也一定要是连续的,而且很方便的是,题目中给出的空缺天数都是递增的,所以不需要重新排序了。 既然需要满足插入的位置也要连续,那么总的方案数就没有那么多了。比如n=5,m=2,有如下几种方案: 左边的红色数字表示不同方案标号,这个标号也正好是该方案中数组a中的第一个插空位置的下标。很容易得到总的方案数有n-m+1个,所以我们只需将i从0到n-m+1循环枚举每一种方案,求最长的连续提交天数。 对于某一种方案,如上所述,其插空的开始下标正好为i,因为要插m个空,所以结束下标为m+i-1。那么这两者之间总的连续提交天数怎么算呢?比如上图中的方案2,如果把第55和56天填上,那么实际的连续提交天数应该是从31~89.也就是要从i-1对应的那天的后一天(31)算起,到m+i-1-1对应的那天的前一天(89)结束,总共就是89-31+1,也就是a[m+i-1+1]-a[i-1]-1;如果遇到最后一个空,因为m+i-1+1可能超过数组a的范围,所以我们添加一个a[n]=101,俗称哨兵;如果i==0的话,没有i-1,所以分开讨论。 最终的代码如下: [cpp] #include<iostream> #include<vector> using namespace std; int main() { int t; cin>>t; int n,m; while(t–) { cin>>n>>m; vector<int> a(n+1); for(int i=0;i<n;i++) cin>>a[i]; a[n]=101;//哨兵 if(m>=n) cout<<100<<endl; else { int max_days=0;//最长连续提交天数 int case_num=n-m+1;//总的枚举情况数 for(int i=0;i<case_num;i++) { int last_index=m+i-1;//该方案下最后一个下标 if(i==0) { if(a[last_index+1]-1>max_days) max_days=a[last_index+1]-1; } else { if(a[last_index+1]-a[i-1]-1>max_days) max_days=a[last_index+1]-a[i-1]-1; } } cout<<max_days<<endl; } } return 0; } [/cpp] 本代码提交AC,用时0MS,内存0MB。 ]]>

POJ 2586-Y2K Accounting Bug

POJ 2586-Y2K Accounting Bug Y2K Accounting Bug Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 10647 Accepted: 5328 Description Accounting for Computer Machinists (ACM) has sufferred from the Y2K bug and lost some vital data for preparing annual report for MS Inc. All what they remember is that MS Inc. posted a surplus or a deficit each month of 1999 and each month when MS Inc. posted surplus, the amount of surplus was s and each month when MS Inc. posted deficit, the deficit was d. They do not remember which or how many months posted surplus or deficit. MS Inc., unlike other companies, posts their earnings for each consecutive 5 months during a year. ACM knows that each of these 8 postings reported a deficit but they do not know how much. The chief accountant is almost sure that MS Inc. was about to post surplus for the entire year of 1999. Almost but not quite. Write a program, which decides whether MS Inc. suffered a deficit during 1999, or if a surplus for 1999 was possible, what is the maximum amount of surplus that they can post. Input Input is a sequence of lines, each containing two positive integers s and d. Output For each line of input, output one line containing either a single integer giving the amount of surplus for the entire year, or output Deficit if it is impossible. Sample Input 59 237 375 743 200000 849694 2500000 8000000 Sample Output 116 28 300612 Deficit Source Waterloo local 2000.01.29


这个题首先要读懂题意,其中的
ACM knows that each of these 8 postings reported a deficit
我一开始看不懂,不知道这个8代表什么意思,是怎么来的,看过别人的解释才知道,原来前面说每连续5个月会发布一次盈亏报告,而一年又12个月,总共可以发布8次,即:1-5,2-6,3-7,4-8,5-9,6-10,7-11,8-12。注意每次报告的月份和前后的报告的月份是有重叠的。 题目告诉我们这8次报告都是亏损,但是求这一年是否可以盈利,总的盈利最多是多少。这道题有点最优化的感觉,已知约束是8次报告都是亏损,求最优化总的盈利。 这道题可以有两种解法。 贪心 因为要求盈利最大化,所以可以先设12个月都是盈利的,然后再慢慢调整满足8次报告都是亏损的。一次报告是连续的5个月的总和,要保证一次报告是亏损,5个月里至少一个月是亏损的,亏损的月份放在5个月里的最后月份是最好的,因为这样可以和下一个报告重叠,使总的盈利最大。比如1-5使第5月份亏损,就能保证这个报告亏损,则2-6已经包含5月份了,当然这个报告肯定也亏损。这样1-5和2-6实际只有5月份是亏损的。但是如果1-5使第1月份亏损,虽然第一个报告是亏损的,但是2-6还是盈利的,又使第2月份亏损,则1-5和2-6实际有1月份和2月份是亏损的,这就亏损了两个月。 所以每次我们要亏损时,都从该报告的最后月份开始试探,一直往前试探,直到这个报告亏损。一直循环8个报告。最后求这一年总的盈亏。代码如下: [cpp] #include<iostream> #include<vector> using namespace std; int main() { int s,d; while(cin>>s>>d) { int surplus_num;//盈余的月份数 vector<int> month(12,1);//初始的时候每个月都是surplus,1为surplus,0为deficit for(int i=0;i<8;i++)//8个‘连续5个月’ { surplus_num=0; for(int j=0;j<5;j++) surplus_num+=month[i+j]; int k=0; while(surplus_num*s>=(5-surplus_num)*d) { month[i+4-k]=0;//亏损的月份尽量放到后面,因为可以和下一个周期重叠,这样总的surplus会大一些 surplus_num=0; for(int j=0;j<5;j++) surplus_num+=month[i+j]; k++; } } surplus_num=0; for(int i=0;i<12;i++) surplus_num+=month[i]; int rs=surplus_num*s-(12-surplus_num)*d;//这一年总的盈亏 if(rs<0) cout<<"Deficit"<<endl; else cout<<rs<<endl; } return 0; } [/cpp] 这个版本提交AC,用时32MS,内存216K。 枚举 因为这题只有12月份,每个报告只是连续5个月,要是每个报告都亏损,则可以在5个月里有1个月份亏损、2个月份亏损、3个月份亏损、4个月份亏损、5个月份亏损。前4种情况依次求解,直到既满足每个报告亏损,又使12个月总的是盈利。如果前4种都不行,那总的肯定是亏损。 这4种情况可以这样来表示,由于每5个月的报账都为亏损,所有连续的5个月里至少有1个月为亏损,则可能产生最优解的情况为如下4种
1 2 3 4 5 6 7 8 9 10 11 12 s s s s d s s s s d s s //每5个月里只有1个月亏损 s s s d d s s s d d s s //每5个月里只有2个月亏损 s s d d d s s d d d s s //每5个月里只有3个月亏损 s d d d d s d d d d s d //每5个月里只有4个月亏损
从上到下一次枚举四种情况,如果满足条件(每次报账都为亏损),则算出ans = (盈利的月数)*s – (亏损的月数)*d,若ans>0 则输出 否则输出Deficit;若上述4种情况都不满足,则直接输出Deficit. 代码如下: [cpp] #include<iostream> using namespace std; int main() { int a,b; while(cin>>a>>b) { int ans = 0; if(4*a-b<0) { ans = 10*a-2*b; } else if(3*a-2*b<0) { ans = 8*a-4*b; } else if(2*a-3*b<0) { ans = 6*a-6*b; } else if(a-4*b<0) { ans = 3*a-9*b; } else { ans = -1; } if(ans>0) cout<<ans<<endl; else cout<<"Deficit"<<endl; } return 0; } [/cpp] 这个版本提交AC,用时0MS,内存216K。 很显然,枚举速度更快,但是如果总共有1000个月份,每个报告20个月,共50个报告,或者数字更大时,枚举就很麻烦了,所以掌握第一种贪心的方法还是很有必要的。]]>