2024届毕业生的刷题记录之《LeetCode 热题 HOT 100》


开个博客来督促自己坚持每日一题~
(但现在每日一题已经不够了)


LeetCode 热题 HOT 100

两数之和

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

思路
有手就会的题,直接遍历完事…但是如果想做到时间复杂度小于 O ( n 2 ) O(n^2) O(n2)的话,可以找个时间复杂度为 O ( n log ⁡ ( n ) ) O(n\log(n)) O(nlog(n))的算法派个序,再从头尾向中间寻找target就行了(和大了尾巴就往左边走,和小了头部就往右边走)。

代码

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> result;
        for(int i = 0; i < nums.size() - 1; ++i) {
            for(int j = i + 1; j < nums.size(); ++j) {
                if(nums[i] + nums[j] == target) {
                    result.push_back(i);
                    result.push_back(j);
                    return result;
                }
            }
        }
        return result;
    }
};

两数相加

描述
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 0 0 之外,这两个数都不会以 0 0 0 开头。

示例
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807

思路
有两个点要注意:(1)两个数不一样长;(2)进位问题,就算剩下一个数的时候,也要考虑进位的问题。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *result = new ListNode();
        ListNode *now = new ListNode(0, result);
        int digit = 0, add = 0;
        while(l1 != nullptr && l2 != nullptr) {
            if(now->next == nullptr) {
                now->next = new ListNode();
            }
            now = now->next;
            int tmp_result = l1->val + l2->val + add;
            add = 0;
            if(tmp_result < 10) {
                now->val += tmp_result;
            }
            else {
                now->val += tmp_result - 10;
                add = 1;
            }
            l1 = l1->next;
            l2 = l2->next;
            ++digit;
        }
        ListNode *left = nullptr;
        if(l1 != nullptr) {
            left = l1;
        } 
        else if(l2 != nullptr) {
            left = l2;
        }
        while(left != nullptr) {
            if(now->next == nullptr) {
                now->next = new ListNode();
            }
            now = now->next;

            int tmp_result = left->val + add;
            add = 0;
            if(tmp_result < 10) {
                now->val += tmp_result;
            }
            else {
                now->val += tmp_result - 10;
                add = 1;
            }
            left = left->next;
            ++digit;
        }
        if(add == 1) {
            now->next = new ListNode(1);
        }
        return result;
    }
};

无重复字符的最长子串

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

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

思路
有点类似于BM算法的思想。
(1)维护记录当前最长子串在原串中的左右下标;
(2)每次右下标向右移动一个字符,判断该字符是否在当前最长子串中出现过:
(2.a)如果没有出现,那么继续向右移;
(2.b)如果出现了,那么左下标就移动至该字符在当前最长子串中出现的位置的右侧,这样保证新的最长子串没有重复的字符;
(3)更新最长子串的长度。

代码

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        // 检查输入
        if(s.size() == 0) {
            return 0;
        }

        // 寻找最长字串
        int left = 0, right = 1, maxLen = 1;
        while(right < s.size()) {
            string sub = s.substr(left, right - left);
            int index = sub.find(s[right]);
            if(index < 0 || index > sub.size()) {
                ++right;
            }
            else {
                left += index + 1;
                ++right;
            }

            sub = s.substr(left, right - left);
            if(sub.size() > maxLen) {
                maxLen = sub.size();
            }
        }

        return maxLen;
    }
};

寻找两个正序数组的中位数

描述
给定两个大小分别为 m m m n n n 的正序(从小到大)数组 n u m s 1 nums1 nums1 n u m s 2 nums2 nums2。请你找出并返回这两个正序数组的 中位数

算法的时间复杂度应该为 O ( l o g ( m + n ) ) O(log (m+n)) O(log(m+n))

示例
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3],中位数 2

思路
最简单、直接的思路是合并两个数组,但不用完全合并,只需要合并一半就行了。
最后输出的时候需要考虑总长度是奇数还是偶数。

代码

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        vector<int> nums;
        int mid_index = (nums1.size() + nums2.size()) / 2;
        int i1 = 0, i2 = 0;
        while(nums.size() < mid_index + 1) {
            if(i1 >= nums1.size()) {
                nums.push_back(nums2[i2]);
                ++i2;
            }
            else if(i2 >= nums2.size()) {
                nums.push_back(nums1[i1]);
                ++i1;
            }
            else {
                if(nums1[i1] >= nums2[i2]) {
                    nums.push_back(nums2[i2]);
                    ++i2;
                }
                else {
                    nums.push_back(nums1[i1]);
                    ++i1;
                }
            }
        }

        if((nums1.size() + nums2.size()) % 2 == 1) {
            return nums[mid_index];
        }
        else {
            return (nums[mid_index] + nums[mid_index - 1]) / 2.0;
        }
    }
};

标准答案
我的做法的时间复杂度和空间复杂度都会高一点,所以贴一下标准的思路吧。
题目是求中位数,其实就是求第 k 小数的一种特殊情况,而求第 k 小数有一种算法:由于数列是有序的,其实我们完全可以一半儿一半儿的排除。假设我们要找第 k 小数,我们可以每次循环排除掉 k/2 个数。

根据标准答案写的代码

class Solution {
public:
    double findKthMinest(int k, vector<int>& nums1, vector<int>& nums2) {
        // 处理某一个数组为空的情况
        if(nums1.size() == 0) {
            return nums2[k - 1];
        }
        if(nums2.size() == 0) {
            return nums1[k - 1];
        }
        
        // 递归终止条件
        if(k == 1) {
            if(nums1[0] < nums2[0]) {
                return nums1[0];
            }
            else {
                return nums2[0];
            }
        }
        
        // 初始化二分法参数
        int i1 = 0, i2 = 0, index = k / 2 - 1;
        if(i1 + index >= nums1.size()) {
            i1 = nums1.size() - 1;
        }
        else {
            i1 = i1 + index;
        }
        if(i2 + index >= nums2.size()) {
            i2 = nums2.size() - 1;
        }
        else {
            i2 = i2 + index;
        }
        
        if(nums1[i1] <= nums2[i2]) {
            vector<int> new_nums1(nums1.begin() + i1 + 1, nums1.end());
            return findKthMinest(k - i1 - 1, new_nums1, nums2);
        }
        else {
            vector<int> new_nums2(nums2.begin() + i2 + 1, nums2.end());
            return findKthMinest(k - i2 - 1, nums1, new_nums2);
        }
    }

    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int k = (nums1.size() + nums2.size()) / 2;
        
        if((nums1.size() + nums2.size()) % 2 == 0) {
            return (findKthMinest(k, nums1, nums2) + findKthMinest(k + 1, nums1, nums2)) / 2.0;
        }
        else {
            return findKthMinest(k + 1, nums1, nums2);
        }
    }
};

最长回文子串

描述
给你一个字符串 s s s,找到 s s s 中最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例
输入:s = "babad"
输出:"bab"
解释:"aba"同样是符合题意的答案。

思路
要注意回文串可能是奇数,也可能是偶数,这2种情况判断的方法还是略微有点不同的,因此我遍历数组的时候。

另外,解析里面提到了一种时间复杂度为 O ( n ) O(n) O(n) 的算法,但挺难的,希望笔面试都不会考到吧…

代码

class Solution {
public:
    string longestPalindrome(string s) {
        string maxS = s.substr(0, 1);
        for(double i = 0.5; i < s.size(); i += 0.5) {
            // 回文串长度为偶数
            if(int(i) != i) {
                int li = int(i - 0.5), ri = int(i + 0.5);
                for(int j = 0; li - j >= 0 && ri + j < s.size(); ++j) {
                    if(s[li - j] == s[ri + j]) {
                        if(s.substr(li - j, (j + 1) * 2).size() > maxS.size()) {
                            maxS = s.substr(li - j, (j + 1) * 2);
                        }
                    }
                    else {
                        break;
                    }
                }
            }
            // 回文串长度为奇数
            else {
                int ii = int(i);
                for(int j = 1; ii - j >= 0 && ii + j < s.size(); ++j) {
                    if(s[ii - j] == s[ii + j]) {
                        if(s.substr(ii - j, j * 2 + 1).size() > maxS.size()) {
                            maxS = s.substr(ii - j, j * 2 + 1);
                        }
                    }
                    else {
                        break;
                    }
                }
            }
        }
        return maxS;
    }
};

N字形变换

描述
将一个给定字符串 s s s 根据给定的行数 n u m R o w s numRows numRows ,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 "PAYPALISHIRING"行数为 3时,排列如下:

P         A         H       N
A    P    L    S    I    I   G
Y         I         R

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);

示例
输入:s = "PAYPALISHIRING", numRows = 3
输出:"PAHNAPLSIIGYIR"

思路
因为要输出所有字符,所以最小的时间复杂度也是 O ( n ) O(n) O(n)
唯一有区别的就是时间复杂度,如果能够推导出每一行字符的规律,那么就无需借助额外的数组,但是我觉得不是很有必要来省这点空间复杂度。
另外要注意输出行数为1的极端情况。

代码

class Solution {
public:
    string convert(string s, int numRows) {
        // Extreme conditions
        if(numRows == 1) {
            return s;
        }
        
        // Init
        string* rows = new string[numRows];
        for(int i = 0; i < numRows; ++i) {
            rows[i] = "";
        }
        
        // Convert
        int row = 0, dir = 1;
        for(int i = 0; i < s.size(); ++i) {
            rows[row] += s[i];
            row += dir;
            if(row == 0) {
                dir = -dir;
            }
            else if(row == numRows - 1) {
                dir = -dir;
            }
        }
        
        // Output
        string result = "";
        for(int i = 0; i < numRows; ++i) {
            result += rows[i];
        }
        delete[] rows;
        return result;
    }
};

整数反转

描述
给你一个 32 位的有符号整数 x x x ,返回将 x x x 中的数字部分反转后的结果。

如果反转后整数超过 32 32 32 位的有符号整数的范围 [ − 2 31 , 2 31 − 1 ] [−2^{31}, 2^{31} − 1] [231,2311] ,就返回 0 0 0

提示:
假设环境不允许存储 64 位整数(有符号或无符号)。
− 2 31 < = x < = 2 31 − 1 -2^{31} <= x <= 2^{31} - 1 231<=x<=2311

示例
输入:x = -123
输出:-321

思路
整体不难,但是有好几个坑要注意:
(1)遇到负数的时候我们正常会把负号提取出来,然后把 x x x 转换为正整数处理,但是需要记住C++中 signed int负数范围是比正数范围要大的;
(2)C++中stringint的函数是stoi()intstring的函数是to_string()

代码

class Solution {
public:
    int reverse(int x) {
        // Init and Extreme conditions
        string tmp = "", sign = "";
        if (x == 0 || x == -2147483648) {
            return 0;
        }
        else if (x < 0) {
            sign = "-";
            x = -x;
        }
        
        // Reverse
        while (x > 0) {
            tmp += to_string(x % 10);
            x = x / 10;
        }
        
        // Delete the leading 0s
        for (int i = 0; i < tmp.size(); ++i) {
            if (tmp[i] == '0') {
                continue;
            }
            else {
                tmp = tmp.substr(i, tmp.size() - i);
                break;
            }
        }
        
        // Determine whether the inverted integer exceeds 32 bits
        if (tmp.length() == 10) {
            string bound;
            if (sign == "") {
                bound = "2147483647";
            }
            else {
                bound = "2147483648";
            }
            for (int i = 0; i < tmp.size(); ++i) {
                if (tmp[i] > bound[i]) {
                    return 0;
                }
                else if (tmp[i] < bound[i]){
                    break;
                }
            }
        }
        
        return stoi(sign + tmp);
    }
};

字符串转换整数

描述
请你来实现一个 myAtoi(string s)函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi函数)。

函数 myAtoi(string s)的算法如下:

  1. 读入字符串并丢弃无用的前导空格
  2. 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
  3. 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
  4. 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 0 0 。必要时更改符号(从步骤 2 开始)。
  5. 如果整数数超过 32 32 32 位有符号整数范围 [ − 2 31 , 2 31 − 1 ] [−2^{31}, 2^{31} − 1] [231,2311] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 2 31 2^{31} 231 ,大于 2 31 − 1 2^{31} − 1 2311 的整数应该被固定为 2 31 − 1 2^{31} − 1 2311
  6. 返回整数作为最终结果。

注意:

  • 本题中的空白字符只包括空格字符 ' '
  • 除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。

示例
输入:s = "4193 with words"
输出:4193

思路
该题和上一题非常相似,只不过多了一个读取数字的操作而已。因为题目给出的算法步骤已经非常详细了,所以考验的更多的是编程能力以及对极限测试用例的判断。

代码

class Solution {
public:
    int myAtoi(string s) {
        // 初始化
        int begin = 0;
        
        // 丢弃无用的前导空格
        while (s[begin] == ' ' && begin < s.size()) {
            ++begin;
        }
        if (begin == s.size()) {
            return 0;
        }
        
        // 检查符号
        string sign = "";
        if (s[begin] == '-') {
            sign = "-";
            ++begin;
        }
        else if(s[begin] == '+') {
            ++begin;
        }
        if (begin == s.size()) {
            return 0;
        }
        
        // 读入数字
        int end = begin;
        while (s[end] - '0' >= 0 && s[end] - '0' <= 9) {
            ++end;
        }
        // 没有数字则返回0
        if (end == begin) {
            return 0;
        }
        
        // 删除多余的0
        while(s[begin] == '0') {
            ++begin;
        }
        
        // 截取整串数字
        string tmp = s.substr(begin, end - begin);
        if (tmp.size() == 0) {
            return 0;
        }
        
        // 判断整数数超过 32 位有符号整数范围, 是的话就截断
        if (tmp.length() > 10) {
            if (sign == "") {
                return 2147483647;
            }
            else {
                return -2147483648;
            }
        }
        else if (tmp.length() == 10) {
            string bound;
            if (sign == "") {
                bound = "2147483647";
            }
            else {
                bound = "2147483648";
            }
            for (int i = 0; i < tmp.size(); ++i) {
                if (tmp[i] > bound[i]) {
                    return stoi(sign + bound);
                }
                else if (tmp[i] < bound[i]){
                    break;
                }
            }
        }
        
        return stoi(sign + tmp);
    }
};

回文数

描述
给你一个整数 x x x ,如果 x x x 是一个回文整数,返回 true;否则,返回 false

回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

例如, 121 121 121 是回文,而 123 123 123 不是。

示例
输入:x = -121
输出:false

思路
根据题目的要求:
(1) 负数肯定不是回文数;
(2) 0肯定是回文数;
(3) 正数的话要满足正序和倒序读都是一样的才是,最简单的就是之间转换成字符串来处理;

代码

class Solution {
public:
    bool isPalindrome(int x) {
        if (x < 0) {
            return false;
        }
        else if (x == 0) {
            return true;
        }
        else {
            string s = to_string(x);
            int l = 0, r = s.size() - 1;
            while(l < r) {
                if (s[l] != s[r]) {
                    return false;
                }
                ++l;
                --r;
            }
            return true;
        }
    }
};

标准答案
因为在进阶要求里面,不允许借助字符串来实现这一程序,所以要换种思路,直接通过比较数字来判断是否属于回文数。
先考虑数字位数是偶数的情况,回文数即代表着数字后半部分反过来后要等于前半部分;再反观奇数的情况,其实最中间的数字是什么并不重要,我们要判断的还是去掉中间数字后数字后半部分反过来后是否等于前半部分
因此我们要解决的问题就是如何将数字的后半部分反过来,并且如何知道反转数字的位数已经达到原始数字位数的一半?由于整个过程我们不断将原始数字除以 10,然后给反转后的数字乘上 10,所以,当原始数字小于或等于反转后的数字时,就意味着我们已经处理了一半位数的数字了。

正则表达式匹配

描述
给你一个字符串s和一个字符规律p,请你来实现一个支持'.''*'的正则表达式匹配。

  • '.'匹配任意单个字符;
  • '*'匹配零个或多个前面的那一个元素;

所谓匹配,是要涵盖 整个 字符串s的,而不是部分字符串。

思路
这题乍一看非常难,但是用递归的思路来解决,就会很清晰明了。
首先说一下递归的终止条件:

  • 字符串不为空、模式串为空,无法匹配,返回false
  • 字符串为空、模式串不为空,且模式串首模式不带有*,不可能匹配成功,返回false
  • 字符串和模式串都为空,匹配成功,返回true
  • 字符串首字符和模式串首模式不匹配,证明无法匹配,返回false

接下来就是如何递归了:

  • 模式串首模式是简单的字符,不包括'.''*',那么直接去掉字符串和模式串的首字符,进入下一层递归就可以了;
  • 模式串首模式包含了'.''*',那么就要继续分情况讨论了:
    – 只有'.',那么要往下匹配就只能用这个来匹配字符串首字符,所以直接去掉字符串和模式串的首字符,进入下一层递归就可以了;
    – 只有'*'和前一个字符,如果前一个字符可以匹配上字符串首字符,那么就进入去掉字符串首字符和模式串首模式、只去掉字符串首字符、只去掉模式串首模式这三种情况;如果前一个字符不能匹配上字符串首字符,那么就只进入去掉模式串首模式这种情况。

但是上面根据上面的思路直接做居然超出时间限制了!!!所以想一下怎么缩减时间复杂度。

首先从模式串本身入手,例如a*a*这种模式可以化简为a*,这样能省去很多不必要的时间开销。
(以上简化思路受到了没通过的测试用例的启发)

代码

class Solution {
public:
    bool match(string s, string p) {
        // 递归终止条件
        if (s.size() == 0 && p.size() == 0) {
            return true;
        }
        if (s.size() != 0 && p.size() == 0) {
            return false;
        }
        if (s.size() == 0 && p.size() != 0) {
            if (p.size() > 1 && p[1] == '*') {
                return match(s, p.substr(2, p.size() - 2));
            }
            else {
                return false;
            }
        }
        
        // 开始递归
        if (p.size() > 1 && p[1] == '*') {
            if (s[0] == p[0] || p[0] == '.') {
                return match(s.substr(1, s.size() - 1), p.substr(2, p.size() - 2)) ||
                       match(s.substr(1, s.size() - 1), p) ||
                       match(s, p.substr(2, p.size() - 1));
            }
            else {
                return match(s, p.substr(2, p.size() - 2));
            }
        }
        else {
            if (s[0] == p[0] || p[0] == '.') {
                return match(s.substr(1, s.size() - 1), p.substr(1, p.size() - 1));
            }
            else {
                return false;
            }
        }
    }
    
    bool isMatch(string s, string p) {
        int index = 3;
        while (index < p.size()) {
            if (p[index] == '*') {
                if (p[index - 2] == '*') {
                    // "a*a*" to "a*"
                    if (p[index - 3] == p[index - 1]) {
                        p = p.substr(0, index - 3) + p.substr(index - 1, p.size() - index + 1);
                        index -= 2;
                    }
                }
            }
            ++index;
        }
        
        return match(s, p);
    }
};

盛最多水的容器

描述
给定一个长度为 n n n 的整数数组 height。有 n n n 条垂线,第 i i i 条线的两个端点是 (i, 0)(i, height[i])
找出其中的两条线,使得它们与 x x x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。

示例
盛最多水的容器 - 示例
思路
看题目给出的数据范围,直接采用双层循环来遍历肯定会超时(别问我为什么知道 ),所以要想一个巧妙的方法。
这题乍一看确实没想出比较好的做法,无奈只能看别人的答案了。
这题比较经典的做法是双指针法,首先假设数组最左和最右是目前容器的边界,那么基于V=min(height[i], height[j]) * (j - i)这个公式,如果我们移动这两个边界中长的一条,体积毫无疑问会减少;如果移动短的一条,体积可能会增大。所以每次都移动两条边界中短的那一条,记录每次是否超过最大体积,直到边界相遇就可以了。
应该没有人会问“为什么不同时移动两条边界”吧,因为这样会跳过一些容器,应该很容易理解。

代码

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

整数转罗马数字

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

字符数值
I1
V5
X10
L50
C100
D500
M1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给你一个整数,将其转为罗马数字。

示例
输入1: num = 58
输出1: "LVIII"
解释1: L = 50, V = 5, III = 3.
输入2: num = 1994
输出2: "MCMXCIV"
解释2: M = 1000, CM = 900, XC = 90, IV = 4.

思路
因为罗马数字最多也就到千位,所以直接分成个十百千位来处理,虽然代码不是很美观,但是实用就行了;

代码

class Solution {
public:
    string intToRoman(int num) {
        string re = "";
        int tmp = 0;
        
        // 个位
        tmp = num % 10;
        num /= 10;
        if (tmp < 4) {
            while (tmp--) {
                re += "I";
            }
        }
        else if (tmp == 4) {
            re += "IV";
        }
        else if (tmp < 9) {
            re += "V";
            tmp -= 5;
            while (tmp--) {
                re += "I";
            }
        }
        else {
            re += "IX";
        }
        if (num == 0) {
            return re;
        }

        // 十位
        tmp = num % 10;
        num /= 10;
        if (tmp < 4) {
            while (tmp--) {
                re = "X" + re;
            }
        }
        else if (tmp == 4) {
            re = "XL" + re;
        }
        else if (tmp < 9) {
            tmp -= 5;
            while (tmp--) {
                re = "X" + re;
            }
            re = "L" + re;
        }
        else {
            re = "XC" + re;
        }
        if (num == 0) {
            return re;
        }

        // 百位
        tmp = num % 10;
        num /= 10;
        if (tmp < 4) {
            while (tmp--) {
                re = "C" + re;
            }
        }
        else if (tmp == 4) {
            re = "CD" + re;
        }
        else if (tmp < 9) {
            tmp -= 5;
            while (tmp--) {
                re = "C" + re;
            }
            re = "D" + re;
        }
        else {
            re = "CM" + re;
        }
        if (num == 0) {
            return re;
        }

        // 千位
        tmp = num % 10;
        num /= 10;
        while (tmp--) {
            re = "M" + re;
        }
        return re;   
    }
};

罗马数字转整数

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

字符数值
I1
V5
X10
L50
C100
D500
M1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。

给你一个罗马数字,将其转为整数。

示例
输入1: s = "LVIII"
输出1: "58"
解释1: L = 50, V = 5, III = 3.
输入2: “MCMXCIV”
输出2: 1994
解释2: M = 1000, CM = 900, XC = 90, IV = 4.

思路
因为罗马数字最多也就到千位,所以直接分成个十百千位来处理,虽然代码不是很美观,但是实用就行了;

代码

class Solution {
public:
    int romanToInt(string s) {
        int index = 0, re = 0;

        // 千位
        while (s[index] == 'M') {
            re += 1000;
            ++index;
        }

        // 百位
        if (s[index] == 'C') {
            if (index + 1 < s.size() && s[index + 1] == 'D') {
                re += 400;
                index += 2;
            }
            else if (index + 1 < s.size() && s[index + 1] == 'M') {
                re += 900;
                index += 2;
            }
            else {
                while (s[index] == 'C') {
                    re += 100;
                    ++index;
                }
            }
        }
        else if (s[index] == 'D') {
            re += 500;
            ++index;
            while (s[index] == 'C') {
                    re += 100;
                    ++index;
                }
        }

        // 十位
        if (s[index] == 'X') {
            if (index + 1 < s.size() && s[index + 1] == 'L') {
                re += 40;
                index += 2;
            }
            else if (index + 1 < s.size() && s[index + 1] == 'C') {
                re += 90;
                index += 2;
            }
            else {
                while (s[index] == 'X') {
                    re += 10;
                    ++index;
                }
            }
        }
        else if (s[index] == 'L') {
            re += 50;
            ++index;
            while (s[index] == 'X') {
                    re += 10;
                    ++index;
                }
        }

        // 个位
        if (s[index] == 'I') {
            if (index + 1 < s.size() && s[index + 1] == 'V') {
                re += 4;
                return re;
            }
            else if (index + 1 < s.size() && s[index + 1] == 'X') {
                re += 9;
                return re;
            }
            else {
                while (s[index] == 'I') {
                    re += 1;
                    ++index;
                }
            }
        }
        else if (s[index] == 'V') {
            re += 5;
            ++index;
            while (s[index] == 'I') {
                    re += 1;
                    ++index;
                }
        }

        return re;
    }
};

最长公共前缀

描述
编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

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

思路
遍历就完事了~

代码

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        string re = "";
        int i = 0;
        while(true) {
            if (i >= strs[0].size()) {
                return re;
            }
            char tmp = strs[0][i];
            for (int j = 1; j < strs.size(); ++j) {
                if (i >= strs[j].size() || strs[j][i] != tmp) {
                    return re;
                }
            }
            re += tmp;
            ++i;
        }
        return re;
    }
};

三数之和

描述
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]]满足 i != j、i != kj != k,同时还满足 nums[i] + nums[j] + nums[k] == 0。请你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组

示例
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0
不同的三元组是 [-1,0,1][-1,-1,2]
注意,输出的顺序和三元组的顺序并不重要。

思路
直接三重循环来找肯定会超时,遇到这种涉及数组的题目的时候,如果没有排序的一定要先排序,这样很大程度上可以将时间复杂度控制在 O ( n 2 ) O(n^2) O(n2) O ( n l o g n ) O(nlogn) O(nlogn) 左右。
像这道题,先排序,然后遍历一遍数组,在每次遍历时再设置双指针,所以每次和大了右指针就往左走、和小了左指针就往右走,而且因为本身就是有序的,因此查重起来也方便很多。

代码

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        // 排序
        sort(nums.begin(), nums.end());
        
        // 初始化
        vector<vector<int>> result;
        
        // 特殊情况
        if (nums[0] > 0 || nums[nums.size() - 1] < 0) {
            return result;
        }
        
        // 遍历
        for (int i = 0; i < nums.size(); ++i) {
            // 最小的数大于0了说明不可能有三数和为0
            if (nums[i] > 0) {
                break;
            }
            
            // 双指针模式可以充分利用数组有序这一条件
            int l = i + 1, r = nums.size() - 1;
            while (l < r) {
                if (nums[i] + nums[l] + nums[r] > 0) {
                    --r;
                }
                else if (nums[i] + nums[l] + nums[r] < 0) {
                    ++l;
                }
                else {
                    // 查重
                    bool dumplicated = false;
                    for (int j = result.size() - 1; j >= 0; --j) {
                        if (result[j][0] != nums[i]) {
                            break;
                        }
                        else if (result[j][1] == nums[l] && result[j][2] == nums[r]) {
                            dumplicated = true;
                            break;
                        }
                    }
                    if (!dumplicated) {
                        result.push_back(vector<int>({nums[i], nums[l], nums[r]}));
                    }
                    
                    ++l;
                    --r;
                }
            }
        }
        
        return result;
    }
};

最接近的三数之和

描述
给你一个长度为 n n n 的整数数组 n u m s nums nums 和 一个目标值 t a r g e t target target。请你从 n u m s nums nums 中选出三个整数,使它们的和与 t a r g e t target target 最接近。
返回这三个数的和。

注意:假定每组输入只存在恰好一个解

示例
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2)

思路
这道题和上一道题“三数之和”基本上是一样的,先排序,然后遍历一遍数组,在每次遍历时再设置双指针,所以每次和大了右指针就往左走、和小了左指针就往右走。

代码

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        // 排序
        sort(nums.begin(), nums.end());
        
        // 初始化
        int result = nums[0] + nums[1] + nums[2];
        
        // 特殊情况
        if (nums.size() == 3) {
            return result;
        }
        
        // 遍历
        for (int i = 0; i < nums.size(); ++i) {
        
            // 双指针模式可以充分利用数组有序这一条件
            int l = i + 1, r = nums.size() - 1;
            while (l < r) {
                int tmp = nums[i] + nums[l] + nums[r];
                if (abs(tmp - target) < abs(result - target)) {
                    result = tmp;
                }
                if (tmp > target) {
                    --r;
                }
                else if (tmp < target) {
                    ++l;
                }
                else {
                    return target;
                }
            }
        }
        
        return result;
    }
};

电话号码的字母组合

描述
给定一个仅包含数字 2-9的字符串,返回所有它能表示的字母组合。答案可以按任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

电话按键
示例
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

思路
没什么难度的题目,就是把映射输入花费了点时间,为了代码简洁,采用了递归的写法。

代码

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        if (digits.size() == 0) {
            return vector<string>();
        }
        
        vector<string> num2char;
        if (digits[0] == '2') {
            num2char = {"a", "b", "c"};
        }
        else if (digits[0] == '3') {
            num2char = {"d", "e", "f"};
        }
        else if (digits[0] == '4') {
            num2char = {"g", "h", "i"};
        }
        else if (digits[0] == '5') {
            num2char = {"j", "k", "l"};
        }
        else if (digits[0] == '6') {
            num2char = {"m", "n", "o"};
        }
        else if (digits[0] == '7') {
            num2char = {"p", "q", "r", "s"};
        }
        else if (digits[0] == '8') {
            num2char = {"t", "u", "v"};
        }
        else if (digits[0] == '9') {
            num2char = {"w", "x", "y", "z"};
        }
        
        if (digits.size() == 1) {
            return num2char;
        }
        else {
            vector<string> tmp = letterCombinations(digits.substr(1, digits.size() - 1));
            vector<string> result;
            for (int i = 0; i < num2char.size(); ++i) {
                for (int j = 0; j < tmp.size(); ++j) {
                    result.push_back(num2char[i] + tmp[j]);
                }
            }
            return result;
        }
    }
};

四数之和

描述
给你一个由 n n n 个整数组成的数组 nums,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]](若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

示例
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

思路
两数之和 -> 三数之和 -> 四数之和…
思路和三数之和差不多,只不过多套了一层循环,相当于把四数之和降为三数之和处理。

不过这道题有一个大坑!!!

nums中数据的范围是 − 1 0 9 < = -10^9 <= 109<= nums[i] < = 1 0 9 <= 10^9 <=109,所以在算四数之和的时候是有可能超出int的范围的需要转换为double来处理!

代码

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        // 排序
        sort(nums.begin(), nums.end());
        
        // 初始化
        vector<vector<int>> result;
        
        // 特殊情况
        if (nums.size() < 4) {
            return result;
        }
        
        // 遍历
        for (int j = 0; j < nums.size(); ++j) {
            for (int i = j+ 1; i < nums.size(); ++i) {
                
                // 双指针模式可以充分利用数组有序这一条件
                int l = i + 1, r = nums.size() - 1;
                while (l < r) {
                    double tmp = nums[j] + nums[i];
                    tmp += nums[l] + nums[r];
                    if (tmp > target) {
                        --r;
                    }
                    else if (tmp < target) {
                        ++l;
                    }
                    else {
                        // 查重
                        bool dumplicated = false;
                        for (int k = result.size() - 1; k >= 0; --k) {
                            if (result[k][0] != nums[j]) {
                                break;
                            }
                            else if (result[k][1] == nums[i] && result[k][2] == nums[l] && result[k][3] == nums[r]) {
                                dumplicated = true;
                                break;
                            }
                        }
                        if (!dumplicated) {
                            result.push_back(vector<int>({nums[j], nums[i], nums[l], nums[r]}));
                        }
                        
                        ++l;
                        --r;
                    }
                }
            }
        }
        
        return result;
    }
};

删除链表的倒数第 N 个结点

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

示例
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

思路
单向链表的缺点就在于不能倒序遍历而且无法按下标获取结点,所以最简单的做法就是把链表读取为数组就行了。

虽然上述思路通过了,这边还是贴一下正确思路吧。

维护两个指针指向头部,其中一个指针先走 n n n 步,然后另外那个指针再开始走,先走的指针到达尾部时,慢走的指针就刚好指在倒数第 n n n 个元素。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        // 用容器存储链表
        vector<ListNode*> list;
        ListNode* now = head;
        while (now != nullptr) {
            list.push_back(now);
            now = now->next;
        }

        // 特殊情况
        if (list.size() == 1) {
            return nullptr;
        }

        // 找到倒数第n个节点的下标
        int index = list.size() - n;

        // 删除(要分3种情况)
        if (index == 0) {
            return list[1];
        }
        else if (index == list.size() - 1) {
            list[index - 1]->next = nullptr;
        }
        else {
            list[index - 1]->next = list[index + 1];
        }
        return list[0];
    }
};

有效的括号

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

有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号

示例
输入:s = "()[]{}"
输出:true

思路
明显需要借助栈来解决:遇到左括号直接入栈;遇到右括号,检查栈是否为空或者栈顶元素是否位对应的左括号,不是的话直接返回false,是的话栈顶元素出栈。

代码

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

合并两个有序链表

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

示例
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

思路
如果是数组的话直接归并就行了,但是既然是链表的题目,那么考察的肯定是如果改变指针而不需要借助额外的空间。
因此,思路可以是固定第1个链表,把第2个链表内的元素插入到第1个链表的合适的位置。
这边要注意插入有3种情况:(1)插在最前面;(2)插在中间;(3)整个插在最后面。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if (list1 == nullptr) {
            return list2;
        }
        if (list2 == nullptr) {
            return list1;
        }

        ListNode* pre1 = nullptr, *result = list1;
        while (list1 != nullptr && list2 != nullptr) {
            if (list1->val >= list2->val) {
                if (pre1 == nullptr) {
                    ListNode* tmp2 = list2->next;
                    list2->next = list1;
                    result = list2;
                    pre1 = list2;
                    list2 = tmp2;
                }
                else {
                    ListNode* tmp2 = list2->next;
                    list2->next = list1;
                    pre1->next = list2;
                    pre1 = list2;
                    list2 = tmp2;
                }
            }
            else {
                pre1 = list1;
                list1 = list1->next;
            }
        }

        if (list2 != nullptr) {
            pre1->next = list2;
        }

        return result;
    }
};

括号生成

描述
数字 n代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

思路
这道题看起来很简单,但其实有点坑的。
一开始我想这是3种情况的递归就能解决这个问题:
(1)"(" + generateParenthesis(n - 1) + ")"
(2)"()" + generateParenthesis(n - 1)
(3)generateParenthesis(n - 1) + "()"
但是提交上去后错了,而且苦思不得其解,最后翻了答案才发现是要用 (a)b, an + bn = n - 1这种形式来作为递归方程的。
我比较懒就没有记录中间状态,如果要进一步节省时间的话可以记录一下小于n的各种状态。

代码

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        // 递归终止条件
        if (n == 0) {
            return vector<string>({});
        }
        if (n == 1) {
            return vector<string>({"()"});
        }
        if (n == 2) {
            return vector<string>({"(())", "()()"});
        }
        if (n == 3) {
            return vector<string>({"((()))","(()())","(())()","()(())","()()()"});
        }
        
        // 以 (a)b 的形式构造当前可能的括号串
        vector<string> result;
        for (int an = 0; an <= n - 1; ++an) {
            int bn = n - an - 1;

            vector<string> a = generateParenthesis(an);
            vector<string> b = generateParenthesis(bn);

            if (a.empty()) {
                for (int bi = 0; bi < b.size(); ++bi) {
                    result.push_back("()" + b[bi]);
                }
            }
            else if (b.empty()) {
                for (int ai = 0; ai < a.size(); ++ai) {
                    result.push_back("(" + a[ai] + ")");
                }
            }
            else {
                for (int ai = 0; ai < a.size(); ++ai) {
                    for (int bi = 0; bi < b.size(); ++bi) {
                        result.push_back("(" + a[ai] + ")" + b[bi]);
                    }
                }
            }
        }
        return result;
    }
};

合并K个升序链表

描述
给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例
输入:lists = [[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() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        // 特殊情况
        if (lists.empty()) {
            return nullptr;
        }
        if (lists.size() == 1) {
            return lists[0];
        }

        // 合并成升序数组
        vector<int> arrayResult;
        while (true) {
            int minV = 100000, minI = -1;
            for (int i = 0; i < lists.size(); ++i) {
                if (lists[i] != nullptr && lists[i] -> val < minV) {
                    minV = lists[i]->val;
                    minI = i;
                }
            }
            if (minI != -1) {
                arrayResult.push_back(minV);
                lists[minI] = lists[minI]->next;
            }
            else {
                break;
            }
        }

        // 转换为升序链表
        if (arrayResult.empty()) {
            return nullptr;
        }
        else {
            ListNode* result = new ListNode(arrayResult[0]);
            ListNode* current = result;
            for (int i = 1; i < arrayResult.size(); ++i) {
                current->next = new ListNode(arrayResult[i]);
                current = current->next;
            }
            return result;
        }

    }
};

两两交换链表中的节点

描述
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例
输入:head = [1,2,3,4,5]
输出:[2,1,4,3,5]

思路
既然要交换当前节点和上个节点,那么就要存储上上个节点、上个节点、当前节点和下个节点这4个中间变量。

代码

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

        // 交换第1组(因为涉及到头节点)
        ListNode* result = head->next;
        ListNode* pre = head;
        ListNode* current = head->next;
        ListNode* next = head->next->next;

        current->next = pre;
        pre->next = next;

        ListNode* prepre = current;
        current = next;

        // 交换余下组
        int i = 0;
        while (current != nullptr) {
            if (i) {
                next = current->next;
                prepre->next = current;
                current->next = pre;
                pre->next = next;

                prepre = current;
                current = next;
                
                --i;
            }
            else {
                prepre = pre;
                pre = current;
                current = current->next;

                ++i;
            }
        }

        return result;
    }
};

K 个一组翻转链表

描述
给你链表的头节点 head,每 k个节点一组进行翻转,请你返回修改后的链表。

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

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

示例
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

思路
该题是上一题的拓展,可以把数组分为待翻转区域前、中、后部分,即利用 startend来记录翻转区域的首位置和末位置,用prenext来记录翻转区域的前一个位置和后一个位置。
这样,每次读取到k个元素后就进行一次翻转动作。翻转动作比较好实现,记录当前元素的上下两个元素,把当前元素指向上一个元素,然后当前元素等于下一个元素。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        // 不用翻转的情况
        if (k == 1) {
            return head;
        }

        // 虚假头节点
        ListNode* vHead = new ListNode(0, head);

        // 需要记录待翻转区域的前一个节点,第一个节点、最后一个节点和后一个节点
        ListNode* pre = vHead, * start = head, * end = head->next, * next;

        // 翻转
        int n = 2;
        while (end != nullptr) {
            if (n == k) {
                next = end->next;

                ListNode* currentPre = start, * current = start->next;
                for(int i = 1; i < k; ++i) {
                    ListNode* tmp = current->next;

                    current->next = currentPre;

                    currentPre = current;
                    current = tmp;
                }

                pre->next = end;
                start->next = next;

                pre = start;
                start = pre->next;
                end = start;
                n = 1;
            }
            else {
                end = end->next;
                ++n;
            }
        }

        return vHead->next;
    }
};

删除有序数组中的重复项

描述
给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组 nums 的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

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

示例
输入:nums = [1,2,2,3,4,5]
输出:5, nums = [1,2,3,4,5]

思路
因为我用的一直是C++,所以更多的是考察对vector的理解。
这边要注意使用erase函数删除vector里面的元素时,所有被删除元素之前的元素都会向前移一位,所以相当于迭代器会自动指向被删除的元素之后的元素。

代码

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        vector<int>::iterator it = nums.begin();
        ++it;
        while(it != nums.end()) {
            if (*it == *(it - 1)) {
                nums.erase(it);
            }
            else {
                ++it;
            }
        }

        return nums.size();
    }
};

移除元素

描述
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例
输入:nums = [1,2,2,3,4,5], val = 2
输出:4, nums = [1,3,4,5]

思路
因为我用的一直是C++,所以更多的是考察对vector的理解。
这边要注意使用erase函数删除vector里面的元素时,所有被删除元素之前的元素都会向前移一位,所以相当于迭代器会自动指向被删除的元素之后的元素。

代码

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        vector<int>::iterator it = nums.begin();
        while(it != nums.end()) {
            if (*it == val) {
                nums.erase(it);
            }
            else {
                ++it;
            }
        }
        return nums.size();
    }
};

找出字符串中第一个匹配项的下标

描述
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。

示例
输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad"在下标 06处匹配。第一个匹配项的下标是 0,所以返回 0

思路
模式串匹配那肯定是经典的KMP算法了,这个网上的资料挺多的,大家可以自行去搜索。

代码

class Solution {
public:
    int strStr(string haystack, string needle) {
        // 计算模式串pattern的next数组
        // next[i] 表示: needle[0] ~ needle[i] 这一个子串,使得前k个字符恰等于后k个字符的最大的k
        // 特别地, k 不能取 i + 1(因为这个子串一共才 i + 1 个字符, 自己肯定与自己相等,就没有意义了)
        int* next = new int[needle.size()];
        next[0] = 0;    // next[0]必然是0, 因为只有 1 个字符
        int next_i = 1, next_j = 0;
        while(next_i < needle.size()) {
            if(needle[next_i] == needle[next_j]) {
                next[next_i] = next_j + 1;
                ++next_i;
                ++next_j;
            }
            else if(next_j != 0) {
                next_j = next[next_j - 1];
            }
            else {
                next[next_i] = 0;
                ++next_i;
            }
        }
        
        // 开始使用next进行KMP算法
        int ti = 0, pi = 0;
        while(ti < haystack.size()) {
            // 若匹配到相同字符,则两个串都向前走
            if(haystack[ti] == needle[pi]) {
                ++ti;
                ++pi;
            }
            // 如果匹配失败,且模式串下标pi不在首位,则使用next数组决定模式串下标pi的新值
            else if(pi != 0) {
                pi = next[pi - 1];
            }
            // 如果匹配失败,且模式串下标pi在首位,则直接移动主串下标即可
            else {
                ++ti;
            }
            
            // 判断是否匹配成功
            if(pi == needle.size()) {
                return ti - needle.size();
            }
        }
        
        return -1;
    }
};

下一个排列

描述
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,1]
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
例如,arr = [1,2,3] 的下一个排列是[1,3,2]
类似地,arr = [2,3,1]的下一个排列是 [3,1,2]
arr = [3,2,1]的下一个排列是 [1,2,3],因为 [3,2,1]不存在一个字典序更大的排列。
给你一个整数数组 nums,找出 nums的下一个排列。
必须 原地 修改,只允许使用额外常数空间。

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

思路
我自己的思路只做出来了214/266,就不贴出来了,大家可以去看看标准解析。
很多人理解不了题目,但其实只要想,用数组里面的这些数字去组成一个新的数字,那么给定的这个nums的下一个排列就是刚好比这个数字大一点的下一个数字。

代码

void nextPermutation(vector<int>& nums) {
    if (nums.size() == 1) {
        // 只有1个元素的话,什么都不用处理
        return;
    
    }
    else if (nums.size() == 2) {
        // 只有2个元素的话,直接交换就行了
        int tmp = nums[0];
        nums[0] = nums[1];
        nums[1] = tmp;
        return;
    }
    else {
        // 找到最靠右边的相邻升序对i和j
        int j = nums.size() - 1;
        int i = j - 1;
        while (i >= 0 && nums[i] >= nums[j]) {
            --j;
            --i;
        }
        
        // 如果没找到
        if (i < 0) {
            // 此时,说明数组是降序排序的,即组成的数字最大了
            // 因此,重新将数组变成升序排序,即组成最小的数字
            sort(nums.begin(), nums.end());
        }
        // 如果找到了
        else
        {
            // 找到最靠右边的比数字i大的数字k
            int k = nums.size() - 1;
            while (nums[k] <= nums[i]) {
                --k;
            }
                // 交换数字i和数字k
                int tmp = nums[i];
                nums[i] = nums[k];
                nums[k] = tmp;
                
                // 将数字j及之后的数字变成升序排序
                sort(nums.begin() + j, nums.end());
        }
    }
}

最长有效括号

描述
给你一个只包含 '('')'的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"

思路:自己做出来的困难题目,大概半小时,有点成就感hhhh

  • step1.1:判断每个括号i是否为合法的括号,是的话用数组k[i]记录其对应的括号的位置。这个相信在学栈的时候老师都已经教过了:遇到左括号就入栈;遇到右括号就出栈,没得出栈就是非法右括号;最后还剩在栈里面的就是非法左括号。
  • step1.2:在进行step1的时候,需要将遇到的每个合法的右括号入到一个另外的栈right里面,用于后续的查找。因为每个合法括号子串的最右边肯定是一个合法右括号!
  • step2.1:开始查找。遍历栈right里面的右括号(即从右往左),判断[k[right.top()], right.top]之间的括号是否合法(有对应的括号且同在这个区间内)。
  • step2.1a:如果都合法,那么这个区间就是有效括号子串,这时候就可以更新最长长度啦。但要注意,该区间内的右括号不需要重复判断(因为不可能比当前长了),而且,如果下一个右括号与当前区间相邻,那么当前子串长度不需要置0,因为可能是连着的;
  • step2.1a:如果有一个不合法,那就跳到下一个有效右括号。

代码

class Solution {
public:
    int longestValidParentheses(string s) {
        // 空字符串返回0
        if (s.empty()) {
            return 0;
        }
        else {
            // 获取所有括号对应的括号位置k(非法括号则是-1)
            vector<int> k(s.size(), -1);
            stack<int> left;
            stack<int> right;   // 记录所有有效右括号的位置(从右到左)
            for(int i = 0; i < s.size(); ++i) {
                // 左括号
                if (s[i] == '(') {
                    // 直接入栈
                    left.push(i);
                }
                // 右括号
                else {
                    // 需要找到最近的左括号,否则就是非法的
                    if (left.empty()) {
                        // 讲道理这步不是必须的,但是写出来容易理解
                        k[i] = -1;
                    }
                    else {
                        k[left.top()] = i;
                        k[i] = left.top();
                        left.pop();
                        
                        right.push(i);
                    }
                }
            }
            
            // 所有还在栈里面的左括号都是非法的
            // 讲道理这步不是必须的,但是写出来容易理解
            while (!left.empty()) {
                k[left.top()] = -1;
                left.pop();
            }
            
            int maxNum = 0, num = 0;
            // 从最右边的有效右括号开始计数
            while (!right.empty()) {
                // 当前潜在有效括号串的左右坐标l和r
                int r = right.top();
                int l = k[r];
                bool isLegal = true;
                for (int i = l + 1; i < r; ++i) {
                    // i必须为合法括号且其对应的括号在范围内
                    if (k[i] == -1 || k[i] < l || k[i] > r) {
                        isLegal = false;
                        break;
                    }
                }
                
                // 如果确实是合法的,则更新最大长度
                if (isLegal) {
                    num += r - l + 1;
                    maxNum = max(maxNum, num);
                    
                    right.pop();

                    // 剪枝:合法串内的右括号无需判断
                    while (!right.empty() && right.top() > l) {
                        right.pop();
                    }
                    
                    // 判断下个右括号是否与l相邻
                    if (!right.empty() && right.top() == l - 1) {
                        // 是的话num不清空
                    }
                    else {
                        // 不是的话num需要清空
                        num = 0;
                    }
                }
                // 如果不是合法的,则不做处理
                else {
                    right.pop();
                }
            }
            
            return maxNum;
        }
    }
};

搜索旋转排序数组

描述
整数数组 nums按升序排列,数组中的值互不相同 。
在传递给函数之前,nums在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标从 0开始 计数)。例如, [0,1,2,4,5,6,7]在下标 3处经旋转后可能变为 [4,5,6,7,0,1,2]
给你旋转后的数组 nums和一个整数 target,如果 nums中存在这个目标值 target,则返回它的下标,否则返回 -1
你必须设计一个时间复杂度为 O(log n)的算法解决此问题。

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

思路:看到这个时间复杂度,再加上是搜索算法,那么就应该很快想到二分查找。而对于旋转数组的查找,无非就是比普通的二分查找多了两个判断,来确定待查找的数字是可能在左边还是在右边,这时候就要利用旋转数组最左边的数字一定大于最右边数字这个特性了。代码里面注释写得比较详细就不赘述了。

代码

class Solution {
public:
    int binarySearch(vector<int>& nums, int target, int left, int right) {
        // 特殊情况
        if (left > right) {
            return -1;
        }
        if (left == right) {
            return nums[left] == target?left:-1;
        }
        
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            return mid;
        }
        
        // 数组有序了,正常二分查找就行
        if (nums[left] < nums[right]) {
            // 找左边
            if (nums[mid] > target) {
                return binarySearch(nums, target, left, mid - 1);
            }
            // 找右边
            else {
                return binarySearch(nums, target, mid + 1, right);
            }
        }
        // 数组仍然存在翻转,需要多加判断条件
        else {
            // 翻转点在mid左边
            if (nums[mid] < nums[left]) {
                // 如果target小于mid,那么肯定在左边
                if (nums[mid] > target) {
                    return binarySearch(nums, target, left, mid - 1);
                }
                // 如果target大于mid,那么两边都有可能,需要继续判断
                else {
                    // 如果target比left小,那肯定在右边
                    if (nums[left] > target) {
                        return binarySearch(nums, target, mid + 1, right);
                    }
                    // 如果target比left大,那肯定在左边
                    else {
                        return binarySearch(nums, target, left, mid - 1);
                    }
                }
            }
            // 翻转点在mid右边
            else {
                // 如果target大于mid,那么肯定在右边
                if (nums[mid] < target) {
                    return binarySearch(nums, target, mid + 1, right);
                }
                // 如果target小于mid,那么两边都有可能,需要继续判断
                else {
                    // 如果target比left小,那肯定在右边
                    if (nums[left] > target) {
                        return binarySearch(nums, target, mid + 1, right);
                    }
                    // 如果target比left大,那肯定在左边
                    else {
                        return binarySearch(nums, target, left, mid - 1);
                    }
                }
            }
        }
        
        return -1;
    }
    
    int search(vector<int>& nums, int target) {
        return binarySearch(nums, target, 0, nums.size() - 1);
    }
};

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

描述
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]
你必须设计并实现时间复杂度为 O(log n)的算法解决此问题。

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

思路
还是一样,看到这个时间复杂度和查找两个字,立马就要想到二分查找。与普通二分查找不一样的是,我们要设计一个找到第一个位置的算法和一个找到最后一个位置的算法。
查找算法的主体结构和普通的二分查找类似,唯一不一样的地方在,我们要找第一个位置的时候,就算mid已经等于target,我们还需要继续向左查找,以保证找到的是第一个位置;而查找最后一个位置则相反。
因此,最终分别进行两次二分查找即可得到结果,并满足时间复杂度要求。

代码

class Solution {
public:
    int leftBinarySearch(vector<int>& nums, int target, int left, int right) {
        // 特殊情况
        if (left > right) {
            return -1;
        }
        if (left == right) {
            return nums[left] == target?left:-1;
        }
        
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            // 继续往左边找
            int leftResult = leftBinarySearch(nums, target, left, mid - 1);
            // 找到了就更新,没找到就返回当前
            if (leftResult == -1) {
                return mid;
            }
            else {
                return leftResult;
            }
        }
        
        // 找左边
        if (nums[mid] > target) {
            return leftBinarySearch(nums, target, left, mid - 1);
        }
        // 找右边
        else {
            return leftBinarySearch(nums, target, mid + 1, right);
        }
    }
    
    int rightBinarySearch(vector<int>& nums, int target, int left, int right) {
        // 特殊情况
        if (left > right) {
            return -1;
        }
        if (left == right) {
            return nums[left] == target?left:-1;
        }
        
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            // 继续往右边找
            int rightResult = rightBinarySearch(nums, target, mid + 1, right);
            // 找到了就更新,没找到就返回当前
            if (rightResult == -1) {
                return mid;
            }
            else {
                return rightResult;
            }
        }
        
        // 找左边
        if (nums[mid] > target) {
            return rightBinarySearch(nums, target, left, mid - 1);
        }
        // 找右边
        else {
            return rightBinarySearch(nums, target, mid + 1, right);
        }
    }
    
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> result;
        result.push_back(leftBinarySearch(nums, target, 0, nums.size() - 1));
        result.push_back(rightBinarySearch(nums, target, 0, nums.size() - 1));
        return result;
    }
};

组合总和

描述
给你一个 无重复元素 的整数数组 candidates和一个目标整数 target,找出 candidates中可以使数字和为目标数 target的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target的不同组合数少于 150个。

示例
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]

思路
也没要求时间复杂度,直接暴力求解就行了。
每个数字都有使用 n 次种情况,对于每种情况,候选数组中去掉该数字、目标值减去 n 个该数字后递归求解。

代码

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> results;
        if (candidates.empty() || target <= 0) {
            return results;
        }
        
        for (int ci = 0; ci < candidates.size(); ++ci) {
            // 每个数字多次使用
            int n = 1;
            while (candidates[ci] * n < target) {
                vector<int> newCandidates;
                newCandidates.assign(candidates.begin() + ci + 1, candidates.end());
                int newTarget = target - candidates[ci] * n;
                vector<vector<int>> tempResults = combinationSum(newCandidates, newTarget);
                
                // 组合result
                if (!tempResults.empty()) {
                    for (int ri = 0; ri < tempResults.size(); ++ri) {
                        vector<int> tempResult(n, candidates[ci]);
                        tempResult.insert(tempResult.end(), tempResults[ri].begin(), tempResults[ri].end());
                        results.push_back(tempResult);
                    }
                }
                
                ++n;
            }
            
            // 同个数字加起来是否能够等于target
            if (candidates[ci] * n == target) {
                vector<int> tempResult(n, candidates[ci]);
                results.push_back(tempResult);
            }
        }
        
        return results;
    }
};

接雨水

描述
给定 n个非负整数表示每个宽度为 1的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1]表示的高度图,在这种情况下,可以接 6个单位的雨水。

思路
题目不复杂,思路大概就是先计算哪些区域可以接水(一个降序列和一个升序列组成的凹形空间),之后再判断哪些区域可以连通起来(目前区域的最后一个柱子低于第一个柱子,且下一个区域的最后一个柱子高于目前区域的最后一个柱子)。
但说起来容易,代码上有挺多坑的。

代码

class Solution {
public:
    int trap(vector<int>& height) {
        int result = 0;
        
        // 特殊情况
        if (height.size() == 1) {
            return result;
        }
        
        // 能接水的区域一定存在一个降序序列和一个升序序列
        // 因此down表示降序序列开始的位置,up表示升序序列结束的位置
        int down = -1;
        int up = -1;
        
        // 开始判断哪些区间可以装水
        vector<int> space_bengins;
        vector<int> space_ends;
        for (int i = 0; i < height.size() - 1; ++i) {
            // 当前柱子和下一根柱子是升序
            if (height[i] < height[i + 1]) {
                // 如果前面没有降序序列,那么就不用处理升序的情况
                // 如果前面有降序序列,那么就记录本次升序
                if (down != -1) {
                    up = i + 1;
                }
            }
            // 当前柱子和下一根柱子是降序
            else if (height[i] > height[i + 1]) {
                // 如果前面既有降序序列又有升序序列,那么该区域可用于接水
                if (down != -1 && up != -1) {
                    space_bengins.push_back(down);
                    space_ends.push_back(up);
                    // 重置
                    down = i;
                    up = -1;
                }
                // 否则判断是否有存在的降序序列,没有的话就记录
                if (down == -1) {
                    down = i;
                }
                
            }
            // 相等就不用处理了
        }
        
        // 判断最后一个区域是否可用于接水
        if (down != -1 && up != -1) {
            space_bengins.push_back(down);
            space_ends.push_back(up);
        }
        
        // 开始计算容积
        for (int si = 0; si < space_bengins.size(); ++si) {
            int begin = space_bengins[si], end = space_ends[si];
            
            // 如果当前接水区域的结束柱子低于起始柱子,则需要判断当前接水区域与下一个区域是否可以连通
            for (int sj = si + 1; height[begin] > height[end] &&  sj < space_bengins.size(); ++sj) {
                // 如果区域j的结束柱子高于等于上一个区域的结束柱子,那么可以连通
                if (height[space_ends[sj]] >= height[end]) {
                    end = space_ends[sj];
                    si = sj;
                }
                // 如果区域j的初始柱子高于上个区域的初始柱子,那么停止连通
                else if (height[space_bengins[sj]] > height[begin]) {
                    break;
                }
            }
            
            // 开始计算
            int minH = min(height[begin], height[end]);
            for (int i = begin + 1; i < end; ++i) {
                result += max(0, minH - height[i]);
            }
        }
        
        return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1. Two Sum 2. Add Two Numbers 3. Longest Substring Without Repeating Characters 4. Median of Two Sorted Arrays 5. Longest Palindromic Substring 6. ZigZag Conversion 7. Reverse Integer 8. String to Integer (atoi) 9. Palindrome Number 10. Regular Expression Matching 11. Container With Most Water 12. Integer to Roman 13. Roman to Integer 14. Longest Common Prefix 15. 3Sum 16. 3Sum Closest 17. Letter Combinations of a Phone Number 18. 4Sum 19. Remove Nth Node From End of List 20. Valid Parentheses 21. Merge Two Sorted Lists 22. Generate Parentheses 23. Swap Nodes in Pairs 24. Reverse Nodes in k-Group 25. Remove Duplicates from Sorted Array 26. Remove Element 27. Implement strStr() 28. Divide Two Integers 29. Substring with Concatenation of All Words 30. Next Permutation 31. Longest Valid Parentheses 32. Search in Rotated Sorted Array 33. Search for a Range 34. Find First and Last Position of Element in Sorted Array 35. Valid Sudoku 36. Sudoku Solver 37. Count and Say 38. Combination Sum 39. Combination Sum II 40. First Missing Positive 41. Trapping Rain Water 42. Jump Game 43. Merge Intervals 44. Insert Interval 45. Unique Paths 46. Minimum Path Sum 47. Climbing Stairs 48. Permutations 49. Permutations II 50. Rotate Image 51. Group Anagrams 52. Pow(x, n) 53. Maximum Subarray 54. Spiral Matrix 55. Jump Game II 56. Merge k Sorted Lists 57. Insertion Sort List 58. Sort List 59. Largest Rectangle in Histogram 60. Valid Number 61. Word Search 62. Minimum Window Substring 63. Unique Binary Search Trees 64. Unique Binary Search Trees II 65. Interleaving String 66. Maximum Product Subarray 67. Binary Tree Inorder Traversal 68. Binary Tree Preorder Traversal 69. Binary Tree Postorder Traversal 70. Flatten Binary Tree to Linked List 71. Construct Binary Tree from Preorder and Inorder Traversal 72. Construct Binary Tree from Inorder and Postorder Traversal 73. Binary Tree Level Order Traversal 74. Binary Tree Zigzag Level Order Traversal 75. Convert Sorted Array to Binary Search Tree 76. Convert Sorted List to Binary Search Tree 77. Recover Binary Search Tree 78. Sum Root to Leaf Numbers 79. Path Sum 80. Path Sum II 81. Binary Tree Maximum Path Sum 82. Populating Next Right Pointers in Each Node 83. Populating Next Right Pointers in Each Node II 84. Reverse Linked List 85. Reverse Linked List II 86. Partition List 87. Rotate List 88. Remove Duplicates from Sorted List 89. Remove Duplicates from Sorted List II 90. Intersection of Two Linked Lists 91. Linked List Cycle 92. Linked List Cycle II 93. Reorder List 94. Binary Tree Upside Down 95. Binary Tree Right Side View 96. Palindrome Linked List 97. Convert Binary Search Tree to Sorted Doubly Linked List 98. Lowest Common Ancestor of a Binary Tree 99. Lowest Common Ancestor of a Binary Search Tree 100. Binary Tree Level Order Traversal II

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值