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;
}
};
标签:题目,nums,int,汇总,return,start,vector,path,LeetCode 来源: https://www.cnblogs.com/yanyh-robot/p/16670219.html