▶ 问题:给定一个数组 nums 及一个目标值 target,求数组中是否存在 n 项的和恰好等于目标值
▶ 第 1题,n = 2,要求返回解
● 代码,160 ms,穷举法,时间复杂度 O(n2),空间复杂度 O(1)
1 class Solution 2 { 3 public: 4 vector<int> twoSum(vector<int> nums, int target) 5 { 6 int i, j; 7 for (i = 0; i < nums.size()-1; i++) 8 { 9 for (j = i + 1; j < nums.size(); j++) 10 { 11 if (nums[i] + nums[j] == target) 12 return vector<int> {i, j}; 13 } 14 } 15 return vector<int>{}; 16 } 17 };
● 代码,9 ms,排序后夹逼,需要构造一个结构来保存下标,连着下标一起排序。时间复杂度 O(n log n),空间复杂度 O(1)
1 class Solution 2 { 3 public: 4 struct Node 5 { 6 int val; 7 int index; 8 Node(int pval, int pindex) : val(pval), index(pindex) {} 9 bool operator < (const Node& b) const {return val < b.val;} 10 }; 11 12 vector<int> twoSum(vector<int>& nums, int target) 13 { 14 int i, j, temp; 15 vector<Node> nums2; 16 for (i = 0; i < nums.size(); i++) 17 nums2.push_back(Node(nums[i], i)); 18 sort(nums2.begin(), nums2.end()); 19 for (i = 0, j = nums2.size() - 1; i < j; (temp > target) ? j-- : i++ ) 20 { 21 if ((temp = nums2[i].val + nums2[j].val) == target) 22 return vector<int>{nums2[i].index, nums2[j].index}; 23 } 24 return vector<int>{}; 25 } 26 };
● 代码,10 ms,使用散列表,遍历数组时将项放入散列表,同时检查当前项和散列表中已经有的项是否构成问题的解,利用散列表查找时间为常数的特点加快检查速度,最快的解法算法与之相同。时间复杂度 O(n),空间复杂度 O(n)
1 class Solution 2 { 3 public: 4 vector<int> twoSum(vector<int> nums, int target) 5 { 6 int i; 7 unordered_map<int, int> map; 8 for (i = 0; i < nums.size(); i++) 9 { 10 if (map.find(target - nums[i])!=map.end()) 11 return vector<int>{map[target - nums[i]], i }; 12 map[nums[i]] = i; 13 } 14 return vector<int>{}; 15 } 16 };
▶ 第 15 题,n = 3,target = 0
● 大佬的代码,110 ms,排序后夹逼,先指定第一个(最小的)元素,剩下的两个元素使用前面二元和的方法进行夹逼,并跳掉一些多余分支,最快的解法算法与之相同,时间复杂度 O(n2),空间复杂度 O(1)
1 class Solution 2 { 3 public: 4 vector<vector<int> > threeSum(vector<int> &num) 5 { 6 int i, left, right; 7 vector<vector<int>> output; 8 9 sort(num.begin(), num.end()); 10 for (i = 0; i < num.size();) 11 { 12 if (num[i] > 0) // 第一个元素已经大于零,则三个元素的和不可能等于零,剩下的界都不用检查了 13 break; 14 for (left = i + 1, right = num.size() - 1; left < right;) // 搜索二元和,可能有多解,必须全部搜索完 15 { 16 if (num[left] + num[right] + num[i] < 0) 17 left++; 18 else if (num[left] + num[right] + num[i] > 0) 19 right--; 20 else 21 { 22 output.push_back(vector<int>{num[i], num[left], num[right]}); 23 for (left++; left < right && num[left] == num[left - 1]; left++); // 跳过第二个数重复的情况 24 for (right--; left < right && num[right] == num[right + 1]; right--); // 跳过第三个数重复分情况 25 } 26 } 27 for (i++; i < num.size() && num[i] == num[i - 1]; i++); // 跳过第一个数重复分情况,即调整下一次检查的起点 28 } 29 return output; 30 } 31 };
▶ 第 16 题,n = 3,求最近似解,第 259 题要收费 Orz
● 代码,6 ms,排序后夹逼,使用 output 来记录当前最优解的信息。最快的解法算法与之相同,时间复杂度 O(n2),空间复杂度 O(1)
1 class Solution 2 { 3 public: 4 inline int diff(int a, int b) { return (a > b) ? (a - b) : (b - a); } 5 6 int threeSumClosest(vector<int> &num, int target) 7 { 8 int i, target2, left, right, sum2, output; 9 10 std::sort(num.begin(), num.end()); 11 for (i = 0, output = INT_MAX>>1; i < num.size();) 12 { 13 for (target2 = target - num[i], left = i + 1, right = num.size() - 1; left < right;) 14 { 15 sum2 = num[left] + num[right]; 16 if (sum2 < target2) 17 left++; 18 else if (sum2 > target2) 19 right--; 20 else 21 return target; 22 if (diff(sum2, target2) < diff(target,output)) 23 output = sum2 + num[i]; 24 } 25 for (i++; i < num.size() && num[i] == num[i - 1]; i++); 26 } 27 return output; 28 } 29 };
▶ 第18 题,n = 4
● 代码,12 ms,排序后夹逼,先指定两个较小的元素,剩下的两个元素使用前面二元和的方法进行夹逼,并跳掉一些多余分支,最快的解法算法与之相同,时间复杂度 O(n3),空间复杂度 O(1)
1 class Solution4 2 { 3 public: 4 vector<vector<int>> fourSum(vector<int>& nums, int target) 5 { 6 int n, i, j, left, right, sum; 7 vector<vector<int>> output; 8 n = nums.size(); 9 sort(nums.begin(), nums.end()); 10 for (i = 0; i < n - 3; i++) // 指定第一个元素 11 { 12 if (i > 0 && nums[i] == nums[i - 1] || nums[i] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) 13 continue; // 跳过第一个元素重复或过小的情况 14 if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) 15 break; // 第一个元素过大,结束搜索 16 for (j = i + 1; j < n - 2; j++) // 指定第二个元素 17 { 18 if (j > i + 1 && nums[j] == nums[j - 1] || nums[i] + nums[j] + nums[n - 2] + nums[n - 1] < target) 19 continue; // 跳过第二个元素重复过小的情况 20 if (nums[i] + nums[j] + nums[j + 1] + nums[j + 2]>target) 21 break; // 第二个元素过大,结束搜索 22 for(left = j + 1, right = n - 1; left < right;) // 搜索第三和第四个元素 23 { 24 sum = nums[left] + nums[right] + nums[i] + nums[j]; 25 if (sum < target) 26 left++; 27 else if (sum > target) 28 right--; 29 else 30 { 31 output.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]}); 32 for (left++; nums[left] == nums[left - 1] && left < right; left++); 33 for (right--; nums[right] == nums[right + 1] && left < right; right--); 34 } 35 } 36 } 37 } 38 return output; 39 } 40 };
▶ 第 167 题,n = 2,默认已经排好了序,可以使用第 1 题结果,第170 题要付费 Orz
● 夹逼,7 ms,时间复杂度 O(n)
1 class Solution 2 { 3 public: 4 vector<int> twoSum(vector<int>& numbers, int target) 5 { 6 int left, right; 7 vector<int>output; 8 for (left = 0, right = numbers.size() - 1; left < right;) 9 { 10 if (numbers[left] + numbers[right] == target) 11 return vector<int>{left + 1, right + 1}; 12 else if (numbers[left] + numbers[right] > target) 13 right--; 14 else 15 left++; 16 } 17 return vector<int>(); 18 } 19 };
● 大佬的代码,7 ms,使用二分法加速,时间复杂度 O(log n)
1 class Solution 2 { 3 public: 4 int largestSmallerOrLastEqual(vector<int>& numbers, int start, int end, int target) 5 { 6 int lp, rp, mp; 7 for (lp = start, rp = end, mp = lp + (rp - lp) / 2; lp <= rp; mp = lp + (rp - lp) / 2) 8 { 9 if (numbers[mp] > target) 10 rp = mp - 1; 11 else 12 lp = mp + 1; 13 } 14 return rp; 15 } 16 int smallestLargerOrFirstEqual(vector<int>& numbers, int start, int end, int target) 17 { 18 int lp, rp, mp; 19 for (lp = start, rp = end, mp = lp + (rp - lp) / 2; lp <= rp; mp = lp + (rp - lp) / 2) 20 { 21 if (numbers[mp] < target) 22 lp = mp + 1; 23 else 24 rp = mp - 1; 25 } 26 return lp; 27 } 28 vector<int> twoSum(vector<int>& numbers, int target) 29 { 30 const int n = numbers.size(); 31 if (numbers.size() == 0) 32 return vector<int>{}; 33 int start, end; 34 for (start = 0, end = n - 1; start < end;) 35 { 36 if (numbers[start] + numbers[end] == target) 37 return vector<int> {start + 1, end + 1}; 38 if (numbers[start] + numbers[end] > target) 39 end = largestSmallerOrLastEqual(numbers, start, end, target - numbers[start]);// end 向前移动到满足 numbers[end] <= target - numbers[start] 的最后一个整数 40 else 41 start = smallestLargerOrFirstEqual(numbers, start, end, target - numbers[end]);// start 向后移动到满足 numbers[start] >= target - numbers[end] 的第一个整数 42 } 43 return vector<int>{}; 44 } 45 };
▶ 第 454 题,n = 4,每个加数分别从指定的集合中选出
● 自己的方法,1550 ms,这是将第 18 题的算法移植过来得到的,在使用计数器 counta,countb,countc,countd 计算重复解以前结果为超时,说明这种逐步搜索的方法对于独立的集合来说效率不足
1 class Solution 2 { 3 public: 4 int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) 5 { 6 const int n = A.size(); 7 int a, b, c, d, count, counta, countb, countc, countd; 8 sort(A.begin(), A.end()), sort(B.begin(), B.end()), sort(C.begin(), C.end()), sort(D.begin(), D.end()); 9 10 for (a = count = 0; a < n; a++) 11 { 12 if (A[a] + B[n - 1] + C[n - 1] + D[n - 1] < 0) // A[a] 过小 13 continue; 14 if (A[a] + B[0] + C[0] + D[0] > 0) // A[a] 过大 15 break; 16 for (counta = 1; a < n - 1 && A[a + 1] == A[a]; a++, counta++);// A[a] 重复计数 17 for (b = 0; b < n; b++) 18 { 19 if (A[a] + B[b] + C[n - 1] + D[n - 1] < 0) // B[b] 过小 20 continue; 21 if (A[a] + B[b] + C[0] + D[0] > 0) // B[b] 过大 22 break; 23 for (countb = 1; b < n - 1 && B[b + 1] == B[b]; b++, countb++);// B[b] 重复计数 24 for (c = 0, d = n - 1; c < n && d >= 0;) 25 { 26 if (A[a] + B[b] + C[c] + D[d] < 0) 27 c++; 28 else if (A[a] + B[b] + C[c] + D[d] > 0) 29 d--; 30 else 31 { 32 for (countc = 1; c < n - 1 && C[c + 1] == C[c]; c++, countc++); 33 for (countd = 1; d > 0 && D[d - 1] == D[d]; d--, countd++); 34 count += counta * countb * countc * countd; 35 c++, d--; 36 } 37 } 38 } 39 } 40 return count; 41 } 42 };
● 大佬的解法,197 ms,unordered_map 查找
1 class Solution 2 { 3 public: 4 int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) 5 { 6 unordered_map<int, int> abSum; 7 int count = 0; 8 auto it = abSum.begin(); 9 for (auto a : A) 10 { 11 for (auto b : B) 12 abSum[a + b]++; 13 } 14 for (auto c : C) 15 { 16 for (auto d : D) 17 { 18 if ((it = abSum.find(0 - c - d))!= abSum.end()) 19 count += it->second; 20 } 21 } 22 return count; 23 } 24 };
● 大佬的解法,203 ms,二分查找
1 class Solution 2 { 3 public: 4 int biSearch(vector<int> & nums, int x, bool LEFT)// 在 nums 中搜索值为 x 的元素,由于存在重复元素,使用了两种二分搜索,用 LEFT 区分 5 { 6 int lp, rp, mp; 7 for (lp = 0, rp = nums.size() - 1, mp = (lp + rp) / 2; lp <= rp; mp = (lp + rp) / 2) 8 { 9 if (LEFT)// 搜索最靠左的 x 10 { 11 if (nums[mp] >= x) 12 rp = mp - 1; 13 else 14 lp = mp + 1; 15 } 16 else // 搜索最靠右的 x 17 { 18 if (nums[mp] <= x) 19 lp = mp + 1; 20 else 21 rp = mp - 1; 22 } 23 } 24 return LEFT ? lp : rp; 25 } 26 int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) 27 { 28 const int n = A.size(); 29 vector<int> s, m; 30 int i, j, sum, ans; 31 for (i = 0; i < n; i++) 32 { 33 for (j = 0; j < n; j++) 34 s.push_back(A[i] + B[j]), m.push_back(C[i] + D[j]); 35 } 36 sort(s.begin(), s.end()), sort(m.begin(), m.end()); 37 for (i = sum = ans = 0; i < s.size(); ++i) 38 { 39 if (i != 0 && s[i] == s[i - 1])// 重复计数 40 ans += sum; 41 else 42 { 43 sum = biSearch(m, -s[i], false) - biSearch(m, -s[i], true) + 1;// 第二个重复计数 44 ans += sum; 45 } 46 } 47 return ans; 48 } 49 };
▶ 第 657 题,n = 2,数据结构是中根二叉树
● 代码,38 ms,中根序遍历这棵树,把数字都取出来(恰好为升序数列),再用前面的方法计算
1 class Solution 2 { 3 public: 4 bool findTarget(TreeNode* root, int k) 5 { 6 vector<int> nums; 7 inorder(root, nums); 8 for (int i = 0, j = nums.size() - 1; i < j;) 9 { 10 if (nums[i] + nums[j] == k) 11 return true; 12 (nums[i] + nums[j] < k) ? i++ : j--; 13 } 14 return false; 15 } 16 void inorder(TreeNode* root, vector<int>& nums)// 中根序遍历树 17 { 18 if (root == NULL) 19 return; 20 inorder(root->left, nums); 21 nums.push_back(root->val); 22 inorder(root->right, nums); 23 } 24 };
● 代码,38 ms,从树的中根序两端向中间遍历
1 class BSTIterator 2 { 3 private: 4 stack<TreeNode*> s; 5 bool forward; 6 public: 7 BSTIterator(TreeNode* root, bool forward) 8 { 9 for (this->forward = forward; root != nullptr; root = forward ? root->left : root->right) 10 s.push(root); 11 } 12 TreeNode* next() 13 { 14 if (s.empty()) 15 return NULL; 16 TreeNode *top, *tmp; 17 for (top = s.top(), s.pop(), tmp = (forward ? top->right : top->left); tmp != nullptr; s.push(tmp), tmp = (forward ? tmp->left : tmp->right)); 18 return top; 19 } 20 }; 21 class Solution 22 { 23 public: 24 bool findTarget(TreeNode* root, int k) 25 { 26 int sum; 27 BSTIterator forward(root, true), backward(root, false);// 获取最前结点和最后结点(最小和最大元素) 28 TreeNode *fn, *bn; 29 for (fn = forward.next(), bn = backward.next(); fn != bn;) 30 { 31 sum = fn->val + bn->val; 32 if (sum == k) 33 return true; 34 (sum < k) ? (fn = forward.next()) : (bn = backward.next()); 35 } 36 return false; 37 } 38 };