nSum系列題目總結
1、Two Sum
原題地址:https://leetcode.com/problems/two-sum/description/
題目描述:
Given an array of integers, return indices of the two numbers such that they add up to a specific target.You may assume that each input would have exactly one solution, and you may not use the same
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9, return [0,1].
思路:設定一個雜湊表用來儲存元素num及下標,這樣可以在o(1)的時候複雜度裡面查詢到target-num這個元素是否存在。如果存在是接返回兩個元素的下標即可。
AC程式碼:
vector<int> twoSum(vector<int> &numbers, int target) { //Key is the number and value is its index in the vector. unordered_map<int, int> hash; vector<int> result; for (int i = 0; i < numbers.size(); i++) { int numberToFind = target - numbers[i]; //如果numberToFind在雜湊表中存在,直接返回兩個數的下標 if (hash.find(numberToFind) != hash.end()) { //+1 because indices are NOT zero based result.push_back(hash[numberToFind] + 1); result.push_back(i + 1); return result; } //如果不在雜湊表中,則將當前元素放入雜湊表中 hash[numbers[i]] = i; } return result; }
(2)3sum
原題地址:https://leetcode.com/problems/3sum/description/
題目描述:
Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.Note: The solution set must not contain duplicate triplets.
For example, given array S = [-1, 0, 1, 2, -1, -4],
A solution set is: [ [-1, 0, 1], [-1, -1, 2] ]
思路:引用上一題的思路,在遍歷到元素num的時候,相當於求後兩個元素是否能組成和為-num。但是會出現重複元素的情況,因此需要先排序。
先固定第一個元素first,然後second指向下一個元素,third指向最後一個元素,找出second+third=-first的情況
---------------------------------------------------- ^ ^ ^ | | | | +- second third +-first 如果nums[first] + nums[second] + nums[third] < 0, 這是需要增大sum,所以second++
---------------------------------------------------- ^ ^ ^ | | | | +- second third +-first 如果sum > target,即nums[first] + nums[second] + nums[third] >0,third--。 如果sum = target,直接存起來當前的結果
---------------------------------------------------- ^ ^ ^ | | | | +- second third +-first
迴圈的條件是second<third
---------------------------------------------------- ^ ^ ^ | | | | +- second third +-first
結束條件first=len-3, second = len-2, third = len-1 ---------------------------------------------------- ^ ^ ^ | | `- third | +- second +-first
AC程式碼:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int len = nums.size();
vector<vector<int> > res;
sort(nums.begin(), nums.end());
for(int index = 0;index < len;index++){
int target = -nums[index];
int front = index+1;
int back = len-1;
while(front < back){
int sum = nums[front] + nums[back];
if(sum < target){
front++;
}else if(sum > target){
back--;
}else{
vector<int> triplet(3, 0);
triplet[0] = nums[index];
triplet[1] = nums[front];
triplet[2] = nums[back];
res.push_back(triplet);
while(front < back && nums[front] == triplet[1])
front++;
while(front < back && nums[back] == triplet[2])
back--;
}
}//while
while(index+1 < len && nums[index+1] == nums[index]){
index++;
}
}//for
return res;
}
};
(3)3Sum Closest
原題地址:https://leetcode.com/problems/3sum-closest/description/
題目描述:
Given an array S of n integers, find three integers in S such that the sum is closest to a given number, target. Return the sum of the three integers. You may assume that each input would have exactly one solution.For example, given array S = {-1 2 1 -4}, and target = 1.
The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).
思路:
前面和上一題思路一樣,只是在算的過程中存一下差diff,每算出來一個結果的時候和diff進行比較,如果比diff還小,用當前結果覆蓋之前算出來的結果
AC程式碼:
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int len = nums.size();
if(len<3){
return 0;
}
sort(nums.begin(), nums.end());
int first,second,third;
int diff =INT_MAX; //間隔
int value; //值
for(first=0;first<len-2;++first){
second = first+1;
third = len-1;
while(second < third){
int sum = nums[first]+nums[second]+nums[third];
if(sum == target){
return sum;
}else if(sum < target){
if((target-sum) < diff){
diff = target-sum;
value = sum;
}
second++;
}else{
if((sum-target) < diff){
diff = sum-target;
value = sum;
}
third--;
}
}//while
}//for
return value;
}
};
(4)4Sum
原題地址:https://leetcode.com/problems/4sum/description/
題目描述:
Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target.Note: The solution set must not contain duplicate quadruplets.
For example, given array S = [1, 0, -1, 0, -2, 2], and target = 0.
A solution set is: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]
思路:
先固定前兩個數,後兩個數跟前面題同樣的思路
AC程式碼:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int> > res;
int len = nums.size();
if(len<4){
return res;
}
sort(nums.begin(), nums.end());
for(int i=0;i<len-3;++i){
if(i>0 && nums[i] == nums[i-1]) continue; //去掉重複的數
if((nums[i]+nums[i+1]+nums[i+2]+nums[i+3]) > target) break; //做一些剪枝,最小的四個數相加都比target大時,肯定無解
if((nums[i]+nums[len-1]+nums[len-2]+nums[len-3]) < target) continue; //做一些剪枝,當i固定時,當前最大的相加都比target小i++
for(int j=i+1;j<len-1;++j){
//再固定第二個數
if(j>i+1 && nums[j] == nums[j-1]) continue;
if((nums[i] + nums[j] + nums[j+1] + nums[j+2]) > target) break;
if((nums[i] + nums[j] + nums[len-1] + nums[len-2]) < target) continue;
int left = j+1;
int right = len-1;
while(left < right){
int sum = nums[i]+nums[j]+nums[left]+nums[right];
if(sum == target){
res.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
do{left++;}while(left<right && nums[left] == nums[left-1]);
do{right--;}while(left<right && nums[right] == nums[right+1]);
}else if(sum < target){
left++;
}else{
right--;
}
}
}
}//for
return res;
}
};
(5)今年騰訊的一道筆試題:拼湊硬幣
題目描述:小Q十分富有,擁有非常多的硬幣,小Q擁有的硬幣是有規律的,對於所有的非負整數K,小Q恰好各有兩個面值為2^k的硬幣,所有小Q擁有的硬幣就是1,1,2,2,4,4,8,8.....小Q有一天去商店購買東西需要支付n元錢,小Q想知道有多少種方案從他擁有的硬幣中選取一些拼湊起來恰好是n元(如果兩種方案某個面值的硬幣選取的個數不一樣就考慮為不一樣的方案)
輸入:
輸入包括一個整數n(1<=n<=10^18),表示小Q需要支付多少錢,注意n的範圍
輸出:
輸出一個整數,表示小Q可以拼湊出n元錢的方案數
樣例輸入:6
樣例輸出:3 (例如4+2, 4+1+1, 2+2+1+1)
常規方法:使用動態規劃
使用陣列dp[n, i]表示使用1,1,2,2,4,4, ..., 2^i, 2^i可以組合成n的方案數
dp[0, i] = 1,當n=0時,即所有面值的硬幣都不選,所以只有一種方案
dp[1, i] = 1, 當n=1時,只能選取一個一元的硬幣,所以只有一種方案
res[2, 0]=1,即只選取兩個一元硬幣,所以只有一種方案
res[n, 0] = 0,當n>=3時,無法只使用1組成大於2的數,所以方案數為0
res[n, i] = sum(num[n-2^i*m, i-1]), n, i取其他,0<=m<=2
程式碼:
#include <iostream>
#include<cmath>
using namespace std;
int main()
{
int n;
while(cin>>n){
//一些特殊情況處理
if(n == 0 || n == 1){
return 1;
}
int m = log(n)/log(2) + 1;
//dp[n,m]
int i,j;
int** dp = new int*[n+1];
for(i=0;i<=n;++i){
dp[i] = new int[m];
}
//初始化二維陣列
for(i=0;i<m;++i){
dp[0][i] = 1;
dp[1][i] = 1;
}
dp[1][0] = 1;
dp[2][0] = 1;
for(i=1;i<=n;++i){
for(j=1;j<m;++j){
int sum = 0;
for(int m=0;m<3;++m){
int rest = (int)(i-pow(2, j)*m);
if(rest >= 0){
sum+=dp[rest][j-1];
}
}
dp[i][j] = sum;
}
}
cout<<dp[n][m-1]<<endl;
}
return 0;
}
一個很秒的思路:
將硬幣分為兩份:1,2,4,8,16,... 和1,2,4,8,16,...
組成兩個數值為a, b的兩個數,他們的和是a+b=n;
a在第一份中只有可能有一種組合方式(採用二進位制的思想,任何一個數都可以由若干個2^i的數相加的和組成),而b = n-a也只會有一種組合形式。
將a和b使用二進位制表示,舉個例子n=11,有a=101, b=110這種組合,即a = 1+0+4=5,b=0+2+4=6。但是注意到會有重複的情況,比如111+100和101+110本質上是同一種組合方法,所以需要將二個數做二進位制異或然後去重。
程式碼:
#include <iostream>
#include<set>
using namespace std;
int main()
{
int n;
while(cin>>n){
set<int> countset;
for(int i=1;i<=n/2;i++){
int result =i^(n-i);
countset.insert(result);
}
cout<<countset.size()<<endl;
}
return 0;
}