其他分享
首页 > 其他分享> > LeetCode题目答案及理解汇总(持续更新)

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& 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;
    }
};

标签:题目,nums,int,汇总,return,start,vector,path,LeetCode
来源: https://www.cnblogs.com/yanyh-robot/p/16670219.html