跳转至

Leetcode 201-300

206. Reverse Linked List (Easy)

Reverse a singly linked list.

ListNode* reverseList(ListNode* head) {
    if (head == NULL || head->next == NULL) return head;
    ListNode *prev = NULL, *node = head, *next = NULL;
    while (true) {
        next = node->next;
        node->next = prev;
        if (next == NULL) return node;
        prev = node;
        node = next;
    }
}

207. Course Schedule (Medium)

There are a total of n courses you have to take, labeled from 0 to n-1.

即判断一个有向图有没有环

思路

DFS,看过程中有没有后向边,即v正在访问

bool dfs(unordered_map<int, vector<int> >& e, unordered_map<int, int>& color, int u) {
    color[u] = 1;
    if (!e.count(u)) {
        color[u] = 2;
        return true;
    }
    for (auto v : e[u])
        if (color[v] == 0 && !dfs(e, color, v)) return false;
        else if (color[v] == 1) return false;
    color[u] = 2;
    return true;
}
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
    unordered_map<int, vector<int> > e;
    unordered_map<int, int> color;
    for (auto p : prerequisites) {
        if (!color.count(p.first)) color[p.first] = 0;
        if (!color.count(p.second)) color[p.second] = 0;
        e[p.second].push_back(p.first);
    }
    for (auto it : color) if (color[it.second] == 0 && !dfs(e, color, it.first)) return false;
    return true;
}

208. Implement Trie (Prefix Tree) (Medium)

Implement a trie with insert, search, and startsWith methods.

Trie trie = new Trie();
trie.insert("apple");
trie.search("apple");   // returns true
trie.search("app");     // returns false
trie.startsWith("app"); // returns true
trie.insert("app");   
trie.search("app");     // returns true

思路

完成一棵前缀树即可

class Trie {
struct Node {
    vector<Node*> next;
    char c;
    bool tail;
    Node(char c) : c(c), tail(0), next(vector<Node*>(26, NULL)) {}
};  
public:
    vector<Node*> heads;
    /** Initialize your data structure here. */
    Trie() {
        this->heads = vector<Node*>(26, NULL);
    }
    /** Inserts a word into the trie. */
    void insert(string word) {
        if (heads[word[0] - 'a'] == NULL) heads[word[0] - 'a'] = new Node(word[0]);
        Node *ptr = heads[word[0] - 'a'];
        for (int i = 1; i < word.size(); i ++) {
            char c = word[i];
            if (ptr->next[c-'a'] == NULL) ptr->next[c-'a'] = new Node(c);
            ptr = ptr->next[c-'a'];
        }
        ptr->tail = 1;
    }

    /** Returns if the word is in the trie. */
    bool search(string word) {
        if (heads[word[0] - 'a'] == NULL) return NULL;
        Node* ptr = heads[word[0] - 'a'];
        for (int i = 1; i < word.size(); i ++) {
            char c = word[i];
            ptr = ptr->next[c - 'a'];
            if (ptr == NULL) return false;
        }
        return ptr->tail;
    }

    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        if (heads[prefix[0] - 'a'] == NULL) return NULL;
        Node* ptr = heads[prefix[0] - 'a'];
        for (int i = 1; i < prefix.size(); i ++) {
            char c = prefix[i];
            ptr = ptr->next[c - 'a'];
            if (ptr == NULL) return false;
        }
        return true;
    }
};

210. Course Schedule II (Medium)

给出一些相互依赖的课程关系,返回一个可能的修课顺序,如果不存在,返回空集

思路

拓扑排序即可

class Solution {
public:
    bool dfs(const vector<vector<int>>& graph, vector<int>& color, vector<int>& result, int node, int& rank) {
        color[node] = 1;
        for (int v : graph[node]) {
            if (color[v] == 0) {
                if (!dfs(graph, color, result, v, rank)) return false;
            }
            else if (color[v] == 1) return false;
        }
        color[node] = 2;
        result[rank - 1] = node; rank --;
        return true;
    }
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> result(numCourses, -1);
        vector<int> color(numCourses, 0);
        vector<vector<int>> graph(numCourses, vector<int>());
        for (auto& edge : prerequisites) graph[edge[1]].push_back(edge[0]);
        int rank = numCourses;
        for (int i = 0; i < numCourses; i ++)
            if (color[i] == 0)
                if (!dfs(graph, color, result, i, rank)) return {};
        return result;
    }
};

212. Word Search II (Hard)

class Solution {
private:
    class TreeNode {
    public:
        vector<TreeNode*> children;
        char c;
        bool end;
        TreeNode(char c) : c(c), end(false), children(vector<TreeNode*>(26, nullptr)) {}
    };
public:
    void recursion(vector<vector<char>>& board, int i, int j, int n, int m, TreeNode* node, vector<string>& res, string& s) {
        if (node->end) res.push_back(s);
        auto func = [&board, n, m, node, &s, &res, this](int i, int j) {
            if (i < 0 || i >= n || j < 0 || j >= m || board[i][j] == '-') return;
            TreeNode* next = node->children[(board[i][j] - 'a')];
            if (next == nullptr) return;
            char temp = '-';
            swap(board[i][j], temp);
            string ss = s + temp;
            recursion(board, i, j, n, m, next, res, ss);
            swap(board[i][j], temp);
        };
        func(i - 1, j);
        func(i + 1, j);
        func(i, j - 1);
        func(i, j + 1);
    }
    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        if (board.size() == 0 || words.size() == 0 || board[0].size() == 0) return {};
        TreeNode* root = new TreeNode('$');
        for (auto& word : words) {
            TreeNode* node = root;
            for (int i = 0; i < word.size(); i ++) {
                int rank = (word[i] - 'a');
                if (node->children[rank] == nullptr)
                    node->children[rank] = new TreeNode(word[i]);
                node = node->children[rank];
            }
            node->end = true;
        }
        vector<string> res;
        int n = board.size(), m = board[0].size();
        for (int i = 0; i < n; i ++) {
            for (int j = 0; j < m; j ++) {
                int rank = (board[i][j] - 'a');
                if (root->children[rank] != nullptr) {
                    char temp = '-';
                    swap(temp, board[i][j]);
                    string s = "";
                    s += temp;
                    recursion(board, i, j, n, m, root->children[rank], res, s);
                    swap(temp, board[i][j]);
                }
            }
        }
        unordered_map<string, int> dict;
        vector<string> result;
        for (auto& s : res) {
            if (!dict.count(s)) result.push_back(s);
            dict[s] ++;
        }
        return result;
    }
};

改进如下,将word直接存储在叶子节点上,然后访问到一次后置为空即可,这样时间可以从150ms上下降到44ms(99.25%)

struct TrieNode {
    TrieNode* children[26] = {nullptr};
    string word;
};
class Solution {
public:
    void recursion(vector<vector<char>>& board, int i, int j, int n, int m, \
         TrieNode* node, vector<string>& res) {
        if (i < 0 || i >= n || j < 0 || j >= m || board[i][j] == '-') return;
        if (node->children[(board[i][j] - 'a')] == nullptr) return;
        node = node->children[(board[i][j] - 'a')];
        if (node->word != "") {
            res.push_back(node->word);
            node->word = "";
        }
        char c = board[i][j];
        board[i][j] = '-';
        recursion(board, i - 1, j, n, m, node, res);
        recursion(board, i + 1, j, n, m, node, res);
        recursion(board, i, j - 1, n, m, node, res);
        recursion(board, i, j + 1, n, m, node, res);
        board[i][j] = c;
    }
    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        if (board.size() == 0 || words.size() == 0 || board[0].size() == 0) return {};
        TrieNode* root = new TrieNode();
        for (auto& word : words) {
            TrieNode* node = root;
            for (int i = 0; i < word.size(); i ++) {
                int rank = (word[i] - 'a');
                if (node->children[rank] == nullptr)
                    node->children[rank] = new TrieNode();
                node = node->children[rank];
            }
            node->word = word;
        }
        vector<string> res;
        int n = board.size(), m = board[0].size();
        for (int i = 0; i < n; i ++)
            for (int j = 0; j < m; j ++)
                recursion(board, i, j, n, m, root, res);
        return res;
    }
};

215. Kth Largest Element in an Array (Medium)

找到一个array中第k大的数

快排

partition后看pivot是否是第k个,如果不是选择走左边还是右边,因为这里只需要执行一边,可以用迭代来写

快排时间复杂度为O(N),因为每次丢掉一半,递归树只有一侧,时间复杂度为

N+N/2+N/4+...+1=2N
int findKthLargest(vector<int>& nums, int k) {
    int left = 0, right = nums.size() - 1;
    while (true) {
        int r = right, pivot = nums[left];
        for (int i = right; i > left; i --)
            if (nums[i] <= pivot) swap(nums[r --], nums[i]);
        swap(nums[left], nums[r]);
        if (r == k - 1) return nums[r];
        else if (r > k - 1) right = r - 1;
        else left = r + 1;
    }
}

优先级队列

时间复杂度为O(Nlogk)

比较直接的想法,连续扫描k次数组时间复杂度为O(kN)

int findKthLargest(vector<int>& nums, int k) {
    priority_queue<int, std::vector<int>, greater<int> > pq;
    for (auto i : nums) {
        pq.push(i);
        if (pq.size() > k) pq.pop();
    }
    return pq.top();
}

218. The Skyline Problem (Hard)

天际线,有若干矩形形状的建筑,这些矩形互相重叠,求这些矩形拼在一起的轮廓

轮廓可以用关键点表示出来,即从最左端/高度为0开始画线,只有遇到关键点时,上移或下移来改变画笔高度,最终画出轮廓来,如下图

数据格式,输入矩阵为[lo, hi, height],分别为x轴上的范围和高度,输出为所有关键点,对于下图,输入输出如下

Input: [ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ]
Output: [ [2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0] ]

思路

关键点的横坐标一定是矩阵在x轴上的左节点/右节点,而对应的高度则是这个点上所有建筑的最高高度

按照这个思路,先确定关键点所有的可能位置,即矩阵的所有顶点,按照横坐标排序,然后记录当前最高高度,从左到右扫描,如果在某个节点时,最高高度发生了变化,说明这个点是关键点,则加入结果中

如上图,在x=5点最高高度没有发生变化,不是关键点,在x=7上最高高度从15变成12,可以作为关键点加入结果中

实现上,维持高度可以用multiset,可以记录多个重复高度,而每次只删除一个,同时内部红黑树可以维护高度大小顺序。 在排序时,同个位置的不同节点,应该保证左节点在右节点前面,即先在multiset里先加后删,例如两个矩阵相邻,不会出现先加[x, 0],再加[x, h]的情况,这里将左节点的高度设为负数,即可以在sort时保证这点,默认的sort比较函数对vector会依次比较各个元素,直到某个元素不同时小的在前

vector<vector<int>> getSkyline(vector<vector<int>>& buildings) {
    vector<vector<int>> points, res;
    for (auto& b : buildings) {
        points.push_back({b[0], -b[2]});
        points.push_back({b[1], b[2]});
    }
    sort(points.begin(), points.end());
    multiset<int> heights;
    heights.insert(0);
    int prev = 0, cur = 0;
    for (auto& point : points) {
        if (point[1] < 0) heights.insert(-point[1]);
        else heights.erase(heights.find(point[1]));
        cur = *heights.rbegin();
        if (cur != prev) {
            res.push_back({ point[0], cur });
            prev = cur;
        }
    }
    return res;
}

221. Maximal Square (Medium)

Given a 2D binary matrix filled with 0's and 1's, find the largest square containing only 1's and return its area.

Input: 
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
Output: 4

解法一

和之前85一样的思路,每一行记录往上连续1个数,然后用栈计算length,再计算面积

int maximalSquare(vector<vector<char>>& matrix) {
    if (matrix.size() == 0 || matrix[0].size() == 0) return 0;
    vector<int> heights(matrix[0].size()+1, 0);
    int mx = 0;
    for (int i = 0; i < matrix.size(); i ++) {
        for (int j = 0; j < matrix[0].size(); j ++) heights[j] = (matrix[i][j] == '1') ? 1+heights[j] : 0;
        stack<int> s;
        int ptr = 0;
        while (ptr < heights.size()) {
            if (s.empty() || heights[ptr] >= heights[s.top()]) s.push(ptr ++);
            else {
                int height = heights[s.top()];
                s.pop();
                int length = s.empty() ? ptr : ptr - 1 - s.top();
                mx = max(mx, min(length, height) * min(length, height));
            }
        }
    }
    return mx;
}

解法二 DP

dp[i][j] = min(dp[i-1][j], min(dp[i-1][j-1], dp[i][j-1]))

实际复杂度和之前一样

int maximalSquare(vector<vector<char>>& matrix) {
    if (matrix.size() == 0 || matrix[0].size() == 0) return 0;
    vector<vector<int> > dp(matrix.size()+1, vector<int>(matrix[0].size()+1, 0));
    int mx = 0;
    for (int i = 1; i <= matrix.size(); i ++)
        for (int j = 1; j <= matrix[0].size(); j ++)
            if (matrix[i-1][j-1] == '1') {
                dp[i][j] = min(dp[i-1][j], min(dp[i][j-1], dp[i-1][j-1])) + 1;
                mx = max(dp[i][j], mx);
            }
    return mx * mx;
}

222. Count Complete Tree Nodes (Medium)

Given a complete binary tree, count the number of nodes.

思路

BFS思路,层次遍历即可

int countNodes(TreeNode* root) {
    if (root == nullptr) return 0;
    deque<TreeNode*> q;
    q.push_back(root);
    int res = 0;
    while (!q.empty()) {
        int s = q.size();
        res += s;
        for (int i = 0; i < s; i ++) {
            auto node = q.front();
            q.pop_front();
            if (node->left != nullptr) q.push_back(node->left);
            if (node->right != nullptr) q.push_back(node->right);
        }
    }
    return res;
}

223. Rectangle Area (Medium)

给出两个矩形,求两个矩形的总覆盖面积,矩形由左下角和右上角坐标定义,长和宽都和坐标轴平行

思路

重点是如何求重叠面积,这里重叠矩形的左下角[A, B]和右上角[C, D]里的四个坐标,都是由原来两个矩形的坐标组成的

比如如果有重叠矩形,左下角横坐标一定是两个矩形左下角横坐标的较大值,右上角横坐标一定是两个矩形右上角横坐标的较小值,同时判断得出的左下角横坐标是否小于右上角横坐标,即可以确定是否存在重叠矩形

int computeArea(int A, int B, int C, int D, int E, int F, int G, int H) {
    int left = max(A,E), right = max(min(C,G), left);
    int bottom = max(B,F), top = max(min(D,H), bottom);
    return (C - A) * (D - B) - (right - left) * (top - bottom) + (G - E) * (H - F);
}

224. Basic Calculator (Hard)

算术式求解,包含括号、数字,符号只包含加减

Input: "(1+(4+5+2)-3)+(6+8)"
Output: 23

思路

可以先转换为逆波兰式,然后对逆波兰式求解,这是通用解法

但本题所要求的式子其实很简单,可以有更简洁的解法

先考虑不带括号只有加减的式子,那么因为没有优先级区别,每次读完一个数字,对前面求和即可,只需要记录前面的结果cur当前的运算符号op。如1-2+3,读到-时,已经得到了数字1,那么cur为1,op更新为减号,下次读到+,cur更新为1-2=-1,op更新为加号,读完全式后返回-1+3=2即可

那么加入括号后,等于要先计算一下中间的式子,这里可以递归来做,但更简单的是用一个栈来暂存一下之前的数值,然后来计算括号里面的式子,计算完后再继续之前的运算

1-2+(3-4)+5这个式子,在计算到左括号时,res=-1op='+',将两者都推入栈中,然后初始化为0+,计算括号里面的式子。在遇到右括号时,得到的结果cur=-1,这时将栈中缓存的结果推出,和现在的结果一起运算:-1 + -1=-2,此时op没有值,但因为右括号紧跟着一定是运算符,到时候再更新即可

代码如下,因为op只有两种情况,所以就表示为取值1或-1的整数,这样可以和结果存在一个栈中,另外注意在遇到符号时,num要初始化为0

int calculate(string s) {
    int sign = 1, cur = 0, num = 0;
    stack<int> buf;
    for (char c : s) {
        if (c == ' ') continue;
        if (c >= '0' && c <= '9') num = 10 * num + (c - '0');
        else if (c == '(') {
            buf.push(cur);
            buf.push(sign);
            cur = 0;
            sign = 1;
        } else if (c == ')') {
            cur += sign * num;
            num = 0;
            cur *= buf.top(); buf.pop();
            cur += buf.top(); buf.pop();
        } else if (c == '+' || c == '-') {
            cur += sign * num;
            sign = (c == '+') ? 1 : -1;
            num = 0;
        }
    }
    return cur + sign * num;
}

226. Invert Binary Tree (Easy)

Invert a binary tree.

     4
   /   \
  2     7
 / \   / \
1   3 6   9
     4
   /   \
  7     2
 / \   / \
9   6 3   1

思路

递归即可

TreeNode* invertTree(TreeNode* root) {
    if (root == NULL) return root;
    TreeNode *l = invertTree(root->left), *r = invertTree(root->right);
    root->right = l;
    root->left = r;
    return root;
}

227. Basic Calculator II (Medium)

计算算术式的值,算术式包含数字,符号为加减乘除

Input: " 3+5 / 2 "
Output: 5

思路

同样可以用逆波兰式求解

不过也可以直接计算,保存以下几个值

  • res: 前面已求值的式子的结果
  • cur: 当前正在求值的部分结果
  • num: 当前解析的数字
  • op: 上一个符号

在解析完一个数字(遇到运算符或到达式子末尾)后

  1. 根据上一个符号更新cur的值,如2*3,在解析完3后,cur=2op='*',这时cur更新为2*3=6
  2. 如果遇到的符号是加减号或者式子已经到达末尾,那么之前部分可以加入到res中,同时cur置为0,只有是乘除号的时候,这部分数值还需要考虑后面的数字
  3. 更新op(如果遇到的是符号),num置为0

代码如下

int calculate(string s) {
    long long cur = 0, res = 0, num = 0;
    char op = '+';
    for (int i = 0; i < s.size(); i ++) {
        char c = s[i];
        if (c >= '0' && c <= '9') {
            num *= 10;
            num += (c - '0');
        } 
        if (c == '+' || c == '-' || c == '*' || c == '/' || i == s.size() - 1) {
            switch (op) {
            case '+':
                cur += num;
                break;
            case '-':
                cur -= num;
                break;
            case '*':
                cur *= num;
                break;
            case '/':
                cur /= num;
                break;
            }
            if (c == '+' || c == '-' || i == s.size() - 1) {
                res += cur;
                cur = 0;
            }
            op = c;
            num = 0;
        }
    }
    return res;
}

230. Kth Smallest Element in a BST (Medium)

Given a binary search tree, write a function kthSmallest to find the kth smallest element in it.

Input: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
Output: 1

思路

先在左节点找第k小,如果找到直接返回,没找到则看root是不是恰好是第k个,不然就在右节点里找

void recursion(int& result, int& k, TreeNode* root) {
    if (root == nullptr) return;
    recursion(result, k, root->left);
    k --;
    if (k == 0) result = root->val;
    if (k <= 0) return;
    return recursion(result, k, root->right);
}

int kthSmallest(TreeNode* root, int k) {
    int result = 0;
    recursion(result, k, root);
    return result;
}

233. Number of Digit One (Hard)

Given an integer n, count the total number of digit 1 appearing in all non-negative integers less than or equal to n.

Input: 13
Output: 6 
Explanation: Digit 1 occurred in the following numbers: 1, 10, 11, 12, 13.
给一个正整数n,算从1到n所有数里出现1的个数

思路

分数位统计,个数上的1每10位出现一次,所以n % 10即可,之后每一位也是,但十位上是每隔100次出现1,每次连续出现10次,百位亦然

所以从个位开始考察,每次除以10再乘以这位的进制,即是1的数量

但这里要注意的是当这位不为0的时候,要单独计算,个位又分为是否是1,如果不是1,那么加上rank,如果是1,则要看后面数字是多少,是多少就会重复多少次

代码如下

int countDigitOne(int n) {
    int m = n, num = 0;
    long long rank = 1;
    while (m > 0) {
        if (m % 10 > 1) num += rank;
        else if (m % 10 == 1) num += (n % rank + 1);
        num += (m / 10) * rank;
        rank *= 10;
        m /= 10;
    }
    return num;
}

234. Palindrome Linked List (Easy)

判断一个链表是否是回文串

思路

先算长度,然后拆一半下来reverse,与剩下的半段即可

ListNode* reverseList(ListNode* head) {
    ListNode* rank = NULL;
    ListNode* temp = head;
    if (head != NULL) rank = head->next;
    while (rank != NULL) {
        ListNode* tt = rank->next;
        rank->next = temp;
        temp = rank;
        rank = tt;
    }
    if (head != NULL) head->next = NULL;
    return temp;
}
bool isPalindrome(ListNode* head) {
    int length = 0;
    for (ListNode* node = head; node != NULL; node = node->next) length++;
    if (length < 2) return true;
    int mid = (length % 2) ? (length+1)/2 : length/2;
    ListNode* head0 = head;
    for (int rank = 0; rank != mid; head0 = head0->next, rank ++) ;
    head0 = reverseList(head0);
    ListNode* head1 = head;
    for (ListNode* head1 = head; head0 != NULL; head1=head1->next, head0=head0->next)
        if (head1->val != head0->val) return false;
    return true;
}

236. Lowest Common Ancestor of a Binary Tree (Medium)

给一棵树,和两个节点,返回这两个节点的最小公共祖先

如下图,5和1的最小祖先是3

Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
Output: 3
Explanation: The LCA of nodes 5 and 1 is 3.

思路

递归来做,这里不太好想条件,其实递归找到一个p和q就可以返回,这里很关键,然后判断左右是否都在

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if (root==NULL || root==p || root==q) return root;
    TreeNode* leftSide = lowestCommonAncestor(root->left, p, q);
    TreeNode* rightSide = lowestCommonAncestor(root->right, p, q);
    if (leftSide != NULL && rightSide != NULL) return root;
    if (leftSide == NULL)return rightSide;
    return leftSide;
}

238. Product of Array Except Self (Medium)

给一个数组,对每个位置求除该数外其余数之积,不用触发

思路

要求的积即为这个数左边数乘积和右边数乘积之积,扫描两遍即可

vector<int> productExceptSelf(vector<int>& nums) {
    vector<int> result(nums.size(), 1);
    if (nums.size() == 0) return result;
    result[0] = nums[0];
    for (int i = 1; i < result.size() - 1; i ++) result[i] = nums[i] * result[i-1];
    int product = 1;
    for (int i = result.size() - 1; i >= 0; i --) {
        result[i] = (i > 0) ? result[i-1]*product : product;
        product *= nums[i];
    }
    return result;
}

239. Sliding Window Maximum (Hard)

Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Return the max sliding window.

Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3
Output: [3,3,5,5,6,7] 
Explanation: 
Window position                Max
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

思路

用一个辅助deque来保存窗口内最大值,思想就是当后面出现一个更大的数时,前面保存的数都作废,否则就加到deque后面,同时如果滑出窗口的数还在用,也要从deque中摘除出去

vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    vector<int> result;
    deque<int> temp;
    for (int i = 0; i < nums.size(); i ++) {
        while (!temp.empty() && temp.back() < nums[i]) temp.pop_back();
        temp.push_back(nums[i]);
        if (i >= k && nums[i-k] == temp.front()) temp.pop_front();
        if (i >= k - 1) result.push_back(temp.front());
    }
    return result;
}

240. Search a 2D Matrix II (Medium)

Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:

  • Integers in each row are sorted in ascending from left to right.
  • Integers in each column are sorted in ascending from top to bottom.

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]
Given target = 5, return true.

Given target = 20, return false.

思路

从左下角出发,向右数字变大,向上数字变小,如果target存在,沿着这两个方向一定可以找到

bool searchMatrix(vector<vector<int>>& matrix, int target) {
    if (matrix.size() == 0 || matrix[0].size() == 0) return 0;
    int i = matrix.size() - 1, j = 0;
    while (i >= 0 && j < matrix[0].size()) {
        if (matrix[i][j] == target) return true;
        else if (matrix[i][j] < target) j ++;
        else i --;
    }
    return false;
}

253. Meeting Rooms II (Medium)

Given an array of meeting time intervals consisting of start and end times [[s1,e1],[s2,e2],...] (si < ei), find the minimum number of conference rooms required.

For example,
Given [[0, 30],[5, 10],[15, 20]],
return 2.

思路

整体思路就是看一个会议开始前,之前有没有结束的会议,如果有就不用加会议室,不然就要新加会议室

解法一

用map来存start和end,start对应1,end对应-1,最后按照key顺序加起来,过程中记录最大值即为答案

int minMeetingRooms(vector<Interval>& intervals) {
    map<int, int> m;
    for (auto a : intervals) {
        ++m[a.start];
        --m[a.end];
    }
    int rooms = 0, res = 0;
    for (auto it : m) {
        res = max(res, rooms += it.second);
    }
    return res;
}

解法二

把start和end放在一个vector里面,sort一下,然后从头扫描start和end,如果start小于当前end,加一,否则两者都往后移

int minMeetingRooms(vector<Interval>& intervals) {
    vector<int> starts, ends;
    int res = 0, endpos = 0;
    for (auto a : intervals) {
        starts.push_back(a.start);
        ends.push_back(a.end);
    }
    sort(starts.begin(), starts.end());
    sort(ends.begin(), ends.end());
    for (int i = 0; i < intervals.size(); ++i) {
        if (starts[i] < ends[endpos]) ++res;
        else ++endpos;
    }
    return res;
}

解法三

先以start对所有时间对排序,用一个小顶堆来记录end,然后依次访问时间对,有两种情况,当前start大于等于最小的end,则入堆同时将堆顶元素推出,否则只入堆

最后堆大小即为答案

int minMeetingRooms(vector<Interval>& intervals) {
    sort(intervals.begin(), intervals.end(), [](const Interval &a, const Interval &b){return a.start < b.start;});
    priority_queue<int, vector<int>, greater<int>> q;
    for (auto a : intervals) {
        if (!q.empty() && q.top() <= a.start) q.pop();
        q.push(a.end);
    }
    return q.size();
}

265. Paint House II (Hard)

n个房子,每个房子可以涂k种颜色,每个房子涂颜色的cost不一样,记作cost[n][k],相邻房子不能涂一种颜色,求涂完所有房子的最低成本

Input: [[1,5,3],[2,9,4]]
Output: 5
Explanation: Paint house 0 into color 0, paint house 1 into color 2.
Minimum cost: 1 + 4 = 5; 
Or paint house 0 into color 2, paint house 1 into color 0. Minimum cost: 3 + 2 = 5. 

思路

对第m层来说,只需要前m-1层染色代价的最小值和倒数第二小值,再记录前一层的颜色,然后这层其它颜色的代价和前m-1层最小值相加,和前一层重复的颜色和倒数第二小值相加,从中取最小值即可

代码如下,可以注意下更新最小值和第二小值代码,两次比较即可

int minCostII(vector<vector<int>>& costs) {
    if (costs.size() == 0 || costs[0].size() == 0) return 0;
    int min1 = 0, min2 = 0, idx = -1;
    for (int i = 0; i < costs.size(); i ++) {
        int mn1 = INT_MAX, mn2 = INT_MAX, temp, tempIdx;
        for (int j = 0; j < costs[0].size(); j ++) {
            temp = costs[i][j] + (j == idx ? min2 : min1);
            if (temp < mn1) {
                mn2 = mn1; mn1 = temp; tempIdx = j;
            }
            else if (temp < mn2) mn2 = temp;
        }
        min1 = mn1; min2 = mn2; idx = tempIdx;
    }
    return min1;
}

272. Closest Binary Search Tree Value II (Hard)

给一棵非空的二分搜索树以及一个target值,找k个二分搜索树中最接近这个值的节点

Input: root = [4,2,5,1,3], target = 3.714286, and k = 2
    4
   / \
  2   5
 / \
1   3
Output: [4,3]

思路

中序遍历,将小于target的放在一个栈里,大于target的放在一个队列里,然后比较两者的开头元素哪个小即可,代码如下

vector<int> closestKValues(TreeNode* root, double target, int k) {
    stack<int> ls;
    deque<int> rs;
    stack<TreeNode*> s;
    TreeNode* ptr = root;
    while (ptr != nullptr || !s.empty()) {
        while (ptr != nullptr) {
            s.push(ptr);
            ptr = ptr->left;
        }
        if (!s.empty()) {
            ptr = s.top();
            if (ptr->val < target) ls.push(ptr->val);
            else rs.push_back(ptr->val);
            s.pop();
            ptr = ptr->right;
        }
    }
    vector<int> result;
    while (result.size() < k && (rs.size() + ls.size() > 0)) {
        bool lflag = false, rflag = false;
        if (ls.empty()) rflag = true;
        else if (rs.empty()) lflag = true;
        else {
            double dis1 = (rs.front() - target) * (rs.front() - target);
            double dis2 = (ls.top() - target) * (ls.top() - target);
            if (dis1 < dis2) rflag = true;
            else lflag = true;
        }
        if (lflag) {
            result.push_back(ls.top());
            ls.pop();
        } else if (rflag) {
            result.push_back(rs.front());
            rs.pop_front();
        }
    }
    return result;
}

273. Integer to English Words (Hard)

将一个非负int整数转换为英文表示,只写数字,不考虑and

Input: 123
Output: "One Hundred Twenty Three"
Input: 1234567891
Output: "One Billion Two Hundred Thirty Four Million Five Hundred Sixty Seven Thousand Eight Hundred Ninety One"

思路

int最大正数2^{31}-1 \approx 2*10^9,所以考虑Billion, Million, Thousand这几个量级即可,每个量级上将1-999的数字表示出来,加上量级即可

注意需要对0做特判,写的时候要多注意细节

class Solution {
private:
    string digits[9] = {"One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"};
    string tens[10] = {"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
    string hs[8] = {"Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"};
    string parse(int num) {
        string res = "";
        if (num >= 100) res = " " + digits[num/100 - 1] + " Hundred";
        num %= 100;
        if (num >= 10) {
            if (num < 20) {
                res = res + " " + tens[num - 10];
                return res.substr(1);
            }
            else res += (" " + hs[num/10 - 2]);
        }
        num %= 10;
        if (num > 0) res += (" " + digits[num - 1]);
        return res.substr(1);
    }
public:
    string numberToWords(int num) {
        if (num == 0) return "Zero";
        string magnitude[4] = {"Billion", "Million", "Thousand", ""};
        int orders[4] = { 1000000000, 1000000, 1000, 1 };
        string res = "";
        int order = 1000000000;
        for (int i = 0; i < 4; i ++) {
            if (num >= orders[i]) {
                res += (" " + parse(num/orders[i]));
                if (i < 3) res += (" " + magnitude[i]);
                num %= orders[i];
            }
        }
        return res.substr(1);
    }
};

279. Perfect Squares (Medium)

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n.

Input: n = 12
Output: 3 
Explanation: 12 = 4 + 4 + 4.

类内部定义static

class A {
public:
    void m() {
        static int num = 0;
        num ++;
        cout << num << '\n';
    }
};

int main() {
    A a;
    a.m(); // 1
    a.m(); // 2
    a.m(); // 3
}

解法一 DP

dp[0-n],dp[i]表示数字n对应的最小平方数个数

int numSquares(int n) {
    static vector<int> dp = {0};
    while (dp.size() <= n) {
        int m = dp.size();
        int mx = m;
        for (int i = 1; i*i <= m; i ++)
            mx = min(mx, dp[m-i*i]+1);
        dp.push_back(mx);
    }
    return dp[n];
}

四平方和定理 每个正整数均可表示为4个整数的平方和


282. Expression Add Operators (Hard)

给一串数字,向其中加入+ - *运算符号,使得式子结果等于target

Input: num = "123", target = 6
Output: ["1+2+3", "1*2*3"] 

思路

深搜来遍历,这里需要注意的技巧是记录上一个+或-时加上的数,然后在遇到乘号时,先对应减或加上这个数,然后再乘,如下,记下-2,然后在遇到乘法时先加上2,然后进行之后的深搜遍历

1 - 2 = -1 prev = -2
1 - 2 * 3 = -1 + 2 - 2 * 3 = -5 
vector<string> result;
void helper(const string& num, int begin, int target, long value, long prevVal, string& s, int len) {
    if (begin == num.size()) {
        if (target == value) result.push_back(s.substr(0, len));
        return;
    }
    long numValue = 0;
    int opPos = len;
    if (begin > 0) len ++;
    for (int i = begin; i < num.size(); i ++) {
        if (num[begin] == '0' && i > begin) return;
        numValue *= 10; numValue += (num[i] - '0');
        s[len ++] = num[i];
        string substr = num.substr(begin, i - begin + 1);
        if (begin > 0) {
            s[opPos] = '+'; helper(num, i + 1, target, value + numValue, numValue, s, len);
            s[opPos] = '-'; helper(num, i + 1, target, value - numValue, -numValue, s, len);
            s[opPos] = '*'; helper(num, i + 1, target, value - prevVal + prevVal * numValue, prevVal * numValue, s, len);
        }
        else helper(num, i + 1, target, numValue, numValue, s, len);
    }
}
vector<string> addOperators(string num, int target) {
    string s(num.size() * 2, '\0');
    helper(num, 0, target, 0, 0, s, 0);
    return result;
}

283. Move Zeroes (Easy)

Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements.

Input: [0,1,0,3,12]
Output: [1,3,12,0,0]

思路

双指针,移动即可

void moveZeroes(vector<int>& nums) {
    if (nums.size() == 0) return;
    int zeroPtr = 0;
    while (zeroPtr < nums.size() && nums[zeroPtr] != 0) zeroPtr ++;
    for (int i = 0; i < nums.size() && zeroPtr < nums.size(); i ++)
        if (nums[i] != 0 && i > zeroPtr) {
            swap(nums[i], nums[zeroPtr]);
            while (nums[zeroPtr] != 0) zeroPtr ++;
        }
}

285. Inorder Successor in BST (Medium)

Given a binary search tree and a node in it, find the in-order successor of that node in the BST.

The successor of a node p is the node with the smallest key greater than p.val.

求二叉树中一个点的中序遍历后继

思路

用栈来进行中序遍历,发现栈顶元素为p时,将下一个元素返回即可

TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
    stack<TreeNode*> s;
    TreeNode *ptr = root;
    bool flag = false;
    while (ptr != nullptr || !s.empty()) {
        while (ptr != nullptr) {
            s.push(ptr);
            ptr = ptr->left;
        }
        if (!s.empty()) {
            ptr = s.top();
            s.pop();
            if (flag) return ptr;
            if (ptr == p) flag = true;
            ptr = ptr->right;
        }
    }
    return nullptr;
}

这里应该也可以用morris来遍历输出,但一直内存溢出,不知道为什么


286. Walls and Gates (Medium)

一个二维数组,有三种可能值

  • -1 表示墙或障碍物
  • 0 表示门
  • INF 即INT_MAX,表示空地

求空地到最近门的距离,如果到不了,继续填充INF即可

思路

从各个门处开始宽搜,更新距离即可

要注意已经搜过的元素不能再进入队列,这里判断元素的距离是否小于当前维护的距离即可,若小于不进行更新

void wallsAndGates(vector<vector<int>>& rooms) {
    vector<vector<int>> step = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    if (rooms.size() == 0 || rooms.size() == 0) return;
    int num = 0;
    for (int i = 0; i < rooms.size(); i ++) {
        for (int j = 0; j < rooms[0].size(); j ++) {
            if (rooms[i][j] != 0) continue;
            num ++;
            queue<pair<int, int>> q;
            q.push({i, j});
            int level = 0;
            while (!q.empty()) {
                level ++;
                int size = q.size();
                while (size --) {
                    auto& node = q.front();
                    for (auto& v : step) {
                        int newi = node.first + v[0], newj = node.second + v[1];
                        if (newi < 0 || newi == rooms.size() || newj < 0 || newj == rooms[0].size()) continue;
                        if (rooms[newi][newj] < level) continue;
                        rooms[newi][newj] = min(level, rooms[newi][newj]);
                        q.push({newi, newj});
                    }
                    q.pop();
                }
            }
        }
    }
}

287. Find the Duplicate Number (Medium)

n个数里只有一个重复,找到该数

位图

因为数组中数范围是1-n,所以可以对每个bit位,统计一下从1-n正常的bit个数,然后再比较一下nums的bit个数,当后者bit个数更多时,说明重复数该位为1

这个算法前提是数组中1-n都要出现,不过leetcode上可以通过 20ms(21.37%)

int findDuplicate(vector<int>& nums) {
    int res = 0;
    for (int i = 0; i < 32; i ++) {
        int bit = (1 << i), cnt1 = 0, cnt2 = 0;
        for (int j = 0; j < nums.size(); j ++) {
            if (j & bit) cnt1 ++;
            if (nums[j] & bit) cnt2 ++;
        }
        if (cnt2 > cnt1) res += bit;
    }
    return res;
}

链表环

很巧妙的方法,把nums[k]看做k的下一个节点,因为有循环,所以一定会有环,而且从0开始访问,不会进入自环中,所以等价于142. Linked List Cycle II (Medium)这道题,时间为 12ms(99.81%)

int findDuplicate(vector<int>& nums) {
    int fast = 0, slow = 0;
    while (true) {
        fast = nums[nums[fast]];
        slow = nums[slow];
        if (fast == slow) break;
    }
    slow = 0;
    while (slow != fast) {
        fast = nums[fast];
        slow = nums[slow];
    }
    return fast;
}

291. Word Pattern II (Hard)

给一个pattern串和一个str串,看str是否满足pattern的格式

Input: pattern = "abab", str = "redblueredblue"
Output: true

思路

还是深搜来遍历,不过可以利用单词长度剪枝,即每确定一个char-string的映射,计算下这样映射所得的字符串长度,看是否超过str长度

然后从pattern串开始,深搜来遍历所有组合

bool wordPatternMatch(string pattern, string str) {
    vector<int> cnt(26, 0);
    vector<string> dict(26, "");
    for (char p : pattern) cnt[p - 'a'] ++;
    unordered_set<string> words;
    return helper(pattern, 0, str, 0, cnt, dict, words, pattern.size());
}
bool helper(const string& pattern, int pp, const string& str, int sp, const vector<int>& cnt, vector<string>& dict, unordered_set<string>& words, int lowerLimit) {
    if (pp == pattern.size() && sp == str.size()) return true;
    if (pp == pattern.size() || sp == str.size()) return false;
    int rank = pattern[pp] - 'a';
    string& word = dict[rank];
    if (word.size() == 0) {
        for (int i = sp; i < str.size(); i ++) {
            word += str[i];
            if (words.count(word)) continue;
            int newLimit = lowerLimit + cnt[rank] * (word.size() - 1);
            if (newLimit > str.size()) break;
            words.insert(word);
            if (helper(pattern, pp + 1, str, i + 1, cnt, dict, words, newLimit)) return true;
            words.erase(word);
        }
        word = "";
        return false;
    } else {
        if (str.size() < word.size() + sp) return false;
        if (str.substr(sp, word.size()) != word) return false;
        if (helper(pattern, pp + 1, str, sp + word.size(), cnt, dict, words, lowerLimit)) return true;
    }
    return false;
}

297. Serialize and Deserialize Binary Tree (Hard)

Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.

思路

用一个queue来BFS即可,注意左右孩子必须同时加入,即使一个是NULL

class Codec {
public:
    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if (root == NULL) return "null";
        queue<TreeNode*> q;
        q.push(root);
        string s = "";
        int realNum = 1;
        while (!q.empty()) {
            TreeNode *node = q.front();
            q.pop();
            string v = (node != NULL) ? to_string(node->val) : "null";
            if (s == "") s = v;
            else s = s + "=" + v;
            if (node != NULL) {
                realNum --;
                q.push(node->left);
                q.push(node->right);
                if (node->left != NULL) realNum ++;
                if (node->right != NULL) realNum ++;
            }
            if (realNum == 0) break;
        }
        return s;
    }

    TreeNode* deserialize(string data) {
        if (data == "null") return NULL;
        int left = 0;
        string sub = data.substr(0, data.find_first_of('='));
        TreeNode *root = new TreeNode(stoi(sub));
        data = (data.size() > sub.size()) ? data.substr(sub.size() + 1) : "";
        queue<TreeNode*> q;
        q.push(root);
        while (data.size() > 0) {
            TreeNode* node = q.front(); q.pop();

            string sub = data.substr(0, data.find_first_of('='));
            TreeNode *node1 = NULL, *node2 = NULL;
            if (sub != "null") node1 = new TreeNode(stoi(sub));
            if (node1 != NULL) q.push(node1);
            if (node != NULL) node->left = node1;
            data = (data.size() > sub.size()) ? data.substr(sub.size() + 1) : "";
            if (data.size() == 0) break;

            sub = data.substr(0, data.find_first_of('='));
            if (sub != "null") node2 = new TreeNode(stoi(sub));
            if (node2 != NULL) q.push(node2);
            data = (data.size() > sub.size()) ? data.substr(sub.size() + 1) : "";
            if (node != NULL) node->right = node2;
            if (data.size() == 0) break;
        }
        return root;
    }
};

300. Longest Increasing Subsequence (Medium)

Given an unsorted array of integers, find the length of longest increasing subsequence.

求上升子序列

思路

用DP来做的话,O(N^2)时间内可以很简单做出来,即dp[i]表示以nums[i]结尾的最长上升子序列长度,对dp[i+1],从nums[0]到nums[i]比较一下,dp[i+1]=max(dp[j]+1) where nums[j]<nums[i+1]

下面算法是O(NlogN),具体思路为二分来更新序列,具体过程为维护一个vector,对num in nums

  • 如果num小于vector首元素,首元素改为num
  • 如果num大于vector尾元素,v.push_back(num)
  • 如果num在中间,找到不小于num的元素,将其改为num

这样的过程不太好理解,下面以[1,3,5,2,8,4,6]为例说明一下背后思想

在扫描到6时,上升序列有如下几种

1
1 2
1 2 4
1 3 4
1 3 5
1 3 6
1 3 5 8

注意只要末尾元素确定,那么1 2 41 3 4两个序列一样,而长度一样时,末尾元素小的一个序列严格优于大的那个,如1 3 41 3 5,接下来后者的扩展前者都可以做到,因此只看前者即可

所以vector任意前i位,维护的都是目前为止长度为i且末尾元素最小的序列,最后看vector长度即可

int lengthOfLIS(vector<int>& nums) {
    if (nums.size() < 2) return nums.size();
    vector<int> v;
    v.push_back(nums[0]);
    for (int num : nums) {
        if (num < v.front()) v[0] = num;
        else if (num > v.back()) v.push_back(num);
        else {
            int l = 0, r = v.size() - 1;
            while (l <= r) {
                int m = (l + r) / 2;
                if (v[m] >= num) r = m - 1;
                else l = m + 1;
            }
            v[l] = num;
        }
    }
    return v.size();
}