跳转至

LeetCode Hot100 笔记

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

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

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

中位数的位置:奇数为中间的数,偶数为中间两个数的平均值

trick:分别找第 (n + 1) / 2, (n + 2) / 2 两个数,求平均值(奇偶均适用)

O(log (m+n)) :二分查找 O(m+n):归并排序

C++
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    int n = nums1.size() + nums2.size();
    int left = get_Kth_value(nums1, 0, nums2, 0, (n + 1) / 2);
    int right = get_Kth_value(nums1, 0, nums2, 0, (n + 2) / 2);
    return (left + right) / 2.0;
}
int get_Kth_value(const vector<int>& nums1, int i, const vector<int>& nums2, int j, int k)
{
    if(i >= nums1.size()) return nums2[j + k - 1]; // nums1空数组
    if(j >= nums2.size()) return nums1[i + k - 1]; // nums2空数组
    if(k == 1) return min(nums1[i], nums2[j]);

    int mid_nums1 = (i + k / 2 - 1 < nums1.size()) ? nums1[i + k / 2 - 1] : INT_MAX;
    int mid_nums2 = (j + k / 2 - 1 < nums2.size()) ? nums2[j + k / 2 - 1] : INT_MAX;

    if(mid_nums1 < mid_nums2) 
        return get_Kth_value(nums1, i + k / 2, nums2, j, k - k / 2);
    else return get_Kth_value(nums1, i, nums2, j + k / 2, k - k / 2);
}

5. 最长回文子串

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

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

中心扩散法:选定一个中心向两边扩散。

C++
string longestPalindrome(string s) {
    int n = s.size();
    string res;
    for(int i = 0; i < n; ++i)
    {
        ExtendFromCentor(s, res, i, i); // 子串数目为奇数
        ExtendFromCentor(s, res, i, i + 1); // 子串数目为偶数
    }
    return res;
}
void ExtendFromCentor(const string& s, string& res, int left, int right){
    while(left >= 0 && right < s.size() && s[left] == s[right])
    {
        --left;
        ++right;
    }
    if(right - left - 1 > res.size()) res = s.substr(left + 1, right - left -1);
}

10. 正则表达式匹配

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

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

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

C++
bool isMatch(string s, string p) {
    int n_s = s.size(), n_p = p.size();
    s = ' ' + s;
    p = ' ' + p;
    vector<vector<bool>> dp(n_s + 1, vector<bool>(n_p + 1, false));
    dp[0][0] = true;
    for(int i = 0; i <= n_s; ++i) // s可以为空,p不能为空
    {
        for(int j = 1; j <= n_p; ++j)
        {
            if(j + 1 <= n_p && p[j + 1] == '*') continue;
            if(p[j] != '*') dp[i][j] = i >= 1 && j >= 1 && dp[i - 1][j - 1] && (p[j] == '.' || s[i] == p[j]);
            else dp[i][j] = (j >= 2 && dp[i][j - 2]) || (i >= 1 && dp[i - 1][j] && (p[j - 1] == '.' || s[i] == p[j - 1]));
        }
    }
    return dp[n_s][n_p];
}

11. 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0)(i, height[i])

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

示例:

输入:[1,8,6,2,5,4,8,3,7] 输出:49 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

双指针:

指针 i,j 分别指向两端,容器面积由短板决定,res = min(h[i] , h[j]) * (j - i) 若移动短板,容器的短板可能变大,则容器的面积可能会变大 若移动长板,容器的短板不变或变小,则容器的面积一定减小 因此,每次移动短板,求的移动过程中容器的最大值

C++
int maxArea(vector<int>& height) {
    int n = height.size();
    int left = 0, right = n - 1, res = 0;
    while(left < right)
    {
        res = max(min(height[left], height[right]) * (right - left), res);
        if(height[left] < height[right]) ++left;
        else --right;
    }
    return res;
}
Java
class Solution {
    public int maxArea(int[] height) {
        int n = height.length, left = 0, right = n - 1, res = 0;
        while(left < right) {
            res = Math.max(res, Math.min(height[left], height[right]) * (right - left));
            if(height[left] < height[right]) ++left;
            else --right;
        }
        return res;
    }
}

15. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

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

C++
vector<vector<int>> threeSum(vector<int>& nums) {
    int n = nums.size();
    sort(nums.begin(), nums.end());
    vector<vector<int>> ret;
    for(int i = 0; i < n - 2; ++i){
        if(nums[i] > 0) break;
        if(i >= 1 && nums[i] == nums[i - 1]) continue;
        int j = i + 1, k = n - 1;
        while(j < k){
            int sum = nums[i] + nums[j] + nums[k];
            if(sum == 0){
                ret.push_back({nums[i], nums[j], nums[k]});
                while(j < k && nums[j] == nums[j + 1]) ++j;
                while(j < k && nums[k] == nums[k - 1]) --k;
                ++j;
                --k;
            }
            else if(sum < 0) ++j;
            else --k;
        }
    }
    return ret;
}

22. 括号生成

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

通过回溯的方法来模拟每次生成的是左括号还是右括号

通过一个堆栈st来判断生成的括号是否有效,当存在一个相匹配的左右括号时,从堆栈中弹出该对括号,若最终生成的括号有效,则堆栈为空

C++
vector<string> generateParenthesis(int n) {
    vector<string> res;
    string path = "";
    stack<char> st;
    backtracking(n, res, path, st);
    return res;
}
void backtracking(int n, vector<string>& res, string& path, stack<char>& st)
{
    if(path.size() == 2 * n)
    {
        if(st.empty()) res.push_back(path);
        return;
    }
    path.push_back('(');
    st.push('(');
    backtracking(n, res, path, st);
    st.pop();
    path.pop_back();
    if(!st.empty() && st.top() == '(')
    {
        path.push_back(')');
        st.pop();
        backtracking(n, res, path, st);
        st.push('(');
        path.pop_back();
    }
}

23. 合并 K 个升序链表

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

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

使用优先队列priority_queue来判断K个链表中的最小值

C++
ListNode* mergeKLists(vector<ListNode*>& lists) {
    int n = lists.size();
    if(lists.size() == 0) return nullptr;
    priority_queue<ListNode*, vector<ListNode*>, cmp> que;
    ListNode* dummyhead = new ListNode();
    ListNode* cur = dummyhead;
    for(int i = 0; i < n; ++i)
    {
        if(lists[i] != nullptr) que.push(lists[i]);
    }
    while(!que.empty())
    {
        ListNode* temp = que.top();
        que.pop();
        cur->next = temp;
        cur = cur->next;
        if(temp->next != nullptr) que.push(temp->next);
    }
    return dummyhead->next;
}
class cmp{
    public:
    bool operator()(ListNode* a, ListNode* b)
    {
        return a->val > b->val;
    }
};

31. 下一个排列

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

  • 例如,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 的下一个排列。

C++
void nextPermutation(vector<int>& nums) {
    int n = nums.size(), i = n - 2;
    while(i >= 0 && nums[i] >= nums[i + 1]) --i;
    if(i >= 0)
    {
        int j = n - 1;
        while(j >= 0 && nums[j] <= nums[i]) --j;
        swap(nums[i], nums[j]);
    }
    reverse(nums, i + 1, n - 1);
}
void reverse(vector<int>& nums, int left, int right)
{
    while(left < right)
    {
        swap(nums[left], nums[right]);
        ++left;
        --right;
    }
}

32 最长有效括号

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

方法1: 动态规划

​ dp[i]: 以s[i]结尾的字符串s的最长有效括号的长度,因此只要当s[i]为)时才有效

​ 当s[i - 1] 为(时,dp[i] = dp[i - 2] + 2

​ 当s[i - 1] 为)时,则s为dp[i - 2 - dp[i - 1]], _, dp[i - 1], s[i]的形式,判断 _ 处是否为(,则

​ dp[i] = dp[i - 1] + 2 + dp[i - 2 - dp[i - 1]]

方法2: 栈

​ 构建一个栈,存放s中字符的下标,栈顶元素为最后一个没有匹配的右括号的下标,预先入栈 -1

​ 当s[i]为(时入栈,push下标

​ 当s[i]为)时,弹出栈顶元素,判断栈是否为空

​ 当栈为空时,说明该)没有与之匹配的(,将该)的下标入栈

​ 当栈不为空时,计算res = i - st.top()

C++
// method 1 动态规划
int longestValidParentheses(string s) {
    int n = s.size(), res = 0;
    if(n == 0 || n == 1) return res;
    vector<int> dp(n); // dp[i]:以s[i]结尾的字符串s的最长有效括号的长度
    dp[0] = 0;
    for(int i = 1; i < n; ++i){
        if(s[i] == ')'){
            if(s[i - 1] == '(') dp[i] = ((i - 2) >= 0 ? dp[i - 2] : 0 ) + 2;
            else if((i - 1 - dp[i - 1]) >= 0 && s[i - 1 - dp[i - 1]] == '(')
                dp[i] = dp[i - 1] + 2 + ((i - 2 - dp[i - 1]) >= 0 ? dp[i - 2 - dp[i - 1]] : 0);
        }
        res = max(res, dp[i]);
    }
    return res;
}

// method 2 栈
int longestValidParentheses(string s) {
    int n = s.size(), res = 0;
    if(n == 0 || n == 1) return res;
    stack<int> st;
    st.push(-1);
    for(int i = 0; i < n; ++i){
        if(s[i] == '(') st.push(i);
        else{
            st.pop();
            if(st.empty()) st.push(i);
            else res = max(res, i - st.top());
        }
    }
    return res;
}

33 搜索旋转排序数组

整数数组 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) 的算法解决此问题。

O(log n) 应该使用二分查找

nums : 4,5,6,7,|0,1,2 分为左右两边

当mid在左边时:

​ target < nums[mid] 时,right = mid - 1

​ 其余情况,left = mid + 1

当mid在右边时:

​ target > nums[mid]时,left = mid + 1

​ 其余情况,right = mid - 1

C++
// 二分查找
int search(vector<int>& nums, int target){
    int n = nums.size();
    int left = 0, right = n - 1;
    while(left <= right){
        int mid = left + (right - left) / 2;
        if(nums[mid] == target) return mid;
        else if(nums[mid] >= nums[0]){
            if(nums[0] <= target && target < nums[mid]) right = mid - 1;
            else left = mid + 1;
        }
        else{
            if(nums[mid] < target && target < nums[0]) left = mid + 1;
            else right = mid - 1;
        }
    }
    return -1;
}

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

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

通过二分查找来查找第一个和最后一个

设置一个标志flag,为true时查找第一个,为false时查找最后一个

C++
vector<int> searchRange(vector<int>& nums, int target) {
    int n = nums.size();
    if(n == 0 || nums[0] > target || nums[n - 1] < target) return {-1, -1};
    return {binarySearch(nums, target, true), binarySearch(nums, target, false)};
}

// flag = true : 查找第一个
// flag = false : 查找最后一个
int binarySearch(vector<int>& nums, int target, bool flag){
    int n = nums.size(), left = 0, right = n - 1;
    while(left <= right){
        int mid = left + (right - left) / 2;
        if(nums[mid] > target) right = mid - 1;
        else if(nums[mid] < target) left = mid + 1;
        else{
            if(flag){
                if(mid == 0 || nums[mid - 1] != nums[mid]) return mid;
                else right = mid - 1;
            }
            else{
                if(mid == n - 1 || nums[mid + 1] != nums[mid]) return mid;
                else left = mid + 1;
            }
        }
    }
    return -1;
}

42 接雨水

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

单调栈

当存在雨水时,需确定雨水的左右两柱子的最小高度,和两柱子之间的距离 - 当前高度小于等于栈顶高度,入栈,指针后移 - 当前高度大于栈顶高度: - 栈顶高度出栈,作为水槽的底heightBase - 需确认heightBase左右两柱子高度的最小值 - 当新栈顶为空时,表明不存在左柱子,水槽为空,退出 - 否则,计算当前高度与新栈顶高度的最小值,作为水槽的高heightMin,计算当前指针与新栈顶指针的差值,作为水槽的宽distance,水槽的面积 = distance * (heightMin - heightBase),更新结果res - 重复上一过程,否则当前高度入栈,指针后移

C++
int trap(vector<int>& height) {
    int n = height.size(), res = 0;
    stack<int> st;
    for(int i = 0; i < n; ++i){
        while(!st.empty() && height[i] > height[st.top()]){
            int heightBase = height[st.top()];
            st.pop();
            if(st.empty()) break;
            int heightMin = min(height[st.top()], height[i]);
            int distance = i - st.top() - 1;
            res += distance * (heightBase - heightMin);
        }
        st.push(i);
    }
    return res;
}
Java
class Solution {
    public int trap(int[] height) {
        int n = height.length, res = 0;
        Stack<Integer> st = new Stack<>();
        for(int i = 0; i < n; ++i) {
            while(!st.isEmpty() && height[i] > height[st.peek()]) {
                int baseHeight = height[st.peek()];
                st.pop();
                if(st.isEmpty()) break;
                int minHeight = Math.min(height[i], height[st.peek()]);
                res += (minHeight - baseHeight) * (i - st.peek() - 1);
            }
            st.push(i);
        }
        return res;
    }
}

72 编辑距离

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:插入一个字符、删除一个字符、替换一个字符

动态规划

dp[i] [j] : word1的前i个字符转换成word2前j个字符所需的最小操作数

  • 当word1[i - 1] == word2[j - 1]时:
    • 不需要进行操作,dp[i] [j] = dp[i - 1] [j - 1]
  • 当word1[i - 1] == word2[j - 1]时:
    • 存在三种操作使得最后一个字符相同:插入、删除、替换,取三种操作的最小值来更新dp[i] [j]
    • 插入:dp[i] [j - 1]
    • 删除:dp[i - 1] [j]
    • 替换:dp[i - 1] [j - 1]
C++
int minDistance(string word1, string word2) {
    int n1 = word1.size(), n2 = word2.size();
    if(n1 == 0 || n2 == 0) return max(n1, n2);
    vector<vector<int>> dp(n1 + 1, vector<int>(n2 + 1)); // dp[i][j]: word1的前i个字符转换成word2前j个字符所需的最小操作数
    for(int i = 0; i <= n1; ++i) dp[i][0] = i;
    for(int j = 0; j <= n2; ++j) dp[0][j] = j;
    for(int i = 1; i <= n1; ++i){
        for(int j = 1; j <= n2; ++j){
            if(word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
            else dp[i][j] = min({dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]}) + 1;
        }
    }
    return dp[n1][n2];
}

75 颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 012 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

双指针:left(1区间的左边)、right(1区间的右边)

  • nums[i] == 0
    • 交换 i 和 left,left右移,i++
  • nums[i] == 1
    • i++
  • nums[i] == 2
    • 交换 i 和 right,right左移
C++
void sortColors(vector<int>& nums) {
    int n = nums.size();
    int i = 0, left = 0, right = n - 1;
    while(i < right){
        if(nums[i] == 0){
            swap(nums[left],nums[i]);
            ++left;
            ++i;
        }
        else if(nums[i] == 1) ++i;
        else{
            swap(nums[i], nums[right]);
            --right;
        }
    }
}

76 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

示例:

输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC" 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

思路:滑动窗口

C++
string minWindow(string s, string t) {
    int n_s = s.size(), n_t = t.size();
    vector<int> need(128);
    for(int i = 0; i < n_t; ++i) ++need[t[i]];
    int left = 0, right = 0, lenght = INT_MAX, start = 0, count = 0;
    while(right < n_s){
        if(need[s[right]] > 0) ++count;
        --need[s[right]];
        if(count == n_t){
            while(left < right && need[s[left]] < 0){
                ++need[s[left]];
                ++left;
            }
            if(right - left + 1 < lenght){
                start = left;
                lenght = right - left + 1;
            }
            ++need[s[left]];
            ++left;
            --count;
        }
        ++right;
    }
    return lenght == INT_MAX ? "" : s.substr(start, lenght);
}
Java
class Solution {
    public String minWindow(String s, String t) {
        if(s.length() < t.length()) return new String();
        int m = s.length(), n = t.length(), resPos = 0, resSize = Integer.MAX_VALUE, count = 0;
        int[] hash = new int[128];
        for(int i = 0; i < n; ++i) {
            ++hash[t.charAt(i)];
        }
        for(int left = 0, right = 0; right < m; ++right) {
            if(hash[s.charAt(right)] > 0) ++count;
            --hash[s.charAt(right)];
            if(count == n) {
                while(left < right && hash[s.charAt(left)] < 0) {
                    ++hash[s.charAt(left)];
                    ++left;
                }
                if(right - left + 1 < resSize) {
                    resPos = left;
                    resSize = right - left + 1;
                }
                ++hash[s.charAt(left)];
                ++left;
                --count;
            }
        }
        return s.substring(resPos, resSize == Integer.MAX_VALUE ? 0 : resPos + resSize);
    }
}

96 不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 二叉搜索树有多少种?返回满足题意的二叉搜索树的种数。

G(n): 由n个节点组成的二叉搜索树的个数

f(i): 以i为根结点的二叉搜索树的个数,其左子树节点个数i - 1,右子树节点个数n - i,则f(i) = G(i - 1) * G(n - i)

则G(n) = f(1) + f(2) + ... + f(n) = G(0) * G(n - 1) + G(1) * G(n - 2) + ... + G(n - 1) * G(0)

C++
int numTrees(int n){
    vector<int> dp(n + 1);
    dp[0] = 1;
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= i; ++j){
            dp[i] += dp[j - 1] * dp[i - j];
        }
    }
    return dp[n];
}

98 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

二叉搜索树满足左子树的所有节点小于根结点,右子树的所有值大于根结点

即二叉搜索树的中序遍历是递增的

对二叉树进行中序遍历,使用pre来保存前一个节点的值,若当前节点<=pre,返回false

C++
long long pre = INT64_MIN;
bool isValidBST(TreeNode* root){
    if(root == nullptr) return true;
    if(!isValidBST(root->left)) return false;
    if(pre >= root->val) return false;
    pre = root->val;
    return isValidBST(root->right);
}

105 从前序与中序遍历序列构造二叉树

给定两个整数数组 preorderinorder ,其中 preorder 是二叉树的先序遍历inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

前序遍历:根左右

中序遍历:左右根

  • 前序遍历的头节点为根节点,在中序遍历中找到根节点的索引,则其左右分别为左右子树
  • 每次不必对遍历序列进行分别,可设定左右指针作为序列的有效范围
C++
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder){
    int n = preorder.size();
    if(n == 0) return nullptr;
    unordered_map<int, int> map;
    for(int i = 0; i < n; ++i){
        map[inorder[i]] = i;
    }
    return backtracking(preorder, 0, n - 1, inorder, 0, n - 1, map);
}
TreeNode* backtracking(const vector<int>& preorder, int preLeft, int preRight, const vector<int>& inorder, int inLeft, int inRight, unordered_map<int, int>& map){
        if(preLeft > preRight) return nullptr;
        int rootValue = preorder[preLeft];
        int rootIndex = map[rootValue];
        TreeNode* root = new TreeNode(rootValue);
        root->left = backtracking(preorder, preLeft + 1, preLeft + rootIndex - inLeft, inorder, inLeft, rootValue - 1, map);
        root->right = backtracking(preorder, preLeft + rootIndex - inLeft + 1, preRight, inorder, rootIndex + 1, inRight, map);
        return root;
}

114 二叉树展开为链表

给你二叉树的根结点 root ,请你将它展开为一个单链表:

  • 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null
  • 展开后的单链表应该与二叉树 先序遍历 顺序相同。

img

分别将左右子树进行展开,左节点为空,右节点指向左子树,左子树的末尾右节点指向右子树

C++
void flatten(TreeNode* root) {
    if(root == nullptr) return;
    TreeNode* leftChildTree = root->left;
    TreeNode* rightChildTree = root->right;
    flatten(leftChildTree);
    flatten(rightChildTree);
    if(leftChildTree == nullptr) return;
    root->left = nullptr;
    root->right = leftChildTree;
    TreeNode* cur = leftChildTree;
    while(cur->right != nullptr){
        cur = cur->right;
    }
    cur->right = rightChildTree;
}

124 二叉树的最大路径和

128 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入:nums = [100,4,200,1,3,2] 输出:4 解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:

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

提示:

0 <= nums.length <= 105 -109 <= nums[i] <= 109

解析:

时间复杂度要求为 O(n),不能使用排序,考虑哈希表 将所有元素加入哈希表,遍历数组,找到序列的起始元素,计算该序列的长度

Java
class Solution {
    public int longestConsecutive(int[] nums) {
        if(nums.length == 0) return 0;
        Set<Integer> set = new HashSet<>();
        for(int num : nums) {
            set.add(num);
        }
        int res = 0;
        for(int num : nums) {
            if(!set.contains(num - 1)) {
                int end = num + 1;
                while(set.contains(end)) {
                    ++end;
                }
                res = Math.max(res, end - num);
            }
        }
        return res;
    }
}

139 单词拆分

146 LRU缓存

148 排序链表

207 课程表

208 实现Trie(前缀树)

215 数组中的第K个最大元素

221 最大正方形

234 回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

Java
// 递归
class Solution {
    private ListNode frontNode;
    public boolean isPalindrome(ListNode head) {
        frontNode = head;
        return backtracking(head);
    }
    private boolean backtracking(ListNode cur) {
        if(cur == null) return true;
        if(backtracking(cur.next) && cur.val == frontNode.val) {
            frontNode = frontNode.next;
            return true;
        }
        return false;
    }
}

// 反转链表
class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode fast = head, slow = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        if(fast != null) slow = slow.next;
        slow = reverse(slow);
        fast = head;
        while(slow != null) {
            if(fast.val != slow.val) return false;
            fast = fast.next;
            slow = slow.next;
        }
        return true;
    }
    private ListNode reverse(ListNode head) {
        if(head == null) return null;
        ListNode pre = null, cur = head;
        while(cur != null) {
            ListNode nextNode = cur.next;
            cur.next = pre;
            pre = cur;
            cur = nextNode;
        }
        return pre;
    }
}

239 滑动窗口最大值

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

返回 滑动窗口中的最大值 。

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7]

示例 2:

输入:nums = [1], k = 1 输出:[1]

提示:

1 <= nums.length <= 105 -104 <= nums[i] <= 104 1 <= k <= nums.length

思路:优先队列

Java
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] pair1, int[] pair2) {
                return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
            }
        });
        int n = nums.length;
        int[] res = new int[n - k + 1];
        for(int i = 0; i < k; ++i) {
            pq.offer(new int[]{nums[i], i});
        }
        res[0] = pq.peek()[0];
        for(int i = k; i < n; ++i) {
            pq.offer(new int[]{nums[i], i});
            while(pq.peek()[1] <= i - k) {
                pq.poll();
            }
            res[i - k + 1] = pq.peek()[0];
        }
        return res;
    }
}

253 会议室Ⅱ

279 完全平方数

283 移动零

438 找到字符串中所有字母异位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:

输入: s = "cbaebabacd", p = "abc" 输出: [0,6] 解释: 起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。 起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

示例 2:

输入: s = "abab", p = "ab" 输出: [0,1,2] 解释: 起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。 起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。 起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

提示:

1 <= s.length, p.length <= 3 * 104 s 和 p 仅包含小写字母

Java
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> res = new ArrayList<>();
        if(s.length() < p.length()) return res;
        int ns = s.length(), np = p.length();
        int[] hash = new int[26];
        int[] temp = new int[26];
        for(int i = 0; i < np; ++i) {
            ++hash[p.charAt(i) - 'a'];
            ++temp[s.charAt(i) - 'a'];
        }
        if(Arrays.equals(hash, temp)) res.add(0);
        int left = 1, right = np;
        while(right < ns) {
            --temp[s.charAt(left - 1) - 'a'];
            ++temp[s.charAt(right) - 'a'];
            if(Arrays.equals(hash, temp)) res.add(left);
            ++left;
            ++right;
        }
        return res;
    }
}

560 和为 k 的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。

子数组是数组中元素的连续非空序列。

示例 1:

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

示例 2:

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

提示:

1 <= nums.length <= 2 * 104 -1000 <= nums[i] <= 1000 -107 <= k <= 107

思路:前缀和

Java
class Solution {
    public int subarraySum(int[] nums, int k) {
        int n = nums.length, sum = 0, res = 0;
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        for(int i = 0; i < n; ++i) {
            sum += nums[i];
            if(map.containsKey(sum - k)) res += map.get(sum - k);
            map.put(sum, map.getOrDefault(sum, 0) + 1);
        }
        return res;
    }
}

718 最长公共子数组(子串)

1143 最长公共子序列