分治算法
分治算法在程序设计中应用广泛,总体思想是将一个大的问题分解为子问题,再将子问题的解进行治理得到最终解,下面看几个问题。
最大子列和
本题要求我们求出具有最大和的连续子数组,运用分治法的思路为:将数组分为两个子数组,先求出左边数组的最大子列和再求出右边数组的最大子列和,这就是我们所说的分,接下来就是治,此时如何得到整个数组的最大子列和呢,能仅仅从左右两边数组的最大子列和求出最大值吗,可能不行,因为我们还需要考虑一种情况:即跨越左右边界的连续数组也有可能成为解,因此我们从边界出发向左右两个方向生长求出一个最大子列和再与刚刚的两个值比较求出最大的即可。下面看一下代码:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if(nums.size()==0){
return 0;
}
return seqmaxSubArray(nums,0,nums.size());
}
int seqmaxSubArray(vector<int>& nums,int left,int right){
if(right-left == 0){
return 0;
}
if(right-left==1){
return nums[left];
}
int mid = left + (right-left) / 2;
int leftSubMax = seqmaxSubArray(nums,left,mid);
int rightSubMax = seqmaxSubArray(nums,mid,right);
int leftmax = INT_MIN;
int sum_left = 0;
int left_idx = 0;
for(int i=mid-1;i>=0;i--){
sum_left += nums[i];
if(sum_left>leftmax){
left_idx = i;
leftmax = sum_left;
}
}
int rightmax = INT_MIN;
int sum_right = 0;
int right_idx = 0;
for(int i=mid;i<right;i++){
sum_right += nums[i];
if(sum_right>rightmax){
right_idx = i;
rightmax = sum_right;
}
}
return max(max(leftSubMax,rightSubMax),leftmax+rightmax);
}
};
- 时间复杂度: o ( n l o g n ) o(nlogn) o(nlogn)
- 空间复杂度: o ( l o g n ) o(logn) o(logn),因为递归树的深度为 l o g n logn logn因此得到空间复杂度。
动态规划解法
本题也可以通过动态规划求解,动态规划的难点在于如何确定状态空间与状态转移方程,在本题中,我们将状态空间定义为以该节点作为子序列结尾的最大子列和,这样得出状态转移方程为
s
u
m
(
i
)
=
m
a
x
(
s
u
m
(
i
−
1
)
+
n
u
m
s
[
i
]
,
n
u
m
s
[
i
]
)
sum(i)=max (sum(i-1)+nums[i],nums[i])
sum(i)=max(sum(i−1)+nums[i],nums[i])
在实际编程中,通常可以不需要开辟一个数组来存储所有的状态,因为某个状态被用于求出下一个状态后就不需要了,下面的代码就是这样:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if(nums.size()==0){
return 0;
}
if(nums.size()==1){
return nums[0];
}
//int left = 0;
int right = 0;
int sum = 0 ;
int ans = INT_MIN ;
while(right<nums.size()){
sum = max(sum+nums[right],nums[right]);
ans = max(sum,ans);
right++;
}
return ans;
}
- 时间复杂度: o ( n ) o(n) o(n)
- 空间复杂度: o ( 1 ) o(1) o(1)
Pow(x, n)
本题思路较为简单,求x的n次幂,分别求出其n/2次幂然后再相乘即可。当然需要注意n分为奇数和偶数的情况:
class Solution {
public:
double myPow(double x, int n) {
if(x==1){
return 1.0;
}
if(n==0){
return 1;
}
double res = 1;
long nums = abs(n);
if(n%2==0){
res = myPow(x,nums/2);
res = res*res;
}
else{
res = myPow(x,nums/2);
res = res*res*x;
}
if(n<0){
res = 1/res;
}
return res;
}
};
- 时间复杂度: o ( n l o g n ) o(nlogn) o(nlogn)
- 空间复杂度: o ( l o g n ) o(logn) o(logn)
多数元素
本题比较容易想到基于散列表的思路:遍历数组,对于在散列表中查不到对应key的元素将其添加至散列表中,并置value为1,如果查找到,将对应的value加一,最后遍历一遍散列表得到value最大的那个key。 时间与空间复杂度均为
o
(
n
)
o(n)
o(n)
基于分治算法的思路类似,将数组分为两部分,求出两部分的众数,则整体的众数一定为二者之一(可以通过反证法证明),这时我们比较两个值出现的次数,出现更多的即为众数。 下面只给出分治算法的代码:
class Solution {
public:
int majorityElement(vector<int>& nums) {
if(nums.size()==0){
return 0;
}
return findMaj(nums,0,nums.size());
}
int findMaj(vector<int>& nums,int left,int right){
if(right-left==1){
// sum = 1;
return nums[left];
}
int mid = left + (right-left) / 2;
int sum_l = 0;
int l = findMaj(nums,left,mid);
int sum_r = 0;
int r = findMaj(nums,mid,right);
for(int i=left;i<right;i++){
if(nums[i] == l){
sum_l++;
}
else if(nums[i] == r){
sum_r++;
}
}
if(sum_l>sum_r){
return l;
}
return r;
}
};
- 时间复杂度: o ( n l o g n ) o(nlogn) o(nlogn)
- 空间复杂度: o ( l o g n ) o(logn) o(logn)