双指针法效率的优势:通过两个指针在一个for循环下完成两个for循环的工作。
除了链表一些题目一定要使用双指针,其他题目都是使用双指针来提高效率,一般是将O(n^2)的时间复杂度,降为O(n)
27. 移除元素
数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。
法一,暴力
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
for (int i = 0; i < size; i++) {
if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
for (int j = i + 1; j < size; j++) {
nums[j - 1] = nums[j];
}
i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
size--; // 此时数组的大小-1
}
}
return size;
}
};
法二,双指针
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
//双指针
int j=0;
for(int i=0;i<nums.size();i++){
if(nums[i]!=val){
nums[j]=nums[i];
j++;
}
}
return j;
}
};
/**
* 相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int leftIndex = 0;
int rightIndex = nums.size() - 1;
while (leftIndex <= rightIndex) {
// 找左边等于val的元素
while (leftIndex <= rightIndex && nums[leftIndex] != val){
++leftIndex;
}
// 找右边不等于val的元素
while (leftIndex <= rightIndex && nums[rightIndex] == val) {
-- rightIndex;
}
// 将右边不等于val的元素覆盖左边等于val的元素
if (leftIndex < rightIndex) {
nums[leftIndex++] = nums[rightIndex--];
}
}
return leftIndex; // leftIndex一定指向了最终数组末尾的下一个元素
}
};
344.反转字符串
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
void reverseString(vector<char>& s) {
for (int i = 0, j = s.size() - 1; i < s.size()/2; i++, j--) {
swap(s[i],s[j]);
}
}
};
题目:剑指Offer 05.替换空格
其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这么做有两个好处:
- 不用申请新数组。
- 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
string replaceSpace(string s) {
int oldSize=s.size();
int count=0;
for(auto &i:s){
if(i==' '){
count++;
}
}
s.resize(oldSize+2*count);
int newSize=s.size();
for(int i=oldSize-1,j=newSize-1;j>i;i--,j--){
if(s[i]!=' '){
s[j]=s[i];
}
else{
s[j]='0';
s[j-1]='2';
s[j-2]='%';
j-=2;
}
}
return s;
}
};
151.翻转字符串里的单词
class Solution {
public:
//去除多余空格,双指针
void removeExtraSpaces(string &s){
int slow=0;
for(int i=0;i<s.size();i++){
if(s[i]!=' '){
if(slow!=0) s[slow++]=' ';
while(i<s.size()&&s[i]!=' '){
s[slow++]=s[i++];
}
}
}
s.resize(slow);//注意resize
}
//区间翻转
void reverseStr(string &s,int start,int end){
for(int i=start,j=end;i<j;i++,j--){
swap(s[i],s[j]);
}
}
string reverseWords(string s) {
//去除空格
removeExtraSpaces(s);
//整体翻转
reverseStr(s,0,s.size()-1);
//局部翻转
int count=0;
for(int i=0;i<=s.size();i++){
if(i==s.size()||s[i]==' '){
reverseStr(s,count,i-1);
count=i+1;
}
}
return s;
}
};
206.反转链表
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode*pre=NULL;
ListNode*cur=head;
while(cur){
ListNode*next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
}
return pre;
}
};
//时间复杂度:O(n)
//空间复杂度:O(n)
class Solution {
public:
ListNode*reverseNode(ListNode* pre,ListNode*cur){
if(cur==NULL){
return pre;
}
ListNode*next=cur->next;
cur->next=pre;
return reverseNode(cur,next);
}
ListNode* reverseList(ListNode* head) {
return reverseNode(NULL,head);
}
};
19.删除链表的倒数第N个节点
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* slow = dummyHead;
ListNode* fast = dummyHead;
while(n-- && fast != NULL) {
fast = fast->next;
}
fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
// ListNode *tmp = slow->next; C++释放内存的逻辑
// slow->next = tmp->next;
// delete tmp;
return dummyHead->next;
}
};
面试题 02.07. 链表相交
//时间复杂度:O(m+n)
//空间复杂度:O(1)
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0, lenB = 0;
while (curA != NULL) { // 求链表A的长度
lenA++;
curA = curA->next;
}
while (curB != NULL) { // 求链表B的长度
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
swap (lenA, lenB);
swap (curA, curB);
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap--) {
curA = curA->next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != NULL) {
if (curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
142.环形链表II
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode*slow=head,*fast=head;
while(fast&&fast->next){
fast=fast->next->next;
slow=slow->next;
if(slow==fast){
ListNode*cur=head;
while(cur!=slow){
cur=cur->next;
slow=slow->next;
}
return cur;
}
}
return NULL;
}
};
第15题. 三数之和
// 时间复杂度: O(n2)
// 空间复杂度:O(n)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>>ans;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++){
if(nums[i]>0) return ans;
if(i>0&&nums[i]==nums[i-1]) continue;
int j=i+1,k=nums.size()-1;
while(j<k){
if(nums[i]+nums[j]+nums[k]>0){
k--;
}
else if(nums[i]+nums[j]+nums[k]<0){
j++;
}
else{
ans.push_back({nums[i],nums[j],nums[k]});
while(j<k&&nums[k]==nums[k-1]){
k--;
}
while(j<k&&nums[j]==nums[j+1]){
j++;
}
j++;
k--;
}
}
}
return ans;
}
};
第18题. 四数之和
有一些细节需要注意,例如: 不要判断nums[k] > target 就返回了,三数之和 可以通过 nums[i] > 0 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是[-4, -3, -2, -1],target是-10,不能因为-4 > -10而跳过。但是我们依旧可以去做剪枝,逻辑变成nums[i] > target && (nums[i] >=0 || target >= 0)就可以了。
//时间复杂度: O(n3)
// 空间复杂度:O(n)
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>>ans;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++){
if(nums[i]>target&&nums[i]>=0) break;
if(i>0&&nums[i]==nums[i-1]) continue;
for(int j=i+1;j<nums.size();j++){
if(nums[i]+nums[j]>target&&nums[i]+nums[j]>=0) break;
if(j>i+1&&nums[j]==nums[j-1]) continue;
int left=j+1,right=nums.size()-1;
while(left<right){
if((long)nums[i]+nums[j]+nums[left]+nums[right]>target){
right--;
}
else if((long)nums[i]+nums[j]+nums[left]+nums[right]<target){
left++;
}
else{
ans.push_back({nums[i],nums[j],nums[left],nums[right]});
while(left<right&&nums[right]==nums[right-1]){
right--;
}
while(left<right&&nums[left]==nums[left+1]){
left++;
}
left++;
right--;
}
}
}
}
return ans;
}
};