目录
参考代码随想录
数组
数组下标都是从0开始
数组内存空间的地址是连续的
C++的二维数组在内存的地址也是连续的
数组中的元素不能删除,只能覆盖
二分法
只要看到面试题里给出的数组是有序数组,都可以想一想是否可以使用二分法
704、二分查找
数组第一题很简单,有两种解法:for暴力法、二分法
for暴力法
class Solution {
public:
int search(vector<int>& nums, int target) {
for(int i =0; i < nums.size(); i++) {
if (nums[i] == target) return i;
}
return -1;
}
};
二分法
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
要利用middle更新left和right
class Solution {
public:
int search(vector<int>& 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;
}
};
35、搜索插入位置
这个题和上个题的区别就是:如果目标值不存在于数组中,返回它将会被按顺序插入的位置
那么有两种返回:
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<int>& 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;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置
这个题就是写两个二分法,分别找到左边界和右边界
但是有些绕,给自己都绕晕了
69.x的平方根
这个题要注意两点:
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.移除元素
这个题使用双指针法,通过一个快指针和一个慢指针在一个for循环中完成两个for循环的任务
快指针:寻找新数组的元素
慢指针:指向更新新数组的位置
当快指针指向的元素不是val时,正常更新
只有当快指针指向的新的数组中元素是val时,此时不更新了
class Solution {
public:
int removeElement(vector<int>& 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.删除有序数组中的重复项
26. 删除有序数组中的重复项 - 力扣(Leetcode)
这个题和上题类似,就是唯一要注意的时,由于要进行判断nums[fastindex] != nums[fastindex - 1],所以无论是slowindex还是fastindex都要从1开始,并且从1开始很合理(只有一个数不能重复)
class Solution {
public:
int removeDuplicates(vector<int>& 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. 移动零
掌握了双指针方法还是挺简单的
class Solution {
public:
void moveZeroes(vector<int>& 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、比较含退格的字符串
这个题还是很有意思的,牵扯到挺多小知识点的
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、有序数组的平方
这个题也是双指针,但不是快慢指针,而是左右指针
左右指针要建立一个新的数组,用以保存结果
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int size = nums.size();
int n = size - 1;
vector<int> 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、长度最小的子数组
窗口:满足其和≥s的长度最小的连续子数组。
移动起始位置:如果当前窗口的值>s,窗口向前移动。
移动结束位置:使用for循环的索引。
1、int类型的最大值是INT32_MAX,刚开始要给result赋此值用以比较
class Solution {
public:
int minSubArrayLen(int target, vector<int>& 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、水果成篮
维护好i和j(i和j之间是滑动窗口),用i和j初始化两个篮子,j向右滑动,如果是两个篮子里的就更新结果。如果不是两个篮子里面的,就用J位置的水果更新lan2,并将i移动到j的前面,然后在向前面找和i相对应一样的水果,更新i,更新完毕后更新结果。
class Solution {
public:
int totalFruit(vector<int>& 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、最小覆盖子串
略难,先略过
螺旋矩阵
59、螺旋矩阵II
坚持左闭右开原则
顺时针画矩阵的过程:
上行:从左到右
右列:从上到下
下列:从右向左
左列:从下到上
代码
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(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;
}
};
54、螺旋矩阵
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if (matrix.size() == 0 || matrix[0].size() == 0)
return {};
int rows = matrix.size(), columns = matrix[0].size();
int total = rows * columns;
vector<int> 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;
}
};
剑指Offer 29、顺时针打印矩阵
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
和螺旋矩阵其实是一样的,只不过这个是用一维矩阵存储,并且count从0开始
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector <int> 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、移除链表元素
这个题还是较为简单的
/**
* 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、设计链表
这个就是让自己写一个链表,这里选择设计单链表,包含:定义、添加删除元素等
这个题能通过也不容易,写出来有好多个小错误,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、翻转链表
这个题比较简单,但是要有思路,主要是运用双指针方法
使用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、两两交换链表的节点
这个题需要在判断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个节点
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;
}
};
02.07、 链表相交
同: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
主要考察两知识点:
- 判断链表是否有环
使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环
- 如果有环,如何找到这个环的入口
从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点
这个题个人感觉还是相当有难度的,判断有环喝寻找入口涉及到数学题,不是那么能想到的。
/**
* 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;
}
};