450. K组翻转链表
把问题分步处理:1.考虑一个函数实现反转k个数,只要外面加个循环就能解决问题 2.反转k个数分三步A.记录首尾 B.翻转 C.首尾连接
class Solution {
public:
ListNode * reverseKGroup(ListNode * head, int k) {
if (k==0 || head==NULL) return head;
ListNode* DummyNode=new ListNode(0);
DummyNode->next=head;
head=DummyNode;
while (head->next!=NULL) {//这里的head是下一组待反转节点的前一个节点
//需要明白反转第n,n+1,..n+k个节点,动的节点还包括n-1,因为n-1要链接n+k
//构建一个helper函数,希望其能反转head->next接下来的k个节点,并返回下一组节点的头指针。
head=helper(head,k);
if (head==NULL) break;//处理不够K个数返回NULL的情况
}
return DummyNode->next;
}
ListNode* helper(ListNode * head, int k) {
//定点,确认这一组k个数的头、尾方便连接
ListNode* n1=head->next;
ListNode* nk=head;
for (int i=0;i<k;i++){
nk=nk->next;
if (nk==NULL) return NULL;
}
ListNode* nkp=nk->next;
ListNode* prev=NULL;
ListNode* cur=n1;
while(cur!=nkp) {
ListNode* tmp=cur->next;
cur->next=prev;
prev=cur;
cur=tmp;
}
//连接
n1->next=nkp;
head->next=nk;
return n1;
}
};
96. 链表划分
使用两个链表头,将一个链表拆成两个。
class Solution {
public:
ListNode * partition(ListNode * head, int x) {
if (head==NULL) return head;
ListNode* low=new ListNode(0);
ListNode* high=new ListNode(0);
ListNode* dummyl=low;
ListNode* dummyh=high;
ListNode* cur=head;
while (cur!=NULL) {
if (cur->val<x) {
low->next=cur;
low=low->next;
}
else {
high->next=cur;
high=high->next;
}
cur=cur->next;
}
high->next=NULL;//记得加这一句!!high后面要有结尾才不会报错!
//处理low里面啥也没有的情况
if (dummyl->next==NULL) return dummyh->next;
else {
low->next=dummyh->next;
return dummyl->next;
}
}
};
class Solution {
public:
ListNode * mergeTwoLists(ListNode * l1, ListNode * l2) {
// write your code here
if (l1==NULL) return l2;
if (l2==NULL) return l1;
ListNode* dummy=new ListNode(0);
ListNode* cur=dummy;
while (l1!=NULL &&l2!=NULL) {
if (l1->val<=l2->val) {
cur->next=l1;
l1=l1->next;
}
else {
cur->next=l2;
l2=l2->next;
}
cur=cur->next;
}
if (l1==NULL && l2!=NULL) cur->next=l2;
if (l2==NULL && l1!=NULL) cur->next=l1;
return dummy->next;
}
};
36. 翻转链表 II
技巧:从dummy开始走几步,就到第几个节点。
class Solution {
public:
ListNode * reverseBetween(ListNode * head, int m, int n) {
if (head==NULL) return NULL;
ListNode* dummy=new ListNode(0);
dummy->next=head;
ListNode* cur=dummy;
for (int i=0;i<m-1;i++)
cur=cur->next;
ListNode* nodemp=cur;
ListNode* nodem=cur->next;
cur=cur->next;
ListNode * prev=NULL;
int k=m;
while (k<=n) {
ListNode* tmp=cur->next;
cur->next=prev;
prev=cur;
cur=tmp;
k++;
}
nodemp->next=prev;
nodem->next=cur;
return dummy->next;
}
};
511. 交换链表当中两个节点
注意两个点挨着的情况要特殊处理
class Solution {
public:
ListNode * swapNodes(ListNode * head, int v1, int v2) {
if (head==NULL) return NULL;
ListNode* dummy=new ListNode(0);
dummy->next=head;
ListNode* cur=head;
ListNode* prev=dummy;
ListNode *prev1=NULL,*n1=NULL,*aft1=NULL;
ListNode *prev2=NULL,*n2=NULL,*aft2=NULL;
int k=0;
while (cur!=NULL) {
if (cur->val==v1)
{
prev1=prev;
n1=cur;
aft1=n1->next;
k++;
}
if (cur->val==v2)
{
prev2=prev;
n2=cur;
aft2=n2->next;
k++;
}
prev=cur;
cur=cur->next;
}
if (k==2) {
if (n1->next==n2) {
prev1->next=n2;
n2->next=n1;
n1->next=aft2;
}
else if (n2->next==n1) {
prev2->next=n1;
n1->next=n2;
n2->next=aft1;
}
else {
prev2->next=n1;
n1->next=aft2;
prev1->next=n2;
n2->next=aft1;
}
}
return dummy->next;
}
};
99. 重排链表
注意掌握链表找中点的方法。
class Solution {
public:
void reorderList(ListNode * head) {
if (head==NULL || head->next==NULL) return;
ListNode* mid=findMid(head);//找到中间节点;
ListNode* head2=reverse(mid->next);//把后面的一半翻转;
mid->next=NULL;//彻底分成两个链表
head=merge(head,head2);//前一半和后一半融合;
}
ListNode* findMid(ListNode* head) {
ListNode* slow=head;
ListNode* fast=head->next;
//慢的一次走一步,快的一次走两步,快的走到头,慢的正好到中间
//注意条件,考虑到奇偶,使得中间节点是靠前的那个
while (fast!=NULL && fast->next!=NULL) {
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
ListNode* reverse(ListNode* head) {
ListNode* prev=NULL;
ListNode* cur=head;
while (cur!=NULL) {
ListNode* tmp=cur->next;
cur->next=prev;
prev=cur;
cur=tmp;
}
return prev;
}
ListNode* merge(ListNode *n1,ListNode*n2) {
ListNode* dummy=new ListNode(0);
ListNode* cur=dummy;
int flag=0;
while (n1!=NULL && n2!=NULL){
if (flag%2==0) {
cur->next=n1;
n1=n1->next;
}
else {
cur->next=n2;
n2=n2->next;
}
cur=cur->next;
flag++;
}
if (n1!=NULL) cur->next=n1;
if (n2!=NULL) cur->next=n2;
return dummy->next;
}
};
170. 旋转链表
考虑使三步旋转法做这道题,只要写一个旋转接口调用三次就可以。注意k有可能大于n。
class Solution {
public:
ListNode * rotateRight(ListNode * head, int k) {
int n=getLength(head);
if (n==0) return NULL;
if (k>=n) k=k%n;
if (k==0||n==1) return head;
head=reverse(1,n-k,head);
head=reverse(n-k+1,n,head);
head=reverse(1,n,head);
return head;
}
int getLength(ListNode* head) {
int i=0;
while (head!=NULL) {
head=head->next;
i++;
}
return i;
}
ListNode* reverse(int start, int end, ListNode* head) {
if (start==end) return head;
ListNode* dummy=new ListNode(0);
dummy->next=head;
ListNode* cur=dummy;
for (int i=0;i<start-1;i++) cur=cur->next;//找到起始位置的前一个位置
ListNode* start_prev=cur;
cur=cur->next;
ListNode* startn=cur;
ListNode* prev=NULL;
int k=start;
while (k<=end) {
ListNode* tmp=cur->next;
cur->next=prev;
prev=cur;
cur=tmp;
k++;
}
startn->next=cur;
start_prev->next=prev;
return dummy->next;
}
};
另一个方法:虽然链表移动了,但是本质上方向并没有改变,若看作一个环链,则只是改变了首尾的位置。可以利用这个特点找到新的头尾,重新连接即可。
class Solution {
public:
ListNode * rotateRight(ListNode * head, int k) {
int n=getLength(head);
if (n==0) return NULL;
if (k>=n) k=k%n;
if (k==0||n==1) return head;
ListNode* dummy=new ListNode(0);
dummy->next=head;
ListNode* tail=dummy;
ListNode* old_tail=dummy;
for (int i=0;i<k;i++)
old_tail=old_tail->next;
while (old_tail->next!=NULL) {
tail=tail->next;
old_tail=old_tail->next;
}
old_tail->next=dummy->next;
dummy->next=tail->next;
tail->next=NULL;
return dummy->next;
}
int getLength(ListNode* head) {
int i=0;
while (head!=NULL) {
head=head->next;
i++;
}
return i;
}
};
105. 复制带随机指针的链表
最常规的思路和拷贝图一样,第一步找到所有的node,第二步建立映射,第三步复制关系。
class Solution {
public:
RandomListNode *copyRandomList(RandomListNode *head) {
if (head==NULL) return NULL;
map<RandomListNode*,RandomListNode*> f;
set<RandomListNode*> s;
RandomListNode *dummy=new RandomListNode(0);
dummy->next=head;
while (head!=NULL)
{
if (f.count(head)==0) {
RandomListNode *tmp=new RandomListNode(head->label);
s.insert(head);
f[head]=tmp;
}
head=head->next;
}
for (auto node:s) {
f[node]->next=f[node->next];
f[node]->random=f[node->random];
}
return f[dummy->next];
}
};
如果不使用额外空间,则考虑利用next指针,形成一个1->1'->2->2'.....的链表,再将其拆分。
class Solution {
public:
RandomListNode *copyRandomList(RandomListNode *head) {
if (head==NULL) return NULL;
RandomListNode *dummy=new RandomListNode(0);
RandomListNode* cur=head;
//复制节点
while (cur!=NULL){
RandomListNode* copy_cur=new RandomListNode(cur->label);
copy_cur->next=cur->next;
cur->next=copy_cur;
cur=cur->next->next;
}
//复制random关系
cur=head;
while (cur!=NULL) {
if (cur->random==NULL) cur->next->random=NULL;
else cur->next->random=cur->random->next;
cur=cur->next->next;
}
//分开
cur=head->next;
dummy->next=cur;
while(cur->next!=NULL) {
cur->next=cur->next->next;
cur=cur->next;
}
return dummy->next;
}
};
102. 带环链表
牛逼算法,一个快结点,一个慢结点,要是快结点再次碰到慢接点而不是走出链表则说明有环。
class Solution {
public:
bool hasCycle(ListNode * head) {
if (head==NULL || head->next==NULL) return false;
ListNode* slow=head;
ListNode* fast=head;
while (fast!=NULL&&fast->next!=NULL) {
slow=slow->next;
fast=fast->next->next;
if (slow==fast) return true;
}
return false;
}
};
380. 两个链表的交叉
接上个算法,将A的末尾和B的头连起来,如果有环则说明有交叉点。
若需要求交叉点在那,则需要证明:假如单链的长度是l1,相遇是慢点在环上走了x,环的长度是l2,则2(l1+x)=l1+l2+x,即l2-x=l1,也就是说一个点从当前位置开始,一个点从A头上开始走,相遇的点就是交叉点。
ListNode * getIntersectionNode(ListNode * headA, ListNode * headB) {
if (headA==NULL || headB==NULL) return NULL;
ListNode* cur=headA;
while (cur->next!=NULL)
cur=cur->next;
cur->next=headB;
ListNode* slow=headA;
ListNode* fast=headA;
while (fast!=NULL || fast->next!=NULL) {
slow=slow->next;
fast=fast->next->next;
if (slow==fast)
{
ListNode* count=headA;
while (count!=fast)
{
fast=fast->next;
count=count->next;
}
return fast;
}
}
cur->next=NULL;
return NULL;
}
};
98. 链表排序
数组排序中符合时间复杂度O(NlogN),原地排序的只有快速排序,但是由于链表的特殊性,归并排序也可以原地排序。
class Solution {
public:
ListNode * sortList(ListNode * head) {
//注意出口条件,只剩一个数了就返回
if (head==NULL || head->next==NULL) return head;
ListNode* mid=findMid(head);
ListNode* right=sortList(mid->next);
mid->next=NULL;
ListNode* left=sortList(head);
return merge(left,right);
}
ListNode* findMid(ListNode* head) {
ListNode *slow=head;
ListNode *fast=head->next;
while (fast!=NULL && fast->next!=NULL) {
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
ListNode * merge(ListNode *n1,ListNode *n2) {
ListNode* dummy=new ListNode(0);
ListNode* cur=dummy;
while (n1!=NULL && n2!=NULL) {
if (n1->val<=n2->val) {
cur->next=n1;
n1=n1->next;
}else {
cur->next=n2;
n2=n2->next;
}
cur=cur->next;
}
if (n1!=NULL) cur->next=n1;
if (n2!=NULL) cur->next=n2;
return dummy->next;
}
};
快排利用链表的性质,拉了三个链表,分别是小于、等于、大于标杆值的链表,然后拼在一起即可。并不是利用交换把小的往前放大的往后放。牛逼!注意注释中的小细节。
class Solution {
public:
ListNode * sortList(ListNode * head) {
//注意出口条件,只剩一个数了就返回
if (head==NULL || head->next==NULL) return head;
ListNode* mid=findMid(head);
int pivot=mid->val;
ListNode* leftDummy=new ListNode(0);
ListNode* rightDummy=new ListNode(0);
ListNode* midDummy=new ListNode(0);
ListNode* leftcur=leftDummy;
ListNode* rightcur=rightDummy;
ListNode* midcur=midDummy;
ListNode* cur=head;
while (cur!=NULL) {
if (cur->val<pivot) {
leftcur->next=cur;
leftcur=leftcur->next;
}else if (cur->val>pivot) {
rightcur->next=cur;
rightcur=rightcur->next;
}else {
midcur->next=cur;
midcur=midcur->next;
}
cur=cur->next;
}
//加尾巴!!!重点!!!
leftcur->next=NULL;
rightcur->next=NULL;
midcur->next=NULL;
ListNode* left=sortList(leftDummy->next);
ListNode* right=sortList(rightDummy->next);
return concat(left,midDummy->next,right);
}
ListNode* findMid(ListNode* head) {
ListNode *slow=head;
ListNode *fast=head->next;
while (fast!=NULL && fast->next!=NULL) {
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
ListNode* concat(ListNode* left, ListNode* mid,ListNode* right) {
ListNode* dummy=new ListNode(0);
//新建一个tail不停的找结尾并连接,不可以用left直接找,值会改变!!
ListNode* tail=dummy;
tail->next=left;
tail=getTail(tail);
tail->next=mid;
tail=getTail(tail);
tail->next=right;
return dummy->next;
}
ListNode* getTail(ListNode* head) {
if (head==NULL) return NULL;
while (head->next!=NULL) head=head->next;
return head;
}
};
106. 有序链表转换为二分查找树
本质还是分治算法,但是注意mid的左子树链末尾要手动添加NULL,而且要判断左子树链表是不是空。
class Solution {
public:
TreeNode * sortedListToBST(ListNode * head) {
if (head==NULL) return NULL;
if (head->next==NULL) {
TreeNode* root=new TreeNode(head->val);
return root;
}
ListNode* mid=findMid(head);
TreeNode* root=new TreeNode(mid->val);
root->right=sortedListToBST(mid->next);
if (mid==head) root->left=NULL;
else {
ListNode* cur=new ListNode(0);
cur->next=head;
while (cur->next!=mid) cur=cur->next;
cur->next=NULL;
root->left=sortedListToBST(head);
}
return root;
}
ListNode* findMid (ListNode* head) {
ListNode* slow=head;
ListNode* fast=head->next;
while (fast!=NULL && fast->next!=NULL) {
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
};
378. 将二叉查找树转换成双链表
注意处理左右子树为空的情况,尤其是左子树为空,应该返回mid指针。
class Solution {
public:
DoublyListNode * bstToDoublyList(TreeNode * root) {
if (root==NULL) return NULL;
DoublyListNode* mid=new DoublyListNode(root->val);
if (root->left==NULL && root->right==NULL) {
return mid;
}
DoublyListNode* leftlist=bstToDoublyList(root->left);
if (leftlist!=NULL) {
DoublyListNode* cur=leftlist;
while (cur->next !=NULL) cur=cur->next;
mid->prev=cur;
cur->next=mid;
}
DoublyListNode* rightlist=bstToDoublyList(root->right);
if (rightlist!=NULL){
mid->next=rightlist;
rightlist->prev=mid;
}
return leftlist==NULL?mid:leftlist;
}
};
41. 最大子数组
使用前缀和的思想,区间和sum(a,b)是两个前缀和subsum[b]和subsum[a]的差,要想得到最大区间和,要求subsum[a]最小,subsum[b]最大。用三个变量分别记录当前前缀和、最小前缀和以及最大结果即可。
class Solution {
public:
int maxSubArray(vector<int> &nums) {
if (nums.size()==1) return nums[0];
int subsum=0;
int minsubsum=0;
int res=INT_MIN;
for (int i=0;i<nums.size();i++)
{
subsum=subsum+nums[i];
res=max(res,subsum-minsubsum);
minsubsum=min(minsubsum,subsum);
}
return res;
}
};
138. 子数组之和
还是利用子数组,如果两个前缀和子数组相等,说明这两个之间的数加起来是0。用map判断是不是有重复的前缀和了,不需要全部记录下来再用两重for循环。注意处理第1个数的前缀数组(0)。
class Solution {
public:
vector<int> subarraySum(vector<int> &nums) {
if (nums.size()==0) return vector<int>();
vector<int> res;
unordered_map<int,int> subSum;
int sum=0;
subSum[0]=-1;
for (int i=0;i<nums.size();i++) {
sum+=nums[i];
if (subSum.find(sum)==subSum.end()) {
subSum[sum]=i;
}else{
res.push_back(subSum[sum]+1);
res.push_back(i);
return res;
}
}
}
};
139. 最接近零的子数组和
思路和上面的一样,不同的是要记录每个前缀子数组的和、位置,然后排序,判断相邻和的差哪个最小。
class Solution {
public:
vector<int> subarraySumClosest(vector<int> &nums) {
if (nums.size()==0) return vector<int>();
vector<int> res(2,0);
vector<pair<int,int>> subSum;
subSum.push_back(make_pair(0,-1));
int sum=0;
//计算前缀子数组和
for (int i=0;i<nums.size();i++) {
sum+=nums[i];
subSum.push_back(make_pair(sum,i));
}
sort(subSum.begin(),subSum.end());//重新排序
//找最小差值
int minSum=INT_MAX;
int begin=0;
int end=0;
for (int i=0;i<subSum.size()-1;i++) {
if (subSum[i+1].first-subSum[i].first<minSum) {
minSum=subSum[i+1].first-subSum[i].first;
begin=min(subSum[i+1].second,subSum[i].second)+1;
end=max(subSum[i+1].second,subSum[i].second);
}
if (minSum==0) break;
}
res[0]=begin;
res[1]=end;
return res;
}
};
64. 合并排序数组
这个题的特别之处在于A的大小为m+n,要把B合并进A。由于A的后半部分是空的,所以采用从大到小、从前往后的排法。
class Solution {
public:
void mergeSortedArray(int A[], int m, int B[], int n) {
int index=m+n-1;
int ia=m-1;
int ib=n-1;
while (ia>=0 && ib>=0) {
if (A[ia]>=B[ib]) {
A[index--]=A[ia--];
}else {
A[index--]=B[ib--];
}
}
while (ib>=0) {A[index--]=B[ib--];}
}
};
547. 两数组的交集
三种方法:A.HashMap B.排序&归并 C.排序&二分查找。
注意结果数组也要去重:
auto last = unique(intersect.begin(), intersect.end());
intersect.erase(last, intersect.end());
65. 两个排序数组的中位数
可以进一步简化为寻找两个排序数组的第K大的数。
问题的对数时间复杂度算法为:每次排除A,B数组较小的K/2个数(可以用数学算法证明,若A[K/2-1]<B[K/2-1],那么A的前K/2个数中一定不含邮第K大的数)。
具体实现的时候要注意:1.第K大的数是A[K-1] ,注意脚标问题;
2.每次排除K/2个数,剩下K-K/2个数,而且当K=1的时候就要停止,不然会死循环。
3.扔掉不是真的删除,是通过脚标移动进行的,要注意判断脚标是否越界。如果数组内的数不足K/2个,就不扔(直接设置为INT_MAX)。
class Solution {
public:
double findMedianSortedArrays(vector<int> &A, vector<int> &B) {
int m=A.size();
int n=B.size();
//处理奇偶问题,偶数的情况找两次即可。
if ((m+n)%2==0) {
int res1=findk(A,B,(m+n)/2);
int res2=findk(A,B,(m+n)/2+1);
return (res1+res2)/2.0;
}else return findk(A,B,(m+n)/2+1)/1.0;
}
int findk(vector<int> &A, vector<int> &B,int k) {
int m=A.size()-1;
int n=B.size()-1;
int i=0; int j=0;
while (k>1) {//注意,k==1的时候就退出,手动比较当前脚标。
//判断脚标是否越界,越界说明不够扔了,不够扔就扔另外一个。
//用INT_MAX避免被扔掉。
int ia=i+k/2-1>m?INT_MAX:A[i+k/2-1];
int ib=j+k/2-1>n?INT_MAX:B[j+k/2-1];
if (ia<ib) {
i=i+k/2;//移动到被扔的下一个数的位置
} else {
j=j+k/2;
}
k=k-k/2;
}
//处理NULL或者最后坐标加出界的情况
if (A.size()==0 || i>m) return B[j];
if (B.size()==0 || j>n) return A[i];
return (A[i]<B[j]?A[i]:B[j]);
}
};
烙饼排序
首先我们知道有n个烙饼,对其进行排序,移动的次数最多为2n-3。使用汉诺塔的思想,每次最多使用两次操作将最大的饼放在最下面(先把最大的饼翻到最上面,然后再整个翻下来)。
想要优化这个方法,只能通过搜索+减枝。搜索即是枚举所有操作,记录排好序的所有次数。剪枝可以采用以下几个条件:
1.操作次数大于当前最少的操作次数;
2.已经排好序了;
3.当前已有的操作次数+预计的操作次数大于当前最少的操作次数。由于每次操作只能使两个烙饼相邻有序,所以统计当前烙饼数组,统计相邻确无序的烙饼组数,即为预计的最小操作次数;
4.当前烙饼如果上一次操作刚反转过则不考虑;
5.当前状态若之前之前出现过则也不考虑;将已经出现过的状态转换成字符串或者一个数保存到栈内,退出当前状态的时候记得要把状态出栈。
睡眠排序、面条排序、猴子排序
其中猴子排序使用到了洗牌算法,其他算法不用掌握。
607. 两数之和 III-数据结构设计
这里使用unordered_multiset作为存储结构。有一点需要注意的是,set和map的底层都是红黑树,可以保证有序;而unordered_set 和unordered_map的底层才是哈希表。muti指可以支持相同key值。
class TwoSum {
public:
unordered_multiset<int> hash;
void add(int number) {
hash.insert(number);
}
bool find(int value) {
for (auto n:hash) {
if (value==2*n) {
if (hash.count(n)>=2) return true;
}
else if (hash.count(value-n)>=1) return true;
}
return false;
}
};
608. 两数和 II-输入已排序的数组
已经排好序情况可以使用两根指针。
class Solution {
public:
vector<int> twoSum(vector<int> &nums, int target) {
vector<int> res;
int i=0; int j=nums.size()-1;
while (i<j) {
if (nums[i]+nums[j]==target) {
res.push_back(i+1);
res.push_back(j+1);
return res;
}
if (nums[i]+nums[j]<target) i++;
else j--;
}
}
};
587. 两数之和 - 不同组成
不可以先去重。
class Solution {
public:
int twoSum6(vector<int> &nums, int target) {
sort(nums.begin(),nums.end());
int res=0;
int i=0; int j=nums.size()-1;
while (i<j) {
if (nums[i]+nums[j]==target) {
res++;
while (i<j && nums[i+1]==nums[i]) i++;
while (i<j && nums[j-1]==nums[j]) j--;
}
if (nums[i]+nums[j]<target) i++;
else j--;
}
return res;
}
};
57. 三数之和
注意三个数都要处理重复情况。
class Solution {
public:
vector<vector<int>> threeSum(vector<int> &numbers) {
vector<vector<int>> res;
sort(numbers.begin(),numbers.end());
for (int i=0;i<=numbers.size()-3;i++) {
if (i!=0 && numbers[i]==numbers[i-1]) continue;
int target=-numbers[i];
int left=i+1; int right=numbers.size()-1;
while (left<right) {
if (numbers[left]+numbers[right]==target) {
vector<int> tmp;
tmp.push_back(numbers[i]);
tmp.push_back(numbers[left]);
tmp.push_back(numbers[right]);
res.push_back(tmp);
while (left<right && numbers[left+1]==numbers[left]) left++;
while (left<right && numbers[right-1]==numbers[right]) right--;
left++; right--;
}
else if (numbers[left]+numbers[right]<target) left++;
else right--;
}
}
return res;
}
};
609. 两数和-小于或等于目标值
最快的方法还是两个指针不回头,应该想明白一个问题:nums[i]+nums[j+1]>target && nums[i]+nums[j]<=target 则nums[i+1]的解最多只能从j开始,因此ij都不回头。
class Solution {
public:
int twoSum5(vector<int> &nums, int target) {
if (nums.size()<2) return 0;
sort(nums.begin(),nums.end());
int res=0;
int i=0;
int j=nums.size()-1;
while (i<j) {
if (nums[i]+nums[j]<=target) {
res=res+(j-i);
i++;
}
if (nums[i]+nums[j]>target) j--;
}
return res;
}
};
443. 两数之和 II
总个数减去上面那个题即可,或者把ij的移动换过来。
class Solution {
public:
int twoSum2(vector<int> &nums, int target) {
if (nums.size()<2) return 0;
sort(nums.begin(),nums.end());
int res=0;
int i=0;
int j=nums.size()-1;
while (i<j) {
if (nums[i]+nums[j]>target) {
res=res+(j-i);
j--;
}
if (nums[i]+nums[j]<=target) i++;
}
return res;
}
};
class Solution {
public:
int twoSumClosest(vector<int> &nums, int target) {
sort(nums.begin(),nums.end());
int res=INT_MAX;
int i=0; int j=nums.size()-1;
while (i<j) {
if (nums[i]+nums[j]==target) {
return 0;
}
res=min(res,abs(nums[i]+nums[j]-target));
if (nums[i]+nums[j]<target) i++;
else j--;
}
return res;
}
};
class Solution {
public:
int threeSumClosest(vector<int> &numbers, int target) {
if (numbers.size()<3) return -1;
sort(numbers.begin(),numbers.end());
int res=INT_MAX;
for (int i=0;i<numbers.size()-2;i++) {
int j=i+1;
int k=numbers.size()-1;
while (j<k) {
int sum=numbers[i]+numbers[j]+numbers[k];
if (sum==target) return target;
if (abs(sum-target)<abs(res-target)) res=sum;
if (sum<target) j++;
else k--;
}
}
return res;
}
};
58. 四数之和
也可以沿用上面的方法,固定两个数找另外两个数。下面的代码是尝试使用hash表做的。
class Solution {
public:
vector<vector<int>> fourSum(vector<int> &numbers, int target) {
sort(numbers.begin(),numbers.end());
vector<vector<int>> res;
if (numbers.size()<4) return res;
unordered_map<int,vector<pair<int,int>>> twosum;
//构建两和,并记录两数和的组合
for (int i=0;i<numbers.size()-1;i++)
for (int j=i+1;j<numbers.size();j++) {
if (twosum.find(numbers[i]+numbers[j])==twosum.end()) {
vector<pair<int,int>> tmp;
tmp.push_back(make_pair(i,j));
twosum[numbers[i]+numbers[j]]=tmp;
}
else {
int flag=0;
for (auto sum:twosum[numbers[i]+numbers[j]])
if (numbers[sum.first]==numbers[i] && numbers[sum.second]==numbers[j]
|| numbers[sum.first]==numbers[j] && numbers[sum.second]==numbers[i]){
flag=1;
break;
}
if (flag==0) twosum[numbers[i]+numbers[j]].push_back(make_pair(i,j));
}
}
for (int i=0;i<numbers.size()-1;i++){
//不可以用if (i!=0 && nums[i]==nums[i-1]) continue去重
//因为有可能第一个nums[i]被用过了是不符合条件的
for (int j=i+1;j<numbers.size();j++) {
if (twosum.find(target-numbers[i]-numbers[j])!=twosum.end()) {
for (auto sum:twosum[target-numbers[i]-numbers[j]]) {
if (i>sum.first && i>sum.second) {//防止找到镜像解(a+b+c+d)和(c+d+a+b)
vector<int> tmp;
tmp.push_back(numbers[sum.first]);
tmp.push_back(numbers[sum.second]);
tmp.push_back(numbers[i]);
tmp.push_back(numbers[j]);
//最后答案去重
int flag=0;
for (int k=0;k<res.size();k++)
if (res[k]==tmp) {flag=1; break;}
if (flag==0) res.push_back(tmp);
}
}
}
}
}
return res;
}
};
610. 两数和 - 差等于目标值
这个题可以用同向指针做。但是注意两个问题:
1.题目要求输出index,所以排序时要记录index的变化;
2.差值,没有说正负,判断的时候用abs;
3.输出要求index从小到大,输出的时候要检查一下。
当然也可以用hashmap做,应该会简单一些。
class Solution {
public:
vector<int> twoSum7(vector<int> &nums, int target) {
vector<int> res;
if (nums.size()==0) res;
vector<pair<int,int>> m;
for (int i=0;i<nums.size();i++) {
m.push_back(make_pair(nums[i],i));
}
sort(m.begin(),m.end());
int i=0;
int j=1;
while (j<m.size()) {
if (i==j) j++;
else if (abs(m[j].first-m[i].first)==abs(target)) {
res.push_back(min(m[i].second+1,m[j].second+1));
res.push_back(max(m[i].second+1,m[j].second+1));
return res;
}else if (abs(m[j].first-m[i].first)>abs(target)) i++;
else j++;
}
}
};