Monthly Archives: June 2017

LeetCode Add One Row to Tree

LeetCode Add One Row to Tree Given the root of a binary tree, then value v and depth d, you need to add a row of nodes with value v at the given depth d. The root node is at depth 1. The adding rule is: given a positive integer depth d, for each NOT null tree nodes N in depth d-1, create two tree nodes with value v as N's left subtree root and right subtree root. And N's original left subtree should be the left subtree of the new left subtree root, its original right subtree should be the right subtree of the new right subtree root. If depth d is 1 that means there is no depth d-1 at all, then create a tree node with value v as the new root of the whole original tree, and the original tree is the new root’s left subtree. Example 1:

Input:
A binary tree as following:
       4
     /   \
    2     6
   / \   /
  3   1 5
v = 1
d = 2
Output:
       4
      / \
     1   1
    /     \
   2       6
  / \     /
 3   1   5
Example 2:
Input:
A binary tree as following:
      4
     /
    2
   / \
  3   1
v = 1
d = 3
Output:
      4
     /
    2
   / \
  1   1
 /     \
3       1
Note:
  1. The given d is in range [1, maximum depth of the given tree + 1].
  2. The given binary tree has at least one tree node.

在二叉树深度为d的那一层增加一行值全为v的节点,如果d=1的话,新增一个值为v的根节点,原来的树作为新根节点的左子树。 简单题,层次遍历到深度为d-1时,对d-1层的所有节点,都增加值为v的左右孩子,如果d-1层的节点原来有左右孩子,则原来的左右孩子接到新加入的v的节点上,作为左右孩子。 完整代码如下: [cpp] class Solution { public: TreeNode* addOneRow(TreeNode* root, int v, int d) { if (d == 1) { TreeNode* newRoot = new TreeNode(v); newRoot->left = root; return newRoot; } queue<TreeNode*> tree; tree.push(root); int depth = 0; while (!tree.empty()) { ++depth; int n = tree.size(); if (depth == d – 1) { for (int i = 0; i < n; ++i) { TreeNode* p = tree.front(); tree.pop(); if (p == NULL)continue; TreeNode* l = new TreeNode(v); l->left = p->left; p->left = l; TreeNode* r = new TreeNode(v); r->right = p->right; p->right = r; } break; } else { for (int i = 0; i < n; ++i) { TreeNode* p = tree.front(); tree.pop(); if (p == NULL)continue; tree.push(p->left); tree.push(p->right); } } } return root; } }; [/cpp] 本代码提交AC,用时16MS。]]>

LeetCode Maximum Distance in Arrays

LeetCode Maximum Distance in Arrays Given m arrays, and each array is sorted in ascending order. Now you can pick up two integers from two different arrays (each array picks one) and calculate the distance. We define the distance between two integers a and b to be their absolute difference |a-b|. Your task is to find the maximum distance. Example 1:

Input:
[[1,2,3],
 [4,5],
 [1,2,3]]
Output: 4
Explanation:
One way to reach the maximum distance 4 is to pick 1 in the first or third array and pick 5 in the second array.
Note:
  1. Each given array will have at least 1 number. There will be at least two non-empty arrays.
  2. The total number of the integers in all the m arrays will be in the range of [2, 10000].
  3. The integers in the m arrays will be in the range of [-10000, 10000].

给定m个已从小到大排序的数组,要求从两个不同的数组中取出两个数,使得这两个数的差的绝对值最大,问最大的差的绝对值是多少。 因为数组已排序,所以最大的差值肯定等于某个数组的最大值和另一个数组的最小值的差。这里唯一需要注意的是两个数必须选自两个不同的数组。 我开始的做法是这样的,把所有数组的最大值和最小值都拿出来,然后重新排个序。在新数组中,如果最大值和最小值来自不同的数组,则他们的差就是最终结果。加入最大值和最小值是来自同一个数组的,则求最大值和次小值的差以及次大值和最小值的差中的最大值。因为最大值和最小值来自同一个数组,所以最大值和次小值以及次大值和最小值肯定不是来自同一个数组的。 代码如下: [cpp] class Solution { struct P { int idx; int value; P(int i, int v) :idx(i), value(v) {}; bool operator<(const P& p) { return this->value < p.value; } }; public: int maxDistance(vector<vector<int>>& arrays) { vector<P> vp; for (int i = 0; i < arrays.size(); ++i) { vp.push_back({ i,arrays[i][0] }); vp.push_back({ i,arrays[i][arrays[i].size() – 1] }); } sort(vp.begin(), vp.end()); int ans = 0, n = vp.size(); if (vp[0].idx != vp[n – 1].idx)return abs(vp[n – 1].value – vp[0].value); return max(abs(vp[n – 1].value – vp[1].value), abs(vp[n – 2].value – vp[0].value)); } }; [/cpp] 本代码提交AC,用时26MS。因为要对m个数组的最大值和最小值排序,所以时间复杂度为O((2m)lg(2m))。 讨论区还有一种解法,只需要O(m)的复杂度。我们遍历每个数组,记录之前所有数组的最小值的最小值以及最大值的最大值,然后用当前数组的最大值和最小值减去之前所有数组中的最小值的最小值以及最大值的最大值,求绝对值,更新结果。这样就作差的两个数肯定来自不同的数组,避免了之前的问题。 代码如下: [cpp] class Solution { public: int maxDistance(vector<vector<int>>& arrays) { int ans = INT_MIN; int curMin = arrays[0][0], curMax = arrays[0][arrays[0].size() – 1]; for(int i = 1; i < arrays.size(); ++i) { ans = max(ans, abs(arrays[i][0] – curMax)); ans = max(ans, abs(arrays[i][arrays[i].size() – 1] – curMin)); curMin = min(curMin, arrays[i][0]); curMax = max(curMax, arrays[i][arrays[i].size() – 1]); } return ans; } }; [/cpp] 本代码提交AC,用时22MS。]]>

LeetCode LRU Cache

146. LRU Cache

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.

get(key) – Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) – Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

The cache is initialized with a positive capacity.

Follow up:
Could you do both operations in O(1) time complexity?

Example:

LRUCache cache = new LRUCache( 2 /* capacity */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.put(4, 4);    // evicts key 1
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4

实现一个LUR cache。 我们知道数据的访问都有时间局部性和空间局部性,所以为了加快访问速度,把经常访问的数据放到高速缓存cache中,这样先查cache,不命中再查内存或者外存。 但是cache一般都很小,当新数据需要插入cache时,如果cache满,则需要替换掉cache中旧的元素。此时替换哪个元素就有很多策略了,可以把最久没访问的替换出去,也可以把访问频率最低的替换出去,还可以把最先进入的替换出去等等,各种策略就形成了不同的缓存替换算法。 LRU的意思是Least Recently Used (LRU),即把最近最久没访问的元素替换出去,是指时间上最久没访问的,而不是频率上访问最少的哦。 题目要求实现LRU的两个接口,分别是

  1. get(key),从LRU中获取key对应的value,如果LRU中不存在key,则返回-1
  2. put(key,value),把key和对应的value插入到LRU中,如果LRU满,则要替换掉最近最久没访问的元素

要求这两个操作都在O(1)时间内完成。 我们来分析一下应该用哪些数据结构使这两个操作都在O(1)时间内完成。首先get需要在O(1)时间内判断key是否在LRU中,所以可能需要用hash来存储<key,value>对。其次,需要缓存替换时,要在O(1)时间内把最久没访问的<key,value>删掉,同时插入新的<key,value>,删除和插入能在O(1)时间内完成的就是链表了。

综合上面的分析,我们可以用unordered_map+list来实现LRU,STL的list是一个双向链表。 参考这份代码。我们使用list<int> used来保存当前在LRU中的那些key,注意是key;用unordered_map<int, pair<int, list<int>::iterator>> cache来保存真实的缓存,cache的key就是LRU中现存的key,value是一个pair,保存了key对应的value,以及这个key在链表中的迭代器(指针)。

两个操作的实现如下:

put(key,value)。每当有新<key,value>进入时,如果key已经在cache中,则只需要更新key对应的value,同时,刚访问的元素下次很可能再次访问,所以需要把这个key提到链表的表头(为了不让这个key沉到表尾)。如果key不在cache中,但cache已满,则需要把链表尾部的key对应的数据在cache中删掉,同时也需要在链表中删掉这个尾节点;如果cache不满,则把新key插入到链表表头,同时把<key,value>插入到cache中,且key在链表中的位置是list.begin()。

get(key)。如果key不在cache中,直接返回-1。如果key在cache中,直接从cache中得到对应的value,同时根据迭代器,把key提到链表表头。 根据上述算法描述,我们需要抽象一个函数,moveToHead(list<int>::iterator &it),即把迭代器it指向的那个key移到表头,包括两个操作,把it从it位置删掉,把it的值插到表头。

完整代码如下:

class LRUCache {
private:
    unordered_map<int, pair<int, list<int>::iterator> > cache;
    list<int> used;
    int _capacity; // 把it节点**剪切**到表头
    void moveToHead(list<int>::iterator& it)
    {
        int key = *it;
        used.erase(it);
        used.push_front(key);
        cache[key].second = used.begin();
    }
public:
    LRUCache(int capacity)
        : _capacity(capacity)
    {
    }
    int get(int key)
    {
        if (cache.find(key) == cache.end())
            return -1;
        int value = cache[key].first;
        moveToHead(cache[key].second);
        return value;
    }
    void put(int key, int value)
    {
        if (cache.find(key) != cache.end()) {
            cache[key].first = value;
            moveToHead(cache[key].second);
        }
        else {
            if (used.size() == _capacity) {
                cache.erase(used.back());
                used.pop_back();
            }
            used.push_front(key);
            cache[key] = { value, used.begin() };
        }
    }
};

本代码提交AC,用时96MS。

我们还可以自己实现list,就是一个双向链表。这个双向链表包括头结点head和尾节点tail。为了插入删除的方便,我们设置了一个用不到的head和tail,所以真正的数据其实是在head和tail之间的,如下,节点1~3才是真正的数据节点

头 --> 节 --> 节 --> 节 --> 尾
节     点     点     点     节
点 <-- 1  <-- 2 <-- 3  <-- 点

这里我们抽象出了3个函数,removeFromList和moveToHead完成上一种实现中的moveToHead功能,即先把节点从原链表中删除(相当于摘下来),然后把这个节点安放到表头(上图节点1)。removeTail就是在需要替换时,删除表尾节点(上图节点3)。

完整代码如下:

class LRUCache {
private:
    struct Node {
        int key, value;
        Node *pre, *next;
        Node(int k, int v)
            : key(k)
            , value(v)
            , pre(NULL)
            , next(NULL){};
    };
    unordered_map<int, Node*> cache;
    Node *head, *tail;
    int _capacity; // 从原链表中删除节点node(摘下)
    void removeFromList(Node* node)
    {
        node->pre->next = node->next;
        node->next->pre = node->pre;
    }
    // 把节点node放到“表头”(安放)
    void moveToHead(Node* node)
    {
        node->next = head->next;
        head->next->pre = node;
        node->pre = head;
        head->next = node;
    }
    // 删除“尾节点”
    void removeTail()
    {
        Node* target = tail->pre;
        target->pre->next = target->next;
        target->next->pre = target->pre;
        delete target;
    }

public:
    LRUCache(int capacity)
        : _capacity(capacity)
    {
        head = new Node(0, 0); // 辅助头结点
        tail = new Node(0, 0); // 辅助尾节点
        head->next = tail;
        tail->pre = head;
    }
    int get(int key)
    {
        if (cache.find(key) == cache.end())
            return -1;
        int value = cache[key]->value;
        removeFromList(cache[key]);
        moveToHead(cache[key]);
        return value;
    }
    void put(int key, int value)
    {
        if (cache.find(key) != cache.end()) {
            cache[key]->value = value;
            removeFromList(cache[key]);
            moveToHead(cache[key]);
        }
        else {
            if (cache.size() == _capacity) {
                cache.erase(tail->pre->key);
                removeTail();
            }
            Node* node = new Node(key, value);
            cache[key] = node;
            moveToHead(node);
        }
    }
};

本代码提交AC,用时98MS。

LeetCode Lowest Common Ancestor of a Binary Tree

LeetCode Lowest Common Ancestor of a Binary Tree Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w as descendants (where we allow a node to be a descendant of itself).”

        _______3______
       /              \
    ___5__          ___1__
   /      \        /      \
   6      _2       0       8
         /  \
         7   4
For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3. Another example is LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.
给定一棵二叉树和两个节点p,q,问p和q的最近公共祖先节点是哪个。LCA问题。 思路:要找p和q的最近公共祖先,很直观的方法是找到从根节点分别到p和q的路径,然后把其中一条路径(比如根到p)上的点hash成path1,从另一条路径(比如根到q)的底端(q)往根节点走,把走过的每个点去path1中找,如果找到,则就是他们的LCA,因为这是距离q最近、且在path1中的点。 但是怎样找到根到p和q的路径呢。这里换一种思路,定义如下新的数据结构,par表示该节点的父节点,根节点的父节点为-1。 [cpp] struct MyNode { TreeNode* node; int par; MyNode(TreeNode* n_, int p_) :node(n_), par(p_) {}; }; [/cpp] 先整体把树层次遍历一遍,成为一个保存MyNode的数组,记录每个点的父节点在数组中的下标。在遍历的同时,记录下p和q节点的下标。这样通过下标往前递归就可以找到p和q到根节点的路径了。 找到路径之后,对其中一条路径hash,另一条路径在该hash中找。代码如下: [cpp] class Solution { private: struct MyNode { TreeNode* node; int par; MyNode(TreeNode* n_, int p_) :node(n_), par(p_) {}; }; public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { vector<MyNode> nodes; int i = 0, j = 0; queue<MyNode> tree; tree.push({ root,-1 }); while (!tree.empty()) { MyNode cur = tree.front(); tree.pop(); if (cur.node == NULL) continue; if (cur.node == p)i = nodes.size(); if (cur.node == q)j = nodes.size(); tree.push({ cur.node->left,nodes.size() }); tree.push({ cur.node->right,nodes.size() }); nodes.push_back(cur); } if (i == j)return nodes[i].node; unordered_set<int> path1; while (i != -1) { path1.insert(i); i = nodes[i].par; } while (j != -1) { if (path1.find(j) != path1.end()) { return nodes[j].node; } j = nodes[j].par; } return NULL; } }; [/cpp] 本代码提交AC,用时19MS。 讨论区还有一种递归的解法,很有意思。我们在以root为根的树中找p和q的LCA,如果root等于p或q其中之一,说明p或q就是他们的LCA。否则分别在左子树和右子树中递归的找LCA,如果发现在左右子树中找到的LCA都不为null,说明他们p和q分别位于root的两个子树,类似样例中的5和1,则root就是他们的LCA。如果某个子树返回的LCA为空,说明p和q都不在这个子树中,则先遇到谁,谁就是LCA,比如样例中的5和4,都不在3的右子树,在递归3的左子树的时候,先遇到了5,所以5是LCA。 完整代码如下: [cpp] class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if (root == NULL || root == p || root == q)return root; TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q); if (left != NULL&&right != NULL)return root; return left != NULL ? left : right; } }; [/cpp] 本代码提交AC,用时23MS。]]>

LeetCode Integer Replacement

LeetCode Integer Replacement Given a positive integer n and you can do operations as follow:

  1. If n is even, replace n with n/2.
  2. If n is odd, you can replace n with either n + 1 or n - 1.
What is the minimum number of replacements needed for n to become 1? Example 1:
Input:
8
Output:
3
Explanation:
8 -> 4 -> 2 -> 1
Example 2:
Input:
7
Output:
4
Explanation:
7 -> 8 -> 4 -> 2 -> 1
or
7 -> 6 -> 3 -> 2 -> 1

给定一个数n,不断对n进行如下两种操作中的一种
  1. 如果n是偶数,把n变成n/2
  2. 如果n是奇数,把n变成n+1或者n-1
问要把n变为1,需要的最少的变换次数是多少。 为啥我首先想到的是BFS。。。这不就相当于每个点根据情况可以走不同的方向,问走到终点1需要的最少步数吗?很像BFS呀。代码如下: [cpp] class Solution { private: struct P { long long val; int step; P(long long v_, int s_) :val(v_), step(s_) {}; }; public: int integerReplacement(int n) { P p(n, 0); queue<P> qp; qp.push(p); while (!qp.empty()) { P f = qp.front(); qp.pop(); if (f.val == 1)return f.step; else if (f.val & 1) { qp.push(P(f.val + 1, f.step + 1)); qp.push(P(f.val – 1, f.step + 1)); } else { qp.push(P(f.val / 2, f.step + 1)); } } return 0; } }; [/cpp] 本代码提交AC,用时6MS,只击败5%的人。。。 看讨论区,用位运算求解。要想快速的到达1,则n的二进制中0越多越好,因为0越多,后续越有可能用n/2快速的去掉一位。所以如果n是奇数时,我们判断一下n+1和n-1哪个包含的0越多,就走哪步。 但是每次都需要求n+1和n-1中1的个数,复杂度有点高。 其实,我们只需要关注n的最后两个二进制位即可。任何一个数的二进制尾数最后两位可能有四种情况,00、01、10、11,如果n是偶数,即以00或10结尾,则显然n/2能快速的减少一位。但是如果是01或者11呢。如果是01,则减1会变成00,如果加1,会变成10。显然,减1之后多出一个0,而加1之后0的个数没变,所以如果是以01结尾,则减1更优。 如果末尾是11,则减1变成10,加1变成100,显然加1更优。 有一个例外是,如果n就等于3,即二进制为11,则11→10→1比11→100→10→1更优,即当n==3时,减1更好。 完整代码如下: [cpp] class Solution { public: int integerReplacement(int n) { if (n == INT_MAX)return 32; int ans = 0; while (n > 1) { if ((n & 1) == 0) n >>= 1; else if (n == 3 || ((n >> 1) & 1) == 0)–n; else ++n; ++ans; } return ans; } }; [/cpp] 本代码提交AC,用时3MS。]]>

LeetCode Longest Uncommon Subsequence II

LeetCode Longest Uncommon Subsequence II Given a list of strings, you need to find the longest uncommon subsequence among them. The longest uncommon subsequence is defined as the longest subsequence of one of these strings and this subsequence should not be any subsequence of the other strings. A subsequence is a sequence that can be derived from one sequence by deleting some characters without changing the order of the remaining elements. Trivially, any string is a subsequence of itself and an empty string is a subsequence of any string. The input will be a list of strings, and the output needs to be the length of the longest uncommon subsequence. If the longest uncommon subsequence doesn’t exist, return -1. Example 1:

Input: "aba", "cdc", "eae"
Output: 3
Note:
  1. All the given strings’ lengths will not exceed 10.
  2. The length of the given list will be in the range of [2, 50].

给定一个字符串数组,问这些字符串的最长非公共子序列的长度是多少。非公共子序列是指任意一个字符串的子序列,不是其他所有字符串的子序列。要求这样的子序列的最大长度。 首先非公共子序列不可能来自那些出现过多次的字符串,比如数组中有两个字符串都是”abcd”,那么其中一个”abcd”的任意一个子序列,必定是另一个”abcd”的子序列,不满足非公共子序列的定义。所以我们首先对所有字符串及其出现频率hash一下,只考察那些出现一次的字符串的子序列。 另一个观察是,如果存在非公共子序列,则最长的非公共子序列肯定是某个完整的字符串,这一点从LeetCode Longest Uncommon Subsequence I可知。 所以,我们首先把字符串按长度从大到小排序,排序完之后,遍历每个字符串s,如果s出现的频率大于1,直接不考虑;否则,采用贪心策略,我们在比s更长的那些字符串中判断s是否是他们的子序列,如果是,s不是非公共子序列;否则s是非公共子序列,因为后面都是比s更短的字符串,s肯定也不是他们的子序列,又我们根据字符串长度排序了。所以s的长度就是非公共子序列的最大长度。 完整代码如下: [cpp] class Solution { private: // 判断child是否是par的子序列 bool isSubSeq(const string& par, const string& child) { int i = 0, j = 0, m = par.size(), n = child.size(); while (i < m&&j < n) { if (par[i] == child[j])++j; ++i; } return j == n; } public: int findLUSlength(vector<string>& strs) { unordered_map<string, int> hash; for (const auto& s : strs)++hash[s]; sort(strs.begin(), strs.end(), [](const string& s1, const string& s2) {return s1.size() > s2.size(); }); for (int i = 0; i < strs.size(); ++i) { if (hash[strs[i]] > 1)continue; bool isSub = false; for (int j = i – 1; j >= 0; –j) { if (isSubSeq(strs[j], strs[i])) { isSub = true; break; } } if (!isSub)return strs[i].size(); } return -1; } }; [/cpp] 本代码提交AC,用时3MS。 参考:http://bookshadow.com/weblog/2017/04/03/leetcode-longest-uncommon-subsequence-ii/]]>

LeetCode Minimum Size Subarray Sum

209. Minimum Size Subarray Sum 209. Minimum Size Subarray Sum

Given an array of n positive integers and a positive integer s, find the minimal length of a contiguous subarray of which the sum ≥ s. If there isn’t one, return 0 instead.

Example: 

Input: s = 7, nums = [2,3,1,2,4,3]
Output: 2
Explanation: the subarray [4,3] has the minimal length under the problem constraint.

Follow up:If you have figured out the O(n) solution, try coding another solution of which the time complexity is O(n log n).  209. Minimum Size Subarray Sum


给定一个正整数数组和一个数s,要求从数组中找出最短的子串,使得子串的和大于等于s。
解法1,双指针法,O(n)。维护两个指针left和right,初始时都为0,我们的目标就是使[left,right)的和大于等于s。所以先right右移,直到和大于等于s,此时尝试缩小区间,即left也右移,在右移的过程中,更新最短子串长度,且累加和要减去left指向的数。这样不断的先移right,后移left,并更新最短长度。

代码如下,非常简洁易懂。

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums)
    {
        int n = nums.size(), left = 0, right = 0, sum = 0, ans = INT_MAX;
        while (right < n) {
            while (right < n && sum < s)
                sum += nums[right++];
            while (left <= right && sum >= s) {
                ans = min(ans, right – left);
                sum -= nums[left++];
            }
        }
        return ans == INT_MAX ? 0 : ans;
    }
};

本代码提交AC,用时13MS。
解法2,二分查找,O(nlgn)。我们先想想暴力方法,枚举每个left,从left开始枚举right,直到累加和第一次超过sum,更新最短长度。这样两层循环,时间复杂度是O(n^2)的。有没有办法把内层查找right的时间复杂度降低呢?
遇到子数组和的问题,马上要想到借助累加和数组。所以我们先求到原数组的累加和数组accuSum[n+1],其中accuSum[i]等于原数组中前i-1个数之和。我们利用accuSum来循环right,对于每个left,它要找一个right,使得left和right之间的和大于等于s,也就相当于在accuSum中找一个right,使得accuSum[right]>=accuSum[left]+s,等价于accuSum[right]-accuSum[left]>=s,即left到right的累加和大于等于s。因为原数组是正整数数组,所以accuSum是递增有序的,所以我们可以在accuSum中二分查找。即查找right的复杂度降为了O(lgn),所以总的复杂度就是O(nlgn)了。
完整代码如下:

class Solution {
private:
    int searchRight(vector<int>& accuSum, int l, int r, int target)
    {
        while (l <= r) {
            int m = l + (r – l) / 2;
            if (accuSum[m] == target)
                return m;
            else if (accuSum[m] > target)
                r = m – 1;
            else
                l = m + 1;
        }
        return l;
    }
public:
    int minSubArrayLen(int s, vector<int>& nums)
    {
        int n = nums.size(), ans = INT_MAX;
        vector<int> accuSum(n + 1, 0);
        for (int i = 1; i <= n; ++i)
            accuSum[i] = accuSum[i – 1] + nums[i – 1];
        for (int left = 0; left < n; ++left) {
            int right = searchRight(accuSum, left, accuSum.size() – 1, accuSum[left] + s);
            if (right == n + 1)
                break;
            ans = min(ans, right – left);
        }
        return ans == INT_MAX ? 0 : ans;
    }
};

本代码提交AC,用时16MS。]]>

LeetCode Find K Pairs with Smallest Sums

LeetCode Find K Pairs with Smallest Sums You are given two integer arrays nums1 and nums2 sorted in ascending order and an integer k. Define a pair (u,v) which consists of one element from the first array and one element from the second array. Find the k pairs (u1,v1),(u2,v2) …(uk,vk) with the smallest sums. Example 1:

Given nums1 = [1,7,11], nums2 = [2,4,6],  k = 3
Return: [1,2],[1,4],[1,6]
The first 3 pairs are returned from the sequence:
[1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]
Example 2:
Given nums1 = [1,1,2], nums2 = [1,2,3],  k = 2
Return: [1,1],[1,1]
The first 2 pairs are returned from the sequence:
[1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]
Example 3:
Given nums1 = [1,2], nums2 = [3],  k = 3
Return: [1,3],[2,3]
All possible pairs are returned from the sequence:
[1,3],[2,3]

给定两个从小到大排列的数组,要求选出k个数对(u_i,v_i),其中u_i来自第一个数组nums1,v_i来自第二个数组v_i,使得这k个数对的和最小。 要使得k各数对的和最小,那么这k个数对本身肯定是前k个最小的。遇到要选前k个最小或者最大的问题,一般都用优先队列(堆)来做,比如维护一个大小为k的大顶堆,当堆中元素大于k时,弹出堆顶的最大的元素,也就是堆中维护的一直是当前最小的前k个元素。当所有元素都遍历完时,返回堆中所有元素即可。 优先队列采用STL中的priority_queue,需要自定义比较运算符。 完整代码如下: [cpp] class Solution { private: struct P { int x, y; P(int x_, int y_) :x(x_), y(y_) {}; bool operator<(const P& p) const{ return (this->x + this->y) < (p.x + p.y); } bool operator>(const P& p) const { return (this->x + this->y) > (p.x + p.y); } }; public: vector<pair<int, int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) { priority_queue<P> pq; // 大顶堆 for (int i = 0; i < nums1.size(); ++i) { for (int j = 0; j < nums2.size(); ++j) { P p(nums1[i], nums2[j]); pq.push(p); if (pq.size() > k)pq.pop(); // 弹出最大元素 } } vector<pair<int, int>> ans; while (!pq.empty()) { // 保留的都是最小元素 ans.push_back(pair<int, int>(pq.top().x, pq.top().y)); pq.pop(); } return ans; } }; [/cpp] 本代码提交AC,用时99MS。]]>

LeetCode Course Schedule

207. Course Schedule

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

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

Example 1:

Input: numCourses = 2, prerequisites = [[1,0]]
Output: true
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0. So it is possible.

Example 2:

Input: numCourses = 2, prerequisites = [[1,0],[0,1]]
Output: false
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0, and to take course 0 you should
             also have finished course 1. So it is impossible.

Constraints:

  • The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
  • You may assume that there are no duplicate edges in the input prerequisites.
  • 1 <= numCourses <= 10^5

排课表问题,一看就是拓扑排序的题。
之前介绍过,拓扑排序有两种解法,一种是类似BFS的Kahn算法,另一种是DFS算法,具体可以参考维基百科的伪代码
解法1:Kahn算法。基本思想,首先把所有入度为0的点放到集合S中(表示这些课没有先修课程,可以首先完成),然后不断从S中取点x,把x指向的所有的点y的边(x,y)删掉,如果删掉之后,y的入度也变为0了,把y也加入到S中。当S为空时,如果图中还有边没删掉,说明形成环了,否则是一个DAG。
想象一下图中的一个环,如同时有边(x,y)和(y,x),则x和y因为都有入度,说明都不可能加入到集合S中,所以这两条边永远都删不掉。
Kahn的算法很好理解,代码类似于BFS,如下:

class Solution {
public:
    bool canFinish(int numCourses, vector<pair<int, int> >& prerequisites)
    {
        int numEdges = prerequisites.size();
        vector<set<int> > pre(numCourses, set<int>()), next(numCourses, set<int>());
        for (const auto& p : prerequisites) {
            pre[p.first].insert(p.second);
            next[p.second].insert(p.first);
        }
        queue<int> Q;
        for (int i = 0; i < numCourses; ++i) {
            if (pre[i].size() == 0)
                Q.push(i);
        }
        while (!Q.empty()) {
            int node = Q.front();
            Q.pop();
            for (const auto& n : next[node]) {
                pre[n].erase(node);
                –numEdges;
                if (pre[n].size() == 0)
                    Q.push(n);
            }
        }
        return numEdges == 0;
    }
};

本代码提交AC,用时15MS。
解法2:DFS算法。还可以用DFS求解,基本思想是,维护两个访问标记数组,visited[i]表示全局是否被访问数组,相当于维基百科中的permanently标记,onpath[i]表示当前DFS路径是否被访问数组,相当于维基百科中的temporarily标记。
对于所有visited[i]没访问的点i,我们DFS它。把从i开始DFS到的点标记为onpath[j],如果在DFS的过程中访问到了onpath[k]被标记过的点,说明这条路形成了环,返回false,不是DAG。否则,如果visited[node]==1,说明node是DFS之前的点时被访问过,而之前的DFS返回了true,说明从node DFS的结果是不会形成环的,可以直接返回true。否则如果visited[node]==0,我们从node继续DFS,具体步骤是,首先标记onpath[node]=1,然后把node指向的所有点都DFS一遍,如果都没发现换,则永久标记visited[node]=1,说明从node这个点DFS是没问题的,不会形成环的;同时复位onpath[node]=0,以便下次DFS使用。
完整代码如下,实现方法完全参考维基百科中的DFS版本。

class Solution {
private:
    bool dfs(vector<int>& visited, vector<int>& onpath, vector<vector<int> >& graph, int node)
    {
        if (onpath[node] == 1)
            return false;
        if (visited[node] == 0) {
            onpath[node] = 1;
            for (int i = 0; i < graph.size(); ++i) {
                if (graph[node][i] == 1) {
                    if (!dfs(visited, onpath, graph, i))
                        return false;
                }
            }
            visited[node] = 1;
            onpath[node] = 0;
            return true;
        }
        return true;
    }

public:
    bool canFinish(int numCourses, vector<pair<int, int> >& prerequisites)
    {
        vector<vector<int> > graph(numCourses, vector<int>(numCourses, 0));
        for (const auto& p : prerequisites)
            graph[p.second][p.first] = 1;
        vector<int> visited(numCourses, 0), onpath(numCourses, 0); // visited==permanently; onpath=temporarily
        for (int i = 0; i < numCourses; ++i) {
            if (visited[i] == 0) {
                if (!dfs(visited, onpath, graph, i))
                    return false;
            }
        }
        return true;
    }
};

本代码提交AC,用时62MS,比Kahn慢好多。

二刷。上述解法也太复杂了,直接循环把入度为0的删掉就行了,完整代码如下:

class Solution {
public:
	bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
		vector<vector<int>> graph(numCourses, vector<int>(numCourses, 0));
		vector<int> indegree(numCourses, 0);
		for (int i = 0; i < prerequisites.size(); ++i) {
			graph[prerequisites[i][1]][prerequisites[i][0]] = 1;
			++indegree[prerequisites[i][0]];
		}
		int finish = 0;
		while (true) {
			bool found = false;
			for (int i = 0; i < numCourses; ++i) {
				if (indegree[i] == 0) {
					--indegree[i];
					++finish;
					found = true;
					for (int j = 0; j < numCourses; ++j) {
						if (graph[i][j] == 1) {
							graph[i][j] = 0;
							--indegree[j];
						}
					}
				}
			}
			if (!found)break;
		}
		return finish == numCourses;
	}
};

本代码提交AC,用时76MS。

LeetCode Add Bold Tag in String

LeetCode Add Bold Tag in String Given a string s and a list of strings dict, you need to add a closed pair of bold tag <b> and </b> to wrap the substrings in s that exist in dict. If two such substrings overlap, you need to wrap them together by only one pair of closed bold tag. Also, if two substrings wrapped by bold tags are consecutive, you need to combine them. Example 1:

Input:
s = "abcxyz123"
dict = ["abc","123"]
Output:
"<b>abc</b>xyz<b>123</b>"
Example 2:
Input:
s = "aaabbcc"
dict = ["aaa","aab","bc"]
Output:
"<b>aaabbc</b>c"
Note:
  1. The given dict won’t contain duplicates, and its length won’t exceed 100.
  2. All the strings in input have length in range [1, 1000].

给定一个字符串s和一个字典dict,如果dict中的单词是s的子串,则要对这个子串加<b></b>的标签。如果有多个重叠的子串都需要加标签,则这些标签需要合并。比如样例2中,子串aaa、aab和bc重叠了,则他们的标签合并,最终结果就是<b>aaabbc</b>c。 这一题其实比第三题要简单。 因为有多个子串可能重叠,为了方便,设置一个长度和s一样的数组flag,flag[i]=1表示第i位需要被标签包围,flag[i]=0表示不需要被标签包围。 把dict中的每个字符串,去s中找,判断是否是s的子串,如果是,则把子串对应的flag位置1。当dict中的所有字符串都查完了。最后遍历flag,把连续1对应的子串加上标签就好了。 这里字符串匹配查找我直接用stl的find,自己写kmp也是可以的。完整代码如下: [cpp] class Solution { public: string addBoldTag(string s, vector<string>& dict) { int n = s.size(); string flag(n, ‘0’); for (const auto& sub : dict) { size_t pos = s.find(sub, 0); while (pos != string::npos) { fill(flag.begin() + pos, flag.begin() + pos + sub.size(), ‘1’); pos = s.find(sub, pos + 1); } } string ans = ""; int i = 0, j = 0; while (flag[i] == ‘0’)++i; ans += s.substr(0, i); while (i < n) { int j = i + 1; while (j < n&&flag[j] == ‘1’)++j; ans += "<b>" + s.substr(i, j – i) + "</b>"; if (j >= n)break; i = j; while (j < n&&flag[j] == ‘0’)++j; ans += s.substr(i, j – i); i = j; } return ans; } }; [/cpp] 本代码提交AC,用时22MS。]]>