LeetCode题目答案及理解汇总(持续更新)
面试算法题
dfs相关
全排列

#include<bits/stdc++.h>
using namespace std;
const int N = 10;
//用一个path数组来存储每次到底层的路径
int path[N];
//用一个布尔数组来存储每次已经遍历的点,默认是false
bool st[N];
int n;
//u表示当前的层数
void dfs(int u)
{
//当已经到达最底层了,溯回并输出路径
if( u == n )
{
for(int i = 0 ; i < n ; i++) printf("%d " , path[i] );
//作用跟printf("%s\n",s),默认帮你换行
puts("");
//溯回上一层
return;
}
else
{
//这里从第一个数开始循环
for(int i = 1; i <= n ; i++)
{
//如果该数字未被访问,就使用
if( !st[i] )
{
path[u] = i;
//标记第i个数已经被使用
st[i] = true;
//进入下一层
dfs( u + 1 );
//还原现场
st[i] = false;
}
}
}
}
int main()
{
cin >> n;
dfs(0);
return 0;
}
n皇后问题
//y总第一个解法,按列来枚举`
#include<bits/stdc++.h>
using namespace std;
const int N = 20;
char g[N][N];
bool col[N] , dg[N] , udg[N];
int n;
void dfs(int u)
{
if( u == n )
{
for(int i = 0 ; i < n ; i++) puts( g[i] );
puts("");
return;
}
else
{
for(int i = 0 ; i < n ; i++)
{
//当满足列没有皇后,对角线没有皇后,反对角线没有皇后
if( !col[i] && !dg[u + i] && !udg[n - i + u] )
{
//这一格子放置皇后
g[u][i] = 'Q';
//此时这一列,这一对角线,这一反对角线就不能放置皇后了
col[i] = dg[u + i] = udg[n - i + u] = true;
//递归到下一层
dfs(u + 1);
//递归出来后返回上一层,并还原现场
col[i] = dg[u + i] = udg[n - i + u] = false;
//把皇后给做掉
g[u][i] = '.';
}
}
}
}
int main()
{
cin >> n;
for(int i = 0 ; i < n ; i ++)
{
for(int j = 0 ; j < n; j++) g[i][j] = '.';
}
dfs(0);
return 0;
}
//第二种方法,遍历每一个格子
#include<bits/stdc++.h>
using namespace std;
const int N = 20;
char g[N][N];
bool row[N] , col[N] , dg[N] , udg[N];
int n;
//表示第x行第y列,放置了s个皇后
void dfs(int x , int y , int s)
{
//当当前x行已经到达边界,转到下一行,列数归零
if( y == n ) y = 0 , x++;
//当到最后一个行,如果此时已经存在了n个皇后,就输出结果
//为什么不判断y呢?因为最后一行只能放一个皇后
if( x == n )
{
if( s == n )
{
for(int i = 0 ; i < n ; i++) puts( g[i] );
puts("");
}
return;
}
//放皇后
if( !row[x] && !col[y] && !dg[y + x] && !udg[y - x + n] )
{
g[x][y] = 'Q';
row[x] = col[y] = dg[x + y] = udg[y - x + n] = true;
dfs(x , y + 1 , s + 1 );
row[x] = col[y] = dg[x + y] = udg[y - x + n] = false;
g[x][y] = '.';
}
//不放置皇后
dfs( x , y + 1 , s );
}
int main()
{
cin >> n;
for(int i = 0 ; i < n ; i ++)
{
for(int j = 0 ; j < n; j++) g[i][j] = '.';
}
dfs(0 , 0 , 0);
return 0;
}
子集
解题思路:
1.DFS 和回溯算法区别
DFS 是一个劲的往某一个方向搜索,而回溯算法建立在 DFS 基础之上的,但不同的是在搜索过程中,达到结束条件后,恢复状态,回溯上一层,再次搜索。因此回溯算法与 DFS 的区别就是有无状态重置
2.何时使用回溯算法
当问题需要 "回头",以此来查找出所有的解的时候,使用回溯算法。即满足结束条件或者发现不是正确路径的时候(走不通),要撤销选择,回退到上一个状态,继续尝试,直到找出所有解为止
3.怎么样写回溯算法(从上而下,※代表难点,根据题目而变化)
①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
②根据题意,确立结束条件
③找准选择列表(与函数参数相关),与第一步紧密关联※
④判断是否需要剪枝
⑤作出选择,递归调用,进入下一层
⑥撤销选择
4.回溯问题的类型
这里先给出,我总结的回溯问题类型,并给出相应的 leetcode题目(一直更新),然后再说如何去编写。特别关注搜索类型的,搜索类的搞懂,你就真的搞懂回溯算法了,是前面两类是基础,帮助你培养思维
类型 题目链接
子集、组合:子集、子集 II、组合、组合总和、组合总和 II
全排列:全排列、全排列 II、字符串的全排列、字母大小写全排列
搜索:解数独、单词搜索、N皇后、分割回文串、二进制手表
注意:子集、组合与排列是不同性质的概念。子集、组合是无关顺序的,而排列是和元素顺序有关的,如 [1,2] 和 [2,1] 是同一个组合(子集),但 [1,2] 和 [2,1] 是两种不一样的排列!!!!因此被分为两类问题
5.回到子集、组合类型问题上来(ABC 三道例题)
A、 子集 - 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
解题步骤如下
①递归树
观察上图可得,选择列表里的数,都是选择路径(红色框)后面的数,比如[1]这条路径,他后面的选择列表只有"2、3",[2]这条路径后面只有"3"这个选择,那么这个时候,就应该使用一个参数start,来标识当前的选择列表的起始位置。也就是标识每一层的状态,因此被形象的称为"状态变量",最终函数签名如下
C++
//nums为题目中的给的数组
//path为路径结果,要把每一条 path 加入结果集
void backtrack(vector<int>nums,vector<int>&path,int start)
②找结束条件
此题非常特殊,所有路径都应该加入结果集,所以不存在结束条件。或者说当 start 参数越过数组边界的时候,程序就自己跳过下一层递归了,因此不需要手写结束条件,直接加入结果集
C++
**res为结果集,是全局变量vector<vector<int>>res,到时候要返回的
res.push_back(path);//把每一条路径加入结果集
③找选择列表
在①中已经提到过了,子集问题的选择列表,是上一条选择路径之后的数,即
C++
for(int i=start;i<nums.size();i++)
④判断是否需要剪枝
从递归树中看到,路径没有重复的,也没有不符合条件的,所以不需要剪枝
⑤做出选择(即for 循环里面的)
C++
void backtrack(vector<int>nums,vector<int>&path,int start)
{
for(int i=start;i<nums.size();i++)
{
path.push_back(nums[i]);//做出选择
backtrack(nums,path,i+1);//递归进入下一层,注意i+1,标识下一个选择列表的开始位置,最重要的一步
}
}
⑤撤销选择
整体的 backtrack 函数如下
C++
class Solution {
public:
vector<int> path;
vector<vector<int>> res;
void backtrack(vector<int>nums,vector<int>path,int start)
{
res.push_back(path);
for(int i=start;i<nums.size();i++)
{
path.push_back(nums[i]);//做出选择
backtrack(nums,path,i+1);//递归进入下一层,注意i+1,标识下一个选择列表的开始位置,最重要的一步
path.pop_back();//撤销选择
}
}
vector<vector<int>> subsets(vector<int>& nums) {
backtrack(nums,path,0);
return res;
}
};
完整代码:
class Solution {
public:
vector<int> t;
vector<vector<int>> ans;
void dfs(int cur, vector<int>& nums) {
if (cur == nums.size()) {
ans.push_back(t);
return;
}
t.push_back(nums[cur]);
dfs(cur + 1, nums);
t.pop_back();
dfs(cur + 1, nums);
}
vector<vector<int>> subsets(vector<int>& nums) {
dfs(0, nums);
return ans;
}
};
B、子集 II(剪枝思想)--问题描述:
给定一个可能 包含重复元素 的整数数组 nums,返回该数组所有可能的子集(幂集)。
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
解题步骤
①递归树
可以发现,树中出现了大量重复的集合,②和③和第一个问题一样,不再赘述,我们直接看第四步
④判断是否需要剪枝,需要先对数组排序,使用排序函数 sort(nums.begin(),nums.end())
显然我们需要去除重复的集合,即需要剪枝,把递归树上的某些分支剪掉。那么应去除哪些分支呢?又该如何编码呢?
观察上图不难发现,应该去除当前选择列表中,与上一个数重复的那个数,引出的分支,如 “2,2” 这个选择列表,第二个 “2” 是最后重复的,应该去除这个 “2” 引出的分支
(去除图中红色大框中的分支)
编码呢,刚刚说到是 “去除当前选择列表中,与上一个数重复的那个数,引出的分支”,说明当前列表最少有两个数,当i>start时,做选择的之前,比较一下当前数,与上一个数 (i-1) 是不是相同,相同则 continue,
C++
void backtrack(vector<int>& nums,vector<int>&path,int start)
{
res.push_back(path);
for(int i=start;i<nums.size();i++)
{
if(i>start&&nums[i]==nums[i-1])//剪枝去重
continue;
}
}
⑤做出选择
C++
void backtrack(vector<int>& nums,vector<int>&path,int start)
{
res.push_back(path);
for(int i=start;i<nums.size();i++)
{
if(i>start&&nums[i]==nums[i-1])//剪枝去重
continue;
temp.push_back(nums[i]);
backtrack(nums,path,i+1);
}
}
⑥撤销选择
整体的backtrack函数如下
C++
** sort(nums.begin(),nums.end());
void backtrack(vector<int>& nums,vector<int>&path,int start)
{
res.push_back(path);
for(int i=start;i<nums.size();i++)
{
if(i>start&&nums[i]==nums[i-1])//剪枝去重
continue;
temp.push_back(nums[i]);
backtrack(nums,path,i+1);
temp.pop_back();
}
}
完整代码:
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {
result.push_back(path);
for (int i = startIndex; i < nums.size(); i++) {
// used[i - 1] == true,说明同一树支candidates[i - 1]使用过
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
// 而我们要对同一树层使用过的元素进行跳过
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
path.push_back(nums[i]);
used[i] = true;
backtracking(nums, i + 1, used);
used[i] = false;
path.pop_back();
}
}
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end()); // 去重需要排序
backtracking(nums, 0, used);
return result;
}
};
C、组合总和 - 问题描述
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
输入: candidates = [1,2,3], target = 3,
所求解集为:
[
[1,1,1],
[1,2],
[3]
]
解题步骤
①递归树
(绿色箭头上面的是路径,红色框[]则为结果,黄色框为选择列表)
从上图看出,组合问题和子集问题一样,1,2 和 2,1 `是同一个组合,因此 需要引入start参数标识,每个状态中选择列表的起始位置。另外,每个状态还需要一个 sum 变量,来记录当前路径的和,函数签名如下
C++
void backtrack(vector
②找结束条件
由题意可得,当路径总和等于 target 时候,就应该把路径加入结果集,并 return
C++
if(target==sum)
{
res.push_back(path);
return;
}
③找选择列表
C++
for(int i=start;i<nums.size();i++)
④判断是否需要剪枝
从①中的递归树中发现,当前状态的sum大于target的时候就应该剪枝,不用再递归下去了
C++
for(int i=start;i<nums.size();i++)
{
if(sum>target)//剪枝
continue;
}
⑤做出选择
题中说数可以无限次被选择,那么 i 就不用 +1 。即下一层的选择列表,从自身开始。并且要更新当前状态的sum
C++
for(int i=start;i<nums.size();i++)
{
if(sum>target)
continue;
path.push_back(nums[i]);
backtrack(nums,path,i,sum+nums[i],target);//i不用+1(重复利用),并更新当前状态的sum
}
⑤撤销选择
整体的 backtrack 函数如下
C++
void backtrack(vector
{
for(int i=start;i<nums.size();i++)
{
if(sum>target)
continue;
path.push_back(nums[i]);
backtrack(nums,path,i,sum+nums[i],target);//更新i和当前状态的sum
pacht.pop_back();
}
}
总结:子集、组合类问题,关键是用一个 start 参数来控制选择列表!!最后回溯六步走:
①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
②根据题意,确立结束条件
③找准选择列表(与函数参数相关),与第一步紧密关联※
④判断是否需要剪枝
⑤作出选择,递归调用,进入下一层
⑥撤销选择
CodeTope测试
无重复字符的最长字串
class Solution {
public:
int lengthOfLongestSubstring(string s) {
string str;
int i,j,max=0,n=s.length();
for(i=0;i<n;i++){
for(j=i;j<n;j++){
if(str.find(s[j])==string::npos)str=str+s[j];
else break;
}
max=(max>str.length())?max:str.length();
str="";
}
return max;
}
};
有效的括号
class Solution {
public:
bool isValid(string s) {
int n = s.size();
if (n % 2 == 1) {
return false;
}
unordered_map<char, char> pairs = {
{')', '('},
{']', '['},
{'}', '{'}
};
stack<char> stk;
for (char ch: s) {
if (pairs.count(ch)) {
if (stk.empty() || stk.top() != pairs[ch]) {
return false;
}
stk.pop();
}
else {
stk.push(ch);
}
}
return stk.empty();
}
};
两数之和
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int i=0,j;
vector<int> ans;
for(i=0;i<nums.size();i++){
for(j=i+1;j<nums.size();j++){
if(nums[i]+nums[j]==target){
ans.push_back(i);
ans.push_back(j);
break;
}
}
}
return ans;
}
};
(自己写的)
反转链表(迭代)
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
最长回文
class Solution {
public:
string longestPalindrome(string s) {
int n=s.size();
string ans;
int i=0,j=0,len=0;
for(i=0;i<=n;i++){
for(j=0;j<=i;j++){
if(s[i-j]==s[i+j]&&(j*2+1)>len){
len=j*2+1;
ans=s.substr(i-j,i+j+1);
}
}
for(j=0;j<=i;j++){
if(s[i-j]==s[i+j+1]&&(j*2+2)>len){
len=j*2+2;
ans=s.substr(i-j,i+j+2);
}
}
}
return ans;
}
};
为什么不对???
class Solution {
public:
string longestPalindrome(string s) {
int len=s.size();
if(len==0||len==1)
return s;
int start=0;//记录回文子串起始位置
int end=0;//记录回文子串终止位置
int mlen=0;//记录最大回文子串的长度
for(int i=0;i<len;i++)
{
int len1=expendaroundcenter(s,i,i);//一个元素为中心
int len2=expendaroundcenter(s,i,i+1);//两个元素为中心
mlen=max(max(len1,len2),mlen);
if(mlen>end-start+1)
{
start=i-(mlen-1)/2;
end=i+mlen/2;
}
}
return s.substr(start,mlen);
//该函数的意思是获取从start开始长度为mlen长度的字符串
}
private:
int expendaroundcenter(string s,int left,int right)
//计算以left和right为中心的回文串长度
{
int L=left;
int R=right;
while(L>=0 && R<s.length() && s[R]==s[L])
{
L--;
R++;
}
return R-L-1;
}
};
环形链表
快慢指针
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (fast == nullptr || fast->next == nullptr) {
return false;
}
slow = slow->next;
fast = fast->next->next; //总有一天能追上
}
return true;
}
};
哈希表
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode*> seen;
while (head != nullptr) {
if (seen.count(head)) {
return true;
}
seen.insert(head);
head = head->next;
}
return false;
}
};
猜数字游戏
class Solution {
public:
string getHint(string secret, string guess) {
int bulls = 0;
vector<int> cntS(10), cntG(10);
for (int i = 0; i < secret.length(); ++i) {
if (secret[i] == guess[i]) {
++bulls;
} else {
++cntS[secret[i] - '0'];
++cntG[guess[i] - '0'];//出现个数
}
}
int cows = 0;
for (int i = 0; i < 10; ++i) {
cows += min(cntS[i], cntG[i]);//最小出现次数
}
return to_string(bulls) + "A" + to_string(cows) + "B";
}
};
最大子数组和
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int pre = 0, maxAns = nums[0];
for (const auto &x: nums) {
pre = max(pre + x, x);
maxAns = max(maxAns, pre);
}
return maxAns;
}
};
合并两个有序数组
偷跑方法
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
for (int i = 0; i != n; ++i) {
nums1[m + i] = nums2[i];
}
sort(nums1.begin(), nums1.end());
}
};
正常方法
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1 = 0, p2 = 0;
int sorted[m + n];
int cur;
while (p1 < m || p2 < n) {
if (p1 == m) {
cur = nums2[p2++];
} else if (p2 == n) {
cur = nums1[p1++];
} else if (nums1[p1] < nums2[p2]) {
cur = nums1[p1++];
} else {
cur = nums2[p2++];
}
sorted[p1 + p2 - 1] = cur;
}
for (int i = 0; i != m + n; ++i) {
nums1[i] = sorted[i];
}
}
};
排序数组(快排)
冒泡排序
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
// bubbleSort
int n = nums.size();
for (int i = 0; i < n - 1; ++i) {
bool flag = false;
for (int j = 0; j < n - 1 - i; ++j) {
if (nums[j] > nums[j + 1]) {
swap(nums[j], nums[j + 1]);
flag = true;
}
}
if (flag == false) break; //无交换,代表当前序列已经最优
}
return nums;
}
};
三数之和
https://leetcode.cn/problems/3sum/
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
// 枚举 a
for (int first = 0; first < n; ++first) {
// 需要和上一次枚举的数不相同
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
// c 对应的指针初始指向数组的最右端
int third = n - 1;
int target = -nums[first];
// 枚举 b
for (int second = first + 1; second < n; ++second) {
// 需要和上一次枚举的数不相同
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// 需要保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target) {
--third;
}
// 如果指针重合,随着 b 后续的增加
// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
if (second == third) {
break;
}
if (nums[second] + nums[third] == target) {
ans.push_back({nums[first], nums[second], nums[third]});
}
}
}
return ans;
}
};
看懂了,但自己写总是没思路,真难受。
大致思路:定义三个指针a,b,c隐含关系:a<b<c
先排序
循环a++,若nums[a]>0则不可能sum=0
c在最右即最大数
b=a+1;b++
c+b>target,需要变小,则c--;c+b<target,需要变大,则b++;
最长公共前缀
https://leetcode.cn/problems/longest-common-prefix/
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1:
输入:strs = ["flower","flow","flight"]
输出:"fl"
示例 2:
输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (!strs.size()) {
return "";
}
int length = strs[0].size();
int count = strs.size();
for (int i = 0; i < length; ++i) {
char c = strs[0][i];
for (int j = 1; j < count; ++j) {
if (i == strs[j].size() || strs[j][i] != c) {
return strs[0].substr(0, i);
}
}
}
return strs[0];
}
};
注意.substring(str.begin(),str.end())和.substr(str.begin(),length)的区别
思路还是比较简单的:每次查询第一个串的一个字符,是否与其他串相同。
二分查找
https://leetcode.cn/problems/binary-search/
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0,right=nums.size()-1,mid=0;
while(left<=right){
mid=(left+right)/2;
if(nums[mid]==target){
return mid;
}
else if(nums[mid]<target)left=mid+1;
else right=mid-1;
}
return -1;
}
};
这题就不多说了,没什么难度。
多数元素
https://leetcode.cn/problems/majority-element/
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:nums = [3,2,3]
输出:3
示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(),nums.end());
int n=nums.size();
return nums[n/2];
}
};
这题也很简单,第一次有了看到题就有完整思路的感觉
当时也想到了暴力搜索,出现次数超过n/2就是多数。
买股票的最佳时机
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int inf = 1e9;
int minprice = inf, maxprofit = 0;
for (int price: prices) {
maxprofit = max(maxprofit, price - minprice);
minprice = min(price, minprice);
}
return maxprofit;
}
};
最开始用暴力,结果时间上没过。
这种方法只遍历一次,比如我打算在第n天卖出,则最大利润肯定是,在(0,n)中选择历史最低点买入。因此只需要一个变量记录并更新历史最低点即可。
相交链表
https://leetcode.cn/problems/intersection-of-two-linked-lists/
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *pa = headA;
ListNode *pb = headB;
while(pa!=pb){
pa=pa==NULL?headB:pa->next;
pb=pb==NULL?headA:pb->next;
}
return pa;
}
};
这是一种巧妙解法:
原理是:指针A、B同时开始,若路程不同,则短的一方(假设为B)会指向长的一方A起始,此时再过gap(差距)A也遇到NULL跳转到B起始,神奇的事情发生了,两者同步了:与NULL的距离相同了,齐头并进,若有相交,AB必定相遇。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set<ListNode *> visited;
ListNode *temp = headA;
while (temp != nullptr) {
visited.insert(temp);
temp = temp->next;
}
temp = headB;
while (temp != nullptr) {
if (visited.count(temp)) {
return temp;
}
temp = temp->next;
}
return nullptr;
}
};
这是暴力且直接的哈希表解法
先记录一条链上所有,然后第二条对比。
环形链表II
https://leetcode.cn/problems/linked-list-cycle-ii/
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode*> seen;
while (head != nullptr) {
if (seen.count(head)) {
return head;
}
seen.insert(head);
head = head->next;
}
return 0;
}
};
好像做过,直接用哈希表
环形链表
https://leetcode.cn/problems/linked-list-cycle/
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode*> seen;
while (head != nullptr) {
if (seen.count(head)) {
return true;
}
seen.insert(head);
head = head->next;
}
return false;
}
};
就是这个
验证IP地址
https://leetcode.cn/problems/validate-ip-address/
给定一个字符串 queryIP。如果是有效的 IPv4 地址,返回 "IPv4" ;如果是有效的 IPv6 地址,返回 "IPv6" ;如果不是上述类型的 IP 地址,返回 "Neither" 。
有效的IPv4地址 是 “x1.x2.x3.x4” 形式的IP地址。 其中 0 <= xi <= 255 且 xi 不能包含 前导零。例如: “192.168.1.1” 、 “192.168.1.0” 为有效IPv4地址, “192.168.01.1” 为无效IPv4地址; “192.168.1.00” 、 “192.168@1.1” 为无效IPv4地址。
class Solution {
public:
string validIPAddress(string queryIP) {
if (queryIP.find('.') != string::npos) {
// IPv4
int last = -1;
for (int i = 0; i < 4; ++i) {
int cur = (i == 3 ? queryIP.size() : queryIP.find('.', last + 1));
if (cur == string::npos) {
return "Neither";
}
if (cur - last - 1 < 1 || cur - last - 1 > 3) {
return "Neither";
}
int addr = 0;
for (int j = last + 1; j < cur; ++j) {
if (!isdigit(queryIP[j])) {
return "Neither";
}
addr = addr * 10 + (queryIP[j] - '0');
}
if (addr > 255) {
return "Neither";
}
if (addr > 0 && queryIP[last + 1] == '0') {
return "Neither";
}
if (addr == 0 && cur - last - 1 > 1) {
return "Neither";
}
last = cur;
}
return "IPv4";
}
else {
// IPv6
int last = -1;
for (int i = 0; i < 8; ++i) {
int cur = (i == 7 ? queryIP.size() : queryIP.find(':', last + 1));
if (cur == string::npos) {
return "Neither";
}
if (cur - last - 1 < 1 || cur - last - 1 > 4) {
return "Neither";
}
for (int j = last + 1; j < cur; ++j) {
if (!isdigit(queryIP[j]) && !('a' <= tolower(queryIP[j]) && tolower(queryIP[j]) <= 'f')) {
return "Neither";
}
}
last = cur;
}
return "IPv6";
}
}
};
额,看的题解,还是昏的。
正则看起来简洁,但表达式同样复杂。
用栈实现堆操作
https://leetcode.cn/problems/implement-queue-using-stacks/
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
class MyQueue {
private:
stack<int> inStack, outStack;
void in2out() {
while (!inStack.empty()) {
outStack.push(inStack.top());
inStack.pop();
}
}
public:
MyQueue() {}
void push(int x) {
inStack.push(x);
}
int pop() {
if (outStack.empty()) {
in2out();
}
int x = outStack.top();
outStack.pop();
return x;
}
int peek() {
if (outStack.empty()) {
in2out();
}
return outStack.top();
}
bool empty() {
return inStack.empty() && outStack.empty();
}
};
重点就在于取出最开始入栈元素。用另一个栈调一下方向即可,因为这里,即使后续又push,但pop永远会先把另一个栈中元素取完。
爬楼梯
https://leetcode.cn/problems/climbing-stairs/
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
class Solution {
public:
int climbStairs(int n) {
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
};
这里要分析一下爬楼梯的规律
1层:1;2层:11,2;3层:111,21,12;4层:1111,121,211,22,112是一个斐波那契数列,要到达n层:n-1层+1||n-2层+2
合并两个有序链表
https://leetcode.cn/problems/merge-two-sorted-lists/
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2){
if (list1 == nullptr) {
return list2;
} else if (list2 == nullptr) {
return list1;
} else if (list1->val < list2->val) {
list1->next = mergeTwoLists(list1->next, list2);
return list1;
} else {
list2->next = mergeTwoLists(list1, list2->next);
return list2;
}
}
};
比较巧妙的递归,我反正想不出来。。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* preHead = new ListNode(-1);
ListNode* prev = preHead;
while (l1 != nullptr && l2 != nullptr) {
if (l1->val < l2->val) {
prev->next = l1;
l1 = l1->next;
} else {
prev->next = l2;
l2 = l2->next;
}
prev = prev->next;
}
prev->next = l1 == nullptr ? l2 : l1;
return preHead->next;
}
};
我一开始思路就是这样,但跑不起来,少了ListNode* preHead = new ListNode(-1);
所以返回的链表都少了表头
2022·9·10
感觉篇幅有点长了,所以还是用时间分割一下
数组中第K个最大元素
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入: [3,2,1,5,6,4], k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
sort(nums.begin(),nums.end());
int i,n=nums.size();
for(i=n-1;;i--){
k--;
if(k==0)return nums[i];
}
return 0;
}
};
看中等题,还以为用sort会超时,没想到竟然能通。。
看了一下官方解法:用一个大小为K的堆,每次读一个数,插入堆,溢出则弹出最小的那个
全排列
https://leetcode.cn/problems/permutations/
在dfs中已经做过了,这里不多说
删除排序链表中重复元素
https://leetcode.cn/problems/remove-duplicates-from-sorted-list/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) {
return head;
}
ListNode* ans = head;
while (ans->next) {
if (ans->val == ans->next->val)ans->next = ans->next->next;
else ans = ans->next;
}
return head;
}
};
第一次做,思路都对,结果却不通过。看了解答才发现,应该返回head,因为ans会指向->null的结点。。属实是傻了。
最大数
https://leetcode.cn/problems/largest-number/
给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
示例 1:
输入:nums = [10,2]
输出:"210"
class Solution {
private:
static bool cmp(const int& a, const int& b) {
string sa = to_string(a);
string sb = to_string(b);
return sa + sb > sb + sa;
}
public:
string largestNumber(vector<int>& nums) {
string ans;
sort(nums.begin(), nums.end(), cmp);
for (int& t : nums) {
ans += to_string(t);
}
if (ans[0] == '0') { // 不能是00000
return "0";
}
return ans;
}
};
这题如果知道了sort()的用法的话会简单很多。sort(nums.begin(), nums.end(), cmp)
结束后,基本就是输出了。
官方解答没有直接用字符相加,更像是对阶,将两数都变成同位,相加作比。
只出现一次的数字
https://leetcode.cn/problems/single-number/
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
class Solution {
public:
int singleNumber(vector<int>& nums) {
sort(nums.begin(),nums.end());
int i,n=nums.size(),same;
if(n==1)return nums[0];
for(i=0;i<n;i++){
if(nums[i]==nums[i+1]){
same=nums[i];
}
if(nums[i]==same)continue;
else return nums[i];
}
return 0;
}
};
要考虑数组只有一个或零个的可能
字符串转换整数
https://leetcode.cn/problems/string-to-integer-atoi/
class Automaton {
string state = "start";
unordered_map<string, vector<string>> table = {
{"start", {"start", "signed", "in_number", "end"}},
{"signed", {"end", "end", "in_number", "end"}},
{"in_number", {"end", "end", "in_number", "end"}},
{"end", {"end", "end", "end", "end"}}
};
int get_col(char c) {
if (isspace(c)) return 0;
if (c == '+' or c == '-') return 1;
if (isdigit(c)) return 2;
return 3;
}
public:
int sign = 1;
long long ans = 0;
void get(char c) {
state = table[state][get_col(c)];
if (state == "in_number") {
ans = ans * 10 + c - '0';
ans = sign == 1 ? min(ans, (long long)INT_MAX) : min(ans, -(long long)INT_MIN);
}
else if (state == "signed")
sign = c == '+' ? 1 : -1;
}
};
class Solution {
public:
int myAtoi(string str) {
Automaton automaton;
for (char c : str)
automaton.get(c);
return automaton.sign * automaton.ans;
}
};
正则还行,这自动机真不会。
自动机题解
2022·9·11
二叉树的中序遍历
https://leetcode.cn/problems/binary-tree-inorder-traversal/
给定一个二叉树的根节点 root
,返回 它的 中序 遍历 。
class Solution {
public:
void dfs(TreeNode* root,vector<int>& res){
if(root==NULL){return;}
dfs(root->left,res);
res.push_back(root->val);
dfs(root->right,res);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
dfs(root,res);
return res;
}
};
自己做,但返回结果都是空,我寻思思路也没错。看题解,结果是void dfs(TreeNode* root,vector<int>& res)
vector
二叉树的层序遍历
https://leetcode.cn/problems/binary-tree-level-order-traversal/
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector <vector <int>> ret;
if (!root) {
return ret;
}
queue <TreeNode*> q;
q.push(root);
while (!q.empty()) {
int currentLevelSize = q.size();
ret.push_back(vector <int> ());
for (int i = 1; i <= currentLevelSize; ++i) {
auto node = q.front(); q.pop();
ret.back().push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
return ret;
}
};
思路和上一道题类似,这里就不展开了
滑动窗口最大值
https://leetcode.cn/problems/sliding-window-maximum/
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
priority_queue<pair<int, int>> q;
for (int i = 0; i < k; ++i) {
q.emplace(nums[i], i);
}
vector<int> ans = {q.top().first};
for (int i = k; i < n; ++i) {
q.emplace(nums[i], i);
while (q.top().second <= i - k) {
q.pop();
}
ans.push_back(q.top().first);
}
return ans;
}
};
知道用队列,但忘接口方法了。
优先队列详解:https://www.cnblogs.com/huashanqingzhu/p/11040390.html
字符串相加
https://leetcode.cn/problems/add-strings/
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。
示例 1:
输入:num1 = "11", num2 = "123"
输出:"134"
class Solution {
public:
string addStrings(string num1, string num2) {
int i = num1.length() - 1, j = num2.length() - 1, add = 0;
string ans = "";
while (i >= 0 || j >= 0 || add != 0) {
int x = i >= 0 ? num1[i] - '0' : 0;
int y = j >= 0 ? num2[j] - '0' : 0;
int result = x + y + add;
ans.push_back('0' + result % 10);
add = result / 10;
i -= 1;
j -= 1;
}
reverse(ans.begin(), ans.end());
return ans;
}
};
扑克牌中顺子
https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/
从若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
示例 1:
输入: [1,2,3,4,5]
输出: True
class Solution {
public:
bool isStraight(vector<int>& nums) {
sort(nums.begin(),nums.end());
int boss = 0;
for(;boss<nums.size();++boss){
if(nums[boss]!=0)
break;
}
int pre = nums[boss];
int count=0;
for(int i=boss+1;i<nums.size();++i){
if(nums[i] != pre+1){
if(nums[i] == pre){
return false;
}
count = count + nums[i] - pre - 1;
}
pre = nums[i];
}
if(count > boss)
return false;
return true;
}
};
模拟:1.模拟手上清牌,可以用排序,这样的话大小王作为0就排在前面,后面的都是正整数牌
2.清点boss牌,有多少张boss牌,这里由于是在多幅牌当中,甚至可能出现五张王
3.对不是boss牌的第一张进行记录,第一种情况:后面一张牌如果是恰好比前一张牌大1,说明是连着的,中间的差值不用记录,因为差值为0。第二种情况:如果后一张牌和前一张牌相等,说明肯定不是顺子,直接返回false。第三种情况:后一张牌比前一张牌大,且大超过1,说明有可能是顺子,这里的话先记录下count,也就是到时候需要用几张boss牌来填充。
4.如果需要的万能牌个数count是大于实际手上的boss牌,那么说明boss牌不够,返回false。如果这个缺口count比较小,可以利用boss牌来填充,返回true。
K个一组翻转链表
https://leetcode.cn/problems/reverse-nodes-in-k-group/
给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
Deque<ListNode> stack = new ArrayDeque<ListNode>();
ListNode dummy = new ListNode(0);
ListNode p = dummy;
while (true) {
int count = 0;
ListNode tmp = head;
while (tmp != null && count < k) {
stack.add(tmp);
tmp = tmp.next;
count++;
}
if (count != k) {
p.next = head;
break;
}
while (!stack.isEmpty()){
p.next = stack.pollLast();
p = p.next;
}
p.next = tmp;
head = tmp;
}
return dummy.next;
}
}
当时没想明白,之后有时间,对着Java写C++的。
2022·9·13
找到实习了,暂不更新