ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

LeetCode题目答案及理解汇总(持续更新)

2022-09-08 17:32:40  阅读:250  来源: 互联网

标签:题目 nums int 汇总 return start vector path 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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有