面试算法题

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& nums,vector&path,int start,int sum,int target)

②找结束条件

由题意可得,当路径总和等于 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& nums,vector&path,int start,int sum,int target)

{

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 阶 + 1 阶
  2. 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;
}
};

正则还行,这自动机真不会。

自动机题解

https://leetcode.cn/problems/string-to-integer-atoi/solution/zi-fu-chuan-zhuan-huan-zheng-shu-atoi-by-leetcode-/

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

找到实习了,暂不更新

LeetCode题目答案及理解汇总(持续更新)的更多相关文章

  1. 《WCF技术剖析》博文系列汇总[持续更新中]

    原文:<WCF技术剖析>博文系列汇总[持续更新中] 近半年以来,一直忙于我的第一本WCF专著<WCF技术剖析(卷1)>的写作,一直无暇管理自己的Blog.在<WCF技术剖 ...

  2. 中国.NET:各地微软技术俱乐部汇总(持续更新中...)

    中国.NET:各地微软技术俱乐部汇总(持续更新中...)   本文是转载文,源地址: https://www.cnblogs.com/panchun/p/JLBList.html by ​史记微软. ...

  3. redis日常使用汇总--持续更新

    redis日常使用汇总--持续更新 工作中有较多用到redis的场景,尤其是触及性能优化的方面,传统的缓存策略在处理持久化和多服务间数据共享的问题总是不尽人意,此时引入redis,但redis是单线程 ...

  4. 跟我学SpringCloud | 终篇:文章汇总(持续更新)

    SpringCloud系列教程 | 终篇:文章汇总(持续更新) 我为什么这些文章?一是巩固自己的知识,二是希望有更加开放和与人分享的心态,三是接受各位大神的批评指教,有任何问题可以联系我: inwsy ...

  5. LeetCode 题目的 Python 实现(持续更新中)

    Python-LeetCode 是一个使用 Python 语言解决 LeetCode 问题的代码库,库有以下几个方面需要注意: 所有题目都是 AC 的: 按照题目顺序,每 50 个放在一个目录下,方便 ...

  6. 优步UBER司机全国各地最新奖励政策汇总(持续更新...)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://didi-uber.com/archiv ...

  7. 痞子衡嵌入式:史上最强i.MX RT学习资源汇总(持续更新中...)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MX RT学习资源. 类别 资源 简介 官方汇总 i.MXRT产品主页 恩智浦官方i.MXRT产品主页,最权威的资料都在这里,参考手 ...

  8. IT书籍下载汇总--持续更新

    本书单由北北分享,并持续更新,请将该地址加入收藏夹:北北的书单 .badge{float:right;}.list-group-item > .badge + .badge{margin-rig ...

  9. Type Script在Visual Studio 2013中的问题汇总(持续更新…)

    TypeScript在vs2012下的问题 TypeScript对VS2012支持度比较低,建议升级为VS2013版本以上. 在VS2013中无法创建TypeScript项目 VS2013默认不支持T ...

随机推荐

  1. P4315 月下“毛景树”(树链剖分)

    P4315 月下"毛景树"(树链剖分) 题面 简述: 边权转点权(在dfs1处转换) 把一条边权赋值在深度更深的上 需要实现对单边权的染色 , 路径边权的染色 , 路径边权的增加 ...

  2. 从零开始Blazor Server(9)--修改Layout

    目前我们的MainLayout还是默认的,这里我们需要修改为BootstrapBlazor的Layout,并且处理一下菜单. 修改MainLayout BootstrapBlazor已经自带了一个La ...

  3. OSSCore 开源解决方案介绍

    基于.NetCore的积木化服务框架,主要将常规解决方案进行进一步的抽象下沉形成相关基础可选框架单元(在Framework 目录),并在此基础上实现常规系统模块(在Modules 目录),如用户管理, ...

  4. Blazor和Vue对比学习(知识点杂锦3.04):Blazor中C#和JS互操作(超长文)

    C#和JS互操作的基本语法是比较简单的,但小知识点特别多,同时,受应用加载顺序.组件生命周期以及参数类型的影响,会有比较多坑,需要耐心的学习.在C#中调用JS的场景会比较多,特别是在WASM模式下,由 ...

  5. 架构与思维:互联网高性能Web架构

    1 什么是高性能Web架构 在互联网业务中,我们经常会面临流量巨大的复杂的分布式场景.这就要求我们在设计系统的时候保证系统具有承载高并发(High Concurrency)的能力,同时能够保证系统的高 ...

  6. openstack中Keystone组件简解

    一.Keystone服务概述 在Openstack框架中,keystone(Openstack Identity Service)的功能是负责验证身份.校验服务规则和发布服务令牌的,它实现了Opens ...

  7. 读完 RocketMQ 源码,我学会了如何优雅的创建线程

    RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时.高可靠的消息发布与订阅服务. 这篇文章,笔者整理了 RocketMQ 源码中创建线程的几点技巧,希望大家读完之后,能 ...

  8. SpringBoot整合JWT实现登录认证

    什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点 ...

  9. VSCODE 配置远程调试环境

    以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」https://www.cnblogs.com/englyf/p/16691460.html 我的需求是,在Windows桌面 ...

  10. TortoiseSVN 执行清理( cleanUp )失败的解决方案

    TortoiseSVN 执行清理( cleanUp )失败的解决方案 今天碰到了一个比较棘手的问题,在这里做一下记录,以方便自己和有需要的朋友在之后碰到该类问题时有个参考. 现象 更新SVN时弹出清理 ...