前言:
无论什么题目,都可以遍历来计算,时间复杂度是最高的,是其他方法的上限。其实如果你只能想到穷举的方法,那说明你可能正在接近答案,因为有一种最优答案不是自成一派的,而是从最差答案修改而来,也就是说我们可以通过隐藏的性质来进行筛选,从而降低时间复杂度,本文的两题都是如此。
最近连续在写leetcode的时候连续遇到两道题目,题目的完成率很高,但是我却没有写出来,后来发现这类问题缺乏反应,总结后为其取名为穷举优化法,废话不多说,小二,上题目。
Container with Most Water
Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.
题目要求选择两块木板,然后着两块木板中间可以盛放的水量最多。
O(n^2)法:
两两计算水量。
优化法:
如前言所说,我们可以寻找隐藏性质。
首先选择首尾两块木板a1和an。那么水量S1n=min(a1,an)*(n-1)。
假设a1<an,那么可以得出结论:S1n>Sin(i>1)。也就是说什么S2n,S3n……都不用计算了,因为他们都小于S1n,看到了吧,立马少计算了n-2次。
既然结论是这样,那我们只需要拿S1n和其他S来比较,于是我们计算其他S,由于a1<an,所以我们抛弃第一块板(因为所有以第一块板为边的水量都小于S1n),计算第二块板为边的S,计算S2n。然后依次类推即可。
很明显优化法的时间复杂度为O(n)。
3Sum Closest
Given an array S of n integers, find three integers in S such that the sum is closest to a given number, target. Return the sum of the three integers. You may assume that each input would have exactly one solution.
要求是求三个元素的和最接近目标值t
O(n^3)法:
呵呵O(∩_∩)O~
优化法:
首先我们进行排序O(nlgn),为什么这里要排序而上一题的最大存水量不需要呢?因为上一题除了木板高度还和木板之间的间距有关,所以不需要排序。但是该题如果不排序你真的只能O(n^3)的方法来计算了,因为你没有办法根据性质来筛选,真的没有,所以排序是个好东西,浪费了O(nlgn)的时间,但是增加了很多性质,这些性质能够利用之,来降低复杂度,先抑后扬么?。。。。
言归正传,排好序后,我们先确定含有第一个元素的三元素小集合的最接近t的值S1。还剩两个元素,我们先取第2个和第n个,此时计算S12n,若S12n<t,则计算S13n,为什么可以直接计算S13n而跳过S12i(i>2)呢,同Container with Most Water,我们可以证明S12i<S12n<t,所以S12n是最接近t的和,看到没有,又省下了n-3次。
以此类推,就可以计算出包含第一个元素a1的三元组中最接近t的那个S1。再以此类推计算包含第二个元素的三元组的S2……最后从Si中挑出最接近t的即为最佳解。
其他题:
4Sum (难度更大,但是采用的方法类似,只是更加复杂)
总结:
如前言所说,穷举法是个好东西,不要抛弃它,我们要做的是循序渐进,不断挖掘性质来优化,或者不断通过排序等操作(这些操作会浪费一些时间空间复杂度,但是换来的是远比穷举好很多的优化算法,所以先抑后扬吧),这些操作能够使数据重新显露出符合题目的性质,我们赶快抓紧,利用这些性质来降低复杂度,那么目的也就达到了。
这两题还有一个共同点,就是通过不断的缩小范围来达到最优解,我在想缩小范围的过程也可以认为是一种类似贪心但是方向相反的方法,也就是现有一个解,然后把这个解构造成一种贪心解(例如题一中,S1n当做贪心解的条件是S1i都小于S1n,以此来排除一些可能性)。
附代码:
int maxArea(vector<int> &height) {
int capability = 0;
size_t left = 0, right = height.size() - 1;
while (left < right)
{
int water =
min(height[left], height[right]) * (right - left);
if (water > capability) capability = water;
if (height[left] < height[right])
{
++left;
}
else
{
--right;
}
}
int threeSumClosest(vector<int> &num, int target) {
sort(num.begin(), num.end());
int n = num.size();
int close = num[0] + num[1] + num[n-1];
int interval = abs(target - close);
for(int i = 0; i < n - 2; ++i)
{
int j = i + 1;
int k = n - 1;
while(j < k)
{
int sum = num[i] + num[j] + num[k];
if(sum == target)
{
return target;
}
else if(sum > target)
{
if(abs(sum - target) < interval)
{
close = sum;
interval = abs(sum - target);
}
--k;
}
else
{
if(abs(sum - target) < interval)
{
close = sum;
interval = abs(sum - target);
}
++j;
}
}
}
return close;
}