力扣刷题
一、数组
1.二分法查找
适用场景:
-
数组按大小顺序排列 -
无重复值
形式:
- [left,right]
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // 在左区间,所以[left, middle - 1],且需要比较nums[middle],取right=middle-1
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
- [left,right)
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int middle = left + ((right - left) >> 1);//>>等价于除2取左整数
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中,right取middle,即不去比较nums[middle]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
错误解法
int searchInsert(vector<int>& nums, int target) {
int n = nums.size();
int l = 0, r = n - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (nums[mid] > target)
r = mid - 1;
else
l = mid;//由于除以2取左整数,导致l的值无法更新,因此会陷入死循环,破解方式为后面加个1
}
return l;
}
####注:
2.双指针法
适用场景
思路:前后指针,数组复制
二、哈希表
###1.判断一个数是否为快乐数:
? 1. 求取数值各个位的平方和
? 2. unordered_set的应用
class Solution {
public:
int getSum(int n)
{
int sum=0;
while(n)
{
sum+=(n%10)*(n%10);
n/=10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int>set1;
int numb=getSum(n);
while(numb!=1)
{
if(set1.count(numb))
{
return false;
}
set1.insert(numb);
numb=getSum(numb);
}
return true;
}
};
2.查找两数之和
-
题目概述:在数组中找到和为目标值的两个数并返回下标 -
关键词:查找:当需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,首先想到用哈希法 ? unordered_set与unordered_map底层实现都是哈希表,而unordered_map是key—value结构 ? 返回下标:不仅要查找元素,还要返回其下标,则需要用key—value来存放元素 unordered_map常见用法
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int>map1;
for(int i=0;i<nums.size();i++)
{
int a=target-nums[i];
if(map1.find(a)==map1.end())
{
map1.insert(pair<int,int>(nums[i],i));
}
else
return {map1.find(a)->second,i};
}
return {};
}
};
3.查找四数之和(与四数之和同理)
-
题目概述:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。 -
难点:降低时间复杂度和去重 双指针法:快慢指针,头尾指针 class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>>fournumssum;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++)
{
if(i>0&&nums[i]==nums[i-1])//剪枝操作
continue;
for(int j=i+1;j<nums.size();j++)
{
if(j>i+1&&nums[j]==nums[j-1])//二次剪枝
continue;
int left=j+1;
int 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
{
fournumssum.push_back({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--;
right--;
left++;
}
}
}
}
return fournumssum;
}
};
三、链表
注意点
- 设置虚拟头节点可以省去很多麻烦,但在程序结束时最好将其释放
- 单链表中很多操作需要设置临时节点用以保存某些节点
- 注意操作顺序,如力扣24题交换相邻节点时,改变指向时要遵循链表读取顺序
24.两两交换链表的相邻节点
即:
注意:
顺序变换遵循单链表的遍历顺序,用临时指针保存节点,操作完成后释放虚拟头节点的内存区域
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode *dummyhead=new ListNode(0);
dummyhead->next=head;
ListNode* cur=dummyhead;
while(cur->next!=NULL&&cur->next->next!=NULL)
{
ListNode* temp1=cur->next;
ListNode* temp2=cur->next->next->next;
cur->next=cur->next->next;
cur->next->next=temp1;
temp1->next=temp2;
cur=cur->next->next;
}
ListNode* temp=dummyhead->next;
delete dummyhead;
return temp;
}
};
四、字符串
-
方法:双指针 -
交换两数不用中间变量的方法 a=a^b;
b=a^b;
a=a^b;
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
关键点:记录位置,降低复杂度
class Solution {
public:
int strStr(string haystack, string needle) {
int rtnnum=-1;
for(int i=0,j=0;i<haystack.size();)
{
if(haystack[i+j]==needle[j])//逐个比对
{
j++;//j指向模式串
}
else
{
i++;//继续向下对比
j=0;//返回模式串初始位置
}
if(j==needle.size())//全部比对完成
{
return i;
break;
}
}
return rtnnum;
}
};
class Solution {
public:
int strStr(string haystack, string needle) {
return haystack.find(needle);
}
};
文本串
模式串需要有一定对称性
前缀:包含首字母不包含尾字母的子字符串(从左往右),如aabaaf的前缀有a,aa,aab,aaba,aabaa,。后缀同理
前缀表:(相等前后缀数量?)
next数组求法:
初始化-》处理前后缀不相同的情况-》处理前后缀相同的情况
i指向后缀末尾,j指向与后缀相同的最长前缀末尾(其实整体减一更容易理解
void getNext(int* next, const string& s){
int j = -1;
next[0] = j;
for(int i = 1; i < s.size(); i++) {
while (j >= 0 && s[i] != s[j + 1]) {
j = next[j];
}
if (s[i] == s[j + 1]) {
j++;
}
next[i] = j;
}
}
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++) {
while (j > 0 && s[i] != s[j]) {
j = next[j - 1];
}
if (s[i] == s[j]) {
j++;
}
next[i] = j;
}
}
五、栈和队列
栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中不是连续分布。
缺省情况(构造函数缺省)下默认底层容器是deque(双向队列),其在内存中的数据分布也是不连续的。
###1.用栈(stack)实现队列(queue)
栈的特点:先入后出
队列的特点:先入先出
要想用栈实现队列,需要用两个栈来维护入队何出队顺序。入队时,进入stIn,出队时,当stOut不为空时只需从stOut出,若为空,则需要将stIn依次全部转移到stOut中,以此保证队列先进先出的特点。
class MyQueue {
public:
stack<int> stIn;
stack<int> stOut;
/** Initialize your data structure here. */
MyQueue() {
}
/** Push element x to the back of queue. */
void push(int x) {
stIn.push(x);
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
// 只有当stOut为空的时候,再从stIn里导入数据(导入stIn全部数据)
if (stOut.empty()) {
// 从stIn导入数据直到stIn为空
while(!stIn.empty()) {
stOut.push(stIn.top());
stIn.pop();
}
}
int result = stOut.top();
stOut.pop();
return result;
}
/** Get the front element. */
int fornt() {
int res = this->pop(); // 直接使用已有的pop函数
stOut.push(res); // 因为pop函数弹出了元素res,所以再添加回去
return res;
}
/** Returns whether the queue is empty. */
bool empty() {
return stIn.empty() && stOut.empty();
}
};
-
方法一:用两个队列,一个作为出栈时的辅助队列 思路:入栈时,进入qIn队列;出栈时,此时要出栈的元素在qIn队尾,要想将其取出,需要将qIn中除此元素的所有队前元素全部依次转移至qOut队列中,交换两个队列后qOut队头即为要出栈的元素。再从qOut出队。(或者第二个队列看作备份队列,出栈时先将除要出栈元素以外的元素依次转移至备份队列中,将该元素出队后将q2赋值给q1) class MyStack {
public:
queue<int> que1;
queue<int> que2; // 辅助队列,用来备份
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
que1.push(x);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int size = que1.size();
size--;
while (size--) { // 将que1 导入que2,但要留下最后一个元素
que2.push(que1.front());
que1.pop();
}
int result = que1.front(); // 留下的最后一个元素就是要返回的值
que1.pop();
que1 = que2; // 再将que2赋值给que1
while (!que2.empty()) { // 清空que2
que2.pop();
}
return result;
}
/** Get the top element. */
int top() {
return que1.back();
}
/** Returns whether the stack is empty. */
bool empty() {
return que1.empty();
}
};
-
方法2:一个队列实现 思路:入栈即入队,出栈时,将除出栈元素之外的所有元素移动至队尾,此时队头即为要出栈的元素 class MyStack {
public:
queue<int> que;
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
que.push(x);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int size = que.size();
size--;
while (size--) { // 将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部
que.push(que.front());
que.pop();
}
int result = que.front(); // 此时弹出的元素顺序就是栈的顺序了
que.pop();
return result;
}
/** Get the top element. */
int top() {
return que.back();
}
/** Returns whether the stack is empty. */
bool empty() {
return que.empty();
}
};
基本思路:相邻匹配,匹配成功出栈,不成功入栈。注意技巧
逆波兰表达式:运算符前的两个元素相运算获得一个元素。若为数字则入栈,若为运算符则出栈两个元素,运算结果入栈。stoi(s),字符串转数字
若使用暴力解法,需要找出每个滑动窗口的最大值,时间复杂度:O(n*k)。
大顶堆确实可以弹出最大值,但却无法弹出其他值,导致其维护的不是滑动窗口内的数值。
用单调队列:
copy某评论:单调队列真是一种让人感到五味杂陈的数据结构,它的维护过程更是如此…就拿此题来说,队头最大,往队尾方向单调…有机会站在队头的老大永远心狠手辣,当它从队尾杀进去的时候,如果它发现这里面没一个够自己打的,它会毫无人性地屠城,把原先队里的人头全部丢出去,转身建立起自己的政权,野心勃勃地准备开创一个新的王朝…这时候,它的人格竟发生了一百八十度大反转,它变成了一位胸怀宽广的慈父!它热情地请那些新来的“小个子”们入住自己的王国…然而,这些小个子似乎天性都是一样的——嫉妒心强,倘若见到比自己还小的居然更早入住王国,它们会心狠手辣地找一个夜晚把它们通通干掉,好让自己享受更大的“蛋糕”;当然,遇到比自己强大的,它们也没辙,乖乖夹起尾巴做人。像这样的暗杀事件每天都在上演,虽然王国里日益笼罩上白色恐怖,但是好在没有后来者强大到足以干翻国王,江山还算能稳住。直到有一天,闯进来了一位真正厉害的角色,就像当年打江山的国王一样,手段狠辣,野心膨胀,于是又是大屠城…历史总是轮回的。
class Solution {
public:
class myque{
public:
deque<int>que;
void push(int value)
{
while(!que.empty()&&value>que.back())
que.pop_back();
que.push_back(value);
}
void pop(int value)
{
if(!que.empty()&&value==que.front())//若滑动窗口左端值正好为最大值,就得pop掉
que.pop_front();
}
int getmax()
{
return que.front();
}
};
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
myque que;
vector<int>result;
for(int i=0;i<k;i++)
{
que.push(nums[i]);
}
result.push_back(que.getmax());
for(int i=k;i<nums.size();i++)
{
que.pop(nums[i-k]);
que.push(nums[i]);
result.push_back(que.getmax());
}
return result;
}
};
大顶堆与小顶堆的概念
顶堆:披着优先级队列外衣的堆
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
};
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
优先队列有三个参数,其声明式为:
priority_queue< type, container, function >
这三个参数,后面两个可以省略,第一个不可以。其中: **type:**数据类型; **container:**实现优先队列的底层容器; **function:**元素之间的比较方式; 对于container,要求必须是数组形式实现的容器,例如vector、deque,而不能使list。 在STL中,默认情况下(不加后面两个参数)是以vector为容器,以 operator< 为比较方式,所以在只使用第一个参数时,优先队列默认是一个最大堆,每次输出的堆顶元素是此时堆中的最大元素。
六、二叉树
1.递归
? (1). 问题的定义是按递归定义的(Fibonacci函数,阶乘,…);
(2). 问题的解法是递归的(有些问题只能使用递归方法来解决,例如,汉诺塔问题,…);
(3). 数据结构是递归的(链表、树等的操作,包括树的遍历,树的深度,…)。
在求解规模不确定的情况下,由于函数调用的开销,递归常常会带来性能问题
2.深度优先与广度优先
- 深度优先:用递归和迭代(用栈操作)
- 广度优先:用递归和迭代(队列操作)
3 .二叉树顺序遍历
- 递归遍历(深层思想也是栈,因为递归能干的栈也能干)
void trversal(vector<int>&result,TreeNode* cur)//前序遍历递归法,确定返回值和参数列表
{
if(cur==NULL)return;//确定结束条件
result.push_back(cur->val);//根
trversal(result,cur->left);//左
trversal(result,cur->right);//右
}
vector<int> preorderTraversal(TreeNode* root) //前序遍历
{
vector<int>result;
stack<TreeNode*>st;
TreeNode* cur=root;
st.push(cur);
if(cur==NULL)return;
while(!st.empty())
{
cur=cur.top();
result.push_back(cur->val);//根
st.pop();
if(cur->right)st.push(cur->right);//右
if(cur->left)st.push(cur->left);//左
}
}
vector<int> preorderTraversal(TreeNode* root) //中序遍历,与前序遍历思路不太一样
{
vector<int>result;
stack<TreeNode*>st;
TreeNode* cur=root;
st.push(cur);
if(cur==NULL)return;
while(!st.empty())
{
cur=cur.top();
result.push_back(cur->val);//根
st.pop();
if(cur->right)st.push(cur->right);//右,注意入栈条件
if(cur->left)st.push(cur->left);//左
}
}
###3.二叉树层序遍历(深度优先遍历)
void order(TreeNode* cur, vector<vector<int>>& result, int depth)//确定返回值和参数列表
{
if (cur == nullptr) return;
if (result.size() == depth) result.push_back(vector<int>());//若该层无容器(表现为深度==二维容器大小),则放入个容器
result[depth].push_back(cur->val);//把当前节点放入对应层中
order(cur->left, result, depth + 1);//深度递增,从左到右
order(cur->right, result, depth + 1);
}
-
迭代遍历(队列实现) 思想:将出队元素的左右子入队
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
vector<vector<int>> result;
while (!que.empty()) {
int size = que.size();
vector<int> vec;
// 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if (node->left) que.push(node->left);//注意入栈条件
if (node->right) que.push(node->right);
}
result.push_back(vec);
}
return result;
}
;//深度递增,从左到右 order(cur->right, result, depth + 1); }
- 迭代遍历(队列实现)
思想:将出队元素的左右子入队
```C++
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
vector<vector<int>> result;
while (!que.empty()) {
int size = que.size();
vector<int> vec;
// 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if (node->left) que.push(node->left);//注意入栈条件
if (node->right) que.push(node->right);
}
result.push_back(vec);
}
return result;
}
|