C++实现LeetCode(1-150)


LeetCode_CPP版题目及答案


更多: github_LeetCode_CPP版题目及答案.

1、两数之和

给定一个整数数组 nums 和一个目标值 target,
请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。

给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int len = nums.size();
        map<int, int> mp;
        vector<int> vec(2,0);
        for(int i = 0; i < len; i++){
            int other = target - nums[i];
            if(mp.count(other) > 0 && mp[other] != i){
                vec[0] = mp[other];
                vec[1] = i;
                break;
            }
            mp[nums[i]] = i;
        }
        return vec;
    }
};

2、两数相加

给出两个非空的链表用来表示两个非负的整数。
其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储一位数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字0之外,这两个数都不会以0开头。

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

// 各个节点相加,当大于9时产生进位1,加在下一个节点中
// 最后一组节点若产生进位,则需要再次新增一个节点值为1;没有进位则结束
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* L = new ListNode(0);
        ListNode* result = L;
        int flag = 0;
        while(l1 != NULL || l2 != NULL){
            int sum_ = 0;
            if(l1){
                sum_ += l1->val;
                l1 = l1->next;
            }
            if(l2){
                sum_ += l2->val;
                l2 = l2->next;
            }
            if(flag) sum_+=1;
            L->next = new ListNode(sum_ % 10);
            if(sum_ > 9) flag = 1;
            else flag = 0;
            L = L->next;
        }
        if(flag) L->next = new ListNode(1);
        return result->next;
    }
};

3、无重复字符的最长子串(medium)

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

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

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

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

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        // 双指针; aim,end
        if(s.size() == 0 || s.size() == 1) return s.size();
        int p = 0, max_len = 1;
        for(int end = 1;end < s.size(); end++){
            for(int aim = p; aim < end; aim++){
                if(s[aim] == s[end]){
                    p = aim + 1;
                    if(max_len < end - aim) max_len = end - aim;
                    break;
                }
            }
            if(max_len < end - p + 1) max_len = end - p + 1;
        }
        return max_len;
    }
};

4、两个有序数组的中位数(hard)

"""
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 。
请找出这两个有序数组的中位数。 要求算法的时间复杂度为 O(log (m+n))  。
你可以假设 nums1 和 nums2 不同时为空。

# 示例1
nums1 = [1, 3]
nums2 = [2]
中位数是 2.0

# 示例2
nums1 = [1, 2]
nums2 = [3, 4]
中位数是 (2 + 3)/2 = 2.5
"""


class Solution(object):
    def findMedianSortArrays(self, nums1, nums2):
        nums = nums1 + nums2
        nums.sort()
        mid = len(nums) // 2  # 整除
        if mid % 2 == 1:
            return float(nums[mid])
        else:
            return (nums[mid - 1] + nums[mid]) / 2.0


nums1 = [1, 2]
nums2 = [3, 4]
s = Solution()
result = s.findMedianSortArrays(nums1, nums2)
print("result", result)

"""
result 2.5
"""
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        // 参考:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/4-xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-shu/
        int n = nums1.size();
        int m = nums2.size();
        if (n > m) return findMedianSortedArrays(nums2, nums1);
        int L_max1, L_max2, R_min1, R_min2, c1, c2, lo = 0, hi = 2 * n;
        while (lo <= hi) {
            c1 = lo + hi >> 1;  // 二分
            c2 = n + m - c1;

            L_max1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
            R_min1 = (c1 == 2 * n) ? INT_MAX : nums1[c1 / 2];
            L_max2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
            R_min2 = (c2 == 2 * m) ? INT_MAX : nums2[c2 / 2];

            if (L_max1 > R_min2) hi = c1 - 1;
            else if (L_max2 > R_min1) lo = c1 + 1;
            else break;  // L_max1 <= R_min2 && L_max2 <= R_min1 时停止
        } 
        return (max(L_max1, L_max2) + min(R_min1, R_min2)) / 2.0;
    }
};

5、最长回文子串(medium)

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

示例 1:
输入: "babad"
输出: "bab"
注意: "aba"也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
"""


class Solution(object):
    longest_s = ""  # 最长回文子串
    maxLen = 0  # 长度

    def longestPalindrome(self, s):
        """
        :param s: str
        :return: str
        """
        len_s = len(s)
        if len_s == 1:
            return s
        for i in range(len_s):
            # 单核
            self.find_longest_Palindrome(s, i, i)
            self.find_longest_Palindrome(s, i, i + 1)
        return self.longest_s

    def find_longest_Palindrome(self, s, low, high):
        # 从中间向两端延伸
        while low >= 0 and high < len(s):
            if s[low] == s[high]:
                low -= 1
                high += 1
            else:
                break
        # high - low - 1表示当前字符串长度
        if high - low - 1 >= self.maxLen:
            self.maxLen = high - low - 1
            self.longest_s = s[low + 1:high]


str = "cbbd"
s = Solution()
result = s.longestPalindrome(str)
print("result:", result)

"""
result: bb
"""
class Solution {
public:
    // 中心扩散
    string longestPalindrome(string s) {
        int len_1, len_2, len_ = 1;
        int start = 0, end = 0;
        for(int i = 0; i < s.size(); i++){
            len_1 = expandAroundCenter(s, i, i);
            len_2 = expandAroundCenter(s, i, i + 1);
            len_ = max(len_, max(len_1, len_2));
            if(len_ > end - start + 1){
                start = i - (len_ - 1) / 2;
                end  = i + len_ / 2;
            }   
        }
        return s.substr(start, len_);
    }
    
private:
    int expandAroundCenter(string s, int low, int high){
        while(low >= 0 && high < s.size()){
            if(s[low] == s[high]){
                low--;
                high++;
            }
            else break;
        }
        return high - low - 1;
    }
};

6、Z字形变换

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:
L   C   I   R
E T O E S I I G
E   D   H   N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"。

输入: s = "LEETCODEISHIRING", numRows = 4
输出: "LDREOEIIECIHNTSG"
解释:
L     D     R
E   O E   I I
E C   I H   N
T     S     G

class Solution {
public:
    string convert(string s, int numRows) {
        if(numRows == 1) return s;
        vector<string> res(min(int(s.size()), numRows));
        bool direction = false;
        int cur_row= 0;
        for(char s_ : s){
            res[cur_row] += s_;
            if(cur_row == 0 || cur_row == numRows - 1) direction = !direction;
            cur_row += direction ? 1 : -1;
        }
        string str;
        for(string res_: res) str += res_;
        return str;
    }
};

7、整数反转

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

输入: 123
输出: 321

输入: -123
输出: -321

class Solution {
public:
    int reverse(int x) {
        // 考虑溢出问题
        int ret = 0;
        while(x){
            int pop = x % 10;
            x = x / 10;
            if(ret > INT_MAX / 10 || (ret == INT_MAX / 10 && pop > 7)) return 0;  // 正数溢出
            if(ret < INT_MIN / 10 || (ret == INT_MIN / 10 && pop < -8 )) return 0;  // 负数溢出
            ret = ret * 10 + pop;
        }
        return ret;
    }
};

8、字符串转换整数(atoi)

输入: "42"
输出: 42

输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。

输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。因此无法执行有效的转换。
     
输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。因此返回 INT_MIN (231)class Solution {
public:
    int myAtoi(string str) {
        int flag = 1, i = 0,res = 0;
        while (str[i] == ' ') i++;
        if (str[i] == '-') flag = -1; 
        if (str[i] == '-' || str[i] == '+') i++; 
        while (i < str.size() && isdigit(str[i])){
            int r = str[i] - '0';
            if (res > INT_MAX / 10 || (res == INT_MAX / 10 && r > 7)) {  // 处理溢出
                return flag > 0 ? INT_MAX : INT_MIN;
            }
            res = 10 * res + r;
            i++;
        }
        return flag > 0 ? res : -res;
    }
};

9、回文数

输入: 121
输出: true

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

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

class Solution {
public:
    bool isPalindrome(int x) {
        if(x < 0 || (x != 0 && x % 10 == 0)) return false;
        int expect_num = 0;
        // 反转一半数字
        while(x > expect_num){
            expect_num = expect_num * 10 + x % 10;
            x = x / 10;
        }
        return x == expect_num || x == expect_num / 10;
    }
};

10、正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.''*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素

输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c'0, 'a' 被重复一次。因此可以匹配字符串 "aab"class Solution {
public:
    vector<vector<int>> f;
    int n, m;
    bool isMatch(string s, string p) {
        n = s.size();
        m = p.size();
        f = vector<vector<int>> (n + 1, vector<int>(m + 1, -1));
        return dp(0, 0, s, p);
    }
    
    // 动态规划 
    bool dp(int x, int y, string &s, string &p) {
        if (f[x][y] != -1) return f[x][y];
        if(y == m){
            return f[x][y] = x == n;
        }
        bool first_con = (x < n) && (s[x] == p[y] || p[y] == '.');
        bool ans;
        if (y + 1 < m && p[y + 1] == '*') {
            ans = dp(x, y + 2, s, p) || first_con && dp(x + 1, y, s, p);
        }
        else {
            ans = first_con  && dp(x + 1, y + 1, s, p);
        }
        return f[x][y] = ans;
    }
};

11、盛最多水的容器(medium)

给定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

class Solution {
public:
    int maxArea(vector<int>& height) {
        int i = 0, j = height.size() - 1;
        int area = (j - i) * min(height[i], height[j]);
        while (i < j) {
            area = max(area, (j - i) * min(height[i], height[j]));
            if (height[i] < height[j]) i++;
            else j--;
        }
        return area;
    }
};

12、整数转罗马数字

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符          数值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000
例如, 罗马数字 2 写做 II ,即为两个并列的 112 写做 XII ,即为 X + II 。 
27 写做  XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。
但也存在特例,例如 4 不写做 IIII,而是 IV。
数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。
同样地,数字 9 表示为 IX。

输入: 3
输出: "III"

输入: 58
输出: "LVIII"
解释: L = 50, V = 5, III = 3.

class Solution {
public:
    string intToRoman(int num) {
        map<int, string> mp;
        mp[1] = "I";
        mp[4] = "IV";
        mp[5] = "V";
        mp[9] = "IX";
        mp[10] = "X";
        mp[40] = "XL";
        mp[50] = "L";
        mp[90] = "XC";
        mp[100] = "C";
        mp[400] = "CD";
        mp[500] = "D";
        mp[900] = "CM";
        mp[1000] = "M";    
        int nums[13] = {1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000};
        int t = 12;
        string str="";
        while (num) {
            if (num >= nums[t]) {
                str += mp[nums[t]];
                num -= nums[t];
            }
            else t--;
        }
        return str;
    }
};

13、罗马数字转整数

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符          数值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

输入: "III"
输出: 3

输入: "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3

class Solution {
public:
    int romanToInt(string s) {
        int total[256];
        total['I'] = 1;
        total['V'] = 5;
        total['X'] = 10;
        total['L'] = 50;
        total['C'] = 100;
        total['D'] = 500;
        total['M'] = 1000;
        int num = 0;
        for (int i = 0; i < s.size(); i++){
            if((i == s.size() - 1) || total[s[i]] >= total[s[i + 1]]) {
                num += total[s[i]];
            }
            else 
                num -= total[s[i]];
        }
        return num;
    }
};

14、最长公共前缀

"""
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。

示例 1:
输入: ["flower","flow","flight"]
输出: "fl"

示例 2:
输入: ["dog","racecar","car"]
输出: ""
"""

class Solution(object):
    # 方法1
    def longestCommonPrefix1(self, strs):
        """
        :param strs: List[str]
        :return: str
        """
        len_str = len(strs)
        if len_str == 0:
            return ''
        list_len = []
        for i in range(len_str):
            list_len.append(len(strs[i]))
        list_len.sort()
        # 取出最短的子串
        # 我这里是直接取第一个子串的前min_len
        min_len = list_len[0]
        b0 = strs[0][0:min_len]  # 最短的子串
        com = b0
        for s in strs:
            for j in range(list_len[0]):
                if s[j] != com[j]:
                    # 判断到有不等的地方
                    a0 = s[0:j]
                    if len(b0) >= len(a0):  # 上一个最长公共前缀是否比现在长
                        b0 = a0
        return b0

    # 方法2:高级
    def longestCommonPrefix2(self, strs):
        res = ""
        if len(strs) == 0:
            return ""
        for each in zip(*str):
            if (len(set(each))) == 1:
                res += each[0]
            else:
                return res
        return res


str = ["flower", "flow", "flight"]
s = Solution()
result = s.longestCommonPrefix2(str)
print("result:", result)
class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        int len_ = strs.size();
        int count_ = INT_MAX;
        string result = "";
        for (int i = 0; i < len_; i++) {
            if(count_ >= strs[i].size()) count_ = strs[i].size();
        }
        for (int i = 0; i < count_; i++){
            int j;
            for(j = 1; j < len_; j++){
                if(strs[0][i] == strs[j][i]) continue;
                else break;
            }
            if (j == len_) result += strs[0][i];
            else break;
        }
        return result;
    }
};

15、三数之和

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int len_ = nums.size();
        int left,right,i;
        vector<vector<int>> res;
        for (i = 0; i < len_ - 2; i++) {
            if (nums[i] > 0) break;
            if (i > 0 && nums[i - 1] == nums[i]) continue;
            left = i + 1, right = len_ - 1;
            while(left < right){
                if (nums[i] + nums[left] + nums[right] == 0) {
                    res.push_back({nums[i], nums[left], nums[right]});
                    left++;
                    while (left < right && nums[left] == nums[left-1]) left++;
                    right--;
                    while (left < right && nums[right] == nums[right+1]) right--;
                }
                else if(nums[i] + nums[left] + nums[right] < 0) left++;
                else right--;
            }
        }
        return res;
    }
};

16、最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。
找出 nums 中的三个整数,使得它们的和与 target 最接近。
返回这三个数的和。假定每组输入只存在唯一答案。

例如,给定数组 nums = [-121-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        // 和与目标值接近
        sort(nums.begin(), nums.end());
        int close_with_target = nums[0] + nums[1] + nums[2];
        for (int i = 0; i < nums.size() - 2; i++) {
            int digit1 = i;
            int digit2 = i + 1;
            int digit3 = nums.size() - 1;
            int tmp = nums[digit1] + nums[digit2] + nums[digit3];
            while (digit2 < digit3) {
                if (abs(tmp - target) < abs(close_with_target - target)) {
                    close_with_target = tmp;
                }
                int dif = target - tmp;
                if (dif == 0) return target;
                if (dif > 0) {
                    digit2++;
                }
                else digit3--;
                tmp = nums[digit1] + nums[digit2] + nums[digit3];
            }
        }
        return close_with_target;
    }
};

17、电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        vector<string> res;
        map<char, string> mp = {{'2', "abc"}, {'3', "def"}, {'4', "ghi"},
                                {'5', "jkl"}, {'6', "mno"}, {'7', "pqrs"},
                                {'8', "tuv"}, {'9', "wxyz"}};
        int size_ = digits.size();
        queue<string> que;
        for (int i = 0; i < mp[digits[0]].size(); i++) {
            string str;
            str.push_back(mp[digits[0]][i]);  // char转string
            que.push(str);
        }
        for (int i = 1; i < size_; i++) {
            int len_ = que.size();
            while (len_--) {
                for (int j = 0; j < mp[digits[i]].size(); j++) {
                    string s_front = que.front();
                    s_front += mp[digits[i]][j];
                    que.push(s_front);
                }
                que.pop();
            }
        }
        while (!que.empty()) {
            res.push_back(que.front());
            que.pop();
        }
        return res;
    }
};

18、四数之和

给定一个包含 n 个整数的数组 nums 和一个目标值 target,
判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?
找出所有满足条件且不重复的四元组。
注意:答案中不可以包含重复的四元组。

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]

思路:
使用四个指针(a<b<c<d)。固定最小的a和b在左边,c=b+1,d=_size-1 移动两个指针包夹求解。
 保存使得nums[a]+nums[b]+nums[c]+nums[d]==target的解。偏大时d左移,偏小时c右移。c和d相
 遇时,表示以当前的a和b为最小值的解已经全部求得。b++,进入下一轮循环b循环,当b循环结束后。
 a++,进入下一轮a循环。 即(a在最外层循环,里面嵌套b循环,再嵌套双指针c,d包夹求解)

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        int size_ = nums.size();
        for (int a = 0; a < size_ - 3; a++) {
            if(a > 0 && nums[a] == nums[a - 1]) continue;
            for (int b = a + 1; b < size_ - 2; b++) {
                if (b > a + 1 && nums[b] == nums[b - 1]) continue;
                int c = b + 1;
                int d = size_ - 1;
                while (c < d) {
                    if (nums[a] + nums[b] + nums[c] + nums[d] < target) {
                        c++;
                    }
                    else if (nums[a] + nums[b] + nums[c] + nums[d] > target) {
                        d--;
                    }
                    else {
                        res.push_back({nums[a], nums[b], nums[c], nums[d]});
                        c++;
                        while (c < d && nums[c - 1] == nums[c]) c++;
                        d--;
                        while (c < d && nums[d + 1] == nums[d]) d--;
                    }
                }
            }
        }
        return res;
    }
};

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

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.

/**
 * 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) {
        ListNode* p1 = head;
        ListNode* p2 = head;
        int i;
        for (i = 0; p1 != NULL; i++) {
            if (i > n) p2 = p2 -> next;
            p1 = p1 -> next;
        }
        if (i == n) return head -> next;
        ListNode* p3 = p2 -> next;
        p2 -> next = p3 -> next;
        return head;
    }
};

20、有效的括号

给定一个只包括 '('')''{''}''['']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

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

class Solution {
public:
    bool isValid(string s) {
        stack<int> stk;
        int len_ = s.size();
        for (int i = 0; i < len_; i++) {
            if (s[i] == '(' || s[i] == '[' || s[i] == '{') {
                stk.push(s[i]);
            }
            else{
                if (stk.empty()) return false;
                else {
                    if (s[i] == ')' && stk.top() != '(') return false;
                    if (s[i] == ']' && stk.top() != '[') return false;
                    if (s[i] == '}' && stk.top() != '{') return false;
                    stk.pop();
                }
            }
        }
        return stk.empty();
    }
};

21、合并两个有序链表(easy)

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

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

/**
 * 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* head = new ListNode(0);
        ListNode* p = head;
        if (l1 == NULL) return l2;
        if (l2 == NULL) return l1;
        while (l1 && l2) {
            if (l1 -> val <= l2 -> val) {
                head -> next = l1;
                l1 = l1 -> next;
            }
            else {
                head -> next = l2;
                l2 = l2 -> next;
            }
            head = head -> next;
        }
        if (l1) {
            head -> next = l1;
        }
        else {
            head -> next = l2;
        }
        return p -> next;
    }
};

22、括号生成(medium)

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

例如,给出 n = 3,生成结果为:
[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

class Solution {
public:
    // 回溯法
    vector<string> generateParenthesis(int n) {
        vector<string> res;
        backTrack(res, "", n, 0);
        return res; 
    }

    // n:可用的左括号(的个数;   index:可用的右括号)的个数;
    void backTrack(vector<string> &res, string track, int n, int index){
        if (n == 0 && index == 0) res.push_back(track);
        else {
            if (n > 0) backTrack(res, track + "(", n - 1, index + 1);  // 使用一个左括号,就会生成一个右括号要使用
            if (index > 0) backTrack(res, track + ")", n, index - 1);  // 使用右括号
        }

    }
};

23、合并K个排序链表(hard)

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

输入:
[
  1->4->5,
  1->3->4,
  2->6
]
输出: 1->1->2->3->4->4->5->6

/**
 * 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) {
        // merge两两合并
        int size_ = lists.size();
        if (size_ == 0) return NULL;
        if (size_ == 1) return lists[0];
        ListNode* p = lists[0];
        for (int i = 1; i < size_; i++) {
            p = merge(p, lists[i]); 
        }
        return p;
    }

    ListNode* merge(ListNode* L1, ListNode* L2){
        ListNode* Head = new ListNode(0);
        ListNode* pHead = Head;
        while (L1 && L2) {
            if (L1 -> val < L2 -> val) {
                Head -> next = L1;
                L1 = L1 -> next;
            }
            else {
                Head -> next = L2;
                L2 = L2 -> next;
            }
            Head = Head -> next;
        }
        Head -> next = L1 ? L1 : L2;
        return pHead -> next;
    }
};

24、两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

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

/**
 * 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* pNext = head->next;
        // head->next = swapPairs(pNext->next);
        // pNext->next = head;
        // return pNext;

        // 非递归
        ListNode* p = new ListNode(0);
        p->next = head;
        ListNode* tmp = p; 
        while (tmp->next != NULL && tmp->next->next !=NULL) {
            ListNode* start = tmp->next;
            ListNode* end = tmp->next->next;
            start->next = end->next;
            end->next = start;
            tmp->next = end;
            tmp = start;
        }
        return p->next;
    }
};

25、K个一组翻转链表(hard)

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

给定这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5

说明 :
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* pre = dummy;
        ListNode* end = dummy;
        while (end->next) {
            for (int i = 0; i < k && end!=NULL; i++) end = end->next;
            if (end == NULL) break;
            ListNode* start = pre->next;
            ListNode* next_ = end->next;
            end->next = NULL;
            pre->next = reverse(start);
            start->next = next_;
            pre = start;
            end = start;
        }
        return dummy->next;   
    }

    ListNode* reverse(ListNode* head){
        ListNode* p = head;
        ListNode* pre = NULL;
        while (head) {
            ListNode* tmp = head->next;
            head->next = pre;
            pre = head;
            head = tmp;
        }
        return pre;
    }
};

26、删除排序数组中的重复项

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

给定 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 len_ = nums.size();
        if (len_ == 0) return 0;
        int i = 0;
        for (int j = 1; j < len_; j++) {
            if (nums[i] != nums[j]) {
                i++;
                nums[i] = nums[j];
            }
        }
        return i + 1;
    }
};

27、移除元素

给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        // 双指针
        int i = 0;
        for (int j = 0; j < nums.size(); j++) {
            if (nums[j] != val) {
                nums[i++] = nums[j];
            }
        }
        return i;
    }
};

28、实现str()

实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,
在 haystack 字符串中找出 needle 字符串出现的第一个位置 (0开始)。
如果不存在,则返回  -1。

输入: haystack = "hello", needle = "ll"
输出: 2

class Solution {
public:
    vector<int> getnext(string p) {
        int len_p = p.size();
        vector<int> next;
        next.push_back(-1);  // 初始值
        int j = 0, k = -1;  // j:后缀; k:前缀
        while (j < len_p - 1) {
            if ((k == -1) || (p[j] == p[k])){
                j++;
                k++;
                next.push_back(k);
            }
            else {
                k = next[k];
            }
        }
        return next;
    }

    int strStr(string haystack, string needle) {
        // KMP字符串匹配
        // 源串不回溯,模式串回溯
        int  i = 0, j = 0;  // i:源串; j:模式串
        int len_ha = haystack.size();
        int len_ne = needle.size();
        vector<int> next;  // 存储模式串匹配时的跳转情况
        next = getnext(needle);
        while ((i < len_ha) && (j < len_ne)) {
            if ((j == -1) || (haystack[i] == needle[j])){
                i++;
                j++;
            }
            else {
                j = next[j];  // 跳转模式串的下一次匹配位置
            }
        }
        if(j == len_ne) return i - j;
        return -1;
    }
};

29、两数相除

给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。

输入: dividend = 10, divisor = 3
输出: 3

输入: dividend = 7, divisor = -3
输出: -2

说明:
被除数和除数均为 32 位有符号整数。
除数不为 0。
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [2^31,  {2^31}1]。
本题中,如果除法结果溢出,则返回 {2^31}1class Solution {
public:
    int divide(int dividend, int divisor) {
        // 全部转换为负数,可以避免正数移位的边界问题
        int flag = dividend > 0 == divisor > 0;  // dividend 与 divisor 同号返回1,异号返回0;
        if (dividend > 0) dividend = -dividend;
        if (divisor > 0) divisor = -divisor;
        int result = 0;
        while (dividend <= divisor) {
            unsigned int temp_result = -1;
            unsigned int temp_divisor = divisor;
            while (dividend <= (temp_divisor << 1)) {
                if (temp_result <= (INT_MIN >> 1)) break;
                temp_result = temp_result << 1;
                temp_divisor = temp_divisor << 1;
            }
            result += temp_result;
            dividend -= temp_divisor;
        }
        if (flag){
            if (result <= INT_MIN) return INT_MAX;
            return -result;
        }
        return result;
    }
};

30、串联所有单词的子串

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

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

输入:
  s = "wordgoodgoodgoodbestword",
  words = ["word","good","best","word"]
输出:[]

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        // 滑动窗口
        // hash表存单词表单词个数,与字符串滑动窗口中单词个数比较 
        vector<int>  res;
        unordered_map<string, int> m1;  // 单词->单词个数
        if (s.empty() || words.empty()) return {};
        int word_size = words[0].size();  // 单词的大小
        int words_num = words.size();  // 单词的个数
        for (auto w : words) m1[w]++;

        for (int i = 0; i < word_size; i++){
            int left = i, right = i, count_ = 0;
            unordered_map<string, int> m2;
            while (right + word_size <= s.size()) {  // 滑动窗口
                string w = s.substr(right, word_size);  // 从S中提取一个单词拷贝到w
                right += word_size;  // 有边界右移一个单词的长度;
                if (m1.count(w) == 0) {  // 单词不在words中
                    count_ = 0;
                    left = right;
                    m2.clear();
                }
                else {  // 单词在words中,添加到m2中
                    m2[w]++;
                    count_++;
                    while (m2.at(w) > m1.at(w)) {  // 一个单词匹配多次,需要缩小窗口
                        string t_w = s.substr(left, word_size);
                        count_--;
                        m2[t_w]--;
                        left += word_size;
                    }
                    if (count_ == words_num) res.push_back(left);
                }
            }
        }
        return res;        
    }
};

31、下一个排列

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,31,3,2
3,2,11,2,3
1,1,51,5,1

class Solution {
public:
    // 翻转nums数组start之后的数,包含nums[start]
    void reverse(vector<int>& nums, int start){
        int i = start, j = nums.size() - 1;
        while (i < j) {
            swap(nums[i], nums[j]);
            i++;
            j--;
        }
    }

    void nextPermutation(vector<int>& nums) {
        int i = nums.size() - 1;
        while (i-1 >= 0 && nums[i-1] >= nums[i]) {  // 找到nums[i-1]
            i--;
        }
        if (i-1 >= 0) {
            int j = nums.size() - 1;
            while (j >= 0 && nums[j] <= nums[i-1]) {  // 找到与nums[i-1]反转的nums[j]
                j--;
            }
            swap(nums[i-1],  nums[j]);
        }
        reverse(nums, i);
    }
};

32、最长有效括号

给定一个只包含 '('')' 的字符串,找出最长的包含有效括号的子串的长度。

输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"

输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"

// 思路:
// 利用两个计数器 left 和 right 。
// 首先,我们从左到右遍历字符串,对于遇到的每个 ‘(’,我们增加 left ;
// 对于遇到的每个 ‘)’ ,我们增加 right 计数器。
// 每当 left == right ,计算有效字符串的长度,记录找到的最长子字符串。
// 如果 right > left,我们将 left 和 right 计数器同时变回 0 。
// 接下来,我们从右到左做一遍类似的工作

// 时间复杂度: O(n) 。遍历两遍字符串。
// 空间复杂度: O(1) 。仅有两个额外的变量 left 和 right 。

class Solution {
public:
    int longestValidParentheses(string s) {
        // 双边遍历; left,right计数
        int left = 0, right = 0, max_len = 0;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == '(') left++;
            else right++;
            if (left == right) max_len = max(max_len, right * 2);
            else if (right > left) {
                left = 0;
                right = 0;
            }

        }
        left = 0, right = 0;
        for (int i = s.size() - 1; i >= 0; i--) {
            if (s[i] == ')') right++;
            else left++;
            if (left == right) max_len = max(max_len, left * 2);
            else if (left > right) {
                left = 0;
                right = 0;
            }

        }
        return max_len;
    }
};

33、搜索旋转排序数组(medium)

假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

class Solution {
public:
    int search(vector<int>& nums, int target) {
        // 二分查找; 查看[left, mid]是否升序,再根据其他条件判断查找范围
        int left = 0, mid = 0, right = nums.size() - 1;
        while (left <= right) {
            mid = (left + right) >> 1; 
            if (nums[mid] == target) return mid;
            if (nums[left] <= nums[mid]) {  // 左边升序,或者mid == left
                if (nums[left] <= target && target <= nums[mid]) right = mid - 1;  // 在左边范围内
                else left = mid + 1;
            }
            else {  // 右边升序
                if (nums[mid] <= target && target <= nums[right]) left = mid + 1;  // 在右边范围内
                else right = mid - 1;
            }
        }
        return -1;
    }
};

34、排序数组查找元素的第一和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        // 二分查找
        int left = 0, mid = 0,  right = nums.size() - 1;
        vector<int> res = {-1,-1};
        if (nums.size() == 0 || nums[left] > target || nums[right] < target) return res;  // 边界
        while (left < right) {  // 找到左边第一个元素
            mid = left + right >> 1;
            if (nums[mid] >= target) right = mid;
            else left = mid + 1;
        }
        if (nums[right] == target) res[0] = right;
        right = nums.size();  // 原因:考虑数组长度为1的情况,不能设置为right = nums.size()-1
        while (left < right) {  // 查找右边的第一个元素
            mid = left + right >> 1;
            if (nums[mid] > target) right = mid;
            else left = mid + 1;
        }
        if (nums[right - 1] == target) res[1] = right - 1;
        return res; 
    }
};

35、搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。
如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。

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

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

输入: [1,3,5,6], 7
输出: 4

输入: [1,3,5,6], 0
输出: 0

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        // 二分查找
        int left = 0, mid = 0, right = nums.size() - 1;
        if (target < nums[left]) return 0;
        if (target > nums[right]) return nums.size();
        while (left < right) {
            mid = left + right >> 1;
            if (nums[mid] > target) right = mid;
            else if(nums[mid] == target) return mid;
            else left = mid + 1;
        }
        return left;
    }
};

36、有效的数独

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
数独部分空格内已填入了数字,空白格用 '.' 表示。

输入:
[
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: true

输入:
[
  ["8","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
     但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
     
说明:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 '.' 。
给定数独永远是 9x9 形式的。

class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        // 9个宫格,宫格内统计字符的数量,每个宫格3*3大小
        // -宫格0-|-宫格1-|-宫格2-
        // -宫格3-|-宫格4-|-宫格5-
        // -宫格6-|-宫格7-|-宫格8-
        map<int, map<char, int>> mp_rows;
        map<int, map<char, int>> mp_cols;
        map<int, map<char, int>> mp_boxes;
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    int n = (i / 3) * 3+ (j / 3);
                    mp_rows[i][board[i][j]]++;  // 行方向计数
                    mp_cols[j][board[i][j]]++;  // 列方向计数
                    mp_boxes[n][board[i][j]]++;  // box宫格内计数
                    if (mp_rows[i][board[i][j]] > 1 || mp_cols[j][board[i][j]] > 1 || mp_boxes[n][board[i][j]] > 1){
                        return false;
                    }
                }
            }
        }
        return true;
    }
};

37、解数独

编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 '.' 表示。

Note:
给定的数独序列只包含数字 1-9 和字符 '.' 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的。

class Solution {
public:
    vector<set<int>> rows, cols, boxes;

    void update(vector<vector<char>> & board) {
        set<int> s = {1,2,3,4,5,6,7,8,9};
        for (int i = 0 ; i < 9; i++) {
            rows.push_back(s);
            cols.push_back(s);
            boxes.push_back(s);
        }
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    int tmp = board[i][j] - '0';
                    rows[i].erase(tmp);
                    cols[j].erase(tmp);
                    boxes[i / 3 + j / 3 * 3].erase(tmp);
                }
            }
        }
        return ;
    }

    bool check(vector<vector<char>> &board, const int &i, const int &j, int num) {
        if (rows[i].find(num) != rows[i].end() && cols[j].find(num) != cols[j].end() && boxes[i / 3 + j / 3 * 3].find(num) != boxes[i / 3 + j / 3 * 3].end()) return true;
        return false;
    }

    int flag = 0;

    // dfs + 回溯
    void dfs(vector<vector<char>> & board, int count) {
        if(count == 81) {
            flag = 1;
            return ;
        }
        int i = count / 9, j = count % 9;
        if ( board[i][j] == '.' ){
            for (int k = 1; k < 10; k++) {  // 检查 1 ~ 9 中数字哪一个可以放入该位置
                if (check(board, i, j, k)) {
                    rows[i].erase(k);
                    cols[j].erase(k);
                    boxes[i / 3 + j / 3 * 3].erase(k);
                    board[i][j] = k + '0';
                    dfs(board, count + 1);
                    if (!flag) {
                    rows[i].insert(k);
                    cols[j].insert(k);
                    boxes[i / 3 + j / 3 * 3].insert(k);
                    board[i][j] = '.';
                    }
                    else return ;
                }
            }
        }
        else dfs(board, count + 1);
        return ;
    }

    void solveSudoku(vector<vector<char>>& board) {
        update(board);
        dfs(board, 0); 
    }
};

38、报数

报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:
1.     1
2.     11
3.     21
4.     1211
5.     111221
1 被读作  "one 1"  ("一个一") ,1111 被读作 "two 1s" ("两个一",2121 被读作 "one 2",  "one 1""一个二" ,  "一个一") ,1211。
给定一个正整数 n(1 ≤ n ≤ 30),输出报数序列的第 n 项。
注意:整数顺序将表示为一个字符串。

输入: 1
输出: "1"

输入: 4
输出: "1211"

class Solution {
public:
    string countAndSay(int n) {
        // 递归,通过n-1个获取第n个
        if (n == 1) return "1";
        string str_old = countAndSay(n - 1);
        string str_new = "";
        char begin = str_old[0];
        int count_ = 1;
        for (int i = 1; i < str_old.size(); i++) {
            if (str_old[i] == begin) {
                count_++;
            }
            else {
                str_new += to_string(count_) + begin;
                begin = str_old[i];
                count_ = 1;
            }
        }
        if (begin == str_old[str_old.size() - 1]) str_new += to_string(count_) + begin;
        return str_new;
    }
};

39、组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以 无限制重复被选取。

说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。 

输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]

输入: candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

class Solution {
public:
    vector<vector<int>> res;  // 所有满足条件的数组集合
    vector<int> t;  // 单个满足条件的数组

    void helper(vector<int>& candidates, int target) {
        for(int i = 0; i < candidates.size(); i++){
            if (candidates[i] > target || (t.size() && candidates[i] < t[t.size() - 1])) continue;  // 防止出现重复的数
            t.push_back(candidates[i]);
            if (candidates[i] == target) res.push_back(t); 
            else helper(candidates, target - candidates[i]);
            t.pop_back();
        }
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        res.clear();
        t.clear();
        helper(candidates, target);
        return res;
    }
};

40、组合总和II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中 只能使用一次。

说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。 

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]

class Solution {
public:
    vector<int> candidates;
    vector<vector<int>> res;
    vector<int> path;

    void DFS(int start, int target) {
        if (target == 0) res.push_back(path);
        for (int i = start; i < candidates.size() && target >= candidates[i]; i++) {
            if (i > start && candidates[i] == candidates[i - 1]) continue;
            path.push_back(candidates[i]);
            DFS(i + 1, target - candidates[i]);
            path.pop_back();
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        this->candidates = candidates;
        DFS(0, target);
        return res;
    }
};

41、缺失的第一个正数

给定一个未排序的整数数组,找出其中没有出现的最小的正整数。

说明:
你的算法的时间复杂度应为O(n),并且只能使用常数级别的空间。

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

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

输入: [7,8,9,11,12]
输出: 1

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        // 首次出现最小的正整数一定是小于等于size_ + 1
        int size_ = nums.size();
        vector<int> tmp(size_ + 1, 0);
        for(int i = 0; i < size_; i++) {
            if (nums[i] > 0 && nums[i] <= size_) {
                tmp[nums[i]] = 1;
            }
        }
        int i = 1;
        while (i <= size_ && tmp[i] != 0) {
            i++;
        }
        return i;
    }
};

42、接雨水(hard)

class Solution {
public:
    int trap(vector<int>& height) {
        // 双指针
        int left = 0, right = height.size() - 1;
        int ans = 0, left_max = 0, right_max = 0;
        while (left < right) {
            if (height[left] < height[right]) {
                if (height[left] >= left_max) left_max = height[left];
                else ans += left_max - height[left];
                left++;
            }
            else {
                if (height[right] >= right_max) right_max = height[right];
                else ans += right_max - height[right];
                right--;
            }
        } 
        return ans;
    }
};

43、字符串相乘

给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。

输入: num1 = "2", num2 = "3"
输出: "6"
示例 2:

输入: num1 = "123", num2 = "456"
输出: "56088"

说明:
num1 和 num2 的长度小于110。
num1 和 num2 只包含数字 0-9。
num1 和 num2 均不以零开头,除非是数字 0 本身。
不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理

class Solution {
public:
    string multiply(string num1, string num2) {
        int len1 = num1.size();
        int len2 = num2.size();
        string res(len1 + len2, '0');
        for (int i = len2 - 1; i >= 0; i--) {
            for (int j = len1 - 1; j >= 0; j--) {
                int temp = (res[i + j + 1] - '0') + (num1[j] - '0') * (num2[i] - '0');
                res[i + j + 1] = temp % 10 + '0';  // 当前位
                res[i + j] += temp / 10;  // 前一位加进位 
            }
        }
        // 去除首位的‘0’
        for (int i = 0; i < len1 + len2; i++) {
            if (res[i] != '0') return res.substr(i);
        }
        return "0";
    }
};

44、通配符匹配

给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?''*' 的通配符匹配。

'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。

说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 ?*。

输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

输入:
s = "aa"
p = "*"
输出: true
解释: '*' 可以匹配任意字符串。

输入:
s = "cb"
p = "?a"
输出: false
解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。

输入:
s = "adceb"
p = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".

输入:
s = "acdcb"
p = "a*c?b"
输入: false

class Solution {
public:
    bool isMatch(string s, string p) {
        // dp[i][j]:s中的前i位是否与P中的第j位相匹配
        int n = s.size(), m = p.size();
        bool dp[n + 1][m + 1] = {false};  // 初始化的bool数组是false
        dp[0][0] = true;
        for (int j = 0; j < m; j++) {  // 初始化
            dp[0][j + 1] = dp[0][j] && p[j] == '*';
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (s[i] == p[j] || p[j] == '?')  // i - 1与j - 1匹配
                    dp[i + 1][j + 1] = dp[i][j];
                if (p[j] == '*') {
                    dp[i + 1][j + 1] = dp[i + 1][j] || dp[i][j + 1];  // p的第j位匹配空:dp[i + 1][j];  p的第j位匹配不为空:dp[i][j + 1];
                }
            }
        }
        return dp[n][m];
    }
};

45、跳跃游戏II

给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。

输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

说明:
假设你总是可以到达数组的最后一个位置。

class Solution {
public:
    int jump(vector<int>& nums) {
        // 贪心法
        // 参考:https://leetcode-cn.com/problems/jump-game-ii/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-10/
        int end = 0;
        int maxPosition = 0;
        int steps = 0;
        for (int i = 0; i < nums.size() - 1; i++) {  // 在i==0的时候,steps增加了1,所以i < nums.size() - 1
            // 找到能跳的最远的
            maxPosition = max(maxPosition, nums[i] + i);
            if (i == end) {  // 遇到边界,更新边界
                end = maxPosition;
                steps++;
            }
        }
        return steps;
    }
};

46、全排列(medium)

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

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

class Solution {
public:
    // 回溯
    void backTrack(int n, vector<int> &nums, vector<vector<int>> &res, int first) {
        if (first == n) res.push_back(nums);
        for(int i = first; i < n; i++) {
            swap(nums[first], nums[i]);
            backTrack(n, nums, res, first + 1);
            swap(nums[first], nums[i]);
        }
    } 

    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        backTrack(nums.size(), nums, res, 0);
        return res;
    }
};

47、全排列II

给定一个可包含重复数字的序列,返回所有不重复的全排列。

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

class Solution {
public:
    // 回溯(辅助函数)
    void backTrack(int n, vector<int> &nums, vector<vector<int>> &res, int first) {
        if (first == n) res.push_back(nums);
        for (int i = first; i < n; i++) {
            bool flag = false;
            for (int j = first; j < i; j++) {
                if (nums[j] == nums[i]) {
                    flag = true;
                    break;
                } 
            } 
            if (flag) continue;
            swap(nums[first], nums[i]);
            backTrack(n, nums, res, first + 1);
            swap(nums[first], nums[i]);            
        }
    }


    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res;
        backTrack(nums.size(), nums, res, 0);
        return res;
    }
};

48、旋转图像

给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。

说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

给定 matrix = 
[
  [1,2,3],
  [4,5,6],
  [7,8,9]
],
原地旋转输入矩阵,使其变为:
[
  [7,4,1],
  [8,5,2],
  [9,6,3]
]

给定 matrix =
[
  [ 5, 1, 9,11],
  [ 2, 4, 8,10],
  [13, 3, 6, 7],
  [15,14,12,16]
], 
原地旋转输入矩阵,使其变为:
[
  [15,13, 2, 5],
  [14, 3, 4, 1],
  [12, 6, 8, 9],
  [16, 7,10,11]
]

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        for (int loop = 0; loop < n / 2 ; loop++)  // 外层循环
            for (int i = loop, j = loop; i < n - 1 - loop; i++) {  // 中层循环
                int temp = matrix[i][j];
                for (int z = 0; z < 4; z++){  // 4个位置旋转
                    int tmpi = i;
                    i = j;
                    j = n - 1 - tmpi;
                    swap(temp,matrix[i][j]);
                }
            }
    }
};

49、字母异位词分组

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

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

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

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector<vector<string>> res;
        map<string, vector<string>> mp;
        for(auto str:strs){
            string tmp = str;
            sort(tmp.begin(), tmp.end());
            mp[tmp].push_back(str);
        }
        for(auto m:mp){
            res.push_back(m.second);
        }
        return res;
    }
};

50、Pow(medium)

实现 pow(x, n) ,即计算 x 的 n 次幂函数。

输入: 2.00000, 10
输出: 1024.00000

输入: 2.10000, 3
输出: 9.26100

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

说明:
-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [231, 2311]class Solution {
public:
    double myPow(double x, int n) {
        double res = 1;
        long long m = n;
        if (m < 0) {
            x = 1 / x;
            m = -m;
        }
        while (m) {
            if (m & 1) {  // E % 2 == 1
                res *= x;
            }
            x *= x;
            m >>= 1;  // m /= 2
        }
        return res;
    }
};

复杂度分析:
时间复杂度:O(logn) 对每一个 n 的二进制位表示,我们都至多需要累乘 1 次,所以总的时间复杂度为 O(logn) 。
空间复杂的:O(1) 我们只需要用到 1 个变量来保存当前的乘积结果。

51、N皇后

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q''.' 分别代表了皇后和空位。
(横竖与左右斜线上仅仅只能出现1个皇后)

输入: 4
输出: [
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。

实现方式:回溯
hills: \ 向右的斜线 row - col == 常数(同一斜线上)
dales: / 向左的斜线 row + col == 常数(同一斜线上)

class Solution {
public:
    int n;
    int rows[100];
    int hills[100];
    int dales[100];
    int queens[100];
    vector<vector<string>> output;

    bool isNotUnderAttack(int row, int col) {
        int res = rows[col] + hills[row - col + n] + dales[row + col];
        return  (res == 0) ? true : false;
    }

    void placeQueen(int row, int col) {
        queens[row] = col;
        rows[col] = 1;
        hills[row - col + n] = 1;
        dales[row + col] = 1;
    }

    void addSolution() {
        vector<string> solution;
        for (int i = 0; i < n; i++) {
            int col = queens[i];
            string s_;
            for (int j = 0; j < col; j++) s_ += ".";
            s_ += "Q";
            for (int j = col + 1; j < n; j++) s_ += ".";
            solution.push_back(s_);
        }
        output.push_back(solution);
    }

    void removeQueen(int row, int col) {
        queens[row] = 0;
        rows[col] = 0;
        hills[row - col + n] = 0;
        dales[row + col] = 0;
    }

    // 回溯
    void backTrack(int row) {
        for (int col = 0; col < n; col++) {
            if (isNotUnderAttack(row, col)) {
                placeQueen(row, col);
                if (row == n - 1) addSolution();
                else backTrack(row + 1);
                removeQueen(row, col);
            }
        }
    }

    vector<vector<string>> solveNQueens(int n) {
        // hills \
        // dales /       
        this -> n = n;
        backTrack(0);
        return output;
    }
};

52、N皇后II

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给定一个整数 n,返回 n 皇后不同的解决方案的数量。

输入: 4
输出: 2
解释: 4 皇后问题存在如下两个不同的解法。
[
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]

class Solution {
public:
    int n, res = 0;
    int rows[100];
    int pie[100];
    int na[100];

    bool isNotAttrack(int row, int col) {
        return rows[col] + pie[row + col] + na[row - col + n] == 0;
    }

    void placeQueen(int row, int col) {
        rows[col] = 1;
        pie[row + col] = 1;
        na[row - col + n] = 1;
    }

    void removeQueen(int row, int col) {
        rows[col] = 0;
        pie[row + col] = 0;
        na[row - col + n] = 0;
    }

    // 回溯
    void backTrack(int row) {
        for (int col = 0; col < n; col++) {
            if (isNotAttrack(row, col)) {
                placeQueen(row, col);
                if (row == n - 1) {
                    res++;
                }
                else backTrack(row + 1);
                removeQueen(row, col);
            }
        }
    }
    
    int totalNQueens(int n) {
        this -> n = n;
        backTrack(0);
        return res;
    }
};

53、最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        int max_1 = nums[0], max_all = nums[0];
        for (int i = 1; i < nums.size(); i++) {
            max_1 = max(max_1 + nums[i], nums[i]);
            max_all = max(max_1, max_all);
        }
        return max_all;
    }
};

复杂度分析
时间复杂度:O(N) 只遍历一次数组。
空间复杂度:O(1) 只使用了常数空间。

54、螺旋矩阵

给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。

输入:
[
 [ 1, 2, 3 ],
 [ 4, 5, 6 ],
 [ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]

输入:
[
  [1, 2, 3, 4],
  [5, 6, 7, 8],
  [9,10,11,12]
]
输出: [1,2,3,4,8,12,11,10,9,5,6,7]

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> res;
        if (matrix.size() == 0) return res;
        int rows = matrix.size();
        int cols = matrix[0].size();
        int left = 0, right = cols - 1, top = 0, bottom = rows - 1;
        while (left <= right && top <= bottom) {
            if (left <= right)  // ->
                for (int i = left; i <= right; i++) 
                    res.push_back(matrix[top][i]);
            if (top < bottom && left <= right)  // 向下
                for (int i = top + 1; i <= bottom; i++)
                    res.push_back(matrix[i][right]);
            if (top < bottom && left < right)  // <-
                for (int i = right - 1; i >= left; i--)
                    res.push_back(matrix[bottom][i]);
            if (top + 1 < bottom && left < right)  // 向上
                for (int i = bottom - 1; i > top; i--)
                    res.push_back(matrix[i][left]);
            left++, right--, top++, bottom--;
        }
        return res;
    }
};

55、跳跃游戏(medium)

给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。

输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 13 步到达最后一个位置。

输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

class Solution {
public:
    bool canJump(vector<int>& nums) {
        // 贪心法
        int index = nums.size() - 1;
        for (int i = index; i >= 0 ; i--) 
            if (i + nums[i] >= index)  // 表示从 i 可以跳到 index 位置,则更新 index ; 其中 index 最初指向终点
                index = i;
        return index == 0;
    }
};

56、合并区间(medium)

给出一个区间的集合,请合并所有重叠的区间。

输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3][2,6] 重叠, 将它们合并为 [1,6].

输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4][4,5] 可被视为重叠区间。

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
    	// 方法1
        vector<vector<int>> res;
        int len_in = intervals.size();
        if(len_in == 0) return res;
        sort(intervals.begin(), intervals.end());
        res.push_back(intervals[0]);
        for (int i = 1, j = 0; i < len_in; i++) {
            if (res[j][1] >= intervals[i][0]) {
                if (res[j][1] < intervals[i][1]) {
                    res[j][1] = intervals[i][1];
                }
            }
            else {
                j++;
                res.push_back(intervals[i]);
            }
        }
        return res; 
	
	// 方法2	
	// 排序 + 指针
        sort(intervals.begin(), intervals.end());
        vector<vector<int>> res;
        for (int i = 0; i < intervals.size(); ) {
            int t = intervals[i][1];
            int j = i + 1;
            while (j < intervals.size() && intervals[j][0] <= t) {
                t = max(t, intervals[j][1]);
                j++;
            }
            res.push_back({intervals[i][0], t});
            i = j;
        }
        return res;	
    }
};

57、插入区间

给出一个无重叠的 ,按照区间起始端点排序的区间列表。
在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。

输入: intervals = [[1,3],[6,9]], newInterval = [2,5]
输出: [[1,5],[6,9]]

输入: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
输出: [[1,2],[3,10],[12,16]]
解释: 这是因为新的区间 [4,8][3,5],[6,7],[8,10] 重叠。

class Solution {
public:
    vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
        intervals.push_back(newInterval);
        vector<vector<int>> res;
        sort(intervals.begin(), intervals.end());
        res.push_back(intervals[0]);
        for (int i = 1, j = 0; i < intervals.size(); i++) {
            if (res[j][1] >= intervals[i][0]) {
                if (res[j][1] < intervals[i][1]) {
                    res[j][1] = intervals[i][1];
                }
            }
            else {
                j++;
                res.push_back(intervals[i]);
            }
        }
        return res;
    }
};

58、最后一个单词的长度

给定一个仅包含大小写字母和空格 ' ' 的字符串,返回其最后一个单词的长度。
如果不存在最后一个单词,请返回 0 。
说明:一个单词是指由字母组成,但不包含任何空格的字符串。

输入: "Hello World"
输出: 5

class Solution {
public:
    int lengthOfLastWord(string s) {
        int end = 0, start = 0, flag = 0;
        for (int i = s.size() - 1; i >= 0; i--) {
            if(flag == 0 && s[i] != ' ') {
                end = i + 1;
                flag = 1;
            }
            if(flag == 1 && s[i] == ' ') {
                start = i + 1;
                break;
            }
        }
        return end - start;
    }
};

59、螺旋矩阵II

给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。

输入: 3
输出:
[
 [ 1, 2, 3 ],
 [ 8, 9, 4 ],
 [ 7, 6, 5 ]
]

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int> (n, 0));
        int num = 0;
        int left = 0, right = n - 1, top = 0, bottom = n - 1;
        while (left <= right && top <= bottom) {
            for (int i = left; i <= right; i++) {
                res[top][i] = ++num;
            }
            if (left <= right && top < bottom) {
                for (int i = top + 1; i <= bottom; i++) {
                    res[i][right] = ++num;
                }
            }
            if (left < right && top < bottom ) {
                for (int i = right - 1; i >= left; i--) {
                    res[bottom][i] = ++num;
                }
            }
            if (left < right && top + 1 < bottom) {
                for (int i = bottom - 1; i > top; i--) {
                    res[i][left] = ++num;
                }
            }
            left++, right--, top++, bottom--;
        }
        return res;
    }
};

60、第k个排列

给出集合 [1,2,3,,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3, 所有排列如下:
"123"
"132"
"213"
"231"
"312"
"321"
给定 n 和 k,返回第 k 个排列。

说明:
给定 n 的范围是 [1, 9]。
给定 k 的范围是[1,  n!]。

输入: n = 3, k = 3
输出: "213"

输入: n = 4, k = 9
输出: "2314"

int factorial(int n){  // 计算阶乘
    int res= 1;
    for (int i = 2; i <= n; i++) {
        res *= i;
    }
    return res;
}

class Solution {
private:
    list<char> ls;
    int this_k;

    int get_k(int n) {  // 假设还剩n个数没有添加,返回需要以该字符开头的那个字符
        int tot = factorial(n - 1);
        int res = 1;
        while (this_k > tot) {
            res++;
            this_k -= tot;
        }
        return res;
    }

    char get_list_k(int k) {  // 获取链表中的第 k 个元素
        auto it = ls.begin();
        for (int i = 0; i < k - 1; i++) 
            it++;
        char res = *it;
        ls.erase(it);
        return res;
    }

public:
    string getPermutation(int n, int k) {
        // 由于集合中的元素没有重复,每一个元素开头的排列的数量均相同,且均为(n-1)!。
        // 由此,我们可以得知,1开头的排列总数为2!=2,同理2,3开头的排列总数也都为2。因此,第3个排列的第一位应当是2。
        this_k = k;
        string res = "";
        for (int i = 1; i <= n; i++) 
            ls.push_back(48 + i);
        for (int i = n; i >= 1; i--)
            res += get_list_k(get_k(i));  // 在字符串末尾添加新的字符
        return res;
    }
};

61、旋转链表

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1: 5->1->2->3->4->NULL
向右旋转 2: 4->5->1->2->3->NULL

输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1: 2->0->1->NULL
向右旋转 2: 1->2->0->NULL
向右旋转 3: 0->1->2->NULL
向右旋转 4: 2->0->1->NULL

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        // 头尾闭环
        // 拆分
        if (head == NULL) return head;
        int count_ = 1;
        ListNode* pHead = head;
        while (pHead -> next) {
            count_++;
            pHead = pHead -> next;
        }
        pHead -> next = head;  // 形成闭环
        int cut_num = count_ - k % count_;
        while (cut_num - 1) {  // 找到尾部的数
            head = head -> next;
            cut_num--;
        }
        pHead = head;  // 尾部非空节点
        head = head -> next;  // 新的头
        pHead -> next = NULL;
        return head;
    }
};

62、不同路径

一个机器人位于一个 m x n 网格的左上角。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。
问总共有多少条不同的路径?
说明:m 和 n 的值均不超过 100。

输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右

输入: m = 7, n = 3
输出: 28

class Solution {
public:
    int uniquePaths(int m, int n) {
        // // 动态规划
        // vector<vector<int>> dp(m, vector<int> (n, 1));
        // for (int i = 1; i < m; i++) {
        //     for (int j = 1; j < n; j++) {
        //         dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        //     }
        // }
        // return dp[m -1 ][n - 1];

        // // 优化1
        // vector<int> pre(n, 1);
        // vector<int> cur(n, 1);
        // for (int i = 1; i < m; i++) {
        //     for (int j = 1; j < n; j++) {
        //         cur[j] = pre[j] + cur[j - 1];
        //     }
        //     pre = cur;
        // }
        // return cur[n - 1];

        // 优化2
        vector<int> cur(n, 1);
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                cur[j] += cur[j-1];
            }
        }
        return cur[n - 1];
    }
};

63、不同路径II

一个机器人位于一个 m x n 网格的左上角。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 10 来表示。

说明:m 和 n 的值均不超过 100。

输入:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        if(obstacleGrid[0][0] == 1) return 0;
        int rows = obstacleGrid.size(), cols = obstacleGrid[0].size();
        long dp[rows][cols];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (0 == i && 0 == j) dp[0][0] = 1;
                else if (0 == i && 0 != j) 
                    dp[i][j] = (obstacleGrid[i][j] == 1) ? 0 : dp[i][j - 1];
                else if (0 != i && 0 == j) 
                    dp[i][j] = (obstacleGrid[i][j] == 1) ? 0 : dp[i - 1][j];
                else dp[i][j] = (obstacleGrid[i][j] == 1) ? 0 : (dp[i - 1][j] + dp[i][j - 1]);
            }
        }
        return dp[rows - 1][cols - 1];
    }
};

64、最小路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 13111 的总和最小。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int rows = grid.size();
        int cols = grid[0].size();
        for (int i = 1; i < rows; i++) {
            grid[i][0] += grid[i - 1][0];  
        }
        for (int i = 1; i < cols ;i++) {
            grid[0][i] += grid[0][i - 1];
        }
        for (int i = 1; i < rows; i++) {
            for (int j = 1; j < cols; j++) {
                grid[i][j] += min(grid[i - 1][j], grid[i][j - 1]);
            }
        }
        return grid[rows - 1][cols - 1];
    }
};

65、有效数字

验证给定的字符串是否可以解释为十进制数字。

"0" => true
" 0.1 " => true
"abc" => false
"1 a" => false
"2e10" => true
" -90e3   " => true
" 1e" => false
"e3" => false
" 6e-1" => true
" 99e2.5 " => false
"53.5e93" => true
" --6 " => false
"-+3" => false
"95a54e53" => false

说明: 我们有意将问题陈述地比较模糊。在实现代码之前,你应当事先思考所有可能的情况。
这里给出一份可能存在于有效十进制数字中的字符列表:
数字 0-9
指数 - "e"/负号 - "+"/"-"
小数点 - "."
当然,在输入中,这些字符的上下文也很重要。

class Solution {
public:
    bool isNumber(string s) {
        int len_s = s.size();
        if (len_s == 0) return false;
        int i = 0, j = s.size() - 1;
        while (s[i] == ' ') i++;
        while (s[j] == ' ') j--;
        if (j == -1) return false;
        s = s.substr(i, j + 1 - i);
        if (s[0] == '+' || s[0] == '-') s = s.substr(1, s.size() - 1);
        if (s.size() == 1 && s[0] == '.') return false;
        int count_n = 0, count_dot = 0, count_e = 0;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] >= '0' && s[i] <= '9') {
                count_n = 1;
                continue;
            }
            else if (s[i] == '.') {
                if (count_dot != 0 || count_e != 0) return false;
                count_dot = 1;
            }
            else if (s[i] == 'e' || s[i] == 'E') {
                if (count_e != 0 || count_n == 0) return false;
                count_e = 1;
                int c = i + 1;
                if (s[c] == '+' || s[c] == '-') c = c + 1;
                if (s[c] == '\0') return false;
                i = c - 1;
            }
            else return false;
        }
        return true;
    }
};

66、加一

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。

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

输入: [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321class Solution {
public:
    vector<int> plusOne(vector<int>& digits) {
        int len_digits = digits.size();
        if (len_digits == 1) {
            if (digits[0] == 9) {
                digits[0] = 0;
                digits.insert(digits.begin(), 1);
            }
            else digits[0]++;
        }
        else digits[len_digits - 1] += 1;
        for (int i = len_digits - 1; i > 0; i--) {
            if (digits[i] % 10 == 0) {
                digits[i] = 0;
                digits[i - 1] = (digits[i - 1] + 1) % 10;
            }
            else {
                break;
            }
        }
        if(digits[0] == 0) digits.insert(digits.begin(), 1);
        return digits;
    }
};

67、二进制求和

给定两个二进制字符串,返回他们的和(用二进制表示)。
输入为非空字符串且只包含数字 10。

输入: a = "11", b = "1"
输出: "100"

输入: a = "1010", b = "1011"
输出: "10101"

class Solution {
public:
    string addBinary(string a, string b) {
        // 补全长度
        // 相加
        int len_a = a.size(), len_b = b.size();
        while (len_a < len_b) {
            a = '0' + a;
            len_a++;
        }
        while (len_a > len_b) {
            b = '0' + b;
            len_b++;
        }
        for (int i = len_a - 1; i > 0; i--) {
            a[i] = a[i] - '0' + b[i];
            if ( a[i] >= '2' ) {
                a[i] = a[i] % 2 + '0';
                a[i - 1] += 1;
            }
        }
        a[0] = a[0] - '0' + b[0];
        if (a[0] >= '2') {
            a[0] = (a[0] - '0') % 2 + '0';
            a = '1' + a;
        }
        return a;
    }
};

68、文本左右对齐


给定一个单词数组和一个长度 maxWidth,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。
你应该使用“贪心算法”来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可用空格 ' ' 填充,使得每行恰好有 maxWidth 个字符。
要求尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。
文本的最后一行应为左对齐,且单词之间不插入额外的空格。

说明:
单词是指由非空格字符组成的字符序列。
每个单词的长度大于 0,小于等于 maxWidth。
输入单词数组 words 至少包含一个单词。

输入:
words = ["This", "is", "an", "example", "of", "text", "justification."]
maxWidth = 16
输出:
[
   "This    is    an",
   "example  of text",
   "justification.  "
]

输入:
words = ["What","must","be","acknowledgment","shall","be"]
maxWidth = 16
输出:
[
  "What   must   be",
  "acknowledgment  ",
  "shall be        "
]
解释: 注意最后一行的格式应为 "shall be    " 而不是 "shall     be",
     因为最后一行应为左对齐,而不是左右两端对齐。       
     第二行同样为左对齐,这是因为这行只包含一个单词。

输入:
words = ["Science","is","what","we","understand","well","enough","to","explain",
         "to","a","computer.","Art","is","everything","else","we","do"]
maxWidth = 20
输出:
[
  "Science  is  what we",
  "understand      well",
  "enough to explain to",
  "a  computer.  Art is",
  "everything  else  we",
  "do                  "
]

class Solution {
public:
    vector<string> fullJustify(vector<string>& words, int maxWidth) {
        vector<string> res;
        int n = words.size();
        for ( int i = 0; i < n; ) {
            int len = 0, num = -1;  // len:当前行的单词总长度;num:当前行包含的空格的数量,单词数 - 1
            while ( len + num <= maxWidth && i < n ) {  // 当前行尚未达到最大,还可以增加单词
                len += words[i].size();
                num++;
                i++;
            }
            if ( len + num > maxWidth ) {  // 当前行已经达到最大,不能增加新的单词
                i--;
                num--;
                len -= words[i].size();
            }
            if ( i != n ) {  // 尚未遍历到最后一个单词
                i -= num + 1;
                int blank = maxWidth - len;
                if ( num > 0) {
                     vector<int> blanks(num, blank / num);
                    for ( int j = 0; j < blank % num; j++ ) blanks[j] += 1;
                    string s;
                    int j;
                    for ( j = 0; j < blanks.size(); j++) {  // 当前行去除最后一个单词后添加
                        s.append(words[i + j]);
                        s.append(blanks[j], ' ');
                    }
                    for ( ; j < num + 1; j++) s.append(words[i+j]);  // 当前行的最后一个单词添加
                    res.push_back(s);
                }
                else {  // 当前行只能有一个单词
                    string s = words[i];
                    s.append(blank, ' ');
                    res.push_back(s);
                }
            }
            else {  // 遍历到最后一个单词
                i -= num + 1;
                string s;
                for ( int j = 0; j < num; j++ ) {  // 当前行去除最后一个单词后的添加
                    s.append(words[i + j]);
                    s.append(" ");
                }
                s.append(words[i + num]);
                s.append(maxWidth - len - num, ' ');
                res.push_back(s);
            }
            i += num + 1;
        }
        return res;
    }
};

69、X的平方根(easy)

实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

输入: 4
输出: 2

输入: 8
输出: 2
说明: 8 的平方根是 2.82842..., 
     由于返回类型是整数,小数部分将被舍去。

// 二分查找
class Solution {
public:
    int mySqrt(int x) {
        int left = 0, mid = 0, right = x;
        if (x <= 1) return x;
        while (left < right) {
            mid = left + right >> 1;
            if (mid <= x / mid) left = mid + 1;
            else right = mid;
        }
        return right - 1;
    }
};

// 扩展,给定精度
double getSqrt(int x,double precision) {
	 double left = 0, right = x;
	 while (1) {
		 double mid = left + (right - left) / 2;
		 if (abs(x /mid - mid) < precision)	return mid;
		 else if (x / mid > mid)	left = mid + 1;
		 else right = mid - 1;
	 }
}

70、爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1+ 12.  2 阶

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1+ 1+ 12.  1+ 23.  2+ 1class Solution {
public:
    int climbStairs(int n) {
        int f1 = 1;
        long f2 = 1;  // 防止出现溢出
        while ( n-- ) {
            f2 = f1 + f2;
            f1 = f2 - f1;
        }
        return f1;
    }
};

71、简化路径

以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。
在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;
此外,两个点 (..) 表示将目录切换到上一级(指向父目录);
两者都可以是复杂相对路径的组成部分。
请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。
最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。

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

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

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

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

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

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

class Solution {
public:
    string simplifyPath(string path) {
        stack<string> st;
        string dir;
        path += "/";
        for (auto c : path) {
            if (c == '/') {
                if (dir == ".." && !st.empty()) st.pop();
                else if (dir != ".." && dir != "." && !dir.empty())
                    st.push(dir);
                dir.clear();
            }
            else dir += c;
        }
        string res;
        while (!st.empty()) {
            string t = st.top();
            st.pop();
            res += string(t.rbegin(), t.rend()) + "/";
        }
        reverse(res.begin(), res.end());
        if (res.empty()) return "/";
        return res;
    }
};

72、编辑距离(hard)

给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符

输入: word1 = "horse", word2 = "ros"
输出: 3
解释: 
horse -> rorse ('h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

输入: word1 = "intention", word2 = "execution"
输出: 5
解释: 
intention -> inention (删除 't')
inention -> enention ('i' 替换为 'e')
enention -> exention ('n' 替换为 'x')
exention -> exection ('n' 替换为 'c')
exection -> execution (插入 'u')

class Solution {
public:
    int minDistance(string word1, string word2) {
        int n = word1.size();
        int m = word2.size();
        vector< vector<int> > dp(n + 1, vector<int>(m + 1, 0));
        for (int i = 1; i <= n; i++) {
            dp[i][0] = i;
        }
        for (int j = 1; j <= m; j++) {
            dp[0][j] = j;
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1;
                if (word1[i - 1] == word2[j - 1]) {
                    dp[i][j] = min(dp[i][j], dp[i - 1][j - 1]);
                }
                else {
                    dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + 1);
                }
            }
        }
        return dp[n][m];
    }
};

73、矩阵置零

给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。

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

输入: 
[
  [0,1,2,0],
  [3,4,5,2],
  [1,3,1,5]
]
输出: 
[
  [0,0,0,0],
  [0,4,5,0],
  [0,3,1,0]
]

class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        bool row = false;  // 第一行是否需要置零
        bool col = false;  // 第一列是否需要置零
        for (int i = 0; i < matrix.size(); i++) {
            for (int j = 0; j < matrix[i].size(); j++) {
                if (matrix[i][j] == 0) {
                    if (i == 0) row = true;  // 第 1 行需要置零
                    if (j == 0) col = true;  // 第 1 列需要置零
                    matrix[i][0] = 0;  // 第 i 行的第一个元素置零,表示第 i 行需要全部置零
                    matrix[0][j] = 0;  // 第 j 列的第一个元素置零,表示第 j 列需要全部置零
                }
            }
        }
        for (int i = 1; i < matrix.size(); i++)  // 第 i 行的第一个元素置零,表示第 i 行需要全部置零
            if (matrix[i][0] == 0) 
                for (int j = 1; j < matrix[i].size(); j++) 
                    matrix[i][j] = 0;
        for (int j = 1; j < matrix[0].size(); j++)  // 第 j 列的第一个元素置零,表示第 j 列需要全部置零
            if (matrix[0][j] == 0)
                for (int i = 1; i < matrix.size(); i++) 
                    matrix[i][j] = 0;
        if (row == true)  // 第 1 行置零
            for (int i = 0; i < matrix[0].size(); i++) 
                matrix[0][i] = 0;
        if (col == true)  // 第 1 列置零
            for (int j = 0 ; j < matrix.size(); j++) 
                matrix[j][0] = 0;
    }
};

74、搜索二维矩阵

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。

输入:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
输出: true

输入:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 13
输出: false

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int rows = matrix.size();
        if (rows == 0) return false;
        int cols = matrix[0].size();
        // 二分查找
        int left = 0, right = rows * cols - 1;
        while (left <= right) {
            int mid = left + right >> 1;
            if (matrix[mid / cols][mid % cols] == target) return true;
            else if (matrix[mid / cols][mid % cols] > target)
                right = mid - 1;
            else left = mid + 1;
        }
        return false;
    }
};

75、颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 012 分别表示红色、白色和蓝色。
注意:不能使用代码库中的排序函数来解决这道题。

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

进阶:
一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出012 元素的个数,然后按照012的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int start = 0;
        int end = nums.size() - 1;
        if (nums.size() == 0) return ;
        for (int i = 0; start <= i && i <= end; i++) {
            if (nums[i] == 0  && start != i) {
                swap(nums[start], nums[i]);
                start++;
                i--;
            }
            if (nums[i] == 2 && end != i) {
                swap(nums[end], nums[i]);
                end--;
                i--;
            }
        }
    }
};

76、最小覆盖子串(hard)

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"

说明:
如果 S 中不存这样的子串,则返回空字符串 ""。
如果 S 中存在这样的子串,我们保证它是唯一的答案。

class Solution {
public:
    string minWindow(string s, string t) {
        // 双指针滑窗方法
        // 1、指针left, right; 指针right向右遍历直到出现满足条件的子串
        // 2、找到子串后,指针left再向右遍历,直到窗口中的内容不满足条件
        // 3、重复1,2;
        unordered_map<char, int> window;  // 滑窗中含有模式串中字符的情况
        unordered_map<char, int> needs;  // 记录模式串中的字符情况
        int start = 0, minLen = INT_MAX;  // 为了截取子串
        int left = 0, right = 0;  // 双指针
        for (char t_ : t) needs[t_]++;
        int match = 0;  // 用于判断匹配的条件
        while (right < s.size()) {
            char s_ = s[right];
            if (needs.count(s_) != 0) {
                window[s_]++;
                if (window[s_] == needs[s_]) match++;
            }
            right++;
            while (match == needs.size()) {  // 当滑窗中的内容符合条件
                if (right - left < minLen) {
                    start = left;
                    minLen = right - left;
                }
                char s1_ = s[left];
                if (needs.count(s1_) != 0) {
                    window[s1_]--;
                    if (window[s1_] < needs[s1_]) match--;
                }
                left++;
            }
        }
        return minLen == INT_MAX ? "" : s.substr(start, minLen);
    }
};

77、组合

给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。

输入: n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> tmp;
    void find(int a, int n, int k) {
        if (k <= 0) {
            ans.push_back(tmp);
            return ;
        }
        for (int i = a; i <= n; i++) {
            tmp.push_back(i);
            find(i + 1, n, k - 1);
            tmp.pop_back();
        }
    }

    vector<vector<int>> combine(int n, int k) {
    	// 回溯法(递归方式的dfs)
        find(1, n, k);
        return ans;
    }
};

78、子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。

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

class Solution {
public:
    vector<vector<int>> res;
    vector<int> vec;

    void find(int step, int num, vector<int> nums) {
        if (num <= nums.size()) res.push_back(vec);
        for (int i = step; i < nums.size(); i++) {
            vec.push_back(nums[i]);
            find(i + 1, num + 1, nums);
            vec.pop_back();
        }
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        find(0, 0, nums);
        return res;
    }
};

79、单词搜索

给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

board =
[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]

给定 word = "ABCCED", 返回 true.
给定 word = "SEE", 返回 true.
给定 word = "ABCB", 返回 false.

class Solution {
public:
    int dir[4][4] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    bool dfs(int x, int y, int index, vector<vector<char>>& board, string &word, vector<vector<bool>>& flag) {
        if (index == word.size() - 1) return word[index] == board[x][y];
        if (word[index] == board[x][y]) {
            flag[x][y] = true;
            for (int i = 0; i < 4; i++) {
                int new_x = x + dir[i][0];
                int new_y = y + dir[i][1];
                if (new_x >= 0 && new_x < board.size() && new_y >= 0 && new_y < board[0].size() && !flag[new_x][new_y])
                    if(dfs(new_x, new_y, index + 1, board, word, flag))
                        return true;
            }
            flag[x][y] = false;
        }
        return false;
    }

    bool exist(vector<vector<char>>& board, string word) {
        int rows = board.size();
        int cols = board[0].size();
        vector<vector<bool>> flag(rows, vector<bool> (cols, false));

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if(dfs(i, j, 0, board, word, flag))
                    return true;
            }
        }
        return false;
    }
};

80、删除排序数组中的重复项II

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

给定 nums = [1,1,1,2,2,3],
函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。
你不需要考虑数组中超出新长度后面的元素。

给定 nums = [0,0,1,1,1,1,2,3,3],
函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。
你不需要考虑数组中超出新长度后面的元素。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        int i = 1, j = 1, count_ = 1;
        while(j < nums.size()) {
            if (nums[j - 1] == nums[j]) count_ += 1;
            else count_ = 1;
            if (count_ <= 2) {
                nums[i] = nums[j];
                i++;
            }
            j++;
        }
        return i;
    }
};

81、搜索旋转排序数组II

假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true

输入: nums = [2,5,6,0,0,1,2], target = 3
输出: false

进阶:
这是 搜索旋转排序数组 的延伸题目,本题中的 nums  可能包含重复元素。
这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?

class Solution {
public:
    bool search(vector<int>& nums, int target) {
    	// 二分查找
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + right >> 1;
            if (nums[mid] == target) return true;
            if (nums[left] == nums[mid]) {
                left++;
                continue;
            }
            if (nums[left] < nums[mid]) {  // 左半边有序
                if (target < nums[mid] && nums[left] <= target) right = mid - 1;
                else left = mid + 1;

            }
            else {  // 右半边有序
                if (nums[mid] < target && target <= nums[right]) left = mid + 1;
                else right = mid - 1;
            }
        }
        return false;
    }
};

82、删除排序链表中的重复元素II

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。

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

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

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* pDummy = new ListNode(0);
        pDummy -> next = head;
        ListNode* p = pDummy;
        while(p -> next) {
            ListNode* tmp = p -> next;
            while (tmp && p -> next ->val == tmp -> val) 
                tmp = tmp -> next;
            if (p -> next -> next == tmp) 
                p = p -> next;
            else p -> next = tmp;
        }
        return pDummy -> next;
    }
};

83、删除排序链表中的重复元素

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

输入: 1->1->2
输出: 1->2

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

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        // // 方法1
        // ListNode* pDummy = new ListNode(0);
        // pDummy -> next = head;
        // ListNode* p = pDummy;
        // while(p -> next) {
        //     ListNode* tmp = p -> next;
        //     ListNode* tmp_ = tmp;
        //     while (tmp && p -> next ->val == tmp -> val) {
        //         tmp_ = tmp;
        //         tmp = tmp -> next;
        //     }
        //     if (p -> next -> next == tmp) 
        //         p = p -> next;
        //     else p -> next = tmp_;
        // }
        // return pDummy -> next;
        
        // 方法2
        ListNode* p = head;
        while (p != NULL && p -> next != NULL) {
            if (p -> next -> val == p -> val) {
                p -> next = p -> next -> next;
            }
            else p = p -> next;
        }
        return head;
    }
};

84、柱状图中最大的矩形(hard)

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

输入: [2,1,5,6,2,3]
输出: 10

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        // 栈实现
        int n = heights.size();
        vector<int> left(n), right(n);
        stack<int> stk;
        for (int i = 0; i < n; i++) {
            while (!stk.empty() && heights[stk.top()] >= heights[i])
                stk.pop();
            left[i] = stk.empty() ? -1 : stk.top();  // 从左遍历时索引值
            stk.push(i);
        }
        stk = stack<int>();
        for(int i = n - 1; i >= 0; i--) {
            while(!stk.empty() && heights[stk.top()] >= heights[i]) 
                stk.pop();
            right[i] = stk.empty() ? n : stk.top();
            stk.push(i);
        }
        int ans = 0; 
        for (int i = 0; i < n; i++) 
            ans = max(ans, (right[i] - left[i] - 1) * heights[i]);
        return ans;
    }
};

85、最大矩形

给定一个仅包含 01 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

输入:
[
  ["1","0","1","0","0"],
  ["1","0","1","1","1"],
  ["1","1","1","1","1"],
  ["1","0","0","1","0"]
]
输出: 6

class Solution {
public:
    void update(vector<vector<vector<int>>>& dp, int i, int j) {
        int line_min = dp[i][j][0];
        int rows  = dp[i][j][1];
        for (int count_ = 0; count_ < rows; count_++) {
            line_min = min(line_min, dp[i - count_][j][0]);
            dp[i][j][2] = max(dp[i][j][2], line_min * (count_ + 1));
        }
    }

    int maximalRectangle(vector<vector<char>>& matrix) {
        // 动态规划 参考:https://leetcode-cn.com/problems/maximal-rectangle/solution/geng-zhi-bai-yi-dian-de-dong-tai-gui-hua-by-vsym/
        if (matrix.size() == 0) return 0;
        int rows = matrix.size();
        int cols = matrix[0].size();
        vector<vector<vector<int>>> dp(rows, vector<vector<int>> (cols, {0, 0, 0}));
        int res = 0;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (matrix[i][j] == '0') ;
                else {
                    if (i == 0 && j == 0)
                        dp[i][j] = {1, 1, 1};
                    else if (i == 0)
                            dp[i][j] = {dp[i][j - 1][0] + 1, 1, dp[i][j - 1][2] + 1};
                    else if (j == 0) 
                            dp[i][j] = {1, dp[i - 1][j][1] + 1, dp[i - 1][j][2] + 1};
                    else {
                        dp[i][j][0] = dp[i][j - 1][0] + 1;
                        dp[i][j][1] = dp[i - 1][j][1] + 1;
                        update(dp, i, j);
                    }
                    res = max(res, dp[i][j][2]);
                }
            }
        }
        return res;
    }
};

86、分隔链表

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。

输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        // 双指针
        // first 存值小于 x 的节点
        // last 存值大于等于 x 的节点
        ListNode* first = new ListNode(0);
        ListNode* first_head = first;
        ListNode* last = new ListNode(0);
        ListNode* last_head = last;
        while (head != NULL) {
            if (head -> val < x) {
                first -> next = head;
                first = first -> next;
            }
            else {
                last -> next = head;
                last = last -> next;
            }
            head = head -> next;
        }
        last -> next = NULL;
        first -> next = last_head -> next;
        return first_head -> next;
    }
};

87、扰乱字符串

给定一个字符串 s1,我们可以把它递归地分割成两个非空子字符串,从而将其表示为二叉树。

下图是字符串 s1 = "great" 的一种可能的表示形式。
    great
   /    \
  gr    eat
 / \    /  \
g   r  e   at
           / \
          a   t
在扰乱这个字符串的过程中,我们可以挑选任何一个非叶节点,然后交换它的两个子节点。

例如,如果我们挑选非叶节点 "gr" ,交换它的两个子节点,将会产生扰乱字符串 "rgeat" 。
    rgeat
   /    \
  rg    eat
 / \    /  \
r   g  e   at
           / \
          a   t
我们将 "rgeat” 称作 "great" 的一个扰乱字符串。

同样地,如果我们继续交换节点 "eat""at" 的子节点,将会产生另一个新的扰乱字符串 "rgtae" 。
    rgtae
   /    \
  rg    tae
 / \    /  \
r   g  ta  e
       / \
      t   a
我们将 "rgtae” 称作 "great" 的一个扰乱字符串。

给出两个长度相等的字符串 s1 和 s2,判断 s2 是否是 s1 的扰乱字符串。

输入: s1 = "great", s2 = "rgeat"
输出: true

输入: s1 = "abcde", s2 = "caebd"
输出: false

class Solution {
public:
    bool isScramble(string s1, string s2) {
        // 动态规划
        // 参考链接:https://leetcode-cn.com/problems/scramble-string/solution/c-dong-tai-gui-hua-by-da-li-wang-36/
        // dp[len][i][j] s1 中以 i 开始长度为 len 的字符串 与 s2 中以 j 开始长度为 len的字符串是否是干扰字符串
        if (s1.size() != s2.size()) return false;
        if (s1.empty()) return true;
        int N = s1.size();
        vector<vector<vector<bool>>> dp(N + 1, vector<vector<bool>> (N, vector<bool> (N, false)));
        for (int i = 0; i < N ; i++) {  // 初始化长度为 1 的字符,若此时 s1 与 s2 中的相等,则是一个干扰字符串
            for (int j = 0; j < N; j++) {
                dp[1][i][j] = s1[i] == s2[j];
            }
        }
        for (int len = 2; len <= N; len++) {
            for (int i = 0; i < N && i + len - 1 < N; i++) {
                for (int j = 0; j < N && j + len - 1 < N; j++) {
                    for (int k = 1; k < len; k++) {
                        if (dp[k][i][j] && dp[len - k][i + k][j + k]) {
                            dp[len][i][j] = true;
                            break;
                        }
                        if (dp[k][i][j + len - k] && dp[len - k][i + k][j]) {
                            dp[len][i][j] = true;
                            break;
                        }
                    }
                }
            }
        }
        return dp[N][0][0];
    }
};

88、合并两个有序数组

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。

说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3
输出: [1,2,2,3,5,6]

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        if (n == 0) return ;
        if (m == 0) {
            nums1 = nums2;
            return ;
        }
        int len = m + n - 1;
        m--;n--;
        while (n >= 0 && m >= 0) {
            if (nums1[m] <= nums2[n]) {
                nums1[len--] = nums2[n--];
            }
            else {
                nums1[len--] = nums1[m--];
            }
        }
        if (m < 0) {
            while (n >= 0) {
                nums1[len--] = nums2[n--];
            }
        }
    }
};

89、格雷编码

格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。格雷编码序列必须以 0 开头。

输入: 2
输出: [0,1,3,2]
解释:
00 - 0
01 - 1
11 - 3
10 - 2
对于给定的 n,其格雷编码序列并不唯一。
例如,[0,2,3,1] 也是一个有效的格雷编码序列。
00 - 0
10 - 2
11 - 3
01 - 1

输入: 0
输出: [0]
解释: 我们定义格雷编码序列必须以 0 开头。
     给定编码总位数为 n 的格雷编码序列,其长度为 2n。当 n = 0 时,长度为 20 = 1。
     因此,当 n = 0 时,其格雷编码序列为 [0]class Solution {
public:
    vector<int> grayCode(int n) {
        // 参考链接:https://leetcode-cn.com/problems/gray-code/solution/c-dong-tai-gui-hua-jian-ji-yi-dong-shi-jian-ji-kon/
        vector<int> result(1);
        result[0] = 0;
        for(int i = 1; i <= n; i++){
            int e = 1 << (i - 1);                           //i - 1位结果前增加一位1
            for(int j = result.size() - 1; j >= 0; j--){    // 镜像排列
                result.push_back(e + result[j]);
            }
        }
        return result;
    }
};

90、子集II

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。

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

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        // 重复的数字在已经出现过的地方才能出现,在没有出现过的地方只能出现那个领头的数字
        vector<vector<int>> res = {{}};  // res.size() = 1;
        sort(nums.begin(), nums.end());
        int start = 0;
        for (int i = 0; i < nums.size(); i++) {
            start = (i && nums[i] == nums[i - 1]) ? start : 0;
            int len = res.size();
            for (int j = start; j < len; j++) {
                auto tmp = res[j];
                tmp.push_back(nums[i]);
                res.push_back(tmp);
            }
            start = len;
        }
        return res;
    }
};

91、解码方法

一条包含字母 A-Z 的消息通过以下方式进行了编码:
'A' -> 1
'B' -> 2
...
'Z' -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。

输入: "12"
输出: 2
解释: 它可以解码为 "AB"1 2)或者 "L"12)。
示例 2:

输入: "226"
输出: 3
解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6)class Solution {
public:
    int numDecodings(string s) {
        // 动态规划
        // dp[i] : str[0...i] 的方法数
        if (s[0] == '0') return 0;
        int cur = 1, pre = 1;
        for (int i = 1; i < s.size(); i++) {
            int tmp = cur;
            if (s[i] == '0') {
                if (s[i - 1] == '1' || s[i - 1] == '2') cur = pre;
                else return 0;
            }
            else if (s[i - 1] == '1' || (s[i - 1] == '2' && s[i] >= '1' && s[i] <= '6'))
                cur += pre;
            pre = tmp;
        }
        return cur;
    }
};

92、反转链表II

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。

输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        ListNode* pDummy  = new ListNode(0);
        pDummy -> next = head;
        auto left = pDummy, right = head;
        int cnt = m - 1; 
        while (cnt--) {
            left = right;
            right = right -> next;
        }
        auto savehead = left;
        auto savetail = right;
        left = right;
        right = right -> next;
        cnt = n - m;
        while (cnt--) {
            auto next_ = right -> next;
            right -> next = left;
            left = right;
            right = next_;
        }
        savehead -> next = left;
        savetail -> next = right;
        return pDummy -> next;
    }
};

93、复原IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]

class Solution {
public:
    vector<string> restoreIpAddresses(string s) {
        string ip;
        helper(s, 0, ip);
        return res;
    }

    void helper(string s, int n, string ip) {
        if (n == 4) {
            if (s.empty()) res.push_back(ip);
        }
        else {
            for (int k = 1; k < 4; k++) {
                if (s.size() < k) break;
                int val = stoi(s.substr(0, k));
                if (val > 255 ||  k != std::to_string(val).size()) continue;  // 010 这种错误
                helper(s.substr(k), n + 1, ip + s.substr(0, k) + (n == 3 ? "" : "."));
            }
        }
        return ;
    }

private:
    vector<string> res;
};

94、二叉树的中序遍历

给定一个二叉树,返回它的中序 遍历。

输入: [1,null,2,3]
   1
    \
     2
    /
   3

输出: [1,3,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:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        mid(root, res);
        return res;
    }

    // void mid(TreeNode* root, vector<int>& res) {
    //     if (root == NULL) return;
    //     mid(root -> left, res);
    //     res.push_back(root -> val);
    //     mid(root -> right, res);
    // }

    void mid(TreeNode* root, vector<int>& res) {
        // 方法2 非递归
        stack<TreeNode*> s;
        TreeNode* cur = root;
        while(!s.empty() || cur != NULL) {
            if (cur != NULL) {
                s.push(cur);
                cur = cur -> left;
            }
            else {
                cur = s.top();
                res.push_back(cur ->val);
                cur = cur -> right;
                s.pop();
            }
        }
    }
};

95、不同的二叉搜索树II

给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。

输入: 3
输出:
[
  [1,null,3,2],
  [3,2,null,1],
  [3,1,null,null,2],
  [2,1,3],
  [1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

/**
 * 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:
    vector<TreeNode*> helper(int start, int end) {
        vector<TreeNode*> res;
        if (start > end) res.push_back(NULL);
        for (int i = start; i <= end; i++) {
            vector<TreeNode*> left = helper(start, i - 1);
            vector<TreeNode*> right = helper(i + 1, end);
            for (auto l : left) {
                for (auto r : right) {
                    TreeNode* root = new TreeNode(i);
                    root -> left = l;
                    root -> right = r;
                    res.push_back(root);
                }
            }
        }
        return res;
    }

    vector<TreeNode*> generateTrees(int n) {
        vector<TreeNode*> res;
        if (n == 0) return res;
        res = helper(1, n); 
        return res;
    }
};

96、不同的二叉搜索树

给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

class Solution {
public:
    int numTrees(int n) {
        // 动态规划
        // 每一个节点 i 都是左边子树 1, 2, .. ,i - 1 和右边子树 i, i + 1, ..., n 这两种的乘积
        vector<int> dp(n + 1, 0);
        dp[0] = 1, dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }        
        return dp[n];
    }
};

97、交错字符串

给定三个字符串 s1, s2, s3, 验证 s3 是否是由 s1 和 s2 交错组成的。

输入: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
输出: true

输入: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
输出: false

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        // 动态规划
        int N1 = s1.size();
        int N2 = s2.size();
        int N3 = s3.size();
        if (N1 + N2 != N3) return false;
        vector<vector<bool>> dp(N1 + 1, vector<bool> (N2 + 1, false));
        dp[0][0] = true;
        for (int i = 0; i <= N1; i++) {
            for (int j = 0; j <= N2; j++) {
                if (i > 0 && s1[i - 1] == s3[i + j - 1])
                    dp[i][j] = dp[i][j] || dp[i - 1][j];
                if (j > 0 && s2[j - 1] == s3[i + j - 1])
                    dp[i][j] = dp[i][j] || dp[i][j - 1];
            }
        }
        return dp[N1][N2];
    }
};

98、验证二叉搜索树(medium)

给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。

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

输入:
    5
   / \
  1   4
     / \
    3   6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
     根节点的值为 5 ,但是其右子节点值为 4/**
 * 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 helper(TreeNode* root, long low, long high) {
        if (root == NULL) return true;
        long val = root -> val;
        if (val <= low || val >= high) return false;
        return helper(root -> left, low, val) && helper(root -> right, val, high);
    }

    bool isValidBST(TreeNode* root) {
        return helper(root, LONG_MIN, LONG_MAX);  // 上界下界
    }
};

99、恢复二叉搜索树

二叉搜索树中的两个节点被错误地交换。
请在不改变其结构的情况下,恢复这棵树。

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

   1
  /
 3
  \
   2
输出: [3,1,null,null,2]

   3
  /
 1
  \
   2

输入: [3,1,4,null,null,2]

  3
 / \
1   4
   /
  2
输出: [2,1,4,null,null,3]

  2
 / \
1   4
   /
  3

/**
 * 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:
    void recoverTree(TreeNode* root) {
    	// 参考链接:https://leetcode-cn.com/problems/recover-binary-search-tree/solution/yin-yong-chang-shu-kong-jian-jie-jue-by-newbie-19/
        vector<TreeNode*> vec;
        TreeNode* pre = NULL;
        recoverTree(root, vec, pre);
        if (vec.size() == 2) {
            int tmp = vec[0] -> val;
            vec[0] -> val = vec[1] -> val;
            vec[1] -> val = tmp;
        }
        else {
            int tmp = vec[0] -> val;
            vec[0] -> val = vec[2] -> val;
            vec[2] -> val = tmp;
        }
        
    }

    void recoverTree(TreeNode* root, vector<TreeNode*>& vec, TreeNode*& pre) {
        if (!root) return ;
        recoverTree(root -> left, vec, pre);
        if (pre && vec.size() == 0) {
            if (root -> val < pre -> val) {
                vec.push_back(pre);
                vec.push_back(root);
            }
        }
        else if (vec.size() == 2 && (pre && root -> val < pre -> val))
            vec.push_back(root);
        pre = root;
        recoverTree(root -> right, vec, pre);
    }
};

100、相同的树

给定两个二叉树,编写一个函数来检验它们是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

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

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

输出: true

输入:      1          1
          /           \
         2             2

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

输出: false

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

        [1,2,1],   [1,1,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;
        else if (p == NULL || q == NULL ||p -> val != q -> val) return false;
        return isSameTree(p -> left, q -> left) && isSameTree(p -> right, q -> right);
    }
};

101、对称二叉树(easy)

给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
    1
   / \
  2   2
 / \ / \
3  4 4  3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
    1
   / \
  2   2
   \   \
   3    3
说明:
如果你可以运用递归和迭代两种方法解决这个问题,会很加分。

/**
 * 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 isSymmetric(TreeNode* root) {
        // return isSymmetric(root, root);  // 常用递归,简单

        // 迭代
        // 每次取 que 中两个节点进行比较;将左右节点的子左右节点按相反(右左)方向压进 que 中;
        queue<TreeNode*> que;
        que.push(root);
        que.push(root);
        while (!que.empty()) {
            TreeNode* t1 = que.front();
            que.pop();
            TreeNode* t2 = que.front();
            que.pop();
            if (t1 == NULL && t2 == NULL) continue;
            if (t1 == NULL || t2 == NULL || t1 -> val != t2 -> val) return false;
            que.push(t1 -> left);
            que.push(t2 -> right);
            que.push(t1 -> right);
            que.push(t2 -> left);
        }
        return true;
    }
    
    // 递归辅助函数
    bool isSymmetric(TreeNode* root1, TreeNode* root2) {
        if (root1 == NULL && root2 == NULL) return true;
        if (root1 == NULL || root2 == NULL || root1 -> val != root2 -> val) return false;
        return isSymmetric(root1 -> left, root2 -> right) && isSymmetric(root1 -> right, root2 -> left);
    }
};

102、二叉树的层次遍历(medium)

给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。

给定二叉树: [3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
返回其层次遍历结果:
[
  [3],
  [9,20],
  [15,7]
]

/**
 * 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:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if (root == NULL) return res;
        queue<TreeNode*> que;
        que.push(root);
        while (!que.empty()) {
            int len_T = que.size();
            vector<int> vec;
            for (int i = 0; i < len_T; i++) {
                TreeNode* tmp = que.front();
                que.pop();
                vec.push_back(tmp -> val);
                if (tmp -> left) que.push(tmp -> left);
                if (tmp -> right) que.push(tmp -> right);
            }
            res.push_back(vec);
        } 
        return res;
    }
};

103、二叉树的锯齿形层次遍历

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

给定二叉树 [3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
返回锯齿形层次遍历如下:
[
  [3],
  [20,9],
  [15,7]
]

/**
 * 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:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if (root == NULL) return res;
        queue<TreeNode*> que;
        que.push(root);
        bool flag = false;
        while (!que.empty()) {
            int len_Q = que.size();
            vector<int> vec;
            for (int i = 0; i < len_Q; i++) {
                TreeNode* tmp = que.front();
                que.pop();
                vec.push_back(tmp -> val);
                if (tmp -> left) que.push(tmp -> left);
                if (tmp -> right) que.push(tmp -> right);
            }
            if (flag) reverse(vec.begin(), vec.end());
            res.push_back(vec);
            flag = !flag;
        }
        return res;
    }
};

104、二叉树的最大深度

给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。

给定二叉树 [3,9,20,null,null,15,7]3
   / \
  9  20
    /  \
   15   7
返回它的最大深度 3 

/**
 * 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) {
        if (root == NULL) return 0;
        int left = maxDepth(root -> left);
        int right = maxDepth(root -> right);
        return max(left + 1, right + 1);
    }
};

105、从前序与中序遍历序列构造二叉树(medium)

根据一棵树的前序遍历与中序遍历构造二叉树。
你可以假设树中没有重复的元素。

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
    3
   / \
  9  20
    /  \
   15   7

/**
 * 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:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if(preorder.size() == 0 || preorder.size() != inorder.size()) return NULL;
        int index_i = 0;
        for (int i = 0; i < inorder.size(); i++) {
            if (inorder[i] == preorder[0]) {
                index_i = i;
                break;
            }
        }
        TreeNode* root = new TreeNode(preorder[0]);
        vector<int> pre_left, pre_right, in_left, in_right;
        for (int i = 0; i < index_i; i++) {
            pre_left.push_back(preorder[i + 1]);
            in_left.push_back(inorder[i]);
        }
        for (int i = index_i + 1; i < preorder.size(); i++) {
            pre_right.push_back(preorder[i]);
            in_right.push_back(inorder[i]);
        }
        root -> left = buildTree(pre_left, in_left);
        root -> right = buildTree(pre_right, in_right);
        return root;
    }
};

106、从中序与后序遍历序列构造二叉树

根据一棵树的中序遍历与后序遍历构造二叉树。
你可以假设树中没有重复的元素。

中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]
返回如下的二叉树:
    3
   / \
  9  20
    /  \
   15   7

/**
 * 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:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 0 || inorder.size() != postorder.size()) return NULL;
        int index_i = 0; 
        for (int i = inorder.size() - 1; i >= 0; i--) {
            if (inorder[i] == postorder[postorder.size() - 1]) {
                index_i = i;
                break;
            }
        }
        TreeNode* root = new TreeNode(postorder[postorder.size() - 1]);
        vector<int> in_left, in_right, post_left, post_right;
        for (int i = 0; i < index_i; i++) {
            in_left.push_back(inorder[i]);
            post_left.push_back(postorder[i]);
        }
        for (int i = index_i; i < inorder.size() - 1; i++) {
            in_right.push_back(inorder[i + 1]);
            post_right.push_back(postorder[i]);
        }
        root ->left = buildTree(in_left, post_left);
        root -> right = buildTree(in_right, post_right);
        return root;
    }
};

107、二叉树的层次遍历II

给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

给定二叉树 [3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
返回其自底向上的层次遍历为:
[
  [15,7],
  [9,20],
  [3]
]

/**
 * 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:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        vector<vector<int>> res;
        if (root == NULL) return res;
        queue<TreeNode*> que;
        que.push(root);
        while (!que.empty()) {
            int len_q = que.size();
            vector<int> vec;
            for (int i = 0; i < len_q; i++) {
                TreeNode* tmp = que.front();
                que.pop();
                vec.push_back(tmp -> val);
                if (tmp -> left) que.push(tmp -> left);
                if (tmp -> right) que.push(tmp -> right);
            }
            res.push_back(vec);
        }
        reverse(res.begin(), res.end());
        return res;
    }
};

108、将有序数组转换为二叉搜索树

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
      0
     / \
   -3   9
   /   /
 -10  5

/**
 * 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:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        if (nums.size() == 0) return NULL;
        if (nums.size() == 1) {
            return new TreeNode(nums[0]);
        }
        TreeNode* root = new TreeNode(nums[(nums.size()) / 2]);
        vector<int> nums1;
        vector<int> nums2;
        for (int i = 0; i < (nums.size()) / 2; i++) nums1.push_back(nums[i]);
        for (int i = (nums.size()) / 2 + 1 ; i < nums.size(); i++) nums2.push_back(nums[i]);
        root -> left = sortedArrayToBST(nums1);
        root -> right = sortedArrayToBST(nums2);
        return root;
    }
};

109、有序链表转换二叉搜索树

给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

给定的有序链表: [-10, -3, 0, 5, 9],
一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:
      0
     / \
   -3   9
   /   /
 -10  5

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
/**
 * 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:
    TreeNode* sortedListToBST(ListNode* head) {
        if (head == NULL) return NULL;
        if (head -> next == NULL) return new TreeNode(head -> val);
        ListNode* L1 = head;
        int index_i = 0;
        for (; L1 != NULL; index_i++) 
            L1 = L1 -> next;
        L1 = head;
        for (int i = 1 ; i < index_i / 2; i++) {
            L1 = L1 -> next;
        }
        TreeNode* root = new TreeNode(L1 -> next -> val);
        ListNode* L_last = L1 -> next -> next;
        L1 -> next = NULL;
        root -> left = sortedListToBST(head);  // 此时的 head 只包含前半部分,如 [-10,-3,0,5,9] 中的 [-10, -3]
        root -> right = sortedListToBST(L_last);
        return root;        
    }
};

110、平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。


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

给定二叉树 [1,2,2,3,3,null,null,4,4]
       1
      / \
     2   2
    / \
   3   3
  / \
 4   4
返回 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 isBalanced(TreeNode* root) {
        if (root == NULL) return true;
        int left = getDepth(root -> left);
        int right = getDepth(root -> right);
        if (abs(left - right) > 1) return false;
        return isBalanced(root -> left) && isBalanced(root -> right); 
    }

    int getDepth(TreeNode* root) {
        if (root == NULL) return 0;
        int left = getDepth(root -> left);
        int right = getDepth(root -> right);
        return max(left, right) + 1;
    }
};

111、二叉树的最小深度

给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。

给定二叉树 [3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
返回它的最小深度  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;
        int left = minDepth(root -> left);
        int right = minDepth(root -> right);
        if (root -> left == NULL || root -> right == NULL)  // 非叶子节点
            return left == 0 ? right + 1 : left + 1;
        else 
            return min(left, right) + 1;
    }
};

112、路径总和

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。

给定如下二叉树,以及目标和 sum = 225
             / \
            4   8
           /   / \
          11  13  4
         /  \      \
        7    2      1
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->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:
    bool hasPathSum(TreeNode* root, int sum) {
        if (root == NULL) return false;
        if (root -> left == NULL && root -> right == NULL) {
            return sum - (root -> val) == 0;
        }
        return hasPathSum(root -> left, sum - (root -> val)) || hasPathSum(root -> right, sum - (root -> val));
    }
};

113、路径总和II

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。

给定如下二叉树,以及目标和 sum = 225
             / \
            4   8
           /   / \
          11  13  4
         /  \    / \
        7    2  5   1
返回:
[
   [5,4,11,2],
   [5,8,4,5]
]

/**
 * 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:
    vector<vector<int>> res;
    vector<int> vec;

    void pathSum_helper(TreeNode* root, int sum) {
        if (root == NULL) return ;
        vec.push_back(root -> val);
        if (root -> left == nullptr && root -> right == nullptr && sum - (root -> val) == 0) {
            res.push_back(vec);
        }
        pathSum(root -> left, sum - (root -> val));
        pathSum(root -> right, sum -(root -> val));
        vec.pop_back();
    }

    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        pathSum_helper(root, sum);
        return res;        
    }
};

114、二叉树展开为链表

给定一个二叉树,原地将它展开为链表。

例如,给定二叉树
    1
   / \
  2   5
 / \   \
3   4   6
将其展开为:
1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

/**
 * 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:
    void flatten(TreeNode* root) {
        while (root != nullptr) {
            if (root -> left != nullptr) {
                auto left_root = root -> left;
                while (left_root -> right != nullptr) left_root = left_root -> right;
                left_root -> right = root -> right;
                root -> right = root -> left;
                root -> left = nullptr;
            }
            root = root -> right;
        }
    }
};

115、不同的子序列

给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。
一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE""ABCDE" 的一个子序列,而 "AEC" 不是)

输入: S = "rabbbit", T = "rabbit"
输出: 3
解释:
如下图所示,3 种可以从 S 中得到 "rabbit" 的方案。
(上箭头符号 ^ 表示选取的字母)
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^

输入: S = "babgbag", T = "bag"
输出: 5
解释:
如下图所示,5 种可以从 S 中得到 "bag" 的方案。 
(上箭头符号 ^ 表示选取的字母)
babgbag
^^ ^
babgbag
^^    ^
babgbag
^    ^^
babgbag
  ^  ^^
babgbag
    ^^^

class Solution {
public:
    int numDistinct(string s, string t) {
        // 动态规划
        // dp[i][j] : S[:j] 中有 T[:i] 子序列的个数
        vector<vector<double>> dp(t.size() + 1, vector<double> (s.size() + 1, 0));
        for (int j = 0; j < s.size() + 1; j++) 
            dp[0][j] = 1;
        for (int i = 1; i < t.size() + 1; i++) {
            for (int j = 1; j < s.size() + 1; j++) {
                if (s[j - 1] == t[i - 1]) 
                    dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
                else 
                    dp[i][j] = dp[i][j - 1];
            }
        }
        return dp[t.size()][s.size()];
    }
};

116、填充每个节点的下一个右侧节点指针

给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;
    Node* next;

    Node() : val(0), left(NULL), right(NULL), next(NULL) {}

    Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}

    Node(int _val, Node* _left, Node* _right, Node* _next)
        : val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
    Node* connect(Node* root) {
        // 层序遍历,每层最后一个节点指向 NULL, 其他指向当前层的下一个节点
        if (root == NULL) return root;
        queue<Node*> que;
        que.push(root);
        while (!que.empty()) {
            int len_q = que.size();
            for(int i = 1; i <= len_q; i++) {
                Node* tmp = que.front();
                que.pop();
                if (i == len_q) tmp -> next = NULL;
                else tmp -> next = que.front();
                if (tmp -> left) que.push(tmp -> left);
                if (tmp -> right) que.push(tmp -> right);
            }
        }
        return root;
    }
};

117、填充每个节点的下一个右侧节点指针II

给定一个二叉树
struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;
    Node* next;

    Node() : val(0), left(NULL), right(NULL), next(NULL) {}

    Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}

    Node(int _val, Node* _left, Node* _right, Node* _next)
        : val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
    Node* connect(Node* root) {
        // 层序遍历,每层最后一个节点指向 NULL, 其他指向当前层的下一个节点
	// 普通二叉树,解法可以用 116 题解法
        if (root == NULL) return root;
        queue<Node*> que;
        que.push(root);
        while (!que.empty()) {
            int len_q = que.size();
            for(int i = 1; i <= len_q; i++) {
                Node* tmp = que.front();
                que.pop();
                if (i == len_q) tmp -> next = NULL;
                else tmp -> next = que.front();
                if (tmp -> left) que.push(tmp -> left);
                if (tmp -> right) que.push(tmp -> right);
            }
        }
        return root;
    }
};

118、杨辉三角

给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
在杨辉三角中,每个数是它左上方和右上方的数的和。

输入: 5
输出:
[
     [1],
    [1,1],
   [1,2,1],
  [1,3,3,1],
 [1,4,6,4,1]
]

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> res(numRows);
        if (numRows == 0) return res;
        res[0].push_back(1);
        for(int i = 1; i < numRows; i++) {
            res[i].push_back(1);
            for (int j = 1; j < i; j++) {
                res[i].push_back(res[i - 1][j - 1] + res[i - 1][j]);
            }
            res[i].push_back(1);
        }
        return res;
    }
};

119、杨辉三角II

给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。
在杨辉三角中,每个数是它左上方和右上方的数的和。

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

class Solution {
public:
    vector<int> getRow(int rowIndex) {
        // if (rowIndex == 0) return {1};
        // vector<vector<int>> res(rowIndex + 1);
        // res[0].push_back(1);
        // for (int i = 1; i < rowIndex + 1; i++) {
        //     res[i].push_back(1);
        //     for (int j = 1; j < i; j++) {
        //         res[i].push_back(res[i - 1][j - 1] + res[i - 1][j]);
        //     }
        //     res[i].push_back(1);
        // }
        // return res[rowIndex];

        // 更优解
        vector<int> result;
        for (int i = 0 ; i <= rowIndex; i++) {
            result.push_back(1); 
            for (int j = i - 1; j > 0; j--) {
                result[j] += result[j - 1];
            }
        }
        return result;
    }
};

120、三角形最小路径和

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

例如,给定三角形:
[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
说明:
如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        vector<vector<int>> dp(triangle.size());
        dp[0].push_back(triangle[0][0]);
        for (int i = 1; i < triangle.size(); i++) {
            dp[i].push_back(dp[i - 1][0] + triangle[i][0]);
            for(int j = 1; j < i; j++) {
                dp[i].push_back(min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j]);
            }
            dp[i].push_back(dp[i - 1][i - 1] + triangle[i][i]);
        }
        int min_ = dp[dp.size() - 1][0];
        for (int i = 1; i < dp.size(); i++) {
            min_ = min(dp[dp.size() - 1][i], min_);
        }
        return min_;

    }
};

121、买卖股票的最佳时机(easy)

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0class Solution {
public:
    int maxProfit(vector<int>& prices) {
	// 方法1
        if (prices.size() == 0) return 0;
        int min_ = prices[0], max_ = prices[0], max_all = 0;
        for (int i = 1;i < prices.size(); i++) {
            if (prices[i] < min_) {  // 当出现最小时, min_ 和 max_ 重置
                min_ = prices[i];
                max_ = prices[i];
            }
            if (prices[i] > max_) {  // 当之后出现的值大于 max_ , 需要更新 max_
                max_ = prices[i];
            }
            max_all = max(max_ - min_, max_all);
        }
        return max_all;
		
	// 方法2
	// 最大收益 = 每个当前的卖出值 - 最小值
	// if(prices.size() == 0) return 0;
        // int min_ = prices[0], max_profit = 0;
        // for (int i = 1; i < prices.size(); i++) {
        //     max_profit = max(max_profit, prices[i] - min_);
        //     min_ = min(prices[i], min_);
        // }
        // return max_profit;
    }
};

122、买卖股票的最佳时机II(easy)

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if (prices.size() == 0) return 0;
        int buy_min = prices[0], sell_max = prices[0], profit = 0;
        for (int i = 1; i < prices.size(); i++) {
            if (buy_min > prices[i]) {
                buy_min = prices[i];
                sell_max = prices[i];
            }
            if (sell_max < prices[i]) {
                sell_max = prices[i];
                profit += (sell_max - buy_min);
                buy_min = prices[i];
            }
        }
        return profit;
    }
};

123、买卖股票的最佳时机III(hard)

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

输入: [3,3,5,0,0,3,1,4]
输出: 6
解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。   
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

输入: [7,6,4,3,1] 
输出: 0 
解释: 在这个情况下, 没有交易完成, 所以最大利润为 0class Solution {
public:
    int maxProfit(vector<int>& prices) {
        // dp[i][k][0]: 从开始到 i 天,发生交易 K 次, 没有持有股票
        // dp[i][k][1]:从开始到 i 天,发生交易 K 次, 持有股票
        if(prices.size() <= 1) return 0;
        int n = prices.size(), maxK = 2;
        vector<vector<vector<int>>> dp(n, vector<vector<int>> (3, vector<int> (2, 0)));
        for (int i = 0; i < n ; i++) {
            for (int k = 1; k <= maxK; k++) {
                if (i == 0) {
                    dp[0][k][0] = 0;
                    dp[0][k][1] = -prices[0];
                    continue;
                }
                // 没有持有股票: 1、之前就没有2、之前有,卖掉了
                dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
                // 持有股票:1、之前就有2、之前没有,新买的,发生交易
                dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
            }
        }
        return dp[n - 1][2][0];
    }
};

124、二叉树中的最大路径和(hard)

给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。

输入: [1,2,3]
       1
      / \
     2   3
输出: 6

输入: [-10,9,20,null,null,15,7]
   -10
   / \
  9  20
    /  \
   15   7
输出: 42

/**
 * 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 maxPathSum(TreeNode* root, int &val) {
        if(root == NULL) return 0;
        int left = maxPathSum(root -> left, val);
        int right = maxPathSum(root -> right, val);
        int lm = root -> val + max(0, left) + max(0, right);
        int ret = root -> val + max(0, max(left, right));
        val = max(val, max(lm, ret));
        return ret;
    }

    int maxPathSum(TreeNode* root) {
        // 参考:https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/solution/er-cha-shu-zhong-de-zui-da-lu-jing-he-by-ikaruga/
        int val = INT_MIN;
        maxPathSum(root, val);
        return val;
    }
};

125、验证字符串(easy)

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。

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

输入: "race a car"
输出: false

class Solution {
public:
    bool isPalindrome(string s) {
        if (s.size() == 0) return true;
        int left = 0, right = s.size() - 1;
        while (left < right) {
            if (!isalpha(s[left]) && !isdigit(s[left])) {
                left++;
                continue;
            }
            if (!isalpha(s[right]) && !isdigit(s[right])) {
                right--;
                continue;
            }
            if (tolower(s[left]) == tolower(s[right])) {
                left++;
                right--;
            }
            else return false;
        }
        return true;
    }
};

126、单词接龙II(hard)

给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。
转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。

说明:
如果不存在这样的转换序列,返回一个空列表。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[
  ["hit","hot","dot","dog","cog"],
  ["hit","hot","lot","log","cog"]
]

输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: []
解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。

const int INF = 1 << 20;

class Solution {
private:
    unordered_map<string, int> wordId;
    vector<string> idWord;
    vector<vector<int>> edges;
public:
    bool transformCheck(const string& str1, const string& str2) {
        int differences = 0;
        for (int i = 0; i < str1.size() && differences < 2; i++) {
            if (str1[i] != str2[i])
                differences++;
        }
        return differences == 1;
    }

    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        // 拓扑图bfs
        int id = 0;
        for (const string& word : wordList) {
            if (!wordId.count(word)) {
                wordId[word] = id++;
                idWord.push_back(word);
            }
        }
        if (!wordId.count(endWord)) {
            return {};
        }
        if (!wordId.count(beginWord)) {
            wordId[beginWord] = id++;
            idWord.push_back(beginWord);
        }
        edges.resize(idWord.size());
        for (int i = 0; i < idWord.size(); i++) {
            for (int j = i + 1; j < idWord.size(); j++) {
                if (transformCheck(idWord[i], idWord[j])) {
                    edges[i].push_back(j);
                    edges[j].push_back(i);
                }
            }
        }
        const int dest = wordId[endWord];
        vector<vector<string>> res;
        queue<vector<int>> q;
        vector<int> cost(id, INF);
        q.push(vector<int> {wordId[beginWord]});
        cost[wordId[beginWord]] = 0;
        while (!q.empty()) {
            vector<int> now = q.front();
            q.pop();
            int last = now.back();
            if (last == dest) {
                vector<string> tmp;
                for (int index : now) {
                    tmp.push_back(idWord[index]);
                } 
                res.push_back(tmp);
            }
            else {
                for (int i = 0; i < edges[last].size(); i++) {
                    int to = edges[last][i];
                    if (cost[last] + 1 <= cost[to]) {
                        cost[to] = cost[last] + 1;
                        vector<int> tmp(now);
                        tmp.push_back(to);
                        q.push(tmp);
                    }
                }
            }
        }
        return res;
    }
};

127、单词接龙(medium)

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。
转换需遵循如下规则:
*每次转换只能改变一个字母。
*转换过程中的中间单词必须是字典中的单词。
说明:
*如果不存在这样的转换序列,返回 0*所有单词具有相同的长度。
*所有单词只由小写字母组成。
*字典中不存在重复的单词。
*你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出: 5
解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
     返回它的长度 5。

输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: 0
解释: endWord "cog" 不在字典中,所以无法进行转换。

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        // hit,1 -> hot,2 -> dot,3 -> log,4 -> log,5
        //                -> lot,3
        unordered_set<string> s;
        for (auto &i : wordList) s.insert(i);
        queue<pair<string, int>> q;
        q.push({beginWord, 1});  // 加入beginword
        string tmp;  // 每个节点的字符
        int step;  // 到达该节点的step
        while (!q.empty()) {
            if(q.front().first == endWord)
                return q.front().second;
            tmp = q.front().first;
            step = q.front().second;
            q.pop();
            // 寻找写一个单词
            char ch;
            for (int i = 0; i < tmp.size(); i++) {
                ch = tmp[i];
                for (char c = 'a'; c <= 'z'; c++) {
                    if (ch == c) continue;
                    tmp[i] = c;
                    if (s.find(tmp) != s.end()) {  // 如果在 s 中找的到
                        q.push({tmp, step + 1});
                        s.erase(tmp);  // 删除该节点
                    }
                    tmp[i] = ch;  // 复原
                }
            }
        }
        return 0;
    }
};

128、最长连续序列(hard)

给定一个未排序的整数数组,找出最长连续序列的长度。
要求算法的时间复杂度为 O(n)。

输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        // unorder_set 底层是hash_set,查找删除复杂度为 O(1); 遍历时间复杂度 O(n), 空间复杂度为 O(1);
        if (nums.size() <= 1) return nums.size();
        unordered_set<int> s(nums.begin(), nums.end());  // 去重
        int res = 1;
        for (auto num : s) {
            if (s.count(num - 1) != 0) continue;  // num 为于子序列中部
            int len = 1;  // 当 num 位于子序列最左边,往下进行,统计序列长度
            while (s.count(num + 1) != 0) {  // num 位于子序列的最左边,统计连续的情况
                len++;
                num++;
            }
            res = max(res, len);
        }
        return res;
    }
};

129、求根到叶子节点数字之和(medium)

给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。
例如,从根到叶子节点路径 1->2->3 代表数字 123。
计算从根到叶子节点生成的所有数字之和。
说明: 叶子节点是指没有子节点的节点。

输入: [1,2,3]
    1
   / \
  2   3
输出: 25
解释:
从根到叶子节点路径 1->2 代表数字 12.
从根到叶子节点路径 1->3 代表数字 13.
因此,数字总和 = 12 + 13 = 25.

输入: [4,9,0,5,1]
    4
   / \
  9   0
 / \
5   1
输出: 1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495.
从根到叶子节点路径 4->9->1 代表数字 491.
从根到叶子节点路径 4->0 代表数字 40.
因此,数字总和 = 495 + 491 + 40 = 1026.

/**
 * 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:
    void sumNumberDFS(TreeNode* root, int path, int& all_path) {
        if (!root) return ;
        path = path * 10 + root -> val;
        if (root -> left == NULL && root -> right == NULL) {
            all_path += path;
            return ;
        }
        sumNumberDFS(root -> left, path, all_path);
        sumNumberDFS(root -> right, path, all_path);
    }

    int sumNumbers(TreeNode* root) {
        int path = 0, all_path = 0;
        sumNumberDFS(root, path, all_path);
        return all_path;
    } 
};

130、被围绕的区域(medium)

给定一个二维的矩阵,包含 'X''O'(字母 O)。
找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O''X' 填充。

X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

class Solution {
public:
    void solve(vector<vector<char>>& board) {
        int rows = board.size();
        if (rows == 0) return ;
        int cols = board[0].size();
        for (int i = 0; i < rows; i++) {
            for(int j = 0; j < cols; j++) {
                bool edge = i == 0 || i == rows - 1 || j == 0 || j == cols - 1;
                if (edge && board[i][j] == 'O') {
                    dfs(board, i , j);
                }
            }
        }
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (board[i][j] == '#') {
                    board[i][j] = 'O';
                }
                else board[i][j] = 'X';
            }
        }
    }

    void dfs(vector<vector<char>>& board, int i, int j) {
        if (i < 0 || i >= board.size() || j < 0 || j >= board[0].size() || board[i][j] == 'X' || board[i][j] == '#')
            return ;
        board[i][j] = '#';
        dfs(board, i - 1, j);
        dfs(board, i + 1, j);
        dfs(board, i, j - 1);
        dfs(board, i, j + 1);
    }
};

131、分割回文串(medium)

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。

输入: "aab"
输出:
[
  ["aa","b"],
  ["a","a","b"]
]

class Solution {
public:
    vector<vector<string>> partition(string s) {
        vector<vector<string>> res;
        vector<string> cur;
        dfs(s, cur, res);
        return res;
    }

    bool isPalindrome(string s) {
        return s == string(s.rbegin(), s.rend());
    }

    void dfs(string s, vector<string>& cur, vector<vector<string>>& res) {
        if (s == "") {
            res.push_back(cur);
            return ;
        }
        for (int i = 1; i <= s.size(); i++) {
            string tmp = s.substr(0, i);
            if (isPalindrome(tmp)) {
                cur.push_back(tmp);
                dfs(s.substr(i, s.size() - 1), cur, res);
                cur.pop_back();
            }
        }
    }
};

132、分割回文串II(hard)

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回符合要求的最少分割次数。

输入: "aab"
输出: 1
解释: 进行一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。

class Solution {
public:
    int minCut(string s) {
        // 动态规划
        // dp[i]: s[0 : i] 是回文需要的分割次数
        // dp[i] = min(dp[i], dp[j] + 1) if dp[j + 1 : i] 是回文
        int len = s.size();
        int dp[len];
        // 初始化
        for (int i = 0; i < len; i++)
            dp[i] = i;
        // 记录子串 s[a : b]是否是回文,一开始初始化为false
        vector<vector<bool>> checkPalindrome(len, vector<bool> (len, false));
        for (int right = 0; right < len; right++) {
            for (int left = 0; left <= right; left++) {
                if (s[left] == s[right] && (right - left <= 2 || checkPalindrome[left + 1][right - 1]))
                    checkPalindrome[left][right] = true;
            }
        }
        // 状态转移
        for (int i = 0; i < len; i++) {
            if (checkPalindrome[0][i]) {
                dp[i] = 0;
                continue;
            }
            for (int j = 0; j < i; j++) {
                if(checkPalindrome[j + 1][i]) {
                    dp[i] = min(dp[i], dp[j] + 1);
                }
            }
        }
        return dp[len - 1];
    }
};

133、克隆图(medium)

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。
图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node])。
class Node {
    public int val;
    public List<Node> neighbors;
}
 
测试用例格式:
简单起见,每个节点的值都和它的索引相同。
例如,第一个节点值为 1,第二个节点值为 2,以此类推。
该图在测试用例中使用邻接列表表示。
邻接列表是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。
给定节点将始终是图中的第一个节点(值为 1)。你必须将给定节点的拷贝作为对克隆图的引用返回。

输入:adjList = [[2,4],[1,3],[2,4],[1,3]]
输出:[[2,4],[1,3],[2,4],[1,3]]
解释:
图中有 4 个节点。
节点 1 的值是 1,它有两个邻居:节点 24 。
节点 2 的值是 2,它有两个邻居:节点 13 。
节点 3 的值是 3,它有两个邻居:节点 24 。
节点 4 的值是 4,它有两个邻居:节点 13 。

输入:adjList = [[]]
输出:[[]]
解释:输入包含一个空列表。该图仅仅只有一个值为 1 的节点,它没有任何邻居。

输入:adjList = []
输出:[]
解释:这个图是空的,它不含任何节点。

输入:adjList = [[2],[1]]
输出:[[2],[1]]

提示:
节点数介于 1100 之间。
每个节点值都是唯一的。
无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
由于图是无向的,如果节点 p 是节点 q 的邻居,那么节点 q 也必须是节点 p 的邻居。
图是连通图,你可以从给定节点访问到所有节点。

/*
// Definition for a Node.
class Node {
public:
    int val;
    vector<Node*> neighbors;
    
    Node() {
        val = 0;
        neighbors = vector<Node*>();
    }
    
    Node(int _val) {
        val = _val;
        neighbors = vector<Node*>();
    }
    
    Node(int _val, vector<Node*> _neighbors) {
        val = _val;
        neighbors = _neighbors;
    }
};
*/
class Solution {
public:
    Node* cloneGraph(Node* node) {
        // BFS + map
        // 1、根据原来节点的节点数,和节点值创建新的节点
        // 2、根据原来节点的对应关系,连接新的对应节点
        if (!node) return NULL;
        queue<Node*> que;
        que.push(node);
        map<Node*, Node*> mp;
        while (!que.empty()) {
            Node* temp = que.front();
            que.pop();
            // 新节点创建
            Node* p = new Node(temp -> val, {});
            mp.insert({temp, p});  // 将新节点 p 与旧节点 temp 之间形成映射
            for (Node* neighborsNode : temp -> neighbors) {
                if (mp.find(neighborsNode) == mp.end()) {
                    que.push(neighborsNode);
                }
            }
        }
        // 遍历所有节点 完成边的连接
        map<Node*, Node*>::iterator iter;
        for (iter = mp.begin(); iter != mp.end(); iter++) {
            for (Node* neighborsNode : iter -> first ->neighbors){
                iter -> second ->neighbors.push_back(mp.find(neighborsNode) -> second);
            }
        }
        return mp.find(node) -> second;
    }
};

134、加油站(medium)

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明: 
如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。

输入: 
gas  = [1,2,3,4,5]
cost = [3,4,5,1,2]
输出: 3
解释:3 号加油站(索引为 3)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。

输入: 
gas  = [2,3,4]
cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int len = gas.size();
        if (len == 0) return -1;
        int cur = 0, total = 0, start_position = 0;
        for (int i = 0; i < len; i++) {
            total += gas[i] - cost[i];
            cur += gas[i] - cost[i];
            if (cur < 0) {
                start_position = i + 1;
                cur = 0;
            }
        }
        return total >= 0 ? start_position : -1;
    }
};

135、分发糖果(hard)

老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。
相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?

输入: [1,0,2]
输出: 5
解释: 你可以分别给这三个孩子分发 212 颗糖果。

输入: [1,2,2]
输出: 4
解释: 你可以分别给这三个孩子分发 121 颗糖果。
     第三个孩子只得到 1 颗糖果,这已满足上述两个条件。

class Solution {
public:
    int candy(vector<int>& ratings) {
        // dp[i] : 第 i 个孩子应该获得的糖果数;
        // 前后各扫描一次
        int len = ratings.size();
        if (len <= 1) return ratings.size();
        vector<int> dp(len, 1);
        for (int i = 1; i < len; i++) {
            if (ratings[i] > ratings[i - 1])
                dp[i] = dp[i - 1] + 1;
        }
        for (int i = len - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1])
                dp[i] = max(dp[i], dp[i + 1] + 1);
        }
        int all_ = 0;
        for (int i = 0; i < len; i++) {
            all_ += dp[i];
        }
        return all_;
    }
};

136、只出现一次的数字(easy)

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

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

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

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        // 异或
        // a 异或 0 = a
        // a 异或 a = 0  
        if (nums.size() == 0) return 0;
        int result = nums[0];
        for (int i = 1; i < nums.size(); i++) {
            result ^= nums[i];
        }
        return result;
    }
};

137、只出现一次的数字II

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

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

输入: [0,1,0,1,0,1,99]
输出: 99

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        // 各个数字和的二进制每位中1的个数是 3 的倍数或者0;
        int res = 0;
        for (int i = 0; i < 32; i++) {
            int tmp = 0;
            for (int j = 0; j < nums.size(); j++) {
                tmp += (nums[j] >> i) & 1;
            }
            res ^= (tmp % 3) << i;
        }
        return res;
    }
};

138、复制带随机指针的链表(medium)

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的 深拷贝。 
我们用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == NULL) return head;
        Node* p = head;
        while (p != nullptr) {
            Node* new_ = new Node(p -> val);
            new_ -> next = p -> next;
            p -> next = new_;
            p = new_ -> next;
        }
        p = head;
        while (p != nullptr) {
            Node* copy_next = p -> next;
            if (p -> random) {
                copy_next -> random = p -> random -> next;
            }
            p = copy_next -> next;
        }
        p = head;
        Node* copy_ = p -> next;
        while (p -> next != nullptr) {
            Node* tmp = p -> next;
            p -> next = tmp -> next;
            p = tmp;
        }
        return copy_;
    }
};

139、单词拆分

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。

输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
     注意你可以重复使用字典中的单词。

输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        // 动态规划
        vector<bool> dp(s.size() + 1, false);
        dp[0] = true;
        for (int i = 0; i <= s.size(); i++) {
            for (auto word : wordDict) {
                int word_size = word.size();
                if (i - word_size >= 0) {
                    // s 中 i - word_size 开始,长度为 word_size 的子串与 word 相比较,相同则返回 0
                    int cur = s.compare(i - word_size, word_size, word);
                    if (cur == 0 && dp[i - word_size]) 
                        dp[i] = true;
                }
            }
        }
        return dp[s.size()];
    }
};

141、环形链表(easy)

给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
如果 pos 是 -1,则在该链表中没有环。

3 -> 2 -> 0 -> -4
     ^          | 
     | -------- V
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

1 - 2
^   |
|---V
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
    	// 快慢指针
        if (head == NULL || head -> next == NULL) return false;
        ListNode* p1 = head;
        ListNode* p2 = head;
        while (p2 != NULL && p2 -> next != NULL) {
            p1 = p1 -> next;
            p2 = p2 -> next -> next;
            if (p1 == p2) return true;
        }
        return false;
    }
};

142、环形链表II(medium)

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 
如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。

3 -> 2 -> 0 -> -4
     ^          | 
     | -------- V
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。


1 - 2
^   |
|---V
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。

输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head == NULL || head -> next == NULL) return NULL;
        ListNode* p1 = head;  // 慢指针
        ListNode* p2 = head;  // 快指针
        while (p2 != NULL && p2 -> next != NULL) {
            p1 = p1 -> next;
            p2 = p2 -> next -> next;
            if (p1 == p2) {
                p2 = head;
                while(p1 != p2) {
                    p1 = p1 -> next;
                    p2 = p2 -> next;
                } 
                return p1;
            }
        }
        return NULL;
    }
};

143、重排链表(medium)

给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

给定链表 1->2->3->4, 重新排列为 1->4->2->3.

给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverse(ListNode* head) {
        ListNode* p_head = head;
        ListNode* p_pre = NULL;
        while (p_head) {
            ListNode* tmp = p_head -> next;
            p_head -> next = p_pre;
            p_pre = p_head;
            p_head = tmp;
        }
        return p_pre;
    }

    void reorderList(ListNode* head) {
        // 找中间节点
        // 中间节点反转
        // 合并
        if (head == NULL || head -> next == NULL) return ;
        ListNode* p_slow = head;
        ListNode* p_fast = head;
        while (p_fast != NULL &&  p_fast -> next != NULL) {
            p_slow = p_slow -> next;
            p_fast = p_fast -> next ->next;
        }
        ListNode* p_mid = p_slow -> next;  // 中间节点之后的节点
        p_slow -> next = NULL;  // 左边 head -...-p_slow - NULL;  p_mid(p_slow -> next) ...
        p_mid = reverse(p_mid);
        ListNode* left = head;
        while (left -> next != NULL && p_mid != NULL) {
            // 保存下一个节点
            ListNode* leftTemp = left -> next;
            ListNode* rightTemp = p_mid -> next;

            // 左 1->2->3 右 5->4
            // 左 1->5->2->3
            left -> next = p_mid;
            p_mid -> next = leftTemp;
            
            // 左 2->3 右 4
            left = leftTemp;
            p_mid = rightTemp;
        }
    }
};

144、二叉树的前序遍历(medium)

给定一个二叉树,返回它的 前序 遍历。

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 
输出: [1,2,3]

/**
 * 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:
    void recursive(TreeNode* root, vector<int>& res) {
        if (root == NULL) return ;
        res.push_back(root -> val);
        recursive(root -> left, res);
        recursive(root -> right, res);
    }

    void iteration(TreeNode* root, vector<int>& res) {
        stack<TreeNode*> stk;
        stk.push(root);
        TreeNode* cur = NULL;
        while (!stk.empty()) {
            cur = stk.top();
            stk.pop();
            res.push_back(cur -> val);
            if (cur -> right) stk.push(cur -> right);
            if (cur -> left) stk.push(cur -> left);
        }
    }

    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        if (root == NULL) return res;
        // recursive(root, res);  // 递归
        iteration(root, res);  // 迭代
        return res;
    }
};

145、二叉树的后序遍历(hard)

给定一个二叉树,返回它的 后序 遍历。

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 
输出: [3,2,1]

/**
 * 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:
    void recursive(TreeNode* root, vector<int>& res) {
        if(root == NULL) return ;
        recursive(root -> left, res);
        recursive(root -> right, res);
        res.push_back(root -> val);
    }

    void iteration(TreeNode* root, vector<int>& res) {
        stack<TreeNode*> stk1, stk2;
        stk1.push(root);
        while (!stk1.empty()) {
            TreeNode* tmp = stk1.top();
            stk1.pop();
            stk2.push(tmp);
            if (tmp -> left) stk1.push(tmp -> left);
            if (tmp -> right) stk1.push(tmp -> right);
        }
        while(!stk2.empty()) {
            res.push_back(stk2.top() -> val);
            stk2.pop();
        }
    }

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        if (root == NULL) return res;
        // recursive(root, res);  // 递归
        iteration(root, res);  // 迭代
        return res;
    }
};

146、LRU缓存机制(medium)

运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得密钥 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得密钥 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4

class LRUCache {
private:
    int cap;
    // 双链表:存储 (key, value) 元组
    list<pair<int , int>> cache;
    // 哈希表:key 映射到 (key, value) 在 cache 中的位置
    unordered_map<int, list<pair<int, int>>::iterator> un_mp;
public:
    LRUCache(int capacity) {
        this -> cap = capacity;
    }
    
    int get(int key) {
        auto it = un_mp.find(key);
        if (it == un_mp.end()) return -1;  // 访问的 key 不存在
        // 将 (key, value) 放到队头
        pair<int, int> kv = *un_mp[key];
        cache.erase(un_mp[key]);
        cache.push_front(kv);
        // 更新 (key, value) 在cache中的位置
        un_mp[key] = cache.begin();
        return kv.second;
    }
    
    void put(int key, int value) {
        auto it = un_mp.find(key);
        if (it == un_mp.end()) {  // 如果 key 不存在
            if (cache.size() == cap) {  // cache 已满
                auto last_pair = cache.back();
                int last_key = last_pair.first;
                un_mp.erase(last_key);
                cache.pop_back();
            }
            // 没满
            cache.push_front(make_pair(key, value));
            un_mp[key] = cache.begin();
        }
        else {
            // 如果 key 存在,更改value并换到队头
            cache.erase(un_mp[key]);
            cache.push_front(make_pair(key, value));
            un_mp[key] = cache.begin();
        }
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

147、对链表进行插入排序(medium)

对链表进行插入排序。

插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。
插入排序算法:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。

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

输入: -1->5->3->4->0
输出: -1->0->3->4->5

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* insertionSortList(ListNode* head) {
        if (head == nullptr || head -> next == nullptr) return head;
        ListNode* pDummy = new ListNode(0);
        pDummy -> next = head;
        ListNode* p_pre = head;
        ListNode* node = head -> next;
        while (node) {
            if (node -> val < p_pre -> val) {
                ListNode* temp = pDummy;
                while (temp -> next ->val < node -> val) {
                    temp = temp -> next;
                }
                p_pre -> next = node -> next;
                node -> next = temp -> next;
                temp -> next = node;
                node = p_pre -> next;
            }
            else {
                p_pre = p_pre -> next;
                node = node -> next;
            }
        }
        return pDummy -> next;
    }
};

148、排序链表(medium)

O(nlogn) 时间复杂度和常数级空间复杂度下,对链表进行排序。

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

输入: -1->5->3->4->0
输出: -1->0->3->4->5

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        // 归并排序
        if (head == nullptr || head -> next == nullptr) return head;
        ListNode* p_fast = head -> next;  // 注意这里的 p_fast 不是 head, 而是 head -> next
        ListNode* p_slow = head;
        while (p_fast != nullptr && p_fast -> next != nullptr) {
            p_slow = p_slow -> next;
            p_fast = p_fast -> next;
        }
        ListNode* tmp = p_slow -> next;
        p_slow -> next = nullptr;
        ListNode* left = sortList(head);  // 左边归并后正序链表
        ListNode* right = sortList(tmp);  // 右边归并后正序链表
        ListNode* p_dummy = new ListNode(0);
        ListNode* p_new = p_dummy;
        while(left != nullptr && right != nullptr) {
            if (left -> val < right -> val) {
                p_new -> next = left;
                left = left -> next;
            }
            else {
                p_new -> next = right;
                right = right -> next;
            }
            p_new = p_new -> next;
        }
        p_new -> next = left != nullptr ? left : right;
        return p_dummy -> next;
    }
};

149、直线上最多的点数(hard)

给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。

输入: [[1,1],[2,2],[3,3]]
输出: 3
解释:
^
|
|        o
|     o
|  o  
+------------->
0  1  2  3  4

输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
输出: 4
解释:
^
|
|  o
|     o        o
|        o
|  o        o
+------------------->
0  1  2  3  4  5  6

class Solution {
public:
    int maxPoints(vector<vector<int>>& points) {
        // 暴力解
        int size_ = points.size();
        if (size_ <= 2) return size_;
        int num_oneline = 0, max_num = 0;
        for (int i = 0; i < size_; i++) {
            int x1 = points[i][0];
            int y1 = points[i][1];
            for (int j = i + 1; j < size_; j++) {
                if(points[j][0] == x1 && points[j][1] == y1) continue; 
                int x2 = points[j][0];
                int y2 = points[j][1];
                num_oneline = 0;
                for(int z = 0; z < size_; z++) {
                    if(points[z][0] == x1 && points[z][1] == y1)  num_oneline++;
                    else if(points[z][0] == x2 && points[z][1] == y2)  num_oneline++;
                    else if (long(points[z][1] - y1) * (x2 - x1) == long(points[z][0] - x1) * (y2 - y1)) num_oneline++;
                }
                max_num = max(max_num, num_oneline);
            }
        }
        if(max_num == 0) return points.size();  // 解决全是一个点的问题
        return max_num;
    }
};

150、逆波兰表达式求值(medium)

根据逆波兰表示法,求表达式的值。
有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

输入: ["2", "1", "+", "3", "*"]
输出: 9
解释: ((2 + 1) * 3) = 9

输入: ["4", "13", "5", "/", "+"]
输出: 6
解释: (4 + (13 / 5)) = 6

输入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
输出: 22
解释: 
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> stk;
        int len_t = tokens.size(); 
        for (int i = 0; i < len_t; i++) {
            if (tokens[i] == "+") {
                int a = stk.top();
                stk.pop();
                int b = stk.top();
                stk.pop();
                stk.push(b + a);
            }
            else if (tokens[i] == "-") {
                int a = stk.top();
                stk.pop();
                int b = stk.top();
                stk.pop();
                stk.push(b - a);
            }
            else if (tokens[i] == "*") {
                int a = stk.top();
                stk.pop();
                int b = stk.top();
                stk.pop();
                stk.push(b * a);
            }
            else if (tokens[i] == "/") {
                int a = stk.top();
                stk.pop();
                int b = stk.top();
                stk.pop();
                stk.push(b / a);
            }
            else {
                int tmp = atoi(tokens[i].c_str());
                stk.push(tmp);
            }
        }
        return stk.top();
    }
};
  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是 LeetCode 27 题的 C++ 完整实现,包含题目描述和注释: ```c++ /* 题目描述: 给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 示例 1: 给定 nums = [3,2,2,3],val = 3, 函数应该返回新的长度 2,nums 中前两个元素均为 2。 示例 2: 给定 nums = [0,1,2,2,3,0,4,2],val = 2, 函数应该返回新的长度 5,nums 中前五个元素为 0、1、3、0、4。 说明: 为什么返回数值是整数,但输出的答案是数组呢? 请注意,输入数组是以“引用”方式传递的,这意味着在函数内部修改输入数组对于调用者是可见的。 你可以想象内部操作如下: // nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝 int len = removeElement(nums, val); // 在函数里修改输入数组对于调用者是可见的。 // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 for (int i = 0; i < len; i++) { print(nums[i]); } 解题思路: 使用指针遍历数组,遇到等于 val 的元素就将其覆盖掉,直到遍历完整个数组。 使用另一个指针记录当前已经覆盖掉了几个元素,即新数组的长度。 时间复杂度为 O(n),空间复杂度为 O(1)。 参考链接: https://leetcode-cn.com/problems/remove-element/ */ class Solution { public: int removeElement(vector<int>& nums, int val) { int n = nums.size(); int len = 0; // 记录新数组的长度 for (int i = 0; i < n; i++) { if (nums[i] != val) { // 当前元素不等于 val,可以保留到新数组中 nums[len++] = nums[i]; // 覆盖掉新数组的下一个位置 } } return len; // 返回新数组的长度 } }; ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值