本次比赛不难,但代码实现起来不易。不容易一次写到bug-free。考察的是用编程语言处理复杂的逻辑,和各种意外情况。比如 第3题,当前一个dp为0时,长度应该更新为2,除此之外,dp+1。第四题,在各种情况下寻找分隔符时,没有找到,应该如何处理。
Rank
Name
Score
Finish Time
Q1 (4)
Q2 (5)
Q3 (5)
Q4 (5)
388 / 4765
YoungForest
24
1:21:20
0:15:54
0:30:16
0:38:05
1:21:20
大概需要1个小时内做完,才能进入前200。
第4题由于一些边界条件,我调试了不少时间。我分析花这么长时间的原因。还是写代码写的少,对变量更新的边界条件不敏感。比如string::find没有找到的时候,其他各个坐标该如何更新。我就是由于没找到的时候,返回了npos(-1), current_find_index
此时应该等于end
,而不是继续在-1上加分隔符的长度。
1025. Divisor Game
一道找规律的题目。
Intution:
dp。 Solution(N) = true if any Solution(N’s divisor) is false, else false。
时间复杂度: O(N^2),
空间复杂度: O(N).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution {public : bool divisorGame (int N) { vector<bool > dp (1001 , false ) ; dp[1 ] = false ; for (int i = 2 ; i <= N; i++) { for (int j = 1 ; j < i; j++) { if (i % j == 0 && dp[i - j] == false ) { dp[i] = true ; break ; } } } return dp[N]; } };
把1~9的结果输出之后发现了了不得的规律。试了一下,一行代码就搞定了。
用数学归纳法可以证明:
前提:
N 为奇数,false;
N 为偶数,true。
如果N为偶数,取x=1,N-1为false。则N为True.
如果N为奇数,它所有的因子也必为奇数,即N-x一定为偶数,true. 则N为False。
时间复杂度: O(1),
空间复杂度: O(1).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : bool divisorGame (int N) { return N % 2 != 1 ; } };
1026. Maximum Difference Between Node and Ancestor
Intution:
递归。由于possible difference可能是| root->val - 左子树中的最小值 |
或| root->val - 左子树中的最大值 |
或 减去右子树中的最大最小值,或 左右子树中Maximun Difference中的较大者。
所以递归函数返回3个值,分别是Maximun Different, 树中最小值,树中最大值。
时间复杂度: O(N),每个节点都会被递归调用一次。
空间复杂度: O(N),树的高度,平均情况O(log N), 最差O(N)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { tuple<int , int , int > dfs (TreeNode* root) { if (!root) { return {0 , -1 , 100001 }; } auto l = dfs (root->left); auto r = dfs (root->right); return { max ({get <0 >(l), get <0 >(r), get <1 >(l) == -1 ? -1 : abs (root->val - get <1 >(l)), get <1 >(r) == -1 ? -1 : abs (root->val - get <1 >(r)), get <2 >(l) == 100001 ? -1 : abs (root->val - get <2 >(l)), get <2 >(r) == 100001 ? -1 : abs (root->val - get <2 >(r))}), max ({root->val, get <1 >(l), get <1 >(r)}), min ({root->val, get <2 >(l), get <2 >(r)}) }; } public : int maxAncestorDiff (TreeNode* root) { auto ret = dfs (root); return get <0 >(ret); } };
1027. Longest Arithmetic Sequence
Intution:
DP.
最优子结构,其中dp[a][b]表示,以坐标a结尾,且差值为b的子序列的长度。
dp[i][A[i] - A[j]] = dp[j][A[i] - A[j]] == 0 ? 2 : dp[j][A[i] - A[j]] + 1。
时间复杂度: O(N ^ 2 log N), 用unordered_map的话可以进一步降为O(N^2).
空间复杂度: O(N ^ 2). 最差情况下,两两差值均不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int longestArithSeqLength (vector<int >& A) { int ret = 0 ; vector<map<int , int >> dp (A.size ()); for (int i = 0 ; i < A.size (); ++i) { for (int j = 0 ; j < i; ++j) { dp[i][A[i] - A[j]] = dp[j][A[i] - A[j]] == 0 ? 2 : dp[j][A[i] - A[j]] + 1 ; ret = max (ret, dp[i][A[i] - A[j]]); } } return ret; } };
1028. Recover a Tree From Preorder Traversal
Intution:
递归构造Tree。难点在于字符串的处理,如何迅速并bug-free地实现。
时间复杂度:O(N * M), 每个节点最多遍历一次字符串;
空间复杂度:O(N), 需要存储最后的树,递归深度最多为N,递归函数本身内存消耗O(1).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Solution { TreeNode* dfs (string& S, int begin, int end, int depth) { string delimiter (depth + 1 , '-' ) ; int first_dash = S.find ('-' , begin); int val_end_index = min (end, first_dash == string::npos ? numeric_limits<int >::max () : first_dash); TreeNode* ret = new TreeNode (stoi (S.substr (begin, val_end_index - begin))); int left_begin = S.find (delimiter, begin); if (left_begin == string::npos || left_begin >= end) { return ret; } int current_find_index = left_begin + delimiter.size (); int left_end = S.find (delimiter, current_find_index); current_find_index = (left_end == string::npos) ? end : left_end + delimiter.size (); while (left_end != string::npos && left_end < end && S[current_find_index] == '-' ) { while (S[current_find_index] == '-' ) ++current_find_index; left_end = S.find (delimiter, current_find_index); current_find_index = (left_end == string::npos) ? end : left_end + delimiter.size (); } ret->left = dfs (S, left_begin + delimiter.size (), left_end < end && left_end != string::npos ? left_end : end, depth + 1 ); int right_begin = current_find_index; if (right_begin < end) ret->right = dfs (S, right_begin, end, depth + 1 ); return ret; } public : TreeNode* recoverFromPreorder (string S) { return dfs (S, 0 , S.size (), 0 ); } };
由于自己的实现虽然想法简单,但实现不易,而且容易写错。我自己就调试了很长时间。
于是去评论区找到了更 优的方案,Iterative Stack.
时间复杂度: O(M), one pass.
空间复杂度: O(N).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Solution {public : TreeNode* recoverFromPreorder (string S) { stack<TreeNode*> s; int i = 0 ; while (i < S.size ()) { int value = 0 , level = 0 ; while (i < S.size () && S[i] == '-' ) { ++level; ++i; } while (i < S.size () && S[i] != '-' ) { value = value * 10 + S[i] - '0' ; ++i; } while (s.size () > level) { s.pop (); } auto node = new TreeNode (value); if (!s.empty () && !s.top ()->left) { s.top ()->left = node; } else if (!s.empty ()) { s.top ()->right = node; } s.push (node); } TreeNode* ret; while (!s.empty ()) { ret = s.top (); s.pop (); } return ret; } };
后记
每周在这里总结一下最近生活的进展吧。
Google summer of code 的申请放弃了。理由是看了一些感兴趣的organization,发现proposal的要求很高。自己很多都达不到。另一方面,自己开源项目的经历确实不够。能拿出手的仅仅是去年在Apache/HAWQ上的一次Contribution。今年可以多参加一些开源活动,丰富简历。
微软的summer intern的笔试的后续结果还没有。Action Center的最新的更新还处于3月31号。
最近在读Lippman的《Essential C++》,侯捷老师在翻译的序里有这么一段话,送给大家。
有些人的学习,自练一身铜筋铁骨,可以在热带丛林中披襟斩棘,在莽莽草原中追奔逐北。有些人的学习,既未习惯大部头书,也未习惯严谨格调,更未习惯自修勤学,是温室里的一朵花,没有自强自立的本钱。
不知道大家怎么样。我反思自己学习的过程,常常“趋利避害”,喜欢简单的皮毛,面对需要花费大量时间钻研的技术即浅尝辄止。而我也知道,作为一名“程序员”,真正拉开与别人距离的都是需要沉下心去钻研的。放眼其他领域,其实也是这样。由于大家都是理智的人,拥有惰性的人,简单的好事一定是趋之若鹜的,做的人多了,供需平衡一打破,门槛一定也日渐提高。
侯老师的话让我醍醐灌顶,自己也需每天反思自己的学习状态,即使纠正错误的思想和方法。这样才能走的更远,飞的更高。