五、 双指针算法学习(代码随想录学习)

1. 移除元素

leetcode链接
思路: 使用双指针遍历数组,一个指针每次向后移动,另一个指针只有遇见符合条件的元素时才移动,并添加新的元素进入数组。

class Solution {
public:
	int removeElement(vector<int>& nums, int val) {
		int i = 0, j = 0;
		int n = nums.size();
		while (i < n) {
			if (nums[i] != val)
				nums[j++] = nums[i];
			i++;
		}
		return j;
	}
};

时间复杂度: O(n)
空间复杂度: O(1)

2. 反转字符串

leetcode链接

思路: 双指针分别指向字符串的首尾,使指针指向元素进行交换并向中间移动两指针。

class Solution {
public:
	void reverseString(vector<char>& s) {
		int l = 0 , r = s.size() - 1;
		while (l < r)
			swap(s[l++],s[r--]);
	}
};

时间复杂度: O(n)
空间复杂度: O(1)

3. 替换数字

题目链接

思路: 将原字符串按照要求进行扩容,则可不用开辟新的空间。利用双指针从尾部进行元素的填入,可以在O(n)的复杂度内完成替换数字。若从首部开始填入,则会导致元素被覆盖,需要更高的复杂度。

#include<iostream>
using namespace std;

int main(){
    string s;
    while(cin >> s){
        int count = 0; // 统计数字字符的数量
        int index = s.size() - 1 ;
        for(int i = 0; i < s.size();i++){
            if(s[i] >= '0' && s[i] <= '9')
            count++;
        }
        // 将s进行扩充
        s.resize(s.size() + 5 * count);
        int newIndex = s.size() - 1;
        while(newIndex >= 0){
            if(s[index] >= '0' && s[index] <= '9'){
                s[newIndex--] = 'r';
                s[newIndex--] = 'e';
                s[newIndex--] = 'b';
                s[newIndex--] = 'm';
                s[newIndex--] = 'u';
                s[newIndex--] = 'n';
            }
            else
                s[newIndex--] = s[index];
            index--;
        }
        cout << s << endl;
    }
    
}

时间复杂度: O(n)
空间复杂度: O(1)

4. 反转字符串中的单词

leetcode链接

思路: 使用双指针遍历字符串,先去去除多余不符合条件的空格。再将整个字符串进行反转,最后将每次单词依次反转即可。

去除多余空格: 当指针指向空格时,直接跳过。当指针指向非空格时,判断是否为首个单词。若为首个单词,则直接填入元素。若不为,则添加一个空格后,再填入元素。

void removeExtraSpaces(string &s) {
		int slow = 0;
		for (int i = 0; i < s.size(); i++) {
			if (s[i] != ' ') { // 当碰到单词开头时
				if (slow != 0)
					s[slow++] = ' '; // 手动添加一个空格,保证每个单词间仅有一个空格
				while (s[i] != ' ' && i < s.size())  // 将后续单词写入由慢指针写入
					s[slow++] = s[i++];
			}
		}
		s.resize(slow);
	}

** 反转字符串:**

string reverseWords(string s) {
		removeExtraSpaces(s);// 移除多余空格
		reverse(s,0,s.size()-1); // 翻转字符串
		int start = 0; // 每个单词的起始下标
		for (int i = 0; i <= s.size(); i++) {
			if (i == s.size() || s[i] == ' ') {
				reverse(s, start, i - 1);
				start = i + 1; // 更新start
			}
		}
		return s;
	}

整体代码为:

class Solution {
public:
	void reverse(string &s,int start,int end) {  // 翻转函数(选定范围的翻转)
		for (int i = start, j = end; i < j; i++, j--) {
			swap(s[i], s[j]);
		}
	}

	void removeExtraSpaces(string &s) {
		int slow = 0;
		for (int i = 0; i < s.size(); i++) {
			if (s[i] != ' ') { // 当碰到单词开头时
				if (slow != 0)
					s[slow++] = ' '; // 手动添加一个空格,保证每个单词间仅有一个空格
				while (s[i] != ' ' && i < s.size())  // 将后续单词写入由慢指针写入
					s[slow++] = s[i++];
			}
		}
		s.resize(slow);
	}

	string reverseWords(string s) {
		removeExtraSpaces(s);// 移除多余空格
		reverse(s,0,s.size()-1); // 翻转字符串
		int start = 0; // 每个单词的起始下标
		for (int i = 0; i <= s.size(); i++) {
			if (i == s.size() || s[i] == ' ') {
				reverse(s, start, i - 1);
				start = i + 1; // 更新start
			}
		}
		return s;
	}
};

时间复杂度: O(n)
空间复杂度: O(1)

5. 反转链表

leetcode链接

思路: 利用双指针,一个指针指向未翻转剩余的链表,一个指针指向已翻转的链表,每次取下一个元素进行翻转,直至遍历完整个链表

class Solution {
public:
	ListNode* reverseList(ListNode* head) {
		if (head == NULL)
			return NULL;
		ListNode* tmp;
		ListNode* cur = head;
		ListNode* pre = NULL;  // 保存已翻转的部分链表
		while (cur) {
			tmp = cur->next;
			cur->next = pre;
			pre = cur;
			cur = tmp;
		}
		return pre;
	}
};

时间复杂度: O(n)
空间复杂度: O(1)

6. 删除链表的倒数第N个结点

leetcode链接

思路: 先让快指针从链表头节点移动N位,再让慢指针指向链表头节点。同时移动快慢指针,当快指针为NULL时,慢指针指向倒数第N个结点,删除该结点

class Solution {
public:
	ListNode* removeNthFromEnd(ListNode* head, int n) {
		ListNode *dummyHead = new ListNode(0,head);
		ListNode *pre = dummyHead;
		while (n--)
			pre = pre->next;
		ListNode *curPre = dummyHead;
		ListNode *cur = dummyHead->next;
		while (pre->next) {
			pre = pre->next;
			curPre = curPre->next;
			cur = cur->next;
		}
		curPre->next = cur->next;
		delete cur;
		head = dummyHead->next;
		delete dummyHead;
		return head;
	}
};

时间复杂度: O(n)
空间复杂度: O(1)

7. 链表相交

leetcode链接

思路: 分别计算两个链表的长度,将长链表的指针移动两链表长度差值的步数,短链表指针指向头结点,比较两指针是否相等,若相等,则返回该指针;若不相等同时向后移动两指针,直至指针为NULL,返回false。

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;
    }
};

时间复杂度: O(n+m)
空间复杂度: O(1)

8. 环形链表

leetcode链接

思路: 利用快慢指针同时从起点出发,若能相遇,则有环;反之,则没有环。

判断环的入口: 根据数学推导可知,
头结点到环入口的距离 = (n - 1)* 环的长度 + 相遇结点到环入口的距离
因此,若存在环,利用双指针分别从头结点和相遇结点进行移动,两指针相遇的位置即入口结点。

class Solution {
public:
	ListNode *detectCycle(ListNode *head) {
		// 定义快慢指针
		ListNode *fast = head;
		ListNode *slow = head;
		while(fast != nullptr && fast->next != nullptr){
			fast = fast->next->next;
			slow = slow->next;
			if (fast == slow) {  //若是有环,则会相等
				ListNode *cur = head;
				// 同时从起点和相遇节点开始遍历,若二者相遇,则一定是在入口节点
				while (cur != slow) {
					cur = cur->next;
					slow = slow->next;
				}
				return cur;
			}
		}
		return nullptr;
	}
};

时间复杂度: O(n)
空间复杂度: O(1)

9. 三数之和

leetcode链接

思路: 先将数组递增排序,然后利用一层for循环确定三元组中的一位,利用双指针和第二层循环确定三元组的另外两位。并且通过剪枝,去除重复的三元组,得到所有三元组。

class Solution {
public:
	vector<vector<int>> threeSum(vector<int>& nums) {
		vector<vector <int>> result;
		sort(nums.begin(), nums.end());
		for (int i = 0; i < nums.size(); i++) {
			if (nums[i] > 0)
				break;
			if (i > 0 && nums[i] == nums[i - 1])
				continue;
			// 定义左右指针
			int l= i + 1;
			int r = nums.size() - 1;
			while (l < r) {
				if (nums[i] + nums[l] + nums[r] > 0)
					r--;
				else if (nums[i] + nums[l] + nums[r] < 0)
					l++;
				else {
					result.push_back(vector <int> {nums[i], nums[l], nums[r]});
					while (l < r && nums[l] == nums[l + 1])
						l++;
					while (l < r && nums[r] == nums[r - 1])
						r--;
					l++;
					r--;
				}
			}
		}
		return result;
	}
};

时间复杂度: O(n²)
空间复杂度: O(1)

10. 四数之和

leetcode链接

思路: 与三数之和类似,求四元组a,b,c,d。通过一层循环确定a,再嵌套一层循环确定b,最后通过一层循环和双指针确定c和d。同理,也可求N数之和。

剪枝操作:

  • 当确定a时的剪枝
// 剪枝处理(i从0开始)
			if (nums[i] > target &&  nums[i] > 0)
				break;
			if (i > 0 && nums[i] == nums[i - 1])
				continue;
  • 当确定b时的剪枝
// 二级剪枝(j从i + 1 开始),a,b,c,d各不相同
			if (nums[i] + nums[j] > target && nums[i] + nums[j] > 0)
				break;
			if (j > i + 1 && nums[j] == nums[j - 1])
				continue;
  • 双指针内部确定c,d时进行去重
	// 添加一个符合条件的a,b,c,d
	res.push_back(vector<int> {nums[i], nums[j], nums[l], nums[r]});
	// 对左右指针进行去重
	while (l < r && nums[l] == nums[l + 1])
		l++;
	while (l < r && nums[r] == nums[r - 1])
		r--;
	// 每次添加一个结果,需要同时缩小左右区间的范围
	l++;
	r--;

整体代码为:

class Solution {
public:
	vector<vector<int>> fourSum(vector<int>& nums, int target) {
		vector <vector <int>> res;
		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 l = j + 1;
				int r = nums.size() - 1;

				while (l < r) {
					if ((long)nums[i] + nums[j] + nums[l] + nums[r] > target)
						r--;
					else if ((long)nums[i] + nums[j] + nums[l] + nums[r] < target)
						l++;
					else {
						res.push_back(vector<int> {nums[i], nums[j], nums[l], nums[r]});
						// 对左右指针进行去重
						while (l < r && nums[l] == nums[l + 1])
							l++;
						while (l < r && nums[r] == nums[r - 1])
							r--;
						// 每次添加一个结果,需要同时缩小左右区间的范围
						l++;
						r--;
					}
				}
			}
		}
		return res;
	}
};

时间复杂度: O(n³)
空间复杂度: O(1)

同理,利用双指针的方法,K数之和的时间复杂度为O(n^(k-1))

11. 双指针总结

代码随想录:双指针总结篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值