目录
数组
二分法
704、二分查找
for暴力法
二分法
35、搜索插入位置
34. 在排序数组中查找元素的第一个和最后一个位置
69.x的平方根
双指针
27.移除元素
26.删除有序数组中的重复项
283. 移动零
844、比较含退格的字符串
977、有序数组的平方
滑动窗口
209、长度最小的子数组
904、水果成篮
76、最小覆盖子串
螺旋矩阵
59、螺旋矩阵II
54、螺旋矩阵
剑指Offer 29、顺时针打印矩阵
链表
链表题
203、移除链表元素
707、设计链表
206、翻转链表
24、两两交换链表的节点
19、删除链表的倒数第N个节点
02.07、 链表相交
142、环形链表 II
参考代码随想录
数组下标都是从0开始
数组内存空间的地址是连续的
C++的二维数组在内存的地址也是连续的
数组中的元素不能删除,只能覆盖
只要看到面试题里给出的数组是有序数组,都可以想一想是否可以使用二分法
力扣
数组第一题很简单,有两种解法:for暴力法、二分法
class Solution {
public:
int search(vector& nums, int target) {
for(int i =0; i < nums.size(); i++) {
if (nums[i] == target) return i;
}
return -1;
}
};
要利用middle更新left和right
class Solution {
public:
int search(vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] < target) {
left = middle + 1;
} else if(nums[middle] > target) {
right = middle - 1;
} else return middle;
}
return -1;
}
};
力扣
这个题和上个题的区别就是:如果目标值不存在于数组中,返回它将会被按顺序插入的位置
那么有两种返回:
1、return left;
2、return right + 1;
这两种都可以,为什么第一个不是返回left - 1呢?因为while(left <= right)的原因,举例:
nums = {1,3,5,6};
target = 2
L = 0;R = 1;
下一步:L = 0;R = 0,此时while(L<= R)继续循环L = 1;R = 0(这里就是关键所在)
这是按照左闭右闭方法做的二分法,如果按照另一种while(L< R),则返回的就是相反的了
class Solution {
public:
int searchInsert(vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int middle = (left + right) / 2;
if(nums[middle] < target) {
left = middle + 1;
} else if (nums[middle] > target){
right = middle - 1;
} else return middle;
}
return left;
}
};
力扣
这个题就是写两个二分法,分别找到左边界和右边界
但是有些绕,给自己都绕晕了
力扣
这个题要注意两点:
1、只有当result在left的if中才会正确更新;
2、此题要是long long类型
class Solution {
public:
int mySqrt(int x) {
int left = 0;
int right = x;//此时右边界使用x
int ans = 0;
while (left <= right) {
int middle = left + (right - left) / 2;
if ((long long)middle*middle <= x) {
ans = middle;//一定要知道这里是用中点逐渐找到ans
left = middle + 1;
} else {
right = middle - 1;
}
}
return ans;
}
};
27. 移除元素 - 力扣(Leetcode)
这个题使用双指针法,通过一个快指针和一个慢指针在一个for循环中完成两个for循环的任务
快指针:寻找新数组的元素
慢指针:指向更新新数组的位置
当快指针指向的元素不是val时,正常更新
只有当快指针指向的新的数组中元素是val时,此时不更新了
class Solution {
public:
int removeElement(vector& nums, int val) {
int slowindex = 0;
int size = nums.size();
for(int fastindex = 0; fastindex < size; fastindex++) {
if (val != nums[fastindex]) {
nums[slowindex] = nums[fastindex];
slowindex++;
}
}
return slowindex;
}
};
26. 删除有序数组中的重复项 - 力扣(Leetcode)
这个题和上题类似,就是唯一要注意的时,由于要进行判断nums[fastindex] != nums[fastindex - 1],所以无论是slowindex还是fastindex都要从1开始,并且从1开始很合理(只有一个数不能重复)
class Solution {
public:
int removeDuplicates(vector& nums) {
int slowindex = 1;
int size = nums.size();
for(int fastindex = 1; fastindex < size; fastindex++){
if(nums[fastindex] != nums[fastindex - 1]) {
nums[slowindex++] = nums[fastindex];
}
}
return slowindex;
}
};
283. 移动零 - 力扣(Leetcode)
掌握了双指针方法还是挺简单的
class Solution {
public:
void moveZeroes(vector& nums) {
int slowindex = 0;
int size = nums.size();
for(int fastindex = 0; fastindex < size; fastindex++) {
if(nums[fastindex] != 0){
nums[slowindex++] = nums[fastindex];
}
}
for(int i = slowindex ; i < size; i++) {
nums[i] = 0;
}
}
};
844. 比较含退格的字符串 - 力扣(Leetcode)
这个题还是很有意思的,牵扯到挺多小知识点的
1、tuige()中是双指针
2、tuige(string& str)中一定要使用引用,要不然无法改变实参,我就是这里一直不通过,debug才找到的。
3、包含string类的使用:s.size()、string类型查找同样是s[i]、string类型可以for(char c : str)
4、三目运算符:
表达式1 ? 表达式2 :表达式3
如果表达式1的值为真,执行表达式2,并返回表达式2的结果;
如果表达式1的值为假,执行表达式3,并返回表达式3的结果。
class Solution {
public:
bool backspaceCompare(string s, string t) {
int s_size = tuige(s);
int t_size = tuige(t);
if (s_size == t_size) {
for (int i = 0; i < s_size; i++) {
if (s[i] != t[i])
return false;
}
}else return false;
return true;
}
//返回的是去掉'#'的字符串的大小
int tuige(string& str) {
//正常双指针的逻辑,牵扯到字符串fastindex的特殊换成了c
int slowindex = 0;
for (char c : str) {
if (c != '#') {
str[slowindex++] = c;
}
else {
slowindex == 0 ? 0 : slowindex--;
}
}
return slowindex;
}
};
977. 有序数组的平方 - 力扣(Leetcode)
这个题也是双指针,但不是快慢指针,而是左右指针
左右指针要建立一个新的数组,用以保存结果
class Solution {
public:
vector sortedSquares(vector& nums) {
int size = nums.size();
int n = size - 1;
vector result(size, 0);
int leftindex = 0;
int rightindex = n;
while(leftindex <= rightindex) {
int temp_left = nums[leftindex] * nums[leftindex];
int temp_right = nums[rightindex] * nums[rightindex];
if (temp_left >= temp_right) {
result[n--] = temp_left;
leftindex++;
} else {
result[n--] = temp_right;
rightindex--;
}
}
return result;
}
};
滑动窗口就是不断调节子序列的起始和终止位置,从而得到我们想要的结果
用一个for循环解决两个for循环的工作(双指针同样也是),其实滑动窗口也可以理解成双指针的一种
一个for循环表示滑动窗口的终止位置
209. 长度最小的子数组 - 力扣(Leetcode)
窗口:满足其和≥s的长度最小的连续子数组。
移动起始位置:如果当前窗口的值>s,窗口向前移动。
移动结束位置:使用for循环的索引。
1、int类型的最大值是INT32_MAX,刚开始要给result赋此值用以比较
class Solution {
public:
int minSubArrayLen(int target, vector& nums) {
int i = 0;
int sublength = 0;
int sum = 0;
int result = INT32_MAX;
for (int j = 0; j < nums.size(); j++) {
sum += nums[j];
while (sum >= target) {
sublength = j - i + 1;
result = result < sublength ? result : sublength;
sum -= nums[i++];
}
}
return result == INT32_MAX ? 0 : result;
}
};
904. 水果成篮 - 力扣(Leetcode)
维护好i和j(i和j之间是滑动窗口),用i和j初始化两个篮子,j向右滑动,如果是两个篮子里的就更新结果。如果不是两个篮子里面的,就用J位置的水果更新lan2,并将i移动到j的前面,然后在向前面找和i相对应一样的水果,更新i,更新完毕后更新结果。
class Solution {
public:
int totalFruit(vector& fruits) {
//i和j定位滑动窗口,lan1和lan2是采摘的基准,采摘的必须是两个篮子里的否则要更新
int i = 0;
int result = 0;
int lan1 = fruits[0];
//使用j遍历
for (int j = 0, lan2 = fruits[j]; j < fruits.size(); j++) {
//是两个篮子里的
if (fruits[j] == lan1 || fruits[j] == lan2) {
result = max(result, j - i + 1);
} else {
//不是两个篮子里的,就是有新的水果数,这时更新lan1和lan2,并用lan1向前遍历和lan1相同的水果
lan2 = fruits[j];
i = j - 1;
lan1 = fruits[i];
while(i > 0 && fruits[i - 1] == lan1) {
i--;
}
result = max(result, j - i + 1);
}
}
return result;
}
};
76. 最小覆盖子串 - 力扣(Leetcode)
略难,先略过
59. 螺旋矩阵 II - 力扣(Leetcode)
坚持左闭右开原则
顺时针画矩阵的过程:
上行:从左到右
右列:从上到下
下列:从右向左
左列:从下到上
代码
class Solution {
public:
vector> generateMatrix(int n) {
vector> res(n, vector(n, 0)); // 使用vector定义一个二维数组
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
int count = 1; // 用来给矩阵中每一个空格赋值
int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
int i,j;
while (loop --) {
i = startx;
j = starty;
// 下面开始的四个for就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for (j = starty; j < n - offset; j++) {
res[startx][j] = count++;
}
// 模拟填充右列从上到下(左闭右开)
for (i = startx; i < n - offset; i++) {
res[i][j] = count++;
}
// 模拟填充下行从右到左(左闭右开)
for (; j > starty; j--) {
res[i][j] = count++;
}
// 模拟填充左列从下到上(左闭右开)
for (; i > startx; i--) {
res[i][j] = count++;
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset += 1;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2) {
res[mid][mid] = count;
}
return res;
}
};
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
class Solution {
public:
vector spiralOrder(vector>& matrix) {
if (matrix.size() == 0 || matrix[0].size() == 0)
return {};
int rows = matrix.size(), columns = matrix[0].size();
int total = rows * columns;
vector res(total); // 使用vector定义一个一维数组存放结果
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int loop = min(rows, columns) / 2;
// 本题的loop计算与59.螺旋矩阵II算法略微差异,因为存在rows和columns两个维度,可自行分析,loop只能取min(rows, columns),例如rows = 5, columns = 7,那loop = 5 / 7 = 2
int mid = min(rows, columns) / 2;
// 1、同样的原理,本题的mid计算也存在上述差异;
// 2、
//如果min(rows, columns)为偶数,则不需要在最后单独考虑矩阵最中间位置的赋值
//如果min(rows, columns)为奇数,则矩阵最中间位置不只是[mid][mid],而是会留下来一个特殊的中间行或者中间列,具体是中间行还是中间列,要看rows和columns的大小,如果rows > columns,则是中间列,相反,则是中间行
//相信这一点不好理解,建议自行画图理解
int count = 0;// 用来给矩阵中每一个空格赋值
int offset = 1;// 每一圈循环,需要控制每一条边遍历的长度
int i,j;
while (loop --) {
i = startx;
j = starty;
// 下面开始的四个for就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for (j = starty; j < starty + columns - offset; j++) {
res[count++] = matrix[startx][j];
}
// 模拟填充右列从上到下(左闭右开)
for (i = startx; i < startx + rows - offset; i++) {
res[count++] = matrix[i][j];
}
// 模拟填充下行从右到左(左闭右开)
for (; j > starty; j--) {
res[count++] = matrix[i][j];
}
// 模拟填充左列从下到上(左闭右开)
for (; i > startx; i--) {
res[count++] = matrix[i][starty];
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset += 2;
}
// 如果min(rows, columns)为奇数的话,需要单独给矩阵最中间的位置赋值
if (min(rows, columns) % 2) {
if(rows > columns){
for (int i = mid; i < mid + rows - columns + 1; ++i) {
res[count++] = matrix[i][mid];
}
} else {
for (int i = mid; i < mid + columns - rows + 1; ++i) {
res[count++] = matrix[mid][i];
}
}
}
return res;
}
};
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
和螺旋矩阵其实是一样的,只不过这个是用一维矩阵存储,并且count从0开始
class Solution {
public:
vector spiralOrder(vector>& matrix) {
vector res;
if(matrix.empty()) return res;
int rl = 0, rh = matrix.size()-1; //记录待打印的矩阵上下边缘
int cl = 0, ch = matrix[0].size()-1; //记录待打印的矩阵左右边缘
while(1){
for(int i=cl; i<=ch; i++) res.push_back(matrix[rl][i]);//从左往右
if(++rl > rh) break; //若超出边界,退出
for(int i=rl; i<=rh; i++) res.push_back(matrix[i][ch]);//从上往下
if(--ch < cl) break;
for(int i=ch; i>=cl; i--) res.push_back(matrix[rh][i]);//从右往左
if(--rh < rl) break;
for(int i=rh; i>=rl; i--) res.push_back(matrix[i][cl]);//从下往上
if(++cl > ch) break;
}
return res;
}
};
使用节点串联起来的线性结构,每个节点由两个部分组成:数据域、指针域
链表的入口节点称为头结点head
上图说的是单链表,双链表:每个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。所以既可以向前查询也可以向后查询。
循环链表
链表的定义
struct ListNode {
int val;
ListNode* next;
ListNode():val(0), next(NULL){}
ListNode(int x):val(x), next(NULL){}
ListNode(int x, ListNode* next):val(x), next(next){}
}
链表题在操作链表时,有两种方法:
1、直接使用原来的链表进行操作
2、设置一个虚拟头结点再进行操作
第一种方法无法同一操作,个人喜欢使用第二种方法
203. 移除链表元素 - 力扣(Leetcode)
这个题还是较为简单的
/**
* 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* removeElements(ListNode* head, int val) {
//设置虚拟头结点
ListNode* Dummyhead = new ListNode(0);
Dummyhead->next = head;
ListNode* cur = Dummyhead;
while(cur->next != nullptr) {
if (cur->next->val == val) {
ListNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
} else {
cur = cur->next;
}
}
//删除头结点
head = Dummyhead->next;
delete Dummyhead;
return head;
}
};
707. 设计链表 - 力扣(Leetcode)
这个就是让自己写一个链表,这里选择设计单链表,包含:定义、添加删除元素等
这个题能通过也不容易,写出来有好多个小错误,debug才出来的
class MyLinkedList {
public:
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(): val(0), next(nullptr){}
LinkedNode(int val): val(val), next(nullptr){}
LinkedNode(int val, LinkedNode* next): val(val), next(next){}
};
MyLinkedList() {
_DummyHead = new LinkedNode(0);
_size = 0;
}
int get(int index) {
if (index >= _size || index < 0)
return -1;
LinkedNode* cur = _DummyHead->next;
while(index--) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
LinkedNode* head = new LinkedNode(val);
head->next = _DummyHead->next;
_DummyHead->next = head;
_size++;
}
void addAtTail(int val) {
LinkedNode* Newnode = new LinkedNode(val);
LinkedNode* cur = _DummyHead;
while(cur->next) {
cur = cur->next;
}
cur->next = Newnode;
_size++;
}
void addAtIndex(int index, int val) {
if (index > _size) return;
if (index < 0) index = 0;
LinkedNode* Newnode = new LinkedNode(val);
LinkedNode* cur = _DummyHead;
while(index--) {
cur = cur->next;
}
Newnode->next = cur->next;
cur->next = Newnode;
_size++;
}
void deleteAtIndex(int index) {
if (index >= _size || index < 0) return;
LinkedNode* cur = _DummyHead;
while(index--) {
cur = cur->next;
}
LinkedNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
_size--;
}
private:
LinkedNode* _DummyHead;
int _size;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
206. 反转链表 - 力扣(Leetcode)
这个题比较简单,但是要有思路,主要是运用双指针方法
使用cur不断更新,由于cur->next指向了ptr,所以原来的cur->next要使用temp来存储
/**
* 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* temp;
ListNode* cur = head;
ListNode* ptr = nullptr;
while(cur) {
temp = cur->next;
cur->next = ptr;
//更新
ptr = cur;
cur = temp;
}
return ptr;
}
};
24. 两两交换链表中的节点 - 力扣(Leetcode)
这个题需要在判断cur->next != nullptr && cur->next->next != nullptr后进行操作
一共三步:
ListNode* temp1 = cur->next;
ListNode* temp2 = cur->next->next->next;
cur->next = cur->next->next;//步骤一
cur->next->next = temp1;//步骤二
cur->next->next->next = temp2;//步骤三
cur = cur->next->next;//下一轮
/**
* 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* swapPairs(ListNode* head) {
ListNode* DummyHead = new ListNode(0);
DummyHead->next = head;
ListNode* cur = DummyHead;//指向了虚拟头结点
while(cur->next != nullptr && cur->next->next != nullptr) {
ListNode* temp1 = cur->next;
ListNode* temp2 = cur->next->next->next;
cur->next = cur->next->next;//步骤一
cur->next->next = temp1;//步骤二
cur->next->next->next = temp2;//步骤三
cur = cur->next->next;//下一轮
}
head = DummyHead->next;
delete DummyHead;
return head;
//return DummyHead->next;
}
};
19. 删除链表的倒数第 N 个结点 - 力扣(Leetcode)
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
要记得返回的是DummyHead->next,而不是head
/**
* 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* DummyHead = new ListNode(0,head);
ListNode* cur = DummyHead;
ListNode* ptr = DummyHead;
while(n-- && cur != nullptr) {
cur = cur->next;
}
cur = cur->next;//让ptr指向要删除的链表元素的前一个元素
while(cur != nullptr) {
cur = cur->next;
ptr = ptr->next;
}
ptr->next = ptr->next->next;
return DummyHead->next;
}
};
同:160.链表相交
面试题 02.07. 链表相交 - 力扣(Leetcode)
求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置
此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。
否则循环退出返回空指针。
一定要知道这里相等是curA =curB, 不是valA=valB
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
int getsize(ListNode *head) {
int size = 1;
ListNode* cur = head;
while(cur != NULL) {
size++;
cur = cur->next;
}
return size;
}
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int sizeA = getsize(headA);
int sizeB = getsize(headB);
ListNode* curA = headA;
ListNode* curB = headB;
if (sizeB > sizeA) {
swap(sizeA, sizeB);
swap(curA, curB);
}
int gap = sizeA - sizeB;
while(gap--) {
curA = curA->next;
}
while(curA != NULL) {
if (curA == curB){
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
142. 环形链表 II - 力扣(Leetcode)
主要考察两知识点:
- 判断链表是否有环
使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环
- 如果有环,如何找到这个环的入口
代码随想录 (programmercarl.com)
从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点
这个题个人感觉还是相当有难度的,判断有环喝寻找入口涉及到数学题,不是那么能想到的。
/**
* 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* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL){
fast = fast->next->next;
slow = slow->next;
//下面就是判断相等并寻找入口的代码
if(fast == slow) {
ListNode* index1 = fast;
ListNode* index2 = head;
while(index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return NULL;
}
};