做有挑战的事情,就从这里开始。
忘记这本书现在在哪儿了,以前还以为能祖祖辈辈留传,现在只能借助 Nowcoder 了。Now coder,Forever thinker。
想以自以为最优美的 code 来体现算法与数据结构的美。
题目:二维数组中的查找
题解:拿左下角 $(rowCount - 1, 0)$ 为起点,如果 $target >a[i][j]$,那么第 $0$ 列就无效了,小于的话,最后一行就无效了,就这样每次删除一行或者一列。
时间复杂度:$O(n + m)$
注意:这题最暴力的做法是 $O(n*m)$ 遍历整个二维数组;稍微优雅的是枚举行,二分列,这样是 $O(n * logm)$
日期:2019 年 7 月 19 日
代码:
class Solution { public: bool Find(int target, vector<vector<int> > array) { if(array.size() == 0) { return false; } int rowCount = array.size(); int colCount = array[0].size(); int i = rowCount - 1; int j = 0; while(i >= 0 && j < colCount) { if(array[i][j] == target) { return true; } array[i][j] > target ? i -- : j ++; } return false; } };
题目:替换空格
题解:这题目正着来的话必然需要开辟新的字符数组去存储结果,可以倒着考虑,计算好新数组的长度,倒着更新就可以了。
时间复杂度:$O(length + spaceCount * 2)$
注意:不喜欢 if else 了,三目运算符瞎用
日期:2019 年 7 月 19 日
代码:
class Solution { public: void replaceSpace(char *str,int length) { int spaceCount = 0; for(int i = 0; i < length; i ++) { str[i] == ' ' ? spaceCount ++ : 0; } int newLength = length + spaceCount * 2; str[newLength] = 0; length --; newLength --; while(length >= 0) { str[length] == ' ' ? (str[newLength --] = '0', str[newLength --] = '2', str[newLength --] = '%') : (str[newLength --] = str[length]); length --; } } };
题目:从尾到头打印链表
题解:正着遍历链表,把结果存到 vector 里面,然后把 vector 倒一下,写这些算法题的时候还是喜欢练童子功,尽量避免调用 STL 函数之类的,手写比较能锻炼 coding 能力和 Bug free。
时间复杂度:$O(listLength)$
日期:2019 年 7 月 19 日
代码:
/** * struct ListNode { * int val; * struct ListNode *next; * ListNode(int x) : * val(x), next(NULL) { * } * }; */ class Solution { public: vector<int> printListFromTailToHead(ListNode* head) { vector<int> result; while(head != NULL) { result.push_back(head->val); head = head->next; } for(int i = 0; i < result.size() / 2; i ++) { int t = result[i]; result[i] = result[result.size() - 1 - i]; result[result.size() - 1 - i] = t; } return result; } };
题解:还有一种思路是先把链表翻转,然后遍历链表即可,不用翻转 vector 了。链表翻转写了那么久,技术不行了... ...
时间复杂度:$O(listLength)$
代码:
/** * struct ListNode { * int val; * struct ListNode *next; * ListNode(int x) : * val(x), next(NULL) { * } * }; */ class Solution { private: ListNode* ReverseList(ListNode* head) { if(head == NULL) { return NULL; } ListNode* a = head; ListNode* b = a -> next; a->next = NULL; while(b) { ListNode* c = b -> next; b -> next = a; head = b; a = b; b = c; } return head; } public: vector<int> printListFromTailToHead(ListNode* head) { head = ReverseList(head); vector<int> result; while(head) { result.push_back(head -> val); head = head -> next; } return result; } };
题目:重建二叉树
题解:前序遍历可以确定 root,中序遍历可以帮助判断左右子树。
时间复杂度:$O(n^2)$
记录:二叉树配中国好声音,可我还是喜欢许嵩的安静与努力,每首歌都赋予了他的灵感,想法与批判
日期:2019 年 7 月 19 日
代码:
/** * Definition for binary tree * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { private: TreeNode* BuildBinaryTree( vector<int>& pre, vector<int>& vin, map<int, int>& preMap, map<int, int>& vinMap, int L, int R) { if(L > R || L < 0 || L >= (int)pre.size() || R < 0 || R > (int)pre.size()) { return NULL; } TreeNode* currentNode = new TreeNode(pre[L]); int x = L; for(int i = L; i <= R; i ++) { if(vinMap[pre[i]] <= vinMap[pre[L]]) { x = i; } } currentNode -> left = BuildBinaryTree(pre, vin, preMap, vinMap, L + 1, x); currentNode -> right = BuildBinaryTree(pre, vin, preMap, vinMap, x + 1, R); return currentNode; } public: TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) { if(pre.size() == 0) { return NULL; } map<int, int> preMap; map<int, int> vinMap; for(int i = 0; i < pre.size(); i ++) { preMap[pre[i]] = i; vinMap[vin[i]] = i; } return BuildBinaryTree(pre, vin, preMap, vinMap, 0, pre.size() - 1); } };
题目:用两个栈实现队列
题解:
1. 如果新元素进来,压到 stack2 里面。
2. 想得到队头的时候,如果 stack1 不为空,弹出 stack1 栈顶;如果 stack1 为空,把 stack2 元素全部导入到 stack1 中,弹出 stack1 栈顶
时间复杂度:线性复杂度,每个元素的生命周期是进入 stack2,导入到 stack1,弹出 stack1
日期:2019 年 7 月 19 日
代码:
class Solution { public: void push(int node) { stack2.push(node); } int pop() { if(stack1.empty()) { while(!stack2.empty()) { int x = stack2.top(); stack2.pop(); stack1.push(x); } } int result = stack1.top(); stack1.pop(); return result; } private: stack<int> stack1; stack<int> stack2; };
题目:旋转数组的最小数字
记录:感觉题目有点问题,如果原数组是非递减的,那么好像不是很好做,二分没法保证单调性,需要用三分找到最大值的位置,最大值后面那个就是最小值。如果题目改为严格递增,那么就好办了,以下代码是原数组严格递增的。当年面试京东实习的时候也是这个题啊,一晃快两年半过去了。
时间复杂度:$O(logn)$
日期:2019 年 7 月 19 日
代码:
class Solution { public: int minNumberInRotateArray(vector<int> rotateArray) { if(rotateArray.size() == 0) { return 0; } if(rotateArray[0] < rotateArray[rotateArray.size() - 1]) { return rotateArray[0]; } int L = 1, R = rotateArray.size() - 1, result = 0; while(L <= R) { int mid = (L + R) / 2; if(rotateArray[mid] >= rotateArray[0]) { L = mid + 1; } else { result = mid; R = mid - 1; } } return rotateArray[result]; } };
题目:斐波那契数列
题解:拿数组记录优化一下,原理和记忆化搜索是一样的
时间复杂度:$O(n)$,不拿数组记下来的话,复杂度会爆表哦。
日期:2019 年 7 月 19 日
代码:
class Solution { public: int fib[50]; int Fibonacci(int n) { return n <= 1 ? n : fib[n] ? fib[n] : fib[n] = Fibonacci(n - 1) + Fibonacci(n - 2); } };
题目:跳台阶
题解:简单的线性 dp,和上一题没差别,但是这题题目出的不规范啊,如果 $n$ 很大,答案马上就爆掉了,也没要求取模,不知道出题人怎么想的。
时间复杂度:$O(n)$
日期:2019 年 7 月 19 日
代码:
class Solution { public: int fib[50] = {0}; int jumpFloor(int n) { return n <= 1 ? 1 : fib[n] ? fib[n] : fib[n] = jumpFloor(n - 1) + jumpFloor(n - 2); } };
题目:变态跳台阶
题解:就是简单的 dp,不拿递归写了,直接循环写着简洁明了。写出 dp 方程,前缀和优化之后发现就是 2 的次方
时间复杂度:$O(n)$,其实可以不用 $O(n)$ 了,既然发现了是 2 的次方,那么快速幂更快。
日期:2019 年 7 月 19 日 22:49:39
代码:
class Solution { public: int jumpFloorII(int number) { int sum = 1; for(int i = 2; i <= number; i ++) { sum = sum * 2; } return sum; } };
题目:矩形覆盖
题解:状态压缩 dp,每一列的状态用四种,全空,全满,上一个空,下一个空,可以用 $0~3$ 来表示状态,$dp[i][j]$ 可以表示第 $1$ 列至第 $i$ 列都填满的情况下,且第 $i + 1$ 列状态为 $j$ 的方案数,这样就容易推了。如果 $n 很大,往往答案会取模,这个时候可以用矩阵快速幂加速这个 dp。
时间复杂度:直接 dp:$O(5 * n)$,矩阵快速幂优化 dp: $O(4^3 * logn)$
日期:2019 年 7 月 20 日 09:26:02
代码:
直接 dp
class Solution { public: int dp[10010][4] = {0}; int rectCover(int number) { dp[1][0] = dp[1][3] = 1; for(int i = 1; i <= number - 1; i ++) { dp[i + 1][0] += dp[i][0]; dp[i + 1][3] += dp[i][0]; dp[i + 1][2] += dp[i][1]; dp[i + 1][1] += dp[i][2]; dp[i + 1][0] += dp[i][3]; } return dp[number][0]; } };
矩阵快速幂
$egin{bmatrix}
dp[1][0] & dp[1][1] & dp[1][2] & dp[1][3]
end{bmatrix}egin{bmatrix}
1 & 0 & 0 & 1\
0 & 0 & 1 & 0\
0 & 1 & 0 & 0\
1 & 0 & 0 & 0
end{bmatrix}^{n-1}
=
egin{bmatrix}
dp[n][0] & dp[n][1] & dp[n][2] & dp[n][3]
end{bmatrix}$
struct M { int row, col; int m[5][5]; M(int r, int c) { row = r; col = c; memset(m, 0, sizeof m); } }; class Solution { private: long long mod = 1e9 + 7; M Mul(M& a, M& b) { M result(a.row, b.col); for(int i = 0; i < result.row; i ++) { for(int j = 0; j < result.col; j ++) { for(int k = 0; k < a.col; k ++) { long long temp = 1LL * a.m[i][k] * b.m[k][j] % mod; result.m[i][j] = (int)((result.m[i][j] + temp) % mod); } } } return result; } public: int rectCover(int number) { if(number == 0) { return 0; } M x(4, 4); for(int i = 0; i < 4; i ++) { x.m[i][i] = 1; } M y(4, 4); y.m[0][0] = 1, y.m[0][3] = 1; y.m[1][2] = 1; y.m[2][1] = 1; y.m[3][0] = 1; number --; while(number > 0) { if(number % 2 == 1) { x = Mul(x, y); } y = Mul(y, y); number = number / 2; } M z(1, 4); z.m[0][0] = 1, z.m[0][3] = 1; z = Mul(z, x); return z.m[0][0]; } };
题目:二进制中1的个数
记录:
什么是底层知识?这个才是啊... ... 工作和学习中往往因为繁杂的业务逻辑而忘却了计算机本身提出来的时候要解决的问题。ACM 更像是一种接近计算机本源问题的拓展应用。
有空要看看这篇博客,虽然工作能挣钱,但是千万不要成为了工作的奴隶,计算机是 01 的世界,是算法的世界,不是业务需求的世界。
时间复杂度:$O(logn)$
日期:2019 年 7 月 20 日 10:24:06
代码:
class Solution { public: int NumberOf1(int n) { int count = 0; while (n != 0) { ++count; n = (n - 1) & n; } return count; } };
题目:数值的整数次方
题解:快速幂就可以了,如果指数是负数,那么最后还需要取倒数
时间复杂度:$O(log(|exponent|))$
日期:2019 年 7 月 20 日 10:28:52
代码:
class Solution { public: double Power(double base, int exponent) { double answer = 1.0; int flag = exponent >= 0 ? 0 : 1; exponent = abs(exponent); while(exponent > 0) { if(exponent % 2 == 1) { answer = answer * base; } base = base * base; exponent = exponent / 2; } if(flag) { answer = 1.0 / answer; } return answer; } };
题解:这题有个要求,移动之后奇数内部的相对顺序不能变,偶数之间的相对顺序也不能变,好像有点恶心了啊这样,就要开辟额外空间来做了。不然的话每次交换最左边的偶数和最右边的奇数就可以了。
时间复杂度:$O(n)$
记录:写代码的时候,各种 C# 语法乱入了... ... 编译错误好几次
日期:2019 年 7 月 20 日 10:49:46
代码:
class Solution { public: void reOrderArray(vector<int> &array) { vector<int> odd; for(int i = 0; i < array.size(); i ++) { if(array[i] % 2 == 1) { odd.push_back(array[i]); } } int p = array.size() - 1; for(int i = array.size() - 1; i >= 0; i --) { if(array[i] % 2 == 0) { array[p --] = array[i]; } } for(int i = 0; i < odd.size(); i ++) { array[i] = odd[i]; } } };
题目:链表中倒数第k个结点
题解:先走一遍链表统计出有多少个节点,然后根据 $k$ 在看看是从前往后的第几个,注意 $k$ 有可能大于链表节点个数
时间复杂度:$O(n)$
日期:2019 年 7 月 20 日 11:05:40
代码:
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } };*/ class Solution { public: ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) { int nodeCount = 0; ListNode* temp = pListHead; while(temp) { nodeCount ++; temp = temp -> next; } if(k > nodeCount) { return NULL; } nodeCount = nodeCount - k; temp = pListHead; while(temp && nodeCount > 0) { nodeCount --; temp = temp -> next; } return temp; } };
题目:反转链表
题解:和 从尾到头打印链表 是一样的
时间复杂度:$O(n)$
日期:2019 年 7 月 20 日 11:07:35
代码:
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } };*/ class Solution { public: ListNode* ReverseList(ListNode* pHead) { if(pHead == NULL) { return NULL; } ListNode* a = pHead; ListNode* b = a -> next; a->next = NULL; while(b) { ListNode* c = b -> next; b -> next = a; pHead = b; a = b; b = c; } return pHead; } };
题目:合并两个排序的链表
题解:和归并排序一样的思路,三目运算符好像要加括号啊,不加括号跑挂了。
时间复杂度:$O(n)$
日期:2019 年 7 月 20 日 11:35:59
代码:
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } };*/ class Solution { public: ListNode* Merge(ListNode* pHead1, ListNode* pHead2) { ListNode* head = NULL; ListNode* result = NULL; while(pHead1 || pHead2) { ListNode* temp; if(pHead1 != NULL && pHead2 != NULL) { pHead1 -> val <= pHead2 -> val ? (temp = pHead1, pHead1 = pHead1 -> next) : (temp = pHead2, pHead2 = pHead2 -> next); } else { pHead1 ? (temp = pHead1, pHead1 = pHead1 -> next) : (temp = pHead2, pHead2 = pHead2 -> next); } !head ? (result = head = temp) : (head -> next = temp, head = temp); } return result; } };
题目:树的子结构
题解:树在子结构不同于子树,子树的话可以用 dfs 序 + KMP 判断,子结构貌似只能暴力?稍后补上这题,再想想。更难一些的是树的同构问题。
时间复杂度:
日期:
代码:
题目:二叉树的镜像
题解:挨个节点换了一下左右子树就可以了,注意并没有顺序要求,什么顺序都可以,我就按 dfs 先序交换了一下。
时间复杂度:$O(n)$
日期:2019 年 7 月 20 日 13:03:34
代码:
/* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } };*/ class Solution { public: void Mirror(TreeNode *pRoot) { if(!pRoot) { return; } swap(pRoot -> left, pRoot -> right); if(pRoot -> left) { Mirror(pRoot -> left); } if(pRoot -> right) { Mirror(pRoot -> right); } } };
题目:顺时针打印矩阵
题解:是个模拟题,转弯的时候顺便记得缩小边界,判断是否需要转弯的时候看一下按目前方向走过去的点出没出边界就可以了。做任何事情的时候尽量不要改变 input。
时间复杂度:$O(n*m)$
日期:2019 年 7 月 20 日 12:55:44
代码:
class Solution { private: int dir[4][2] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} }; bool NeedChangeDir(int x, int y, int d, int upRow, int lowRow, int leftCol, int rightCol) { int nx = x + dir[d][0]; int ny = y + dir[d][1]; return nx < upRow || nx > lowRow || ny < leftCol || ny > rightCol; } public: vector<int> printMatrix(vector<vector<int> > matrix) { vector<int> result; int rowCount = matrix.size(); if(rowCount == 0) { return result; } int colCount = matrix[0].size(); int upRow = 0, lowRow = rowCount - 1; int leftCol = 0, rightCol = colCount - 1; int curX = 0, curY = 0, curDir = 0; for(int i = 0; i < rowCount * colCount; i ++) { result.push_back(matrix[curX][curY]); if(i == rowCount * colCount - 1) { break; } if(NeedChangeDir(curX, curY, curDir, upRow, lowRow, leftCol, rightCol)) { curDir = (curDir + 1) % 4; switch(curDir) { case 0: leftCol ++; break; case 1: upRow ++; break; case 2: rightCol --; break; case 3: lowRow --; break; } } curX = curX + dir[curDir][0]; curY = curY + dir[curDir][1]; } return result; } };
题目:包含min函数的栈
题解:开两个栈,一个存正常的,一个存当前栈里面的最小值,就相当于前缀最小值,就很 easy 了。
记录:这题可以无限拓展啊,如果不是栈的话,就是普通的一堆数字,完全可以支持 insert,getMin,delete 这几种操作。set,线段树之类的数据结构弄一弄就可以了。
时间复杂度:$O(n)$
日期:2019 年 7 月 20 日 11:58:41
代码:
class Solution { private: std::stack<int> stack; std::stack<int> minInStack; public: void push(int value) { stack.push(value); minInStack.push(minInStack.size() >= 1 ? std::min(minInStack.top(), value) : value); } void pop() { stack.pop(); minInStack.pop(); } int top() { return stack.top(); } int min() { return minInStack.top(); } };
题目:栈的压入、弹出序列
题解:题意描述模糊不清,给的是入栈顺序,出栈可以在入栈过程中随时进行。
时间复杂度:$O(n)$
日期:2019 年 7 月 20 日 13:20:00
代码:
class Solution { public: bool IsPopOrder(vector<int> pushV,vector<int> popV) { std::stack<int> stack; int x = 0; for(int i = 0; i < pushV.size(); i ++) { stack.push(pushV[i]); while(!stack.empty() && x < popV.size() && popV[x] == stack.top()) { stack.pop(); x ++; } } return x == popV.size(); } };
题目:从上往下打印二叉树
题解:这题就是个广搜。
时间复杂度:$O(n)$
日期:2019 年 7 月 20 日 13:25:06
代码:
/* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } };*/ class Solution { public: vector<int> PrintFromTopToBottom(TreeNode* root) { std::vector<int> result; if(!root) { return result; } std::queue<TreeNode*> queue; queue.push(root); while(!queue.empty()) { TreeNode* h = queue.front(); queue.pop(); result.push_back(h -> val); if(h -> left) { queue.push(h -> left); } if(h -> right) { queue.push(h -> right); } } return result; } };
题目:二叉搜索树的后序遍历序列
题解:注意题目要求的是二叉搜索树,最后面那个元素是当前子树的根节点,序列 $[L, R]$ 必须要求是 $[L, x]$ 的值都比 $v[R]$ 小,$[x + 1, R - 1]$ 的值都比 $v[R]$ 大,这个就是判断指标。
时间复杂度:$O(n^2)$
日期:2019 年 7 月 20 日 13:41:49
代码:
class Solution { private: bool IsBST(vector<int>& v, int L, int R) { if(v.size() == 0) return false; if(L >= R) return true; int rootVal = v[R]; int x = L - 1; for(int i = L; i < R; i ++) { if(v[i] < rootVal) { x = i; } } int ok = 1; for(int i = L; i <= x; i ++) { if(v[i] > rootVal) { ok = 0; break; } } return ok && IsBST(v, L, x) && IsBST(v, x + 1, R - 1); } public: bool VerifySquenceOfBST(vector<int> sequence) { return IsBST(sequence, 0, sequence.size() - 1); } };
题目:二叉树中和为某一值的路径
题解:DFS 一波下去就可以了,题目有问题啊,如果路径长度一样的按什么顺序排... ...
时间复杂度:$O(n)$
日期:2019 年 7 月 20 日 14:05:21
代码:
/* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } };*/ class Solution { private: vector<int> curPath; vector<vector<int> > result; void DFS(TreeNode* root, int expectNumber) { if(expectNumber == 0 && !root -> left && !root -> right) { result.push_back(curPath); return; } if(root -> left) { curPath.push_back(root -> left -> val); DFS(root -> left, expectNumber - root -> left -> val); curPath.pop_back(); } if(root -> right) { curPath.push_back(root -> right -> val); DFS(root -> right, expectNumber - root -> right -> val); curPath.pop_back(); } } static bool cmp(vector<int>& a, vector<int>& b) { return a.size() > b.size(); } public: vector<vector<int> > FindPath(TreeNode* root,int expectNumber) { if(!root) { return result; } curPath.push_back(root -> val); DFS(root, expectNumber - root -> val); curPath.pop_back(); std::sort(result.begin(), result.end(), cmp); return result; } };
题目:复杂链表的复制
题解:开个 map,给每个节点标个 id,这样就知道 random 的 id 是哪个了。看别人的博客发现有更牛逼的方法,我实在是想不到,只能感叹世界的奇妙... ...
记录:每天跑跑步,运动过后就是饿饿饿。突然记起来中国无人车领导者 Pony.AI 王子恒巨佬曾经给我讲过这个题!向王子恒学习!
时间复杂度:我的是 $O(n*logn)$,博客里的是 $O(n)$,还是要写一下博客里面的方法
日期:2019 年 7 月 20 日 17:35:26
代码:
/* struct RandomListNode { int label; struct RandomListNode *next, *random; RandomListNode(int x) : label(x), next(NULL), random(NULL) { } }; */ class Solution { public: RandomListNode* Clone(RandomListNode* pHead) { if(!pHead) return NULL; std::map<RandomListNode*, int> nodeMap; int id = 0; RandomListNode* temp = pHead; while(temp) { nodeMap[temp] = ++ id; temp = temp -> next; } RandomListNode* newHead = NULL; std::map<int, RandomListNode*> newNodeMap; temp = pHead; int index = 1; while(temp) { RandomListNode* node = new RandomListNode(temp -> label); newNodeMap[index] = node; index == 1 ? (newHead = node) : (newNodeMap[index - 1] -> next = node); temp = temp -> next; index ++; } temp = pHead; index = 1; while(temp) { newNodeMap[index] -> random = newNodeMap[nodeMap[temp -> random]]; temp = temp -> next; index ++; } return newHead; } };
$O(n)$ 的写法,本地测试了没发现问题... ... 但是交上去就是 WA,还要再研究研究。
时间:2019 年 7 月 20 日 20:42:00
/* struct RandomListNode { int label; struct RandomListNode *next, *random; RandomListNode(int x) : label(x), next(NULL), random(NULL) { } }; */ class Solution { public: RandomListNode* Clone(RandomListNode* pHead) { if(!pHead) return NULL; RandomListNode* temp = pHead; while(temp) { RandomListNode* node = new RandomListNode(temp -> label); node -> next = temp -> next; temp -> next = node; temp = temp -> next -> next; } temp = pHead; while(temp) { temp -> next -> random = temp -> random ? temp -> random -> next : NULL; temp = temp -> next -> next; } RandomListNode* nHead = pHead -> next; RandomListNode* pre = pHead; temp = nHead; while(temp -> next) { pre -> next = temp -> next; pre = pre -> next; temp -> next = temp -> next -> next; temp = temp -> next; } return nHead; } };
题目:二叉搜索树与双向链表
题解:中序遍历一下就可以,每次遍历到一个点,把上一次遍历到的点的 right 设置成当前点,当前点的 left 设置成上一个点
时间复杂度:$O(n)$
日期:2019 年 7 月 20 日 20:56:50
记录:Vae 正在合肥演唱会,故乡的演唱会,一定会唱庐州月的吧。他开演唱会的体育馆肯定在他小时候就看到过吧,他小时候看到体育馆的时候会想得到自己长大以后会有那么多人抢着自己的演唱会门票来给大家唱歌听吗?台下的亲戚朋友看到他在台上唱歌是多么的自豪与感动啊。我也想这样,我也想让所有激励过我的人为我自豪和感动,好好努力!!!!!!
代码:
/* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } };*/ class Solution { private: TreeNode* head = NULL; TreeNode* pre = NULL; public: void DFS(TreeNode* cur) { if(cur -> left) DFS(cur -> left); cur -> left = pre; pre ? pre -> right = cur : head = cur; pre = cur; if(cur -> right) DFS(cur -> right); } TreeNode* Convert(TreeNode* pRootOfTree) { if(!pRootOfTree) return NULL; DFS(pRootOfTree); return head; } };
题目:字符串的排列
题解:可以自己写一个 dfs 生成全排列,也可以用 next_permutation,有时间需要回顾一下 next_permutation 的内部原理
时间复杂度:$O(n!)$
日期:2019 年 7 月 21 日 09:53:23
代码:
class Solution { public: vector<string> Permutation(string str) { vector<string> result; if(str == "") return result; do { result.push_back(str); } while (next_permutation(str.begin(), str.end())); return result; } };
DFS 生成所有全排列,可以想办法去重,我用了个 map,还有就是可以用 hash 去重,也可以最后排序去重。
class Solution { private: int flag[10]; int curPermutation[10]; vector<string> result; map<string, bool> strMap; void DFS(int x, int length, string& str) { if(x == length) { string curString = ""; for(int i = 0; i < length; i ++) { curString += str[curPermutation[i]]; } if(!strMap[curString]) { result.push_back(curString); strMap[curString] = true; } return; } for(int i = 0; i < length; i ++) { if(flag[i]) continue; flag[i] = 1; curPermutation[x] = i; DFS(x + 1, length, str); flag[i] = 0; } } public: vector<string> Permutation(string str) { if(str == "") return result; DFS(0, str.length(), str); return result; } };
题解:诸如此类的题有好多,主要要求是尽量少的开辟额外空间,尽可能降低时间复杂度。这题的思想是,每次消除任意两个不相等的数字,最后留下来的那个数字可能就是答案,因为某个数字超过了一半,按照前面说的消除方式,肯定最后能留下来,但是最后留下来的不一定是答案,所以还需要验证一次。
时间复杂度:$O(n)$,空间上只用了两个额外变量。如果排序之后判断的话,时间复杂度就成了 $O(n*logn)$
日期:2019 年 7 月 21 日 10:33:27
代码:
class Solution { public: int MoreThanHalfNum_Solution(vector<int> numbers) { int lastNumber = 0; int count = 0; for(int i = 0; i < numbers.size(); i ++) { if(count == 0) { lastNumber = numbers[i]; count = 1; } else { lastNumber == numbers[i] ? count ++ : count --; } } count = 0; for(int i = 0; i < numbers.size(); i ++) { if(numbers[i] == lastNumber) count ++; } if(count * 2 <= numbers.size()) lastNumber = 0; return lastNumber; } };
题目:最小的K个数
题解:第一种办法是对整个数组排序,然后取最小的 $k$ 个,如果 $n$ 特别大,那么排序的时间代价是可怕的;好策略是存下前 $i$ 个数的最小的 $k$ 个数,新来一个数的时候,如果可以替换掉里面的一个就替换,可以开一个大小为 $k$ 的最大堆来解决。
时间复杂度:$O(n * logk)$
日期:2019 年 7 月 21 日 10:51:29
记录:稍后需要手动实现一个堆,训练童子功;此外,这题还可以用快排的思想来做,均摊 $O(n)$,也需要写一下。
代码:
class Solution { public: vector<int> GetLeastNumbers_Solution(vector<int> input, int k) { priority_queue<int> q; for(int i = 0; i < input.size(); i ++) { if(q.size() < k) { q.push(input[i]); } else { if(!q.empty() && input[i] < q.top()) { q.pop(); q.push(input[i]); } } } vector<int> result; if(q.size() == k) { while(!q.empty()) { result.push_back(q.top()); q.pop(); } } return result; } };
题目:连续子数组的最大和
题解:看遍全网好像就没有人是我这种思路做的,难不成我是第一个想到另一种思路解决这个问题的人?以 $i$ 为结尾的最大子串和岂不是到 $i$ 的前缀和减去最小前缀和。
时间复杂度:$O(n)$
日期:2019 年 7 月 21 日 11:20:55
代码:
class Solution { public: int FindGreatestSumOfSubArray(vector<int> array) { int prefixSum = 0; int minPrefixSum = 0; int answer; for(int i = 0; i < array.size(); i ++) { prefixSum += array[i]; answer = i == 0 ? prefixSum - minPrefixSum : max(answer, prefixSum - minPrefixSum); minPrefixSum = min(minPrefixSum, prefixSum); } return answer; } };
题解:数位统计 dp 可以解决,不知道有没有规律,个人比较喜欢数位 dp,省时省力。好像这篇博客的思考方式很独特,但是数位 dp 是一种适用于更普遍情况的算法。
时间复杂度:$O(19 * 9 * 9)$,主要是打表复杂度
日期:2019 年 7 月 21 日 17:08:25
记录:跑跑步,感谢 HDU 蟹老板的答疑解惑!!!
代码:
#include <bits/stdc++.h> using namespace std; class Solution { private: long long dp[20][10]; void init() { memset(dp, 0, sizeof dp); long long b = 10; dp[1][1] = 1; for(int i = 2; i <= 19; i ++) { for(int j = 0; j <= 9; j ++) { long long sum = 0; for(int k = 0; k <= 9; k ++) sum += dp[i - 1][k]; dp[i][j] = (j == 1 ? sum + b : sum); } b = b * 10LL; } } public: int NumberOf1Between1AndN_Solution(int n) { init(); int temp = n; vector<int> v; while(temp) { v.push_back(temp % 10); temp /= 10; } long long ans = 0; long long b = 0; for(int i = 1; i < v.size(); i ++) { for(int j = 1; j <= 9; j ++) { ans = ans + dp[i][j]; } } for(int i = v.size() - 1; i >= 0; i --) { for(int j = 0; j < v[i]; j ++) { if(i == v.size() - 1 && j == 0) continue; ans = ans + dp[i + 1][j]; } if(v[i] == 1) { long long t = 0; for(int k = i - 1; k >= 0; k --) t = t * 10 + v[k]; ans = ans + t + 1; } } return (int)ans; } }; int main() { Solution sol; cout << sol.NumberOf1Between1AndN_Solution(10) << endl; return 0; }
题目:把数组排成最小的数
题解:这是个排序题,排序 cmp 规则是 $[a][b] < [b][a]$
时间复杂度:$O(Len * n * logn)$,其中 $Len$ 是数字的位数,因为转化成字符串之后比较大小是需要复杂度的
日期:2019 年 7 月 21 日 17:15:10
代码:
class Solution { private: static bool cmp(string& a, string& b) { return a + b < b + a; } string GetStringByInt(int num) { stack<int> s; while(num) { s.push(num % 10); num /= 10; } string x = ""; while(!s.empty()) { x += (char)(s.top() + '0'); s.pop(); } return x; } public: string PrintMinNumber(vector<int> numbers) { vector<string> v; for(int i = 0; i < numbers.size(); i ++) { v.push_back(GetStringByInt(numbers[i])); } sort(v.begin(), v.end(), cmp); string ans = ""; for(int i = 0; i < v.size(); i ++) { ans = ans + v[i]; } return ans; } };
题目:丑数
题解:这篇博客的 4.2 讲的很好,我就不重复了,dp 的思想
时间复杂度:$O(n)$
日期:2019 年 7 月 21 日 18:04:57
代码:
class Solution { public: int GetUglyNumber_Solution(int index) { vector<int> uglyNumber; uglyNumber.push_back(1); int p2 = 0, p3 = 0, p5 = 0; for(int i = 1; i < index; i ++) { uglyNumber.push_back( min(2 * uglyNumber[p2], min(3 * uglyNumber[p3], 5 * uglyNumber[p5]))); while(2 * uglyNumber[p2] <= uglyNumber[i]) p2 ++; while(3 * uglyNumber[p3] <= uglyNumber[i]) p3 ++; while(5 * uglyNumber[p5] <= uglyNumber[i]) p5 ++; } return uglyNumber[index - 1]; } };
题解:搞个东西记录一下每个字符出现了几次
时间复杂度:可以 $O(n)$,我偷懒用了 map 就不是 $O(n)$ 了
日期:2019 年 7 月 21 日 18:18:32
代码:
class Solution { private: map<char, int> charCount; public: int FirstNotRepeatingChar(string str) { for(int i = 0; i < str.length(); i ++) { charCount[str[i]] ++; } for(int i = 0; i < str.length(); i ++) { if(charCount[str[i]] == 1) { return i; } } return -1; } };
题目:数组中的逆序对
题解:逆序对的方法有很多了,借助数据结构或者利用归并排序分治的思想都可以。我用了归并排序(分治的思想)来做这个事情,因为以前写的太多的数据结构了。
时间复杂度:暴力 $O(n^2)$,优秀的是 $O(n*logn)$
日期:2019 年 7 月 21 日 18:52:35
代码:
class Solution { private: const long long mod = 1000000007LL; long long GetInversePairCount(vector<int>& data, int L, int R) { if(L >= R) return 0LL; int mid = (L + R) / 2; long long leftCount = GetInversePairCount(data, L, mid); long long rightCount = GetInversePairCount(data, mid + 1, R); long long crossCount = 0; // [L, mid] ~ [mid + 1, R] vector<int> merge; int pLeft = L, pRight = mid + 1; while(pLeft <= mid || pRight <= R) { if(pLeft <= mid && pRight <= R) { data[pLeft] < data[pRight] ? (merge.push_back(data[pLeft]), pLeft ++, crossCount = (crossCount + (pRight - (mid + 1))) % mod) : (merge.push_back(data[pRight]), pRight ++); } else { pLeft <= mid ? (merge.push_back(data[pLeft]), pLeft ++, crossCount = (crossCount + (pRight - (mid + 1))) % mod) : (merge.push_back(data[pRight]), pRight ++); } } for(int i = L; i <= R; i ++) { data[i] = merge[i - L]; } return ((leftCount + rightCount) % mod + crossCount) % mod; } public: int InversePairs(vector<int> data) { return (int)GetInversePairCount(data, 0, data.size() - 1); } };
题目:两个链表的第一个公共结点
题解:先计算出两个链表的长度,长的那个先跳几步,跳到和短的那个一样为止,然后两个一起跳
时间复杂度:$O(n)$
日期:2019 年 7 月 21 日 19:27:23
代码:
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } };*/ class Solution { public: ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) { int len1 = 0, len2 = 0; ListNode* temp1 = pHead1; ListNode* temp2 = pHead2; while(temp1) len1 ++, temp1 = temp1 -> next; while(temp2) len2 ++, temp2 = temp2 -> next; temp1 = pHead1; temp2 = pHead2; while(len2 < len1) temp1 = temp1 -> next, len1 --; while(len2 > len1) temp2 = temp2 -> next, len2 --; while(temp1 != temp2) temp1 = temp1 -> next, temp2 = temp2 -> next; return temp1; } };
题解:因为是排好序的数字,二分就可以了,找到第一个位置以及最后一个位置
时间复杂度:$O(logn)$
日期:2019 年 7 月 21 日 19:30:56
代码:
class Solution { public: int GetNumberOfK(vector<int> data ,int k) { int left, right, mid; int p1 = -1; left = 0, right = data.size() - 1; while(left <= right) { mid = (left + right) / 2; if(data[mid] < k) left = mid + 1; else if(data[mid] == k) p1 = mid, right = mid - 1; else right = mid - 1; } if(p1 == -1) return 0; int p2 = -1; left = 0, right = data.size() - 1; while(left <= right) { mid = (left + right) / 2; if(data[mid] < k) left = mid + 1; else if(data[mid] == k) p2 = mid, left = mid + 1; else right = mid - 1; } return p2 - p1 + 1; } };
题目:二叉树的深度
题解:DFS
时间复杂度:$O(n)$
日期:2019 年 7 月 21 日 19:34:35
代码:
/* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } };*/ class Solution { private: int maxDeep; void DFS(TreeNode* p, int deep) { maxDeep = max(maxDeep, deep); if(p -> left) DFS(p -> left, deep + 1); if(p -> right) DFS(p -> right, deep + 1); } public: int TreeDepth(TreeNode* pRoot) { if(!pRoot) return 0; maxDeep = 0; DFS(pRoot, 1); return maxDeep; } };
题目:平衡二叉树
题解:是个模拟题,看一下平衡二叉树的定义就可以判断了。稍后写。
时间复杂度:
日期:
代码:
题目:数组中只出现一次的数字
题解:巧妙啊巧妙,这篇博客写的很好。不重复当搬运工了,作为一个有节操的程序员,必要时候引用也是需要的。
时间复杂度:$O(n)$
日期:2019 年 7 月 21 日 19:47:25
代码:
class Solution { public: void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) { int x = 0; for(int i = 0; i < data.size(); i ++) x = x ^ data[i]; x = x & (-x); (*num1) = 0, (*num2) = 0; for(int i = 0; i < data.size(); i ++) { data[i] & x ? (*num1) = (*num1) ^ data[i] : (*num2) = (*num2) ^ data[i]; } } };
题目:和为S的连续正数序列
题解:
时间复杂度:
日期:
代码:
题目:和为S的两个数字
题解:一个指针从左往右,另一个从右往左,扫到的第一对数字就是答案
时间复杂度:$O(n)$
日期:2019 年 7 月 24 日 16:56:54
代码:
class Solution { public: vector<int> FindNumbersWithSum(vector<int> array,int sum) { vector<int> answer; int R = array.size() - 1; for(int i = 0; i < R; i ++) { while(R > i && array[i] + array[R] > sum) R --; if(R > i && array[i] + array[R] == sum) { answer.push_back(array[i]); answer.push_back(array[R]); break; } } return answer; } };
题目:左旋转字符串
题解:可以先对字符串长度去个模,然后旋转
时间复杂度:$O(n)$
日期:2019 年 7 月 23 日 07:10:57
代码:
class Solution { public: string LeftRotateString(string str, int n) { int lenStr = str.length(); if(lenStr == 0) return ""; n = n % lenStr; string result = str.substr(n, lenStr - n); result += str.substr(0, n); return result; } };
题目:翻转单词顺序列
题解:把整个单词翻转一下,然后每个反单词内部再翻转
时间复杂度:$O(n)$
日期:2019 年 7 月 24 日 18:10:22
代码:
class Solution { private: void ReverseString(string& str, int L, int R) { while(L <= R) { swap(str[L], str[R]); L ++, R --; } } public: string ReverseSentence(string str) { ReverseString(str, 0, str.size() - 1); int first = 0, last = 0; while(first < str.size()) { while(first < str.size() && str[first] == ' ') first ++; if(first < str.size()) { last = first; while(last < str.size() && str[last] != ' ') last ++; ReverseString(str, first, last - 1); first = last; } } return str; } };
题目:扑克牌顺子
题解:
时间复杂度:
日期:
代码:
题解:
时间复杂度:
日期:
代码:
题目:求1+2+3+...+n
题解:三目运算符;这篇博客还不错
时间复杂度:$O(n)$
日期:2019 年 7 月 24 日 19:18:02
代码:
class Solution { public: int Sum_Solution(int n) { return n ? n + Sum_Solution(n - 1) : 0; } };
题目:不用加减乘除做加法
题解:$a+b=(a^b) + ((a&b)<<1)$,所以递归求解就可以了
时间复杂度:不太会算... ...
日期:2019 年 7 月 24 日 19:12:08
代码:
class Solution { public: int Add(int num1, int num2) { if(num2 == 0) return num1; if(num1 == 0) return num2; return Add(num1 ^ num2, (num1 & num2) << 1); } };
题目:把字符串转换成整数
题解:模拟题,注意几种情况,“-”,“+”
时间复杂度:$O(n)$
日期:2019 年 7 月 24 日 17:35:33
代码:
class Solution { public: int StrToInt(string str) { int lastAdd = -1; int lastSub = -1; int sum = 0; for(int i = 0; i < str.length(); i ++) { if(str[i] == '+') lastAdd = i; else if(str[i] == '-') lastSub = i; else if(str[i] >= '0' && str[i] <= '9') sum = sum * 10 + str[i] - '0'; else return 0; } if(lastAdd > 0 || lastSub > 0) return 0; if(str.length() == 1 && (lastAdd == 0 || lastSub == 0)) return 0; return lastSub == 0 ? -sum : sum; } };
题目:数组中重复的数字
题解:尝试将所有数字归位,归位过程中如果遇到重复数字就记下来,顺利归位则表明没有重复数字
时间复杂度:$O(n)$
日期:2019 年 7 月 26 日 07:52:21
代码:
class Solution { public: // Parameters: // numbers: an array of integers // length: the length of array numbers // duplication: (Output) the duplicated number in the array number // Return value: true if the input is valid, and there are some duplications in the array number // otherwise false bool duplicate(int numbers[], int length, int* duplication) { for(int i = 0; i < length; i ++) { if(numbers[i] == i) continue; if(numbers[numbers[i]] == numbers[i]) { (*duplication) = numbers[i]; return true; } else { swap(numbers[numbers[i]], numbers[i]); } } return false; } };
题目:构建乘积数组
题解:保存前缀乘积以及后缀乘积。
时间复杂度:$O(n)$
日期:2019 年 7 月 23 日 08:47:47
代码:
class Solution { public: vector<int> multiply(const vector<int>& A) { vector<int> L(A.size()); vector<int> R(A.size()); vector<int> answer(A.size()); if(A.size() == 0) return answer; L[0] = A[0]; for(int i = 1; i < A.size(); i ++) L[i] = L[i - 1] * A[i]; R[A.size() - 1] = A[A.size() - 1]; for(int i = A.size() - 2; i >= 0; i --) { R[i] = R[i + 1] * A[i]; } for(int i = 0; i < A.size(); i ++) { if(i == 0) answer[i] = R[1]; else if(i == A.size() - 1) answer[i] = L[A.size() - 2]; else answer[i] = L[i - 1] * R[i + 1]; } return answer; } };
题目:正则表达式匹配
题解:
时间复杂度:
日期:
代码:
题目:表示数值的字符串
题解:
时间复杂度:
日期:
代码:
题解:记录下每个字符出现的次数,以及最后出现的位置
时间复杂度:插入 $O(1)$,查询 $O(x)$,$x$ 是字符集大小;如果改用 set 维护,那么插入和查询应该都是 $O(logx)$
日期:2019 年 7 月 24 日 17:28:00
代码:
数组维护:
class Solution { private: int lastIndex[500]; int charCount[500]; int count; public: Solution() { count = 0; memset(lastIndex, 0, sizeof lastIndex); memset(charCount, 0, sizeof charCount); } //Insert one char from stringstream void Insert(char ch) { count ++; lastIndex[ch] = count; charCount[ch] ++; } //return the first appearence once char in current stringstream char FirstAppearingOnce() { char answer = '#'; int last = count + 1; for(int i = 0; i < 500; i ++) { if(charCount[i] != 1) continue; if(lastIndex[i] < last) { last = lastIndex[i]; answer = i; } } return answer; } };
Set 维护:待写
题目:链表中环的入口结点
题解:这篇博客写的很好了
时间复杂度:$O(n)$
日期:2019 年 7 月 24 日 20:57:58
代码:
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } }; */ class Solution { public: ListNode* EntryNodeOfLoop(ListNode* pHead) { ListNode* a = pHead; ListNode* b = pHead; while(b && b -> next) { a = a -> next; b = b -> next -> next; if(a == b) { a = pHead; while(a != b) { a = a -> next; b = b -> next; } return a; } } return NULL; } };
题目:删除链表中重复的结点
题解:如果所有数字都一样,应该返回 NULL,这应该是唯一坑点。
时间复杂度:$O(n)$
日期:2019 年 7 月 24 日 16:07:31
代码:
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } }; */ class Solution { public: ListNode* deleteDuplication(ListNode* pHead) { if(!pHead) return NULL; ListNode* head = NULL; ListNode* cur = NULL; ListNode* first = pHead; ListNode* last = pHead; int count = 0; while(last) { while(last && last -> val == first -> val) last = last -> next, count ++; // [first, last -> pre] if(count == 1) { head ? (cur -> next = first, cur = first) : (head = cur = first); } count = 0; first = last; } if(cur) cur -> next = NULL; return head; } };
题目:二叉树的下一个结点
题解:想象中序遍历的过程,当前节点是目前中序遍历到的点,那么接下来肯定会走向右儿子那边,所以第一步就是尝试去中序遍历右子树,如果右子树为空,那么需要考虑往父节点回溯,如果当前节点是父节点的左子树,那么下一个中序遍历的节点就是父节点。
时间复杂度:$O(n)$
日期:2019 年 7 月 24 日 14:51:11
代码:
/* struct TreeLinkNode { int val; struct TreeLinkNode *left; struct TreeLinkNode *right; struct TreeLinkNode *next; TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) { } }; */ class Solution { private: TreeLinkNode* nextNode; void DFS(TreeLinkNode* node) { if(!node) return; if(node -> left) DFS(node -> left); if(!nextNode) nextNode = node; return; } public: TreeLinkNode* GetNext(TreeLinkNode* pNode) { if(!pNode) return NULL; nextNode = NULL; DFS(pNode -> right); if(!nextNode) { TreeLinkNode* f = pNode -> next; TreeLinkNode* c = pNode; while(f) { if(f -> left == c) { nextNode = f; break; } c = f; f = c -> next; } } return nextNode; } };
题目:对称的二叉树
题解:搞两个指针,同时一个往左走,另一个往右走。判断结构是否相同,结构相同,判断值是否相同。
时间复杂度:$O(n)$
日期:2019 年 7 月 24 日 14:35:37
代码:
/* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } }; */ class Solution { private: bool result; void DFS(TreeNode* x, TreeNode* y) { if(!x && !y) return; if((!x && y) || (!y && x) || (x -> val != y -> val)) { result = false; return; } DFS(x -> left, y -> right); DFS(x -> right, y -> left); } public: bool isSymmetrical(TreeNode* pRoot) { result = true; DFS(pRoot, pRoot); return result; } };
题目:按之字形顺序打印二叉树
题解:和下面那题一样的
时间复杂度:$O(n)$
日期:2019 年 7 月 24 日 13:57:38
代码:
/* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } }; */ class Solution { private: void DFS(TreeNode* node, int deep, vector<vector<int> >& answer) { if(!node) return; if((int)answer.size() < deep + 1) { vector<int> v; answer.push_back(v); } answer[deep].push_back(node -> val); if(node -> left) DFS(node -> left, deep + 1, answer); if(node -> right) DFS(node -> right, deep + 1, answer); } public: vector<vector<int> > Print(TreeNode* pRoot) { vector<vector<int> > answer; DFS(pRoot, 0, answer); for(int i = 0; i < answer.size(); i ++) { if(i % 2 == 1) { reverse(answer[i].begin(), answer[i].end()); } } return answer; } };
题目:把二叉树打印成多行
题解:先序遍历,层序都可以了,我是先序遍历 dfs 搞了一下
时间复杂度:$O(n)$
日期:2019 年 7 月 24 日 13:52:41
代码:
/* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } }; */ class Solution { private: void DFS(TreeNode* node, int deep, vector<vector<int> >& answer) { if(!node) return; if((int)answer.size() < deep + 1) { vector<int> v; answer.push_back(v); } answer[deep].push_back(node -> val); if(node -> left) DFS(node -> left, deep + 1, answer); if(node -> right) DFS(node -> right, deep + 1, answer); } public: vector<vector<int> > Print(TreeNode* pRoot) { vector<vector<int> > answer; DFS(pRoot, 0, answer); return answer; } };
题目:序列化二叉树
题解:
时间复杂度:
日期:
代码:
题目:二叉搜索树的第k个结点
题解:二叉搜索数如果中序遍历的话,就是递增顺序,那么只要看中序遍历的第 k 个节点就可以了,巧妙利用一下引用的好处。
时间复杂度:$O(n)$
日期:2019 年 7 月 23 日 08:27:48
代码:
/* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } }; */ class Solution { private: TreeNode* answer; void dfs(TreeNode* node, int& k) { if(!node) return; if(node -> left) dfs(node -> left, k); k --; if(k == 0) { answer = node; } if(node -> right) dfs(node -> right, k); } public: TreeNode* KthNode(TreeNode* pRoot, int k) { answer = NULL; dfs(pRoot, k); return answer; } };
题目:数据流中的中位数
题解:将已知序列劈成两半,小的在左边,大的在右边,可以用优先队列来维护,代码比说的清楚。此外,如果数字范围都在例如 $10^6$ 以内,还可以用树状数组,线段树等方法来做。
时间复杂度:$O(n*logn)$
日期:2019 年 7 月 23 日 07:27:51
代码:
class Solution { private: priority_queue<int> L; priority_queue<int, vector<int>, greater<int> > R; public: void Insert(int num) { if((L.size() + R.size()) % 2 == 1) { num > L.top() ? (R.push(num)) : (R.push(L.top()), L.pop(), L.push(num)); } else { R.empty() || num < R.top() ? (L.push(num)) : (L.push(R.top()), R.pop(), R.push(num)); } } double GetMedian() { return (L.size() + R.size()) % 2 == 1 ? 1.0 * L.top() : 1.0 * (L.top() + R.top()) / 2.0; } };
题目:滑动窗口的最大值
题解:典型的决策单调性,决策单调性问题通常有三种手段,根据题目性质决定:单调队列、斜率优化、分治。这题的话单调队列可以帮忙解决问题,队列里面一直存着有价值的东西,如果有两个位置 $i$ 和 $j$,如果 $i < j$ 且 $a[i] < a[j]$,那么显然 $i$ 变成了无效位置,可以被删除。此外,如果是求无修改的区间最小值,可以用 ST 打表,或者线段树,待修改的可以考虑线段树。这题相当于每次询问的区间长度是固定的。
时间复杂度:$O(n)$
日期:2019 年 7 月 23 日 07:31:29
代码:
class Solution { public: vector<int> maxInWindows(const vector<int>& num, unsigned int size) { vector<int> answer; if(num.size() == 0 || size == 0 || num.size() < size) return answer; deque<pair<int, int> > q; for(int i = 0; i < size; i ++) { while(q.size() && q[(int)q.size() - 1].second <= num[i]) q.pop_back(); q.push_back(make_pair(i, num[i])); } answer.push_back(q[0].second); for(int i = size; i < num.size(); i ++) { while(q.size() && q[0].first < i + 1 - size) q.pop_front(); while(q.size() && q[(int)q.size() - 1].second <= num[i]) q.pop_back(); q.push_back(make_pair(i, num[i])); answer.push_back(q[0].second); } return answer; } };
题目:矩阵中的路径
题解:暴搜。当年来面试 MS intern 的时候好像就问我这个题怎么写。
时间复杂度:不会算
日期:2019 年 7 月 23 日 08:10:11
代码:
class Solution { private: bool answer; int dir[4][2] = { {1, 0}, {-1, 0}, {0, -1}, {0, 1} }; bool IsOut(int& x, int& y, int& rows, int& cols) { return x < 0 || x >= rows || y < 0 || y >= cols; } char GetChar(int& x, int& y, char*& matrix, int& rows, int& cols) { return matrix[x * cols + y]; } void dfs(int x, int y, int p, char*& matrix, int& rows, int& cols, char*& str, vector<int>& flag) { if(str[p] == ' ') { answer = true; return; } for(int i = 0; i < 4; i ++) { int nx = x + dir[i][0], ny = y + dir[i][1]; if(IsOut(nx, ny, rows, cols)) continue; if(flag[nx * cols + ny]) continue; if(GetChar(nx, ny, matrix, rows, cols) != str[p]) continue; flag[nx * cols + ny] = 1; dfs(nx, ny, p + 1, matrix, rows, cols, str, flag); if(answer) return; flag[nx * cols + ny] = 0; } } public: bool hasPath(char* matrix, int rows, int cols, char* str) { answer = false; vector<int> flag(rows * cols); for(int i = 0; i < rows; i ++) { for(int j = 0; j < cols; j ++) { if(GetChar(i, j, matrix, rows, cols) != str[0]) continue; flag[i * cols + j] = 1; dfs(i, j, 1, matrix, rows, cols, str, flag); flag[i * cols + j] = 0; if(answer) break; } } return answer; } };
题目:机器人的运动范围
题解:暴搜。用用引用传递,不会修改的变量最好再加上 const,注意起点 $(0,0)$ 都不满足要求的情况
时间复杂度:不会算
日期:2019 年 7 月 23 日 08:20:47
代码:
class Solution { private: int dir[4][2] = { {1, 0}, {-1, 0}, {0, 1}, {0, -1} }; int GetSum(int x, int y) { int result = 0; while(x) result += (x % 10), x /= 10; while(y) result += (y % 10), y /= 10; return result; } bool IsOut(int& x, int& y, int& rows, int& cols) { return x < 0 || x >= rows || y < 0 || y >= cols; } void dfs(int& threshold, int x, int y, int& rows, int& cols, vector<bool>& flag) { flag[x * cols + y] = true; for(int i = 0; i < 4; i ++) { int nx = x + dir[i][0], ny = y + dir[i][1]; if(IsOut(nx, ny, rows, cols)) continue; if(flag[nx * cols + ny]) continue; if(GetSum(nx, ny) > threshold) continue; dfs(threshold, nx, ny, rows, cols, flag); } } public: int movingCount(int threshold, int rows, int cols) { vector<bool> flag(rows * cols); if(threshold > 0) dfs(threshold, 0, 0, rows, cols, flag); int answer = 0; for(int i = 0; i < flag.size(); i ++) { answer += (int)flag[i]; } return answer; } };