1. Two Sum + 15. 3 Sum + 16. 3 Sum Closest + 18. 4Sum + 167. Two Sum II

▶ 问题:给定一个数组 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 };
原文地址:https://www.cnblogs.com/cuancuancuanhao/p/8186301.html