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. 反转字符串
思路: 双指针分别指向字符串的首尾,使指针指向元素进行交换并向中间移动两指针。
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. 反转字符串中的单词
思路: 使用双指针遍历字符串,先去去除多余不符合条件的空格。再将整个字符串进行反转,最后将每次单词依次反转即可。
去除多余空格: 当指针指向空格时,直接跳过。当指针指向非空格时,判断是否为首个单词。若为首个单词,则直接填入元素。若不为,则添加一个空格后,再填入元素。
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. 反转链表
思路: 利用双指针,一个指针指向未翻转剩余的链表,一个指针指向已翻转的链表,每次取下一个元素进行翻转,直至遍历完整个链表
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个结点
思路: 先让快指针从链表头节点移动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. 链表相交
思路: 分别计算两个链表的长度,将长链表的指针移动两链表长度差值的步数,短链表指针指向头结点,比较两指针是否相等,若相等,则返回该指针;若不相等同时向后移动两指针,直至指针为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. 环形链表
思路: 利用快慢指针同时从起点出发,若能相遇,则有环;反之,则没有环。
判断环的入口: 根据数学推导可知,
头结点到环入口的距离 = (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. 三数之和
思路: 先将数组递增排序,然后利用一层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. 四数之和
思路: 与三数之和类似,求四元组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))