leetcode编程练习

leetcode题集目录

  • 一、字符串,数据(包含DP、STL)专题
    • 3. 无重复字符的最长子串(双指针)
    • 5. 最长回文子串(中心扩散法和动态规划)
    • 10. 正则表达式匹配(动态规划)
    • 11. 盛最多水的容器
    • 15. 三数之和(双指针)
    • 49. 字母异位词分组(字符串,map,排序)
    • 43. 字符串相乘
    • 67. 二进制求和
    • 20. 有效的括号(栈)
    • 71. 简化路径(stack)
    • 26. 删除排序数组中的重复项(双指针)
    • 30. 串联所有单词的子串
    • 66. 加一
    • 125. 验证回文串
    • 232. 用栈实现队列
    • 155. 最小栈
  • 二、链表专题
    • 19. 删除链表的倒数第N个节点
    • 21. 合并两个有序链表
    • 23. 合并K个排序链表(分治法递归)
    • 24. 两两交换链表中的节点(递归)
  • 三、图论专题(BFS 、 DFS 、 并查集、 最短路径等)
    • 547. 朋友圈(DFS与并查集)
    • 200. 岛屿数量(DFS + BFS)
    • 207. 课程表(拓扑排序 + DFS)
  • 四、二叉树与递归专题
    • 22. 括号生成(回朔法)
    • 50. 全排列(递归)
    • 100. 相同的树(转载leetcode大佬)以及写树算法的套路框架
    • 98. 验证二叉搜索树
    • 104. 二叉树的最大深度(递归和BFS)
    • 111. 二叉树的最小深度






一、字符串,数据(包含DP、STL)专题

3. 无重复字符的最长子串(双指针)

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

厦大计算机系机试第一题(只拿了一半好像,回过头来想到是ascii-‘0’)出现的负数造成的 ,哎菜的难受(如果ascii的值比’0’小就异常了)。
本题可采用滑动窗口的方法,重点是2个指针,i和j,分别表示最长子串的首端和末端。
9.9更新: 第一种方法效率更多,可以不适用map,也较好理解,其实双指针对于此题,我认为重点就是如何理解子串开头的慢指针,我的逻辑思维不好,因此举了一个稍微特殊的例子理解了慢指针的妙用。

(结合代码)双指针演示:
i(慢指针)
j(快指针)
a  b  c  d  b  e (uniquestr = a)
i   j
a  b  c  d  b  e(uniquestr = ab)
i      j
a  b  c  d  b  e(uniquestr = abc)
i         j
a  b  c  d  b  e(uniquestr = abcd)
i            j
a  b  c  d  b  e(此时不执行if的第一个分支,i移动)
   i         j
a  b  c  d  b  e(此时不执行if的第一个分支,i移动)
       i     j(此时,j才继续移动,所以i是会移动到不重复的位置上,(uniquestr = cdb))
a  b  c  d  b  e

// 无论i怎么移动,判断子串是否变大的依据是看j,而不是看num[s[i]]
// 如果s[j] 出现了重复,其实并不需要找到s[j]相同的字符,只需要将i依次
//向前移动,并还原num[s[i]]的值,切忌不要直接去想怎么马上移动到第一个重复
//的字符,因为只要这样子移动,而判断ans的变化与否还是看的是j,只要没移动
//到重复的字符,那么j的位置就不变改变,就不用担心出现错误。
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        // i j 双指针
        int len = s.size();
        int num[256] = {0};
        int i = 0, j = 0;
        int ans = 0;
        while(j < len){
            if(num[s[j]] == 0){
                ans = max(ans, j - i + 1);
                num[s[j]]++;
                j++;
            }else{
                num[s[i]] = 0;
                i++;
            }
        }
            return ans;
    }
};

5. 最长回文子串(中心扩散法和动态规划)

给定一个字符 串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

(1)中心扩散法
需要两次判断,用来检测以x为中心的回文是奇数还是偶数的情况。巩固了C语言欠缺的知识(太差了,一脸泪,字符指针的返回值无法使用局部变量 ,必须动态分配内存给字符指针malloc函数,或者使用static作为全局变量,并且在使用malloc的时候,先必须申请的内存空间为返回的字符串的长度len+1,切一定要加上 ans[len] = '\0’结束符,否则无法编译通过。)

 char * longestPalindrome(char * s){
    int maxlen = 0;
    int start = 0;
    int len = strlen(s);
    if(len < 2){
        return s;
    }
    for(int h = 0; h < len; h++){
		int i = h;
		int j = i;
        while(i >= 0 && j <= len && s[i] == s[j] ){
            i--;
            j++;
        }
        if(maxlen < j - i -1){
            maxlen = j - i - 1;
            start = i + 1;
        }
        i = h;
        j = h + 1;
        while(i >= 0 && j <= len && s[i] == s[j] ){
            i--;
            j++;
        }
        if(maxlen < j - i -1){
            maxlen = j - i - 1;
            start = i + 1;
        }
     }
    char * ans =(char *) malloc(sizeof(char) * (maxlen + 1 ));
    strncpy(ans, s + start, maxlen);
    ans[maxlen] = '\0';
   // strcpy(s,ans);
    return ans;   
}```

(2)动态规划法
leetcode编程练习_第1张图片

//动态规划
//C语言版本,不过这个leetcode通过不了,“cbbd” 本地这个这个回文串是“bb”,在服务器上却是“cbb”,
//无语了,不过对比了底下的C++版本可以通过,我觉得思路应该是没问题的,恳请批评指正
//dp[i][j] 表示 从尾i 到 头j组成的字符串是否为回文字符串,分别用1与0表示。
char * longestPalindrome(char * s){
  int dp[1001][1001];
  int len = strlen(s);
  int maxlen = 1;
  int start = 0;
  int end = 0;
  //首先,初始化,自己本身就是个长度为1的回文串。
  for(int i = 0; i < len; i++){
      dp[i][i] = 1;
  }
  //两层for循环,首先i从头遍历,j是一个重点,她是从i的位置向前遍历,
  //重点判断dp[i - 1][j + 1],而不是dp[i + 1][j - 1]]。
  for(int i = 0; i < len; i++){
      for(int j = i; j >= 0; j--){ 
          if((s[i] == s[j]) && (i - j < 2)){//这种是特殊情况,表示i与j相邻的情况。
              dp[i][j] = 1;
          }
          else if((s[i] == s[j]) && (dp[i - 1][j + 1] == 1)){//核心
                  dp[i][j] = 1;
              }          
          if((dp[i][j] == 1) && (i - j + 1 > maxlen)){//判断长度是否超过最大
              maxlen = i - j + 1;
              start = j ;
              end = i;
          }
     }
  }   
  char * ans =(char *) malloc(sizeof(char) * (maxlen + 1 ));
  strncpy(ans, s + start, maxlen);
  ans[maxlen] = '\0';
  return ans;  

}

//C++ 版本

class Solution {
public:
    string longestPalindrome(string s) {
        int len = s.length();
        if (len == 0)
            return s;
        bool dp[len][len];
        int start = 0, end = 0;
        for (int i = 0; i <len; i++)
            dp[i][i] = true;
        for (int r = 0; r < len; r++)
            for (int l = 0; l < r; l++)
                if (s[r]==s[l] && (r-l==1 || dp[l+1][r-1])) {
                    dp[l][r] = true;
                    if (r-l > end-start) {
                        start = l; 
                        end = r;
                    }
                    continue;
                }else{
                    dp[l][r] = false;
                }
        string ans = s.substr(start, end-start+1);
        return ans;
        
    }
};

10. 正则表达式匹配(动态规划)

思路:借鉴leetcode大佬的评论:@袁雷洋 今天提交完答案之后,又在网上看了几个解答的方法,还是感觉这个递归的方法逻辑最清晰,但是思路很容易就迷糊了.在这里稍微梳理一下。
首先建立了一个全部为False的二维矩阵保存递归的结果,行数是(字符串长度+1),列数是(p的长度+1).这里为什么要多建一行一列呢?因为需要考虑到p和s为空的情况.
其中dp[i][j]表明字符串s[:i]与p[:j]的匹配结果.(其中s[:i]最后一个元素为s[i-1],p[:j]最后一个元素为p[j-1],搞懂这一点才看得懂代码的)
首先处理i为0(s==’’)或者j为0(p==’’)的情况.当p==’‘时,只有s==’‘时二者才能匹配,所以首列中,只有首个元素为0,即dp[0][0]=0. 当s==’‘时,只有p==’‘或者p==‘x*y*’(或类似形式)时,二者才能匹配. 所以有
dp[0][j]= =( j >= 2) and (p[j-1] = =’*’) and (dp[0][j-2]).
有必要对上面这个式子解释: 首先j>=2,因为j=1时,p不为空,也不存在首个元素就为*的情况,所以此时p与s肯定无法匹配.dp[0][j]对应p的子串,最后一个元素为p[j-1],只有p[j-1]= =’*'时才有可能匹配成功.同时需要dp[0][j-2]同样为True时,dp[0][j]才能为True.(这个地方好好理解一下,对下面的总代码理解很重要)
下面就使用双重循环开始递归了.dp[i][j]对应s的子串,最后一个元素为s[i-1],对应p的子串,最后一个元素为p[j-1].
(1)首先判断p[j-1]是否为’*’.
如果p[j-1] == '’,那么’‘前面肯定是有一个字母的(比如说b吧),那么需要同时考虑’b*’,此时分两种情况:
①’b*'是无用的:
比如s=‘aaaa’,p=‘a*b*’.此时s与p的配对结果与s与’a*'配对的结果是一样的.所以有 dp[i][j] = dp[i][j-2].
②’b*'是有用的:
比如s=‘aabb’,p=‘a*b*’.之前s=‘aab’,p='a*b’时二者已经配对过了,所以s=‘aab’,p=‘a*b*‘时,二者同样能配对,同样需要判断s[i-1]是否与p[j-2]相同,或者p[j-2]==’.’(即可以代替任何字符)。
所以有 dp[i][j] = dp[i-1][j] and (p[j-2] = = s[i-1] or p[j-2] = = ‘.’)
1与2两种情况有一种成立就行,即取或.
if p[j-1] == ‘*’: dp[i][j] = dp[i][j-2] or (dp[i-1][j] and (p[j-2] = = s[i-1] or p[j-2] == ‘.’))
(2)如果p[j-1]不为’*’.
判断起来就相对简单了.首先要考虑之前的子串是否匹配成功了,即dp[i-1][j-1]的值,同时要考虑dp[i][j]对应的s的子串最后一位s[i-1],p的子串p[j-1]是否相等, p[j-1] == '.‘时同样满足情况,毕竟’.'是万能匹配符. 所以就有 else: dp[i][j] = dp[i-1][j-1] and(p[j-1] == s[i-1] or p[j-1] == ‘.’)
希望能对大家有所帮助。

#include 
bool isMatch(char * s, char * p) {
    int sl = strlen(s);
    int pl = strlen(p); 
    //首先建立了一个全部为False的二维矩阵保存递归的结果,行数是(字符串长度+1),列数是(p的长度+1).这里为什么要多建一行一列呢?因为需要考虑到p和s为空的情况. 其中dp[i][j]表明字符串s[:i]与p[:j]的匹配结果.(其中s[:i]最后一个元素为s[i-1],p[:j]最后一个元素为p[j-1],搞懂这一点才看得懂代码的)
    bool dp[sl + 1][pl + 1];
    memset(dp, false, sizeof(dp));
   //初始化 三步
    dp[0][0] = true;
    
    for(int i = 1; i < strlen(s) + 1; i++){
        dp[i][0] = false;
    }
    for(int j = 1; j < strlen(p) + 1; j++){
        dp[0][j] = j > 1 && dp[0][j - 2]   && p[j - 1] == '*';
    }
    // 有必要对上面这个式子解释: 首先j>=2,因为j=1时,p不为空,也不存在首个元素就为*的情况,所以此时p与s肯定无法匹配.dp[0][j]对应p的子串,最后一个元素为p[j-1],只有p[j-1]=='*'时才有可能匹配成功.同时需要dp[0][j-2]同样为True时,dp[0][j]才能为True.(这个地方好好理解一下,对下面的总代码理解很重要)
    
    for(int i = 1; i < strlen(s) + 1; i++){
        for(int j = 1; j < strlen(p) + 1; j++){
            if(p[j - 1] == '*'){
                dp[i][j] = dp[i][j - 2] ||( (p[j - 2] == s[i - 1] || p[j - 2] =='.')  && dp[i - 1][j]);
            }else{
                dp[i][j] = dp[i - 1][j - 1] &&( (s[i - 1] == p[j - 1]) || p[j - 1] == '.');
            }
        }
    }
    return dp[strlen(s)][strlen(p)];
}

11. 盛最多水的容器

给定 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。
leetcode编程练习_第2张图片
示例:

输入: [1,8,6,2,5,4,8,3,7]
输出: 49
//双指针 i, j
class Solution {
public:
    int maxArea(vector<int>& height) {
        if(height.size() <= 1) return -1;
        int i = 0, j = height.size() - 1, res = 0;
        while(i < j){
            int h = min(height[i], height[j]);
            res = max(res, h * (j - i));
            if(height[i] < height[j]) ++i;
            else --j;
        }
        return res;
    }
};

15. 三数之和(双指针)

双指针的运用,减少时间复杂度。我之前的方法不够好,多次运用vector和set去重,下方为大神写的,简洁易懂。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int len = nums.size();
        vector<vector<int>> ans;
        sort(nums.begin(),nums.end());
        set<vector<int>> s;
        if(len < 3){
            return ans;
        }
        int i, j, k;
        for(i = 0; i < len; i++){
            if(nums[i] > 0)
                break;         
            vector<int> res = nums;
            res.erase(res.begin() + i);
            j = 0;
            k = res.size() - 1;
            while(j < k &&(i == 0  ||  nums[i] > nums[i - 1])){
                if(res[j] + res[k] + nums[i] == 0){
                    vector<int> fs;
                    fs = {nums[i],res[j],res[k]};         
                    sort(fs.begin(),fs.end());
                    if(!s.count(fs)){                    
                        ans.push_back(fs);     
                        s.insert(fs);           
                    } 
                    j++;
                    k--;
                }
                else if(res[j] + res[k] + nums[i] < 0){
                    j++;
                }
                else if(res[j] + res[k] + nums[i] > 0){
                    k--;
                }
            }
        }
        return ans;
    }
};






//大神写法 非常的好理解,我的直接太复杂了TT
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int target;
        vector<vector<int>> ans;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            if (i > 0 && nums[i] == nums[i - 1]) continue; //去重
            target = num[i];
            if (target > 0) break; //排序好后如果target>0那么双指针l 与 r都至少是取他排序后面的数,也就是都比他大,因此可以直接跳过,不可能等于0
            int l = i + 1, r = nums.size() - 1;
            while (l < r) {
                if (nums[l] + nums[r] + target < 0) ++l;
                else if (nums[l] + nums[r] + target > 0) --r;
                else {
                    ans.push_back({target, nums[l], nums[r]});
                    ++l, --r;
                    while (l < r && nums[l] == nums[l - 1]) ++l;
                    while (l < r && nums[r] == nums[r + 1]) --r;
                }
            }
        }
        return ans; 
    }
};

49. 字母异位词分组(字符串,map,排序)

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:

输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
输出:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]

说明:

所有输入均为小写字母。
不考虑答案输出的顺序。

参考leetcode本题题解中的大佬@YouLookDeliciousC的代码,利用map 和sort方法来将相同字母和数量相同的字符串划分为一组,在循环输出。

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        map<string, vector<string>> m;
        string temp;
        for(int i = 0; i <strs.size(); i++){
            temp = strs[i];
            sort(temp.begin(), temp.end());
            m[temp].push_back(strs[i]);//这一步很精妙
        }
        int len = m.size();
        vector<vector<string>> ans;
        ans.resize(len);
        int cnt = 0;
        //map> ::iterator it;
        //注意auto的用法,可以省略上一步。
        for(auto it = m.begin(); it != m.end(); it++){
            ans[cnt++] = (*it).second;
            // ans[cnt++] = it->second;

        }
        return ans;
    }
    
};

43. 字符串相乘

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入: 121
输出: true
示例 2:

输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:

输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。

  /**  转至@SEU.FidGet评论大佬的清晰注释解答,非常的直观,发现自己的竖式乘法白学了...

        num1的第i位(高位从0开始)和num2的第j位相乘的结果在乘积中的位置是[i+j, i+j+1]
        例: 123 * 45,  123的第1位 2 和45的第0位 4 乘积 08 存放在结果的第[1, 2]位中
          index:    0 1 2 3 4  
              
                        1 2 3
                    *     4 5
                    ---------
                          1 5
                        1 0
                      0 5
                        1 2
                      0 8
                    0 4
                    ---------
                    0 5 5 3 5
        这样我们就可以单独都对每一位进行相乘计算把结果存入相应的index中 ,可见,这里有4中组合,分别是15;’10 和 12;05 和08 ;04,此处最高进位是0,但还是要注意如果为1的情况,因此要在数组里留给这进位的位置,即res[0]。       
        **/

class Solution {
public:
    string multiply(string num1, string num2) {
        vector<int> res(num1.size() + num2.size());
        int m = num1.size(), n = num2.size();   
        if (num2[0] == '0' || num1[0] == '0') return "0"; // 先排除是否为0的结果。

        /* res从左到右存储高位到低位两两相乘的结果 */
    	for(int i = num1.size() - 1;i >= 0; i--)
            for(int j = num2.size() - 1; j >= 0; j--)
                res[i + j + 1] += (num1[i] - '0') * (num2[j] - '0'); // 难点:因为是最低位存储最高位,所以要反过来放。
        
        /* 左侧高位,右侧低位,因此结果最开始从右往左依次的进位,容易理解 */       
        for(int k = res.size() - 1; k > 0; k--){
            if(res[k] >= 10){
                    res[k - 1] += res[k] / 10;
                    res[k] %= 10;
            }
        }        
        string res_str = "";
        for(int i=0;i<res.size();i++){
            if(i == 0 && res[0] == 0) continue; //最高位未进位则不处理 例如       2 * 3 = 6 而不是 06.
            res_str =res_str + char(res[i] + '0');
        }        
        return res_str;
    }
    
};

67. 二进制求和

仿造上一题的思路,只不过这里比如11+ 1, 要考虑各自的长度,不能直接计算。

class Solution {
public:
    string addBinary(string a, string b) {
        int la = a.size();
        int lb = b.size();
        int len = 0,max = 0; // max和len初始值都是a和b中最大的长度,但是len会变化,max不会变化。
        int min_len = 0;
        string temp; //temp用来存储 位数较大的那个字符串,例如“11” 与“1”中的"11"。
        if(la > lb){
            len = la;
            temp = a;
            max = la;
            min_len = lb;
        }
        else{
            max = lb;
            len = lb;
            temp = b;
            min_len = la;
        }
        vector<int> res(len + 1, 0);
        la = la - 1;
        lb = lb - 1;
        //         for(int i = len - 1; i >= 0; i--){ //这个for循环只能试用于相同位数的字符串加法。
		//             res[i + 1] = (a[i] - '0') + (b[i] - '0');
		//         }
        for(int i = 0; i < min_len; i++){
            
            res[len--] = (a[la--] - '0') + (b[lb--] - '0');//

        }
        for(int i = max - min_len - 1; i >= 0; i--){ // 这里使用max。
            res[len--] = (temp[i] - '0');
        }
        
        for(int i = max; i >= 1; i--){ // 同理这里也是max。
            if(res[i] >= 2){
                res[i - 1] = res[i - 1] + 1;
                res[i] = res[i] - 2;
            }
        }
        string res_str ="";
        for(int i = 0; i < max + 1; i++){
            if(i == 0 && res[i] == 0) continue;
            res_str +=  (res[i] + '0');    
        }
        return res_str;
    }
};



20. 有效的括号(栈)

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

示例 1:

输入: "()"
输出: true

示例 2:

输入: "()[]{}"
输出: true

示例 3:

输入: "(]"
输出: false

示例 4:

输入: "([)]"
输出: false

(时间复杂度和内存使用都还太多,看来还是直接从string 上下手比较快0.0)
知识点:完整的括号匹配算法流程如下:

首先从前向后扫描字符串:
    ①遇到左括号 x,就把 x 压栈;
    ②遇到右括号 y:
        如果发现栈顶元素x和该括号y匹配,则栈顶元素出栈,继续判断下一个字符;
        如果栈顶元素x和该括号y不匹配,字符串不匹配;
        如果栈为空,字符串不匹配;
    ③扫描完成后,如果栈恰好为空,则字符串匹配,否则,字符串不匹配。

class Solution {
public:
    bool isValid(string s) {
        int len = s.size();
        
        stack<char> ans;
        for(int i = 0; i < len; i++){
            if(s[i] == '(' || s[i] == '{' || s[i] == '[' ){ //①遇到左括号 x,就把 x 压栈;
                ans.push(s[i]);
            }
            else{     //遇到右括号
                    bool flag = isMatch(s[i], ans);
                    if(!flag)
                        return false;
                    ans.pop();                                    
            }
        }
        if(ans.empty())//③扫描完成后,如果栈恰好为空,则字符串匹配,否则,字符串不匹配。

            return true;
        return false;
    }
    bool isMatch(char s, stack<char> ans){
        if(ans.empty())
            return false;
        if(s == ')'){
            return ans.top() == '(';
        }else if (s == ']'){
            return ans.top() == '[';
        }else{
            return ans.top() == '{' ;
        }
        return true;
    }
};

71. 简化路径(stack)

https://leetcode-cn.com/problems/simplify-path/solution/cti-jie-zhan-by-happyblacky/
示例1:

输入:"/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。

示例 2:

输入:"/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。

示例 3:

输入:"/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。

示例 4:

输入:"/a/./b/../../c/"
输出:"/c"

示例 5:

输入:"/a/../../b/../c//.//"
输出:"/c"

示例 6:

输入:"/a//b////c/d//././/.."
输出:"/a/b/c"
//都可以 stack会慢一点,因为要逆序输出
class Solution{
    public:
    string simplifyPath(string path) 
        {
           //vector dir = {};

            stack<string> dir;
            int loc = 0;
            while(loc < path.size())
            {
                string name = "";
                while(loc<path.size() && path[loc] != '/')
                {
                    name += path[loc];
                    loc++;
                }
                if(name != "" && name != ".." && name != ".")
                    dir.push(name);
                if(name == ".." && !dir.empty())
                    dir.pop();
                loc++;
            }
        
            if(dir.empty())
                return "/";
            string dirpath = "";
            vector<string> ans;
            while(!dir.empty())//注意,如果目录是/home/foo,由于栈先进后出,会变成/foo/home,因此需要逆序
            {
                ans.push_back(dir.top());
                dir.pop();
            }
            /*  vector就可以直接循环
            for(int i=0;i
            for(int i = ans.size() - 1; i >= 0; i--){
                dirpath +="/" + ans[i] ;
            }
            return dirpath;
        }
    };



26. 删除排序数组中的重复项(双指针)

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2], 

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 

你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。

你不需要考虑数组中超出新长度后面的元素。
//因为是排序数组,故可以用双指针的方法来做
class Solution {
public:
    int removeDuplicates(vector& nums) {
        int l, r;
        if(nums.size() <= 1)
             return nums.size();
        l = 0, r = 1;
        for(; r < nums.size(); r++){
            if(nums[l] != nums[r]){
                nums[++l] = nums[r];
            }
        }
        return l + 1;
    }
};

30. 串联所有单词的子串

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。

注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

示例 1:

输入:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoor" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。

示例 2:

输入:
  s = "wordgoodgoodgoodbestword",
  words = ["word","good","best","word"]
输出:[]
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> res;
        if(words.size() == 0 || s.size() == 0)
            return {};        
        int m = words[0].size();
        int n = words.size();
        if(s.size() < n * m)
            return {};
        //建立map tmp  和 mp 进行关联
        map<string, int> mp, tmp;
        for(int i = 0; i < words.size(); i++){
            mp[words[i]]++; //考虑到words会有相同的单词,因此选用计数方法较简便
        }
        int j = 0;
        for(int i = 0; i + n * m - 1 < s.size(); i++){
            string str = "";
            for(j = i; j < i + n * m; j = j + m){
                str = s.substr(j, m);
                map<string, int> :: iterator iter;
                iter = mp.find(str);
                if(iter != mp.end()) 
                    tmp[str]++;
                else
                    break;
            }
            if(j == i + n * m && tmp == mp)
                res.push_back(i);
            tmp.clear();
        }
        return res;
        
    }
};





66. 加一

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

示例 1:

输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。

示例 2:

输入: [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321。
//本来想通过 ans = ans * 10 + digit[i] 这种无脑的方法来查看进位变化, 结果leetcode早有准备,给了一个好几个元素组成的数组,直接越界。
class Solution {
public:
    vector<int> plusOne(vector<int>& digits) {
        int len = digits.size();
        // 这个循环参考leetcode题解大佬@YHHZW 的思路,非常巧妙,从最后循环,如果除余不为0,则可以继续循环加一。
        // 124 --》 125
        // 129 --》 130
        // 999 --》 000
        for(int i = len - 1; i >= 0; i--){
            digits[i] ++;
            if(digits[i] % 10 != 0)
                return digits;
            else
                digits[i] = 0;
        }
        //针对 999这种情况
        if(digits[0] == 0){
            vector<int> ans;
            ans.push_back(1);
            for(int i = 0; i < len; i++)
                ans.push_back(digits[i]);
            return ans;
        }
       
        return digits;
    }
};

125. 验证回文串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

示例 1:

输入: "A man, a plan, a canal: Panama"
输出: true

示例 2:

输入: "race a car"
输出: false
//双指针, 这种方法更快一些。底下注释为自己写法,时间太慢了。
class Solution {
public:
    bool isPalindrome(string s) {
               
        int left = 0, right = s.size() - 1;
        while(left < right){
            while(left < right && !isalnum(s[left]))
                left ++;
            while(left < right && !isalnum(s[right]))
                right --;
            if(tolower(s[left]) == tolower(s[right])){
                left ++;   
                right --;
            }
            else{
                return false;
            }         
        }
        return true;       
    }
};

// class Solution {
// public:
//     bool isPalindrome(string s) {
//         vector ans;
//         for(int i = 0; i < s.size(); i++){
//             if(isalnum(s[i])){
//                 ans.push_back(tolower(s[i]));
//             }
//         }                
//         int left = 0, right = ans.size() - 1;
//         while(left < right){          
//             if(ans[left] == ans[right]){
//                 left ++;   
//                 right --;
//             }
//             else{
//                 return false;
//             }
            
//         }
//         return true;       
//     }
// };

232. 用栈实现队列

使用栈实现队列的下列操作:

push(x) – 将一个元素放入队列的尾部。
pop() – 从队列首部移除元素。
peek() – 返回队列首部的元素。
empty() – 返回队列是否为空。
示例:

MyQueue queue = new MyQueue();

queue.push(1);
queue.push(2);  
queue.peek();  // 返回 1
queue.pop();   // 返回 1
queue.empty(); // 返回 false
class MyQueue {
public:
    stack<int> ans;
    stack<int> helper;
    /** Initialize your data structure here. */
    MyQueue() {
        
    }
    
    /** Push element x to the back of queue. */
    void push(int x) {
        helper.push(x);
        
    }
    
    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        while(!helper.empty()){
            int temp = helper.top();
            ans.push(temp);
            helper.pop();
        }
        int tmp = ans.top();
        ans.pop();
        while(!ans.empty()){
            int temp = ans.top();
            helper.push(temp);
            ans.pop();
        }
        return tmp;
    }
    
    /** Get the front element. */
    int peek() {
        while(!helper.empty()){
            int temp = helper.top();
            ans.push(temp);
            helper.pop();
        }
        int  res = ans.top();
        while(!ans.empty()){
            helper.push(ans.top());
            ans.pop();
        }
        return res;
    }
    
    /** Returns whether the queue is empty. */
    bool empty() {
        return ans.empty() && helper.empty();
    }
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue* obj = new MyQueue();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->peek();
 * bool param_4 = obj->empty();
 */

155. 最小栈

设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。

push(x) – 将元素 x 推入栈中。
pop() – 删除栈顶的元素。
top() – 获取栈顶元素。
getMin() – 检索栈中的最小元素。
示例:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.
// 辅助栈的方法
class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> ans;
    stack<int> helper;
    MinStack() {
        
    }
    
    void push(int x) {
        ans.push(x);
        if(helper.empty() || helper.top() >= x){
            helper.push(x);
        }
    }
    
    void pop() {
        int temp = ans.top();
        ans.pop();
        if(temp == helper.top())
            helper.pop();
    }
    
    int top() {
        return ans.top(); 
    }
    
    int getMin() {
        return helper.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(x);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

二、链表专题

19. 删除链表的倒数第N个节点

https://blog.csdn.net/qq_21815981/article/details/79833976

知识点: 快慢指针。
    可以定义两个指针,第一个指针从链表的头指针开始遍历向前走k-1步,第二个指针保持不动;从第K步开始,第二个指针也开始从链表的头指针开始遍历。由于两个指针的距离保持在k-1,当第一个指针到达链表的尾节点时候,第二个指针正好是倒数第K个节点,代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head == NULL ||(n == 1 && head -> next == NULL))
            return NULL;
        ListNode *fast = head, *slow = head;
        for(int i = 0; i < n; i++){
            fast = fast -> next;
        }
        if(!fast) //如果删除的是第一个结点(倒数第N个结点),直接返回后续的
            return head -> next;
        while(fast -> next != NULL){
            fast = fast -> next;
            slow = slow -> next;
        }
        slow -> next = slow ->next ->next;
        return head;
       
    }
};

21. 合并两个有序链表

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

知识点: C++ 中结构体的构造函数。
    如这里的ListNode(int x) : val(x). next(NULL){ }
    这句可以用 ListNode *ans = new ListNode(0) 创建了一个数据值为0,指针域为null的结点。这样子的话,在创建一个 ListNode *result = ans, 最后只要排除了初始化的0这个值就可以了,因此return result -> next 就好。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *p = l1, *q = l2;
        ListNode *ans = new ListNode(0); //重点
        ListNode *result = ans;
        while(p != NULL && q != NULL){
            if(p -> val > q -> val){
                ans -> next = q;
                q = q -> next;
                ans = ans -> next;
            }else{
                ans -> next = p;
                p = p -> next;
                ans = ans ->next;
            }
        }
        if(q != NULL)
            ans -> next = q;
        else if(p != NULL)
            ans -> next = p;
       return result -> next; //重点
    }
  
};

23. 合并K个排序链表(分治法递归)

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

示例:

输入:

    [
      1->4->5,
      1->3->4,
      2->6
    ]

输出: `1->1->2->3->4->4->5->6`

知识点:分治法。
分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

    分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。
    这题可以建立在上一个链表题目的基础之上,将多个链表不断的折半缩小,直到变成2个链表以内,在带入mergeKLists函数中进行合并,思维巧妙,掌握原理后不难理解。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */

    
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        
        if(lists.empty()){
            return NULL;
        }
        if(lists.size() == 1){
            return lists[0];
        }
        if(lists.size() == 2){
            return mergeTwoLists(lists[0],lists[1]);
        }
        int mid = lists.size()/2;
        vector<ListNode*> sub_list1;
        vector<ListNode*> sub_list2;
        for(int i = 0;i<mid;i++){
            sub_list1.push_back(lists[i]);
        }
        for(int i = mid;i<lists.size();i++){
            sub_list2.push_back(lists[i]);
        }
         ListNode* l1 = mergeKLists(sub_list1);
         ListNode* l2 = mergeKLists(sub_list2);
        return  mergeTwoLists(l1,l2);
    }
    
     ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *l = new ListNode(0);
        ListNode* ptr = l;
        while(l1 != NULL && l2 != NULL){
            if(l1->val <= l2->val){
                ptr->next= l1;
                l1 = l1->next;
                ptr =ptr->next;
                
            }else{
                ptr->next = l2;
                l2 = l2->next; 
                ptr =ptr->next;
            }
        }
        if(l1 != NULL){
            ptr->next = l1;
        }
        if(l2 != NULL){
            ptr->next = l2;
        }

        return l -> next;
    }
};

24. 两两交换链表中的节点(递归)

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:

给定 1->2->3->4, 你应该返回 2->1->4->3.

递归。这是这道题目的一个大佬评论给出的思路,即使我在前面做了一个题目,有了递归的感觉,但还是没法写出来TT 太差了。。 最大的缺陷我觉得应该是把它想成一个3个结点的链表,而不是我之前想的22分成,
大佬的博客,关于一篇如何处理递归的问题http://lylblog.cn/blog/4,非常不错。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == NULL || head -> next == NULL)
            return head;
        ListNode * next = head -> next;
        head -> next = swapPairs(next -> next);
        next ->next =head;
        return next;
    }
};

三、图论专题(BFS 、 DFS 、 并查集、 最短路径等)

547. 朋友圈(DFS与并查集)

班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。

给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。

示例 1:

输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。

// dfs遍历, 需要邻接矩阵M[][],  标记数组是否被访问flag[], 统计个数count
class Solution {
public:
    int findCircleNum(vector<vector<int>>& M) {
        int len = M[0].size();
        bool flag[200] ={false};
        int num = 0;
        //dfs
        for(int i = 0; i < len; i++){
            if(flag[i] == false){//如果该点未被访问,即之前D[i][j] == 1 && 
                dfs(i, M, flag);
                num++;
            }
        }
        return num;
    }
    
    void dfs(int i, vector<vector<int>>& M, bool flag[]){
        flag[i] = true; // 表示该学生已经被访问
        for(int j = 0; j < M.size(); j++){
            if(flag[j] == false && M[i][j] == 1){// 如果2个人是朋友,并且是未被访问false(避免重复标记)
                dfs(j, M, flag); //继续遍历该点,直到flag = false 或 M[i][j] == 0
                            }
        }
        
        
    }
};


//并查集的基本使用, 如果是朋友 ==> 则组合他们。 如果父节点不是本身,朋友圈加1
class Solution {
public:   
	//具有路径压缩的寻找父节点函数findFather,非递归写法
    int findFather(vector<int>& father, int x){
        while(father[x] != x){
            x = father[x];
        }
        while(father[temp] != x){     
            i = father[temp];
            father[temp] = x;
            temp = i;
        }		
        return x;
    }
    
    
    /*路径压缩的递归写法
		int findFather(vector& father,int x)       
	    {
	        if (x != father[x])
	        {
	            father[x] = findFather(father, father[x]);     
	        }         
	        return father[x];
	    }
	    */
	
    //合并2个结点Union
    void Union(int i, int j, vector<int>& father){
        int fa = findFather(father, i);
        int fb = findFather(father, j);
        if(fa !=  fb){
             father[fb] = fa;
        }
    }
    
    int findCircleNum(vector<vector<int>>& M) {
        vector<int> father(M.size());
        //初始化 init, 任意结点的父节点是自己本身
        for(int i = 0; i < M.size(); i++){
            father[i] = i;
        }    
        for(int i = 0; i < M.size(); i++){
            for(int j = i + 1; j < M.size(); j++){
                if(M[i][j] == 1){ //如果是朋友,则合并,类比为在一条线上
                    Union(i, j, father);
                }
            }
        }
        int  num = 0;
        for(int i = 0; i < M.size(); i++){
            if(father[i] == i){ // 如果父节点就是自己本身,则说明一定存在一个朋友圈(合并后 就可以去重了)
                num ++;
            }
        }    
        return num;
    }  
};

200. 岛屿数量(DFS + BFS)

给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。

示例 1:

输入:
11110
11010
11000
00000

输出: 1

// DFS  线性扫描整个二维网格,如果一个结点包含 1,则以其为根结点启动深度优先搜索。在深度优先搜索过程中,每个访问过的结点被标记为 0。计数启动深度优先搜索的根结点的数量,即为岛屿的数量。

// 因此不需要增设bool flag数组。 grid数组可以代表标记数组。

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        int nr = grid.size();
        if(grid.size() == 0)
            return 0;        
        int nc = grid[0].size();

        int res = 0;
        for(int i = 0; i < nr; i++){
            for(int j = 0; j < nc; j++){
                if(grid[i][j] == '1'){ // 注意是字符元素
                    res++;                   
                    dfs(i, j, grid);
                }
            }
        }
        return res;
    }
    // dfs 函数,此题就是要注意边界的选取,这是一个易错点。
    void dfs (int i, int j, vector<vector<char>>& grid){
        grid[i][j] = '0';
        if(i - 1 >= 0 && grid[i - 1][j] == '1' ) dfs(i - 1, j, grid);
        if(i + 1 < grid.size() && grid[i + 1][j] == '1' ) dfs(i + 1, j, grid);
        if(j - 1 >= 0 && grid[i][j - 1] == '1' ) dfs(i, j - 1, grid);
        if(j + 1 < grid[0].size() && grid[i][j + 1] == '1' ) dfs(i, j + 1, grid);


    }
};





//BFS 
// pair的用法 ,借鉴官方解析,可以不适用结构体 queue> m  并且pair有2个函数,可以直接使用m.first和m.second
// map 的first和second 必须借助迭代器才能使用,而不能单独使用  
// for(map ::iterator t = m.begin(); t != m.end() m++)  {
// int t1 =  m->first;
// int t2 =  m->second;
class Solution {
public:
    struct land{
        int r;
        int c;
    };
    int numIslands(vector<vector<char>>& grid) {
        int nr = grid.size();
        if(grid.size() == 0)
            return 0;        
        int nc = grid[0].size();
        int res = 0;
        queue<land> q;
        for (int i = 0; i < nr; i++) {
            for (int j = 0; j < nc; j++) {   
                if(grid[i][j] == '1'){
                    q.push({i,j});
                    grid[i][j] == '0';     
                    res ++;
                    while(!q.empty()){ 
                        int t1, t2;
                        land temp = q.front();
                        t1 = temp.r;
                        t2 = temp.c;                       
                        q.pop();
                        if(t1 - 1 >= 0 && grid[t1-1][t2] == '1') {
                            q.push({t1-1, t2}); 
                            grid[t1-1][t2] = '0';
                        }
                        if(t1 + 1 < nr && grid[t1+1][t2] == '1') {
                            q.push({t1+1, t2}); 
                            grid[t1+1][t2] = '0';
                        }
                        if(t2 - 1 >= 0 && grid[t1][t2-1] == '1') {
                          q.push({t1, t2-1}); grid[t1][t2-1] = '0';
                        }
                        if(t2 + 1 < nc && grid[t1][t2+1] == '1') {
                          q.push({t1, t2+1}); grid[t1][t2+1] = '0';
                        }

                    }
                }
            }
        }
        return res;
    }

};

207. 课程表(拓扑排序 + DFS)

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]] 
输出: true

解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
示例 2:

输入: 2, [[1,0],[0,1]]
输出: false

解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。
说明:

输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。

class Solution {
public:
    bool Topologica_sort(int numCourses, vector<vector<int>> &ans, vector<int> &in_degree){
        bool flag = true;
        for(int i = 0; i < numCourses; i++){
            int j = 0;
            while(j < numCourses && in_degree[j] != 0){
                j++;
            }
            if(j == numCourses)
                return false;
            in_degree[j] = -1;
            for(int k = 0; k < ans[j].size(); k++){
                in_degree[ans[j][k]]--;
            }
            
        }
       
        return flag;
    }

	/*  这个函数借助了队列 ,并且使用了DFS算法。
    bool Topologica_sort(int numCourses, vector> &ans, vector &in_degree){
        queue q;
        int cnt = 0;// 用来检验入列的元素,是否与总边数相等
        int j = 0;          
        while(j < numCourses ){
            if(in_degree[j] == 0){
                q.push(j);
            }
            j++;
        }            
        while(!q.empty()){
            for(int i = 0; i < ans[q.front()].size(); i++){
                in_degree[ans[q.front()][i]] --;
                if(in_degree[ans[q.front()][i]] == 0){
                    q.push(ans[q.front()][i]);
                }
            }
            q.pop();
            cnt++;
        }
     //  cout << cnt;
        if(cnt == numCourses)
            return true;
        else
            return false;      
    }
	*/






    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        
        vector<vector<int>> ans(numCourses);
        for(int i = 0; i < prerequisites.size();  i++){
            ans[prerequisites[i][0]].push_back(prerequisites[i][1]) ;
        }
        vector<int> in_degree(numCourses);
        for(int i = 0; i < numCourses; i++){
            for(int j = 0; j < ans[i].size(); j++){
                in_degree[ans[i][j]]++;
            }
        }
        return Topologica_sort(numCourses, ans, in_degree);
        
        
    }
};

四、二叉树与递归专题

22. 括号生成(回朔法)

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

void函数有return的作用:退出函数体,如果在函数体结尾处不加也可自动退出,如果在中途需要 退出的话就用return(如递归函数中。)
vector 函数 形参 function2(vector &vec),传引用,不能直接传入 vec ,这样就变为了值传递,如果是void 则无法返回。
知识点:回朔法(盲区)
    需要再刷好几道才会写,默默学大佬的,对于递归我的理解一直很差,这次自己学到了一个小方法,稍微看个代码和题目自己画画图,例如这题可以画出2个分支,分别是‘(’和 ‘)’二叉树,类似一种DFS的思维。可能这就是数学思维吧,算我一个很致命的弱势,逻辑转的忒慢了,用时间题目 换速度(原来我也是个DP = =)。

/*
例如N = 3;
[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]*/
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> ans;
        func(ans, "", 0, 0, n);
        return ans;
    }
    
    void func(vector<string> &ans, string str, int left, int right, int n){
        if(left > n || right > n || right > left)  //right > left 表示括号匹配不合法。
            return;
        if(left ==  n && right == n){
            ans.push_back(str); //利用ans 存储, 并回溯给上层。
            return;
        }
        func(ans, str + '(', left + 1, right, n);
        func(ans, str + ')', left, right + 1, n);
        
    }
    
};

50. 全排列(递归)

给定一个没有重复数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

    class Solution {
    public:
        vector<vector<int>> permute(vector<int>& nums) {
            vector<vector<int>> results;
            vector<int> res;
            vector<bool> flag(nums.size(),false);//标记元素是否被访问过
            HDFS(results,res,flag,nums,0);
            return results;
        }
       void HDFS(vector<vector<int>> &results,vector<int> &res,vector<bool> &flag,vector<int> nums,int dep){
            if(dep==nums.size()){//达到树叶
                results.push_back(res);
            }
            for(int i=0;i<nums.size();i++){//每层都对每个节点进行检测
                if(!flag[i]){
                    flag[i]=true;//访问节点
                    res.push_back(nums[i]);
                    HDFS(results,res,flag,nums,dep+1);  //进入下一层
                    res.pop_back();//往上一层回退
                    flag[i]=false;
                }
            }
            return ;
        }
    };

100. 相同的树(转载leetcode大佬)以及写树算法的套路框架

作者:labuladong
链接:https://leetcode-cn.com/problems/same-tree/solution/xie-shu-suan-fa-de-tao-lu-kuang-jia-by-wei-lai-bu-/
来源:力扣(LeetCode)

给定两个二叉树,编写一个函数来检验它们是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

示例 1:

输入:  1    1
      /  \  /  \
     2   3 2  3

    [1,2,3],   [1,2,3]

输入: 1    1
     /      \
    2       2

    [1,2],     [1,null,2]

输出: false

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(p == NULL && q == NULL) 
            return true;
        if(p == NULL || q == NULL) 
            return false;
        if(p->val != q->val) 
            return false;
        return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
    }
};


//判断二叉树是否为BST

//难点:root 需要做的不只是和左右子节点比较,而是要整个左子树和右子树所有节点比较。

boolean isValidBST(TreeNode root) {
    return F(root, null, null);
}
boolean F(TreeNode root, TreeNode min, TreeNode max) {
    if (root == null) return true;
    if (min != null && root.val <= min.val) return false;
    if (max != null && root.val >= max.val) return false;
    return F(root.left, min, root) 
        && F(root.right, root, max);
}


//在 BST 中查找一个数是否存在
boolean isInBST(TreeNode root, int target) {
    if (root == null) return false;
    if (root.val == target)
        return true;
    if (root.val < target) 
        return isInBST(root.right, target);
    if (root.val > target)
        return isInBST(root.left, target);
    // root 该做的事做完了,顺带把框架也完成了,妙
}

//在 BST 中插入一个数,对数据结构的操作无非遍历 + 访问,遍历就是“找”,访问就是“改”。具体到这个问题,插入一个数,就是先找到插入位置,然后进行插入操作。

//上一个问题,我们总结了 BST 中的遍历框架,就是“找”的问题。直接套框架,加上“改”的操作即可。一旦涉及“改”,函数就要返回 TreeNode 类型,并且对递归调用的返回值进行接收。

TreeNode insertIntoBST(TreeNode root, int val) {
    // 找到空位置插入新节点
    if (root == null) return new TreeNode(val);
    // if (root.val == val)
    //     BST 中一般不会插入已存在元素
    if (root.val < val) 
        root.right = insertIntoBST(root.right, val);
    if (root.val > val) 
        root.left = insertIntoBST(root.left, val);
    return root;
}

98. 验证二叉搜索树

参照上一题大佬的思路,需要增加一个协助函数F,这样才能符合判断“二叉排序树的子树也是一个二叉排序树”的性质,可以很快完成。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:  
	bool isValidBST(TreeNode* root) {
	    return F(root, NULL, NULL);
	}
	bool F(TreeNode *root, TreeNode* min, TreeNode *max) {
	    if (root == NULL) //空树也是二叉排序树。
	    	return true;
	    if (min != NULL && root->val <= min->val) //如果最小的数(左子树)大于根结点
	    	return false;
	    if (max != NULL && root->val >= max->val) 
	    	return false;
	    return F(root->left, min, root) 
	        && F(root->right, root, max);
	}
};


//利用中序遍历好看二叉树是否为有序的

class Solution {
    double last = -Double.MAX_VALUE;
    public boolean isValidBST(TreeNode root) {
        if (root == null) {
            return true;
        }
        if (isValidBST(root.left)) {
            if (last < root.val) {
                last = root.val;
                return isValidBST(root.right);
            }
        }
        return false;
    }
}

104. 二叉树的最大深度(递归和BFS)

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回它的最大深度 3 。

0916更新,写的更加具体,是自己更能理解。

//方法一:递归
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */

class Solution {
public:
    int maxDepth(TreeNode* root) {
        //第一种特殊情况:这里的root可以理解为是最初的情况,无根节点,因此h为0。
        if(root == NULL) 
            return 0;
        //第二种特殊情况:左右字数为空就是只有一个根节点,那么返回1.
        else if(root -> left == NULL && root -> right == NULL)
        	return 1;
       	//第三种,右子树不为空,那就只递归右子树
        else if(root -> left == NULL)
        	return 1 + maxDepth(root -> right);
        else if(root ->left ==NULL)
        	return 1 + maxDepth(root -> left);
        return max(maxDepth(root -> left), maxDepth(root -> right)) + 1;
    }
};



//方法②DFS
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(root == NULL)
            return 0;
        int num = 0;
        queue<TreeNode *> que;          // 重点:建立队列。
        que.push(root);                 //先将根节点入列。
        while(!que.empty()){            // 终止条件,队列中无结点元素。
            int n = que.size();         // 重点:统计队列内元素个数,表明他们位于同一层
            for(int i = 0;i < n;i++){
                TreeNode *cur = que.front();
                if(cur->left != NULL)
                    que.push(cur->left);
                if(cur->right != NULL)
                    que.push(cur->right);
                que.pop();              // n次循环,每次检查后该结点的左右子树后,就弹出队列。
            }
            num++; // 深度 + 1
        }
        return num;
    }
};


111. 二叉树的最小深度

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。

示例:

给定二叉树 [3,9,20,null,null,15,7],
    3
   /  \
  9  20
    /  \
   15   7

和105比较类似的,可以用递归去做,但是有一个细节卡了很久,就是[1,2]这个测试用例,一直想不清楚,后俩看了下最小深度,才恍然大悟,but太困了递归还是参考了题解的方法。



/*    
	这个测试样例比较特殊,应该输出的是2,而不是1.
	   1
       / 
     2  

*/



/*
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */

class Solution {
public:
    int minDepth(TreeNode* root) {
        if(root == NULL)
            return 0;
        else if(root -> left == NULL &&  root -> right == NULL)
            return 1;
        else if(root -> left == NULL)
            return  1 + minDepth(root -> right);
        else if(root -> right == NULL)
            return  1 + minDepth(root -> left);

        return min(minDepth(root -> left) , minDepth(root -> right)) + 1;
    }
};

你可能感兴趣的:(leetcode)