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)动态规划法

//动态规划
//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 <stdio.h>
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。

示例:

输入: [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都至少是取他排序后面的数,也就是都比他大,因此可以直接跳过,不可能等于0int 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<string, vector> 和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<string, vector<string>> ::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 51 00 51 20 80 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//bc/d//././/.."
输出:"/a/b/c"
//<stack或者<vector>都可以 stack会慢一点,因为要逆序输出
class Solution{public:string simplifyPath(string path) {//vector<string> 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<dir.size()-1;i++){dirpath += dir[i];dirpath += "/";}dirpath += dir[dir.size()-1];*/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<int>& 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]++;elsebreak;}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 --》 000for(int i = len - 1; i >= 0; i--){digits[i] ++;if(digits[i] % 10 != 0)return digits;elsedigits[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<char> 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;//dfsfor(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<int>& father,int x)       {if (x != father[x]){father[x] = findFather(father, father[x]);     }         return father[x];}*///合并2个结点Unionvoid 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<pair<int,int>> m  并且pair有2个函数,可以直接使用m.first和m.second
// map 的first和second 必须借助迭代器才能使用,而不能单独使用
// for(map<int,int> ::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<vector<int>> &ans, vector<int> &in_degree){queue<int> 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;elsereturn 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<string> 函数 形参 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编程练习相关推荐

  1. Leetcode编程C++之1106. 解析布尔表达式

    Leetcode编程C++之1106. 解析布尔表达式 给你一个以字符串形式表述的 布尔表达式(boolean) expression,返回该式的运算结果. 有效的表达式需遵循以下约定: " ...

  2. MIT微软为AI量身打造了一套leetcode编程题

    丰色 发自 凹非寺 量子位 报道 | 公众号 QbitAI 你见过这种编程题目描述方式吗? 不用自然语言描述,也没有输入输出示例,你所看到的就只有一个Python函数. 这是来自MIT.Allen A ...

  3. 麻省理工、微软为AI量身打造了一套leetcode编程题

    点上方计算机视觉联盟获取更多干货 仅作学术分享,不代表本公众号立场,侵权联系删除 转载于:量子位 AI博士笔记系列推荐 周志华<机器学习>手推笔记正式开源!可打印版本附pdf下载链接 你见 ...

  4. java 套娃_【leetcode编程题目354】俄罗斯套娃

    来自 https://leetcode.com/problems/russian-doll-envelopes/ You have a number of envelopes with widths ...

  5. LeetCode 编程 二

    整数转罗马数字 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做 I ...

  6. LeetCode编程练习题

    42. 接雨水 https://leetcode-cn.com/problems/trapping-rain-water/ 面试题 01.06. 字符串压缩 https://leetcode-cn.c ...

  7. LeetCode编程练习 - Count Primes学习心得

    题目: Count the number of prime numbers less than a non-negative number,n. 给定一个非负数n,求小于n的质数的个数. 思路: 定义 ...

  8. 螺旋矩阵常数复杂度解法

    螺旋矩阵 吐槽 昨天参加leetcode编程大赛,做到这题,本想着使用模拟法解决,一看数据规模达到10^9,我有预感这题应该是数学解法,那么我就不会了,之后上网查文档,发现这题是一种数学问题,看了别人 ...

  9. 5月 CSDN 创作者之夜:获奖名单公布

    本期获奖名单从多个角度进行评选,有新人奖.实力新星奖.月度贡献奖.月度文章精选奖等等,具体的奖项以及评选规则可参考下表: 奖项名称 奖项评选规则 数量 现金奖励 持之以恒奖 第三期创作打卡连续四周完成 ...

最新文章

  1. win8系统的计算机共享在哪里设置方法,怎么设置win8无线共享呢?
  2. 如何高效开发端智能算法?MNN 工作台 Python 调试详解
  3. Linux ps aux指令詳解--转
  4. pageadmin CMS网站建设教程:站点添加自定义字段
  5. 前端学习(1903)vue之电商管理系统电商系统之调用api添加用户
  6. Java讲课笔记28:Path接口和Files工具类
  7. 基于JAVA+Servlet+JSP+MYSQL的图书馆座位预约系统
  8. 【C++】类型转换(const_cast、dynamic_cast、static_cast、reinterpret_cast)
  9. 计算机做表格软件有哪些内容,制作表格软件,详细教您excel怎么制作表格
  10. 奇妙的 10^n + 1
  11. c语言第五章函数课后答案,C语言程序设计(第2版) 刘克威,张凌晓著 习题答案-第五章...
  12. x5开源库后续知识点
  13. MCMC如何满足细致平稳条件?
  14. java反射为什么消耗资源_Java反射笔记
  15. 全网最详细的渗透测试靶机实操步骤——vulnhub靶机实战(七)IMF【包含了sql注入,文件上传,gif图片木马制作,缓冲区溢出漏洞sploit等诸多知识点的靶机,超多干货】
  16. GBase 8s 产品简介
  17. linux及安全期中总结——20135227黄晓妍
  18. 看不见的共享电单车战争
  19. 开源网络管理软件-RANCID【新任帮主】
  20. PHP 服务契约,laravel 契约的使用

热门文章

  1. Postgresql中 copy from 的使用
  2. 【OpenCv】检测黑白棋
  3. 电工基础知识(纯干货)
  4. 23种设计模式总结篇【理解及应用】
  5. HTML所有标签演示
  6. 两款推荐的Windows LDAP 客户端工具
  7. Linux极速上手,超全面总结
  8. 企业战略管理教材推荐,它可能是战略管理最好的书
  9. Java高级特性编程及事件_java高级特性是什么
  10. python吃显卡还是内存不足_解决pytorch GPU 计算过程中出现内存耗尽的问题