Author Archives: admin

LeetCode Maximum Average Subarray I

LeetCode Maximum Average Subarray I Given an array consisting of n integers, find the contiguous subarray of given length k that has the maximum average value. And you need to output the maximum average value. Example 1:

Input: [1,12,-5,-6,50,3], k = 4
Output: 12.75
Explanation: Maximum average is (12-5-6+50)/4 = 51/4 = 12.75
Note:
  1. 1 <= k <= n <= 30,000.
  2. Elements of the given array will be in the range [-10,000, 10,000].

给定一个数组,问长度为k的子数组的最大平均数是多少。简单题,因为平均数等于和除以k,k相同,也就相当于求长度为k的最长连续子串的和。 解法是维护一个长度为k的滑动窗口,求滑动窗口内的最大连续子数组的和,然后除以k。 代码如下: [cpp] class Solution { public: double findMaxAverage(vector<int>& nums, int k) { int n = nums.size(); if (n < k)return 0; int i = 0, sum = 0, ans = INT_MIN; for (int j = 0; j < k; ++j)sum += nums[j]; for (int j = k; j < n; ++j) { ans = max(ans, sum); sum += nums[j] – nums[i]; ++i; } ans = max(ans, sum); return ans / double(k); } }; [/cpp] 本代码提交AC,用时176MS。]]>

LeetCode Shopping Offers

LeetCode Shopping Offers In LeetCode Store, there are some kinds of items to sell. Each item has a price. However, there are some special offers, and a special offer consists of one or more different kinds of items with a sale price. You are given the each item’s price, a set of special offers, and the number we need to buy for each item. The job is to output the lowest price you have to pay for exactly certain items as given, where you could make optimal use of the special offers. Each special offer is represented in the form of an array, the last number represents the price you need to pay for this special offer, other numbers represents how many specific items you could get if you buy this offer. You could use any of special offers as many times as you want. Example 1:

Input: [2,5], [[3,0,5],[1,2,10]], [3,2]
Output: 14
Explanation:
There are two kinds of items, A and B. Their prices are $2 and $5 respectively.
In special offer 1, you can pay $5 for 3A and 0B
In special offer 2, you can pay $10 for 1A and 2B.
You need to buy 3A and 2B, so you may pay $10 for 1A and 2B (special offer #2), and $4 for 2A.
Example 2:
Input: [2,3,4], [[1,1,0,4],[2,2,1,9]], [1,2,1]
Output: 11
Explanation:
The price of A is $2, and $3 for B, $4 for C.
You may pay $4 for 1A and 1B, and $9 for 2A ,2B and 1C.
You need to buy 1A ,2B and 1C, so you may pay $4 for 1A and 1B (special offer #1), and $3 for 1B, $4 for 1C.
You cannot add more items, though only $9 for 2A ,2B and 1C.
Note:
  1. There are at most 6 kinds of items, 100 special offers.
  2. For each item, you need to buy at most 6 of them.
  3. You are not allowed to buy more items than you want, even if that would lower the overall price.

给定每个商品的单价和需要购买的数量,并且有一些special offer,相当于捆绑销售的优惠套餐。问刚好买到给定数量的商品,所花的最低费用是多少。 注意给定的限制条件中商品最多只有6种,且每件最多只购买6件,所以可以考虑用暴力方法。 把special offer看成一个背包问题里的“商品”,对于每个special offer,我们有两种选择,可以用或者不用。如果需要的needs数组每一项都大于等于某个special offer的每一项,即可以用该special offer,我们比较用和不用该special offer的最终花费,取花费低的。如果用该special offer,则needs数组需要减掉sp捆绑的每件商品的件数,然后继续递归该sp是否可用,相当于完全背包,不计件数的。如果该sp不能用,则递归考虑下一个sp。最后如果递归考虑完所有sp了,则剩余的商品只能按原价购买,算出按原价购买的费用即可。 完整代码如下: [cpp] class Solution { private: int dot(vector<int>& price, vector<int>& needs) { int ans = 0; for (int i = 0; i < needs.size(); ++i) { ans += price[i] * needs[i]; } return ans; } int shopping(vector<int>& price, vector<vector<int>>& special, vector<int>& needs, int idx) { if (idx == special.size())return dot(price, needs); // 原价购买 int i = 0, n = special[idx].size(); vector<int> small_needs = needs; for (i = 0; i < n – 1; ++i) { if (special[idx][i] > needs[i])break; small_needs[i] -= special[idx][i]; } if (i == n – 1)return min(shopping(price, special, small_needs, idx) + special[idx][n – 1], shopping(price, special, needs, idx + 1)); else return shopping(price, special, needs, idx + 1); } public: int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) { return shopping(price, special, needs, 0); } }; [/cpp] 本代码提交AC,用时6MS。 参考:https://leetcode.com/articles/shopping-offers/,下次记得看到这种数据量非常小的题,首先尝试暴力方法!]]>

LeetCode Decode Ways II

LeetCode Decode Ways II A message containing letters from A-Z is being encoded to numbers using the following mapping way:

'A' -> 1
'B' -> 2
...
'Z' -> 26
Beyond that, now the encoded string can also contain the character ‘*’, which can be treated as one of the numbers from 1 to 9. Given the encoded message containing digits and the character ‘*’, return the total number of ways to decode it. Also, since the answer may be very large, you should return the output mod 109 + 7. Example 1:
Input: "*"
Output: 9
Explanation: The encoded message can be decoded to the string: "A", "B", "C", "D", "E", "F", "G", "H", "I".
Example 2:
Input: "1*"
Output: 9 + 9 = 18
Note:
  1. The length of the input string will fit in range [1, 105].
  2. The input string will only contain the character ‘*’ and digits ‘0’ – ‘9’.

本题是LeetCode Decode Ways的升级版本,引入了一个星号’*’,可以表示’1′-‘9’这9个数字。给定一个加密的字符串,问有多少种解密方法。 这个题说难也难,说简单也简单,我居然跪在了int上,把dp数组由int改为long long就AC了! 和前一题的思路差不多,对于每个字符,都有两种可能的选择,要么该字符单独解码,要么和前一个字符组合起来解码。 设dp[i]表示前i个字符总的解密数。 对于当前字符是数字,前一个字符也是数字的情况,和前一题的思路完全一样,如果是’1’-‘9’,则可以自行解码,dp[i]+=dp[i-1];如果前一个字符和当前字符组合的数字范围在[10,26],可以和前一个字符组合解码,dp[i]+=dp[i-2]。 如果当前字符是数字,但前一个字符是*。如果要和前一个字符组合,当前字符如果在[‘0′,’6’],则前一个*可以取’1’或者’2’,所以dp[i]+=dp[i-2]*2;如果当前字符是[‘7′,’9’],则前一个*只能取’1’,所以dp[i]+=dp[i-2]。 对于*需要特殊处理。如果当前字符是*,前一个字符不是*,要和前一个字符组合,则如果前一个是’1’,则当前*可以取[‘0′,’9’],所以dp[i]+=dp[i-2]*9;如果前一个是’2’,则当前*可以取[‘0′,’6’],所以dp[i]+=dp[i-2]*6。其他情况都不能组合。 如果当前字符和前一个字符都是*的情况,要组合,则**的情况只有15种,注意不是26种,因为要去掉那些个位数和包含0的十位数的情况,剩下就只有15种了。所以dp[i]+=dp[i-2]*15。 完整代码如下: [cpp] const int MOD = 1000000007; typedef long long ll; class Solution { public: int numDecodings(string s) { if (s == "" || s[0] == ‘0’)return 0; s = "^" + s; int n = s.size(); vector<ll> dp(n, 0); dp[0] = 1; if (s[1] == ‘*’)dp[1] = 9; else dp[1] = 1; for (int i = 2; i < n; i++) { if (s[i] >= ‘1’ && s[i] <= ‘9’) dp[i] += dp[i – 1] % MOD; // 独自解析 if ((s[i – 1] == ‘1’ && s[i] >= ‘0’ && s[i] <= ‘9’) || (s[i – 1] == ‘2’ && s[i] >= ‘0’ && s[i] <= ‘6’)) dp[i] += dp[i – 2] % MOD; if (s[i – 1] == ‘*’&&s[i] >= ‘0’&&s[i] <= ‘9’) { if (s[i] >= ‘0’&&s[i] <= ‘6’)dp[i] += dp[i – 2] * 2 % MOD; if (s[i] > ‘6’)dp[i] += dp[i – 2] % MOD; } if (s[i] == ‘*’) { dp[i] += dp[i – 1] * 9 % MOD; // 独自解析 if (s[i – 1] != ‘*’) { if (s[i – 1] == ‘1’)dp[i] += dp[i – 2] * 9 % MOD; else if (s[i – 1] == ‘2’)dp[i] += dp[i – 2] * 6 % MOD; } else { dp[i] += dp[i – 2] * 15 % MOD; } } dp[i] %= MOD; } return dp[n – 1]; } }; [/cpp] 本代码提交AC,用时72MS。比赛的时候dp数组用int存的,死活WA,比赛结束那一秒,改成long long之后就AC了,但是比赛已经结束了。。。 看了下TOP代码,思路比我的稍微清晰一点,原理都是一样的,重构我的代码如下: [cpp] class Solution { public: int numDecodings(string s) { if (s == "" || s[0] == ‘0’)return 0; s = "^" + s; int n = s.size(); vector<ll> dp(n, 0); dp[0] = 1; if (s[1] == ‘*’)dp[1] = 9; else dp[1] = 1; for (int i = 2; i < n; i++) { char cur = s[i], pre = s[i – 1]; ll &curCnt = dp[i], preCnt = dp[i – 1], prePreCnt = dp[i – 2]; if (cur == ‘0’) { if (pre == ‘1’|| pre == ‘2’)curCnt += prePreCnt % MOD; else if (pre == ‘*’)curCnt += prePreCnt * 2 % MOD; else return 0; } else if (cur == ‘*’) { curCnt += preCnt * 9 % MOD; if (pre == ‘1’)curCnt += prePreCnt * 9 % MOD; else if (pre == ‘2’)curCnt += prePreCnt * 6 % MOD; else if (pre == ‘*’)curCnt += prePreCnt * 15 % MOD; } else { // [‘1′,’9′] curCnt += preCnt % MOD; if(pre==’1’)curCnt += prePreCnt % MOD; else if (pre == ‘2’ && cur <= ‘6’)curCnt += prePreCnt % MOD; else if (pre == ‘*’) { if (cur <= ‘6’)curCnt += prePreCnt * 2 % MOD; else curCnt += prePreCnt % MOD; } } curCnt %= MOD; } return dp[n – 1]; } }; [/cpp] 本代码提交AC,用时105MS。]]>

LeetCode Solve the Equation

LeetCode Solve the Equation Solve a given equation and return the value of x in the form of string “x=#value”. The equation contains only ‘+’, ‘-‘ operation, the variable xand its coefficient. If there is no solution for the equation, return “No solution”. If there are infinite solutions for the equation, return “Infinite solutions”. If there is exactly one solution for the equation, we ensure that the value of x is an integer. Example 1:

Input: "x+5-3+x=6+x-2"
Output: "x=2"
Example 2:
Input: "x=x"
Output: "Infinite solutions"
Example 3:
Input: "2x=x"
Output: "x=0"
Example 4:
Input: "2x+3x-6x=x+2"
Output: "x=-1"
Example 5:
Input: "x=x+2"
Output: "No solution"

解一个一元一次方程。简单题,首先把方程分解成等号两边两个子表达式,然后解析这两个子表达式,使成为la+lb*x=ra+rb*x,再转换为(lb-rb)x=ra-la,令lb-rb=b, ra-la=a,则最终的表达式等价于bx=a。 如果b等于0,且a也等于0,则有无穷多解。如果b等于0,但a不等于0,则无解。否则x=a/b。 所以问题的难点是怎样把一个表达式转换为a+bx的形式。也就是代码中的convert函数。遍历字符串,取出每个带符号的操作数,如果该操作数包含’x’,则取出系数,加到b上;否则是常数项,加到a上。 有一个需要注意的点是包含’x’的操作数可能是’-x’或者’+x’,提取系数时需要特殊判断。 代码如下: [cpp] class Solution { private: void convert(string& s, int& a, int& b) { int aa = 0, bb = 0; s += "+"; int start = 0, end = 0; for (end = 0; end < s.size(); ++end) { if (end != 0 && (s[end] == ‘+’ || s[end] == ‘-‘)) { // -x=-1 string tmp = s.substr(start, end – start); if (tmp.find(‘x’) != string::npos) { if (tmp == "x" || tmp == "+x")bb += 1; else if (tmp == "-x")bb -= 1; else bb += stoi(tmp.substr(0, tmp.size() – 1)); } else { aa += stoi(tmp); } start = end; } } a = aa; b = bb; } public: string solveEquation(string equation) { size_t pos = equation.find(‘=’); string left = equation.substr(0, pos), right = equation.substr(pos + 1); int la = 0, lb = 0, ra = 0, rb = 0; convert(left, la, lb); convert(right, ra, rb); int b = lb – rb, a = ra – la; if (b == 0) { if (a == 0)return "Infinite solutions"; else return "No solution"; } else { return "x=" + to_string(a / b); } } }; [/cpp] 本代码提交AC,用时3MS。]]>

LeetCode Average of Levels in Binary Tree

LeetCode Average of Levels in Binary Tree Given a non-empty binary tree, return the average value of the nodes on each level in the form of an array. Example 1:

Input:
    3
   / \
  9  20
    /  \
   15   7
Output: [3, 14.5, 11]
Explanation:
The average value of nodes on level 0 is 3,  on level 1 is 14.5, and on level 2 is 11. Hence return [3, 14.5, 11].
Note:
  1. The range of node’s value is in the range of 32-bit signed integer.

求二叉树每一层的平均数。简单题,BFS层次遍历。代码如下: [cpp] class Solution { public: vector<double> averageOfLevels(TreeNode* root) { if (root == NULL)return{}; vector<double> ans; queue<TreeNode*> q; q.push(root); while (!q.empty()) { double sum = 0; size_t n = q.size(); for (size_t i = 0; i < n; ++i) { TreeNode* f = q.front(); q.pop(); sum += f->val; if (f->left != NULL)q.push(f->left); if (f->right != NULL)q.push(f->right); } ans.push_back(sum / n); } return ans; } }; [/cpp] 本代码提交AC,用时15MS。]]>

LeetCode Continuous Subarray Sum

LeetCode Continuous Subarray Sum Given a list of non-negative numbers and a target integer k, write a function to check if the array has a continuous subarray of size at least 2 that sums up to the multiple of k, that is, sums up to n*k where n is also an integer. Example 1:

Input: [23, 2, 4, 6, 7],  k=6
Output: True
Explanation: Because [2, 4] is a continuous subarray of size 2 and sums up to 6.
Example 2:
Input: [23, 2, 6, 4, 7],  k=6
Output: True
Explanation: Because [23, 2, 6, 4, 7] is an continuous subarray of size 5 and sums up to 42.
Note:
  1. The length of the array won’t exceed 10,000.
  2. You may assume the sum of all the numbers is in the range of a signed 32-bit integer.

给定一个非负整数数组和k,问数组中是否存在长度至少为2的连续子数组,子数组的和是k的n倍,n也是一个整数。 这个题之前好像在哪遇到过。遇到连续子数组的问题,首先要想到前缀和。因为这里要求和是k的n倍,有点麻烦。 首先我们需要知道一个数学知识,如果到i的前缀和accusum1和到j的前缀和accusum2除以k的余数相等,那么(i,j]的连续子数组的和就是k的整数倍。比如第一个样例中,连续子数组的和以及连续子数组的和除以k的余数:
  • [23,25,29,35,42]
  • [5,1,5,5,0]
如果下标从0开始,则下标为0和2的accusum%k都等于5,说明(0,2]的连续子数组的和是k的整数倍。accusum(0,2]=2+4=6,确实是6的整数倍。 这个道理其实很好理解,accusum0%k=5,到了accusum2%k还等于5,说明中间只加了整数倍的k,才导致余数没变,因为整数倍的k 模k是等于0的。 知道了这个道理就好办了,我们用map保存accusum%k的值和计算到现在的accusum的下标,如果后来遇到一个accusum模k的余数在map中,说明找到了一个可能,我们再判断一下他们之间的距离是否至少为2即可。 还有一个特殊的地方需要注意的是,如果k等于0,则不能取模运算。 最后还要注意的一点是,第12行,把当前余数和下标插入map是在else分支的,只有当map中不存在这个余数才插入,否则不插入。比如上面的例子,我们遇到多个accusum模k的余数是5,但是我们map中保存的应该是第0个下标,后续的余数等于5的下标不能更新0这个下标,因为这样才能使得后续的余数相等的下标和map中的下标的差越大,即更好的满足距离至少为2的约束。 完整代码如下: [cpp] class Solution { public: bool checkSubarraySum(vector<int>& nums, int k) { unordered_map<int, int> reminders; reminders[0] = -1; int accusum = 0; for (int i = 0; i < nums.size(); ++i) { accusum += nums[i]; if (k != 0)accusum %= k; // 注意k为0时,不能取模 if (reminders.find(accusum) != reminders.end()) { if (i – reminders[accusum] >= 2)return true; } else reminders[accusum] = i; // 注意必须是在else分支 } return false; } }; [/cpp] 本代码提交AC,用时26MS。]]>

LeetCode Add and Search Word – Data structure design

211. Add and Search Word – Data structure design 211. Add and Search Word – Data structure design

Design a data structure that supports the following two operations:

void addWord(word)
bool search(word)

search(word) can search a literal word or a regular expression string containing only letters a-z or .. A . means it can represent any one letter.

Example:

addWord("bad")
addWord("dad")
addWord("mad")
search("pad") -> false
search("bad") -> true
search(".ad") -> true
search("b..") -> true

Note:
You may assume that all words are consist of lowercase letters a-z. 211. Add and Search Word – Data structure design


实现一个字典,要求可以插入单词和搜索单词,并且支持一种正则表达式,即点号’.’表示任意一个字符。 本题明显是用Trie树来做,插入和搜索普通字符串的方法和Trie树是一模一样的。 唯一需要注意的是搜索带点号的字符串,此时用递归来做,递归的出口是,当遍历到字符串的最后一个字符时,根据是否为点号进行判断。递归向下的过程也是分是否为点号的,如果是点号,则需要尝试26种递归路径。想清楚这个逻辑之后,代码就很好写了。 完整代码如下:

const int N = 26;
class WordDictionary {
private:
    struct Node {
        bool isWord;
        vector<Node*> children;
        Node(bool i)
            : isWord(i)
        {
            for (int i = 0; i < N; ++i)
                children.push_back(NULL);
        };
    };
    Node* root;
    bool search(const string& word, int i, Node* root)
    {
        if (root == NULL)
            return false;
        int idx = word[i] – ‘a’;
        if (i == word.size() – 1) {
            if (word[i] != ‘.’)
                return root->children[idx] != NULL && root->children[idx]->isWord;
            else {
                for (int j = 0; j < N; ++j) {
                    if (root->children[j] != NULL && root->children[j]->isWord)
                        return true;
                }
                return false;
            }
        }
        if (word[i] != ‘.’)
            return search(word, i + 1, root->children[idx]);
        else {
            for (int j = 0; j < N; ++j) {
                if (search(word, i + 1, root->children[j]))
                    return true;
            }
            return false;
        }
    }
public: /** Initialize your data structure here. */
    WordDictionary() { root = new Node(false); } /** Adds a word into the data structure. */
    void addWord(string word)
    {
        Node* cur = root;
        for (const auto& c : word) {
            int idx = c – ‘a’;
            if (cur->children[idx] == NULL)
                cur->children[idx] = new Node(false);
            cur = cur->children[idx];
        }
        cur->isWord = true;
    } /** Returns if the word is in the data structure. A word could contain the dot character ‘.’ to represent any one letter. */
    bool search(string word)
    {
        Node* cur = root;
        return search(word, 0, cur);
    }
};

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

LeetCode Range Sum Query – Mutable

LeetCode Range Sum Query – Mutable Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive. The update(i, val) function modifies nums by updating the element at index i to val. Example:

Given nums = [1, 3, 5]
sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8
Note:
  1. The array is only modifiable by the update function.
  2. You may assume the number of calls to update and sumRange function is distributed evenly.

给定一个一维数组,要求实现范围求和,即求[i,j]之间的元素的和。sumRange(i,j)求i~j的元素和,update(i,val)更新下标为i的元素值为val。 第一敏感使用线段树,很久之前在hihoCoder上遇到过。 建树的方法是类似于树的后序遍历,即左右根。不断把[start,end]二分,构建左右子树,然后构建当前节点,当前节点的sum等于左右子树的sum的和。在递归的时候,递归到start==end时,说明只有一个元素了,此时sum就等于该元素。 查询的方法和建树方法类似,判断区间[i,j]和区间[start,end]的关系,假设start和end的中点是mid,如果j<=mid,递归在左子树查询;如果i>mid,递归在右子树查询;否则在[i,mid]和[mid+1,j]查询然后求和。 更新的方法和查询的方法类似,也是不断判断i和mid的关系,在左子树或者右子树递归更新,当找到该叶子节点时,更新它的sum,返回父节点也更新sum等于新的左右子树的sum的和。 完整代码如下: [cpp] class NumArray { private: struct Node { int start, end, sum; Node *left, *right; Node(int s, int e) :start(s), end(e), sum(0), left(NULL), right(NULL) {}; }; Node *root; Node* constructTree(vector<int> &nums, int start, int end) { Node* node = new Node(start, end); if (start == end) { node->sum = nums[start]; return node; } int mid = start + (end – start) / 2; node->left = constructTree(nums, start, mid); node->right = constructTree(nums, mid + 1, end); node->sum = node->left->sum + node->right->sum; return node; } int sumRange(int i, int j, Node *root) { if (root == NULL)return 0; if (i == root->start&&j == root->end)return root->sum; int mid = root->start + (root->end – root->start) / 2; if (j <= mid)return sumRange(i, j, root->left); else if (i > mid)return sumRange(i, j, root->right); else return sumRange(i, mid, root->left) + sumRange(mid + 1, j, root->right); } void update(int i, int val, Node *root) { if (root->start == root->end && root->start == i) { root->sum = val; return; } int mid = root->start + (root->end – root->start) / 2; if (i <= mid)update(i, val, root->left); else update(i, val, root->right); root->sum = root->left->sum + root->right->sum; } public: NumArray(vector<int> nums) { root = NULL; if (!nums.empty())root = constructTree(nums, 0, nums.size() – 1); } void update(int i, int val) { if (root == NULL)return; update(i, val, root); } int sumRange(int i, int j) { if (root == NULL)return 0; return sumRange(i, j, root); } }; [/cpp] 本代码提交AC,用时172MS。]]>

LeetCode Range Sum Query 2D – Immutable

LeetCode Range Sum Query 2D – Immutable

Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2). Range Sum Query 2D The above rectangle (with the red border) is defined by (row1, col1) = (2, 1) and (row2, col2) = (4, 3), which contains sum = 8. Example:
Given matrix = [
  [3, 0, 1, 4, 2],
  [5, 6, 3, 2, 1],
  [1, 2, 0, 1, 5],
  [4, 1, 0, 1, 7],
  [1, 0, 3, 0, 5]
]
sumRegion(2, 1, 4, 3) -> 8
sumRegion(1, 1, 2, 2) -> 11
sumRegion(1, 2, 2, 4) -> 12
Note:
  1. You may assume that the matrix does not change.
  2. There are many calls to sumRegion function.
  3. You may assume that row1 ≤ row2 and col1 ≤ col2.

给定一个矩阵,要求矩阵中的任意一个子矩阵的和,而且可能有很多次这样的请求。 因为之前在hiho上做过一个用DP求子矩阵的和的题,所以这题就很easy了。 定义一个二维数组dp[i][j]表示固定子矩阵的左上角坐标为(1,1)(假设矩阵的下标从1开始),当右下角坐标为(i,j)时,这个子矩阵的和是多少。则有: dp[i][j]=dp[i][j-1]+dp[i-1][j]-dp[i-1][j-1]+matrix[i][j] 这个很好理解,相当于求面积,dp[i][j-1]+dp[i-1][j]加了两遍dp[i-1][j-1],所以要减掉一个,最后再加上dp[i][j]独有的matrix[i][j]。 有了dp[i][j]之后,怎样求任意一个子矩阵的和呢?假设我们要求第i,j两行、u,v两列交成的子矩阵的和,应该怎样计算呢。这个也很好算,也是面积的加加减减,如下图所示,把(j,v)的面积减去(j,u)左边以及(i,v)上边的面积,但是(i,u)左上的面积减了两次,所以还要加上。总结成公式就是: cursum=dp[j][v]-dp[j][u-1]-dp[i-1][v]+dp[i-1][u-1]
      |               |
-----(i,u)----------(i,v)----
      |               |
      |               |
-----(j,u)----------(j,v)----
      |               |
通过cursum公式,我们很容易就能求出任意一个子矩阵的和了。代码如下: [cpp] class NumMatrix { private: vector<vector<int>> dp; public: NumMatrix(vector<vector<int>> matrix) { if (matrix.empty() || matrix[0].empty())return; int m = matrix.size(), n = matrix[0].size(); for (int i = 0; i < m + 1; ++i)dp.push_back(vector<int>(n + 1, 0)); // 为了方便,多增加了一行和一列 for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { dp[i][j] = dp[i][j – 1] + dp[i – 1][j] – dp[i – 1][j – 1] + matrix[i – 1][j – 1]; } } } int sumRegion(int row1, int col1, int row2, int col2) { if (dp.empty())return 0; ++row1; // 因为dp多一行和一列,所以这里提前加上 ++col1; ++row2; ++col2; return dp[row2][col2] – dp[row2][col1 – 1] – dp[row1 – 1][col2] + dp[row1 – 1][col1 – 1]; } }; [/cpp] 本代码提交AC,用时22MS。]]>

LeetCode Design Twitter

LeetCode Design Twitter

Design a simplified version of Twitter where users can post tweets, follow/unfollow another user and is able to see the 10 most recent tweets in the user’s news feed. Your design should support the following methods:
  1. postTweet(userId, tweetId): Compose a new tweet.
  2. getNewsFeed(userId): Retrieve the 10 most recent tweet ids in the user’s news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent.
  3. follow(followerId, followeeId): Follower follows a followee.
  4. unfollow(followerId, followeeId): Follower unfollows a followee.
Example:
Twitter twitter = new Twitter();
// User 1 posts a new tweet (id = 5).
twitter.postTweet(1, 5);
// User 1's news feed should return a list with 1 tweet id -> [5].
twitter.getNewsFeed(1);
// User 1 follows user 2.
twitter.follow(1, 2);
// User 2 posts a new tweet (id = 6).
twitter.postTweet(2, 6);
// User 1's news feed should return a list with 2 tweet ids -> [6, 5].
// Tweet id 6 should precede tweet id 5 because it is posted after tweet id 5.
twitter.getNewsFeed(1);
// User 1 unfollows user 2.
twitter.unfollow(1, 2);
// User 1's news feed should return a list with 1 tweet id -> [5],
// since user 1 is no longer following user 2.
twitter.getNewsFeed(1);

设计一个Twitter系统,实现三个接口,postTweet发文,follow某人,unfollow某人,getNewsFeed从自己以及follow的人中取出最近发表的三篇推文,按时间从近到远排序。比较麻烦的是getNewsFeed函数的实现。 先来个暴力版本,用unordered_map<int, unordered_set<int>>保存某个人follow的所有人,这样follow和unfollow的操作都可以在O(1)实现。 用vector<Tweet>保存所有推文,而且是最新postTweet的推文都push_back到末尾,所以该容器保存了全局从远到近的所有推文。那么getNewsFeed时,直接从容器末尾往前遍历所有推文,判断该推文是否是自己或者自己follow的用户发表的,如果是,则收集起来,直到收集到10篇推文。 思路和实现都非常简单,代码如下: [cpp] class Twitter { private: struct Tweet { int userId; int tweetId; Tweet(int u, int t) :userId(u), tweetId(t) {}; }; vector<Tweet> Tweets; unordered_map<int, unordered_set<int>> following; public: /** Initialize your data structure here. */ Twitter() { } /** Compose a new tweet. */ void postTweet(int userId, int tweetId) { Tweets.push_back({ userId,tweetId }); } /** Retrieve the 10 most recent tweet ids in the user’s news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. */ vector<int> getNewsFeed(int userId) { vector<int> ans; for (int i = Tweets.size() – 1; i >= 0; –i) { int &uid = Tweets[i].userId; if (uid == userId || following[userId].find(uid) != following[userId].end()) { ans.push_back(Tweets[i].tweetId); if (ans.size() == 10)break; } } return ans; } /** Follower follows a followee. If the operation is invalid, it should be a no-op. */ void follow(int followerId, int followeeId) { following[followerId].insert(followeeId); } /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */ void unfollow(int followerId, int followeeId) { following[followerId].erase(followeeId); } }; [/cpp] 本代码提交AC,用时143MS。 上面的版本比较慢,主要是每次getNewsFeed时都要遍历全局所有的推文。讨论区有一种做法是把每个人发表的推文也保存到一个unordered_map<int, vector<Tweet>>中,那么在getNewsFeed时,只需要拿出自己或自己follow的用户发表的推文,放到一个优先队列中,优先队列自动按时间先后顺序排序了,从优先队列中取出10条推文即可。 为了按时间排序,需要一个全局时间戳,并且重载优先队列的比较运算符。 代码如下: [cpp] class Twitter { private: int timestamp; struct Tweet { int tweetId; int timestamp; Tweet(int tid, int ts) :tweetId(tid), timestamp(ts) {}; bool operator<(const Tweet& t)const { return timestamp < t.timestamp; } }; unordered_map<int, vector<Tweet>> Tweets; unordered_map<int, unordered_set<int>> following; public: /** Initialize your data structure here. */ Twitter() :timestamp(0) { } /** Compose a new tweet. */ void postTweet(int userId, int tweetId) { Tweets[userId].push_back({ tweetId,timestamp++ }); } /** Retrieve the 10 most recent tweet ids in the user’s news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. */ vector<int> getNewsFeed(int userId) { priority_queue<Tweet> pq; for (const auto& uid : following[userId]) { for (const auto& t : Tweets[uid]) { pq.push(t); } } for (const auto& t : Tweets[userId]) { pq.push(t); } vector<int> ans; while (!pq.empty() && ans.size() < 10) { ans.push_back(pq.top().tweetId); pq.pop(); } return ans; } /** Follower follows a followee. If the operation is invalid, it should be a no-op. */ void follow(int followerId, int followeeId) { if (followerId != followeeId)following[followerId].insert(followeeId); // map中不考虑自己follow自己,因为getNewsFeed单独考虑了 } /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */ void unfollow(int followerId, int followeeId) { following[followerId].erase(followeeId); } }; [/cpp] 本代码提交AC,用时33MS,快了好几倍!]]>