Category Archives: LeetCode

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。]]>

LeetCode Valid Triangle Number

LeetCode Valid Triangle Number Given an array consists of non-negative integers, your task is to count the number of triplets chosen from the array that can make triangles if we take them as side lengths of a triangle. Example 1:

Input: [2,2,3,4]
Output: 3
Explanation:
Valid combinations are:
2,3,4 (using the first 2)
2,3,4 (using the second 2)
2,2,3
Note:
  1. The length of the given array won’t exceed 1000.
  2. The integers in the given array are in the range of [0, 1000].

给定一个数组,问从中能取出所少个三元数组,使得取出的三个数能构成一个三角形。 首先明确三条线段构成三角形的条件是任意两边之和要大于第三遍。 先上暴力,直接dfs枚举出所有的三元组,判断能构成三角形,则方案数加1。代码如下: [cpp] class Solution { private: void dfs(int &ans, vector<int>& nums, vector<int>& cand, int idx) { if (cand.size() == 3) { int &a = cand[0], &b = cand[1], &c = cand[2]; if (a + b > c&&a + c > b&&b + c > a) { ++ans; //cout << a << "\t" << b << "\t" << c << endl; } return; } for (int i = idx; i < nums.size(); ++i) { if (cand.size() == 2 && cand[0] + cand[1] <= nums[i])return; cand.push_back(nums[i]); dfs(ans, nums, cand, i + 1); cand.pop_back(); } } public: int triangleNumber(vector<int>& nums) { int ans = 0; vector<int> cand; sort(nums.begin(), nums.end()); dfs(ans, nums, cand, 0); return ans; } }; [/cpp] 本代码提交TLE:219 / 243。数组最大长度是1000,则所有的组合数有1000*999*998=997002000,确实有点大。。。 后来我发现,报TLE的大数据,虽然有1000个数,但是有很多都是重复的,真正不同的数大概只有100个左右。所以我就想,先对数据去重,在所有互异的数中dfs。然后根据每条边重复的次数来求组合数。 比如样例中,互异的数是[2,3,4],dfs发现[2,3,4]可以构成三角形,则所有由[2,3,4]构成的三角形的个数应该是count[2]*count[3]*count[4]=2*1*1=2。 所以我们先对数组做个hash,统计数值及其出现频率的关系。注意,因为边的长度最大也只为1000,所以用一个1000长的数组来hash比用map或者unordered_map占用的内存更少,否则会MLE。 然后分三类情况进行计算:1. 三条边互不相同;2.有两条边的值相等;3.三条边的值都相等。 其中第一种情况用常规的DFS求解。第二种和第三种情况就是简单的枚举。 还需要注意一点是,边长为0的值需要过滤掉。 完整代码如下: [cpp] class Solution { private: void dfs(int& ans, vector<int>& count, vector<int>& nums, vector<int>& cand, int idx) { if (cand.size() == 3) { int &a = cand[0], &b = cand[1], &c = cand[2]; if (a + b > c&&a + c > b&&b + c > a) { ans += count[a] * count[b] * count[c]; // 三边各异 //cout << a << "\t" << b << "\t" << c << endl; } return; } for (int i = idx; i < nums.size(); ++i) { if (cand.size() == 2 && cand[0] + cand[1] <= nums[i])return; cand.push_back(nums[i]); dfs(ans, count, nums, cand, i + 1); cand.pop_back(); } } public: int triangleNumber(vector<int>& nums) { vector<int> mii(1001, 0); for (const auto& i : nums)++mii[i]; // hash vector<int> distinct; for (int i = 1; i < 1001; ++i) { if (mii[i] > 0)distinct.push_back(i); } int ans = 0; vector<int> cand; dfs(ans, mii, distinct, cand, 0); // 三边互不相同 int n = distinct.size(); for (int i = 0; i < n; ++i) { if (mii[distinct[i]] >= 3) { // 三边相同 int &d = mii[distinct[i]]; ans += (d*(d – 1)*(d – 2)) / 6; } for (int j = i + 1; j < n; ++j) { if (mii[distinct[i]] >= 2) { // 两条边一样 int &a = distinct[i], &b = distinct[i], &c = distinct[j]; if (a + b > c&&a + c > b&&b + c > a) { ans += (mii[a] * (mii[a] – 1) / 2)*mii[c]; } } if (mii[distinct[j]] >= 2) { // 两条边一样 int &a = distinct[i], &b = distinct[j], &c = distinct[j]; if (a + b > c&&a + c > b&&b + c > a) { ans += (mii[b] * (mii[b] – 1) / 2)*mii[a]; } } } } return ans; } }; [/cpp] 本代码提交AC,用时1589MS。]]>

LeetCode Design Compressed String Iterator

LeetCode Design Compressed String Iterator Design and implement a data structure for a compressed string iterator. It should support the following operations: next and hasNext. The given compressed string will be in the form of each letter followed by a positive integer representing the number of this letter existing in the original uncompressed string. next() – if the original string still has uncompressed characters, return the next letter; Otherwise return a white space. hasNext() – Judge whether there is any letter needs to be uncompressed. Note: Please remember to RESET your class variables declared in StringIterator, as static/class variables are persisted across multiple test cases. Please see here for more details. Example:

StringIterator iterator = new StringIterator("L1e2t1C1o1d1e1");
iterator.next(); // return 'L'
iterator.next(); // return 'e'
iterator.next(); // return 'e'
iterator.next(); // return 't'
iterator.next(); // return 'C'
iterator.next(); // return 'o'
iterator.next(); // return 'd'
iterator.hasNext(); // return true
iterator.next(); // return 'e'
iterator.hasNext(); // return false
iterator.next(); // return ' '

本题设计了一种压缩字符串格式,即把多个连续相同字符用单个字符已经频率代替了,比如”L1e2t1C1o1d1e1″展开其实就是”LeetCode”。现在要针对这种压缩字符串,设计一个迭代器,实现next和hasNext函数。 我一开始上暴力,即首先把压缩字符串展开成常规字符串,然后直接下标操作。代码如下: [cpp] class StringIterator { private: string line; int cur, n; public: StringIterator(string compressedString) { line = ""; cur = n = 0; vector<char> letters; vector<int> rep; string digits = ""; for (int i = 0; i < compressedString.size(); ++i) { if (isalpha(compressedString[i])) { letters.push_back(compressedString[i]); if (digits != "") { rep.push_back(atoi(digits.c_str())); digits = ""; } } else digits += string(1, compressedString[i]); } rep.push_back(atoi(digits.c_str())); for (int i = 0; i < letters.size(); ++i) { line += string(rep[i], letters[i]); } n = line.size(); } char next() { if (cur < n)return line[cur++]; else return ‘ ‘; } bool hasNext() { return cur < n; } }; [/cpp] 本代码MLE了。因为有个样例是“a1234567890b1234567890”,我表示无语,如果展开确实会MLE。 其实写上面的代码的时候我就知道可以优化,不对压缩字符串展开,而是表示成(a,1234567890)和(b,1234567890),next的时候只对频率递减。这样只需要存储少量字符以及对应的频率。 代码如下: [cpp] typedef long long ll; class StringIterator { private: int cur, n; vector<char> letters; vector<ll> rep; public: StringIterator(string compressedString) { cur = n = 0; string digits = ""; for (int i = 0; i < compressedString.size(); ++i) { if (isalpha(compressedString[i])) { letters.push_back(compressedString[i]); if (digits != "") { rep.push_back(atoll(digits.c_str())); digits = ""; } } else digits += string(1, compressedString[i]); } rep.push_back(atoi(digits.c_str())); n = letters.size(); } char next() { if (rep[cur] > 0) { char ans = letters[cur]; –rep[cur]; return ans; } else { if (cur == n – 1)return ‘ ‘; else { char ans = letters[++cur]; –rep[cur]; return ans; } } } bool hasNext() { return cur < n – 1 || (cur == n – 1 && rep[cur] > 0); } }; [/cpp] 本代码提交AC,用时13MS。]]>

LeetCode Merge Two Binary Trees

LeetCode Merge Two Binary Trees Given two binary trees and imagine that when you put one of them to cover the other, some nodes of the two trees are overlapped while the others are not. You need to merge them into a new binary tree. The merge rule is that if two nodes overlap, then sum node values up as the new value of the merged node. Otherwise, the NOT null node will be used as the node of new tree. Example 1:

Input:
	Tree 1                     Tree 2
          1                         2
         / \                       / \
        3   2                     1   3
       /                           \   \
      5                             4   7
Output:
Merged tree:
	     3
	    / \
	   4   5
	  / \   \
	 5   4   7
Note: The merging process must start from the root nodes of both trees.
上午又参加了一场LeetCode的比赛,第三题花了不少时间,第四题在11:06分AC了,但是比赛好像在11:00就结束了。。。无语,排名只有376。 这题要求把两棵二叉树合并,如果对应位置都有节点,则新节点的值等于两个老节点的值之和,否则等于有值的那个节点。 简单题,直接递归merge就好。代码如下: [cpp] class Solution { public: TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) { if (t1 == NULL&&t2 == NULL)return NULL; if (t1 == NULL&&t2 != NULL)return t2; if (t1 != NULL&&t2 == NULL)return t1; TreeNode* left = mergeTrees(t1->left, t2->left); TreeNode* right = mergeTrees(t1->right, t2->right); TreeNode* root = new TreeNode(t1->val + t2->val); root->left = left; root->right = right; return root; } }; [/cpp] 本代码提交AC,用时49MS。]]>

LeetCode Rotate Function

LeetCode Rotate Function Given an array of integers A and let n to be its length. Assume Bk to be an array obtained by rotating the array A k positions clock-wise, we define a “rotation function” F on A as follow: F(k) = 0 * Bk[0] + 1 * Bk[1] + ... + (n-1) * Bk[n-1]. Calculate the maximum value of F(0), F(1), ..., F(n-1). Note: n is guaranteed to be less than 105. Example:

A = [4, 3, 2, 6]
F(0) = (0 * 4) + (1 * 3) + (2 * 2) + (3 * 6) = 0 + 3 + 4 + 18 = 25
F(1) = (0 * 6) + (1 * 4) + (2 * 3) + (3 * 2) = 0 + 4 + 6 + 6 = 16
F(2) = (0 * 2) + (1 * 6) + (2 * 4) + (3 * 3) = 0 + 6 + 8 + 9 = 23
F(3) = (0 * 3) + (1 * 2) + (2 * 6) + (3 * 4) = 0 + 2 + 12 + 12 = 26
So the maximum value of F(0), F(1), F(2), F(3) is F(3) = 26.

给定一个数组A,和函数F(k),其中F(k) = 0 * Bk[0] + 1 * Bk[1] + … + (n-1) * Bk[n-1],数组B为数组A顺时针旋转k次后的新数组。要求所有F(k)的最大值。 仔细观察样例,会发现$$F(k)=\sum_{i=0}^{n-1}((k+i)\%n)*a_i$$。于是可以算出所有的F(k),然后求最大值。代码如下: [cpp] class Solution { public: int maxRotateFunction(vector<int>& A) { if (A.empty())return 0; int ans = INT_MIN, n = A.size(); for (int k = 0; k < n; ++k) { int cur = 0; for (int i = 0; i < n; ++i) { cur += ((k + i) % n)*A[i]; } ans = max(ans, cur); } return ans; } }; [/cpp] 本代码提交TLE,复杂度是O(kn),遇到大数据时超时了。 如果再仔细研究一下样例,会发现一个更优的递推公式:$$F(k)=F(k-1)+sum-n*A[n-k]$$。这样直接根据前一项F(k-1),可以在O(1)时间算出下一项F(k)。总的时间复杂度只有O(n)。代码如下: [cpp] class Solution { public: int maxRotateFunction(vector<int>& A) { if (A.empty())return 0; int n = A.size(), sum = 0, f0 = 0; for (int i = 0; i < n; ++i) { sum += A[i]; f0 += i*A[i]; } int ans = f0, pre = f0; for (int i = 1; i < n; ++i) { int cur = pre + sum – n * A[n – i]; ans = max(ans, cur); pre = cur; } return ans; } }; [/cpp] 本代码提交AC,用时16MS。]]>

LeetCode Palindrome Partitioning

131. Palindrome Partitioning


Given a string s, partition s such that every substring of the partition is a palindrome.

Return all possible palindrome partitioning of s.

Example:

Input: "aab"
Output:
[
  ["aa","b"],
  ["a","a","b"]
]

本题要求把字符串s分割成若干个子串,使得每个子串都是回文串。如果有多种分割方法,输出所有的分割方案。 很有意思的一道题。我是这样做的:首先用DP求出任意一个子串s[i,…,j]是否为回文串,这就相当于知道了s中哪几个位置可以切割;然后就在s中DFS每个割点,求出所有的分割方案。
DP求s[i,…,j]是否为回文串的过程是这样的,如果s[i]==s[j],且dp[i+1][j-1]也是回文串,则dp[i][j]也是回文串。所以我们需要先求到长度比较短的s[i+1,j-1]是否为回文串,然后再求长度更长的是否为回文串。所以在helper函数的外层循环是子串的长度。

求到了任意一个子串是否为回文串之后,DFS每个割点就好了,这个和枚举排列情况很类似,就不再赘述了。完整代码如下:

class Solution {
private:
    void helper(const string& s, vector<vector<int> >& dp)
    {
        int n = s.size();
        for (int i = 0; i < n; ++i)
            dp[i][i] = 1; // 单个字符自身就是回文串
        for (int len = 2; len <= n; ++len) {
            for (int i = 0; i + len – 1 < n; ++i) {
                if (s[i] == s[i + len – 1] && ((i + 1 <= i + len – 2 && dp[i + 1][i + len – 2] == 1) || i + 1 > i + len – 2)) {
                    dp[i][i + len – 1] = 1;
                }
            }
        }
    }
    void dfs(const string& s, vector<vector<int> >& dp, vector<vector<string> >& ans, vector<string>& cand, int idx)
    {
        if (idx == s.size()) {
            ans.push_back(cand);
            return;
        }
        for (int i = idx; i < s.size(); ++i) {
            if (dp[idx][i] == 1) {
                cand.push_back(s.substr(idx, i – idx + 1));
                dfs(s, dp, ans, cand, i + 1);
                cand.pop_back();
            }
        }
    }
public:
    vector<vector<string> > partition(string s)
    {
        int n = s.size();
        vector<vector<int> > dp(n, vector<int>(n, 0));
        helper(s, dp);
        vector<vector<string> > ans;
        vector<string> cand;
        dfs(s, dp, ans, cand, 0);
        return ans;
    }
};

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

二刷。不用DP提前判断,而是每次都暴力判断是否为回文:

class Solution {
public:
	bool IsPal(const string &s, int l, int r) {
		if (l > r)return false;
		if (l == r)return true;
		while (l < r) {
			if (s[l] != s[r])break;
			++l;
			--r;
		}
		return l >= r;
	}
	void DFS(vector<vector<string>> &ans, vector<string> &cur, string &s, int idx) {
		int n = s.size();
		if (idx >= n) {
			ans.push_back(cur);
			return;
		}
		for (int i = idx; i < n; ++i) {
			if (IsPal(s, idx, i)) {
				cur.push_back(s.substr(idx, i - idx + 1));
				DFS(ans, cur, s, i + 1);
				cur.pop_back();
			}
		}
	}
	vector<vector<string>> partition(string s) {
		vector<vector<string>> ans;
		vector<string> cur;
		DFS(ans, cur, s, 0);
		return ans;
	}
};

本代码提交AC,用时12MS,比第一种方法慢,还是第一种方法提前求DP更高效。

LeetCode Pacific Atlantic Water Flow

LeetCode Pacific Atlantic Water Flow Given an m x n matrix of non-negative integers representing the height of each unit cell in a continent, the “Pacific ocean” touches the left and top edges of the matrix and the “Atlantic ocean” touches the right and bottom edges. Water can only flow in four directions (up, down, left, or right) from a cell to another one with height equal or lower. Find the list of grid coordinates where water can flow to both the Pacific and Atlantic ocean. Note:

  1. The order of returned grid coordinates does not matter.
  2. Both m and n are less than 150.
Example:
Given the following 5x5 matrix:
  Pacific ~   ~   ~   ~   ~
       ~  1   2   2   3  (5) *
       ~  3   2   3  (4) (4) *
       ~  2   4  (5)  3   1  *
       ~ (6) (7)  1   4   5  *
       ~ (5)  1   1   2   4  *
          *   *   *   *   * Atlantic
Return:
[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (positions with parentheses in above matrix).

给定一个矩阵,每个元素表示一个方格中水柱的高度,左上角是太平洋,右下角是大西洋,问哪些坐标的水柱既能流向太平洋,又能流向大西洋。注意每个水柱能流向和它同样高或者比它第的地方。 一看就是DFS或者BFS的题。 拍脑袋解法。对于每个坐标,我们DFS看能否分别到达太平洋或者大西洋。代码如下: [cpp] vector<vector<int>> dirs = { { 0,-1 },{ -1,0 },{ 0,1 },{ 1,0 } }; // 0:left, 1:up, 2:right, 3:down class Solution4 { private: int m, n; bool isOk(int x, int y) { return x >= 0 && x < m&&y >= 0 && y < n; } // type=0:Pacific; 1:Atlantic bool dfs(vector<vector<int>>& matrix, vector<vector<int>>& visited, int x, int y, const int& type) { for (int i = 0; i < dirs.size(); ++i) { int xx = x + dirs[i][0], yy = y + dirs[i][1]; if (isOk(xx, yy) && visited[xx][yy] == 0 && matrix[x][y] >= matrix[xx][yy]) { visited[xx][yy] = 1; if (dfs(matrix, visited, xx, yy, type)) { visited[xx][yy] = 0; return true; } visited[xx][yy] = 0; } else if ((type == 0 && (xx < 0 || yy < 0)) || (type == 1 && (xx >= m || yy >= n))) { return true; } } return false; } public: vector<pair<int, int>> pacificAtlantic(vector<vector<int>>& matrix) { if (matrix.empty() || matrix[0].empty())return{}; m = matrix.size(), n = matrix[0].size(); vector<vector<int>> visited(m, vector<int>(n, 0)); vector<pair<int, int>> ans; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { visited[i][j] = 1; if (dfs(matrix,visited,i,j,0)&&dfs(matrix,visited,i,j,1)) ans.push_back(pair<int, int>(i, j)); visited[i][j] = 0; } } return ans; } }; [/cpp] 本代码提交AC,用时702MS。 这种DFS的解法重复计算非常多,比如(a,b)这个点DFS时会经过(a-1,b-1),而(a-1,b-1)之前其实已经DFS过了。可以在DFS的过程中记录下经过的点的DFS状态,比如在DFS(a,b)时,走到(a-1,b-1),先查状态,发现(a-1,b-1)到不了大西洋,那现在这条路就没必要继续DFS下去了;相反,如果查状态发现(a-1,b-1)能到大西洋,则后续不需要再DFS其他路径了。 保存状态的DFS版本如下: [cpp] vector<vector<int>> dirs = { { 0,-1 },{ -1,0 },{ 0,1 },{ 1,0 } }; // 0:left, 1:up, 2:right, 3:down // memory version class Solution3 { private: int m, n; bool isOk(int x, int y) { return x >= 0 && x < m&&y >= 0 && y < n; } bool dfs(vector<vector<int>>& matrix, vector<vector<int>>& visited, int x, int y, const int& type, vector<vector<vector<int>>>& mem) { for (int i = 0; i < dirs.size(); ++i) { int xx = x + dirs[i][0], yy = y + dirs[i][1]; if (isOk(xx, yy) && visited[xx][yy] == 0 && matrix[x][y] >= matrix[xx][yy]) { if (mem[xx][yy][type] == 1) { mem[x][y][type] = 1; return true; } else if (mem[xx][yy][type] == 0)continue; visited[xx][yy] = 1; if (dfs(matrix, visited, xx, yy, type, mem)) { mem[x][y][type] = 1; visited[xx][yy] = 0; return true; } visited[xx][yy] = 0; } else if ((type == 0 && (xx < 0 || yy < 0)) || (type == 1 && (xx >= m || yy >= n))) { mem[x][y][type] = 1; return true; } } mem[x][y][type] = 0; return false; } public: vector<pair<int, int>> pacificAtlantic(vector<vector<int>>& matrix) { if (matrix.empty() || matrix[0].empty())return{}; m = matrix.size(), n = matrix[0].size(); vector<vector<int>> visited(m, vector<int>(n, 0)); vector<vector<vector<int>>> mem(m, vector<vector<int>>(n, vector<int>(2, -1))); vector<pair<int, int>> ans; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { visited[i][j] = 1; if (mem[i][j][0] == -1) dfs(matrix, visited, i, j, 0, mem); if (mem[i][j][1] == -1) dfs(matrix, visited, i, j, 1, mem); visited[i][j] = 0; if (mem[i][j][0] == 1 && mem[i][j][1] == 1) ans.push_back(pair<int, int>(i, j)); } } return ans; } }; [/cpp] 无奈,在第112/113这个数据上WA了,但是这个数据太大了,面对的是汪洋大海,调试太费劲,暂时没fix bug。 看了下讨论区,发现主流解法是从海洋开始BFS,分别从大西洋和太平洋BFS,记录下两个海洋分别能访问到的点,最后如果某个点同时被两个海洋访问到了,则说明该点既能到达大西洋,也能到达太平洋。 代码如下: [cpp] vector<vector<int>> dirs = { { 0,-1 },{ -1,0 },{ 0,1 },{ 1,0 } }; // 0:left, 1:up, 2:right, 3:down class Solution { private: int m, n; bool isOk(int x, int y) { return x >= 0 && x < m&&y >= 0 && y < n; } void bfs(vector<vector<int>>& matrix, queue<pair<int, int>>& Q, vector<vector<int>>& visited) { while (!Q.empty()) { auto f = Q.front(); Q.pop(); visited[f.first][f.second] = 1; for (int i = 0; i < dirs.size(); ++i) { int x = f.first + dirs[i][0], y = f.second + dirs[i][1]; if (isOk(x, y) && visited[x][y] == 0 && matrix[f.first][f.second] <= matrix[x][y]) { Q.push(pair<int, int>(x, y)); } } } } public: vector<pair<int, int>> pacificAtlantic(vector<vector<int>>& matrix) { if (matrix.empty() || matrix[0].empty())return{}; m = matrix.size(), n = matrix[0].size(); vector<vector<int>> pacific(m, vector<int>(n, 0)), atlantic(m, vector<int>(n, 0)); queue<pair<int, int>> pQ, aQ; for (int i = 0; i < m; ++i) { // vertical pQ.push(pair<int, int>(i, 0)); aQ.push(pair<int, int>(i, n – 1)); } for (int i = 0; i < n; ++i) { // horizontal pQ.push(pair<int, int>(0, i)); aQ.push(pair<int, int>(m – 1, i)); } bfs(matrix, pQ, pacific); bfs(matrix, aQ, atlantic); vector<pair<int, int>> ans; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (pacific[i][j] == 1 && atlantic[i][j] == 1) ans.push_back(pair<int, int>(i, j)); } } return ans; } }; [/cpp] 本代码提交AC,用时减少到45MS。 对于类似的搜索题目,如果是求单个点能否达到目的地,则从起始点或者目的点开始BFS/DFS的效果都是一样的。但是如果要求所有能到达目的点的点,则从目的点开始BFS就能一次性找到这些点,而从每个起始点开始BFS/DFS就非常费劲了。]]>