04.02.05 分治算法 把规模大的问题不断分解为子问题,使得问题规模减小到可以直接求解为止。分治算法从实现方式上来划分,可以分为两种:「递归算法」和「迭代算法」。
使用分治算法解决问题主要分为3个步骤:
分解:把要解决的问题分解为成若干个规模较小、相对独立、与原问题形式相同的子问题。
求解:递归求解各个子问题。
合并:按照原问题的要求,将子问题的解逐层合并构成原问题的解。
归并排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Solution {public : vector<int > tmp; void mergeSort (vector<int >& nums, int left, int right) { if (left >= right){ return ; } int mid = (left + right) / 2 ; mergeSort (nums, left, mid); mergeSort (nums, mid+1 , right); tmp.clear (); int i = left; int j = mid + 1 ; while (i <= mid && j <= right){ if (nums[i] <= nums[j]){ tmp.push_back (nums[i++]); } else { tmp.push_back (nums[j++]); } } while (i <= mid){ tmp.push_back (nums[i++]); } while (j <= right){ tmp.push_back (nums[j++]); } for (int k = left, t = 0 ; k <= right; ++k, ++t){ nums[k] = tmp[t]; } } vector<int > sortArray (vector<int >& nums) { tmp.resize (nums.size ()); mergeSort (nums, 0 , nums.size () - 1 ); return nums; } };
分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : int search (vector<int >& nums, int target) { int n = nums.size (); int left = 0 ; int right = n - 1 ; while (left <= right){ int mid = (left + right) / 2 ; if (nums[mid] == target){ return mid; } else if (nums[mid] < target){ left = mid + 1 ; } else { right = mid - 1 ; } } return -1 ; } };
哈希表 先遍历一遍数组,放入哈希表中,再遍历哈希表,找到多数元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int majorityElement (vector<int >& nums) { int n = nums.size (); unordered_map<int , int > hash; for (int i = 0 ; i < n; i++){ hash[nums[i]]++; } for (auto & kv : hash){ if (kv.second > n / 2 ){ return kv.first; } } return -1 ; } };
时间复杂度:O(n)
空间复杂度:哈希表,O(n)
分治法 如果 num 是数组 nums 的众数,那么我们将 nums 分为两部分,则 num 至少是其中一部分的众数。则我们可以用分治法来解决这个问题。具体步骤如下:
将数组 nums 递归地将当前序列平均分成左右两个数组,直到所有子数组长度为 1。
长度为1的子数组众数肯定是数组中唯一的数,将其返回即可。
将两个子数组依次向上两两合并。
如果两个子数组的众数相同,则说明合并后的数组众数为:两个子数组的众数。
如果两个子数组的众数不同,则需要比较两个众数在整个区间的众数。
最后返回整个数组的众数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { int count_in_range (vector<int >& nums, int target, int low, int high) { int count = 0 ; for (int i = low; i <= high; i++){ if (nums[i] == target){ count++; } } return count; } int majority_element_rec (vector<int >& nums, int low, int high) { if (low == high){ return nums[low]; } int mid = (low + high) / 2 ; int left = majority_element_rec (nums, low, mid); int right = majority_element_rec (nums, mid+1 , high); if (count_in_range (nums, left, low, high) > (high-low+1 ) / 2 ){ return left; } if (count_in_range (nums, right, low, high) > (high-low+1 ) / 2 ){ return right; } return -1 ; }public : int majorityElement (vector<int >& nums) { return majority_element_rec (nums, 0 , nums.size () - 1 ); } };
时间复杂度:O(nlogn)
空间复杂度:O(logn)
投票算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int majorityElement (vector<int >& nums) { int candidate = -1 ; int count = 0 ; for (int num : nums) { if (num == candidate) ++count; else if (--count < 0 ) { candidate = num; count = 1 ; } } return candidate; } };
动态规划 1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : int maxSubArray (vector<int >& nums) { int pre = 0 ; int res = nums[0 ]; for (int i = 0 ; i < nums.size (); i++){ pre = max (pre + nums[i], nums[i]); res = max (res, pre); } return res; } };
分治法 维护四个变量:
lSum:以l为左端点的最大子段和,lSum要么等于左子区间的lSum,要么等于左子区间的iSum+右子区间的lSum,取较大值。
rSum:以r为右端点的最大子段和,rSum要么等于右子区间的rSum,要么等于右子区间的iSum+左子区间的rSum,取较大值。
mSum:[l, r]区间的最大子段和:mSum可能是左子区间的mSum,也可能是右子区间的mSum,也可能跨越左右子区间,即左子区间的rSum+右子区间的lSum。
iSum:[l, r]整个区间和,等于左子区间的iSum+右子区间的iSum。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution {public : struct Status { int lSum, rSum, mSum, iSum; }; Status pushUp (Status left, Status right) { int iSum = left.iSum + right.iSum; int lSum = max (left.lSum, left.iSum + right.lSum); int rSum = max (right.rSum, right.iSum + left.rSum); int mSum = max (max (left.mSum, right.mSum), left.rSum + right.lSum); return (Status){lSum, rSum, mSum, iSum}; } Status get (vector<int >& nums, int left, int right) { if (left == right){ return (Status){nums[left], nums[left], nums[left], nums[left]}; } int mid = (left + right) / 2 ; Status lSub = get (nums, left, mid); Status rSub = get (nums, mid + 1 , right); return pushUp (lSub, rSub); } int maxSubArray (vector<int >& nums) { return get (nums, 0 , nums.size () - 1 ).mSum; } };
分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution {public : vector<int > beautifulArray (int n) { if (n == 1 ){ return {1 }; } vector<int > nums (n) ; for (int i = 0 ; i < n; i++){ nums[i] = i + 1 ; } int leftCnt = (n + 1 ) / 2 ; int rightCnt = n - leftCnt; vector<int > left = beautifulArray (leftCnt); vector<int > right = beautifulArray (rightCnt); for (int i = 0 ; i < leftCnt; i++){ nums[i] = 2 * left[i] - 1 ; } for (int i = 0 ; i < rightCnt; i++){ nums[leftCnt+i] = 2 * right[i]; } return nums; } };
时间复杂度:O(nlogn)
空间复杂度:O(nlogn)
分治法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution {public : vector<int > diffWaysToCompute (string expression) { vector<int > res; for (int i = 0 ; i < expression.length (); i++) { char ch = expression[i]; if (ch == '+' || ch == '-' || ch == '*' ) { vector<int > leftRes = diffWaysToCompute (expression.substr (0 , i)); vector<int > rightRes = diffWaysToCompute (expression.substr (i + 1 )); for (int left : leftRes) { for (int right : rightRes) { if (ch == '+' ) { res.push_back (left + right); } else if (ch == '-' ) { res.push_back (left - right); } else if (ch == '*' ) { res.push_back (left * right); } } } } } if (res.empty ()) { res.push_back (stoi (expression)); } return res; } };
时间复杂度:$O(2^n)$
空间复杂度:$O(2^n)$
分析 如果只剩一个链表,就结束。然后合并两个链表,按照归并排序的方式合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 class Solution {public : ListNode* mergeSort (vector<ListNode*>& lists, int left, int right) { if (left == right){ return lists[left]; } int mid = (left + right) / 2 ; ListNode* leftList = mergeSort (lists, left, mid); ListNode* rightList = mergeSort (lists, mid + 1 , right); return merge (leftList, rightList); } ListNode* merge (ListNode* leftList, ListNode* rightList) { ListNode* dummy = new ListNode (-1 ); ListNode* cur = dummy; while (leftList && rightList){ if (leftList->val <= rightList->val){ cur->next = leftList; leftList = leftList->next; } else { cur->next = rightList; rightList = rightList->next; } cur = cur->next; } while (leftList){ cur->next = leftList; leftList = leftList->next; cur = cur->next; } while (rightList){ cur->next = rightList; rightList = rightList->next; cur = cur->next; } return dummy->next; } ListNode* mergeKLists (vector<ListNode*>& lists) { if (lists.size () == 0 ){ return NULL ; } return mergeSort (lists, 0 , lists.size () - 1 ); } };
时间复杂度:$O(knlogk)$
空间复杂度:$O(logk)$
分析 先用归并排序合并两个数组,然后返回中间索引的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution {public : vector<int > mergeSort (vector<int >& nums1, vector<int >& nums2) { int m = nums1.size (); int n = nums2.size (); vector<int > res (m + n) ; int i = 0 ; int j = 0 ; int k = 0 ; while (i < m && j < n){ if (nums1[i] <= nums2[j]){ res[k++] = nums1[i++]; } else { res[k++] = nums2[j++]; } } while (i < m){ res[k++] = nums1[i++]; } while (j < n){ res[k++] = nums2[j++]; } return res; } double findMedianSortedArrays (vector<int >& nums1, vector<int >& nums2) { vector<int > nums = mergeSort (nums1, nums2); int len = nums.size (); if (len % 2 ){ return nums[len / 2 ]; } else { return (nums[len / 2 - 1 ] + nums[len / 2 ]) / 2.0 ; } } };
时间复杂度:O(m+n)
空间复杂度:O(m+n)
二分算法 有点迷糊,之后再看看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class Solution {public : double findMedianSortedArrays (vector<int >& nums1, vector<int >& nums2) { int n1 = nums1.size (); int n2 = nums2.size (); if (n1 > n2) { return findMedianSortedArrays (nums2, nums1); } int k = (n1 + n2 + 1 ) / 2 ; int left = 0 ; int right = n1; while (left <= right) { int m1 = (left + right) / 2 ; int m2 = k - m1; if (m1 < n1 && m2 > 0 && nums1[m1] < nums2[m2 - 1 ]) { left = m1 + 1 ; } else if (m1 > 0 && m2 < n2 && nums1[m1 - 1 ] > nums2[m2]) { right = m1 - 1 ; } else { int c1; if (m1 == 0 ) { c1 = nums2[m2 - 1 ]; } else if (m2 == 0 ) { c1 = nums1[m1 - 1 ]; } else { c1 = max (nums1[m1 - 1 ], nums2[m2 - 1 ]); } if ((n1 + n2) % 2 == 1 ) { return c1; } int c2; if (m1 == n1) { c2 = nums2[m2]; } else if (m2 == n2) { c2 = nums1[m1]; } else { c2 = min (nums1[m1], nums2[m2]); } return (c1 + c2) / 2.0 ; } } return 0.0 ; } };
时间复杂度:O(log min(m,n))
空间复杂度:0(1)
纵向遍历 纵向遍历,从第一个字符开始,比较每一个字符串的第一个字符是否相等。若相等,则比较第二个字符,若不相等,则结束循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : string longestCommonPrefix (vector<string>& strs) { string res = "" ; for (int i = 0 ; i < strs[0 ].length (); i++){ char ch = strs[0 ][i]; bool flag = true ; for (int j = 1 ; j < strs.size (); j++){ if ((i < strs[j].length () && strs[j][i] != ch) || i >= strs[j].length ()){ flag = false ; break ; } } if (flag){ res += ch; } else { break ; } } return res; } };
分析 可以把数组最右侧元素作为二叉搜索树的根节点值。然后判断数组的左右两侧是否符合左侧值都小于该节点值,右侧值都大于该节点值。如果不满足,则说明不是某二叉搜索树的后序遍历结果。找到左右分界线位置,然后递归左右数组继续查找。
终止条件为数组 开始位置 > 结束位置,此时该树的子节点数目小于等于 1,直接返回 True 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution {public : bool verifyTreeOrder (vector<int >& postorder) { int n = postorder.size (); if (n <= 2 ){ return true ; } return verify (postorder, 0 , n-1 ); } bool verify (vector<int >& postorder, int left, int right) { if (left >= right){ return true ; } int index = left; while (postorder[index] < postorder[right]){ index++; } int mid = index; while (postorder[index] > postorder[right]){ index++; } return index == right && verify (postorder, left, mid-1 ) && verify (postorder, mid, right-1 ); } };
时间复杂度:$O(n^2)$
空间复杂度:O(n)
辅助单调栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : bool verifyTreeOrder (vector<int >& postorder) { stack<int > stk; int root = INT_MAX; for (int i = postorder.size () - 1 ; i >= 0 ; i--){ if (postorder[i] > root) return false ; while (!stk.empty () && stk.top () > postorder[i]){ root = stk.top (); stk.pop (); } stk.push (postorder[i]); } return true ; } };
总结 分治算法