今天是开工后第一次参加LeetCode weekly contest,共作出3道题目,排名为772 / 4174。看着每次排名从200+落到了700+,心情蛮失落的。我认为排名掉落的原因有:1. 排名为200是状态和运气都比较好的情况,之前大多数时候也是700左右。2. 第3题虽然为Hard,最后提交TLE了。但我认为如果再多给半个小时就可以AC了。之所以后面时间不够了,与第2、4题花了比较多时间调试直接相关。还是很多实现不够熟悉,比如bfs, backtracking,不能灵活地默写出来。甚至在做第4题的时候,还需要现场查C++的API,对语言的熟悉程度也不够。
993. Cousins in Binary Tree
在一棵二叉树上找表兄弟。所谓“表兄弟”的定义为,2个节点在同一层,但父节点不同。
Intuition: 递归地找节点的父亲和层数,比较2个节点的父亲和层数是否相同。也即,dfs遍历2遍二叉树。
时间复杂度:O(N),
空间复杂度: 平均O(Log N),最差O(N), 因为需要递归栈。
由于春节假期期间看了LeetCode上的递归专题Introduction to Algorithms - Recursion I 。10min搞定该签到题。
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 { pair<int , int > findNode (TreeNode* root, int value, int parent, int depth) { if (!root) return {0 , depth + 1 }; if (root->val == value) return {parent, depth + 1 }; auto left = findNode (root->left, value, root->val, depth + 1 ); auto right = findNode (root->right, value, root->val, depth + 1 ); if (left.first != 0 ) return left; if (right.first != 0 ) return right; return {0 , depth + 1 }; } public : bool isCousins (TreeNode* root, int x, int y) { auto xNode = findNode (root, x, 0 , 0 ); auto yNode = findNode (root, y, 0 , 0 ); if (xNode.first != yNode.first && xNode.second == yNode.second) return true ; return false ; } };
994. Rotting Oranges
给定一个网格,每个格子有3种状态
什么也没有
好橘子
坏橘子
每天坏橘子会将临近的好橘子变成坏橘子,求所有橘子变坏的天数。如果无穷大的话,返回-1.
Intuition:
从每个坏橘子开始做bfs,一次感染好橘子,并更新好橘子坏掉的天数。因为要计算好橘子距离坏橘子的最短距离,所以一定要用bfs。
检查所有好橘子坏掉的天数,如果有无穷大的橘子,返回-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 49 50 51 52 53 54 55 56 57 class Solution { void bfs (int i, int j, vector<vector<int >>& grid, vector<vector<bool >>& visited, vector<vector<int >> &minDays, int depth) { vector<int > di = {1 , 0 , -1 , 0 }; vector<int > dj = {0 , 1 , 0 , -1 }; queue<tuple<int , int , int >> q; q.push ({i ,j, depth}); while (!q.empty ()) { tuple<int , int , int > current = q.front (); q.pop (); i = get <0 >(current); j = get <1 >(current); depth = get <2 >(current); visited[i][j] = true ; minDays[i][j] = min (minDays[i][j], depth); for (int k = 0 ; k < di.size (); k++) { int new_i = i + di[k]; int new_j = j + dj[k]; if (new_i < grid.size () && new_i >= 0 && new_j < grid[0 ].size () && new_j >= 0 && grid[new_i][new_j] == 1 && visited[new_i][new_j] == false ) q.push ({new_i, new_j, depth + 1 }); } } } public : int orangesRotting (vector<vector<int >>& grid) { vector<vector<int >> minDays (grid.size (), vector <int > (grid[0 ].size (), numeric_limits<int >::max ())); for (int i = 0 ; i < grid.size (); ++i) { for (int j = 0 ; j < grid[i].size (); ++j) { if (grid[i][j] != 1 ) minDays[i][j] = 0 ; } } for (int i = 0 ; i < grid.size (); ++i) { for (int j = 0 ; j < grid[i].size (); ++j) { if (grid[i][j] == 2 ) { vector<vector<bool >> visited (grid.size (), vector <bool > (grid[0 ].size (), false )); bfs (i, j, grid, visited, minDays, 0 ); } } } int result = 0 ; for (int i = 0 ; i < grid.size (); ++i) { for (int j = 0 ; j < grid[i].size (); ++j) { result = max (result, minDays[i][j]); } } return result == numeric_limits<int >::max () ? -1 : result; } };
Solution中也是使用dfs的,不过trick的一点是,通过记录每个节点的深度,在搜索开始时把所有坏橘子加入队列,使得每个节点只需要遍历一遍。更清晰,时间复杂度也更低。比我写的代码要好。
995. Minimum Number of K Consecutive Bit Flips
给定一个数组A,A中值包含0,1这两种状态。给定一个K,提供操作flip,可以将A中长度为K的子数组翻转。
求将A全部翻转为1所需的最小flip次数。
如果无法全部翻转为1,则返回-1.
Intution:
如果要最后都变为1,如果第一位是0的话,必须对从第一位开始的子数组从事flip操作。
这样想下去,必须从头依次翻转所有为0的子数组,如果最后K位中仍然有0,则无法全部翻转。
这其实是一种贪心(Greedy)的算法。
时间复杂度:O(N * K), 结果超时。
空间复杂度: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 class Solution {public : int minKBitFlips (vector<int >& A, int K) { int result = 0 ; int i; for (i = 0 ; i < A.size (); i++) { if (A[i] == 0 ) { if (i + K > A.size ()) { break ; } for (int j = 0 ; j < K; j++) { if (A[i + j] == 0 ) { A[i + j] = 1 ; } else { A[i + j] = 0 ; } } result++; } } if (i < A.size ()) return -1 ; return result; } };
其实,仔细想想,我们并不需要实际进行翻转操作,只需要记录翻转的次数就可以了。所以时间上*K 其实不是必须的。
但是,不实际进行翻转的话,怎么确定一个位置上是0需要翻转,还是1需要翻转。
我们每次记录翻转的结束位置,在此之前的长度为K的子数组是经过flip的,2个flip操作还可以直接抵消。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : int minKBitFlips (vector<int >& A, int K) { int result = 0 ; vector<int > flip (A.size(), 0 ) ; int i; int zero = 0 ; for (i = 0 ; i < A.size (); i++) { zero ^= flip[i]; if (A[i] == zero) { if (i + K > A.size ()) { break ; } result++; zero ^= 1 ; if (i + K < A.size ()) flip[i + K] ^= 1 ; } } if (i < A.size ()) return -1 ; return result; } };
996. Number of Squareful Arrays
如果一个数组任意相邻的2个数的和是某个数的平方,则称这个数组为Squareful Array.
给定一个数组A,求出A的全排列数组中有多少Squareful Arrays。
Intution:
使用backtracking,剪枝的条件是最后相邻的2个数之和不是Perfect Square。
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 49 50 51 52 53 54 55 56 57 class Solution { int count = 0 ; bool isPerfectSquare (long double x) { long double sr = sqrt (x); return ((sr - floor (sr)) == 0 ); } void backtracking (vector<int >& current, multiset<int >& condidates) { int n = current.size (); if (condidates.size () == 0 ) { count++; return ; } for (auto i = condidates.begin (); i != condidates.end ();) { int c = *i; int sum_; if (n > 0 ) { sum_ = current[n - 1 ] + c; if (isPerfectSquare ((long double ) sum_)) { current.push_back (c); auto it = condidates.find (c); condidates.erase (it); backtracking (current, condidates); current.pop_back (); condidates.insert (c); } } else { current.push_back (c); auto it = condidates.find (c); condidates.erase (it); backtracking (current, condidates); current.pop_back (); condidates.insert (c); } i = condidates.upper_bound (c); } } public : int numSquarefulPerms (vector<int >& A) { vector<int > current; multiset<int > condidates; condidates.insert (A.begin (), A.end ()); backtracking (current, condidates); return count; } };
后记
我还没有重新进入学习状态,保持学习的习惯。重新进入一种上进的状态是很难的,我要学会珍惜再珍惜,一定不要再次回到安逸的状态。因为我知道,安逸的状态不好,重新进入上进的状态更难。
每天要有规律地学习和运动,完成新年目标和最初的梦想。