LeetCode--力扣算法整理总结
1、双指针
双指针法是最简单的一种了,大概就是通过两个变量,作为数组或者字符串的下标索引进行操作
双指针一共分为三种,分为快慢指针、左右指针、滑动窗口
左右指针一般就是while(left<right)
................................
力扣3题:给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
................................
双指针法滑动窗口:两个变量为字符串起始位置索引,一个是左指针,一个右指针,当左指针指向内容和右指针指向的内容不一样,右指针++,
一样的话,左指针等于右指针,右指针++。期间左右指针指向的内容是否相等用了一个循环去判断
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if(s.size() <= 1)
return s.size();
int res = 0;
int len = 1;
int left = 0;
int right = 1;
while(right < s.size())
{
len = 1;
int tmp = right;
for(int i=left;i<tmp;i++)
{
if(s[right] == s[i])
{
left++;
right = left+1;
break;
}
if(s[right] != s[i])
len++;
if(i == right-1 && s[right] != s[i])
right++;
}
res = max(res,len);
}
return res;
}
};
................................
力扣15题:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
................................
通过固定一个left,找另外两个数。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
if(nums.size() < 3)
return {};
vector<vector<int>> res;
sort(nums.begin(),nums.end());
int left = 0 , right = nums.size()-1 , point = 1;
for(int i=0;i<nums.size();i++)
{
if(nums[0] > 0)
return {};
left = i;
point = i+1;
right = nums.size()-1;
if(i>0 && nums[i] == nums[i-1])
continue;
while(point < right)
{
if(nums[left] + nums[point] + nums[right] == 0)
{
vector<int> tmp;
tmp.push_back(nums[left]);
tmp.push_back(nums[point]);
tmp.push_back(nums[right]);
res.push_back(tmp);
point++;
right--;
continue;
}
if(nums[left] + nums[point] + nums[right] > 0)
right--;
if(nums[left] + nums[point] + nums[right] < 0)
point++;
}
}
res.erase(unique(res.begin(),res.end()),res.end());
return res;
}
};
2、链表
链表也算比较简单的数据结构,在LeetCode中题目中传进来的头结点都是直接指向链表头的。比如一个链表1->2->3->4.在LeetCode中的head头结点都是直接指向1这个节点的。而不是head->next才指向1.
链表的题一般都涉及链表的翻转啊,链表的指向的迭代问题。一些环形链表的判断也涉及双指针快慢指针的事情。
经典的题就是链表翻转、合并排序链表、两两反转链表、环形链表。
一般处理链表问题,都会新建一个dummy的头结点,这样更改了head依旧可以返回链表
链表问题常规思路:反转链表,遍历链表,写代码时注意用到哪个的next就要判断是否为空while(cur->next && cur->next->next)
...................................
力扣206题:反转一个单链表。
...................................
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *newhead = NULL;
while(head)
{
ListNode *t = head->next;
head->next = newhead;
newhead = head;
head = t;
}
return newhead;
}
};
...............................................
力扣23题:给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
...............................................
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
ListNode *res = NULL;
for(int i=0;i<lists.size();++i)
{
res = mergeTwoList(lists[i], res);
}
return res;
}
ListNode* mergeTwoList(ListNode* l1,ListNode* l2)
{
ListNode* dummy = new ListNode(-1), *cur=dummy;
while(l1 && l2)
{
if(l1->val <= l2->val)
{
cur->next = l1;
l1 = l1->next;
}
else
{
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
cur->next = l1?l1:l2;
return dummy->next;
}
};
...................................
给定一个链表判断是否有环
...................................
class Solution {
public:
bool hasCycle(ListNode *head) {
if (head == nullptr || head->next == nullptr)
return false;
ListNode *low = head;
ListNode *fast = head;
while(fast!=NULL && fast->next!=NULL )
{
fast = fast->next->next;
low = low->next;
if(fast == low)
return true;
}
return false;
}
};
........................
两两交换链表中的节点
.......................
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy = new ListNode(-1),*cur = dummy;
dummy->next = head;
while(cur->next && cur->next->next)
{
ListNode *t = cur->next->next;
cur->next->next = t->next;
t->next = cur->next;
cur->next = t;
cur = cur->next->next;
}
return dummy->next;
}
};
3、单调栈
单调栈分为单调递增栈和单调递减栈
1.1. 单调递增栈即栈内元素保持单调递增的栈
1.2. 同理单调递减栈即栈内元素保持单调递减的栈
操作规则(下面都以单调递增栈为例)
2.1. 如果新的元素比栈顶元素大,就入栈
2.2. 如果新的元素较小,那就一直把栈内元素弹出来,直到栈顶比新元素小
加入这样一个规则之后,会有什么效果
3.1. 栈内的元素是递增的
3.2. 当元素出栈时,说明这个新元素是出栈元素向后找第一个比其小的元素
代码模板
stack<int> st;
for(int i = 0; i < nums.size(); i++)
{
while(!st.empty() && st.top() > nums[i])
{
st.pop();
}
st.push(nums[i]);
}
......................................
力扣84题:给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
......................................
class Solution {
public:
int largestRectangleArea(vector<int> &heights) {
unsigned long size = heights.size();
if (size == 1)
return heights[0];
int res = 0;
stack<int> stk;
for (int i = 0; i < size; ++i)
{
while (!stk.empty() && heights[stk.top()] > heights[i])
{
int length = heights[stk.top()];
stk.pop();
int weight = i;
if (!stk.empty())
{
weight = i - stk.top() - 1;
}
res = max(res, length * weight);
}
stk.push(i);
}
while (!stk.empty())
{
int length = heights[stk.top()];
stk.pop();
int weight = size;
if (!stk.empty())
{
weight = size - stk.top() - 1;
}
res = max(res, length * weight);
}
return res;
}
};
4、树
树的问题一般解法都很一般。
第一种解法就是递归,递归左子树,右子树。
第二种解法就是遍历:设计前序遍历、中序遍历、后序遍历、层次遍历
第三种就是一般碰见搜索二叉树,都会用到中序遍历的性质,搜索二叉树中序遍历后得到的内容是升序排列的
第四种就是深度优先搜索,遍历所有的路径,比如题目路径总和。
.....................
二叉树的中序遍历
.....................
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
func(root,res);
return res;
}
void func(TreeNode *root,vector<int> &res)
{
if (root)
{
func(root->left,res);
res.push_back(root->val);
func(root->right,res);
}
}
};
..................
求二叉树最大深度
.................
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == NULL)
return 0;
int left = maxDepth(root->left);
int right = maxDepth(root->right);
return left>right?left+1:right+1;
}
};
..................
二叉树层次遍历
.................
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
if (!root)
return {};
queue<TreeNode*> q{{root}};
vector<vector<int>> res;
while(!q.empty())
{
vector<int> orderLine;
for (int i = q.size(); i >0 ; --i)//注意这里把q.size()放在初始化,因为循环中改变了q
{
TreeNode* t=q.front();
q.pop();
orderLine.push_back(t->val);
if(t->left)
q.push(t->left);
if(t->right)
q.push(t->right);
}
res.push_back(orderLine);
}
return res;
}
};
.......................
对称二叉树
.......................
class Solution {
public:
bool mySolution(TreeNode* root,TreeNode* root2) {
if (root == NULL && root2 == NULL)
return true;
if (root == NULL && root2 != NULL || root != NULL && root2 == NULL)
return false;
if (root->val != root2->val)
return false;
return mySolution(root->left,root2->right) && mySolution(root->right,root2->left);
}
bool isSymmetric(TreeNode* root)
{
return mySolution(root, root);
}
};
............................
验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
............................
class Solution {
public:
bool isValidBST(TreeNode* root) {
vector<int> res;
orderTravel(root, res);
for (int i=0;i<res.size()-1;i++)
{
if (res[i] >= res[i+1])
return false;
}
return true;
}
void orderTravel(TreeNode *root, vector<int>& res)
{
if (root)
{
orderTravel(root->left, res);
res.push_back(root->val);
orderTravel(root->right, res);
}
}
};
5、深度优先搜索
...................
路径总和:给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
...................
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void dfs(TreeNode* root, int sum)
{
if (root == nullptr)
{
return;
}
path.push_back(root->val);
sum -= root->val;
if (root->left == nullptr && root->right == nullptr && sum == 0)
{
res.push_back(path);
}
dfs(root->left, sum);
dfs(root->right, sum);
path.pop_back();
}
vector<vector<int>> pathSum(TreeNode* root, int sum) {
dfs(root, sum);
return res;
}
};
6、广度优先搜索
BFS一般都是用队列去做。BSF找到的路径一定是最短的。BFS也是有算法框架,代码模板的。
其中的set是Java中的哈希表,对应的就是C++中的unordered_map
二叉树的层次遍历其实也算一种广度优先算法。跟这个模板写的都差不多。
力扣111题:二叉树的最小深度
class Solution {
public:
int minDepth(TreeNode *root) {
if (root == nullptr) {
return 0;
}
queue<pair<TreeNode *, int> > que;
que.emplace(root, 1);
while (!que.empty()) {
TreeNode *node = que.front().first;
int depth = que.front().second;
que.pop();
if (node->left == nullptr && node->right == nullptr) {
return depth;
}
if (node->left != nullptr) {
que.emplace(node->left, depth + 1);
}
if (node->right != nullptr) {
que.emplace(node->right, depth + 1);
}
}
return 0;
}
};
力扣994:腐烂的橘子
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int dirs[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
int row = grid.size();
int colum = grid[0].size();
queue<pair<int,int>> q;
int count = 0;
int time = 0;
for (int i=0;i<row;i++) {
for (int j=0;j<colum;j++) {
if (grid[i][j] == 1)
count++;
else if (grid[i][j] == 2)
q.push({i,j});
}
}
if (count == 0)
return 0;
while (!q.empty()) {
time++;
for (int j=q.size();j>0;j--) {
auto cur = q.front();
q.pop();
for (int k=0;k<4;k++) {
int x = cur.first + dirs[k][0];
int y = cur.second + dirs[k][1];
if (x<0 || x>row-1 || y<0 || y>colum-1 || grid[x][y] == 0)
continue;
if (grid[x][y] == 1) {
count--;
grid[x][y] = 2;
q.push({x,y});
}
}
if (count == 0)
return time;
}
}
return -1;
}
};
力扣752:打开转盘锁
class Solution {
public:
string plusOne(string str,int i)//数字+1
{
str[i] = str[i]=='9'?'0':str[i]+1;
return str;
}
string downOne(string str,int i)//数字-1
{
str[i] = str[i]=='0'?'9':str[i]-1;
return str;
}
int openLock(vector<string>& deadends, string target) {
unordered_set<string>deadset(deadends.begin(),deadends.end());//死亡密码
unordered_set<string>visited;//走过的密码
visited.insert("0000");
queue<string>q;//当前的密码
q.push("0000");
int step=0;
while(!q.empty())
{
int size = q.size();//当前这一批次,广度优先遍历,一批(一层)一批的走
for(int i=0;i<size;i++)
{
string cur = q.front();//这一批次,当前密码
q.pop();
if(deadset.count(cur)) continue; //在死亡密码中,换下一个
if(cur==target) return step; //到达目标,结束
for(int i=0;i<4;i++) //每次转动,有(上、下)*4 种可能
{
string up = plusOne(cur,i); //向上
if(!visited.count(up)) //没有走过
{
q.push(up); //放入当前密码(下一批次计算)
visited.insert(up); //放入走过密码
}
string down = downOne(cur,i); //向下
if(!visited.count(down)) //没有走过
{
q.push(down);
visited.insert(down);
}
}
}
step++;
}
return -1;
}
};
7、回溯法
回溯法和DFS其实是一样的。
回溯法主要注意路径,选择列表做选择,回溯递归,撤销选择
回溯法的代码模板
vector<vector<int> res;//储存所有的路径
vector<vector<int> DFS(vector<int> &nums)//主函数
{
vector<int> track;
backtrace(nums,track,……);
return res;
}
void backtrack(vector<int> &nums,vector<int> &track)//回溯函数
{
//排除不合法的选择
if()//如果满足条件,更新res
{
res.push_back(track);
return;
}
for()//开始循环
{
track.push_back();//做选择
backtrack();//递归回溯
track.pop_back();//撤销选择
}
}
力扣46题:全排列
class Solution {
public:
vector<vector<int>> permute(vector<int>& num) {
vector<vector<int>> res;
vector<int> out, visited(num.size(), 0);
permuteDFS(num, visited, out, res);
return res;
}
void permuteDFS(vector<int>& num, vector<int>& visited, vector<int>& out, vector<vector<int>>& res) {
if (out.size() == num.size()) {res.push_back(out); return;}
for (int i = 0; i < num.size(); ++i) {
if (visited[i] == 1) continue;
visited[i] = 1;
out.push_back(num[i]);
permuteDFS(num, visited, out, res);
out.pop_back();
visited[i] = 0;
}
}
};
力扣77题:组合
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> res;
vector<int> out;
helper(n, k, 1, out, res);
return res;
}
void helper(int n, int k, int level, vector<int>& out, vector<vector<int>>& res) {
if (out.size() == k) {res.push_back(out); return;}
for (int i = level; i <= n; ++i) {
out.push_back(i);
helper(n, k, i + 1, out, res);
out.pop_back();
}
}
};
力扣78:子集
class Solution {
public:
vector<vector<int> > subsets(vector<int> &S) {
vector<vector<int> > res;
vector<int> out;
sort(S.begin(), S.end());
getSubsets(S, 0, out, res);
return res;
}
void getSubsets(vector<int> &S, int pos, vector<int> &out, vector<vector<int> > &res) {
res.push_back(out);
for (int i = pos; i < S.size(); ++i) {
out.push_back(S[i]);
getSubsets(S, i + 1, out, res);
out.pop_back();
}
}
};
8、动态规划
最重点的就是找状态转移方程
动态规划问题解题步骤:
1.确定base case
2.确定状态,也就是原问题和子问题中的变量。比如力扣凑零钱问题中的目标金额就是这题的状态
3.确定选择,也就是导致状态产生变化的行为。每次选择硬币就是在更改目标金额,这就是选择
4.确定dp函数或数组的定义
代码模板:
#初始化base case
dp[0][……][……] = base case;
#进行状态转移
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
状态n………………
if()#做选择(排除子问题无解的情况,如果没有就不写这行话)
dp[状态1][状态2] = 求最值(选择1,选择2,……);
关于子序列的问题的模板
子序列问题有两种定义dp数组的思路
第一种:
一维数组的dp:
含义是:在子数组nums[0……i]中,以nums[i]为结尾的(最长递增子序列的长度为dp[i],以nums[i]为结尾的最大子数组和为nums[i])
第二种:
二维数组的dp:
在涉及两个字符串或数组时含义是:在子数组nums1[0……i]和nums2[0……i]中,要求的子序列(最长公共子序列)长度为dp[i][j]
在只涉及一个字符串或数组时含义是:在子数组nums[0……i]中,要求的子序列(最长回文子序列)长度为dp[i][j](我比较少用这个)
力扣332题:零钱兑换
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount+1,amount+1);
dp[0] = 0;
for(int i=1;i<amount+1;i++)
{
for(int j=0;j<coins.size();j++)
{
if(i-coins[j] >= 0)
dp[i] = min(dp[i],dp[i-coins[j]]+1);
}
}
return dp[amount]>amount?-1:dp[amount];
}
};
力扣300:最长递增子序列
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 0)
return 0;
if(nums.size() == 1)
return 1;
vector<int> dp(nums.size(),1);
for(int i=0;i<nums.size();i++)
{
for(int j=0;j<i;j++)
{
if(nums[j] < nums[i])
dp[i] = max(dp[i],dp[j]+1);
}
}
return *max_element(dp.begin(),dp.end());
}
};
力扣1143题:最长公共子序列
class Solution {
public:
int longestCommonSubsequence(string text1, string text2)
{
int l1 = text1.size();
int l2 = text2.size();
vector<vector<int>> dp (l1+1,vector<int>(l2+1,0));
int i, j;
for(i = 0; i <= l1; i++){ //边界状态
dp[i][0] = 0;
}
for(i = 0; i <= l2; i++){
dp[0][i] = 0;
}
for(i = 1; i <= l1; i++){ //注意下标从1开始
for(j = 1; j <= l2; j++){
if(text1[i-1] == text2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1;
}
else{
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[l1][l2];
}
};
9、分治法
10、二分法
二分法很简单
int binarySearch(vector<int> nums,int target)
{
int left = 0;
int right = nums.size()-1;
while (left <= right)
{
int mid = left + (right - left)/2;//注意这里,和(right+left)/2一样,但是一定程度上可以防止溢出
if(nums[mid] == target)
return mid;
if(nums[mid] < target)
left = mid + 1;
if(nums[mid] == target)
right = mid - 1;
}
return -1;
}
11、贪心算法
12、其他一些小技巧和操作
作者: mengchao
出处:https://www.cnblogs.com/meng-chao/p/14060582.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)