LeetCode OJ is a platform for preparing technical coding interviews.
LeetCode OJ 是为与写代码有关的技术工作面试者设计的训练平台。
LeetCode OJ:http://oj.leetcode.com/
默认题目顺序为题目添加时间倒叙,本文题解顺序与OJ题目顺序一致(OJ会更新,至少目前一致。。。)。 习惯大括号换行,遇到大括号默认不换行的样例代码,导致有的换行有的没换行,实在懒得一个个改……
2015.11.17更新:今天又看leetcode,发现题目列表终于有编号了。 咦,新题目加锁了,要25 dollar一个月的捐赠……
Made By:CSGrandeur : LeetCode 题解
20170209
基本字典树
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 58 class Trie {public : struct Node { Node *nex[26 ]; bool word; Node (){memset (nex, NULL , sizeof (nex)), word = false ;} }; Node *root; Trie () { root = new Node; } void insert (string word) { Node *p = root; for (auto c : word) { if (p->nex[c - 'a' ] == NULL ) p->nex[c - 'a' ] = new Node; p = p->nex[c - 'a' ]; } p->word = true ; } bool search (string word) { Node *p = root; for (auto c : word) { if (p->nex[c - 'a' ] == NULL ) return false ; p = p->nex[c - 'a' ]; } return p->word; } bool startsWith (string prefix) { Node *p = root; for (auto c : prefix) { if (p->nex[c - 'a' ] == NULL ) return false ; p = p->nex[c - 'a' ]; } return true ; } };
20170208
先构造个图。一开始没想拓扑排序的事,就用DFS来判断的,标记-1表示尚未考察
,0表示暂没结果
,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 class Solution {public : vector<int > fst, nex, v; vector<int > ok; bool DFS (int i) { if (ok[i] != -1 ) return ok[i]; ok[i] = 0 ; for (int j = fst[i]; j != -1 ; j = nex[j]) if (!DFS (v[j])) return ok[i] = 0 ; return ok[i] = 1 ; } bool canFinish (int numCourses, vector<pair<int , int >>& prerequisites) { fst.resize (numCourses + 1 , -1 ); ok.resize (numCourses + 1 , -1 ); for (int i = 0 ; i < prerequisites.size (); i ++) { nex.push_back (fst[prerequisites[i].first]); fst[prerequisites[i].first] = nex.size () - 1 ; v.push_back (prerequisites[i].second); } for (int i = numCourses - 1 ; i >= 0 ; i --) numCourses -= DFS (i); return !numCourses; } };
20170208
好像又是老题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : ListNode* reverseList (ListNode* head) { ListNode *pre = NULL ; while (head != NULL ) { ListNode *tmp = head->next; head->next = pre; pre = head; head = tmp; } return pre; } };
20170208
思路一是两个map互相映射,出现矛盾则说明不同构。两个map用于解决其中一个串可能有多个字符对应另一个串同一个字符,只用一个map则会掉这个坑。、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : bool isIsomorphic (string s, string t) { vector<char > mp1 (128 , 0 ) , mp2 (128 , 0 ) ; for (int i = 0 ; i < s.length (); i ++) { if (!mp1[s[i]]) mp1[s[i]] = t[i]; if (!mp2[t[i]]) mp2[t[i]] = s[i]; if (mp1[s[i]] != t[i] || mp2[t[i]] != s[i]) return false ; } return true ; } };
思路二是把俩字符串按同一规则转换,转换后字符串直接比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : void convert (string &s) { vector<char > mp (128 , 0 ) ; int p = 1 ; for (int i = 0 ; i < s.length (); i ++) { if (!mp[s[i]]) mp[s[i]] = p ++; s[i] = mp[s[i]]; } } bool isIsomorphic (string s, string t) { convert (s); convert (t); return s == t; } };
20170208
筛素数,以前比赛时候写的滚瓜烂熟的,现在竟然忘了还复习一下…… 从2开始把每个其倍数都标记成合数,然后往后枚举,枚举到的没标记的一定是素数(如果它是合数,它的质因数肯定比它小,而比它小的质数的倍数都已经标记过了),继续标记其所有倍数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : int countPrimes (int n) { int ret = 0 ; vector<bool > pr (n, true ) ; for (int i = 2 ; i < n; i ++) { if (pr[i]) { ret ++; for (int j = i << 1 ; j < n; j += i) pr[j] = false ; } } return ret; } };
20170208
好像又是老题,开头加个哨兵节点就不用担心head节点被删除的情况了。
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 : ListNode* removeElements (ListNode* head, int val) { ListNode *pre = new ListNode (0 ); pre->next = head; for (ListNode *p = pre; head != NULL ; head = head->next) { if (head->val == val) { p->next = head->next; } else p = head; } return pre->next; } };
20170208
直接思路是用哈希记录判循环,降低空间复杂度可以用两个值一个每次算一步,另一个每次算两步来判循环。 还有个方法是只要大于 6 就一直进行,能保证计算结束,不知道怎么证明的。 代码放传统思路。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : int SumSquare (int x) { int ret = 0 ; while (x) { ret += (x % 10 ) * (x % 10 ); x /= 10 ; } return ret; } bool isHappy (int n) { unordered_set<int > s; while (!s.count (n)) { s.insert (n); n = SumSquare (n); } return s.count (1 ); } };
20170208
二进制从左往右看,当遇到某位不相等的时候,最终结果后面的位都会是0,因为在n~m
之间,连续的数字中这些位肯定会出现0。题目巧在如果思路转换为求二进制最长相同前缀,代码就很简单了。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : int rangeBitwiseAnd (int m, int n) { int cnt = 0 ; while (m != n) { m >>= 1 ; n >>= 1 ; cnt ++; } return m << cnt; } };
还有一种理解方式也很好,把n的右侧的1一一去掉,当n不大于m的时候,n剩下的就是那个相同前缀。
1 2 3 4 5 6 7 8 class Solution {public : int rangeBitwiseAnd (int m, int n) { while (n > m) n = n & (n - 1 ); return n; } };
20170208
我一定是做了道假题。。DFS、BFS随便做,好陈旧。
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 class Solution {public : const int dx[4 ] = {1 , -1 , 0 , 0 }; const int dy[4 ] = {0 , 0 , 1 , -1 }; int row, col; void BFS (vector<vector<char >> &grid, int x, int y) { queue<pair<int , int > > q; q.push (make_pair (x, y)); grid[x][y] = 0 ; while (!q.empty ()) { pair<int , int > now = q.front (); q.pop (); for (int i = 0 ; i < 4 ; i ++) { pair<int , int > nex = make_pair (now.first + dx[i], now.second + dy[i]); if (nex.first >= 0 && nex.first < row && nex.second >= 0 && nex.second < col && grid[nex.first][nex.second] == '1' ) { grid[nex.first][nex.second] = 0 ; q.push (nex); } } } } int numIslands (vector<vector<char >>& grid) { int ret = 0 ; if (grid.size () == 0 ) return 0 ; row = grid.size (); col = grid[0 ].size (); for (int i = 0 ; i < grid.size (); i ++) { for (int j = 0 ; j < grid[0 ].size (); j ++) { if (grid[i][j] == '1' ) ret ++, BFS (grid, i, j); } } return ret; } };
20170208
二叉树层次遍历,加个层数标记。
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 class Solution {public : struct Node { TreeNode *p; int layer; Node (TreeNode *p_, int layer_):p (p_), layer (layer_){} }; vector<int > rightSideView (TreeNode* root) { queue<Node> q; vector<int > ret; if (root != NULL ) q.push (Node (root, 0 )); int last = -1 ; while (!q.empty ()) { Node now = q.front (); q.pop (); if (last != now.layer) { last = now.layer; ret.push_back (now.p->val); } if (now.p->right != NULL ) q.push (Node (now.p->right, now.layer + 1 )); if (now.p->left != NULL ) q.push (Node (now.p->left, now.layer + 1 )); } return ret; } };
20170207
简单的动态规划,两个数一个表示上一个没rob
的当前最佳收益,另一个表示上一个rob了
的,反复更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : int rob (vector<int >& nums) { int dp[2 ] = {0 }; for (auto m : nums) { int tmp = dp[1 ]; dp[1 ] = max (dp[1 ], dp[0 ] + m); dp[0 ] = max (dp[0 ], tmp); } return max (dp[1 ], dp[0 ]); } };
20170207
做过树状数组应该对n&(n-1)
很熟了,n-1
让二进制末尾的1变0,这个1后面的0都变1,和n
做按位与就相当于去掉了最后一个1,这样每次操作都能去掉一个1,一位一位统计就快多了。
1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : int hammingWeight (uint32_t n) { int cnt = 0 ; while (n) { cnt ++; n = n & (n - 1 ); } return cnt; } };
20170207
首先是最直接的方法,把所有bit放到新数中的对应位置。 题目提到一句“If this function is called many times, how would you optimize it?”是很值得思考的。直接的方法进行了32次操作,每个操作里有若干次位运算,这个数字是否可以优化呢? 一个可能思路是并行的分治。问题二分,并行用位运算实现,这样就log(32)=5
次操作了。
直接思路:
1 2 3 4 5 6 7 8 9 class Solution {public : uint32_t reverseBits (uint32_t n) { uint32_t ret = 0 ; for (int i = 0 ; i < 32 ; i ++) ret |= ((n >> i) & 1 ) << (31 - i); return ret; } };
并行分治思路:
1 2 3 4 5 6 7 8 9 10 11 class Solution {public : uint32_t reverseBits (uint32_t n) { n = (n & 0x0000FFFF ) << 16 | (n & 0xFFFF0000 ) >> 16 ; n = (n & 0x00FF00FF ) << 8 | (n & 0xFF00FF00 ) >> 8 ; n = (n & 0x0F0F0F0F ) << 4 | (n & 0xF0F0F0F0 ) >> 4 ; n = (n & 0x33333333 ) << 2 | (n & 0xCCCCCCCC ) >> 2 ; n = (n & 0x55555555 ) << 1 | (n & 0xAAAAAAAA ) >> 1 ; return n; } };
20170207
空间复杂度不是O(1)
的想都不要想。网上方法已经很多了,如: 1、后k
个翻转,前面的翻转,再整个翻转。 2、前k
个和后k
个一一swap,就变成后n-k
个做k
旋转的子问题了,继续进行到结束。 3、把数组分成若干个以k
为间隔的“子数组”,而且这个“子数组”是可以循环回去的,比如1, 2, 3, 4, 5
, k=3
,“子数组”就是1, 4, 2, 5, 3
,对它做step=1
的右循环。 这里放 方法3 的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : void rotate (vector<int >& nums, int k) { k = k % nums.size (); int readynum = 0 ; for (int i = 0 ; readynum < nums.size (); i ++) { int tmp = nums[i]; for (int j = i + k; ; j = (j + k) % nums.size ()) { swap (tmp, nums[j]); readynum ++; if (j == i) break ; } } } };
20170207
一开始思路是常规的记忆化搜索,状态记录为[第i天价格][剩下k次][是否在买入状态]
,是否买入状态是二值的,所以内存主要在于天数和次数的取值,小trick是k取值过大时候,可缩小到天数的1/2
。结果还是超内存了,虽然网上看到有类似解法没超内存,不过看最大那组数据,还是不适合开二维数组的。
这题有个特别之处,当考察第i
天时,并不关心这是多少天了,而关心的只是当前的盈利情况和剩下多少次交易机会,于是数组缩小为次数k
这一维。两个数组分别表示剩下k
次的买入状态余额和非买入状态余额,状态方程是buy[k]=max(buy[k], sell[k-1] - price); sell[k]=max(sell[k], buy[k] + price);
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : int maxProfit (int k, vector<int >& prices) { if (k > prices.size () / 2 ) k = prices.size () / 2 ; vector<int > buy (k + 1 , -0x3f3f3f3f ) , sell (k + 1 , 0 ) ; int ret = 0 ; for (auto price : prices) { for (int i = k; i > 0 ; i --) { buy[i] = max (buy[i], sell[i - 1 ] - price); } for (int i = k; i > 0 ; i --) { sell[i] = max (sell[i], buy[i] + price); ret = max (sell[i], ret); } } return ret; } };
20170207
没遇到网上说的内存问题,直接unordered_map过掉了。用字典树应该会比较快和稳定吧。 对每个长度为10的串进行统计,大于1次就做记录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : vector<string> findRepeatedDnaSequences (string s) { unordered_map<string, bool > mp; vector<string> ret; for (int i = 10 ; i <= s.length (); i ++) { string tmp = s.substr (i - 10 , 10 ); if (!mp.count (tmp)) mp[tmp] = 0 ; else if (mp[tmp] == 0 ) { ret.push_back (tmp); mp[tmp] = 1 ; } } return ret; } };
一开始用递归写了个复杂逻辑的comp函数,后来发现,主要确定两个字符串 a, b 的 a+b 和 b+a 的大小关系,就能确定 a 和 b 的顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public : static bool cmp (const string &a, const string &b) { return a + b > b + a; } string largestNumber (vector& nums) { vector vs; for (auto num : nums) vs.push_back (to_string (num)); sort (vs.begin (), vs.end (), cmp); if (atoi (vs[0 ].c_str ()) == 0 ) return "0" ; string res = "" ; for (auto v : vs) res += v; return res; } };
从左上角开始 DP 的话,无法做到既贪心当前最优,又保证路径最优。
从右下角开始 DP,则可以只考虑路径最优,dp[i][j]表示从(i, j)位置到终点所需最小health值。
这道题也可以用二分答案来直接从左上角贪心,不过速度会慢很多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public : int calculateMinimumHP (vector& dungeon) { int rows = dungeon.size (), cols = dungeon[0 ].size (); vectordp (rows); for (int i = rows - 1 ; i >= 0 ; i --) { dp[i] = vector (cols, 0 ); for (int j = cols - 1 ; j >= 0 ; j --) { if (i < rows - 1 && j < cols - 1 ) dp[i][j] = max (1 , min (dp[i][j + 1 ], dp[i + 1 ][j]) - dungeon[i][j]); else if (i < rows - 1 ) dp[i][j] = max (1 , dp[i + 1 ][j] - dungeon[i][j]); else if (j < cols - 1 ) dp[i][j] = max (1 , dp[i][j + 1 ] - dungeon[i][j]); else dp[i][j] = max (1 , -dungeon[i][j] + 1 ); } } return dp[0 ][0 ]; } };
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 { public : int calculateMinimumHP (vector& dungeon) { int left = 1 , right = 0x3f3f3f3f , mid; while (left < right) { mid = left + right >> 1 ; if (Test (mid, dungeon)) right = mid; else left = mid + 1 ; } return left; } bool Test (int mid, vector tmp) { int rows = tmp.size (), cols = tmp[0 ].size (); for (int i = 0 ; i < rows; i ++) { for (int j = 0 ; j < cols; j ++) { if (i && j && tmp[i - 1 ][j] + mid > 0 && tmp[i][j - 1 ] + mid > 0 ) tmp[i][j] = max (tmp[i - 1 ][j] + tmp[i][j], tmp[i][j - 1 ] + tmp[i][j]); else if (i && tmp[i - 1 ][j] + mid > 0 ) tmp[i][j] = tmp[i - 1 ][j] + tmp[i][j]; else if (j && tmp[i][j - 1 ] + mid > 0 ) tmp[i][j] = tmp[i][j - 1 ] + tmp[i][j]; else if (i || j) tmp[i][j] = -mid; } } return tmp[rows - 1 ][cols - 1 ] + mid > 0 ; } };
变形的非递归中序遍历。
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 BSTIterator { public : stack s; TreeNode *now; BSTIterator (TreeNode *root) { while (!s.empty ()) s.pop (); now = root; while (now) { s.push (now); now = now->left; } } bool hasNext () { return !s.empty (); } int next () { now = s.top (); s.pop (); TreeNode *tmp = now; now = now->right; while (now) { s.push (now); now = now->left; } return tmp->val; } };
阶乘的每个数中,每个因子 2 和每个因子 5 可以构成一个0,而 2 远比 5 多,比如 8 就有三个 2,于是统计因子 5 的个数。
让输入 n 迭代除以 5,第一次得到 5 的倍数的个数,第二次是 25 倍数的个数,最后得到所有因子 5 的个数。
1 2 3 4 5 6 7 8 9 class Solution { public : int trailingZeroes (int n) { int ret = 0 ; while (n) ret += n /= 5 ; return ret; } };
比用Number算Title容易点,在算每一位时候记得加 1 。
1 2 3 4 5 6 7 8 9 class Solution { public : int titleToNumber (string s) { int ret = 0 ; for (auto c : s) ret = ret * 26 + c - 'A' + 1 ; return ret; } };
很有意思的题目,要求的数占超过一半,那么在O(n)时间里把不同的数抵消掉,“多数数”肯定会被留下来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public : int majorityElement (vector& nums) { int x, cnt = 0 ; for (auto num : nums) { if (cnt == 0 ) x = num, cnt ++; else cnt += num == x ? 1 : -1 ; } return x; } };
跳过 0 的 26 进制,处理每一位的时候要减 1 。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public : string convertToTitle (int n) { string ret; while (n) { ret += (n - 1 ) % 26 + 'A' ; n = (n - 1 ) / 26 ; } reverse (ret.begin (), ret.end ()); return ret; } };
有边界数据,所以直接转成long long做,处理正负号。
对小数部分,每次求余数乘 10 再做下一位,当余数重复出现就是循环了。找重复用unordered_map。
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 : string fractionToDecimal (int numerator, int denominator) { string ret = "" ; long long num = numerator, deno = denominator, nm; if (num * deno < 0 ) ret = "-" ; num = abs (num), deno = abs (deno); ret += to_string (num / deno); if (num % deno == 0 ) return ret; unordered_map mp; ret += '.' ; for (long long now = num % deno * 10 ; now; ) { mp[now] = ret.length (); ret += '0' + now / deno; now = now % deno * 10 ; if (mp.count (now)) { ret.insert (mp[now], 1 , '(' ); ret += ')' ; break ; } } return ret; } };
按.
分开,逐个按数字大小比较。 C++怎么就没个方便的split函数呢。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public : int compareVersion (string version1, string version2) { int vpos1 (0 ) , vpos2 (0 ) , lp1 (0 ) , lp2 (0 ) ; version1 += "." , version2 += "." ; while (vpos1 < version1.length () || vpos2 < version2.length ()) { int vnum1 (0 ) , vnum2 (0 ) ; vpos1 = version1.find ("." , lp1); vpos2 = version2.find ("." , lp2); if (vpos1 < version1.length ()) vnum1 = atoi (version1.substr (lp1, vpos1 - lp1).c_str ()), lp1 = vpos1 + 1 ; if (vpos2 < version2.length ()) vnum2 = atoi (version2.substr (lp2, vpos2 - lp2).c_str ()), lp2 = vpos2 + 1 ; if (vnum1 > vnum2) return 1 ; else if (vnum1 <vnum2) return -1 ; } return 0 ; } };
很久没碰过桶排序了,这道题有个很有意思的推理——把 n 个数放到 n 个桶里,两种情形,1、每个桶一个数。2、存在有多个数的桶,那也一定有其他的空桶。
这两种情况下,最大gap都是某个桶的最小值与它前面最近的有数的桶的最大值之差,所以避免了桶内排序,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 { public : int maximumGap (vector& nums) { if (nums.size () < 2 ) return 0 ; int maxNum = *max_element (nums.begin (), nums.end ()); int minNum = *min_element (nums.begin (), nums.end ()); int gap = ceil ((double )(maxNum - minNum) / (nums.size () - 1 )); int bcnt = nums.size (); if (gap == 0 ) return 0 ; vector<pair<int , int > > bucket (bcnt, make_pair (-1 , -1 )); for (int i = 0 ; i < nums.size (); i ++) { int bsite = (nums[i] - minNum) / gap; if (bucket[bsite].first == -1 || bucket[bsite].first > nums[i]) bucket[bsite].first = nums[i]; if (bucket[bsite].second == -1 || bucket[bsite].second < nums[i]) bucket[bsite].second = nums[i]; } int last = minNum, ans = 0 ; for (int i = 0 ; i < bucket.size (); i ++) { if (bucket[i].first != -1 ) ans = max (ans, bucket[i].first - last); if (bucket[i].second != -1 ) last = bucket[i].second; } return ans; } };
O(logn)
1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public : int findPeakElement (vector& nums) { int left = 0 , right = nums.size () - 1 , mid; while (left < right) { mid = left + right >> 1 ; if (nums[mid] < nums[mid + 1 ]) left = mid + 1 ; else right = mid; } return left; } };
先统计两个链表长度,将 长链表 对准 距离末尾 与短链表长度相同 位置,同步遍历返回交叉点。
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 : ListNode *getIntersectionNode (ListNode *headA, ListNode *headB) { int na, nb; ListNode *ha = headA, *hb = headB; for (na = nb = 0 ; ha || hb;) { if (ha) na ++, ha = ha->next; if (hb) nb ++, hb = hb->next; } while (na > nb) headA = headA->next, na --; while (nb > na) headB = headB->next, nb --; while (headA != headB) headA = headA->next, headB = headB->next; return headA; } };
一开始做的是每个元素存两个数的数组,一个表示数值,一个表示“上一个最小值下标”。然后觉得这样的话,不是最小值的那些位置,其实浪费了空间。 还是用两个数组或者两个栈,一个存数,一个存最小值好了。 看Discuss里24ms的答案也没什么特别的,运行时间的浮动吧。
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 MinStack { public : stack v; stack m; void push (int x) { if (v.empty () || x <= m.top ()) m.push (x); v.push (x); } void pop () { if (v.empty ()) return ; if (!m.empty () && m.top () == v.top ()) m.pop (); v.pop (); } int top () { return v.top (); } int getMin () { return m.top (); } };
首先如果首尾大量相同元素,那么“先去掉末尾连续相同元素”和“二分的时候nums[mid]==nums[left]时left++”这样的方法,都会退化为O(n)。于是尽可能想二分的方法,有了下面这个分治的方法,然而经不起推敲,其实即便分治,还是会退化成O(n)。贴上两种代码吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public : int findMin (vector& nums) { int left = 0 , right = nums.size () - 1 , mid = 0 ; while (left < right && nums[left] >= nums[right]) { mid = left + right >> 1 ; if (nums[mid] > nums[left]) left = mid + 1 ; else if (nums[mid] == nums[left]) left ++; else right = mid; } return nums[left]; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public : int findMin (vector& nums) { findRelMin (nums, 0 , nums.size ()); } int findRelMin (vector& nums, int left, int right) { if (left >= right - 1 ) return nums[left]; int mid = left + right >> 1 ; if (nums[left] == nums[right - 1 ]) { int l = findRelMin (nums, left, mid); int r = findRelMin (nums, mid, right); return min (l, r); } int s = left, e = right; while (left < right) { mid = left + right >> 1 ; if (nums[mid] >= nums[s]) left = mid + 1 ; else right = mid; } return left == e ? nums[s] : nums[left]; } };
二分
1 2 3 4 5 6 7 8 9 10 11 class Solution { public : int findMin (vector& nums) { int left = 0 , right = nums.size (), mid; while (left < right) { mid = left + right >> 1 ; if (nums[mid] >= nums[0 ]) left = mid + 1 ; else right = mid; } return left == nums.size () ? nums[0 ] : nums[left]; } };
维护当前位置连续乘积的最大值 tmpp 和最小值 tmpn ,最大值和最小值都可能由三种情况得到:上一个数的 tmppA[i],上一个数的 tmpn A[i],A[i]本身。
不断更新答案,最终输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : int maxProduct (int A[], int n) { int tmpp = A[0 ], tmpn = A[0 ], tmp, ans = A[0 ]; for (int i = 1 ; i < n; i ++) { tmp = tmpp; tmpp = max (max (A[i], A[i] * tmpp), A[i] * tmpn); tmpn = min (min (A[i], A[i] * tmp), A[i] * tmpn); ans = max (ans, tmpp); } return ans; } };
先翻转整个字符串,然后从前往后一个单词一个单词地再翻转一次,同时去除多余空格,等于是扫描两遍,O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : void reverseWords (string &s) { reverse (s.begin (), s.end ()); int start = 0 , end = 0 , j = 0 ; while (start != s.length ()) { while (start != s.length () && s[start] == ' ' ) start ++; for (end = start; end != s.length () && s[end] != ' ' ; end ++); if (j != 0 && start <= end - 1 ) s[j ++] = ' ' ; for (int i = end - 1 ; start < i; start ++, i --) swap (s[i], s[start]), s[j ++] = s[start]; while (start < end) s[j ++] = s[start ++]; } s.resize (j); } };
逆波兰表达式计算四则运算。用栈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : int evalRPN (vector &tokens) { int a, b; stack s; for (int i = 0 ; i < tokens.size (); i ++) { if (isdigit (tokens[i][0 ]) || tokens[i].length () > 1 ) { s.push (atoi (tokens[i].c_str ())); continue ; } a = s.top ();s.pop (); b = s.top ();s.pop (); switch (tokens[i][0 ]) { case '+' : s.push (b + a); break ; case '-' : s.push (b - a); break ; case '*' : s.push (b * a); break ; case '/' : s.push (b / a); break ; } } return s.top (); } };
平面上一条直线最多穿过多少点。乍一看好熟悉的问题,做了这么久计算几何。。。却还真没做过这个小问题。
第一反应当然不能O(n^3)枚举了,枚举圆周好像也不行,毕竟是考察所有点,不是某个点。那么应该就是哈希斜率了吧。
肯定少不了竖直的线,哈希斜率这不像是这类OJ让写的题吧。。忘了map这回事了。
确定思路之后,还是看看别人博客吧,少走点弯路,然后就学习了还有unordered_map这么个东西,还有一个博客 的思路挺好,避免double问题,把斜率转化成化简的x、y组成字符串。
再另外就是重叠的点了,想让题目坑一点,怎能少得了这种数据,单独处理一下。
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 class Solution {public : int maxPoints (vector &points) { int ans = 0 ; for (int i = 0 ; i < points.size (); i ++) { unordered_map<string, int > mp; int tmpans = 0 , same = 0 ; for (int j = i + 1 ; j < points.size (); j ++) { int x = points[j].x - points[i].x, y = points[j].y - points[i].y; int g = gcd (x, y); if (g != 0 ) x /= g, y /= g; else {same ++; continue ;} if (x < 0 ) x = -x, y = -y; string tmp = to_string (x) + " " + to_string (y); if (!mp.count (tmp)) mp[tmp] = 1 ; else mp[tmp] ++; tmpans = max (tmpans, mp[tmp]); } ans = max (tmpans + 1 + same, ans); } return ans; } int gcd (int a, int b) { return a ? gcd (b % a, a) : b; } };
又长见识了,原来链表也可以O(nlogn)排序的。没往下想就查了一下,看到人说用归并,于是才开始想能不能实现。。。
O(n)找到中点,把中点的next变成NULL,对两部分递归。递归结束后对两部分归并,先找到newhead,即两部分的头部val较小的那个,然后归并就把小的从newhead往后续。
把最后的next赋值NULL,返回newhead。
又有空数据@_@.
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 class Solution {public : ListNode *sortList (ListNode *head) { int n = 0 ; ListNode *p = head; while (p != NULL ) n ++, p = p->next; if (n <= 1 ) return head; n >>= 1 ; p = head; while (-- n) p = p->next; ListNode *tmp = p->next; p->next = NULL ; ListNode *nl = sortList (head); ListNode *nr = sortList (tmp); ListNode *newhead; if (nl->val < nr->val) { newhead = nl; nl = nl->next; } else { newhead = nr; nr = nr->next; } p = newhead; while (nl != NULL && nr != NULL ) { if (nl->val < nr->val) p->next = nl, p = p->next, nl = nl->next; else p->next = nr, p = p->next, nr = nr->next; } while (nl != NULL ) p->next = nl, p = p->next, nl = nl->next; while (nr != NULL ) p->next = nr, p = p->next, nr = nr->next; p->next = NULL ; return newhead; } };
指针操作很烦啊。。暴力枚举插入位置,注意细节就能过了。
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 class Solution {public : ListNode *insertionSortList (ListNode *head) { ListNode *newhead = head; if (head == NULL ) return NULL ; head = head->next; newhead->next = NULL ; while (head != NULL ) { if (head->val < newhead->val) { ListNode *tmp = head->next; head->next = newhead; newhead = head; head = tmp; continue ; } ListNode *pre = newhead, *p = newhead->next; while (p != NULL && p->val < head->val) { p = p->next; pre = pre->next; } pre->next = head; head = head->next; pre = pre->next; pre->next = p; } return newhead; } };
新建数据类class Val,保存key、val和访问自增标记updatecnt。
用unordered_map更新数据,增加updatecnt,并把更新的数据放入队列,最关键是处理capacity满了的时候,队列依次出队,map中不存在的和updatecnt和最新数据不相等的项目都忽略,直到发现updatecnt和map中存的最新状态相等,则为(最近未使用)数据,出队后在map中erase。思路有点像STL队列实现版本的Dijkstra。
有一个博客 的方法更好,map中存的是链表的节点指针,链表顺序表示访问情况,这样就把map内容和链表的每个节点一一对应了,没有冗余节点,且更新操作也是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 49 50 51 52 53 54 55 56 57 class Val {public : int key; int val; int updatecnt; }; class LRUCache {public : int cap; unordered_map<int , Val> mp; queue q; LRUCache (int capacity) { cap = capacity; } int get (int key) { if (mp.count (key)) { mp[key].updatecnt ++; q.push (mp[key]); return mp[key].val; } return -1 ; } void set (int key, int value) { if (mp.count (key)) { mp[key].val = value; mp[key].updatecnt ++; q.push (mp[key]); } else { if (mp.size () == cap) { Val tmp; while (!q.empty ()) { tmp = q.front (); q.pop (); if (mp.count (tmp.key) && tmp.updatecnt == mp[tmp.key].updatecnt) break ; } mp.erase (mp.find (tmp.key)); mp[key].key = key; mp[key].val = value; mp[key].updatecnt = 0 ; q.push (mp[key]); } mp[key].key = key; mp[key].val = value; mp[key].updatecnt = 0 ; q.push (mp[key]); } } };
二叉树的非递归后序遍历,考研的时候非常熟悉了,现在写又要想好久。重点是关于右子树遍历时候需要一个标记,或者标记根节点出栈次数,或者标记右子树是否访问。
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 class Solution {public : vector postorderTraversal (TreeNode *root) { vector ans; if (root == NULL ) return ans; stack<TreeNode*> s; TreeNode *visited; while (root != NULL || !s.empty ()) { while (root != NULL ) s.push (root), root = root->left; root = s.top (); if (root->right == NULL || visited == root->right) { ans.push_back (root->val); s.pop (); visited = root; root = NULL ; } else { root = root->right; } } return ans; } };
前序遍历的非递归就容易多了。
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 : vector preorderTraversal (TreeNode *root) { stack<TreeNode*> s; vector ans; if (root == NULL ) return ans; s.push (root); while (!s.empty ()) { root = s.top (); s.pop (); ans.push_back (root->val); if (root->right != NULL ) s.push (root->right); if (root->left != NULL ) s.push (root->left); } } };
找到中间位置,把中间之后的链表反转,两个指针一个从头一个从尾开始互插,奇偶和指针绕得有点晕,理清就好了。。
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 class Solution {public : void reorderList (ListNode *head) { int n = 0 ; ListNode *pre, *p = head; while (p) n ++, p = p->next; if (n < 3 ) return ; n >>= 1 ; pre = p = head; p = p->next; while (n --) p = p->next, pre = pre->next; while (p != NULL ) { ListNode *tmp = p->next; p->next = pre; pre = p; p = tmp; } ListNode *tail = pre; p = head; while (true ) { ListNode *tmp1 = p->next, *tmp2 = tail->next; p->next = tail; tail->next = tmp1; p = tmp1; if (p == tail || p == tmp2) break ; tail = tmp2; } p->next = NULL ; } };
设置两个指针slow
和fast
,从head
开始,slow
一次一步,fast
一次两步,如果fast
能再次追上slow
则有圈。 设slow
走了n
步,则fast
走了2*n
步,设圈长度m,圈起点到head
距离为k
,相遇位置距离圈起点为t
,则有:
n = k + t + pm;
(1)
2*n = k + t + qm;
(2)
这里p和q是任意整数。(不过fast速度是slow二倍,则肯定在一圈内追上,p就是0了)
2 * (1) - (2)
得k = lm - t;(l = q - 2 * p)
即 k
的长度是若干圈少了 t
的长度。 因此这时候,一个指针从head
开始,另一个从相遇位置开始,都每次只走一步,当从head
开始的指针走到圈开始位置时,两指针刚好相遇。
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 class Solution {public : ListNode *detectCycle (ListNode *head) { if (head == NULL ) return NULL ; ListNode *slow, *fast; slow = fast = head; int n = 0 ; do { n ++; if (slow == NULL || fast == NULL ) return NULL ; slow = slow->next; fast = fast->next; if (fast == NULL ) return NULL ; fast = fast->next; if (fast == NULL ) return NULL ; }while (slow != fast); fast = head; while (slow != fast) slow = slow->next, fast = fast->next; return fast; } };
呃,时间逆序做的结果。。。成买一送一了。
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 class Solution {public : bool hasCycle (ListNode *head) { if (head == NULL ) return false ; ListNode *slow, *fast; slow = fast = head; int n = 0 ; do { n ++; if (slow == NULL || fast == NULL ) return NULL ; slow = slow->next; fast = fast->next; if (fast == NULL ) return NULL ; fast = fast->next; if (fast == NULL ) return NULL ; }while (slow != fast); return true ; } };
先递推,dp[i] == true
表示 s
中前 i
个字符的串是符合要求的,枚举位置 i
,对于 i
枚举位置 j < i
,如果 dp[j] == true
且 j ~ i
的串在字典中,则dp[i] = true
。
同时对于这样的 j
, i
,site[i].push_back(j)
,即在 i
位置的可行迭代表中增加位置 j
。
完成site之后,从尾部倒着DFS过去就得到了所有串。
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 class Solution {public : vector DFS (const string &s, vector *site, int ith) { vector res; for (int i = 0 ; i < site[ith].size (); i ++) { vector tmp; string str = s.substr (site[ith][i], ith - site[ith][i]); if (site[site[ith][i]].size () == 0 ) res.push_back (str); else { tmp = DFS (s, site, site[ith][i]); for (int j = 0 ; j < tmp.size (); j ++) res.push_back (tmp[j] + " " + str); } } return res; } vector wordBreak (string s, unordered_set &dict) { vector *site = new vector[s.length () + 1 ]; bool *dp = new bool [s.length () + 1 ]; memset (dp, 0 , sizeof (bool ) * s.length ()); dp[0 ] = true ; for (int i = 1 ; i <= s.length (); i ++) { for (int j = 0 ; j < i; j ++) { if (dp[j] == true && dict.count (s.substr (j, i - j))) site[i].push_back (j), dp[i] = true ; } } return DFS (s, site, s.length ()); } };
参考Word Break II,对于dp标记,当dp[i]为true时候可以停止枚举后面的 j,优化一下常数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : bool wordBreak (string s, unordered_set &dict) { bool *dp = new bool [s.length () + 1 ]; memset (dp, 0 , sizeof (bool ) * (s.length () + 1 )); dp[0 ] = true ; for (int i = 1 ; i <= s.length (); i ++) for (int j = 0 ; j < i; j ++) { dp[i] = dp[i] || dp[j] && dict.count (s.substr (j, i - j)); } return dp[s.length ()]; } };
第一次遍历,把每个节点复制一份放到对应节点的下一个,即组成二倍长的链表:ori1->copy1->ori2->copy2->...
第二次遍历,利用复制节点总是对应节点的下一个节点
特性,将每个ori->next->random
指向ori->random->next
,中间判断一下空指针。
第三次遍历,把两个链表拆开,恢复原链表。
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 class Solution {public : RandomListNode *copyRandomList (RandomListNode *head) { RandomListNode *p = head, *newhead = NULL , *tmp; if (p == NULL ) return NULL ; while (p != NULL ) { tmp = new RandomListNode (p->label); tmp->next = p->next; p->next = tmp; p = tmp->next; } newhead = head->next; p = head; while (p != NULL ) { tmp = p->next; tmp->random = p->random == NULL ? NULL : p->random->next; p = tmp->next; } p = head; while (p != NULL ) { tmp = p->next; p->next = tmp->next; p = tmp->next; tmp->next = p == NULL ? NULL : p->next; } return newhead; } };
方法一:设置cnt[32]记录 32个比特位的1的个数,出现3次的数的对应位的1总数为3的倍数,则统计之后每个位对3取模,剩下的位为1的则对应个数为1的那个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int singleNumber (int A[], int n) { int cnt[32 ] = {0 }; for (int i = 0 ; i < n; i ++) { int tmp = A[i]; for (int j = 0 ; j < 33 ; tmp >>= 1 , j ++) cnt[j] += tmp & 1 ; } int ans = 0 ; for (int i = 0 ; i < 32 ; i ++) ans |= (cnt[i] % 3 ) << i; return ans; } };
方法二:设置int one, two模拟两位二进制来统计各比特位1次数,每当one和two对应二进制位都为1的时候把one和two都清零,最后剩下的one就是要求的数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : int singleNumber (int A[], int n) { int one = 0 , two = 0 ; for (int i = 0 ; i < n; i ++) { two |= one & A[i]; one ^= A[i]; int tmp = one & two; two ^= tmp; one ^= tmp; } return one; } };
一路异或过去就可以了。
1 2 3 4 5 6 7 8 9 class Solution {public : int singleNumber (int A[], int n) { int tmp = 0 ; for (int i = 0 ; i < n; i ++) tmp ^= A[i]; return tmp; } };
时间复杂度 O(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 class Solution {public : int candy (vector &ratings) { int cnt = 0 , i, j, start, nownum; for (i = 0 ; i < ratings.size (); i ++) { if (i == 0 || ratings[i] == ratings[i - 1 ]) nownum = 1 ; else if (ratings[i] > ratings[i - 1 ]) nownum ++; if (i + 1 < ratings.size () && ratings[i + 1 ] < ratings[i]) { start = 1 ; for (j = i + 1 ; j < ratings.size () && ratings[j] < ratings[j - 1 ]; start++, j ++); if (start > nownum) cnt += (start + 1 ) * start >> 1 ; else cnt += ((start - 1 ) * start >> 1 ) + nownum; nownum = 1 ; i = j - 1 ; } else cnt += nownum; } return cnt; } };
证明题。
一、如果从 i 到 j 的时候理论计算气量刚好为负数,则 i ~ j 的加气站都不可以作为起点。
反证一下,从前往后去掉站,如果去掉的站能增加气,即正数,则结果更糟。如果去掉的站是负数,那么负数如果抵消了之前的正数,则在到 j 之前已经负数了,如果不能抵消之前的正数,那么结果还是更糟。
二、判断是否能成行,一个环的和为非负就可以。
假设环为正, 0 ~ j 刚好为负, j + 1 ~ k 刚好为负数,k + 1 之后为正,则 k + 1 为答案。
也反证一下,k + 1 出发,到gas.size() - 1都为正,则转回来到 j - 1 都会为正。如果到 j 时候为负,则整个环不可能为正,所以到 j 的时候也为正,剩下的一样。这样就能够成功转一圈。
1 2 3 4 5 6 7 8 9 10 11 class Solution {public : int canCompleteCircuit (vector &gas, vector &cost) { int i, ans, sum, all; for (i = ans = sum = all = 0 ; i < gas.size (); i ++) { sum += gas[i] - cost[i]; all += gas[i] - cost[i]; if (sum < 0 ) { sum = 0 ; ans = i + 1 ; } } return all >= 0 ? ans : -1 ; } };
label是唯一的,递归,用unordered_map标记。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : unordered_map<int , UndirectedGraphNode *> mp; UndirectedGraphNode *cloneGraph (UndirectedGraphNode *node) { if (node == NULL || mp.count (node->label)) return NULL ; UndirectedGraphNode *tmp = new UndirectedGraphNode (node->label); mp[node->label] = tmp; for (int i = 0 ; i < node->neighbors.size (); i ++) { cloneGraph (node->neighbors[i]); tmp->neighbors.push_back (mp[node->neighbors[i]->label]); } return tmp; } };
O(n^2)的动态规划。
cutdp[i]
表示前 i
个字符最小切割几次。
paldp[i][j] == true
表示 i ~ j
是回文。
在枚举 i 和 i 之前的所有 j 的过程中就得到了 paldp[j][i] 的所有回文判断,而对于 i + 1,paldp[j][i + 1]可由 s[j]、s[i + 1]、dp[j + 1][i]在O(1)判断。
cutdp[i]为所有 j (j < i),当paldp[j + 1][i] true的 cutdp[j] + 1的最小值。注意一下边界。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int minCut (string s) { bool paldp[s.length ()][s.length ()]; int cutdp[s.length ()]; for (int i = 0 ; i < s.length (); i ++) { cutdp[i] = 0x3f3f3f3f ; for (int j = i - 1 ; j >= -1 ; j --) { if (s.at (j + 1 ) == s.at (i) && (j + 2 >= i - 1 || paldp[j + 2 ][i - 1 ])) { paldp[j + 1 ][i] = true ; cutdp[i] = min (cutdp[i], (j >= 0 ? (cutdp[j] + 1 ) : 0 )); } else paldp[j + 1 ][i] = false ; } } return cutdp[s.length () - 1 ]; } };
O(n^2)动态规划,paldp[i][j] == true
表示 i ~ j
是回文。这里DP的方法是基本的,不再多说。
得到paldp之后,DFS一下就可以了。因为单字符是回文,所以DFS的终点肯定都是解,所以不必利用其他的结构存储答案信息。
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 class Solution {public : vectorres; vector tmp; bool **paldp; void DFS (string s, int ith) { if (ith == s.length ()) { res.push_back (tmp); return ; } for (int i = ith; i < s.length (); i ++) { if (paldp[ith][i]) { tmp.push_back (s.substr (ith, i - ith + 1 )); DFS (s, i + 1 ); tmp.pop_back (); } } return ; } vector partition (string s) { paldp = new bool *[s.length ()]; for (int i = 0 ; i < s.length (); i ++) paldp[i] = new bool [s.length ()]; for (int i = 0 ; i < s.length (); i ++) for (int j = i; j >= 0 ; j --) paldp[j][i] = s.at (i) == s.at (j) && (j + 1 >= i - 1 || paldp[j + 1 ][i - 1 ]); DFS (s, 0 ); return res; } };
周围四条边的O做起点搜索替换为第三种符号,再遍历所有符号把O换成X,第三种符号换回O。
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 class Solution {public : typedef pair<int , int > pii; int dx[4 ] = {1 , -1 , 0 , 0 }; int dy[4 ] = {0 , 0 , 1 , -1 }; queue q; void solve (vector &board) { if (board.size () == 0 ) return ; int width = board[0 ].size (); int height = board.size (); for (int i = 0 ; i < width; i ++) { if (board[0 ][i] == 'O' ) board[0 ][i] = '#' , q.push (pair <int , int >(0 , i)); if (board[height - 1 ][i] == 'O' ) board[height - 1 ][i] = '#' , q.push (pii (height - 1 , i)); } for (int i = 1 ; i < height - 1 ; i ++) { if (board[i][0 ] == 'O' ) board[i][0 ] = '#' , q.push (pii (i, 0 )); if (board[i][width - 1 ] == 'O' ) board[i][width - 1 ] = '#' , q.push (pii (i, width - 1 )); } while (!q.empty ()) { pii now = q.front (); q.pop (); for (int i = 0 ; i < 4 ; i ++) { int ty = now.first + dx[i]; int tx = now.second + dy[i]; if (tx >= 0 && tx < width && ty >= 0 && ty < height && board[ty][tx] == 'O' ) { board[ty][tx] = '#' ; q.push (pii (ty, tx)); } } } for (int i = 0 ; i < height; i ++) for (int j = 0 ; j < width; j ++) { if (board[i][j] == 'O' ) board[i][j] = 'X' ; else if (board[i][j] == '#' ) board[i][j] = 'O' ; } } };
遍历一遍加起来。。。
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 class Solution {public : int ans; void DFS (TreeNode *now, int tmp) { if (now->left == NULL && now->right == NULL ) { ans += tmp * 10 + now->val; return ; } if (now->left != NULL ) { DFS (now->left, tmp * 10 + now->val); } if (now->right != NULL ) { DFS (now->right, tmp * 10 + now->val); } } int sumNumbers (TreeNode *root) { if (root == NULL ) return 0 ; ans = 0 ; DFS (root, 0 ); return ans; } };
方法一:一开始竟然想了并查集,其实绕弯了,多此一举。哈希+并查集,把每个数哈希,枚举每个数看相邻的数在不在数组里,并查集合并,只是并查集的复杂度要比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 class Solution {public : unordered_map<int , int > mp, cnt; int ans = 1 ; int fa (int i) { i == mp[i] ? i : (mp[i] = fa (mp[i])); } int longestConsecutive (vector &num) { for (int i = 0 ; i < num.size (); i ++) mp[num[i]] = num[i], cnt[num[i]] = 1 ; for (int i = 0 ; i < num.size (); i ++) { if (mp.count (num[i] + 1 ) && fa (num[i]) != fa (num[i] + 1 )) { cnt[fa (num[i] + 1 )] += cnt[fa (num[i])]; ans = max (cnt[fa (num[i] + 1 )], ans); mp[fa (num[i])] = fa (num[i] + 1 ); } } return ans; } };
方法二:哈希+枚举相邻数。相邻的数在数组里的话,每个数至多访问一次;相邻的数不在数组里的话,枚举会中断。所以设哈希复杂度为O(1)的话,这个方法是严格的O(n)。
其实这个题的数据挺善良,如果出了2147483647
, -2147483648
,那还是用long long 稳妥些。
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 class Solution {public : unordered_map<int , bool > vis; int longestConsecutive (vector &num) { int ans = 0 ; for (int i = 0 ; i < num.size (); i ++) vis[num[i]] = false ; for (int i = 0 ; i < num.size (); i ++) { if (vis[num[i]] == false ) { int cnt = 0 ; for (int j = num[i]; vis.count (j); j ++, cnt ++) { vis[j] = true ; } for (int j = num[i] - 1 ; vis.count (j); j --, cnt ++) { vis[j] = true ; } ans = max (ans, cnt); } } return ans; } };
用数组类型的队列,BFS过程中记录pre路径,搜完后迭代回去保存路径。
似乎卡了常数,用queue队列,另外存路径的方法超时了。
想更快就双向广搜吧。让我想起了POJ那个八数码。
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 class Node { public : string str; int pace; int pre; Node (){} Node (string s, int pa, int pr) { str = s; pace = pa; pre = pr; } }; class Solution {public : vector ans; vector findLadders (string start, string end, unordered_set &dict) { vector q; q.push_back (Node (end, 1 , -1 )); unordered_map<string, int > dis; dis[end] = 1 ; for (int i = 0 ; i < q.size (); i ++) { Node now = q[i]; if (dis.count (start) && now.pace >= dis[start]) break ; for (int j = 0 ; j < now.str.length (); j ++) { string tmp = now.str; for (char c = 'a' ; c <= 'z' ; c ++) { tmp[j] = c; if ((dict.count (tmp) || tmp == start) && (!dis.count (tmp) || dis[tmp] == now.pace + 1 )) { dis[tmp] = now.pace + 1 ; q.push_back (Node (tmp, now.pace + 1 , i)); } } } } for (int i = q.size () - 1 ; i >= 0 && q[i].pace == dis[start]; i --) { if (q[i].str == start) { vector tmp; for (int j = i; j != -1 ; j = q[j].pre) tmp.push_back (q[j].str); ans.push_back (tmp); } } return ans; } };
直接BFS。
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 {public : int ladderLength (string start, string end, unordered_set &dict) { typedef pair<string, int > pii; unordered_set flag; queue q; q.push (pii (start, 1 )); while (!q.empty ()) { pii now = q.front (); q.pop (); for (int i = 0 ; i < now.first.length (); i ++) { string tmp = now.first; for (char j = 'a' ; j <= 'z' ; j ++) { tmp[i] = j; if (tmp == end) return now.second + 1 ; if (dict.count (tmp) && !flag.count (tmp)) { q.push (pii (tmp, now.second + 1 )); flag.insert (tmp); } } } } return 0 ; } };
做过刘汝佳 白书的人想必都知道ctype.h和isdigit(), isalpha(), tolower(), toupper()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : bool valid (char &x) { x = tolower (x); return isdigit (x) || isalpha (x); } bool isPalindrome (string s) { if (s.length () == 0 ) return true ; for (int i = 0 , j = s.length () - 1 ; i < j; i ++, j --) { while (!valid (s[i]) && i < s.length ()) i ++; while (!valid (s[j]) && j >= 0 ) j --; if (i < j && s[i] != s[j]) return false ; } return true ; } };
后续遍历,子问题为子树根节点向叶子节点出发的最大路径和。
即 l = DFS(now->left), r = DFS(now->right)。
此时,ans可能是 now->valid,可能是左边一路上来加上now->valid,可能是右边一路上来,也可能是左边上来经过now再右边一路下去,四种情况。
四种情况更新完ans后,now返回上一层只能是 now->valid或左边一路上来或右边一路上来,三种情况。
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 class Solution {public : int ans; int DFS (TreeNode *now) { if (now == NULL ) return 0 ; int l = max (DFS (now->left), 0 ); int r = max (DFS (now->right), 0 ); ans = max (ans, l + r + now->val); return max (l + now->val, r + now->val); } int maxPathSum (TreeNode *root) { if (root == NULL ) return 0 ; ans = -0x3f3f3f3f ; DFS (root); return ans; } };
前缀pre[i]处理 0 ~ i 买卖一次最优解,后缀suf[i]处理 i ~ prices.size() - 1 买卖一次最优解。
所有位置pre[i] + suf[i]最大值为答案O(n)。
处理最优解的时候是维护前(后)缀prices最小(大)值,与当前prices做差后和前(后)缀最优解比较取最优,O(n)。
总复杂度O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int maxProfit (vector &prices) { int ans = 0 ; vector pre (prices.size()) , suf (prices.size()) ; for (int i = 0 , mtmp = 0x3f3f3f3f ; i < prices.size (); i ++) { mtmp = i ? min (mtmp, prices[i]) : prices[i]; pre[i] = max (prices[i] - mtmp, i ? pre[i - 1 ] : 0 ); } for (int i = prices.size () - 1 , mtmp = 0 ; i >= 0 ; i --) { mtmp = i != prices.size () - 1 ? max (mtmp, prices[i]) : prices[i]; suf[i] = max (mtmp - prices[i], i != prices.size () - 1 ? suf[i + 1 ] : 0 ); } for (int i = 0 ; i < prices.size (); i ++) ans = max (ans, pre[i] + suf[i]); return ans; } };
可以买卖多次,把所有上坡差累加即可。
1 2 3 4 5 6 7 8 9 10 class Solution {public : int maxProfit (vector &prices) { int ans = 0 ; for (int i = 1 ; i < prices.size (); i ++) { if (prices[i] > prices[i - 1 ]) ans += prices[i] - prices[i - 1 ]; } return ans; } };
维护前(后)缀最小(大)值,和当前prices做差更新答案。
1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : int maxProfit (vector &prices) { int ans = 0 ; for (int i = prices.size () - 1 , mtmp = 0 ; i >= 0 ; i --) { mtmp = max (mtmp, prices[i]); ans = max (mtmp - prices[i], ans); } return ans; } };
竟然遇到了ACM递推入门题,想必无数ACMer对这题太熟悉了。
从下往上递推,一维数组滚动更新即可。这里懒省事,直接把原数组改了。
1 2 3 4 5 6 7 8 9 10 11 class Solution {public : int minimumTotal (vector &triangle) { for (int i = triangle.size () - 2 ; i >= 0 ; i --) { for (int j = 0 ; j < triangle[i].size (); j ++) triangle[i][j] = min (triangle[i][j] + triangle[i + 1 ][j], triangle[i][j] + triangle[i + 1 ][j + 1 ]); } return triangle.size () == 0 ? 0 : triangle[0 ][0 ]; } };
滚动数组递推,从后往前以便不破坏上一层递推数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : vector getRow (int rowIndex) { vector ans (rowIndex + 1 , 0 ) ; ans[0 ] = 1 ; for (int i = 0 ; i <= rowIndex; i ++) { for (int j = i; j >= 0 ; j --) { ans[j] = (i == 0 || j == 0 || j == i ? 1 : ans[j] + ans[j - 1 ]); } } return ans; } };
递推。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : vector generate (int numRows) { vector v; for (int i = 0 ; i < numRows; i ++) { vector tmp; for (int j = 0 ; j <= i; j ++) { tmp.push_back (i == 0 || j == 0 || j == i ? 1 : v[i - 1 ][j] + v[i - 1 ][j - 1 ]); } v.push_back (tmp); } return v; } };
题目要求空间复杂度O(1),所以递归、队列等传统方法不应该用。
本题可以利用生成的next指针来横向扫描,即得到一层的next指针之后,可以利用这一层的next指针来给下一层的next指针赋值。
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 class Solution {public : TreeLinkNode *findNext (TreeLinkNode *head) { while (head != NULL && head->left == NULL && head->right == NULL ) head = head->next; return head; } void connect (TreeLinkNode *root) { if (root == NULL ) return ; TreeLinkNode *head, *last, *nexhead; for (head = root; head != NULL ; head = nexhead) { head = findNext (head); if (head == NULL ) break ; if (head->left != NULL ) nexhead = head->left; else nexhead = head->right; for (last = NULL ; head != NULL ; last = head, head = findNext (head->next)) { if (head->left != NULL && head->right != NULL ) head->left->next = head->right; if (last == NULL ) continue ; if (last->right != NULL ) last->right->next = head->left != NULL ? head->left : head->right; else last->left->next = head->left != NULL ? head->left : head->right; } } } };
不用考虑连续的空指针,就不用额外实现找下一个子树非空节点,比Populating Next Right Pointers in Each Node II 容易处理。
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 class Solution {public : void connect (TreeLinkNode *root) { if (root == NULL ) return ; TreeLinkNode *head, *nexhead, *last; for (head = root; head->left != NULL ; head = nexhead) { nexhead = head->left; last = NULL ; while (head != NULL ) { head->left->next = head->right; if (last != NULL ) last->right->next = head->left; last = head; head = head->next; } } } };
典型动态规划。dp[i][j] 表示 T 的前 j 个字符在 S 的前 i 个字符中的解。
对于dp[i + 1][j + 1],由两部分组成:
一、 j + 1 对应到 S 前 i 个字符中的解,忽略 S 的第 i + 1 个字符。
二、判断 S 的第 i + 1 个字符是否和 T 的第 j + 1 个字符相同,如果相同,则加上dp[i][j],否则不加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : int numDistinct (string S, string T) { if (S.length () < T.length ()) return 0 ; vector dp (S.length() + 1 , vector(T.length() + 1 , 0 )) ; for (int i = 0 ; i < S.length (); i ++) dp[i][0 ] = 1 ; dp[0 ][1 ] = 0 ; for (int i = 0 ; i < S.length (); i ++) { for (int j = 0 ; j < T.length (); j ++) { dp[i + 1 ][j + 1 ] = dp[i][j + 1 ]; if (S[i] == T[j]) dp[i + 1 ][j + 1 ] += dp[i][j]; } } return dp[S.length ()][T.length ()]; } };
题意是优先左子树靠前,且排成一列用右子树指针,不管val的大小关系。
后序遍历一遍即可,递归返回子树中尾节点指针,注意各种条件判断。
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 class Solution {public : TreeNode *DFS (TreeNode *now) { if (now->left == NULL && now->right == NULL ) return now; TreeNode *leftok = NULL , *rightok = NULL ; if (now->left != NULL ) leftok = DFS (now->left); if (now->right != NULL ) rightok = DFS (now->right); if (leftok != NULL ) { leftok->right = now->right; now->right = now->left; now->left = NULL ; return rightok ? rightok : leftok; } else return rightok; } void flatten (TreeNode *root) { if (root == NULL ) return ; DFS (root); } };
传统递归,把路径上的数字插入vector,终点判断是否插入答案。
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 class Solution {public : int goal; vectorv; vector curv; void DFS (TreeNode *now, int cursum) { curv.push_back (now->val); if (now->left == NULL && now->right == NULL ) { if (cursum + now->val == goal) { v.push_back (curv); curv.pop_back (); return ; } } if (now->left != NULL ) DFS (now->left, cursum + now->val); if (now->right != NULL ) DFS (now->right, cursum + now->val); curv.pop_back (); } vector pathSum (TreeNode *root, int sum) { goal = sum; if (root == NULL ) return v; DFS (root, 0 ); return v; } };
遍历树。
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 : int goal; bool DFS (TreeNode *now, int cursum) { if (now->left == NULL && now->right == NULL ) return cursum + now->val == goal; if (now->left != NULL && DFS (now->left, cursum + now->val)) return true ; if (now->right != NULL && DFS (now->right, cursum + now->val)) return true ; } bool hasPathSum (TreeNode *root, int sum) { goal = sum; if (root == NULL ) return false ; return DFS (root, 0 ); } };
还是遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : int minDepth (TreeNode *root) { if (root == NULL ) return 0 ; if (root->left == NULL ) return minDepth (root->right) + 1 ; else if (root->right == NULL ) return minDepth (root->left) + 1 ; else return min (minDepth (root->left), minDepth (root->right)) + 1 ; } };
遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : int maxDepth (TreeNode *now) { if (now == NULL ) return 0 ; int l = maxDepth (now->left) + 1 ; int r = maxDepth (now->right) + 1 ; return abs (l - r) > 1 || l < 0 || r < 0 ? -2 : max (l, r); } bool isBalanced (TreeNode *root) { return maxDepth (root) >= 0 ; } };
每次找中点作为根节点,将两边递归,返回根节点指针作为左右节点。
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 class Solution {public : TreeNode *sortedListToBST (ListNode *head) { if (head == NULL ) return NULL ; ListNode *p, *mid, *pre; for (p = mid = head, pre = NULL ; p->next != NULL ; mid = mid->next) { p = p->next; if (p->next == NULL ) break ; p = p->next; pre = mid; }; TreeNode *root = new TreeNode (mid->val); if (pre != NULL ) pre->next = NULL , root->left = sortedListToBST (head); else root->left = NULL ; root->right = sortedListToBST (mid->next); if (pre != NULL ) pre->next = mid; return root; } };
递归做,比链表的容易些。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : TreeNode *convert (vector &num, int left, int right) { if (right == left) return NULL ; int mid = right + left >> 1 ; TreeNode *root = new TreeNode (num[mid]); root->left = convert (num, left, mid); root->right = convert (num, mid + 1 , right); } TreeNode *sortedArrayToBST (vector &num) { return convert (num, 0 , num.size ()); } };
宽搜和深搜都可以,找对层数就行了。
本以为这题亮点是如何一遍实现从底向上顺序的vector,AC之后上网一查也全是最后把vector翻转的。。。
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 class Solution {public : vectorv; void DFS (TreeNode *now, int depth) { if (v.size () <= depth) v.push_back (vector (0 )); v[depth].push_back (now->val); if (now->left != NULL ) DFS (now->left, depth + 1 ); if (now->right != NULL ) DFS (now->right, depth + 1 ); } vector levelOrderBottom (TreeNode *root) { if (root == NULL ) return v; DFS (root, 0 ); for (int i = 0 , j = v.size () - 1 ; i < j; i ++, j --) swap (v[i], v[j]); return v; } };
数据结构经典题。后序遍历的结尾是根节点 Proot,在中序遍历中找到这个节点 Iroot,则 Iroot两边即为左右子树。根据左右子树节点个数,在后序遍历中找到左右子树分界(左右子树肯定不交叉),则几个关键分界点都找到了,对左右子树递归。
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 : TreeNode *build (vector &inorder, int ileft, int iright, vector &postorder, int pleft, int pright) { if (iright == ileft) return NULL ; int root; for (root = ileft; inorder[root] != postorder[pright - 1 ]; root ++); TreeNode *node = new TreeNode (inorder[root]); node->left = build (inorder, ileft, root, postorder, pleft, pleft + root - ileft); node->right = build (inorder, root + 1 , iright, postorder, pleft + root - ileft, pright - 1 ); return node; } TreeNode *buildTree (vector &inorder, vector &postorder) { return build (inorder, 0 , inorder.size (), postorder, 0 , postorder.size ()); } };
和上一题Construct Binary Tree from Inorder and Postorder Traversal方法一样,前序和后序的信息作用相同。
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 class Solution {public : TreeNode *build (vector &inorder, int ileft, int iright, vector &preorder, int pleft, int pright) { if (iright == ileft) return NULL ; int root; for (root = ileft; inorder[root] != preorder[pleft]; root ++); TreeNode *node = new TreeNode (inorder[root]); node->left = build (inorder, ileft, root, preorder, pleft + 1 , pleft + root - ileft); node->right = build (inorder, root + 1 , iright, preorder, pleft + root - ileft + 1 , pright); return node; } TreeNode *buildTree (vector &preorder, vector &inorder) { return build (inorder, 0 , inorder.size (), preorder, 0 , preorder.size ()); } };
遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : int maxDepth (TreeNode *root) { if (root == NULL ) return 0 ; if (root->left == NULL ) return maxDepth (root->right) + 1 ; if (root->right == NULL ) return maxDepth (root->left) + 1 ; return max (maxDepth (root->left), maxDepth (root->right)) + 1 ; } };
BFS,奇偶层轮流走,一层左到右,一层右到左。
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 class Solution {public : vector ans; vector zigzagLevelOrder (TreeNode *root) { if (root == NULL ) return ans; vector<TreeNode*> q1, q2; q1.push_back (root); int depth = 0 ; while (!q1.empty () || !q2.empty ()) { ans.push_back (vector (0 )); for (int i = q1.size () - 1 ; i >= 0 ; i --) { ans[depth].push_back (q1[i]->val); if (q1[i]->left != NULL ) q2.push_back (q1[i]->left); if (q1[i]->right != NULL ) q2.push_back (q1[i]->right); } depth ++; q1.clear (); if (q2.empty ()) continue ; ans.push_back (vector (0 )); for (int i = q2.size () - 1 ; i >= 0 ; i --) { ans[depth].push_back (q2[i]->val); if (q2[i]->right != NULL ) q1.push_back (q2[i]->right); if (q2[i]->left != NULL ) q1.push_back (q2[i]->left); } q2.clear (); depth ++; } return ans; } };
懒省事直接在上一题Binary Tree Zigzag Level Order Traversal的代码上改了一下。
只用一个队列的话,增加个层数信息存队列里即可。
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 class Solution {public : vector ans; vector levelOrder (TreeNode *root) { if (root == NULL ) return ans; vector<TreeNode*> q1, q2; q1.push_back (root); int depth = 0 ; while (!q1.empty () || !q2.empty ()) { ans.push_back (vector (0 )); for (int i = 0 ; i < q1.size (); i ++) { ans[depth].push_back (q1[i]->val); if (q1[i]->left != NULL ) q2.push_back (q1[i]->left); if (q1[i]->right != NULL ) q2.push_back (q1[i]->right); } depth ++; q1.clear (); if (q2.empty ()) continue ; ans.push_back (vector (0 )); for (int i = 0 ; i < q2.size (); i ++) { ans[depth].push_back (q2[i]->val); if (q2[i]->left != NULL ) q1.push_back (q2[i]->left); if (q2[i]->right != NULL ) q1.push_back (q2[i]->right); } q2.clear (); depth ++; } return ans; } };
递归:左指针和右指针,对称递归,即(左的左)和(右的右)对应,(左的右)和(右的左)对应。
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 {public : bool judge (TreeNode *l, TreeNode *r) { if (l->val != r->val) return false ; if (l->left != r->right && (l->left == NULL || r->right == NULL ) || l->right != r->left && (l->right == NULL || r->left == NULL )) return false ; if (l->left != NULL && !judge (l->left, r->right)) return false ; if (l->right != NULL && !judge (l->right, r->left)) return false ; return true ; } bool isSymmetric (TreeNode *root) { if (root == NULL ) return true ; if (root->left == NULL && root->right == NULL ) return true ; else if (root->left != NULL && root->right == NULL || root->left == NULL && root->right != NULL ) return false ; return judge (root->left, root->right); } };
非递归:左右子树分别做一个队列,同步遍历。
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 class Solution {public : bool isSymmetric (TreeNode *root) { if (root == NULL ) return true ; if (root->left == NULL && root->right == NULL ) return true ; else if (root->left != NULL && root->right == NULL || root->left == NULL && root->right != NULL ) return false ; queue q1, q2; q1.push (root->left); q2.push (root->right); while (!q1.empty ()) { TreeNode *now1 = q1.front (), *now2 = q2.front (); q1.pop (); q2.pop (); if (now1->val != now2->val) return false ; if (now1->left != NULL || now2->right != NULL ) { if (now1->left == NULL || now2->right == NULL ) return false ; q1.push (now1->left); q2.push (now2->right); } if (now1->right != NULL || now2->left != NULL ) { if (now1->right == NULL || now2->left == NULL ) return false ; q1.push (now1->right); q2.push (now2->left); } } return true ; } };
同步遍历,比较判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : bool isSameTree (TreeNode *p, TreeNode *q) { if (p == NULL && q == NULL ) return true ; if (p != q && (p == NULL || q == NULL ) || p->val != q->val) return false ; return isSameTree (p->left, q->left) && isSameTree (p->right, q->right); } };
中序遍历是二叉查找树的顺序遍历,a, b表示前驱节点和当前节点,因为只有一对数值翻转了,所以肯定会遇到前驱节点val比当前节点val大的情况一次或两次,遇到一次表示翻转的是相邻的两个节点。 ans1和 ans2指向两个被翻转的节点,当遇到前驱val比当前val大的情况时候,根据第一次还是第二次给ans1和ans2赋值,最终翻转回来。
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 class Solution {public : TreeNode *a, *b; TreeNode *ans1, *ans2; bool DFS (TreeNode *now) { if (now->left != NULL ) DFS (now->left); a = b; b = now; if (a != NULL && a->val > b->val) { if (ans1 == NULL ) ans1 = a; ans2 = b; } if (now->right != NULL ) DFS (now->right); } void recoverTree (TreeNode *root) { if (root == NULL ) return ; a = b = ans1 = ans2 = NULL ; DFS (root); swap (ans1->val, ans2->val); } };
中序遍历,更新前驱节点,与当前节点比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : TreeNode *pre = NULL ; bool isValidBST (TreeNode *root) { if (root == NULL ) return true ; if (root->left != NULL && !isValidBST (root->left)) return false ; if (pre != NULL && pre->val >= root->val) return false ; pre = root; if (root->right != NULL && !isValidBST (root->right)) return false ; return true ; } };
动态规划。如果结果是true,则任意 i
, j
,s1[i]
之前的字符 和 s2[j]
之前的字符,都能够交叉为 s3[i + j]
之前的字符。
由此,当dp[i][j]
时,如果s1[i]==s3[i+j]
,则尝试s1[i]
与s3[i+j]
对应,如果dp[i-1][j]
是true
,则dp[i][j]
也为true
。如果s2[j]==s3[i+j]
则同样处理。
直到最后,判断dp[s1.length()-1][s2.length()-1]
是否为true。为方便初始化,坐标后移了一位。
题目不厚道的出了s1.length()+s2.length() != s3.length()
的数据,特判一下。
看到网上也都是O(n^2)
的解法,我也就放心了。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : bool isInterleave (string s1, string s2, string s3) { if (s1.length () + s2.length () != s3.length ()) return false ; vector dp (s1.length() + 1 , vector(s2.length() + 1 , false )) ; for (int i = 0 ; i <= s1.length (); i ++) for (int j = 0 ; j <= s2.length (); j ++) { if (!i && !j) dp[i][j] = true ; dp[i][j] = dp[i][j] || i > 0 && s3[i + j - 1 ] == s1[i - 1 ] && dp[i - 1 ][j]; dp[i][j] = dp[i][j] || j > 0 && s3[i + j - 1 ] == s2[j - 1 ] && dp[i][j - 1 ]; } return dp[s1.length ()][s2.length ()]; } };
LeetCode目前为止感觉最暴力的。递归遍历所有情况,每次返回子问题(左右子树)的vector的解,两层循环组合这些解。
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 class Solution {public : vector generate (int start, int end) { vectorres; if (start > end) { TreeNode *tmp = NULL ; res.push_back (tmp); return res; } for (int i = start; i <= end; i ++) { vector l = generate (start, i - 1 ), r = generate (i + 1 , end); for (int j = 0 ; j < l.size (); j ++) for (int k = 0 ; k < r.size (); k ++) { TreeNode *tmp = new TreeNode (i); tmp->left = l[j]; tmp->right = r[k]; res.push_back (tmp); } } return res; } vector generateTrees (int n) { return generate (1 , n); } };
经典问题,卡特兰数,可递推,可用公式(公式用组合数,也要写循环)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : int COM (int n, int m) { m = n - m < m ? n - m : m; int res, i, j; for (i = n, res = j = 1 ; i > n - m; i --) { res *= i; for (; j <= m && res % j == 0 ; j ++) res /= j; } return res; } int numTrees (int n) { return COM (n << 1 , n) / (n + 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 class Solution {public : vector res; void inorder (TreeNode *now) { if (now == NULL ) return ; inorder (now->left); res.push_back (now->val); inorder (now->right); } vector inorderTraversal (TreeNode *root) { inorder (root); return res; } };
四层递归枚举分割位置,判断数字范围和前导零,处理字符串。
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 : vector res; void DFS (string s, int last, int cur, string now) { if (cur == 3 ) { if (last == s.length ()) return ; string tmp = s.substr (last, s.length () - last); if (atoi (tmp.c_str ()) <= 255 && (tmp.length () == 1 || tmp[0 ] != '0' )) res.push_back (now + tmp); return ; } string lin; for (int i = last; i < s.length (); i ++) { string tmp = s.substr (last, i - last + 1 ); if (atoi (tmp.c_str ()) <= 255 && (tmp.length () == 1 || tmp[0 ] != '0' )) DFS (s, i + 1 , cur + 1 , now + tmp + "." ); } } vector restoreIpAddresses (string s) { DFS (s, 0 , 0 , "" ); return res; } };
在表头添加一个(哨兵)会好写很多,额外的newhead可以帮助标记翻转之后更换了的头指针。
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 class Solution {public : ListNode *reverseBetween (ListNode *head, int m, int n) { ListNode *newhead = new ListNode (0 ); newhead->next = head; ListNode *pre = newhead, *p = head, *start = NULL ; ListNode *tmp; for (int i = 1 ; p != NULL ; i ++) { tmp = p->next; if (i == m) start = pre; if (i > m && i <= n) p->next = pre; if (i == n) { start->next->next = tmp; start->next = p; } pre = p; p = tmp; } tmp = newhead->next; free (newhead); return tmp; } };
递推:dp[i]
表示前 i
个数字的解码种数。
`dp[i] = if(一)dp[i-1] + if(二)dp[i-2]``
当 i
位置不为0
,可加上 i - 1
位置的解。当当前位置和前一位置组成的两位数满足解码且高位不为0
,可加上 i - 2
位置的解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : int numDecodings (string s) { if (s.length () == 0 ) return 0 ; vector dp (s.length() + 1 , 0 ) ; dp[0 ] = 1 ; for (int i = 0 ; i < s.length (); i ++) { dp[i + 1 ] = (s[i] != '0' ? dp[i] : 0 ) + (i > 0 && s[i - 1 ] != '0' && atoi (s.substr (i - 1 , 2 ).c_str ()) <= 26 ? dp[i - 1 ] : 0 ); } return dp[s.length ()]; } };
统计地存map里,map[i]= j 表示 S 中有 j 个 i。map是有序的,用迭代器递归枚举放入集合的个数。
也可以先排序,用set标记每个数时候被放入过,第一次放入之后才可以继续放同一个数。
代码是用map的方法。
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 class Solution {public : vector res; vector now; map<int , int > mp; void DFS (map<int , int >::iterator i) { if (i == mp.end ()) { res.push_back (now); return ; } map<int , int >::iterator tmp = i; i ++; DFS (i); for (int j = 0 ; j < tmp->second; j ++) { now.push_back (tmp->first); DFS (i); } for (int j = 0 ; j < tmp->second; j ++) now.pop_back (); } vector subsetsWithDup (vector &S) { for (int i = 0 ; i < S.size (); i ++) !mp.count (S[i]) ? (mp[S[i]] = 1 ) : mp[S[i]] ++; DFS (mp.begin ()); return res; } };
格雷码有多种生成方法,可参考维基百科 。
1 2 3 4 5 6 7 8 class Solution {public : vector grayCode (int n) { vector res; for (int i = 0 ; i < (1 << n); i ++) res.push_back ((i >> 1 ) ^ i); return res; } };
从后往前,对 A 来说一个萝卜一个坑,肯定不会破坏前面的数据。具体看代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : void merge (int A[], int m, int B[], int n) { int p = m + n - 1 , i = m - 1 , j = n - 1 ; for (; i >= 0 && j >= 0 ;) { if (A[i] > B[j]) A[p --] = A[i --]; else A[p --] = B[j --]; } while (i >= 0 ) A[p --] = A[i --]; while (j >= 0 ) A[p --] = B[j --]; } };
直接搜索可以过,记忆化搜索可提高效率。
dp[i][j][k]表示从 s1[i] 和 s2[j] 开始长度为 k 的字符串是否是scrambled string。
枚举分割位置,scrambled string要求字符串对应字母的个数是一致的,可以直接排序对比。递归终点是刚好只有一个字母。
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 class Solution {public : string S1, S2; vector<vector > dp; bool judge (string a, string b) { sort (a.begin (), a.end ()); sort (b.begin (), b.end ()); for (int i = 0 ; i < a.length (); i ++) if (a[i] != b[i]) return false ; return true ; } int DFS (int s1start, int s2start, int len) { int &ans = dp[s1start][s2start][len - 1 ]; if (len == 1 ) return ans = S1[s1start] == S2[s2start]; if (ans != -1 ) return ans; if (!judge (S1.substr (s1start, len), S2.substr (s2start, len))) return ans = 0 ; ans = 0 ; for (int i = 1 ; i < len; i ++) { ans = ans || DFS (s1start, s2start, i) && DFS (s1start + i, s2start + i, len - i) || DFS (s1start, s2start + len - i, i) && DFS (s1start + i, s2start, len - i); } return ans; } bool isScramble (string s1, string s2) { S1 = s1, S2 = s2; dp = vector <vector > (s1.length (), vector (s1.length (), vector (s1.length (), -1 ))); return DFS (0 , 0 , s1.length ()); } };
分存大小最后合并。
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 class Solution {public : ListNode *partition (ListNode *head, int x) { ListNode *shead, *bhead, *smaller, *bigger, *p; for (shead = bhead = smaller = bigger = NULL , p = head; p != NULL ; p = p->next) { if (p->val < x) { if (shead == NULL ) shead = p; if (smaller != NULL ) smaller->next = p; smaller = p; } else { if (bhead == NULL ) bhead = p; if (bigger != NULL ) bigger->next = p; bigger = p; } } if (smaller != NULL ) smaller->next = bhead; if (bigger != NULL ) bigger->next = NULL ; return shead != NULL ? shead : bhead; } };
方法一:linecnt[i][j]
统计第 i
行到第 j
位置有多少个连续的 '1'
,接下来枚举列,每一列相当于一次直方图最大矩形统计,计算每个位置向前和向后最远的不少于当前位置值的位置,每次更新结果,总复杂度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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class Solution {public : int maximalRectangle (vector &matrix) { if (matrix.size () == 0 ) return 0 ; int H = matrix.size (), W = matrix[0 ].size (); int ans = 0 ; vector left (H) , right (H) ; vector linecnt (H, vector(W, 0 )) ; for (int i = 0 ; i < H; i ++) { int last = 0 ; for (int j = 0 ; j < W; j ++) { if (matrix[i][j] == '1' ) last ++; else last = 0 ; linecnt[i][j] = last; } } for (int k = 0 ; k < W; k ++) { for (int i = 0 ; i < H; i ++) { if (i == 0 ) left[i] = -1 ; else { left[i] = i - 1 ; while (left[i] > -1 && linecnt[left[i]][k] >= linecnt[i][k]) left[i] = left[left[i]]; } } for (int i = H - 1 ; i >= 0 ; i --) { if (i == H - 1 ) right[i] = H; else { right[i] = i + 1 ; while (right[i] < H && linecnt[right[i]][k] >= linecnt[i][k]) right[i] = right[right[i]]; } ans = max (ans, (right[i] - left[i] - 1 ) * linecnt[i][k]); } } return ans; } };
用单调栈,理论复杂度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 class Solution {public : int maximalRectangle (vector &matrix) { if (matrix.size () == 0 ) return 0 ; vector linecnt (matrix.size(), vector(matrix[0 ].size(), 0 )) ; for (int i = 0 ; i < matrix.size (); i ++) { int last = 0 ; for (int j = 0 ; j < matrix[0 ].size (); j ++) { if (matrix[i][j] == '1' ) last ++; else last = 0 ; linecnt[i][j] = last; } } int ans = 0 ; for (int k = 0 ; k < matrix[0 ].size (); k ++) { stack s, site; vectorlast (matrix.size ()); for (int i = 0 ; i < matrix.size (); i ++) { while (!s.empty () && s.top () >= linecnt[i][k]) s.pop (), site.pop (); if (!s.empty ()) last[i] = site.top () + 1 ; else last[i] = 0 ; s.push (linecnt[i][k]); site.push (i); } while (!s.empty ()) s.pop (), site.pop (); for (int i = matrix.size () - 1 ; i >= 0 ; i --) { while (!s.empty () && s.top () >= linecnt[i][k]) s.pop (), site.pop (); if (!s.empty ()) ans = max (ans, (site.top () - last[i]) * linecnt[i][k]); else ans = max (ans, (int )(matrix.size () - last[i]) * linecnt[i][k]); s.push (linecnt[i][k]); site.push (i); } } return ans; } };
方法二:每个 1
的点当作一个矩形的底部,left[j]、right[j]、height[j]
表示当前行第 j
个位置这个点向左、右、上伸展的最大矩形的边界,作为滚动数组,下一行的数据可以由上一行结果得到,总复杂度O(n^2)。
left[j] = max(这一行最左, left[j](上一行最左) );
right[j] = min(这一行最右,right[j](上一行最右) );
height[j] = height[j - 1] + 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 class Solution {public : int maximalRectangle (vector &matrix) { if (matrix.size () == 0 ) return 0 ; int H = matrix.size (), W = matrix[0 ].size (); vector left (W, -1 ) , right (W, W) , height (W, 0 ) ; int ans = 0 ; for (int i = 0 ; i < H; i ++) { int last = -1 ; for (int j = 0 ; j < W; j ++) { if (matrix[i][j] == '1' ) { if (last == -1 ) last = j; left[j] = max (left[j], last); height[j] ++; } else { last = -1 ; left[j] = -1 ; height[j] = 0 ; } } last = -1 ; for (int j = W - 1 ; j >= 0 ; j --) { if (matrix[i][j] == '1' ) { if (last == -1 ) last = j; right[j] = min (right[j], last); ans = max (ans, height[j] * (right[j] - left[j] + 1 )); } else { last = -1 ; right[j] = W; } } } return ans; } };
参考上一题Maximal Rectangle方法一。
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 class Solution {public : int largestRectangleArea (vector &height) { if (height.size () == 0 ) return 0 ; vector left (height.size()) , right (height.size()) ; int ans = 0 ; for (int i = 0 ; i < height.size (); i ++) { if (i == 0 ) left[i] = -1 ; else { left[i] = i - 1 ; while (left[i] > -1 && height[i] <= height[left[i]]) left[i] = left[left[i]]; } } for (int i = height.size () - 1 ; i >= 0 ; i --) { if (i == height.size () - 1 ) right[i] = height.size (); else { right[i] = i + 1 ; while (right[i] < height.size () && height[i] <= height[right[i]]) right[i] = right[right[i]]; } ans = max (ans, (right[i] - left[i] - 1 ) * height[i]); } return ans; } };
加个表头乱搞吧。
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 class Solution {public : ListNode *deleteDuplicates (ListNode *head) { if (head == NULL || head->next == NULL ) return head; ListNode *newhead = new ListNode (0 ); newhead->next = head; for (ListNode *pre = newhead, *now = head, *nex = head->next; nex != NULL ;) { if (now->val == nex->val) { while (nex != NULL && now->val == nex->val) { free (now); now = nex; nex = nex->next; } free (now); pre->next = nex; if (nex == NULL ) break ; } else pre = now; now = nex; nex = nex->next; } head = newhead->next; free (newhead); return head; } };
直接操作。
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 class Solution {public : ListNode *deleteDuplicates (ListNode *head) { if (head == NULL || head->next == NULL ) return head; for (ListNode *pre = head, *p = head->next; p != NULL ;) { while (p != NULL && pre->val == p->val) { pre->next = p->next; free (p); p = pre->next; } if (p == NULL ) break ; pre = p; p = p->next; } return head; } };
以mid为界,左右两边至少有一边是有序的。由于不可避免地会有O(n)的可能性,所以确定的时候二分,不确定的时候单位缩减边界。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : bool search (int A[], int n, int target) { int left = 0 , right = n - 1 , mid; while (left < right) { mid = left + right >> 1 ; if (target < A[mid] && A[left] < target) right = mid; else if (target < A[right] && A[mid] < target) left = mid + 1 ; else { if (A[left] == target || A[right] == target) return true ; else if (A[left] < target) left ++; else if (target < A[right]) right --; else return false ; } } return A[left] == target ? true : false ; } };
记下放了几个,多了不放。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : int removeDuplicates (int A[], int n) { int i, j, cnt; for (i = j = cnt = 0 ; i < n; i ++) { if (j != 0 && A[j - 1 ] == A[i]) cnt ++; else cnt = 0 ; if (cnt < 2 ) A[j ++] = A[i]; } return j; } };
基础DFS。
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 class Solution {public : int dx[4 ] = {1 , -1 , 0 , 0 }; int dy[4 ] = {0 , 0 , 1 , -1 }; bool DFS (int x, int y, vector &board, string word, int ith) { if (board[x][y] != word[ith]) return false ; if (ith == word.length () - 1 ) return true ; board[x][y] = '.' ; for (int i = 0 ; i < 4 ; i ++) { int nx = x + dx[i]; int ny = y + dy[i]; if (nx >= 0 && ny >= 0 && nx < board.size () && ny < board[0 ].size ()) { if (DFS (nx, ny, board, word, ith + 1 )) { board[x][y] = word[ith]; return true ; } } } board[x][y] = word[ith]; return false ; } bool exist (vector &board, string word) { for (int i = 0 ; i < board.size (); i ++) { for (int j = 0 ; j < board[0 ].size (); j ++) { if (DFS (i, j, board, word, 0 )) return true ; } } return false ; } };
基础DFS。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : vector now; vector res; void DFS (vector &S, int ith) { if (ith == S.size ()) { res.push_back (now); return ; } DFS (S, ith + 1 ); now.push_back (S[ith]); DFS (S, ith + 1 ); now.pop_back (); } vector subsets (vector &S) { sort (S.begin (), S.end ()); DFS (S, 0 ); return res; } };
基础DFS。
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 : vector now; vector res; void DFS (int n, int ith, int sum, int k) { if (sum == k) { res.push_back (now); return ; } if (sum + n - ith + 1 > k) { DFS (n, ith + 1 , sum, k); } now.push_back (ith); DFS (n, ith + 1 , sum + 1 , k); now.pop_back (); } vector combine (int n, int k) { DFS (n, 1 , 0 , k); return res; } };
先统计 T
中各字符都有多少个,然后两个下标一前(i
)一后(j
)在 S
上跑, 当 i
跑到把 T
中字符都包含的位置时候,让 j
追到第一个包含 T
的字符的地方,更新结果,去掉 j
这个位置字符的统计,让 i
继续跑,如此反复。
i
和 j
都只遍历一遍 S
,复杂度 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 class Solution {public : string minWindow (string S, string T) { vector cnt (256 , 0 ) , need (256 , 0 ) ; int sum = 0 , len = 0x3f3f3f3f ; string ans; for (int i = 0 ; i < T.size (); i ++) need[T[i]] ++; for (int i = 0 , j = 0 ; i < S.length (); j ++) { while (i < S.length () && sum < T.length ()) { if (cnt[S[i]] < need[S[i]]) sum ++; cnt[S[i]] ++; i ++; } while (sum == T.length () && j < S.length ()) { cnt[S[j]] --; if (cnt[S[j]] < need[S[j]]) break ; j ++; } if (sum < T.length ()) break ; if (i - j < len) ans = S.substr (j, i - j), len = i - j; sum --; } return ans; } };
轮流找:
1 2 3 4 5 6 7 8 9 10 class Solution {public : void sortColors (int A[], int n) { int find = 0 ; for (int i = 0 , j = n - 1 ; i < n; i ++) { if (A[i] == find) continue ; while (j > i && A[j] != find) j --; if (j > i) swap (A[i], A[j]); else i --, j = n - 1 , find ++; } } };
找到哪个放哪个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : void sortColors (int A[], int n) { int p0, p1, p2; for (p0 = 0 , p1 = p2 = n - 1 ; p0 < p1; ) { if (A[p0] == 0 ) p0 ++; if (A[p0] == 1 ) swap (A[p0], A[p1 --]); if (A[p0] == 2 ) { swap (A[p0], A[p2 --]); p1 = p2; } } } };
写两个二分查找。或者把整个矩阵看作一维,直接二分,换算坐标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : bool searchMatrix (vector &matrix, int target) { int left, right, mid; for (left = 0 , right = matrix.size (); left < right - 1 ; ) { mid = left + right >> 1 ; if (matrix[mid][0 ] > target) right = mid; else left = mid; } if (left == matrix.size () || right == 0 ) return false ; vector &a = matrix[left]; for (left = 0 , right = a.size (); left < right - 1 ;) { mid = left + right >> 1 ; if (a[mid] > target) right = mid; else left = mid; } if (left == a.size () || right == 0 ) return false ; return a[left] == target; } };
O(m+n)
的方法是容易想到的,而空间复杂度O(1)
,只要利用原矩阵的一行和一列来使用O(m+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 class Solution {public : void setZeroes (vector &matrix) { if (matrix.size () == 0 ) return ; int x = -1 , y = -1 ; for (int i = 0 ; i < matrix.size (); i ++) { for (int j = 0 ; j < matrix[0 ].size (); j ++) { if (matrix[i][j] == 0 ) { if (x == -1 ) { x = i, y = j; } else { matrix[x][j] = 0 ; matrix[i][y] = 0 ; } } } } if (x == -1 ) return ; for (int i = 0 ; i < matrix.size (); i ++) for (int j = 0 ; j < matrix[0 ].size (); j ++) if ((matrix[x][j] == 0 || matrix[i][y] == 0 ) && (i != x && j != y)) matrix[i][j] = 0 ; for (int i = 0 ; i < matrix.size (); i ++) matrix[i][y] = 0 ; for (int j = 0 ; j < matrix[0 ].size (); j ++) matrix[x][j] = 0 ; } };
动态规划,先初始化 dp[i][0]
和 dp[0][i]
,即每个字符串对应空串的编辑距离为串长度,之后对每个位置取子问题加上当前位置 改、删、增得解的最小值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : int minDistance (string word1, string word2) { vector dp (word1.length() + 1 , vector(word2.length() + 1 , 0 )) ; for (int i = 0 ; i < word1.length (); i ++) dp[i + 1 ][0 ] = i + 1 ; for (int i = 0 ; i < word2.length (); i ++) dp[0 ][i + 1 ] = i + 1 ; for (int i = 0 ; i < word1.length (); i ++) for (int j = 0 ; j < word2.length (); j ++) { if (word1[i] != word2[j]) dp[i + 1 ][j + 1 ] = min (dp[i][j] + 1 , min (dp[i][j + 1 ] + 1 , dp[i + 1 ][j] + 1 )); else dp[i + 1 ][j + 1 ] = min (dp[i][j], min (dp[i][j + 1 ] + 1 , dp[i + 1 ][j] + 1 )); } return dp[word1.length ()][word2.length ()]; } };
好烦人的题,没什么好说的。
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 class Solution {public : string simplifyPath (string path) { stack s; string str; for (int i = 0 ; i < path.length (); i ++) { if (path[i] == '/' ) { if (str == ".." ) { if (!s.empty ()) s.pop (); } else if (str != "." && str != "" ) s.push (str); str.clear (); } else str += path[i]; } if (str == ".." ) { if (!s.empty ()) s.pop (); } else if (str != "." && str != "" ) s.push (str); if (s.empty ()) return "/" ; for (str.clear (); !s.empty (); s.pop ()) str = "/" + s.top () + str; return str; } };
递推,就是斐波那契数列。
1 2 3 4 5 6 7 8 class Solution {public : int climbStairs (int n) { return (int ) (pow ((1 +sqrt (5 ))/2 , n + 1 ) / sqrt (5 ) - pow ((1 -sqrt (5 ))/2 , n + 1 ) / sqrt (5 ) + 0.1 ); } };
牛顿迭代。 设输入为n,f(x)=x^2-n
,解就是f(x)=0
时的x
。 设猜了一数x[0]
,那么在f(x)
在x[0]
处的切线与x轴的交点x[1]
更接近目标解(可画图看看)。 那么递推下去,x[i]=(x[i-1]+n/x[i-1])/2
,用double,越推越精确,直到自己想要的精度。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : int sqrt (int x) { double now, last; if (x == 0 ) return 0 ; for (now = last = (double )x; ; last = now) { now = (last + (x / last)) * 0.5 ; if (fabs (last - now) < 1e-5 ) break ; } return (int )(now + 1e-6 ); } };
每行限制长度,空格均匀插入,不能完全平均的情况下优先靠前的单词间隔。
最后一行特别处理,单词间只有一个空格,剩下的放在末尾。
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 class Solution {public : vector fullJustify (vector &words, int L) { vector ans; int cnt = 0 , i, j, k, l; for (i = 0 , j = 0 ; j < words.size (); i ++) { if (i < words.size ()) { cnt += words[i].length (); if (i == j) continue ; } if (i == words.size () || (L - cnt) / (i - j) < 1 ) { int blank = 0 ; if (i < words.size ()) blank = (i - j - 1 ) ? (L - cnt + words[i].length ()) / (i - j - 1 ) : 0 ; string tmp = "" ; l = i < words.size () ? (L - cnt + words[i].length () - blank * (i - j - 1 )) : 0 ; for (k = j; k < i; k ++, l --) { tmp += words[k]; if (k != i - 1 ) { if (i != words.size ()) { for (int bl = 0 ; bl < blank; bl ++) tmp += " " ; if (l > 0 ) tmp += " " ; } else tmp += " " ; } } while (tmp.length () < L) tmp += " " ; ans.push_back (tmp); cnt = 0 ; j = i; i --; } } return ans; } };
大整数加法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : vector plusOne (vector &digits) { int cur, i; if (digits.size () == 0 ) return digits; for (i = digits.size () - 1 , cur = 1 ; i >= 0 ; i --) { int tmp = digits[i] + cur; cur = tmp / 10 ; digits[i] = tmp % 10 ; } if (cur) digits.insert (digits.begin (), cur); return digits; } };
用DFA也不麻烦,题目定义太模糊,为了理解规则错很多次也没办法。
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 class Solution {public : int f[11 ][129 ]; const int fail = -1 ; const int st = 0 ; const int pn = 1 ; const int di = 2 ; const int del = 3 ; const int ddi = 4 ; const int ndel = 5 ; const int dibl = 6 ; const int ex = 7 ; const int epn = 8 ; const int edi = 9 ; const int end = 10 ; void buildDFA () { memset (f, -1 , sizeof (f)); f[st][' ' ] = st; f[st]['+' ] = f[st]['-' ] = pn; for (int i = '0' ; i <= '9' ; i ++) { f[st][i] = f[pn][i] = f[di][i] = di; f[del][i] = f[ndel][i] = f[ddi][i] = ddi; f[ex][i] = f[epn][i] = f[edi][i] = edi; } f[di]['.' ] = ndel; f[st]['.' ] = f[pn]['.' ] = del; f[di][' ' ] = f[ndel][' ' ] = f[ddi][' ' ] = f[dibl][' ' ] = f[edi][' ' ] = dibl; f[di][0 ] = f[ndel][0 ] = f[dibl][0 ] = f[ddi][0 ] = f[edi][0 ] = end; f[di]['e' ] = f[ndel]['e' ] = f[ddi]['e' ] = ex; f[ex][' ' ] = ex; f[ex]['+' ] = f[ex]['-' ] = epn; } bool DFA (const char *s) { int situ = st; for (int i = 0 ;; i ++) { situ = f[situ][s[i]]; if (situ == end) return true ; if (situ == fail) return false ; } return true ; } bool isNumber (const char *s) { buildDFA (); return DFA (s); } };
翻转,大整数加法,再翻转。无心情优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : string addBinary (string a, string b) { reverse (a.begin (), a.end ()); reverse (b.begin (), b.end ()); string c; int cur = 0 , i; for (i = 0 ; i < min (a.length (), b.length ()); i ++) { int tmp = a[i] - '0' + b[i] - '0' + cur; cur = tmp >> 1 ; c += (tmp & 1 ) + '0' ; } string &t = a.length () > b.length () ? a : b; for (; i < t.length (); i ++) { int tmp = t[i] - '0' + cur; cur = tmp >> 1 ; c += (tmp & 1 ) + '0' ; } if (cur) c += '1' ; reverse (c.begin (), c.end ()); return c; } };
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : int minPathSum (vector &grid) { if (grid.size () == 0 ) return 0 ; for (int i = 0 ; i < grid.size (); i ++) { for (int j = 0 ; j < grid[0 ].size (); j ++) { int tmp = 0x3f3f3f3f ; if (i > 0 ) tmp = min (tmp, grid[i][j] + grid[i - 1 ][j]); if (j > 0 ) tmp = min (tmp, grid[i][j] + grid[i][j - 1 ]); grid[i][j] = tmp == 0x3f3f3f3f ? grid[i][j] : tmp; } } return grid[grid.size () - 1 ][grid[0 ].size () - 1 ]; } };
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : int uniquePathsWithObstacles (vector &obstacleGrid) { if (obstacleGrid.size () == 0 ) return 0 ; obstacleGrid[0 ][0 ] = obstacleGrid[0 ][0 ] != 1 ; for (int i = 0 ; i < obstacleGrid.size (); i ++) for (int j = 0 ; j < obstacleGrid[0 ].size (); j ++) { if (i == 0 && j == 0 ) continue ; if (obstacleGrid[i][j] == 1 ) { obstacleGrid[i][j] = 0 ; continue ; } if (i > 0 ) obstacleGrid[i][j] += obstacleGrid[i - 1 ][j]; if (j > 0 ) obstacleGrid[i][j] += obstacleGrid[i][j - 1 ]; } return obstacleGrid[obstacleGrid.size () - 1 ][obstacleGrid[0 ].size () - 1 ]; } };
这是当年学组合数时候的经典题型吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : int COM (int a, int b) { b = min (b, a - b); int ret = 1 , i, j; for (i = a, j = 1 ; i > a - b; i --) { ret *= i; for (; j <= b && ret % j == 0 ; j ++) ret /= j; } return ret; } int uniquePaths (int m, int n) { return COM (m + n - 2 , m - 1 ); } };
因为k可能比长度大,需要求长度然后k对长度取模。那么就不要矫情地追求双指针一遍扫描了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : ListNode *rotateRight (ListNode *head, int k) { if (head == NULL ) return NULL ; int cnt; ListNode *en, *p; for (cnt = 1 , en = head; en->next != NULL ; cnt ++, en = en->next); k %= cnt; for (p = head, cnt --; cnt != k; cnt --, p = p->next); en->next = head; en = p->next; p->next = NULL ; return en; } };
一位一位算,每一位优先没使用过的较小的数字,而其后剩下的m个位置有 m! 种排列方法,用 k 减去,直到k不大于这个方法数,则这一位就是枚举到的这个数。
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 : int permu[10 ]; bool vis[10 ]; string getPermutation (int n, int k) { permu[0 ] = 1 ; for (int i = 1 ; i < 10 ; i ++) permu[i] = permu[i - 1 ] * i; memset (vis, 0 , sizeof (vis)); string ans; for (int i = 1 ; i <= n; i ++) { for (int j = 1 ; j <= n; j ++) { if (!vis[j]) { if (k > permu[n - i]) k -= permu[n - i]; else { ans += '0' + j; vis[j] = true ; break ; } } } } return ans; } };
直接算每个位置的数是多少有木有很霸气→_→。 先看当前位置之外有几个嵌套的正方形,再看当前位置在当前正方形四条边的第几条,求出坐标(x,y)位置的数。
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 class Solution {public : vector res; vector nsq; int calnum (int i, int j, int n) { int num, tmp; tmp = min (min (i, j), min (n - 1 - i, n - 1 - j)); num = nsq[tmp]; if (i == tmp) return num + j - tmp + 1 ; if (n - j - 1 == tmp) return num + n - 2 * tmp + i - tmp; if (n - i - 1 == tmp) return num + 2 * (n - 2 * tmp) - 2 + n - j - tmp; return num + 3 * (n - 2 * tmp) - 3 + n - i - tmp; } vector generateMatrix (int n) { nsq.push_back (0 ); for (int i = n; i > 0 ; i -= 2 ) nsq.push_back (4 * i - 4 ); for (int i = 1 ; i < nsq.size (); i ++) nsq[i] += nsq[i - 1 ]; for (int i = 0 ; i < n; i ++) { vector tmp; for (int j = 0 ; j < n; j ++) { tmp.push_back (calnum (i, j, n)); } res.push_back (tmp); } return res; } };
从后往前找。
1 2 3 4 5 6 7 8 9 class Solution {public : int lengthOfLastWord (const char *s) { int i, j; for (i = strlen (s) - 1 ; i >= 0 && s[i] == ' ' ; i --); for (j = i - 1 ; j >= 0 && s[j] != ' ' ; j --); return i < 0 ? 0 : i - j; } };
end 比 newInterval 的 start 小的 intervals 直接插入,从 end 比 newInterval 的 start 大的 intervals 开始,到 start 比 newInterval 的 end 大的 intervals 结束,对这部分区间合并,再把之后的 intervals直接插入,特判 newInterval 最小和最大两种极端情况。
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 class Solution {public : vector res; vector insert (vector &intervals, Interval newInterval) { if (intervals.size () == 0 ) { res.push_back (newInterval); return res; } int i, j; for (i = 0 ; i < intervals.size () && newInterval.start > intervals[i].end; i ++) res.push_back (intervals[i]); for (j = i; j < intervals.size () && newInterval.end >= intervals[j].start; j ++); if (j != 0 && i != intervals.size ()) res.push_back (Interval (min (intervals[i].start, newInterval.start), max (intervals[j - 1 ].end, newInterval.end))); else res.push_back (newInterval); for (; j < intervals.size (); j ++) res.push_back (intervals[j]); return res; } };
先按start排个序,然后慢慢合并。。。
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 class Solution {public : vector res; static bool cxompp (const Interval &a, const Interval &b) {return a.start < b.start;} vector merge (vector &intervals) { if (intervals.size () == 0 ) return res; sort (intervals.begin (), intervals.end (), cxompp); Interval last = intervals[0 ]; for (int i = 1 ; i < intervals.size (); i ++) { if (last.end >= intervals[i].start) last.end = max (last.end, intervals[i].end); else res.push_back (last), last = intervals[i]; } res.push_back (last); return res; } };
维护最大可跳距离,每个位置都枚举一次。
1 2 3 4 5 6 7 8 9 10 class Solution {public : bool canJump (int A[], int n) { if (n == 0 ) return false ; int i, jumpdis; for (i = jumpdis = 0 ; i < n && jumpdis >= 0 ; i ++, jumpdis --) jumpdis = max (A[i], jumpdis); return i == 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 class Solution {public : int dx[4 ] = {0 , 1 , 0 , -1 }; int dy[4 ] = {1 , 0 , -1 , 0 }; vector res; bool JudgeValid (int x, int y, vector &vis, vector &matrix) { return x >= 0 && x < matrix.size () && y >= 0 && y < matrix[0 ].size () && vis[x][y] == false ; } vector spiralOrder (vector &matrix) { int dir, x, y, nx, ny; if (matrix.size () == 0 ) return res; vector vis (matrix.size(), vector(matrix[0 ].size(), false )) ; for (dir = x = y = 0 ; JudgeValid (x, y, vis, matrix); x = nx, y = ny) { res.push_back (matrix[x][y]); vis[x][y] = true ; nx = x + dx[dir]; ny = y + dy[dir]; if (!JudgeValid (nx, ny, vis, matrix)) { dir = (dir + 1 ) % 4 ; nx = x + dx[dir]; ny = y + dy[dir]; } } return res; } };
代码二:
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 class Solution {public : int dx[4 ] = {0 , 1 , 0 , -1 }; int dy[4 ] = {1 , 0 , -1 , 0 }; vector res; vector spiralOrder (vector &matrix) { int dir, x, y, nx, ny; int l, r, u, d; if (matrix.size () == 0 ) return res; l = u = -1 ; r = matrix[0 ].size (); d = matrix.size (); for (dir = x = y = 0 ; res.size () < matrix.size () * matrix[0 ].size (); x = nx, y = ny) { res.push_back (matrix[x][y]); nx = x + dx[dir]; ny = y + dy[dir]; if (nx == d || nx == u || ny == r || ny == l) { dir = (dir + 1 ) % 4 ; if (dir == 0 ) l ++, r --, d --; else if (dir == 3 ) u ++; nx = x + dx[dir]; ny = y + dy[dir]; } } return res; } };
最大子串和,子串要求至少包含一个数字。
一个变量 sum 表示当前求得的子串和,当 sum 小于0时,对后面的子串没有贡献,则把 sum 置零,中间处理一下要求至少包含一个数字的要求即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : int maxSubArray (int A[], int n) { int ans = A[0 ], sum = 0 ; for (int i = 0 ; i < n; i ++) { sum += A[i]; if (sum < 0 ) sum = 0 , ans = max (ans, A[i]); else ans = max (ans, sum); } return ans; } };
题目没说 n 的取值范围,就不用 位运算 做标记了。
老老实实开三个 bool 数组,一个标记纵列,另外两个标记两个斜列,一行一行DFS。
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 class Solution {public : vector col, lc, rc; int ans; void DFS (int cur, int n) { if (cur == n) { ans ++; return ; } for (int i = 0 ; i < n; i ++) { if (!col[i] && !lc[n - cur - 1 + i] && !rc[cur + i]) { col[i] = lc[n - cur - 1 + i] = rc[cur + i] = true ; DFS (cur + 1 , n); col[i] = lc[n - cur - 1 + i] = rc[cur + i] = false ; } } } int totalNQueens (int n) { ans = 0 ; col.resize (n, 0 ); lc.resize (n << 1 , 0 ); rc.resize (n << 1 , 0 ); DFS (0 , n); return ans; } };
同上
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 class Solution {public : vector tmp; vector res; vector col, lc, rc; void DFS (int cur, int n) { if (cur == n) { res.push_back (tmp); return ; } string now (n, '.' ) ; for (int i = 0 ; i < n; i ++) { if (!col[i] && !lc[n - cur - 1 + i] && !rc[cur + i]) { col[i] = lc[n - cur - 1 + i] = rc[cur + i] = true ; now[i] = 'Q' ; tmp.push_back (now); DFS (cur + 1 , n); tmp.pop_back (); now[i] = '.' ; col[i] = lc[n - cur - 1 + i] = rc[cur + i] = false ; } } } vector solveNQueens (int n) { col.resize (n, 0 ); lc.resize (n << 1 , 0 ); rc.resize (n << 1 , 0 ); DFS (0 , n); return res; } };
很多人用特判错过了 n=-2147483648
这么优美的 trick,而不特判的话,似乎只能 long long 了。
经典的快速幂,用二进制理解也好,用折半理解也好,网上很多资料。
1 2 3 4 5 6 7 8 9 10 class Solution {public : double pow (double x, int n) { double res = 1 ; long long nn = n; if (nn < 0 ) x = 1 / x, nn = -nn; while (nn) { if (nn & 1 ) res *= x; x *= x; nn >>= 1 ; } return res; } };
这概念以前没听过诶。。题也没看到样例,不知道以后会不会更新,网上查了才明白啥意思。
调换单词字母顺序能一致的单词集合全放进答案。比如有tea, eat, aet,就都要放进答案,有cat, atc,就都要放进答案,而如果孤零零有个dog,没其他可和他一组的,那么就不放进答案。
手写hash能更快些,但是题目没给数据范围,给hash数组定多大都没合理性,干脆用unordered_map好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : vector res; vector anagrams (vector &strs) { unordered_map<string, int > mp; for (int i = 0 ; i < strs.size (); i ++) { string tmp = strs[i]; sort (tmp.begin (), tmp.end ()); if (!mp.count (tmp)) mp[tmp] = 0 ; else mp[tmp] ++; } for (int i = 0 ; i < strs.size (); i ++) { string tmp = strs[i]; sort (tmp.begin (), tmp.end ()); if (mp.count (tmp) && mp[tmp] > 0 ) res.push_back (strs[i]); } return res; } };
四个一组,就地旋转。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : void rotate (vector &matrix) { if (matrix.size () == 0 ) return ; int len = matrix.size (); int lenlimi = len + 1 >> 1 ; for (int i = 0 ; i < lenlimi; i ++) for (int j = 0 ; j < (len & 1 ? lenlimi - 1 : lenlimi); j ++) { int tmp = matrix[i][j]; matrix[i][j] = matrix[len - j - 1 ][i]; matrix[len - j - 1 ][i] = matrix[len - i - 1 ][len - j - 1 ]; matrix[len - i - 1 ][len - j - 1 ] = matrix[j][len - i - 1 ]; matrix[j][len - i - 1 ] = tmp; } } };
有重复数字,把数字统计起来好了。因为题目没说数字大小,所以统计用了unordered_map。
也可以把数组排序,DFS时跳过重复的数字。
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 class Solution {public : unordered_map<int , int > mp; vector tmp; vector res; int numsize; void DFS (int cnt) { if (cnt == numsize) { res.push_back (tmp); } for (unordered_map<int , int >::iterator it = mp.begin (); it != mp.end (); it ++) { if (it->second != 0 ) { tmp.push_back (it->first); it->second --; DFS (cnt + 1 ); tmp.pop_back (); it->second ++; } } } vector permute (vector &num) { numsize = num.size (); for (int i = 0 ; i < num.size (); i ++) { if (!mp.count (num[i])) mp[num[i]] = 1 ; else mp[num[i]] ++; } DFS (0 ); return res; } };
虽然题目没说有没有重复数字。。既然 Permutations II 说有了,那就当这个没有吧。
传统DFS。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : vector res; void DFS (int cur, vector &num) { if (cur == num.size ()) { res.push_back (num); return ; } for (int i = cur; i < num.size (); i ++) { swap (num[cur], num[i]); DFS (cur + 1 , num); swap (num[cur], num[i]); } } vector permute (vector &num) { DFS (0 , num); return res; } };
维护一步最远到达的位置,到达这个位置之前的位置需要的步数都是一样的,到达这个位置的时候,下一步的最远位置已经更新完毕。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int jump (int A[], int n) { int nex = 0 , pace = 0 , far = 0 ; for (int i = 0 ; i <= nex && i < n - 1 ; i ++) { far = max (far, A[i] + i); if (i == nex) { pace ++; nex = far; } } return pace; } };
同步扫描两个字符串,每当 p 遇到 *
,记录s和p的当前扫描位置,当 s 与 p 不匹配时,跑扫描指针回到 *
后一个字符, s 扫描指针回到上次遇到 *
之后与 p 开始匹配位置的下一个位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : bool isMatch (const char *s, const char *p) { int last_star = -1 , last_s = -1 , i, j; for (i = j = 0 ; s[i]; ) { if (s[i] == p[j] || p[j] == '?' ) i ++, j ++; else if (p[j] == '*' ) last_star = ++ j, last_s = i; else if (last_star != -1 ) i = ++ last_s, j = last_star; else return false ; } while (p[j] == '*' ) j ++; return !s[i] && !p[j]; } };
翻转num1和num2,大整数乘法,把结果再翻转。注意 int 和 char 的转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : string multiply (string num1, string num2) { string ans (num1.length() + num2.length() + 1 , 0 ) ; reverse (num1.begin (), num1.end ()); reverse (num2.begin (), num2.end ()); int cur = 0 , i, j, k; for (i = 0 ; i < num1.length (); i ++) { for (j = 0 ; j < num2.length (); j ++) { ans[i + j] += cur + (num1[i] - '0' ) * (num2[j] - '0' ); cur = ans[i + j] / 10 ; ans[i + j] %= 10 ; } for (k = i + j; cur; k ++) { ans[k] += cur; cur = ans[k] / 10 ; ans[k] %= 10 ; } } for (k = ans.length () - 1 ; k > 0 && ans[k] == 0 ; k --); ans.resize (k + 1 ); for (int i = 0 ; i < ans.length (); i ++) ans[i] += '0' ; reverse (ans.begin (), ans.end ()); return ans; } };
对于每个位置,取这个位置左边最高的
和右边最高的
的较低者,如果较低者
比这个位置高,则这个位置存水高度为较低者
减该位置高度。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : int trap (int A[], int n) { vector pre; int i, maxheight, ans; for (i = maxheight = 0 ; i < n; i ++) { maxheight = max (A[i], maxheight); pre.push_back (maxheight); } for (maxheight = ans = 0 , i = n - 1 ; i > 0 ; i --) { maxheight = max (A[i], maxheight); ans += max (0 , min (pre[i] - A[i], maxheight - A[i])); } return ans; } };
题目要求时间O(n),空间O(1),经分析,不得不破坏原数组 A。
方法一:
剔除非整数,把原数组 A 当作存在标记,存在的数 x 则 A[x-1]取负数。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : int firstMissingPositive (int A[], int n) { int i, j; for (i = j = 0 ; i < n; i ++) if (A[i] > 0 ) A[j ++] = A[i]; for (i = 0 ; i < j; i ++) if (abs (A[i]) <= j) A[abs (A[i]) - 1 ] = -abs (A[abs (A[i]) - 1 ]); for (i = 0 ; i < j; i ++) if (A[i] > 0 ) return i + 1 ; return j + 1 ; } };
方法二:把出现的符合范围的数swap到下标和数对应的位置,再次遍历,数和下标不对应则是第一个没出现的数。注意处理有重复数字。
1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : int firstMissingPositive (int A[], int n) { int i; for (i = 0 ; i < n; i ++) while (A[i] <= n && A[i] > 0 && A[i] != i + 1 && A[A[i] - 1 ] != A[i]) swap (A[i], A[A[i] - 1 ]); for (i = 0 ; i < n; i ++) if (A[i] != i + 1 ) return i + 1 ; return i + 1 ; } };
基础DFS
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 class Solution {public : vector tmp; vector ans; void DFS (vector &num, int ith, int now, int target) { if (now == target) { ans.push_back (tmp); return ; } if (ith == num.size ()) return ; int cnt = 0 ; while (now <= target) { DFS (num, ith + 1 , now, target); now += num[ith]; cnt ++; tmp.push_back (num[ith]); } while (cnt --) tmp.pop_back (); } vector combinationSum (vector &candidates, int target) { sort (candidates.begin (), candidates.end ()); DFS (candidates, 0 , 0 , target); return ans; } };
如果一个数没有被用,那么后面重复的这个数就别用,避免重复解。
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 {public : vector tmp; vector ans; void DFS (vector &num, int ith, int now, int target) { if (now == target) { ans.push_back (tmp); return ; } if (ith == num.size ()) return ; int nex; for (nex = ith + 1 ; nex < num.size () && num[nex] == num[ith]; nex ++); DFS (num, nex, now, target); if (num[ith] + now <= target) { now += num[ith]; tmp.push_back (num[ith]); DFS (num, ith + 1 , now, target); tmp.pop_back (); } } vector combinationSum2 (vector &num, int target) { sort (num.begin (), num.end ()); DFS (num, 0 , 0 , target); return ans; } };
直接模拟,递推。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : string countAndSay (int n) { string f[2 ]; f[0 ] = "1" ; for (int i = 1 ; i < n; i ++) { f[i & 1 ].clear (); for (int j = 0 ; j < f[i & 1 ^ 1 ].length ();) { int cnt; char x = f[i & 1 ^ 1 ][j]; for (cnt = 0 ; j < f[i & 1 ^ 1 ].length () && f[i & 1 ^ 1 ][j] == x; cnt ++, j ++); f[i & 1 ] += '0' + cnt; f[i & 1 ] += x; } } return f[n & 1 ^ 1 ]; } };
这道题考察回溯和数独结果的判断。ACM做过,就直接拿dancing links代码了,4ms。
关于dancing links,对于面试题来说变态了些,应该不至于考察。
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 class Solution {public : int rw[10 ], cl[10 ], in[10 ], RW[81 ], CL[81 ], IN[81 ], goal; char buf[100 ]; void Mark (int i, int num) { rw[RW[i]] ^= 1 << num; cl[CL[i]] ^= 1 << num; in[IN[i]] ^= 1 << num; } void init () { int i; for (i = 0 ; i < 10 ; ++ i) cl[i] = rw[i] = in[i] = 0 ; for (i = goal = 0 ; buf[i]; ++ i) goal += buf[i] == '.' ; for (i = 0 ; i < 81 ; ++ i) { RW[i] = i / 9 , CL[i] = i % 9 , IN[i] = i / 3 % 3 + i / 27 * 3 ; if (buf[i] != '.' ) Mark (i, buf[i] - '1' ); } } inline int Judge (int i, int num) {return ~(rw[RW[i]] | cl[CL[i]] | in[IN[i]]) & (1 << num);} int Oper (int sx, int k, int cur) { Mark (sx, k), buf[sx] = k + '1' ; if (dfs (cur + 1 )) return 1 ; Mark (sx, k), buf[sx] = '.' ; return 0 ; } int JudgeRWCLIN (int cur) { int i, j, k, x, cnt, sx; for (i = 0 ; i < 9 ; ++ i) for (k = 0 ; k < 9 ; ++ k) { if (~rw[i] & (1 << k)) { for (j = cnt = 0 ; j < 9 ; ++ j) { x = i * 9 + j; if (buf[x] == '.' && Judge (x, k)) ++ cnt, sx = x; } if (cnt == 0 ) return 0 ; else if (cnt == 1 ) return Oper (sx, k, cur); } if (~cl[i] & (1 << k)) { for (j = cnt = 0 ; j < 9 ; ++ j) { x = j * 9 + i; if (buf[x] == '.' && Judge (x, k)) ++ cnt, sx = x; } if (cnt == 0 ) return 0 ; else if (cnt == 1 ) return Oper (sx, k, cur); } if (~in[i] & (1 << k)) { for (j = cnt = 0 ; j < 9 ; ++ j) { x = i / 3 * 27 + j / 3 * 9 + i % 3 * 3 + j % 3 ; if (buf[x] == '.' && Judge (x, k)) ++ cnt, sx = x; } if (cnt == 0 ) return 0 ; else if (cnt == 1 ) return Oper (sx, k, cur); } } return 2 ; } bool dfs (int cur) { int i, j, num, cnt; if (cur == goal) return true ; for (i = 0 ; i < 81 ; ++ i) if (buf[i] == '.' ) { for (j = cnt = 0 ; j < 9 ; ++ j) if (Judge (i, j)) ++ cnt, num = j; if (cnt == 0 ) return false ; if (cnt == 1 ) return Oper (i, num, cur); } if ((num = JudgeRWCLIN (cur)) == 0 ) return false ; else if (num == 1 ) return true ; for (i = 0 ; i < 81 ; ++ i) if (buf[i] == '.' ) { for (j = 0 ; j < 9 ; ++ j) if (Judge (i, j)) { Mark (i, j), buf[i] = j + '1' ; if (dfs (cur + 1 )) return true ; Mark (i, j), buf[i] = '.' ; } } return false ; } void solveSudoku (vector &board) { int site = 0 ; for (int i = 0 ; i < 9 ; i ++) for (int j = 0 ; j < 9 ; j ++) buf[site ++] = board[i][j]; init (); dfs (0 ); site = 0 ; for (int i = 0 ; i < 9 ; i ++) for (int j = 0 ; j < 9 ; j ++) board[i][j] = buf[site ++]; } };
行列九宫格都判断一下。
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 : bool isValidSudoku (vector &board) { bool flag[3 ][9 ][9 ]; memset (flag, false , sizeof (flag)); for (int i = 0 ; i < 9 ; i ++) { for (int j = 0 ; j < 9 ; j ++) { if (board[i][j] != '.' ) { int x = board[i][j] - '1' ; if (flag[0 ][i][x] == true ) return false ; flag[0 ][i][x] = true ; if (flag[1 ][j][x] == true ) return false ; flag[1 ][j][x] = true ; if (flag[2 ][i / 3 * 3 + j / 3 ][x] == true ) return false ; flag[2 ][i / 3 * 3 + j / 3 ][x] = true ; } } } return true ; } };
二分
1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : int searchInsert (int A[], int n, int target) { int left, right, mid; for (left = 0 , right = n; left < right; ) { mid = left + right >> 1 ; if (A[mid] == target) return mid; if (A[mid] > target) right = mid; else left = mid + 1 ; } return left; } };
二分,容易错。可以用lower_bound和upper_bound。
手工代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : vector searchRange (int A[], int n, int target) { int left, right, mid, l, r; for (left = 0 , right = n; left < right; ) { mid = left + right >> 1 ; if (A[mid] >= target) right = mid; else left = mid + 1 ; } l = left; for (left = 0 , right = n; left < right; ) { mid = left + right >> 1 ; if (A[mid] > target) right = mid; else left = mid + 1 ; } r = left - 1 ; if (l >= n || A[l] != target) return vector (2 , -1 ); vector ans = {l, r}; return ans; } };
STL:
1 2 3 4 5 6 7 8 9 10 class Solution {public : vector searchRange (int A[], int n, int target) { int l = lower_bound (A, A + n, target) - A; int r = upper_bound (A, A + n, target) - A; if (l == n || A[l] != target) return vector (2 , -1 ); vector ans = {l, r - 1 }; return ans; } };
还是二分,但是要判断一下 mid 在哪部分里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int search (int A[], int n, int target) { int left = 0 , right = n - 1 , mid; while (left < right) { mid = left + right >> 1 ; if (A[mid] == target) return mid; if (A[mid] >= A[left]) { if (target < A[mid] && A[left] <= target) right = mid; else left = mid + 1 ; } else { if (target <= A[right] && A[mid] < target) left = mid + 1 ; else right = mid; } } return A[left] == target ? left : -1 ; } };
这道题时间限制在O(n)
,用一个 stack 实现括号配对+统计, 为了方便实现,写成数组的形式。
对不同深度的括号配对统计个数,一层配对成功把该层统计结果加给上一层,这一层清空。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : int longestValidParentheses (string s) { vector cnt (1 , 0 ) ; int i, ans; for (i = ans = 0 ; i < s.length (); i ++) { if (s[i] == '(' ) cnt.push_back (0 ); else { if (cnt.size () > 1 ) { cnt[cnt.size () - 2 ] += *cnt.rbegin () + 2 ; cnt.pop_back (); ans = max (ans, *cnt.rbegin ()); } else cnt[0 ] = 0 ; } } return ans; } };
从后往前找到第一个非降序的 num[i]
,再重新从后往前找到第一个比 num[i]
大的,swap(num[i], num[j])
,再把 i
之后的排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : void nextPermutation (vector &num) { int i, j; for (i = num.size () - 2 ; i >= 0 && num[i] >= num[i + 1 ]; i --); for (j = num.size () - 1 ; j > i && num[j] <= num[i]; j --); if (i < j) { swap (num[i], num[j]); sort (num.begin () + i + 1 , num.end ()); } else reverse (num.begin (), num.end ()); } };
直观的方法是枚举起点,判断这个起点下的子串是否合法,O(S.length()*L.size())
。
其实可以把 S 分成 L[0].length()
个序列,每个序列都是元素间相隔 L[0].length()
的(string开头),这些序列互不相干。
如下表,假设 L[0].length()=4
,第一行数字为分组组号,第二行数字表示 S 的序号。 1 2 (0)|(1)|(2)|(3)|(0)|(1)|(2)|(3)|(0)|(1)|(2)|(3)|(0)|(1)|(2)|(3)|(0)|(1)|(2)|(3)|(0)| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| 14| 15| 16| 17| 18| 19| 20|
对每个序列,用单调队列的思路来处理,一个一个子串入队,当包含了 L 中所有 string 的时候,保存答案。当新元素入队时超出统计允许时————即 L 中有 3 个 "str", 而这时候遇到第 4 个————则开始出队,一直出到队列里不足 3 个 "str",然后继续。
这样复杂度为O(L[0].length() * S.length() / L[0].length()) = O(S.length())
。目前提交结果是180ms。
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 58 59 60 61 62 63 class Solution {public : vector findSubstring (string S, vector &L) { vector ans; if (L.size () == 0 ) return ans; unordered_map<string, int > mp, sum; int llen = L[0 ].length (), i, front, rear; for (int i = 0 ; i < L.size (); i ++) { if (!mp.count (L[i])) mp[L[i]] = 1 ; else mp[L[i]] ++; } for (i = 0 ; i < llen; i ++) { sum = mp; int cnt = 0 ; for (front = rear = i; front + llen <= S.length (); front += llen) { string tmp = S.substr (front, llen); if (sum.count (tmp)) { if (sum[tmp] > 0 ) { sum[tmp] --; cnt ++; if (cnt == L.size ()) { ans.push_back (rear); } } else { while (sum[tmp] == 0 ) { string ntmp = S.substr (rear, llen); sum[ntmp] ++; cnt --; rear += llen; } sum[tmp] --; cnt ++; if (cnt == L.size ()) { ans.push_back (rear); } } } else { while (rear < front) { string ntmp = S.substr (rear, llen); sum[ntmp] ++; cnt --; rear += llen; } rear += llen; cnt = 0 ; } } } return ans; } };
假设 dividend 与 divisor 正负一致, divisor^(2^n)
为最接近 dividend 的 divisor 的幂,那么令 newdividend = dividend - divisor^(2^n)
,ans = ans + 2^n
,问题就更新为 newdividend 除以 divisor,如此迭代。用 divisor(2 n) 是因为 divisor 不停地辗转加自己就可以得到了。
有 -2147483648
这样的极限数据,因为 int 范围是 -2147483648~+2147483647
,发现负数比正数范围(多1),干脆把所有数都转成负数算,这样就避免用 long long 了。最后考察一下flag。
(如果转成正数的话,int 的 -(-2147483648)还是 -2147483648。。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : int divide (int dividend, int divisor) { bool flag = false ; if (divisor > 0 ) divisor = -divisor, flag ^= true ; if (dividend > 0 ) dividend = -dividend, flag ^= true ; int ans = 0 , res = divisor, ex = 1 ; if (divisor < dividend) return 0 ; while (res >= dividend - res) { res += res; ex += ex; } while (res <= divisor && dividend) { if (res >= dividend) { dividend -= res; ans += ex; } res >>= 1 ; ex >>= 1 ; } return flag ? -ans : ans; } };
KMP。
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 class Solution {public : char *strStr (char *haystack, char *needle) { int hlen = (int )strlen (haystack), nlen = (int )strlen (needle); if (nlen == 0 ) return haystack; vector next (nlen + 1 ) ; next[0 ] = -1 ; for (int i = 0 , j = -1 ; i < nlen;) { if (j == -1 || needle[i] == needle[j]) { i ++, j ++; if (needle[i] != needle[j]) next[i] = j; else next[i] = next[j]; } else j = next[j]; } for (int i = 0 , j = 0 ; i < hlen;) { if (j == -1 || haystack[i] == needle[j]) i ++, j ++; else j = next[j]; if (j == nlen) return haystack + i - j; } return NULL ; } };
两个游标 i, j 异步挪动,把不等于给定值的数往前挪。
1 2 3 4 5 6 7 8 9 class Solution {public : int removeElement (int A[], int n, int elem) { int i, j; for (i = j = 0 ; i < n; i ++) if (A[i] != elem) A[j ++] = A[i]; return j; } };
两个游标 i, j 异步挪动,不重复值往前挪。
1 2 3 4 5 6 7 8 9 class Solution {public : int removeDuplicates (int A[], int n) { int i, j; for (i = j = 1 ; i < n; i ++) if (A[i] != A[i - 1 ]) A[j ++] = A[i]; return n ? j : 0 ; } };
用头插法来做的,顺序插入到首节点之后,就反转了。每 k 个节点处理之后,把首节指针点移动到下 k 个的开头。最后面不足 k 个的话,再反转回来。
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 class Solution {public : int Reverse (ListNode *&pre, ListNode *&p, int k) { int i; ListNode *nex, *tmp; for (i = 1 ; p != NULL ; i ++, p = tmp) { if (i == 1 ) nex = p; tmp = p->next; p->next = pre->next; pre->next = p; if (i == k) i = 0 , pre = nex; } nex->next = NULL ; return i; } ListNode *reverseKGroup (ListNode *head, int k) { if (head == NULL ) return NULL ; ListNode *tmphead = new ListNode (0 ), *pre = tmphead, *p = head; tmphead->next = head; if (Reverse (pre, p, k) != 1 ) { p = pre->next; Reverse (pre, p, k); } return tmphead->next; } };
Reverse Nodes in k-Group的简化版。
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 : ListNode *swapPairs (ListNode *head) { if (head == NULL ) return NULL ; ListNode *tmphead = new ListNode (0 ), *pre = tmphead, *p = head, *tmp, *nex; tmphead->next = head; for (int i = 0 ; p != NULL ; i ++, p = tmp) { if (i & 1 ^ 1 ) nex = p; tmp = p->next; p->next = pre->next; pre->next = p; if (i & 1 ) pre = nex; } nex->next = NULL ; return tmphead->next; } };
一个堆(这里用了优先级队列),把所有 list 的首元素放堆里,O(logn)取得最小值插入新队列,异步推进。
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 : struct comp { bool operator () (ListNode *a,ListNode *b) {return a->val > b->val;} }; ListNode *mergeKLists (vector &lists) { ListNode *tmphead = new ListNode (0 ), *p = tmphead; priority_queue<ListNode*, vector<ListNode*>, comp> q; for (int i = 0 ; i < lists.size (); i ++) if (lists[i] != NULL ) q.push (lists[i]); while (!q.empty ()) { p->next = q.top (); p = p->next; q.pop (); if (p ->next != NULL ) q.push (p->next); } return tmphead->next; } };
DFS,保持当前右括号不多于左括号。
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 class Solution {public : string tmp; vector ans; void DFS (int left, int right, int n) { if (left == right && left == n) { ans.push_back (tmp); return ; } if (left < n) { tmp[left + right] = '(' ; DFS (left + 1 , right, n); } if (right < left) { tmp[left + right] = ')' ; DFS (left, right + 1 , n); } } vector generateParenthesis (int n) { tmp.resize (n << 1 ); DFS (0 , 0 , n); return ans; } };
归并排序的一次操作,设个哨兵头结点,结束后free。
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 : ListNode *mergeTwoLists (ListNode *l1, ListNode *l2) { ListNode *thead = new ListNode (0 ), *p = thead; while (l1 != NULL && l2 != NULL ) { if (l1->val < l2->val) p->next = l1, p = l1, l1 = l1->next; else p->next = l2, p = l2, l2 = l2->next; } while (l1 != NULL ) p->next = l1, p = l1, l1 = l1->next; while (l2 != NULL ) p->next = l2, p = l2, l2 = l2->next; p = thead->next; free (thead); return p; } };
用栈配对。
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 : bool isValid (string s) { stack st; for (int i = 0 ; i < s.length (); i ++) { switch (s[i]) { case '(' : st.push ('(' ); break ; case '[' : st.push ('[' ); break ; case '{' : st.push ('{' ); break ; case ')' : if (st.empty () || st.top () != '(' ) return false ; st.pop (); break ; case ']' : if (st.empty () || st.top () != '[' ) return false ; st.pop (); break ; case '}' : if (st.empty () || st.top () != '{' ) return false ; st.pop (); break ; } } return st.empty (); } };
两个指针相隔 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 class Solution {public : ListNode *removeNthFromEnd (ListNode *head, int n) { ListNode *pre, *slow, *quick; ListNode *newhead = new ListNode (0 ); newhead->next = head; int i = 0 ; for (pre = slow = quick = newhead; quick != NULL ; i ++) { pre = slow; if (i >= n) slow = slow->next; quick = quick->next; } pre->next = slow->next; free (slow); return newhead->next; } };
尝试了O(n^2)
的,但是应该常数很大吧,超时了。就是哈希存两两的和,然后通过查哈希表找到 两两+两两,要判断数字重复情况。这题数据量挺大的,O(n^3)
如果用不太好的方式实现的话也会超。
O(n^3)
方法:先对num排序,然后从两头枚举两个数,O(n^2)
,后两个数在前两个数之间的两端开始,和小了左边的往右,和大了右边的往左调整,O(n)
,总共O(n^3)
。
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 class Solution {public : vector ans; vector fourSum (vector &num, int target) { if (num.size () < 4 ) return ans; sort (num.begin (), num.end ()); for (int left = 0 ; left < num.size () - 3 ;) { for (int right = num.size () - 1 ; right > left + 2 ;) { int ml = left + 1 , mr = right - 1 ; while (ml < mr) { int tmpsum = num[left] + num[right] + num[ml] + num[mr]; if (tmpsum > target) mr --; else if (tmpsum < target) ml ++; else { vector tmp = {num[left], num[ml], num[mr], num[right]}; ans.push_back (tmp); ml ++; mr --; } for (; ml != left + 1 && ml < mr && num[ml] == num[ml - 1 ]; ml ++); for (; mr != right - 1 && ml < mr && num[mr] == num[mr + 1 ]; mr --); } for (right --; right > left + 2 && num[right] == num[right + 1 ]; right --); } for (left ++; left < num.size () - 3 && num[left] == num[left - 1 ]; left ++); } return ans; } };
基础DFS。
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 : const vector v = {"" , "" , "abc" , "def" , "ghi" , "jkl" , "mno" , "pqrs" , "tuv" , "wxyz" }; vector ans; string tmp; void DFS (int cur, string d) { if (cur == d.length ()) { ans.push_back (tmp); return ; } for (int i = 0 ; i < v[d[cur] - '0' ].length (); i ++) { tmp[cur] = v[d[cur] - '0' ][i]; DFS (cur + 1 , d); } } vector letterCombinations (string digits) { tmp.resize (digits.length ()); DFS (0 , digits); return ans; } };
O(n^2)
,先排序,枚举第一个数,后两个数一个在第一个数后边一个开始,一个从 末尾开始,和4Sum类似调整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : int threeSumClosest (vector &num, int target) { bool findans = false ; int ans; sort (num.begin (), num.end ()); for (int i = 0 ; i < num.size (); i ++) { for (int left = i + 1 , right = num.size () - 1 ; left < right;) { int tmpsum = num[i] + num[left] + num[right]; if (tmpsum > target) right --; else if (tmpsum < target) left ++; else return tmpsum; if (!findans || abs (tmpsum - target) < abs (ans - target)) ans = tmpsum, findans = true ; } } return ans; } };
同上。
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 class Solution {public : vector ans; vector threeSum (vector &num) { if (num.size () < 3 ) return ans; sort (num.begin (), num.end ()); for (int i = 0 ; i < num.size ();) { for (int left = i + 1 , right = num.size () - 1 ; left <right;) { int tmpsum = num[i] + num[left] + num[right]; if (tmpsum < 0 ) left ++; else if (tmpsum > 0 ) right --; else { vector tmp = {num[i], num[left], num[right]}; ans.push_back (tmp); left ++; right --; } for (; left != i + 1 && left < right && num[left] == num[left - 1 ]; left ++); for (; right != num.size () - 1 && left < right && num[right] == num[right + 1 ]; right --); } for (i ++; i < num.size () && num[i] == num[i - 1 ]; i ++); } return ans; } };
一个一个扫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : string ans; string longestCommonPrefix (vector &strs) { if (strs.size () == 0 ) return ans; if (strs.size () == 1 ) return strs[0 ]; for (int j = 0 ; ; j ++) { for (int i = 1 ; i < strs.size (); i ++) if (strs[i].size () == j || strs[i][j] != strs[i - 1 ][j]) return ans; ans += strs[0 ][j]; } return ans; } };
各有各的方法,重点是记录(上一个)数比(这个)数大或小,来确定谁减谁。基本是右结合的,所以从后往前扫好处理些。
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 : int ro[128 ]; int romanToInt (string s) { ro['I' ] = 1 ; ro['V' ] = 5 ; ro['X' ] = 10 ; ro['L' ] = 50 ; ro['C' ] = 100 ; ro['D' ] = 500 ; ro['M' ] = 1000 ; int ans = -1 , last; for (int i = s.length () - 1 ; i >= 0 ; i --) { if (ans == -1 ) ans = ro[s[i]]; else { if (last > ro[s[i]]) ans -= ro[s[i]]; else ans += ro[s[i]]; } last = ro[s[i]]; } return ans; } };
每个十进制位格式是一样的,只是字母替换一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : vector table = {"" , "I" , "II" , "III" , "IV" , "V" , "VI" , "VII" , "VIII" , "IX" }; string ro = "IVXLCDM" ; char convert (char x, int i) { if (x == 'I' ) return ro[i]; if (x == 'V' ) return ro[i + 1 ]; if (x == 'X' ) return ro[i + 2 ]; } string intToRoman (int num) { string ans; for (int i = 0 ; num; i += 2 , num /= 10 ) { int x = num % 10 ; string tmp = table[x]; for (int j = 0 ; j < tmp.size (); j ++) tmp[j] = convert (tmp[j], i); ans = tmp + ans; } return ans; } };
从两端开始枚举,较高的挡板往中间枚举的话一定无法得到更优解,故反复从较低挡板向中间枚举,O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : int maxArea (vector &height) { int left = 0 , right = height.size () - 1 , ans = -1 ; while (left < right) { ans = max (ans, min (height[left], height[right]) * (right - left)); if (height[left] < height[right]) left ++; else right --; } return ans; } };
每遇到一个 *
,问题都会出现分枝,需要用到栈或者递归。
没有 *
的情况好处理,遇到 *
的时候,穷举所有匹配长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : bool isMatch (const char *s, const char *p) { if (*p == 0 ) return *s == 0 ; if (*(p + 1 ) != '*' ) { if (*s && (*s == *p || *p == '.' )) return isMatch (s + 1 , p + 1 ); return false ; } else { for (; *s && (*s == *p || *p == '.' ); s ++) if (isMatch (s, p + 2 )) return true ; return isMatch (s, p + 2 ); } } };
首先处理负数的trick。然后主要思路就是通过 while(...) a = a * 10 + x % 10;
来将 x 翻转。
但是注意到 x 很大的时候,翻转的 x 会超出 int 范围,也许会刚好成为另一个和 a 得出的数相等的正数,所以不能完全翻转后判断,而可以在翻转恰好一半的时候判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : bool isPalindrome (int x) { if (x < 0 ) return false ; if (x == 0 ) return true ; int a = 0 , b = x, cnt = 1 ; while (x /= 10 ) cnt ++; for (; b && cnt >= 0 ; b /= 10 , cnt -= 2 ) { if (cnt == 1 ) return a == b / 10 ; else if (cnt == 0 ) return a == b; a = a * 10 + b % 10 ; } return false ; } };
任何类似多符号、符号数字间有空格的小问题都直接输出 0,这就好办了。处理越界用 long long。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : int atoi (const char *str) { long long ans = 0 ; bool flag = false ; for (; *str == ' ' ; str ++); if (*str == '+' ) str ++; else if (*str == '-' ) flag = true , str ++; for (; isdigit (*str); str ++) { ans = ans * 10 + *str - '0' ; if ((flag ? -ans : ans) > INT_MAX) return INT_MAX; else if ((flag ? -ans : ans) < INT_MIN) return INT_MIN; } return (int )(flag ? -ans : ans); } };
还是关于越界的讨论,不过这道题本身没有设置处理方式,重点在于面试时的交流。
1 2 3 4 5 6 7 8 9 class Solution {public : int reverse (int x) { int a = 0 ; for ( int b = x >= 0 ? x : -x; b; b /= 10 ) a = a * 10 + b % 10 ; return x >= 0 ? a : -a; } };
题意的 "z" 字形指一列nRows个,然后斜着往右上一格一个回到第一行,然后再一列nRows个。比如nRows=5,如下: 1 2 3 4 5 1 9 17 25 2 8 10 16 18 24 26 3 7 11 15 19 23 27 … 4 6 12 14 20 22 28 30 5 13 21 29
每行字母在原字符串中的间隔是有规律的,虽然两层for循环,但是s中每个字母只访问了一次,O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : string convert (string s, int nRows) { if (nRows == 1 ) return s; string ans; int a = (nRows << 1 ) - 2 , b = 0 ; for (int i = 0 ; i < nRows; i ++, a -= 2 , b += 2 ) { bool flag = false ; for (int j = i; j < s.length (); j += flag ? (b ? b : a) : (a ? a : b), flag ^= 1 ) ans += s[j]; } return ans; } };
网上O(n)的方法是厉害啊。。。简单解释如下:
1、预处理字符串,前后加(哨兵)字符比如 !
,每个字母旁边加辅助字符比如#
,这样例如字符串 s="ababbcbb"
就变成 tmp="!#a#b#a#b#b#c#b#b#!"
。这样的好处是不用讨论回文串长度的奇偶。
2、对转化后的串,维护一个 center 和一个 reach,center 是当前已发现的 reach 最远的回文串中心位置,reach 是这个回文串最右端的位置,center和reach可初始化为 1,即第一个#
的位置。
3、维护一个数组vector r(tmp.length())
,r[i]
表示 i 位置为中心的回文串半径。
4、在考察位置 i 的时候,所有 j < i 的 r[j] 都是已知的子问题。如果 i 在 reach 的左边,则 i 包含在以 center 为中心的回文串中,那么可以想到,如果和 i 关于 center 对称位置的 mirrori 为中心的回文串覆盖范围没有到达 center 为中心的回文串边缘,则 i 为中心的回文串肯定和 mirrori 的一样。而如果 mirrori 的回文串到达了边缘甚至超过,或者 i 本来就在 reach 的右边,那么对 i 为中心的回文串进行一次扩展,则结果 或者刚好不扩展,或者一定更新了reach。无论怎样,这里都得到了 r[i]。知道了所有 r[i],答案就出来了。
核心问题在于第4步“对 i 为中心的回文串进行扩展”的复杂度。每次发生“对 i 扩展”,必然是对 reach 的扩展(也可能刚好不扩展,这个不影响复杂度),而 reach 的扩展范围是 tmp 的长度大约 2n,所以总复杂度为 O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : string longestPalindrome (string s) { int center = 1 , reach = 1 , ansstart = 0 , anslength = 0 ; string tmp = "!#" ; for (int i = 0 ; i < s.length (); i ++) tmp += s[i], tmp += '#' ; tmp + '!' ; vector r (tmp.length()) ; for (int i = 2 ; i < tmp.length (); i ++) { int mirrori = center * 2 - i; r[i] = reach > i ? min (r[mirrori], reach - i) : 0 ; for (; tmp[i + r[i] + 1 ] == tmp[i - r[i] - 1 ]; r[i] ++); if (i + r[i] > reach) reach = i + r[i], center = i; if (r[i] > anslength) { ansstart = i - r[i] >> 1 ; anslength = r[i]; } } return s.substr (ansstart, anslength); } };
如果 A[pa] < B[pb],那么 A[pa] 一定在 A 与 B 合并后的前 pa + pb + 2 个数中。
证明: A 中有 pa + 1 个数 <= A[pa],B 中有小于 pb + 1 个数 <= A[pa],合并后有少于pa + pb + 2 个数 <= A[pa]。
利用这个性质迭代找 A 与 B 合并后的第 k 大数。
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 findKth (int A[], int m, int B[], int n, int k) { int pm, pn; while (true ) { if (m == 0 ) return B[k - 1 ]; if (n == 0 ) return A[k - 1 ]; if (k == 1 ) return min (A[k - 1 ], B[k - 1 ]); if (m <= n) pm = min (k >> 1 , m), pn = k - pm; else pn = min (k >> 1 , n), pm = k - pn; if (A[pm - 1 ] < B[pn - 1 ]) A += pm, m -= pm, k -= pm; else if (A[pm - 1 ] > B[pn - 1 ]) B += pn, n -= pn, k-= pn; else break ; } return A[pm - 1 ]; } double findMedianSortedArrays (int A[], int m, int B[], int n) { if ((m + n) & 1 ) return findKth (A, m, B, n, (m + n >> 1 ) + 1 ); else return (findKth (A, m, B, n, m + n >> 1 ) + findKth (A, m, B, n, (m + n >> 1 ) + 1 )) * 0.5 ; } };
维护一个不重复字符的区间。
代码一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : int lengthOfLongestSubstring (string s) { vector isin (128 , false ) ; int ans = 0 ; for (int front = 0 , rear = 0 ; front < s.length (); front ++) { if (isin[s[front]]) for (; rear < front && isin[s[front]]; isin[s[rear]] = false , rear ++); isin[s[front]] = true ; ans = max (ans, front - rear + 1 ); } return ans; } };
代码二:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : int lengthOfLongestSubstring (string s) { vector site (128 , -1 ) ; int nowstart = -1 , ans = 0 ; for (int i = 0 ; i < s.length (); i ++) { if (site[s[i]] >= nowstart) nowstart = site[s[i]] + 1 ; site[s[i]] = i; ans = max (i - nowstart + 1 , ans); } return ans; } };
大整数加法的链表版。
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 class Solution {public : ListNode *addTwoNumbers (ListNode *l1, ListNode *l2) { ListNode *ans = new ListNode (0 ), *p = ans; int cur = 0 ; while (l1 != NULL || l2 != NULL || cur) { p->val = (l1 ? l1->val : 0 ) + (l2 ? l2->val : 0 ) + cur; cur = p->val / 10 ; p->val %= 10 ; if (l1) l1 = l1->next; if (l2) l2 = l2->next; if (l1 || l2 || cur) p->next = new ListNode (0 ); p = p->next; } return ans; } };
哈希存位置,O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : vector twoSum (vector &numbers, int target) { unordered_map<int , int > mp; vector ans; for (int i = 0 ; i < numbers.size (); i ++) { if (mp.count (target - numbers[i])) { ans.push_back (mp[target - numbers[i]] + 1 ); ans.push_back (i + 1 ); break ; } if (!mp.count (numbers[i])) mp[numbers[i]] = i; } return ans; } };