移除元素
例题27(简单)移除元素
注意要点:
- 在数组专题中已经有涉及,直接快慢指针,如果碰到要删除的元素,就直接快指针右移跳过;否则就把快指针的值赋值给慢指针,然后一起右移。
下面贴出代码:
CPP版本
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.size(); fast++)
{
if (nums[fast] == val) {continue;}
else {nums[slow++] = nums[fast];}
}
return slow;
}
};
C版本
int removeElement(int* nums, int numsSize, int val){
int slow = 0;
for (int fast = 0; fast < numsSize; fast++)
{
if (nums[fast] == val) {continue;}
else {nums[slow++] = nums[fast];}
}
return slow;
}
反转字符串
例题344(简单)反转字符串
注意要点:
- 相向双指针,交换当前两指针的值后,均向内移动一位。
下面贴出代码:
CPP版本
class Solution {
public:
void reverseString(vector<char>& s) {
for (int i = 0; i < s.size() / 2; i++)
{
int tmp = s[i];
s[i] = s[s.size() - 1 - i];
s[s.size() - 1 - i] = tmp;
}
}
};
C版本
void reverseString(char* s, int sSize){
for (int i = 0; i < sSize / 2; i++)
{
int tmp = s[i];
s[i] = s[sSize - 1 - i];
s[sSize - 1 - i] = tmp;
}
}
替换空格
例题 剑指Offer05(简单)替换空格
注意要点:
- 需要注意替换的方向:从前向后就是O(n^2)(还需要在填充后进行移位操作);而预先扩充数组空间后从后向前就仅有O(n)。
下面贴出代码:
CPP版本
class Solution {
public:
string replaceSpace(string s) {
//统计空格数量
int count = 0;
for (int i = 0; i < s.size(); i++)
{
if (s[i] == ' ') {count++;}
}
//扩充为所需的数组大小
int oldSize = s.size();
s.resize(s.size() + 2 * count);
int newSize = s.size();
for (int i = newSize - 1, j = oldSize - 1; i >= 0, j >= 0; i--, j--)
{
if (s[j] == ' ')
{
s[i--] = '0';
s[i--] = '2';
s[i] = '%';
}
else {s[i] = s[j];}
}
return s;
}
};
C版本
char* replaceSpace(char* s){
int size = 0;
for (int i = 0; i < strlen(s); i++)
{
if (s[i] != ' ') {size++;}
else {size += 3;}
}
char* ans = (char* )malloc(sizeof(char) * (size + 1));
int ptr_s = strlen(s) - 1, ptr_ans = size;
ans[ptr_ans] = '\0';
ptr_ans--;
while (ptr_s >= 0)
{
if (s[ptr_s] != ' ')
{
ans[ptr_ans] = s[ptr_s];
ptr_s--;
ptr_ans--;
}
else
{
ans[ptr_ans] = '0';
ans[ptr_ans-1] = '2';
ans[ptr_ans-2] = '%';
ptr_ans -= 3;
ptr_s--;
}
}
return ans;
}
翻转字符串中的单词
例题151(中等)反转字符串中的单词
注意要点:
- 最重要的思路:首先先删掉多余的空格,反转整个字符串,然后寻找到每一个单词,再把单词翻转;
- 删除多余的空格,使用快慢指针就可以,类似之前的移除元素;
下面贴出代码:
CPP版本
class Solution {
public:
void myreverse(string& s, int start, int end)
{
int mid = start + (end - start) / 2;
for (int i = start; i < mid; i++)
{
swap(s[i], s[end - 1 - (i - start)]);
}
}
void removeExtraSpaces(string& s)
{
int slow = 0;
for (int i = 0; i < s.size(); i++)
{
if (s[i] != ' ')
{
if (slow) {s[slow++] = ' ';}
while (i < s.size() && s[i] != ' ') {s[slow++] = s[i++];}
}
}
s.resize(slow);
}
string reverseWords(string s) {
removeExtraSpaces(s);
myreverse(s, 0, s.size());
int start = 0;
for (int i = 0; i <= s.size(); i++)
{
if (i == s.size() || s[i] == ' ')
{
myreverse(s, start, i);
start = i + 1;
}
}
return s;
}
};
C版本
char * reverseWords(char * s){
int slow = 0;
for (int i = 0; i < strlen(s); i++)
{
if (s[i] != ' ')
{
if (slow) {s[slow++] = ' ';}
while (i < strlen(s) && s[i] != ' ') {s[slow++] = s[i++];}
}
}
s[slow] = '\0';
int left = 0, right = strlen(s) - 1; // 当前头部仍可能有空格时的字符串长度
while (left < right)
{
char tmp = s[right];
s[right] = s[left];
s[left] = tmp;
left++;
right--;
}
int len_tmp = strlen(s);
int len = strlen(s);
// 接下来翻转单词
int word_left = 0;
for (int i = 0; i < len + 1; i++)
{
if (s[i] == ' ' || s[i] == '\0')
{
int word_right = i - 1;
while (word_left < word_right)
{
char tmp = s[word_right];
s[word_right] = s[word_left];
s[word_left] = tmp;
word_left++;
word_right--;
}
word_left = i + 1;
}
}
return s;
}
翻转链表
例题206(简单)翻转链表
注意要点:
- 翻转的思路是找到当前节点的前一个节点,并修改当前节点的next指针,然后向后移动一位;所以需要保存pre以及cur->next辅助计算。
下面贴出代码:
CPP版本
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;
ListNode* pre = nullptr;
while (cur)
{
ListNode* tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
};
C版本
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* dummy = (struct ListNode* )malloc(sizeof(struct ListNode));
dummy->next = head;
struct ListNode* cur = dummy->next;
struct ListNode* pre = NULL;
while (cur)
{
struct ListNode* tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
删除链表的倒数第N个节点
例题19(中等)删除链表的倒数第N个节点
注意要点:
- 避免删除的是头结点,所以设置虚拟头节点辅助计算;
- 通过快慢指针,快指针移动(n+1)次,再与慢指针一起移动直至链表尾部,慢指针所在位置即为倒数(n+1)所在;
- 移动n+1次,就可以直接通过next来删除倒数第n个,避免还要记录pre节点。
下面贴出代码:
CPP版本
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* slow = dummy;
ListNode* fast = dummy;
while (n--) {fast = fast->next;}
fast = fast->next;
while (fast)
{
slow = slow->next;
fast = fast->next;
}
ListNode* tmp = slow->next;
slow->next = slow->next->next;
delete(tmp);
tmp = nullptr;
return dummy->next;
}
};
C版本
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
struct ListNode* dummyhead = malloc(sizeof(struct ListNode));
dummyhead->next = head;
struct ListNode* slow = dummyhead;
struct ListNode* fast = dummyhead;
while (n--) {fast = fast->next;}
fast = fast->next;
while (fast)
{
slow = slow->next;
fast = fast->next;
}
slow->next = slow->next->next;
return dummyhead->next;
}
链表相交
例题160(简单)链表相交
注意要点:
- 通过遍历长度,并使得两链表的当前指针对齐,进而可以同时移动指针判断是否相交;
- 对齐方法:计算链表长度差,并在长链表指针移动长度差个次数,来对齐两个指针;
- 相交是地址相等,不能简单判断值相等。
下面贴出代码:
CPP版本
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int totalA = 0, totalB = 0;
ListNode* curA = headA;
ListNode* curB = headB;
//统计两个链表的长度
while (curA) {curA = curA->next; totalA++;}
while (curB) {curB = curB->next; totalB++;}
//移动到距离结尾相同距离处
curA = headA, curB = headB;
int sub = totalA - totalB;
if (sub > 0)
{
while (sub--) {curA = curA->next;}
}
else {while (sub++) {curB = curB->next;}}
//判断地址是否相同,而不是val是否相同
while (curA)
{
if (curA == curB) {return curA;}
else
{
curA = curA->next;
curB = curB->next;
}
}
return NULL;
}
};
C版本
/**
1. Definition for singly-linked list.
2. struct ListNode {
3. int val;
4. struct ListNode *next;
5. };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode* p1 = malloc(sizeof(struct ListNode));
struct ListNode* p2 = malloc(sizeof(struct ListNode));
p1->next = headA;
p2->next = headB;
int l1 = 0, l2 = 0;
while (p1->next)
{
p1 = p1->next;
l1++;
}
while (p2->next)
{
p2 = p2->next;
l2++;
}
p1 = headA;
p2 = headB;
int delta = l1 - l2;
// printf("delta = %d\n", delta);
// printf("p1 = %d\n", p1);
if (delta >= 0)
{
while(delta--) {p1 = p1->next;}
}
else
{
while(delta++) {p2 = p2->next;}
}
// printf("p1 = %d\n", p1);
while (p1)
{
if (p1 == p2)
{
return p1;
}
p1 = p1->next;
p2 = p2->next;
}
return NULL;
}
环形链表
例题142(中等)环形链表II
注意要点:
- 是否成环的判断:快慢指针,快指针走两步,慢指针走一步,如果能相遇,则证明成环;记录下当前节点;
- 寻找成环点:快指针从当前节点出发,慢指针从头结点出发,移动相同步数,相遇点即为成环点。
这道题涉及到的数学推导可以自行退导一下,不乐意推导就直接记结论就可以做题了。
下面贴出代码:
CPP版本
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast) //有环
{
//从相遇节点,slow回到head,再次移动同样步数,再次相遇就是成环点
slow = head;
while (slow != fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
}
return NULL;
}
};
C版本
/**
1. Definition for singly-linked list.
2. struct ListNode {
3. int val;
4. struct ListNode *next;
5. };
*/
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode* slow = head;
struct ListNode* fast = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
{
while (slow != head)
{
slow = slow->next;
head = head->next;
}
return head;
}
}
return NULL;
}
三数之和
例题15(中等)三数之和
注意要点:
- 首先把数组变成单调数组,这样才可以简化后续的算法复杂度;
- for循环内部,通过单调的特性,进行双指针的简化复杂度算法;如果>target,那么right指针就左移来减小和;如果<target,那么left指针就右移来增大和。
下面贴出代码:
CPP版本
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ret;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 2; i++)
{
//排序后的最小值已经是正的,不可能达到和为0
if (nums[i] > 0) {return ret;}
//剪枝操作,不可以从第一个开始,这样会使得答案有遗漏
if (i && nums[i] == nums[i - 1]) {continue;}
int left = i + 1, right = nums.size() - 1;
while (left < right)
{
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {right--;}
else if (sum < 0) {left++;}
else
{
ret.push_back(vector<int>{nums[i], nums[left], nums[right]});
while (left < right && nums[left] == nums[left + 1]) {left++;}
while (left < right && nums[right] == nums[right - 1]) {right--;}
left++;
right--;
}
}
}
return ret;
}
};
C版本
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
int cmp(const void* a, const void* b)
{
return *(int* )a - *(int* )b;
}
int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
int** ans = (int** )malloc(sizeof(int* ) * 3000 * 6);
*returnSize = 0;
if (numsSize < 3) {return ans;}
qsort(nums, numsSize, sizeof(int), cmp);
for (int i = 0; i < numsSize - 2; i++)
{
if (nums[i] > 0) {break;}
else
{
if (i && nums[i] == nums[i - 1]) {continue;}
int left = i + 1, right = numsSize - 1;
while (right > left)
{
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {right--;}
else if (sum < 0) {left++;}
else
{
int* arr = (int* )malloc(sizeof(int) * 3);
arr[0] = nums[i];
arr[1] = nums[left];
arr[2] = nums[right];
ans[(*returnSize)++] = arr;
while (right > left && nums[right] == nums[right - 1]) {right--;}
while (right > left && nums[left] == nums[left + 1]) {left++;}
right--;
left++;
}
}
}
}
*returnColumnSizes = (int* )malloc(sizeof(int) * (*returnSize));
for (int k = 0; k < (*returnSize); k++) {(*returnColumnSizes)[k] = 3;}
return ans;
}
四数之和
例题18(中等)四数之和
注意要点:
- 方法与三数之和很类似,只不过for需要循环两次,注意剪枝和去重都可以写两次。
下面贴出代码:
CPP版本
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> ret;
if (nums.size() < 4) {return ret;}
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 3; i++)
{
if (nums[i] > target && nums[i] >= 0) {break;}
if (i && nums[i] == nums[i - 1]) {continue;}
for (int j = i + 1; j <nums.size() - 2; 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
{
ret.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
while (left < right && nums[left] == nums[left + 1]) {left++;}
while (left < right && nums[right] == nums[right-1]) {right--;}
left++;
right--;
}
}
}
}
return ret;
}
};
C版本
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
int cmp(const void* a, const void* b)
{
return *(int* )a - *(int* )b;
}
int** fourSum(int* nums, int numsSize, int target, int* returnSize, int** returnColumnSizes){
int** res= (int** )malloc(sizeof(int* ) * 1001);
*returnSize = 0;
if (numsSize < 4) {return res;}
qsort(nums, numsSize, sizeof(int), cmp);
int len = numsSize;
for (int i = 0; i < len - 3; i++)
{
if (nums[i] > target && nums[i] >= 0) {break;}
if (i && nums[i] == nums[i - 1]) {continue;}
for (int j = i + 1; j < len - 2; 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 = len - 1;
while (left < right)
{
if ((long)nums[i] + nums[j] + nums[left] + nums[right] < target) {left++;}
else if ((long)nums[i] + nums[j] + nums[left] + nums[right] > target) {right--;}
else
{
int* arr = (int* )malloc(sizeof(int) * 4);
arr[0] = nums[i];
arr[1] = nums[j];
arr[2] = nums[left];
arr[3] = nums[right];
res[(*returnSize)++] = arr;
while (left < right && nums[left] == nums[left + 1]) {left++;}
while (left < right && nums[right] == nums[right-1]) {right--;}
left++;
right--;
}
}
}
}
*returnColumnSizes = (int* )malloc(sizeof(int* ) * (*returnSize));
for (int i = 0; i < (*returnSize); i++) {(*returnColumnSizes)[i] = 4;}
return res;
}
总结
- 双指针法非常常用,只要涉及删改或者交换,都可以考虑是否需要用双指针法,包括有快慢指针,以及相向指针;
- 双指针法用来降低时间复杂度,大多需要配合数组的预排序;
- 反向操作就需要先记录pre,以及当前的cur形成双指针。