Divide-and-Conquer的三個步驟:算法
Divide the problem into a number of subproblems that are smaller instances of the
same problem.
Conquer the subproblems by solving them recursively. If the subproblem sizes are
small enough, however, just solve the subproblems in a straightforward manner.
Combine the solutions to the subproblems into the solution for the original problem.數組
簡單來講,就是將原問題分解成性質相同的子問題,到子問題小到可以直接解決的時候再將子問題遞歸式的合爲原問題,這種方法不必定能下降問題的複雜度,但它在必定程度上使問題變得更直觀簡潔,而且此類方法很容易分析複雜度。ide
一些簡單的名詞解釋,what is recursive case and what is base case?通常來講,base case就是可以直接解決的子問題,除了這些子問題都是recursive case。idea
recurrences在這裏的意思是遞歸式。遞歸和Divide-and-Conquer有什麼關係?但咱們想要用Divide-and-Conquer的時候,用遞歸來實現實際上是一個很天然的過程。並且藉助recurrences能幫助咱們用(後面會講的)master method分析整個算法的運行時間。spa
例如,下面就是一個recurrences。
code
A recurrence of the form in equation (4.2) characterizes a divideand-conquer algorithm that creates a subproblems, each of which is 1=b the
size of the original problem, and in which the divide and combine steps together take f(n) time.
對於上述的recurrences,咱們能夠這樣解讀:它將原問題分紅了a個子問題,每一個子問題的規模是原問題的1/b,而且每一次拆分和組合的過程須要f(n)的時間。orm
關於recurrences有一些細節須要注意:
1.When we state and solve recurrences, we often omit floors, ceilings, and boundary conditions.
對於某些recurrences來講會帶有向上取整/向下取整等符號,在咱們計算複雜度等的時候能夠忽略這些符號。
2.sometimes we ignore the (if n=1) case,The reason is that although changing
the value of T(1) changes the exact solution to the recurrence, the solution typically doesn’t change by more than a constant factor, and so the order of growth is
unchanged. 有時候咱們寫一個recurrences的時候會自動省略它的base case的狀況,由於這類狀況一般不會對
算法的複雜度形成影響。對象
給出這樣一個問題,解決它的算法有哪些?
blog
俗稱「暴力破解法」,匹配每一對日子,並比較profit的最大值得出答案。遞歸
class Solution { public: int maxProfit(vector<int>& prices) { int size=prices.size(); int profit=INT_MIN; for(int cnt=0;cnt<size;cnt++){ for(int i=cnt;i<size;i++){ profit=max(profit,prices[i]-prices[cnt]); } } return profit; } };
太簡單也就不提了。
其實這道題我在leetcode見過,#121. Best Time to Buy and Sell Stock
有一個很清晰也很簡單的解決辦法,這個問題實際上是要咱們作到「低價買,高價賣」,
因此咱們只須要知道每個price
後面的max-price
便可,
先從數組尾端開始遍歷一遍獲得每一個price
後面的max-price
,再遍歷一遍比較便可,time complexity:O(n), space complexity:O(n)。
class Solution { public: int maxProfit(vector<int>& prices) { if(prices.size()<=0) return 0; int sold=INT_MIN;//儘可能大 vector<int> inverse_max=prices; for(auto end=inverse_max.end()-1;end>=inverse_max.begin();end--) sold=*end=max(sold,*end); int profit=0; for(int cnt=0;cnt<prices.size();cnt++){ profit=max(inverse_max[cnt]-prices[cnt],profit); } return profit; } };
剛開始看到這個賣股票的時候我懵逼了,由於實在想不到它爲何能用Divide-and-Conquer作,直到書上
告訴我這裏有一個tranformation我才恍然大悟。所謂的transformation,就是不用原來的prices數據,而是
用價格的變化做爲處理對象。由此,此問題就變成了Maximum-subarray problem。
對於最大子數組這個問題,其實也有更爲簡潔粗暴的解決方案:
//leetcode #53. Maximum Subarray class Solution { public: int maxSubArray(vector<int>& nums) { int result = INT_MIN, f = 0; for (int i = 0; i < nums.size(); ++i) { f = max(f + nums[i], nums[i]); result = max(result, f); } return result; } };
書上爲了介紹Divide-and-Conquer,就強行用了Divide-and-Conquer作。
總體思路是:將原數組分爲左右兩個數組,此數組的Maximum-subarray要麼所有在左邊,要麼所有在右邊,
要麼一部分在左邊一部分在右邊,只需比較這三種狀況下的Maximum-subarray,就能獲得答案,算法的僞代碼是:
Algorithm:FIND_MAX_CROSSING_SUBARRAY(A,low,mid,high) left_sum=INT_MIN sum=0 for i=mid downto low sum+=A[i] if sum>left_sum left_sum=sum max_left=i right_sum=INT_MIN sum=0 for j=mid+1 to high sum+=A[j] if sum>right_sum right_sum=sum max_right=i return(max_left,max_right,left_sum+right_sum) Algorithm:FIND_MAXIMUM_SUBARRAY(A,low,high) if high==low return(low,high,A[low]) //base case:only one element else mid=(low+high)/2 (left_low,left_high,left_sum)=FIND_MAXIMUM_SUBARRAY(A,low,mid) (right_low,right_high,right_sum)=FIND_MAXIMUM_SUBARRAY(A,mid+1,high) (cross_low,cross_high,cross_sum)=FIND_MAX_CROSSING_SUBARRAY(A,low,mid,high) return max(left,right,cross)
而對於這個Divide-and-Conquer算法來講,它的recurrences是:
與merge-sort的recurrences相同,能夠得知,這個算法的時間複雜度是:O(nlogn)
What does FIND-MAXIMUM-SUBARRAY return when all elements of A are negative?
I think the algorithm will return the maximum negative number in the array.
Write pseudocode for the brute-force method of solving the maximum-subarray
problem. Your procedure should run in O(n^2) time.
Too easy to write, I think.
Suppose we change the definition of the maximum-subarray problem to allow the
result to be an empty subarray, where the sum of the values of an empty subarray is 0. How would you change any of the algorithms that do not allow empty
subarrays to permit an empty subarray to be the result?
Solution: Check all of the return value, if the value is negative then return zero.
Use the following ideas to develop a nonrecursive, linear-time algorithm for the
maximum-subarray problem. Start at the left end of the array, and progress toward
the right, keeping track of the maximum subarray seen so far. Knowing a maximum
subarray of A[1..j], extend the answer to find a maximum subarray ending at index j+1 by
using the following observation: a maximum subarray of A[1..j+1]
is either a maximum subarray of A[1..j] or a subarray A[i..j+1], for some
i belongs to [1,j+1]. Determine a maximum subarray of the form A[i..j+1] in
constant time based on knowing a maximum subarray ending at index j.
class Solution { public: int maxSubArray(vector<int>& nums) { //遍歷nums //遇到一個數將其加入temp_subarray,若temp_sum+nums[cnt]>0,則保持,若其<=0,比較sum和temp_sum //若sum小,則sum=temp_sum,且subarray=temp_subarray,sum大,則無視 //而後令temp_subarray的首尾都等於cnt+1 int temp_sum=0; int sum=INT_MIN; vector<int> temp_subArray,subArray; for(int cnt=0;cnt<nums.size();cnt++){ temp_subArray.push_back(nums[cnt]);//遇到一個數先將其加入temp_subArray temp_sum+=nums[cnt]; if(temp_sum<=0){ if(temp_subArray.size()==1){//是爲了不連着兩個負數的狀況,好比{-1,-2},沒有加這個條件就AC不了} if(nums[cnt]>sum){ sum=temp_sum; temp_subArray.pop_back(); } temp_subArray.erase(temp_subArray.begin(),temp_subArray.end()); temp_sum=0; } else{ if(temp_sum-nums[cnt]>sum){ //找到新子串 sum=temp_sum; temp_subArray.pop_back(); } temp_subArray.erase(temp_subArray.begin(),temp_subArray.end()); temp_sum=0; } } else{ sum=temp_sum>sum?temp_sum:sum; } } return sum; } };
關於這道題,上面是我在作Leetcode那道題的時候第一次用的解法,時間複雜度也是O(n),可是有可能不符合這道題的思路,這道題給出的思路我其實有點想不通, 若是有人以爲本身瞭解那個思路具體是怎麼作的,麻煩在評論區留言,請不吝賜教!