这星期算法课刚好讲到分治算法,便在leetcode上面找了两道关于分治算法的练习题来练练手。
第一题:
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.
解题思路:
题面很简单,只是要求找到给定的数组中的主元,这个主元就是在该数组中出现的次数大于 ⌊ n/2 ⌋次的数。
暴力求解的话需要对数组中的一半的数遍历一遍数组,复杂度为O(n*n),显然,这样的复杂度是不能接受的。因此,可以通过分治的思想来解决这道题。
首先,以一个数作为比较数,将数组中的数分成三部分,比该数小的,比该数大的,与该数相等的。而后就可以缩小搜索的范围。并在更小的范围内应用相同的方法继续操作。
分治算法的最差复杂度也是O(n*n),但是,这种情况发生的概率非常低,而通过选择合适的划分元素,可以将复杂度降到O(n),比直接使用最快的排序算法O(nlog n)还快。
代码:
#include <iostream>
#include <vector>
using namespace std;
int size;
/*此处的接口设计只是为了与leetcode上所给函数相同,自定义函数可以增加begin和end参数,以减少空间的开销*/
int findMajorityElement(vector<int>& nums) {
int com = nums[0]; //此处偷懒,将第一个数作为划分的依据
vector<int> v1, v2; //通过重新设置函数的参数可以减少这一部分的空间开销
for (int i = 1; i < nums.size(); i++) {
if (nums[i] < com) v1.push_back(nums[i]);
else if (nums[i] > com) v2.push_back(nums[i]);
}
if ((size % 2 == 0 && v1.size() + v2.size() < size / 2) ||
(size % 2 == 1 && v1.size() + v2.size() <= size / 2))
return com;
if (v1.size() > size / 2) return findMajorityElement(v1);
if (v2.size() > size / 2) return findMajorityElement(v2);
}
int main() {
vector<int> nums;
int temp;
cin >> size;
for (int i = 0; i < size; i++) {
cin >> temp;
nums.push_back(temp);
}
cout << findMajorityElement(nums) << endl;
}
第二题:
215. Kth Largest Element in an Array
Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.
For example,
Given [3,2,1,5,6,4] and k = 2, return 5.Note:
You may assume k is always valid, 1 ≤ k ≤ array’s length.
解题思路:
找出数组中的第K大的数,显然,这道题目也是与上一题的思路相同,都可以通过分治算法来加以解决。
同样是做一个划分,找到一个数作为划分的依据,然后缩小搜索的范围,同样可以将复杂度从O(nlog n)降到O(n)。
在上一题的代码中,可以发现,我直接将数组的第一个数作为划分的依据,这样的做法对于某些特殊的数组(如递增数组,递减数组)而言无法做到降低复杂度的作用。
同时,如果单纯使用第一个数作为划分依据,在这道题中的一个大的递增数组会导致超时或者爆内存。因此我改为随机生成下标的方法来规避这样的特殊数据。
代码:
#include <iostream>
#include <vector>
#include <stdlib.h>
using namespace std;
int findKthLargest(vector<int>& nums, int k) {
vector<int> v1, v2;
int index = rand() % nums.size();
int com = nums[index];
int count = 0;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] < com) v1.push_back(nums[i]);
else if (nums[i] > com) v2.push_back(nums[i]);
else count++;
}
if (v2.size() >= k) return findKthLargest(v2, k);
else if (v2.size() + count >= k) return com;
else return findKthLargest(v1, k - count - v2.size());
}
int main() {
vector<int> nums;
int temp, n, k;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> temp;
nums.push_back(temp);
}
cin >> k;
cout << findKthLargest(nums, k) << endl;
}
总结:
这一类可以将大的问题转化为同类型的子问题,非常适合使用分治算法来加以解决,同时,在使用分治算法时,应该注意规避特殊的数据,且通过合适的方法减少空间的开销。