力扣HOT100题目及代码

力扣100

Posted by WenlSun" on July 14, 2020

两数之和(简单)

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。两数之和

参考代码

1
2
3
4
5
6
7
8
9
vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int, int> hash;
    for (int i = 0; i < nums.size(); i++) hash[nums[i]] = i;
    for (int i = 0; i < nums.size(); i++) {
        if (hash.count(target - nums[i]) && hash[target - nums[i]] != i)
            return {i, hash[target - nums[i]]};
    }
    return {-1, -1};
}

两数相加(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    auto dummy = new ListNode(-1);
    auto p = dummy;
    int carry = 0;
    while (l1 || l2 || carry) {
        int n1 = l1 ? l1->val : 0;
        int n2 = l2 ? l2->val : 0;
        int sum = (n1 + n2 + carry) % 10;
        carry = (n1 + n2 + carry) / 10;
        if (l1) l1 = l1->next;
        if (l2) l2 = l2->next;
        p->next = new ListNode(sum);
        p = p->next;
    }
    return dummy->next;
}

无重复字符的最长子串(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
int lengthOfLongestSubstring(string s) {
    unordered_map<char, int> hash;
    int res = 0;
    for (int i = 0, j = 0; j < s.size(); j++) {
        hash[s[j]]++;
        while (hash[s[j]] > 1) hash[s[i++]]--;
        res = max(res, j - i + 1);
    }
    return res;
}

寻找两个正序数组的中位数(困难)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    vector<int> res;
    int p1 = 0, p2 = 0;
    while (p1 < nums1.size() && p2 < nums2.size()) {
        if (nums1[p1] < nums2[p2])
            res.push_back(nums1[p1++]);
        else
            res.push_back(nums2[p2++]);
    }
    while (p1 < nums1.size()) res.push_back(nums1[p1++]);
    while (p2 < nums2.size()) res.push_back(nums2[p2++]);
    if (res.size() & 1)
        return res[res.size() / 2];
    else
        return (double)(res[res.size() / 2] + res[res.size() / 2 - 1]) / 2;
}

最长回文子串(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string longestPalindrome(string s) {
    string res;
    for (int i = 0; i < s.size(); i++) {
        string s1 = helper(s, i, i);
        string s2 = helper(s, i, i + 1);
        res = res.size() > s1.size() ? res : s1;
        res = res.size() > s2.size() ? res : s2;
    }
    return res;
}

string helper(string s, int l, int r) {
    while (l >= 0 && r < s.size() && s[l] == s[r]) l--, r++;
    return s.substr(l + 1, r - l - 1);
}

正则表达式匹配(困难)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool isMatch(string s, string p) {
    int m = s.size(), n = p.size();
    vector<vector<bool>> dp(m + 1, vector<bool>(n + 1));
    dp[0][0] = true;
    for (int i = 2; i <= n; i++)
        if (p[i - 1] == '*') dp[0][i] = dp[0][i - 2];
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (s[i - 1] == p[j - 1] || p[j - 1] == '.')
                dp[i][j] = dp[i - 1][j - 1];
            if (p[j - 1] == '*') {
                if (p[j - 2] != s[i - 1] && p[j - 2] != '.')
                    dp[i][j] = dp[i][j - 2];
                else
                    dp[i][j] = dp[i - 1][j] || dp[i][j - 1] || dp[i][j - 2];
            }
        }
    }
    return dp[m][n];
}

盛最多水的容器(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
int maxArea(vector<int>& height) {
    int l = 0, r = height.size() - 1;
    int res = 0;
    while (l < r) {
        res = max(res, min(height[l], height[r]) * (r - l));
        if (height[l] < height[r])
            l++;
        else
            r--;
    }
    return res;
}

三数之和(中等)

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。三数之和

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> res;
    if (nums.size() < 3) return res;
    sort(nums.begin(), nums.end());  // 为了去重
    int n = nums.size();
    for (int i = 0; i < n - 2; i++) {
        if (i == 0 || nums[i] != nums[i - 1]) { // 第一次去重
            int l = i + 1, r = n - 1;
            while (l < r) {
                if (nums[l] + nums[r] > -nums[i])
                    r--;
                else if (nums[l] + nums[r] < -nums[i])
                    l++;
                else {
                    if (l == i + 1 || nums[l] != nums[l - 1]) { // 第二次去重
                        res.push_back({nums[l], nums[r], nums[i]});
                    }
                    l++, r--;
                }
            }
        }
    }
    return res;
}

电话号码的字母组合(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<string> res;
// 设置键盘的映射
vector<string> letterCombinations(string digits) {
    if (digits.empty()) return res;
    string path;
    dfs(digits, 0, path);
    return res;
}

void dfs(string& s, int idx, string& path) {
    if (idx == s.size()) {
        res.push_back(path);
        return;
    }

    string t = m[s[idx]];
    for (auto c : t) {
        path.push_back(c);
        dfs(s, idx + 1, path);
        path.pop_back();
    }
}

删除链表的倒数第N个节点(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
ListNode* removeNthFromEnd(ListNode* head, int n) {
    if (!head) return nullptr;
    auto dummy = new ListNode(-1);
    dummy->next = head;
    auto pre = dummy, cur = head;
    for (int i = 0; i < n; i++) cur = cur->next;
    while (cur) {
        pre = pre->next;
        cur = cur->next;
    }
    pre->next = pre->next->next;
    return dummy->next;
}

有效的括号(简单)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool isValid(string s) {
    if (s.empty()) return true;
    unordered_map<char, char> m; // 初始化括号对,因为解析的问题没有具体初始化
    stack<char> stk;
    for (auto c : s) {
        if (stk.empty())
            stk.push(c);
        else if (c == m[stk.top()])
            stk.pop();
        else
            stk.push(c);
    }
    return stk.empty();
}

合并两个有序链表(简单)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    auto dummy = new ListNode(-1);
    auto p = dummy;
    while (l1 && l2) {
        if (l1->val < l2->val) {
            p->next = l1;
            l1 = l1->next;
            p = p->next;
        } else {
            p->next = l2;
            l2 = l2->next;
            p = p->next;
        }
    }
    while (l1) {
        p->next = l1;
        l1 = l1->next;
        p = p->next;
    }
    while (l2) {
        p->next = l2;
        l2 = l2->next;
        p = p->next;
    }
    return dummy->next;
}

括号生成(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vector<string> res;
// 回溯 做减法
vector<string> generateParenthesis(int n) {
    string path;
    dfs(n, n, path);
    return res;
}

void dfs(int l, int r, string& path) {
    if (l == 0 && r == 0) {
        res.push_back(path);
        return;
    }
    if (l > 0) {
        path.push_back('(');
        dfs(l - 1, r, path);
        path.pop_back();
    }
    if (l < r && r > 0) {
        path.push_back(')');
        dfs(l, r - 1, path);
        path.pop_back();
    }
}

合并k个排序链表(困难)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
ListNode* mergeKLists(vector<ListNode*>& lists) {
    return merge(lists, 0, lists.size() - 1);
}

// 分治 两路两路归并
ListNode* merge(vector<ListNode*>& lists, int l, int r) {
    if (l == r) return lists[l];
    if (l < r) {
        int mid = (l + r) >> 1;
        return mergeLists(merge(lists, l, mid), merge(lists, mid + 1, r));
    }
    return nullptr;
}

ListNode* mergeLists(ListNode* l1, ListNode* l2) {
    auto dummy = new ListNode(-1);
    auto p = dummy;
    while (l1 && l2) {
        if (l1->val < l2->val) {
            p->next = l1;
            l1 = l1->next;
            p = p->next;
        } else {
            p->next = l2;
            l2 = l2->next;
            p = p->next;
        }
    }
    while (l1) {
        p->next = l1;
        l1 = l1->next;
        p = p->next;
    }
    while (l2) {
        p->next = l2;
        l2 = l2->next;
        p = p->next;
    }
    return dummy->next;
}

下一个排列(中等)

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。下一个排列

参考代码

1

最长有效括号(困难)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int longestValidParentheses(string s) {
    int l = 0, r = 0, maxLen = 0;
    for (int i = 0; i < s.size(); i++) { // 从左向右统计
        if (s[i] == '(')
            l++;
        else
            r++;
        if (l == r) maxLen = max(maxLen, l + r);
        if (r > l) l = 0, r = 0;
    }
    l = 0, r = 0;
    for (int i = s.size() - 1; i >= 0; i--) { // 从右向左统计
        if (s[i] == ')')
            r++;
        else
            l++;
        if (l == r) maxLen = max(maxLen, l + r);
        if (l > r) l = 0, r = 0;
    }
    return maxLen;
}

搜索旋转排序数组(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int search(vector<int>& nums, int target) {
    if (!nums.size()) return -1;
    int l = 0, r = nums.size() - 1;
    while (l < r) {
        int mid = l + r >> 1;
        if (nums[mid] <= nums.back())
            r = mid;
        else
            l = mid + 1;
    }
    if (target <= nums.back()) {
        r = nums.size() - 1;
    } else {
        l = 0, r--;
    }
    while (l < r) {
        int mid = l + r >> 1;
        if (nums[mid] >= target)
            r = mid;
        else
            l = mid + 1;
    }
    return nums[l] == target ? l : -1;
}

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

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<int> searchRange(vector<int>& nums, int target) {
    if (!nums.size()) return {-1, -1};
    int l = 0, r = nums.size() - 1;
    while (l < r) { // 左边界
        int mid = l + r >> 1;
        if (nums[mid] >= target)
            r = mid;
        else
            l = mid + 1;
    }
    if (nums[l] != target) return {-1, -1};
    int t = l;
    l = 0, r = nums.size() - 1;
    while (l < r) { // 右边界
        int mid = l + r + 1 >> 1;
        if (nums[mid] <= target)
            l = mid;
        else
            r = mid - 1;
    }
    return {t, l};
}

组合总和(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vector<vector<int>> res;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
    if (!candidates.size()) return res;
    vector<int> path;
    dfs(candidates, 0, target, path);
    return res;
}

void dfs(vector<int>& candidates, int idx, int target, vector<int>& path) {
    if (target == 0) {
        res.push_back(path);
        return;
    }
    for (int i = idx; i < candidates.size(); i++) {
        if (candidates[i] <= target) {
            path.push_back(candidates[i]);
            dfs(candidates, i, target - candidates[i], path);
            path.pop_back();
        }
    }
}

接雨水(困难)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int trap(vector<int>& height) { // 单调栈
    int res = 0;
    stack<int> stk;
    for (int i = 0; i < height.size(); i++) {
        int last = 0;  // 记录之前的高度
        // 维护一个单调递减栈
        while (stk.size() && height[stk.top()] <= height[i]) {
            int t = stk.top();
            stk.pop();
            // 当前层的雨水
            res += (height[t] - last) * (i - t - 1);
            last = height[t];  // 之前的高度更新为当前高度
        }
        // 右边墙低于左边墙的情况
        if (stk.size()) res += (height[i] - last) * (i - stk.top() - 1);
        stk.push(i);
    }
    return res;
}

// 双指针
int trap(vector<int>& height) {
    if (height.size() < 2) return 0;
    int res = 0, l = 0, r = height.size() - 1, ml = 0, mr = 0;
    while (l < r) {
        if (height[l] < height[r]) { // 当右侧高于左侧,则储水量由左侧的最大值和当前值决定
            if (height[l] >= ml)  // 如果当前值大于左侧的最大值,则没有存储到水,更新左侧最大值
                ml = height[l];
            else // 否则,计算当前墙的储水量
                res += ml - height[l];
            l++;
        } else { // 若果左墙高于右墙,则储水量由右侧最大值和当前墙的高度决定
            if (height[r] >= mr) // 如果当前墙的高度大于右侧最大值,说明当前无储水,更新右侧最大值
                mr = height[r];
            else // 否则利用最大值和当前墙的高度计算当前储水量
                res += mr - height[r];
            r--;
        }
    }
    return res;
}

全排列(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vector<vector<int>> res;
vector<bool> st;
vector<vector<int>> permute(vector<int>& nums) {
    vector<int> path;
    st = vector<bool>(nums.size());
    dfs(nums, 0, path);
    return res;
}

void dfs(vector<int>& nums, int idx, vector<int>& path) {
    if (idx == nums.size()) {
        res.push_back(path);
        return;
    }
    for (int i = 0; i < nums.size(); i++) {
        if (!st[i]) {
            path.push_back(nums[i]);
            st[i] = true;
            dfs(nums, idx + 1, path);
            path.pop_back();
            st[i] = false;
        }
    }
}

旋转图像(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void rotate(vector<vector<int>>& matrix) {
    int m = matrix.size(), n = matrix[0].size();
    // 对角翻转
    for (int i = 0; i < m; i++) {
        for (int j = i; j < n; j++) {
            int t = matrix[i][j];
            matrix[i][j] = matrix[j][i];
            matrix[j][i] = t;
        }
    }
    // 水平翻转
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n / 2; j++) {
            int t = matrix[i][j];
            matrix[i][j] = matrix[i][n - j - 1];
            matrix[i][n - j - 1] = t;
        }
    }
}

void rotate(vector<vector<int>>& matrix) {
    if (matrix.size() == 0) {
        return;
    }
    // start 和 end 界定当前圈一行的起始和终止位置
    for (int start = 0, end = matrix.size() - 1; start < end;
            start++, end--) {
        // s,e 用于旋转四个坐标,旋转的次数小于当前圈的长度end
        for (int s = start, e = end; s < end; s++, e--) {
            int temp = matrix[start][s];
            matrix[start][s] = matrix[e][start];
            matrix[e][start] = matrix[end][e];
            matrix[end][e] = matrix[s][end];
            matrix[s][end] = temp;
        }
    }
}

字母异位词分组(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
vector<vector<string>> groupAnagrams(vector<string>& strs) {
    unordered_map<string, vector<string>> hash;
    for (auto s : strs) {
        string key = s;
        sort(key.begin(), key.end());
        hash[key].push_back(s);
    }
    vector<vector<string>> res;
    for (auto item : hash) res.push_back(item.second);
    return res;
}

最大子序列和(简单)

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

参考代码

1
2
3
4
5
6
7
8
9
int maxSubArray(vector<int>& nums) {
    int res = INT_MIN, last = 0;
    for (auto n : nums) {
        int t = max(last, 0) + n;
        res = max(res, t);
        last = t;
    }
    return res;
}

跳跃游戏(中等)

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

参考代码

1
2
3
4
5
6
7
8
bool canJump(vector<int>& nums) {
    int maxPos = 0;
    for (int i = 0; i < nums.size(); i++) {
        if (i > maxPos) return false;
        maxPos = max(maxPos, nums[i] + i);
    }
    return true;
}

合并区间(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static bool cmp(vector<int> a, vector<int> b) { return a[0] < b[0]; }

vector<vector<int>> merge(vector<vector<int>>& intervals) {
    vector<vector<int>> res;
    if (!intervals.size()) return res;
    sort(intervals.begin(), intervals.end(), cmp); // 先排序
    res.push_back(intervals[0]);
    int idx = 0;
    for (int i = 1; i < intervals.size(); i++) {
        if (res[idx][1] >= intervals[i][0])
            res[idx][1] = max(res[idx][1], intervals[i][1]);
        else {
            res.push_back(intervals[i]);
            idx++;
        }
    }
    return res;
}

不同路径(中等)

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?不同路径

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
int uniquePaths(int m, int n) {
    // 动态规划:状态表示:dp[i][j]表示到达i,j这个位置的所有路径
    vector<vector<int>> dp(m, vector<int>(n));
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (!i || !j)
                dp[i][j] = 1; // base case
            else
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; // 状态计算
        }
    }
    return dp[m - 1][n - 1];
}

最小路径和(中等)

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。最小路径和

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
int minPathSum(vector<vector<int>>& grid) {
    int m = grid.size(), n = grid[0].size();
    vector<vector<int>> dp(m, vector<int>(n)); // 表示到达位置i,j的最小路径和
    dp[0][0] = grid[0][0]; // base case
    for (int i = 1; i < m; i++) dp[i][0] = dp[i - 1][0] + grid[i][0];
    for (int j = 1; j < n; j++) dp[0][j] = dp[0][j - 1] + grid[0][j];
    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; // 状态转移
        }
    }
    return dp[m - 1][n - 1];
}

爬楼梯(简单)

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 爬楼梯

参考代码

1
2
3
4
5
6
7
8
9
10
11
int climbStairs(int n) {
    if (n == 1) return 1;
    if (n == 2) return 2;
    int a = 1, b = 2, res;
    for (int i = 3; i <= n; i++) {
        res = a + b;
        a = b;
        b = res;
    }
    return res;
}

编辑距离(困难)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
int minDistance(string w1, string w2) {
    int m = w1.size(), n = w2.size();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1));
    for (int i = 0; i <= m; i++) dp[i][0] = i;
    for (int j = 0; j <= n; j++) dp[0][j] = j;
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1; // 插入、删除
            dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + (w1[i - 1] != w2[j - 1])); // 替换
        }
    }
    return dp[m][n];
}

颜色分类(中等)

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。颜色分类

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
void sortColors(vector<int>& nums) {
    int l = 0, r = nums.size() - 1, cur = 0;
    while (cur <= r) {
        if (nums[cur] == 0) {
            swap(nums[cur], nums[l]);
            l++, cur++;
        } else if (nums[cur] == 2) {
            swap(nums[cur], nums[r]);
            r--;
        } else
            cur++;
    }
}

最小覆盖子串(困难)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
string minWindow(string s, string t) {
    unordered_map<char, int> need, window; // 分别记录需要的字符及次数,窗口的字符及次数
    for (auto c : t) need[c]++; // 统计需要的字符
    int l = 0, r = 0, idx = 0; // 左指针,右指针,记录最左侧的索引
    int matched = 0, minLen = INT_MAX; // 匹配的字符数目,最小长度
    while (r < s.size()) {
        auto c = s[r];
        if (need.count(c)) { // 如果需要的字符串中包含c
            window[c]++;
            if (window[c] == need[c]) matched++; // 如果数量相同,说明匹配了一个字符
        }
        r++;
        while(matched== need.size()) { // 如果已经匹配,则缩小窗口
            if (r - l < minLen) { // 更新长度
                minLen = r - l;
                idx = l;
            }
            auto c = s[l];
            if(need.count(c)) { // 缩小窗口
                window[c]--;
                if (window[c] < need[c]) matched--;
            }
            l++;

        }
    }
    return minLen == INT_MAX ? "" : s.substr(idx, minLen);
}

子集(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vector<vector<int>> res;
vector<vector<int>> subsets(vector<int>& nums) {
    vector<int> path;
    dfs(nums, 0, path);
    return res;
}

void dfs(vector<int>& nums, int idx, vector<int>& path) {
    res.push_back(path);
    for (int i = idx; i < nums.size(); i++) {
        path.push_back(nums[i]);
        dfs(nums, i + 1, path);
        path.pop_back();
    }
}

单词搜索(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int m, n;
int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};
bool exist(vector<vector<char>>& board, string word) {
    if (!board.size() || !board[0].size()) return false;
    m = board.size(), n = board[0].size();
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (board[i][j] == word[0] && dfs(board, i, j, 0, word))
                return true;
        }
    }
    return false;
}

bool dfs(vector<vector<char>>& board, int x, int y, int idx, string& word) {
    if (board[x][y] != word[idx]) return false;
    if (idx == word.size() - 1) return true;
    board[x][y] = '.';
    for (int i = 0; i < 4; i++) {
        int a = x + dx[i], b = y + dy[i];
        if (a >= 0 && a < m && b >= 0 && b < n)
            if (dfs(board, a, b, idx + 1, word)) return true;
    }
    board[x][y] = word[idx];
    return false;
}

柱状图中最大的矩形(困难)

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。柱状图中最大的矩形

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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.size() && heights[stk.top()] >= heights[i]) stk.pop();
        if (stk.empty())
            left[i] = -1;
        else
            left[i] = stk.top();
        stk.push(i);
    }
    while (stk.size()) stk.pop();
    for (int i = n - 1; i >= 0; i--) { // 找右边最近的小于当前柱子的位置
        while (stk.size() && heights[stk.top()] >= heights[i]) stk.pop();
        if (stk.empty())
            right[i] = n;
        else
            right[i] = stk.top();
        stk.push(i);
    }
    int res = 0;
    for (int i = 0; i < n; i++) { // 寻找最大值
        res = max(res, heights[i] * (right[i] - left[i] - 1));
    }
    return res;
}

最大矩形(困难)

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

思路:使用数据栈,借助柱状图中面积最大的矩形,首先沿着某一维度累加1的个数(遇到0时重置),可得到沿着另一维度的柱状图,每个位置的数字表示到当前位置有多少个1,然后沿着另一个维度 按照柱状图中最大矩形求的最大矩形。时间复杂度:O(MN),空间复杂度:O(M)或O(N)。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int maximalRectangle(vector<vector<char>>& matrix) {
    int m = matrix.size(), n = matrix[0].size();
    vector<int> dp(n);
    int res = 0;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            dp[j] = matrix[i][j] == '1' ? dp[j] + 1 : 0;
        }
        res = max(res, largestRectangleArea(dp));
    }
    return res;
}

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.size() && heights[stk.top()] >= heights[i]) stk.pop();
        if (stk.empty())
            left[i] = -1;
        else
            left[i] = stk.top();
        stk.push(i);
    }
    while (stk.size()) stk.pop();
    for (int i = n - 1; i >= 0; i--) {
        while (stk.size() && heights[stk.top()] >= heights[i]) stk.pop();
        if (stk.empty())
            right[i] = n;
        else
            right[i] = stk.top();
        stk.push(i);
    }
    int res = 0;
    for (int i = 0; i < n; i++) {
        res = max(res, heights[i] * (right[i] - left[i] - 1));
    }
    return res;
}

二叉树的中序遍历(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

class Solution {
   public:
    vector<int> res;
    // 递归版本
    vector<int> inorderTraversal(TreeNode* root) {
        dfs(root);
        return res;
    }

    void dfs(TreeNode* root) {
        if (!root) return;
        dfs(root->left);
        res.push_back(root->val);
        dfs(root->right);
    }

    // 非递归版
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> stk;
        while (root || stk.size()) {
            while (root) {
                stk.push(root);
                root = root->left;
            }
            root = stk.top();
            stk.pop();
            res.push_back(root->val);
            root = root->right;
        }
        return res;
    }
};

不同的二叉搜索树(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
   public:
    /**
     * 思路:假设n个节点存在二叉搜索树的个数是G(n),令f(i)表示以i为根节点的二叉搜索树,则:
     * G(n) = f(1) + f(2) + ... + f(n)
     * 当以i为根节点时:f(i) = G(i-1) * G(n-i),左子树有i-1个结点,右子树有n-i个结点
     * 综上有:G(n) = G(0)*G*(n-1) + G(1)*G(n-2) + ... + G(n-1)*G(0) 卡特兰数 
     * 通过dp来实现
     */
    int numTrees(int n) {
        vector<int> dp(n + 1);
        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];
    }
};

验证二叉搜索树(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
   public:
    typedef long long LL;
    // 判断当前结点是否在正确的区间
    bool isValidBST(TreeNode* root) { return dfs(root, INT_MIN, INT_MAX); }

    bool dfs(TreeNode* root, LL minV, LL maxV) {
        if (!root) return true;
        if (root->val < minV || root->val > maxV) return false; // 不在正确的区间,返回false
        // 缩小区间 dfs左右子树
        return dfs(root->left, minV, root->val - 1ll) &&
               dfs(root->right, root->val + 1ll, maxV);
    }
};

对称二叉树(简单)

给定一个二叉树,检查它是否是镜像对称的。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
       return dfs(root, root);
    }

    bool dfs(TreeNode* r1, TreeNode* r2) {
        if (!r1 && !r2) return true;
        if (!r1 || !r2) return false;
        if (r1->val != r2->val) return false;
        return dfs(r1->left, r2->right) && dfs(r1->right, r2->left);
    }
};

二叉树的层序遍历(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if (!root) return res;
        queue<TreeNode*> q;
        q.push(root);
        while(q.size()) {
            int len = q.size();
            vector<int> t;
            for (int i = 0; i < len; i++) {
                auto node = q.front();
                q.pop();
                t.push_back(node->val);
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
            res.push_back(t);
        }
        return res;
    }
};

二叉树的最大深度(简单)

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

参考代码

1
2
3
4
5
6
7
8
9
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (!root) return 0;
        int l = maxDepth(root->left);
        int r = maxDepth(root->right);
        return max(l, r) + 1;
    }
};

从前序和中序遍历序列构造二叉树(中等)

根据一棵树的前序遍历与中序遍历构造二叉树。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
    unordered_map<int, int> pos;
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        for (int i = 0; i < n; i++) pos[inorder[i]] = i;
        return dfs(preorder, inorder, 0, n - 1, 0, n - 1);
    }

    TreeNode* dfs(vector<int>& preorder, vector<int>& inorder, int pl, int pr, int il, int ir) {
        if (pl > pr) return nullptr;
        int val = preorder[pl];
        int k = pos[val];
        int len = k - il;
        auto root = new TreeNode(val);
        root->left = dfs(preorder, inorder, pl + 1, pl + len, il, k - 1);
        root->right = dfs(preorder, inorder, pl + len + 1, pr, k + 1, ir);
        return root;
    }
};

二叉树展开为链表(中等)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
    TreeNode* pre = nullptr;
    void flatten(TreeNode* root) {
        if (!root) return;
        flatten(root->right);
        flatten(root->left);
        root->right = pre;
        root->left = nullptr;
        pre = root;
    }
};

买卖股票的最佳时机(简单)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int dp_i_0 = 0, dp_i_1 = INT_MIN;
        for (auto p : prices) {
            dp_i_0 = max(dp_i_0, dp_i_1 + p);
            dp_i_1 = max(dp_i_1, -p);
        }
        return dp_i_0;
    }
};

二叉树中的最大路径和(困难)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
    int res = INT_MIN;
    int maxPathSum(TreeNode* root) {
        dfs(root);
        return res;
    }
    // 枚举所有的顶点
    int dfs(TreeNode* root) {
        if (!root) return 0;
        int l = dfs(root->left); // 计算左子树最大和
        int r = dfs(root->right); // 计算右子树最大和
        res = max(res, l + r + root->val); //更新答案
        return max(0, max(l, r) + root->val);
    }
};

最长连续序列(困难)

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

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
   public:
    /**
     * 使用哈希表
     * 时间复杂度:O(n)
     */
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> hash;
        for (auto n : nums) hash.insert(n);
        int res = 0;
        for (auto n : hash) { // 注意这儿需要从set中枚举
            if (!hash.count(n - 1)) {  // 从没有比当前数小一的数开始
                int len = 0;
                // 统计以当前数字开始的连续序列长度
                while (hash.count(n)) {
                    len++, n++;
                }
                res = max(res, len);
            }
        }
        return res;
    }

    // 并查集
    unordered_map<int, int> p;
    int longestConsecutive(vector<int>& nums) {
        for (auto n : nums) p[n] = n + 1;
        int res = 0;
        for (auto n : nums) {
            int r = find(n + 1);
            res = max(res, r - n);
        }
        return res;
    }

    int find(int x) {
        return p.count(x) ? p[x] = find(p[x]) : x;
    }
}

只出现一次的数字(简单)

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

参考代码

1
2
3
4
5
6
7
8
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res = 0;
        for (auto n : nums) res ^= n;
        return res;
    }
};

单词拆分(中等)

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。单词拆分

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
   public:
    /**
     * 动态规划
     * 状态表示:dp[i]表示字符串前i个字符能否由单词表中的单词表示
     * 状态计算:dp[i] = true 如果前i个字符中从后面拆出来一个单词表中的单词,
     * 并且剩余的字符串dp[i-len(单词)]可以用单词表中单词表示时。
     * 时间复杂度:O(n^2)
     */
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> dict;
        for (auto d : wordDict) dict.insert(d);
        vector<bool> dp(s.size() + 1);
        dp[0] = true;
        for (int i = 1; i <= s.size(); i++) {
            for (int j = 0; j < i; j++) {
                if (dp[j] && dict.count(s.substr(j, i - j))) { // 满足条件说明,可以表示,设置为true后直接跳出循环。
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.size()];
    }
};

环形链表(简单)

给定一个链表,判断链表中是否有环。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
    bool hasCycle(ListNode *head) {
        auto s = head;
        auto f = head;
        while(f && f->next) {
            f = f->next->next;
            s = s->next;
            if (s == f) return true;
        }
        return false;
    }
};

环形链表II(中等)

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        auto s = head;
        auto f = head;
        while (f && f->next) {
            f = f->next->next;
            s = s->next;
            if (s == f) break;
        }
        if (!f || !f->next) return nullptr;
        s = head;
        while (s != f) {
            s = s->next;
            f = f->next;
        }
        return s;
    }
};

LRU 缓存机制(中等)

运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class LRUCache {
public:
    typedef pair<int, int> PII;
    unordered_map<int, list<PII>::iterator> m; // 哈希表
    list<PII> cache; // 双向链表
    int cap = 0;

    LRUCache(int capacity) {
        cap = capacity;
    }

    int get(int key) {
        if (m.count(key)) {
            auto t = *m[key]; // 找到双向链表中的结点
            cache.erase(m[key]); // 先删除该节点
            cache.push_front(t);  // 再把节点放在链表头
            m[key] = cache.begin(); // 把哈希表中的值更新为链接头
            return t.second;
        } else return -1;
    }

    void put(int key, int value) {
        if (m.count(key)) { // 如果包含键,则直接删除链表结点
            cache.erase(m[key]);
        } else {
            if (cache.size() == cap) {
                auto last = cache.back(); // 记录最后一个结点的值
                cache.pop_back(); // 删除最后一个结点
                m.erase(last.first); // 删除最后一个结点在哈希表中的键
            }
        }
        cache.push_front({key, value});
        m[key] = cache.begin();
    }
};

归并排序链表(中等)

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。归并排序链表

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

class Solution {
   public:
    /**
     * 递归版本
     * 时间复杂度:O(nlogn)
     * 空间复杂度:O(logn)
     */
    ListNode* sortList(ListNode* head) {
        if (!head || !head->next) return head;  // 边界条件
        auto s = head;
        auto f = head;
        // 快慢指针找中间结点
        while (!f || !f->next) {
            f = f->next->next;
            s = s->next;
        }
        auto t = s->next;  // 断开链表
        s->next = nullptr;
        auto l = sortList(head);        // 递归排序前部分
        auto r = sortList(t);           // 递归排序后部分
        auto dummy = new ListNode(-1);  // 构建虚拟结点
        auto cur = dummy;
        // 归并两部分
        while (l && r) {
            if (l->val < r->val) {
                cur->next = l;
                l = l->next;
                cur = cur->next;
            } else {
                cur->next = r;
                r = r->next;
                cur = cur->next;
            }
        }
        while (l) {
            cur->next = l;
            l = l->next;
            cur = cur->next;
        }
        while (r) {
            cur->next = r;
            r = r->next;
            cur = cur->next;
        }
        return dummy->next;
    }

    /**
     * 迭代版
     * 时间复杂度:O(nlogn)
     * 空间复杂度:O(1)
     */
    ListNode* sortList(ListNode* head) {
        int n = 0;
        for (auto p = head; p; p = p->next) n++; // 统计节点数量
        auto dummy = new ListNode(-1); // 构建虚拟结点
        dummy->next = head;
        // 枚举区间长度
        for (int i = 1; i < n; i *= 2) {
            auto cur = dummy;
            // 枚举每隔两个区间的起点
            for (int j = 0; j + i < n; j += i * 2) {
                auto first = cur->next, second = first;
                // 寻找第二个区间的起点
                for (int k = 0; k < i; k++) second = second->next;
                int f = 0, s = 0;
                // 归并
                while (f < i && s < i && second) {
                    if (first->val < second->val) {
                        cur->next = first;
                        cur = cur->next;
                        first = first->next;
                        f++;
                    } else{
                        cur->next = second;
                        cur = cur->next;
                        second = second->next;
                        s++;
                    }
                }
                while (f < i) {
                    cur->next = first;
                    cur = cur->next;
                    first = first->next;
                    f++;
                }
                while (s < i && second) {
                    cur->next = second;
                    cur = cur->next;
                    second = second->next;
                    s++;
                }
                // 转到下下一个区间
                cur->next = second;
            }
        }
        return dummy->next;
    }
};

乘积最大子数组(中等)

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。乘积最大子数组

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Solution {
   public:
    /**
     * 动态规划
     * 状态表示:dpmax[i]表示到i位置最大的乘积,dpmin[i]表示到i位置最小的乘积
     * 状态计算: 见代码
     * 时间复杂度:O(n)
     * 空间复杂度:O(n)
     */
    int maxProduct(vector<int>& nums) {
        int n = nums.size();
        vector<int> dpmax(nums), dpmin(nums); // dpmax和dpmin分别记录最大值和最小值
        for (int i = 1; i < n; i++) {
            if (nums[i] > 0) { // 如果当前数是正数,则正常计算
                dpmax[i] = max(dpmax[i - 1] * nums[i], nums[i]);
                dpmin[i] = min(dpmin[i - 1] * nums[i], nums[i]);
            } else { // 如果当前数是负数,则最大数变最小数,最小数变最大数 
                dpmax[i] = max(dpmin[i - 1] * nums[i], nums[i]);
                dpmin[i] = min(dpmax[i - 1] * nums[i], nums[i]);
            }
        }
        int res = INT_MIN;
        for (int i = 0; i < n; i++) {
            res = max(res, max(dpmax[i], dpmin[i]));
        }
        return res;
    }

    /**
     * 空间优化
     * 时间复杂度:O(n)
     * 空间复杂度:O(1)
     */
    int maxProduct(vector<int>& nums) {
        int n = nums.size();
        int minV = 1, maxV = 1, res = INT_MIN;
        for (auto n : nums) {
            if (n < 0) {
                int t = maxV;
                maxV = minV;
                minV = t;
            }
            maxV = max(maxV * n, n);
            minV = min(minV * n, n);
            res = max(res, maxV);
        }
        return res;
    }
};

相交链表(简单)

编写一个程序,找到两个单链表相交的起始节点。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        auto p = headA, q = headB;
        while (p != q) {
            if (p) p = p->next;
            else p = headB;
            if (q) q = q->next;
            else q = headA;
        }
        return p;
    }
};

多数元素(简单)

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
    /**
     * 摩尔投票法
     * 时间复杂度:O(n)
     * 空间复杂度:O(1)
     */
    int majorityElement(vector<int>& nums) {
        int res, cnt = 0;
        // 摩尔投票
        for (auto n : nums) {
            if (!cnt) {
                res = n;
                cnt = 1;
            } else if (n == res) cnt++;
            else cnt--;
        }
        cnt = 0;
        // 验证
        for (auto n : nums) if (n == res) cnt++;
        return cnt > nums.size() / 2 ? res : -1;
    }
};

打家劫舍(简单)

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if (!n) return 0;
        int dp_i_1 = 0, dp_i_2 = 0, dp_i = 0;
        for (auto n : nums) {
            dp_i = max(dp_i_1, dp_i_2 + n);
            dp_i_2 = dp_i_1;
            dp_i_1 = dp_i;
        }
        return dp_i;
    }
};

岛屿数量(中等)

给你一个由 ’1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。岛屿数量

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
   public:
    int m, n;
    int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};
    // 二维深搜
    int numIslands(vector<vector<char>>& grid) {
        if (!grid.size() || !grid[0].size()) return 0;
        m = grid.size(), n = grid[0].size();
        int res = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '1') {
                    dfs(grid, i, j);
                    res++;
                }
            }
        }
        return res;
    }

    void dfs(vector<vector<char>>& grid, int x, int y) {
        grid[x][y] = '0';
        for (int i = 0; i < 4; i++) {
            int a = x + dx[i], b = y + dy[i];
            if (a >= 0 && a < m && b >= 0 && b < n && grid[a][b] == '1')
                dfs(grid, a, b);
        }
    }
};

反转链表(简单)

反转一个单链表。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
    /**
     * 递归版本
     */
    ListNode* reverseList(ListNode* head) {
        if (!head || !head->next) return head;
        auto last = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return last;
    }
    /**
     * 非递归版本
     */
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        auto cur = head;
        while (cur) {
            auto nx = cur->next;
            cur->next = pre;
            pre = cur;
            cur = nx;
        }
        return pre;
    }
};

课程表(中等)

你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?课程表

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class Solution {
   public:
    /**
     * 本题可约化为:课程安排图是否是有向无环图(DAG)。即课程间规定了前置条件,但不能构成任何环路,否则课程前置条件将不成立。
     * 广度优先搜索, 拓扑排序
     * 1.统计课程安排图中每个结点的入度,生成入度表
     * 2.借助一个队列,将所有入度为0的结点入队
     * 3.当队列非空的时候,依次将队首节点出队,在课程安排表中删除此结点
     * (不是真正删除此节点,而是将此节点对应的所有邻接结点的入度减一,如果减一后邻接结点的入度为0,
     * 说明该节点的课程可以学习,则将该节点入队)。
     * 4.每次队列出队时,即当前课程已修完,课程总数减一,最后通过剩余课程是否是0来判断是否可以修完课程
     */
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> indegree(numCourses);     // 统计每门课程的入度
        vector<vector<int>> adj(numCourses);  // 邻接表
        queue<int> q;                         // 队列
        // 统计入度以及构建邻接表
        for (auto c : prerequisites) {
            indegree[c[0]]++;
            adj[c[1]].push_back(c[0]);
        }
        // 将入度为0的课程结点入队
        for (int i = 0; i < numCourses; i++)
            if (indegree[i] == 0) q.push(i);
        while (q.size()) {
            int pre = q.front();  // 当前课程出队
            q.pop();
            numCourses--;  // 剩余课程减一
            // 与当前结点相邻的结点的入度减一
            for (auto c : adj[pre])
                // 如果相邻结点的入度减一之后等于0,说明该节点的课程可以修,该节点入队
                if (--indegree[c] == 0) q.push(c);
        }
        // 最后判断能否修完所有课程
        return numCourses == 0;
    }

    /**
     * 深度优先 判断有向图中是否有环
     * 1.借助一个标志列表flag,用于判断每个结点(课程)的状态:
     * (1)未被访问 i=0;
     * (2)已被其他节点启动的dfs访问 i=-1;
     * (3)已被当前结点启动的dfs访问 i=1;
     * 2.对课程的每个结点依次执行dfs,判断每个结点起步的dfs是否有环,若有环直接返回false
     * (1)终止条件:flag[i]=-1,说明当前结点已被其他结点启动的dfs访问,无需重复搜索,直接返回true。flag[i]=1说明在本轮dfs搜索中i被第2次搜索,说明有环,直接返回false。
     * (2)将当前访问的结点的标记置为1,即标记其被本轮dfs访问。
     * (3)递归访问当前节点i的所有邻接节点j,当发现环直接返回 False
     * (4)当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点flag置为−1并返回True。
     */
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<vector<int>> adj(numCourses);
        vector<int> flag(numCourses);
        for (auto c : prerequisites) adj[c[1]].push_back(c[0]);  //构建邻接表
        for (int i = 0; i < numCourses; i++) {  // 从每个课程结点开始遍历
            if (!dfs(adj, flag, i)) return false;
        }
        return true;
    }

    bool dfs(vector<vector<int>>& adj, vector<int>& flag, int i) {
        if (flag[i] == 1) return false;
        if (flag[i] == -1) return true;
        flag[i] = 1; // 将当前访问的结点的标记置为1,即标记其被本轮dfs访问
        // 递归遍历当前结点的邻接结点
        for (auto c : adj[i]) {
            if (!dfs(adj, flag, c)) return false;
        }
        flag[i] = -1; // 当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点flag置为−1
        return true;
    }
};

实现Trie(前缀树)

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。实现Trie

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Trie {
public:
    /** Initialize your data structure here. */
    struct Node {
        bool is_end;
        Node* son[26];
        Node() {
            is_end = false;
            for (int i = 0 ; i < 26; i++) son[i] = nullptr;
        }
    }*root;

    Trie() {
        root = new Node();
    }

    /** Inserts a word into the trie. */
    void insert(string word) {
        auto p = root;
        for (auto c : word) {
            int idx = c - 'a';
            if (!p->son[idx]) p->son[idx] = new Node();
            p = p->son[idx];
        }
        p->is_end = true;
    }

    /** Returns if the word is in the trie. */
    bool search(string word) {
        auto p = root;
        for (auto c : word) {
            int idx = c - 'a';
            if (!p->son[idx]) return false;
            p = p->son[idx];
        }
        return p->is_end;
    }

    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        auto p = root;
        for (auto c : prefix) {
            int idx = c - 'a';
            if (!p->son[idx]) return false;
            p = p->son[idx];
        }
        return true;
    }
};

数组中第K个最大的元素(中等)

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。数组中的第K个最大元素

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Solution {
   public:
    /**
     * 直接快排
     * 时间复杂度:O(nlogn)
     */
    int findKthLargest(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end()), reverse(nums.begin(), nums.end());
        return nums[k - 1];
    }
    /**
     * 使用堆
     * 时间复杂度:O(nlogk)
     * 空间复杂度:O(k)
     */
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int, vector<int>, greater<int>> q;
        for (auto n : nums) {
            q.push(n);
            if (q.size() > k) q.pop();
        }
        return q.top();
    }

    /**
     * 快排的思想
     * 时间复杂度:O(n)
     */
    int findKthLargest(vector<int>& nums, int k) {
        int idx = -1;
        int l = 0, r = nums.size() - 1;
        while (idx != k - 1) {
            int idx = partition(nums, l, r);
            if (idx < k - 1)
                l = idx + 1;
            else if (idx > k - 1)
                r = idx - 1;
            else
                break;
        }
        return nums[k - 1];
    }

    int partition(vector<int>& nums, int l, int r) {
        int flag = l;
        int idx = l + 1;
        for (int i = idx; i <= r; i++) {
            if (nums[i] > nums[flag]) {
                swap(nums[i], nums[idx]);
                idx++;
            }
        }
        swap(nums[flag], nums[idx - 1]);
        return idx - 1;
    }
};

最大正方形(中等)

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。最大正方形

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
   public:
    /**
     * 动态规划
     * 状态表示:dp[i][j]表示以ij为右下角的最大正方形边长
     * 状态计算:dp[i][j]受限于dp[i-1][j-1]、dp[i-1][j]和dp[i][j-1]中的最小值
     */
    int maximalSquare(vector<vector<char>>& matrix) {
        if (!matrix.size() || !matrix[0].size()) return 0;
        int m = matrix.size(), n = matrix[0].size();
        vector<vector<int>> dp(m, vector<int>(n));
        int len = 0;
        for (int i = 0; i < m ; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == '1') {
                    if (!i || !j) dp[i][j] = 1;
                    else dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
                    len = max(len, dp[i][j]);
                }
            }
        }
        return len * len;
    }
};

翻转二叉树(简单)

翻转一棵二叉树。

参考代码

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (!root) return root;
        auto t = root->left;
        root->left = invertTree(root->right);
        root->right = invertTree(t);
        return root;
    }
};

回文链表(简单)

请判断一个链表是否为回文链表。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
    ListNode* left; // 左侧
    bool isPalindrome(ListNode* head) {
        left = head;
        return dfs(head);
    }

    bool dfs(ListNode* head) {
        if(!head) return true;
        bool res = dfs(head->next); // dfs到最右端
        res = res && (left->val == head->val);
        left = left->next;
        return res;
    }
};

二叉树的最近公共祖先(中等)

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。二叉树的最近公共祖先

参考代码

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (!root || root == p || root == q) return root;
        auto left = lowestCommonAncestor(root->left, p, q);
        auto right = lowestCommonAncestor(root->right, p, q);
        if (!left) return right;
        if (!right) return left;
        return root;
    }
};

除自身以外数组的乘积(中等)

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。除自身以外数组的乘积

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
   public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> res(n, 1);
        int t = 1;
        for (int i = 0; i < n; i++) {
            res[i] *= t;
            t *= nums[i];
        }
        t = 1;
        for (int i = n - 1; i >= 0; i--) {
            res[i] *= t;
            t *= nums[i];
        }
        return res;
    }
};

滑动窗口的最大值(困难)

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。滑动窗口最大值

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
   public:
    /**
     * 维护一个单调递减队列
     */
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        deque<int> q;
        vector<int> res;
        for (int i = 0; i < n; i++) {
            if (q.size() && i - k + 1 > q.front()) q.pop_front(); // 如果窗口大于k,则队头出队
            while (q.size() && nums[q.back()] < nums[i]) q.pop_back(); // 维护单调递减队列
            q.push_back(i);
            if (i >= k - 1) res.push_back(nums[q.front()]); // 窗口满足要求,则入队
        }
        return res;
    }
};

搜索二维矩阵II(中等)

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:每行的元素从左到右升序排列。每列的元素从上到下升序排列。搜索二维矩阵II

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        if (!matrix.size() || !matrix[0].size()) return false;
        int m = matrix.size(), n = matrix[0].size();
        int r = 0, c = n - 1;
        while (r < m && c >= 0) {
            if (matrix[r][c] == target) return true;
            else if (matrix[r][c] < target) r++;
            else c--;
        }
        return false;
    }
};

完全平方数(中等)

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。完全平方数

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
   public:
    /**
     * 动态规划
     * 状态表示:dp[i]表示组成数字i需要的最少平方数
     * 状态计算:dp[i] = min(dp[i], dp[i - j * j] + 1)
     */
    int numSquares(int n) {
        vector<int> dp(n + 1);
        for (int i = 1; i <= n; i++) {
            dp[i] = i;
            for (int j = 1; j * j <= i; j++) {
                dp[i] = min(dp[i], dp[i - j * j] + 1);
            }
        }
        return dp[n];
    }
};

移动零(简单)

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

参考代码

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int idx = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i]) nums[idx++] = nums[i];
        }
        for(int i = idx; i < nums.size(); i++) nums[i] = 0;
    }
};

寻找重复数(中等)

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Solution {
   public:
    /**
     * 抽屉原理 + 二分法
     * 时间复杂度:O(n)
     * 空间复杂度:O(1)
     */
    int findDuplicate(vector<int>& nums) {
        int l = 1, r = nums.size() - 1;
        while (l < r) {
            int mid = l + r >> 1;
            int cnt = 0;
            for (auto x : nums) cnt += l <= x && x <= mid;
            if (cnt > mid - l + 1)
                r = mid;
            else
                l = mid + 1;
        }
        return l;
    }

    /**
     * 双指针,寻找环的入口 和 环形链表很像
     */
    int findDuplicate(vector<int>& nums) {
        int s = 0, f = 0;
        while (true) {
            s = nums[s];
            f = nums[nums[f]];
            if (s == f) break;
        }
        s = 0;
        while (true) {
            s = nums[s];
            f = nums[f];
            if (s == f) break;
        }
        return s;
    }
};

二叉树的序列化与反序列化(困难)

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。二叉树的序列化与反序列化

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

class Codec {
   public:
    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        string res;
        dfs1(root, res);
        return res;
    }

    void dfs1(TreeNode* root, string& res) {
        if (!root) {
            res += "#,";
            return;
        }
        res += to_string(root->val) + ',';
        dfs1(root->left, res);
        dfs1(root->right, res);
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        int idx = 0;
        return dfs2(data, idx);
    }

    TreeNode* dfs2(string& data, int& idx) {
        if (data[idx] == '#') {
            idx += 2;
            return nullptr;
        }
        bool is_minus = false;
        if (data[idx] == '-') {
            is_minus = true;
            idx++;
        }
        int t = 0;
        while (data[idx] != ',') {
            t = t * 10 + data[idx] - '0';
            idx++;
        }
        idx++;
        if (is_minus) t = -t;
        auto root = new TreeNode(t);
        root->left = dfs2(data, idx);
        root->right = dfs2(data, idx);
        return root;
    }
};

最长上升子序列(中等)

给定一个无序的整数数组,找到其中最长上升子序列的长度。最长上升子序列

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
   public:
    /**
     * 动态规划
     * 状态表示:dp[i]表示以i为结尾的子序列的最大长度
     * 状态计算:dp[i] = max(dp[i], dp[j] + 1) 当nums[j] < nums[i]时
     * 时间复杂度:O(n^2)
     */
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        if (!n) return 0;
        vector<int> dp(n);
        for (int i = 0; i < n; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i]) dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        return dp[n - 1];
    }

    /**
     * 时间复杂度:O(nlogn)
     */
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        if (!n) return 0;
        vector<int> dp(n);
        int len = 0;
        for (int i = 0; i < n; i++) {
            int l = 0, r = len;
            while (l < r) {
                int mid = l + r >> 1;
                if (dp[mid] >= nums[i]) r = mid;
                else l = mid + 1;
            }
            if (l ==len) len++;
            dp[l] = nums[i];
        }
        return len;
    }
};

删除无效的括号(困难)

删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。删除无效的括号

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class Solution {
   public:
    /**
     * 深搜dfs
     * 首先统计需要删除的左括号和右括号的数量,
     * 然后递归遍历尝试删除每一个左括号和右括号,
     * 当需要从连续的左括号或右括号中删除一个时,我们删除第一个括号,剩余的跳过以避免重复
     */
    vector<string> res;
    vector<string> removeInvalidParentheses(string s) {
        int l = 0, r = 0;  // 统计需要删除的左括号的数量和右括号的数量
        for (auto c : s) {
            if (c == '(')
                l++;
            else if (c == ')') {
                if (l > 0)
                    l--;
                else
                    r++;
            }
        }
        dfs(s, 0, l, r);
        return res;
    }

    void dfs(string& s, int idx, int l, int r) {
        if (l == 0 && r == 0) {
            if (isValid(s)) res.push_back(s);
            return;
        }
        // 从当前位置向后遍历
        for (int i = idx; i < s.size(); i++) {
            if (i != idx && s[i] == s[i - 1]) continue;  // 去重
            if (s[i] == '(' && l > 0) {                  // 删除左括号
                string t = s;
                t.erase(i, 1);
                dfs(t, i, l - 1, r);
            }
            if (s[i] == ')' && r > 0) {  // 删除右括号
                string t = s;
                t.erase(i, 1);
                dfs(t, i, l, r - 1);
            }
        }
    }

    /**
     * 宽度优先 bfs
     * 1.删除最小括号使得字符串满足要求,那么我们怎么去删除呢,可以考虑每次给定的字符串删除一个字符有多少种可能,
     * 在这么多种可能中如果出现一例合法的,也就可以结束了.
     * 2.每次删除一个字符后的所有可能都入队列,每次处理队列数据的时候也是把当前队列中的所有元素统一处理,
     * 这样其实层次关系就出来了,在给定的层次关系下,计算计算得到最小值时的所有可能数据
     */
    vector<string> removeInvalidParentheses(string s) {
        vector<string> res;
        queue<string> q;
        unordered_set<string> hash;
        q.push(s);
        while (q.size()) {
            string t = q.front();
            q.pop();
            if (isValid(t))
                res.push_back(t);
            else if (!res.size()) {
                for (int i = 0; i < t.size(); i++) {
                    if (t[i] == '(' || t[i] == ')') {
                        string cur = t;
                        cur.erase(i, 1);
                        if (!hash.count(cur)) {
                            hash.insert(cur);
                            q.push(cur);
                        }
                    }
                }
            }
        }
        return res;
    }

    // 判断括号是否是合法的
    bool isValid(string s) {
        int cnt = 0;
        for (auto c : s) {
            if (c == '(')
                cnt++;
            else if (c == ')')
                cnt--;
            if (cnt < 0) return false;
        }
        return cnt == 0;
    }
};

买卖股票的最佳时机含冷冻期(中等)

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。最佳买卖股票时机含冷冻期

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
   public:
    int maxProfit(vector<int>& prices) {
        int dp_i_0 = 0, dp_i_1 = INT_MIN, dp_pre_0 = 0;
        for (auto p : prices) {
            int t = dp_i_0;
            dp_i_0 = max(dp_i_0, dp_i_1 + p);
            dp_i_1 = max(dp_i_1, dp_pre_0 - p); 
            dp_pre_0 = t; 
        }
        return dp_i_0;
    }
};

戳气球(困难)

有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。现在要求你戳破所有的气球。如果你戳破气球 i ,就可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。求所能获得硬币的最大数量。戳气球

思路:参考题解

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
   public:
    /**
     * 动态规划
     * 如何设计使得子问题之间没有相关性
     * 状态表示:dp[i][j]:表示戳破开区间i到j之间的所有气球获得的最大金币,最后要求的就是dp[0][n+1](我们在开头和结尾分别添加一个虚拟气球)
     * 状态计算:假设开区间走后一个戳破的气球为k,则在戳破第k个气球之前,需要将开区间(i,k)即dp[i][k]和开区间(k,j)即dp[k][j]中的所有气球戳破,
     * 最后戳破第k个气球point[i]* point[k]* point[j],所以状态转移方程是,dp[i][j] = max(dp[i][k]+dp[k][j]+point[i]*point[k] * point[j])i<k<j
     */
    int maxCoins(vector<int>& nums) {
        int n = nums.size();
        vector<int> point(n + 2, 1); // 为数组前后添加虚拟气球
        for (int i = 0; i < n; i++) point[i + 1] = nums[i];
        vector<vector<int>> dp(n + 2, vector<int>(n + 2));
        // 枚举区间起点
        for (int i = n + 1; i >= 0; i--)
            // 枚举区间终点
            for (int j = i + 1; j < n + 2; j++)
                // 枚举区间中的点
                for (int k = i + 1; k < j; k++)
                    dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + point[i] * point[k] * point[j]);
        return dp[0][n + 1];
    }
};

零钱兑换(中等)

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。零钱兑换

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
   public:
    /**
     * 动态规划
     * 状态表示:dp[i]表示凑金额为i时花费的最小金币个数
     * 状态计算:以最后一个金币用的哪一个来转移,dp[i] = min(dp[i], dp[i - c] + 1).
     * 时间复杂度:O(sn)
     */
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, amount + 1); //初始化一个比较大的值
        dp [0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (auto c : coins) {
                if (i >= c) dp[i] = min(dp[i], dp[i - c] + 1);
            }
        }
        return  dp[amount] == amount + 1 ? -1 : dp[amount];
    }
};

打家劫舍III(中等)

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。打家劫舍III

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

class Solution {
   public:
    unordered_map<TreeNode*, int> memo;
    int rob(TreeNode* root) {
        if (!root) return 0;
        if (memo.count(root)) return memo[root];
        int do_it = // 如果抢当前结点
            root->val +
            (root->left ? rob(root->left->left) + rob(root->left->right) : 0) +
            (root->right ? rob(root->right->left) + rob(root->right->right): 0);
        int not_do = rob(root->left) + rob(root->right); // 不抢当前结点
        int res = max(do_it, not_do); // 取最大
        memo[root] = res;
        return res;
    }
};

比特位计数(中等)

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。比特位计数

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
   public:
    /**
     * 时间复杂度:O(nk)
     */
    vector<int> countBits(int num) {
        vector<int> res;
        for (int i = 0; i <= num; i++) {
            int cnt = 0;
            int n = i;
            while (n) {  // 统计每一个数字的二进制中1的个数
                n = n & (n - 1);
                cnt++;
            }
            res.push_back(cnt);
        }
        return res;
    }

    /**
     * x可以看成是x’左移一位再加上新添进来的一位的结果
     * 时间复杂度:O(n)
     */
    vector<int> countBits(int num) {
        vector<int> res(num + 1);
        for (int i = 1; i <= num; i++) res[i] = res[i >> 1] + (i & 1);
        return res;
    }
};

前k个高频元素(中等)

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。前k个高频元素

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Solution {
   public:
    typedef pair<int, int> PII;
    /**
     * 使用堆或优先队列,维护一个大小为k的小顶堆。
     * 时间复杂度:O(nlogk)
     * 空间复杂度:O(n)
     */
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> hash;
        for (auto n : nums) hash[n]++;
        vector<PII> t;
        for (auto item : hash) t.push_back({item.second, item.first});
        priority_queue<PII, vector<PII>, greater<PII>> q;
        for (auto n : t) {
            q.push(n);
            if (q.size() > k) q.pop();
        }
        vector<int> res;
        while (q.size()) {
            res.push_back(q.top().second);
            q.pop();
        }
        return res;
    }

    /**
     * 利用快排的思想
     * 时间复杂度:O(n)
     * 空间复杂度:O(n)
     */
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> hash;
        for (auto n : nums) hash[n]++;
        vector<PII> t;
        for (auto item : hash) t.push_back({item.second, item.first});
        int idx = -1, l = 0, r = t.size() - 1;
        while (idx != k - 1) { // 快排的思想
            idx = partition(t, l, r);
            if (idx > k - 1)
                r = idx - 1;
            else if (idx < k - 1)
                l = idx + 1;
            else
                break;
        }
        vector<int> res;
        for (int i = 0; i < k; i++) res.push_back(t[i].second);
        return res;
    }

    int partition(vector<PII>& nums, int l, int r) {
        int flag = l;
        int idx = l + 1;
        for (int i = idx; i <= r; i++) {
            if (nums[i].first > nums[flag].first) {
                swap(nums[i], nums[idx]);
                idx++;
            }
        }
        swap(nums[flag], nums[idx - 1]);
        return idx - 1;
    }
};

字符串解码(中等)

给定一个经过编码的字符串,返回它解码后的字符串。字符串解码

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
   public:
    /**
     * 栈
     */
    string decodeString(string s) {
        string res;
        stack<int> ns;
        stack<string> ss;
        int n = 0;
        for (auto c : s) {
            // 遇到左括号,数字入栈,当前字符串入栈,然后分别清空
            if (c == '[') {
                ns.push(n);
                ss.push(res);
                n = 0;
                res.clear();
                // 遇到右括号,括号中间的字符串出现了多少次
            } else if (c == ']') {
                int tn = ns.top();  // 次数
                ns.pop();
                auto ts = ss.top();
                ss.pop();
                // res表示当前括号区间的字符串,然后重复tn次
                for (int i = 0; i < tn; i++) ts += res;
                res = ts;
                // 是数字
            } else if (c >= '0' && c <= '9')
                n = n * 10 + c - '0';
            else  // 是字符
                res += c;
        }
        return res;
    }
};

除法求值(中等)

给出方程式 A / B = k, 其中 A 和 B 均为用字符串表示的变量, k 是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0。除法求值

参考代码

1
2
3
4
5
6
class Solution {
public:
    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
        // 并查集
    }
};

根据身高重建队列(中等)

假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。根据身高重建队列

思路:参考题解

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
   public:
    // 先按照身高降序排序,如果身高相同,则按人数升序排序
    static bool cmp(vector<int> a, vector<int> b) {
        if (a[0] == b[0]) return a[1] < b[1];
        return a[0] > b[0];
    }
    // 前面的个子小的看不见
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(), people.end(), cmp);  // 排序
        vector<vector<int>> res;
        // 将人按照人数放在对应的索引位置
        for (auto p : people) res.insert(res.begin() + p[1], p);
        // 删除占位的空间
        res.erase(res.begin() + people.size(), res.end());
        return res;
    }
};

分割等和子集(中等)

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。分割等和子集

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
   public:
    /**
     * 动态规划,转换成0-1背包问题
     * 样品是所有的数字,背包容量是所有数字和的一半。
     * 状态表示:dp[j] 表示背包容量为j时是否可以装满
     * 状态计算:要么装当前数字,要么不装
     */
    bool canPartition(vector<int>& nums) {
        int w = 0, n = nums.size();
        for (auto t : nums) w += t;  // 计算数字之和
        if (w % 2) return false;     // 不能被2整除,直接返回false
        vector<bool> dp(w / 2 + 1, false);
        dp[0] = true;
        // 0-1背包模板
        for (auto n : nums) {
            for (int j = w / 2; j >= n; j--) {
                dp[j] = dp[j] || dp[j - n];
            }
        }
        return dp[w / 2];
    }
};

路径总和III(简单)

给定一个二叉树,它的每个结点都存放着一个整数值。找出路径和等于给定数值的路径总数。路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。路径总和III

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
    int pathSum(TreeNode* root, int sum) {
        if (!root) return 0;
        int res = dfs(root, sum);
        int l = pathSum(root->left, sum);
        int r = pathSum(root->right, sum);
        return res + l + r;
    }

    int dfs(TreeNode* root, int sum) {
        if (!root) return 0;
        sum -= root->val;
        int t = sum == 0;
        return t + dfs(root->left, sum) + dfs(root->right, sum);
    }
};

找到字符串中所有字母异位词(中等)

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。找到字符串中所有字母异位词

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
   public:
    /**
     * 滑动窗口
     */
    vector<int> findAnagrams(string s, string p) {
        int m = s.size(), n = p.size();
        unordered_map<char, int> need, window;
        vector<int> res;
        for (auto c : p) need[c]++;
        int l = 0, r = 0, matched = 0;
        while (r < m) {  // 右边界
            char c = s[r];
            if (need.count(c)) {
                window[c]++;
                if (window[c] == need[c]) matched++;
            }
            r++;
            while (matched == need.size()) {  // 如果匹配,缩小左边界
                if (r - l == p.size()) res.push_back(l);
                char t = s[l];
                if (need.count(t)) {
                    window[t]--;
                    if (window[t] < need[t]) matched--;
                }
                l++;
            }
        }
        return res;
    }
};

找到所有数组中消失的数字(简单)

给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。找到所有在 [1, n] 范围之间没有出现在数组中的数字。找到所有数组中消失的数字

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
   public:
    /**
     * 利用哈希表
     * 时间复杂度:O(n)
     * 空间复杂度:O(n)
     */
    vector<int> findDisappearedNumbers(vector<int>& nums) {
        vector<int> res;
        unordered_set<int> hash;
        for (auto n : nums) hash.insert(n);
        for (int i = 1; i <= nums.size(); i++)
            if (!hash.count(i)) res.push_back(i);
        return res;
    }

    /**
     * 思路:把nums[nums[i]-1]位置的数字标记为相反数,说明nums[i]这个数字存在,
     * 最后遍历数组一次,如果某个位置的数字非负,则说明缺失当前位置对应的数字
     * 时间复杂度:O(n)
     * 空间复杂度:O(1)
     */
    vector<int> findDisappearedNumbers(vector<int>& nums) {
        vector<int> res;
        for (auto n : nums) {
            n = abs(n);
            if (nums[n - 1] > 0) nums[n - 1] = -nums[n - 1]; // 标记为相反数
        }
        for (int i = 0; i < nums.size(); i++) { // 统计非负的位置
            if (nums[i] > 0) res.push_back(i + 1);
        }
        return res;
    }
};

汉明距离(简单)

两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。给出两个整数 x 和 y,计算它们之间的汉明距离。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
    int hammingDistance(int x, int y) {
        int n = x ^ y;
        int res = 0;
        while (n) {
            res++;
            n = n & (n - 1);
        }
        return res;
    }
};

目标和(中等)

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。目标和

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
   public:
    int res = 0;
    /**
     * 深搜
     */
    int findTargetSumWays(vector<int>& nums, int S) {
        if (!nums.size()) return 0;
        dfs(nums, 0, S);
        return res;
    }

    void dfs(vector<int>& nums, int idx, long long s) {
        if (idx == nums.size()) {
            if (s == 0) res++;
            return;
        }
        dfs(nums, idx + 1, s + nums[idx]);
        dfs(nums, idx + 1, s - nums[idx]);
    }

    typedef long long LL;
    /**
     * 转换成0-1背包问题求方案数
     * 用A表示正数集合,B表示负数集合,Q表示所有集合
     * 所以sum(A) + sum(B) = sum(Q);sum(A) - sum(B) = S;
     * sum(A) = (S + sum(Q)) / 2; 即sum(A)即为样本容量
     */
    int findTargetSumWays(vector<int>& nums, int S) {
        LL sum = accumulate(nums.begin(), nums.end(), 0);
        if (S > sum || (S + sum) % 2) return 0;
        int p = (S + sum) / 2;
        vector<int> dp(p + 1);
        dp[0] = 1;
        for (auto n : nums) {
            for (int j = p; j >= n; j--) {
                dp[j] += dp[j - n];
            }
        }
        return dp[p];
    }
};

把二叉搜索树转换成累加树(简单)

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
   public:
    int pre = 0;  // 记录比当前结点大的值的和
    // 中序遍历倒过来
    TreeNode* convertBST(TreeNode* root) {
        if (!root) return root;
        convertBST(root->right);  // 先去右子树
        root->val += pre;         // 更新当前节点的值
        pre = root->val;          // 更新pre
        convertBST(root->left);   // 去左子树
        return root;
    }
};

二叉树的直径(简单)

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

class Solution {
   public:
    int res = 0;
    int diameterOfBinaryTree(TreeNode* root) {
        if (!root) return 0;
        dfs(root);
        return res;
    }

    int dfs(TreeNode* root) {
        if (!root) return 0;
        int l = dfs(root->left);
        int r = dfs(root->right);
        res = max(res, l + r);
        return max(l, r) + 1;
    }
};

和为k的子数组(中等)

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。和为K的子数组

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Solution {
   public:
    /**
     * 双指针
     * 固定左端点,移动右端点
     * 时间复杂度:O(n^2)
     * 空间复杂度:O(1)
     */
    int subarraySum(vector<int>& nums, int k) {
        int res = 0;
        for (int i = 0; i < nums.size(); i++) {
            int sum = 0;
            for (int j = i; j < nums.size(); j++) {
                sum += nums[j];
                if (sum == k) res++;
            }
        }
        return res;
    }

    /**
     * 以i为终点的子数组
     */
    int subarraySum(vector<int>& nums, int k) {
        int res = 0;
        for (int i = 0; i < nums.size(); i++) {
            int sum = 0;
            for (int j = i; j >= 0; j--) {
                sum += nums[j];
                if (sum == k) res++;
            }
        }
        return res;
    }

    /**
     * 前缀和 + 优化
     * 如果区间j,i满足和为k,则pre[i]-pre[j-1] == k,所以 pre[j-1] = pre[i] - k;
     * 时间复杂度:O(n)
     * 空间复杂度:O(n)
     */
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> hash;
        hash[0] = 1;
        int pre = 0, res = 0;
        for (int i = 0; i < nums.size(); i++) {
            pre += nums[i];
            if (hash.count(pre - k)) res += hash[pre - k];
            hash[pre]++;
        }
        return res;
    }
};

最短无序连续子数组(简单)

给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。你找到的子数组应是最短的,请输出它的长度。简单

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Solution {
   public:
    /**
     * 暴力,枚举区间起点和终点,如果起点和终点不在正确的位置,记录起点和终点的位置。
     * 时间复杂度:O(n^2)
     */
    int findUnsortedSubarray(vector<int>& nums) {
        int l = nums.size(), r = 0;  //记录左边界和右边界
        for (int i = 0; i < nums.size() - 1; i++) {
            for (int j = i + 1; j < nums.size(); j++) {
                if (nums[j] < nums[i]) {  // 如果不在正确的位置上,更新左右边界
                    l = min(l, i);
                    r = max(r, j);
                }
            }
        }
        return r - l > 0 ? r - l + 1 : 0;
    }

    /**
     * 排序
     * 时间复杂度:O(nlogn)
     * 空间复杂度:O(n)
     */
    int findUnsortedSubarray(vector<int>& nums) {
        vector<int> t(nums);
        sort(t.begin(), t.end());
        int l = nums.size(), r = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] != t[i]) {
                l = min(l, i);
                r = max(r, i);
            }
        }
        return r - l > 0 ? r - l + 1 : 0;
    }

    /**
     * 单调栈
     * 时间复杂度:O(n)
     * 空间复杂度:O(n)
     */
    int findUnsortedSubarray(vector<int>& nums) {
        int l = nums.size(), r = 0;
        stack<int> stk;
        for (int i = 0; i < nums.size(); i++) {
            while (stk.size() && nums[stk.top()] > nums[i]) {
                l = min(l, stk.top());
                stk.pop();
            }
            stk.push(i);
        }
        while (stk.size()) stk.pop();
        for (int i = nums.size() - 1; i >= 0; i--) {
            while (stk.size() && nums[stk.top()] < nums[i]) {
                r = max(r, stk.top());
                stk.pop();
            }
            stk.push(i);
        }
        return r - l > 0 ? r - l + 1 : 0;
    }
};

合并二叉树(简单)

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

参考代码

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (!t1) return t2;
        if (!t2) return t1;
        auto root = new TreeNode(t1->val + t2->val);
        root->left = mergeTrees(t1->left, t2->left);
        root->right = mergeTrees(t1->right, t2->right);
        return root;
    }
};

任务调度器(中等)

给定一个用字符数组表示的 CPU 需要执行的任务列表。其中包含使用大写的 A - Z 字母表示的26 种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。CPU 在任何一个单位时间内都可以执行一个任务,或者在待命状态。然而,两个相同种类的任务之间必须有长度为 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。你需要计算完成所有任务所需要的最短时间。任务调度器

思路:参考题解

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
   public:
    /**
     * 思路:首先安排数量最多的任务,假设数量为k,则k-1组中需要 (k-1) * (n+1)
     * 的时间, 然后将剩余的任务加上去,即和A有相同数量的任务都加1,
     * 最后考虑特殊情况,不需要等待,就可以完成,即需要时间为所有任务的数量。
     */
    int leastInterval(vector<char>& tasks, int n) {
        int nt = tasks.size();
        int m[26] = {0};
        for (auto t : tasks) m[t - 'A']++;
        int mx = 0;
        for (int i = 0; i < 26; i++) mx = max(mx, m[i]);
        int res = (mx - 1) * (n + 1);
        for (int i = 0; i < 26; i++) {
            if (mx == m[i]) res++;
        }
        return max(nt, res);
    }
};

回文子串(中等)

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。回文子串

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
   public:
    /**
     * 中心扩展法
     * 枚举每一个可能的中心位置,长度为n的字符串,有2*n-1个中心,然后向两边扩展
     */
    int countSubstrings(string s) {
        int n = s.size(), res = 0;
        for (int i = 0; i < 2 * n - 1; i++) {
            int l = i / 2, r = l + i % 2;  // 注意这儿求中心位置的方法
            while (l >= 0 && r < n && s[l] == s[r]) {
                res++, r--, l++;
            }
        }
        return res;
    }
    /**
     * 动态规划
     * 状态表示:dp[i][j]表示字符串i到j的子串是否是回文串
     * 状态计算:见代码
     */
    int countSubstrings(string s) {
        int res = 0, n = s.size();
        vector<vector<bool>> dp(n, vector<bool>(n));
        for (int j = 0; j < n; j++) {
            dp[j][j] = true;
            res++;
            for (int i = 0; i < j; i++) {
                dp[i][j] = (s[i] == s[j]) && (j - i < 2 || dp[i + 1][j - 1]);
                if (dp[i][j]) res++;
            }
        }
        return res;
    }
};

每日温度(中等)

请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。每日温度

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
   public:
    /**
     * 单调栈
     */
    vector<int> dailyTemperatures(vector<int>& T) {
        vector<int> res(T.size());
        stack<int> stk;
        for (int i = T.size() - 1; i >= 0; i--) {
            while (stk.size() && T[stk.top()] <= T[i]) stk.pop();
            res[i] = stk.empty() ? 0 : stk.top() - i;
            stk.push(i);
        }
        return res;
    }
};