分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
在众多的排序算法中使用到分治法的有归并排序,详见我的博客:http://blog.csdn.net/monkey_rose/article/details/52652460,和快速排序,详见我的博客:http://blog.csdn.net/monkey_rose/article/details/53931289
使用分治法的关键线索是分割得到的子问题和原问题是相同类型的问题,区别是规模的大小,那么可以轻松地用递归来解决问题,不难想到,通常在分割问题时,子问题与原问题规模相差要足够大,解决问题的效率才会明显提高,比如可以将原问题的规模减半划为2个子问题。
题目:
169.Majority Element(题目链接)
Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋
times.
You may assume that the array is non-empty and the majority element always exist in the array.
首先我想到的解法是在vector中找到两个不同的数,将其移出,那么原出现最多的数依然是新的vector中出现最多的数,层层递归,直到vector足够小,那么按照我的思路,写了如下的程序:
class Solution {
public:
int majorityElement(vector<int>& nums) {
if (nums.size() == 1) return nums[0];
if (nums.size() <= 3) {
if (nums[0] == nums[1]) return nums[0];
else {
if (nums[0] == nums[2]) return nums[0];
else return nums[1];
}
}
std::vector<int>::iterator head = nums.begin();
std::vector<int>::iterator it;
for (it = nums.begin(); it != nums.end(); it++){
if (*it != *head) {
nums.erase(it);
nums.erase(head);
break;
}
}
if (it == nums.end()) return nums[0];
else return majorityElement(nums);
}
};
然而,算法的效率并不如意:
分析原因,以上算法的时间复杂度为O(n^2),因为执行majorityElement函数n/2次,函数的时间复杂度是O(n),问题出现在我们执行了太多次函数,也就是分割时规模减小得不够,如果我们每次分割能将vector分成大小相同的2个vector,那么函数执行的次数就可以有效减小到logn,而如果做这样的分割,原vector种的major值一定是2个子vector中至少一个的major值,那么当2个子vector中major值不同时,则要去判断哪个值是原vector的major值,判断的时间复杂度是O(n),函数的时间复杂度也是O(n),但是执行次数减少到logn,算法的复杂度减为O(nlogn):
-
class Solution {
-
public:
-
int majorityElement(vector<int>& nums) {
-
int size = nums.size();
-
if(size == 1) return nums[0];
-
vector<int> nums1(nums.begin(),nums.begin()+size/2);
-
int major1 = majorityElement(nums1);
-
vector<int> nums2(nums.begin()+size/2,nums.end());
-
int major2 = majorityElement(nums2);
-
if(major1 == major2) return major2;
-
else{
-
int temp = 0;
-
for(int i = 0; i < size; i++)
-
if(major1 == nums[i]) temp++;
-
if(temp > size/2) return major1;
-
return major2;
-
}
-
}
-
};
换了效率更高的分治法后通过了全部的测试样例,之后我又在其他博客上学到了一种很巧妙的方法,不是用的分治法,时间复杂度为O(n),空间复杂度为O(1),这个算法只是将vector遍历一次,对于每个数值来说,遍历时每次出现一次,可以将count++,出现其他数时将count--,那么最后可以使count大于0的只有我们要得到的major:
-
class Solution {
-
public:
-
int majorityElement(vector<int>& nums) {
-
int major = nums[0], count = 1;
-
for (int i = 1; i < nums.size(); i++) {
-
if (count == 0) {
-
major = nums[i];
-
count++;
-
}
-
else if (major == nums[i]) {
-
count++;
-
}
-
else {
-
count--;
-
}
-
}
-
return major;
-
}
-
};
经过这道题可以感受到,分治法可以为做题时提供一种思路,但是因为用到了递归,其空间复杂度会比较大,而时间复杂度也未必是最佳,不过一般也不会是最差,所以在解一些题目时可以优先考虑分治法,并优化分治策略,同时可以思考有没有适合的更好的算法。