【leetcode算法面试】leetcode题目8-分治

   分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。即一种分目标完成程序算法,简单问题可用二分法完成。

  • 分治法基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同
  • 递归的解这些子问题,然后将各子问题的解合并得到原问题的解

 

题号题目备注
4Median of Two Sorted Arrays两个有序数组的中位数 
50Pow(x, n) 求x的n次方 
53Maximum Subarray 最大连续子数组和分治法 / 动态规划
69Sqrt(x) 求平方根 
215

Kth Largest Element in an Array 数组中第k大的数字

分治法 / 堆排序
   
   
   
   
   

 

4. [LeetCode] Median of Two Sorted Arrays

    两个有序数组的中位数

       给定两个已经升序排序过的数组,求这两个数组的中位数;中位数的定义为把两个数组合并过后进行升序排序后,处于数组中间的那个数,此时如果合并后的数组元素个数为偶数,则为中间两个数的平均值。

       最简单的就是采用归并排序的思想把两个数组进行合并,然后取中间的数就可以了。但问题在于,这个题目限定了时间复杂度为O(log(m+n)),而合并算法的时间复杂度为O(nlogn),另外一个方法是设置一个双指针,一开始都指向两个数组的开头,不停地比较两个指针指向的元素的大小,指向小元素的指针的往前移一个元素去追指向大元素的指针,一直移动(len1+len2)/2次后就能得到中位数,但是这个算法的时间复杂度仍然不符合题意,为O(n)。

      在有序又要求log级的时间复杂度,可以考虑分治策略,采用二分法

      需要找的就是第k小的元素问题

对于一个长度为n的已排序数列a,若n为奇数,中位数为a[n / 2 + 1] , 
    若n为偶数,则中位数(a[n / 2] + a[n / 2 + 1]) / 2
    如果我们可以在两个数列中求出第K小的元素,便可以解决该问题
    不妨设数列A元素个数为n,数列B元素个数为m,各自升序排序,求第k小元素
    取A[k / 2] B[k / 2] 比较,
    如果 A[k / 2] > B[k / 2] 那么,所求的元素必然不在B的前k / 2个元素中(证明反证法)
    反之,必然不在A的前k / 2个元素中,于是我们可以将A或B数列的前k / 2元素删去,求剩下两个数列的
    k - k / 2小元素,于是得到了数据规模变小的同类问题,递归解决
    如果 k / 2 大于某数列个数,所求元素必然不在另一数列的前k / 2个元素中,同上操作就好。

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
	lenNums1 := len(nums1)
	lenNums2 := len(nums2)
	return float64(findKth(nums1, 0, nums2, 0, (lenNums1+lenNums2+1)/2)+findKth(nums1, 0, nums2, 0, (lenNums1+lenNums2+2)/2)) / 2
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func findKth(nums1 []int, s1 int, nums2 []int, s2 int, k int) int {
	if s1 >= len(nums1) {
		return nums2[s2+k-1]
	}
	if s2 >= len(nums2) {
		return nums1[s1+k-1]
	}
	if k == 1 {
		return min(nums1[s1], nums2[s2])
	}
	if s1+k/2-1 >= len(nums1) {
		return findKth(nums1, s1, nums2, s2+k/2, k-k/2)
	} else if s2+k/2-1 >= len(nums2) {
		return findKth(nums1, s1+k/2, nums2, s2, k-k/2)
	}

	if nums1[s1+k/2-1] > nums2[s2+k/2-1] {
		return findKth(nums1, s1, nums2, s2+k/2, k-k/2)
	} else {
		return findKth(nums1, s1+k/2, nums2, s2, k-k/2)
	}
}

 

  参考资料:

https://leetcode.com/problems/median-of-two-sorted-arrays/

https://leetcode.com/problems/median-of-two-sorted-arrays/discuss/2496/Concise-JAVA-solution-based-on-Binary-Search

https://leetcode.com/problems/median-of-two-sorted-arrays/discuss/2499/Share-my-simple-O(log(m%2Bn))-solution-for-your-reference

https://leetcode.com/problems/median-of-two-sorted-arrays/discuss/2471/Very-concise-O(log(min(MN)))-iterative-solution-with-detailed-explanation

 

50. [LeetCode] Pow(x, n) 求x的n次方

Implement pow(x, n), which calculates x raised to the power n(xn).

Example 1:

Input: 2.00000, 10
Output: 1024.00000
Example 2:

Input: 2.10000, 3
Output: 9.26100
Example 3:

Input: 2.00000, -2
Output: 0.25000
Explanation: 2-2 = 1/22 = 1/4 = 0.25
Note:

-100.0 < x < 100.0
n is a 32-bit signed integer, within the range [−231, 231 − 1]

        递归来折半计算,每次把n缩小一半,这样n最终会缩小到0,任何数的0次方都为1,这时候我们再往回乘,如果此时n是偶数,直接把上次递归得到的值算个平方返回即可,如果是奇数,则还需要乘上个x的值。还有一点需要引起我们的注意的是n有可能为负数,对于n是负数的情况,我们可以先用其绝对值计算出一个结果再取其倒数即可

class Solution {
public:
    double myPow(double x, int n) {
        if (n < 0) return 1 / power(x, -n);
        return power(x, n);
    }
    double power(double x, int n) {
        if (n == 0) return 1;
        double half = power(x, n / 2);
        if (n % 2 == 0) return half * half;
        return x * half * half;
    }
};

 

53. Maximum Subarray 最大连续子数组和

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

Follow up:

If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

  分治法解决:

      数组分为左半部分下标为(l,mid-1)和右半部分下标为(mid+1,r)以及中间元素下标为mid,接下来递归求出左半部分的最大子序和:left=helper(nums,l,mid-1); 右半部分最大子序和right=helper(nums,mid+1,r)

将数组均分为两个部分,那么最大子数组会存在于:

  • 左侧数组的最大子数组
  • 右侧数组的最大子数组
  • 左侧数组的以右侧边界为边界的最大子数组+右侧数组的以左侧边界为边界的最大子数组
func maxSubArray(nums []int) int {
	return divide(nums, 0, len(nums)-1)
}

func max(x, y int) int {
	if x < y {
		return y
	}
	return x
}

func divide(nums []int, left, right int) int {
	if left >= right {
		return nums[left]
	}
	var mid = (left + right) / 2
	var leftSum = divide(nums, left, mid-1)
	var rightSum = divide(nums, mid+1, right)
	var midSum = nums[mid]

	var curSum = nums[mid]
	for i := mid - 1; i >= left; i-- {
		curSum += nums[i]
		if midSum < curSum {
			midSum = curSum
		}
	}

	curSum = midSum
	for i := mid + 1; i <= right; i++ {
		curSum += nums[i]
		if midSum < curSum {
			midSum = curSum
		}
	}

	return max(leftSum, max(rightSum, midSum))
}

 

69. [LeetCode] Sqrt(x) 求平方根

Implement int sqrt(int x).

Compute and return the square root of x.

    二分搜索法来找平方根

class Solution {
public:
    int mySqrt(int x) {
        if (x <= 1) return x;
        int left = 0, right = x;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (x / mid >= mid) left = mid + 1;
            else right = mid;
        }
        return right - 1;
    }
};

 

215. [LeetCode] Kth Largest Element in an Array 

   数组中第k大的数字

    堆时间复杂度nlogk,快排时间最好n,最差情况n方 。用到了快速排序Quick Sort的思想

快速排序中,每一次迭代,我们需要选取一个关键元素pivot,然后将数组分割成三个部分:

  1. 小于关键元素pivot的元素
  2. 等于关键元素pivot的元素
  3. 大于关键元素pivot的元素
func findKthLargest(nums []int, k int) int {
	var start, end = 0, len(nums) - 1
	for {
		var pos = partition(nums, start, end)
		if pos == k-1 {
			return nums[pos]
		} else if pos < k-1 {
			start = pos + 1
		} else {
			end = pos - 1
		}
	}
}

func partition(nums []int, start, end int) int {
	var pivot, l, r = nums[start], start, end
	for l < r {
		for nums[r] <= pivot && l < r {
			r--
		}
		if l < r {
			nums[l] = nums[r]
			l++
		}
		for nums[l] >= pivot && l < r {
			l++
		}
		if l < r {
			nums[r] = nums[l]
			r--
		}
	}
	nums[l] = pivot
	return l
}

 

 

一. 分治算法的控制抽象

Type DAndC(P)

{

        if  Small (P)  return S(P);

        else {

                divide P into smaller instances P1, P2, ..., Pk, K >= 1;

                Apply DAndC to each of these subproblems;

                return Combin (DAndC(p1) , ... , DAndC(Pk));

        }

}

 

1. 查找最大与最小值

分治算法 P= {n, a[i], ....... , a[j]} 。       如果n = 1, 最大最小为a[i],           如果n = 2, 可以通过一次比较解决问题。

void MaxMin(int i, int j, Type &max, Type &min)

{

       if (i == j)

              max = min = a[i];

       else if (i == j - 1)

       {

               if (a[i] < a[j]) {

                      max = a[j]; min = a[i];

               }

               else {

                     max = a[i]; min = a[j];

               }

       }

       else {

               int mid = (i + j) / 2;

               MaxMin(i, mid, max, min);

               MaxMin(mid+1, j, max1, min2);

               if (max < max1) max = max1;

               if (min > min1) min = min1;

       }

}

 

 

2. 归并排序算法

是分治法(Divide-and-Conquer)的典型应用。该算法的时间复杂度为O(nlogn), 其操作的步骤如下: 

  • Divide:把n个元素的序列分为两个元素个数为n/2的子序列。
  • Conquer:递归的调用归并排序算法对两个子序列进行排序Combine:对排好序的子序列进行合并,得到最后排序的结果

    归并算法用示意图表示如下:

     

    void MergeSort (int low, int high) {

           if (low < high) {

                    int mid = (low + high) / 2;

                    MergeSort (low, mid);

                    MergeSort (mid + 1, high);

                    Merge (low, mid , high);

           }

    }

    void Merge (int low, int mid, int high) {

           int i = low, m = mid, j = mid + 1, n = high, k = 0;

           while ((low <= m) && (j <= n)) {

                   if (a[i] < a[j])

                          temp[k++] = a[i++];

                   else

                          temp[k++] = a[j++] 

           }

           while (i <= m) 

                   temp[k++] = a[i++];

           while (j <= n)

                   temp[k++] = a[j++];

    }

     

    3. 快速排序

    该方法的基本思想是:

    • 先从数列中取出一个数作为基准数。
    • 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
    • 再对左右区间重复第二步,直到各区间只有一个数。

    对挖坑填数进行总结

    • i =L; j = R; 将基准数挖出形成第一个坑a[i]。
    • j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
    • i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
    • 再重复执行2,3二步,直到i==j,将基准数填入a[i]中。
    void QuickSort (int a[], int l, int r) {
    
           int i = l, j = r, temp = a[l];
    
           while (i < j) {
    
                  while ((i < j) && temp <= a[j])
    
                           j--;
    
                  if (i < j) 
    
                           a[i++] = a[j];
    
                  while ((i < j) && temp > a[i])
    
                           i++;
    
                  if (i < j)
    
                           a[j--] = a[i];
    
           }
    
           a[i] = temp;
    
           QuickSort(a, l, i-1);
    
           QuickSort(a, i+1, r);
    
    }

     

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值