文章目录
- 数据结构
- 算法和数据操作
- 递归和循环
- 查找和排序
- 回溯
- 动态规划和贪心
- 位运算
- 代码的完整性
- 代码鲁棒性
- 比较抽象的问题
- 38. 二叉树的镜像
- 39. 对称的二叉树
- 40. 顺时针打印矩阵
- 41. 包含min函数的栈
- 42. 栈的压入、弹出序列
- 43. 不分行从上往下打印二叉树
- 44. 分行从上往下打印二叉树
- 45. 之字形打印二叉树
- 46. 二叉搜索树的后序遍历序列
- 47. 二叉树中和为某一值的路径
- 48. 复杂链表的复刻
- 49. 二叉搜索树与双向链表
- 50. 序列化二叉树
- 51. 数字排列
- 52. 数组中出现次数超过一半的数字
- 53. 最小的k个数
- AcWing 54. 数据流中的中位数
- 55. 连续子数组的最大和
- 56. 从1到n整数中1出现的次数
- 57. 数字序列中某一位的数字
- 58. 把数组排成最小的数
- 59. 把数字翻译成字符串
- 60. 礼物的最大价值
- 61. 最长不含重复字符的子字符串
- 62. 丑数
- 63. 字符串中第一个只出现一次的字符
- 64. 字符流中第一个只出现一次的字符
- 65. 数组中的逆序对
- 66. 两个链表的第一个公共结点
- 67. 数字在排序数组中出现的次数
数据结构
数组
13. 找出数组中重复的数字
给定一个长度为 n 的整数数组 nums,数组中所有的数字都在 0∼n−1 的范围内。
数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。
请找出数组中任意一个重复的数字。
注意:如果某些数字不在 0∼n−1 的范围内,或数组中不包含重复数字,则返回 -1;
样例
给定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。
返回 2 或 3。
交换,O(n)
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
int n = nums.size();
for(int i : nums) if(i < 0 || i >= n) return -1;
for(int i = 0; i < n; i++)
{
int num = nums[i];
if(i == num) continue;
if(nums[num] == num) return num;
swap(nums[num], nums[i]);
}
return -1;
}
};
14. 不修改数组找出重复的数字
给定一个长度为 n+1 的数组nums,数组中所有的数均在 1∼n 的范围内,其中 n≥1。
请找出数组中任意一个重复的数,但不能修改输入的数组。
样例
给定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。
返回 2 或 3。
思考题:如果只能使用 O(1) 的额外空间,该怎么做呢?
数的范围在1~n之间。按照mid分为两个区间,[l,mid] [mid+1, r]这两个数的范围内肯定会有重复。计算所有数落在这两个区间内的个数,如果多了,说明肯定是这个区间里的数重复了。O(nlogn)
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
int l = 1, r = nums.size() - 1;
// 数的范围从1-n
while(l < r)
{
int mid = l + r >> 1;
// [l, mid] [mid + 1, r]两个范围的数
int s = 0;
// 计算所有数据中处于[l, mid]这个范围的数的个数是多少
for(int i : nums)
if(i >= l && i <= mid)
s++;
// 如果【l,mid】这个范围内的数大于mid-l+1就说明多的数是在这个范围中的
if(s > mid - l + 1) r = mid;
else l = mid + 1;
}
return l;
}
};
15. 二维数组中的查找
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
样例
输入数组:
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]
如果输入查找数值为7,则返回true,
如果输入查找数值为5,则返回false。
主要是寻找单调性。左上右下的点有单调性。每次都会少一行或一列。复杂度是O(n+m)
class Solution {
public:
bool searchArray(vector<vector<int>> a, int t) {
if(a.empty() || a[0].empty()) return false;
int i = 0, j = a[0].size() - 1;
while(i < a.size() && j >= 0)
{
if(a[i][j] == t) return true;
if(a[i][j] > t) j--;
else i++;
}
return false;
}
};
字符串
16. 替换空格
请实现一个函数,把字符串中的每个空格替换成"%20"。
你可以假定输入字符串的长度最大是1000。
注意输出字符串的长度可能大于1000。
样例
输入:“We are happy.”
输出:“We%20are%20happy.”
这道题用string没啥意思。书里说是先分配空间,然后从后往前复制。
class Solution {
public:
string replaceSpaces(string &str) {
int len = 0;
for(char c :str)
if(c == ' ') len += 3;
else len += 1;
int j = len - 1, i = str.size() - 1;
str.resize(len);
while(i >= 0)
{
if(str[i] == ' ')
{
str[j--] = '0';
str[j--] = '2';
str[j--] = '%';
}
else str[j --] = str[i];
i--;
}
return str;
}
};
链表
17. 从尾到头打印链表
输入一个链表的头结点,按照 从尾到头 的顺序返回节点的值。
返回的结果用数组存储。
样例
输入:[2, 3, 5]
返回:[5, 3, 2]
- 遍历然后reverse
- 用栈记录结果
- 递归
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
vector<int> printListReversingly(ListNode* head) {
vector<int> ret;
ListNode* ptr = head;
while(ptr)
{
ret.push_back(ptr->val);
ptr = ptr->next;
}
reverse(ret.begin(), ret.end());
return ret;
}
};
反转链表
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
样例
输入:1->2->3->4->5->NULL
输出:5->4->3->2->1->NULL
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL;
while(head)
{
ListNode* next = head->next;
head->next = pre;a
pre = head;
head = next;
}
return pre;
}
};
树
18. 重建二叉树
输入一棵二叉树前序遍历和中序遍历的结果,请重建该二叉树。
注意:
二叉树中每个节点的值都互不相同;
输入的前序遍历和中序遍历一定合法;
样例
给定:
前序遍历是:[3, 9, 20, 15, 7]
中序遍历是:[9, 3, 15, 20, 7]
返回:[3, 9, 20, null, null, 15, 7, null, null, null, null]
返回的二叉树如下所示:
3
/
9 20
/
15 7
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> pre, in;
unordered_map<int, int> inpos;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
pre = preorder;
in = inorder;
for(int i = 0; i < in.size(); i++) inpos[in[i]] = i;
return build(0, pre.size() - 1, 0, in.size() - 1);
}
TreeNode* build(int prest, int preed, int inst, int ined)
{
if(prest > preed) return NULL;
int rootval = pre[prest];
int inmid = inpos[rootval];
int leftlen = inmid - inst;
int rightlen = ined - inmid;
TreeNode* u = new TreeNode(rootval);
u->left = build(prest + 1, prest + leftlen, inst, inmid - 1);
u->right = build(prest + leftlen + 1, preed, inmid + 1, ined);
return u;
}
};
19. 二叉树的下一个节点
给定一棵二叉树的其中一个节点,请找出中序遍历序列的下一个节点。
注意:
如果给定的节点是中序遍历序列的最后一个,则返回空节点;
二叉树一定不为空,且给定的节点一定不是空节点;
样例
假定二叉树是:[2, 1, 3, null, null, null, null], 给出的是值等于2的节点。
则应返回值等于3的节点。
解释:该二叉树的结构如下,2的后继节点是3。
2
/
1 3
- 有右节点
- 右子树最左边的
- 一路向上,如果朝着左边拐了一下,那拐过去的父节点就是下一个节点。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode *father;
* TreeNode(int x) : val(x), left(NULL), right(NULL), father(NULL) {}
* };
*/
class Solution {
public:
TreeNode* inorderSuccessor(TreeNode* p) {
if(!p) return NULL;
if(p->right)
{
TreeNode* next = p->right;
while(next->left) next = next->left;
return next;
}
while(p->father && p->father->right == p) p = p->father;
if(p->father) return p->father;
return NULL;
}
};
栈和队列
20. 用两个栈实现队列
请用栈实现一个队列,支持如下四种操作:
push(x) – 将元素x插到队尾;
pop() – 将队首的元素弹出,并返回该元素;
peek() – 返回队首元素;
empty() – 返回队列是否为空;
注意:
你只能使用栈的标准操作:push to top,peek/pop from top, size 和 is empty;
如果你选择的编程语言没有栈的标准库,你可以使用list或者deque等模拟栈的操作;
输入数据保证合法,例如,在队列为空时,不会进行pop或者peek等操作;
样例
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // returns 1
queue.pop(); // returns 1
queue.empty(); // returns false
一个栈不断往另一个栈倒就行。
class MyQueue {
private:
stack<int> in, out;
public:
/** Initialize your data structure here. */
MyQueue() {
}
/** Push element x to the back of queue. */
void push(int x) {
in.push(x);
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
if(out.size())
{
int v = out.top();
out.pop();
return v;
}
while(in.size())
{
out.push(in.top());
in.pop();
}
return pop();
}
/** Get the front element. */
int peek() {
if(out.size())
{
int v = out.top();
return v;
}
while(in.size())
{
out.push(in.top());
in.pop();
}
return peek();
}
/** Returns whether the queue is empty. */
bool empty() {
return in.empty() && out.empty();
}
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* bool param_4 = obj.empty();
*/
算法和数据操作
递归和循环
21. 斐波那契数列
输入一个整数 n ,求斐波那契数列的第 n 项。
假定从0开始,第0项为0。(n<=39)
样例
输入整数 n=5
返回 5
class Solution {
public:
int Fibonacci(int n) {
int f[n + 1] = {0};
f[0] = 0;
f[1] = 1;
for(int i = 2; i <= n; i++) f[i] = f[i - 1] + f[i - 2];
return f[n];
}
};
查找和排序
22. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个升序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
数组可能包含重复项。
注意:数组内所含元素非负,若数组大小为0,请返回-1。
样例
输入:nums=[2,2,2,0,1]
输出:0
这道题是具有二分的性质的。左边区间都大于等于第一个元素(当然要把右边区间最左边几个排除掉)然后二分找右边区间的左端点就好。
class Solution {
public:
int findMin(vector<int>& nums) {
int n = nums.size() - 1;
if(n < 0) return -1;
while(n >= 0 && nums[n] == nums[0]) n--;
if(nums[n] >= nums[0]) return nums[0];
int l = 0, r = n;
while(l < r)
{
int mid = l + r >> 1;
if(nums[mid] >= nums[0]) l = mid + 1;
else r = mid;
}
return nums[l];
}
};
回溯
23. 矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。
路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。
如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。
注意:
输入的路径不为空;
所有出现的字符均为大写英文字母;
样例
matrix=
[
[“A”,“B”,“C”,“E”],
[“S”,“F”,“C”,“S”],
[“A”,“D”,“E”,“E”]
]
str=“BCCE” , return “true”
str=“ASAE” , return “false”
遍历+dfs
class Solution {
public:
int n, m;
bool st[1000][1000];
bool dfs(vector<vector<char>>& matrix, string &str, int x, int y, int u)
{
if(u >= str.size()) return true;
int dx[4] = {1, -1, 0, 0} ,dy[4] = {0, 0, 1, -1};
st[x][y] = true;
for(int i = 0; i < 4; i++)
{
int a = x + dx[i], b = y + dy[i];
if(a < 0 || a >= n || b < 0 || b >= m || st[a][b] || matrix[a][b] != str[u]) continue;
if(dfs(matrix, str, a, b, u + 1)) return true;
}
return false;
}
bool hasPath(vector<vector<char>>& matrix, string &str) {
n = matrix.size();
if(!n) return false;
m = matrix[0].size();
if(!m) return false;
memset(st, false, sizeof st);
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
if(matrix[i][j] == str[0])
if(dfs(matrix, str, i, j, 1))
return true;
else
memset(st, false, sizeof st);
return false;
}
};
24. 机器人的运动范围
地上有一个 m 行和 n 列的方格,横纵坐标范围分别是 0∼m−1 和 0∼n−1。
一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格。
但是不能进入行坐标和列坐标的数位之和大于 k 的格子。
请问该机器人能够达到多少个格子?
样例1
输入:k=7, m=4, n=5
输出:20
样例2
输入:k=18, m=40, n=40
输出:1484
解释:当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。
但是,它不能进入方格(35,38),因为3+5+3+8 = 19。
注意:
0<=m<=50
0<=n<=50
0<=k<=100
class Solution {
public:
bool check(int a, int b, int k)
{
int t = 0;
while(a)
{
t += a % 10;
a /= 10;
}
while(b)
{
t += b % 10;
b /= 10;
}
return t <= k;
}
int movingCount(int threshold, int rows, int cols)
{
if(rows == 0 || cols == 0) return 0;
typedef pair<int, int> PII;
queue<PII> q;
q.push({0, 0});
bool st[rows][cols];
memset(st, false, sizeof st);
int cnt = 0;
while(q.size())
{
auto t = q.front(); q.pop();
int x = t.first, y = t.second;
if(st[x][y]) continue;
st[x][y] = true;
if(check(x, y, threshold)) cnt++;
int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
for(int i = 0; i < 4; i++)
{
int a = x + dx[i], b = y + dy[i];
if(a < 0 || a >= rows || b < 0 || b >= cols || st[a][b] || !check(a, b, threshold)) continue;
q.push({a, b});
}
}
return cnt;
}
};
动态规划和贪心
25. 剪绳子
给你一根长度为 n 绳子,请把绳子剪成 m 段(m、n 都是整数,2≤n≤58 并且 m≥2)。
每段的绳子的长度记为k[0]、k[1]、……、k[m]。k[0]k[1] … k[m] 可能的最大乘积是多少?
例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。
样例
输入:8
输出:18
f(i)表示长度为i的绳子所有被剪的方法中乘积最大的那种。f(i)分为(0, i - j) (i-j, i)两段。后面长度为j的这一段不去动它。前面(i-j)这一段可剪可不剪。然后遍历每个分界点j。
class Solution {
public:
int maxProductAfterCutting(int n) {
int dp[n + 1];
memset(dp, 0, sizeof dp);
for(int i = 2; i <= n; i++)
for(int j = 1; j < i; j++)
dp[i] = max(dp[i], max(dp[i - j] * j, (i - j) * j));
return dp[n];
}
};
位运算
26. 二进制中1的个数
输入一个32位整数,输出该数二进制表示中1的个数。
注意:
负数在计算机中用其绝对值的补码来表示。
样例1
输入:9
输出:2
解释:9的二进制表示是1001,一共有2个1。
样例2
输入:-2
输出:31
解释:-2在计算机里会被表示成11111111111111111111111111111110,
一共有31个1。
class Solution {
public:
int NumberOf1(int n) {
int res = 0;
for(int i = 31; i >= 0; i--)
{
res += (n >> i) & 1;
}
return res;
}
};
代码的完整性
27. 数值的整数次方
实现函数double Power(double base, int exponent),求base的 exponent次方。
不得使用库函数,同时不需要考虑大数问题。
注意:
不会出现底数和指数同为0的情况
样例1
输入:10 ,2
输出:100
样例2
输入:10 ,-2
输出:0.01
主要考虑负数情况
class Solution {
public:
double Power(double base, int e) {
double res = 1;
int sign = e > 0 ? 1 : -1;
e = abs(e);
while(e)
{
if(e & 1) res *= base;
e >>= 1;
base = base * base;
}
if(sign < 0) res = 1 / res;
return res;
}
};
28. 在O(1)时间删除链表结点
给定单向链表的一个节点指针,定义一个函数在O(1)时间删除该结点。
假设链表一定存在,并且该节点一定不是尾节点。
样例
输入:链表 1->4->6->8
删掉节点:第2个节点即6(头节点为第0个节点)
输出:新链表 1->4->8
交换数据,跳过,然后删除
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* u) {
swap(u->val, u->next->val);
ListNode* next = u->next;
u->next = next->next;
delete next;
}
};
29. 删除链表中重复的节点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留。
样例1
输入:1->2->3->3->4->4->5
输出:1->2->5
样例2
输入:1->1->1->2->3
输出:2->3
因为可能删掉头节点。所以要建立一个虚拟节点。每次都去看接下来这个数是不是只有一个。如果不是就直接跳过这一段。p指向每一段数字的前一个位置。q寻找下一段数字的开始位置。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* head) {
auto dummy = new ListNode(-1);
dummy->next = head;
auto p = dummy;
while(p->next)
{
auto q = p->next;
while(q && p->next->val == q->val) q = q->next;
if(p->next->next == q) p = p->next;
else p = q;
}
return dummy->next;
}
};
30. 正则表达式匹配
请实现一个函数用来匹配包括’.‘和’*'的正则表达式。
模式中的字符’.‘表示任意一个字符,而’*'表示它前面的字符可以出现任意次(含0次)。
在本题中,匹配是指字符串的所有字符匹配整个模式。
例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配。
样例
输入:
s=“aa”
p=“a*”
输出:true
这道题真的挺不错的。f(i, j)表示s的i以后所有字母和p的j以后所有字母是否相同。
如果si == pj,或者pj == ‘.’。那i,j可以匹配。f(i, j) = f(i+1, j+1) && (si == pj || pj == ‘.’)
如果pj+1 == ‘*’。那就有两种情况:
- 不匹配,f(i, j + 2)
- 匹配,f(i + 1, j)。虽然说能够匹配多个,但是也无需遍历,这种情况会递归算出所有匹配多个的情况。
class Solution {
public:
int n, m;
int f[1000][1000];
bool isMatch(string s, string p) {
memset(f, -1, sizeof f);
n = s.size();
m = p.size();
return dp(0, 0, s, p);
}
bool dp(int x, int y, string& s, string& p)
{
if(f[x][y] != -1) return f[x][y];
if(y == m) return f[x][y] = x == n;
bool ismatch = (x < n) && (s[x] == p[y] || p[y] == '.');
bool ans;
if(y + 1 < m && p[y + 1] == '*')
ans = dp(x, y + 2, s, p) || // 不匹配
ismatch && dp(x + 1, y, s, p) ; // 匹配一个字符
else
ans = ismatch && dp(x + 1, y + 1, s, p);
return f[x][y] = ans;
}
};
31. 表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。
但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。
注意:
小数可以没有整数部分,例如.123等于0.123;
小数点后面可以没有数字,例如233.等于233.0;
小数点前面和后面可以有数字,例如233.666;
当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4;
样例:
输入: “0”
输出: true
这道题我觉得没啥意思,不做了。
32. 调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序。
使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分。
样例
输入:[1,2,3,4,5]
输出: [1,3,5,2,4]
双指针
class Solution {
public:
void reOrderArray(vector<int> &array) {
int n = array.size();
int l = 0, r = n - 1;
while(l < r)
{
while(l < r && array[r] % 2 == 0) r--;
while(l < r && array[l] % 2 == 1) l++;
if(l < r) swap(array[l], array[r]);
}
}
};
代码鲁棒性
33. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个结点。
注意:
k >= 0;
如果k大于链表长度,则返回 NULL;
样例
输入:链表:1->2->3->4->5 ,k=2
输出:4
快慢指针,统计长度,快指针先走k步。然后快慢一起走,快指针走到底的时候返回慢指针就好。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* findKthToTail(ListNode* head, int k) {
int n = 0;
ListNode* u = head;
while(u)
{
n++;
u = u->next;
}
if(k > n) return NULL;
ListNode* slow = head, *fast = head;
while(k-- && fast) fast = fast->next;
while(fast && slow)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
};
34. 链表中环的入口结点
给定一个链表,若其中包含环,则输出环的入口节点。
若其中不包含环,则输出null。
样例
给定如上所示的链表:
[1, 2, 3, 4, 5, 6]
2
注意,这里的2表示编号是2的节点,节点编号从0开始。所以编号是2的节点就是val等于3的节点。
则输出环的入口节点3.
- 判定有没有环?快慢指针看会不会追尾
- 快慢指针相遇之后,让一个指针回到起点。两个指针一步一步走,相遇的时候就是环的入口
证明:假设快慢指针在c点相遇。慢指针往后退y步,快指针往后退2y步。可以发现x+y肯定是圈的周长的倍数。所以在c处再走x步就正好是一个周长,正好就能走到b处。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *entryNodeOfLoop(ListNode *head) {
auto slow = head, fast = head;
while(slow && fast)
{
slow = slow->next;
fast = fast->next;
if(fast) fast = fast->next;
else return NULL;
if(slow == fast)
{
slow = head;
while(slow != fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
}
return NULL;
}
};
35. 反转链表
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
样例
输入:1->2->3->4->5->NULL
输出:5->4->3->2->1->NULL
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL;
while(head)
{
ListNode* next = head->next;
head->next = pre;a
pre = head;
head = next;
}
return pre;
}
};
36. 合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。
样例
输入:1->3->5 , 2->4->5
输出:1->2->3->4->5->5
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* merge(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(-1);
ListNode* p = dummy;
while(l1 || l2)
{
if(l1 && l2)
{
if(l1->val <= l2->val)
{
p->next = l1;
l1 = l1->next;
}
else
{
p->next = l2;
l2 = l2->next;
}
}
else if(l1)
{
p->next = l1;
l1 = l1->next;
}
else
{
p->next = l2;
l2 = l2->next;
}
p = p->next;
p->next = NULL;
}
return dummy->next;
}
};
37. 树的子结构
输入两棵二叉树A,B,判断B是不是A的子结构。
我们规定空树不是任何树的子结构。
样例
树A:
8
/ \
8 7
/
9 2
/
4 7
树B:
8
/
9 2
返回 true ,因为B是A的子结构。
这道题还比较有意思。递归地来做比较简单。思路就是遍历树地每一个节点。每个节点都尝试和目标子树匹配一下。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool hasSubtree(TreeNode* u1, TreeNode* u2) {
if(!u1 || !u2) return false;
if(check(u1, u2)) return true;
return hasSubtree(u1->left, u2) || hasSubtree(u1->right, u2);
}
bool check(TreeNode* u1, TreeNode* u2)
{
if(!u2) return true;
if(!u1 || u1->val != u2->val ) return false;
return check(u1->left, u2->left) && check(u1->right, u2->right);
}
};
比较抽象的问题
38. 二叉树的镜像
输入一个二叉树,将它变换为它的镜像。
样例
输入树:
8
/
6 10
/ \ /
5 7 9 11
[8,6,10,5,7,9,11,null,null,null,null,null,null,null,null]
输出树:
8
/
10 6
/ \ /
11 9 7 5
[8,10,6,11,9,7,5,null,null,null,null,null,null,null,null]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void mirror(TreeNode* u) {
if(!u) return;
swap(u->left, u->right);
mirror(u->left);
mirror(u->right);
}
};
39. 对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。
如果一棵二叉树和它的镜像一样,那么它是对称的。
样例
如下图所示二叉树[1,2,2,3,4,4,3,null,null,null,null,null,null,null,null]为对称二叉树:
1
/
2 2
/ \ /
3 4 4 3
如下图所示二叉树[1,2,2,null,4,4,3,null,null,null,null,null,null]不是对称二叉树:
1
/
2 2
\ /
4 4 3
这道题也还不错,递归来做比较简单。要对称的话,左边和右边相等,右边也和左边相等。递归的时候这两种情况都要考虑到。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isSymmetric(TreeNode* u) {
if(!u) return true;
return check(u, u);
}
bool check(TreeNode* p1, TreeNode* p2)
{
if(!p1 || !p2) return !p1 && !p2;
return p1->val == p2->val && check(p1->left, p2->right) && check(p1->right, p2->left);
}
};
40. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
样例
输入:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
经典问题。记录好边界值和访问过的值就行。每次一个方向走到底之后变换方向就行。
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
vector<int> res;
int n = matrix.size();
if(!n) return res;
int m = matrix[0].size();
int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
int dir = 0, x = 0, y = 0;
vector<vector<bool>> st(n, vector<bool>(m, false));
for(int i = 0; i < n * m; i++)
{
res.push_back(matrix[x][y]);
st[x][y] = true;
int a = x + dx[dir], b = y + dy[dir];
if(a < 0 || a >= n || b < 0 || b >= m || st[a][b])
{
dir = (dir + 1) % 4;
a = x + dx[dir], b = y + dy[dir];
}
x = a, y = b;
}
return res;
}
};
41. 包含min函数的栈
设计一个支持push,pop,top等操作并且可以在O(1)时间内检索出最小元素的堆栈。
push(x)–将元素x插入栈中
pop()–移除栈顶元素
top()–得到栈顶元素
getMin()–得到栈中最小元素
样例
MinStack minStack = new MinStack();
minStack.push(-1);
minStack.push(3);
minStack.push(-4);
minStack.getMin(); --> Returns -4.
minStack.pop();
minStack.top(); --> Returns 3.
minStack.getMin(); --> Returns -1.
颇有一点单调栈的感觉。维护一个最小值栈,和栈同步,不过记录的是相同高度位置处的最小值。
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
stk.push(x);
if(mnstk.empty())
mnstk.push(x);
else
mnstk.push(min(x, mnstk.top()));
}
void pop() {
stk.pop();
mnstk.pop();
}
int top() {
return stk.top();
}
int getMin() {
return mnstk.top();
}
stack<int> stk, mnstk;
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
42. 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。
假设压入栈的所有数字均不相等。
例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
注意:若两个序列长度不等则视为并不是一个栈的压入、弹出序列。若两个序列都为空,则视为是一个栈的压入、弹出序列。
样例
输入:[1,2,3,4,5]
[4,5,3,2,1]
输出:true
这道题的核心就是。如果栈顶和出栈序列要匹配的位置一样,就一定要出栈。否则这个位置肯定会被其他字符占用,肯定就匹配不了了。
class Solution {
public:
bool isPopOrder(vector<int> pushV,vector<int> popV) {
if(pushV.size() != popV.size()) return false;
stack<int> stk;
int i = 0;
for(int v : pushV)
{
stk.push(v);
while(stk.size() && stk.top() == popV[i])
{
i++;
stk.pop();
}
}
return i == popV.size();
}
};
43. 不分行从上往下打印二叉树
从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印。
样例
输入如下图所示二叉树[8, 12, 2, null, null, 6, null, 4, null, null, null]
8
/
12 2
/
6
/
4
输出:[8, 12, 2, 6, 4]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> printFromTopToBottom(TreeNode* root) {
vector<int> ret;
if(!root) return ret;
queue<TreeNode*> q;
q.push(root);
while(q.size())
{
auto t = q.front(); q.pop();
ret.push_back(t->val);
if(t->left) q.push(t->left);
if(t->right) q.push(t->right);
}
return ret;
}
};
44. 分行从上往下打印二叉树
从上到下按层打印二叉树,同一层的结点按从左到右的顺序打印,每一层打印到一行。
样例
输入如下图所示二叉树[8, 12, 2, null, null, 6, null, 4, null, null, null]
8
/
12 2
/
6
/
4
输出:[[8], [12, 2], [6], [4]]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> printFromTopToBottom(TreeNode* root) {
vector<vector<int>> ret;
if(!root) return ret;
queue<TreeNode*> q;
q.push(root);
while(q.size())
{
int len = q.size();
vector<int> tmp;
while(len --)
{
auto t = q.front(); q.pop();
tmp.push_back(t->val);
if(t->left) q.push(t->left);
if(t->right) q.push(t->right);
}
ret.push_back(tmp);
}
return ret;
}
};
45. 之字形打印二叉树
请实现一个函数按照之字形顺序从上向下打印二叉树。
即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
样例
输入如下图所示二叉树[8, 12, 2, null, null, 6, 4, null, null, null, null]
8
/
12 2
/
6 4
输出:[[8], [2, 12], [6, 4]]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> printFromTopToBottom(TreeNode* root) {
vector<vector<int>> ret;
if(!root) return ret;
queue<TreeNode*> q;
q.push(root);
bool rev = false;
while(q.size())
{
int len = q.size();
vector<int> tmp;
while(len --)
{
auto t = q.front(); q.pop();
tmp.push_back(t->val);
if(t->left) q.push(t->left);
if(t->right) q.push(t->right);
}
if(rev) reverse(tmp.begin(), tmp.end());
rev = !rev;
ret.push_back(tmp);
}
return ret;
}
};
46. 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。
如果是则返回true,否则返回false。
假设输入的数组的任意两个数字都互不相同。
样例
输入:[4, 8, 6, 12, 16, 14, 10]
输出:true
因为这是搜索树。后序遍历最后一个节点肯定是树根。然后前面的肯定分成两段。前一段比后一段大。如果发现大小由交错了,肯定不是后序遍历序列。然后递归判断两个子树就行。
class Solution {
public:
vector<int> seq;
bool verifySequenceOfBST(vector<int> sequence) {
seq = sequence;
return dfs(0, seq.size() - 1);
}
bool dfs(int l, int r)
{
if(l >= r) return true;
int root = seq[r];
int k = l;
while(k < r && seq[k] < root) k++;
for(int i = k; i < r; i++)
if(seq[i] < root)
return false;
return dfs(l, k - 1) && dfs(k, r - 1);
}
};
47. 二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。
从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
样例
给出二叉树如下所示,并给出num=22。
5
/
4 6
/ /
12 13 6
/ \ /
9 1 5 1
输出:[[5,4,12,1],[5,6,6,5]]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> findPath(TreeNode* root, int sum) {
vector<vector<int>> ret;
vector<int> path;
dfs(root, sum, 0, path, ret);
return ret;
}
void dfs(TreeNode* u, int target, int cur, vector<int>& path, vector<vector<int>>& ret)
{
if(!u) return;
cur += u->val;
path.push_back(u->val);
if(!u->left && !u->right && cur == target) ret.push_back(path);
if(u->left) dfs(u->left, target, cur, path, ret);
if(u->right) dfs(u->right, target, cur, path, ret);
path.pop_back();
}
};
记录从树根到树根路径的数值就行。
48. 复杂链表的复刻
请实现一个函数可以复制一个复杂链表。
在复杂链表中,每个结点除了有一个指针指向下一个结点外,还有一个额外的指针指向链表中的任意结点或者null。
注意:
函数结束后原链表要与输入时保持一致。
这道题有多种思路。比较简单的就是用一个哈希表记录映射关系。还有一个比较巧妙地方法。就是先在每个节点后面添加一个复制地节点。然后在有random节点地地方p->next->random=p->random->next。最后再把复制的节点全都摘下来就行。确实比较巧妙
/**
* Definition for singly-linked list with a random pointer.
* struct ListNode {
* int val;
* ListNode *next, *random;
* ListNode(int x) : val(x), next(NULL), random(NULL) {}
* };
*/
class Solution {
public:
ListNode *copyRandomList(ListNode *head) {
for(auto p = head; p; )
{
auto np = new ListNode(p->val);
auto next = p->next;
p->next = np;
np->next = next;
p = next;
}
for(auto p = head; p; p = p->next->next)
{
if(p->random)
p->next->random = p->random->next;
}
auto dummy = new ListNode(-1);
auto cur = dummy;
for(auto p = head; p; )
{
cur->next = p->next;
cur = cur->next;
p->next = p->next->next;
cur->next = NULL;
p = p->next;
}
return dummy->next;
}
};
49. 二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。
要求不能创建任何新的结点,只能调整树中结点指针的指向。
注意:
需要返回双向链表最左侧的节点。
例如,输入下图中左边的二叉搜索树,则输出右边的排序双向链表。
这道题也是也比较巧妙。可以写一个递归函数。返回子树的最右边和最左边的节点。然后把树根和两个子树左右两个相连。返回的时候返回最左和最右的节点就好。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* convert(TreeNode* root) {
if(!root) return NULL;
auto ret = dfs(root);
return ret.first;
}
pair<TreeNode*, TreeNode*> dfs(TreeNode* u)
{
if(!u->left && !u->right) return {u, u};
if(u->left && u->right)
{
auto l = dfs(u->left), r = dfs(u->right);
l.second->right = u; u->left = l.second;
r.first->left = u; u->right = r.first;
return {l.first, r.second};
}
if(u->left)
{
auto l = dfs(u->left);
l.second->right = u; u->left = l.second;
return {l.first, u};
}
auto r = dfs(u->right);
r.first->left = u; u->right = r.first;
return {u, r.second};
}
};
50. 序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树。
您需要确保二叉树可以序列化为字符串,并且可以将此字符串反序列化为原始树结构。
样例
你可以序列化如下的二叉树
8
/
12 2
/
6 4
为:"[8, 12, 2, null, null, 6, 4, null, null, null, null]"
注意:
以上的格式是AcWing序列化二叉树的方式,你不必一定按照此格式,所以可以设计出一些新的构造方式。
这道题我只能说stringstream真的好用啊。哈哈哈。序列化和反序列化按照同样的顺序就可以。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
ostringstream out;
dfs(root, out);
string ret = out.str();
return ret;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
istringstream in(data);
return dfs2(in);
}
void dfs(TreeNode* u, ostringstream& out)
{
if(!u)
{
out << " null ";
return ;
}
out << u->val << ' ';
dfs(u->left, out);
dfs(u->right, out);
}
TreeNode* dfs2(istringstream& in)
{
string str;
in >> str;
if(str == "null") return NULL;
int val = stoi(str);
auto root = new TreeNode(val);
root->left = dfs2(in);
root->right = dfs2(in);
return root;
return NULL;
}
};
51. 数字排列
输入一组数字(可能包含重复数字),输出其所有的排列方式。
样例
输入:[1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
class Solution {
public:
vector<bool> st;
void dfs(vector<int>& nums, vector<int>& path, vector<vector<int>>& ret)
{
if(path.size() == nums.size())
{
ret.push_back(path);
return ;
}
for(int i = 0; i < nums.size(); i++)
{
if(st[i]) continue;
if(i > 0 && !st[i - 1] && nums[i - 1] == nums[i]) continue;
st[i] = true;
path.push_back(nums[i]);
dfs(nums, path, ret);
path.pop_back();
st[i] = false;
}
}
vector<vector<int>> permutation(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<int> path;
vector<vector<int>> ret;
int n = nums.size();
st = vector<bool>(n, false);
dfs(nums, path, ret);
return ret;
}
};
直接dfs
52. 数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
假设数组非空,并且一定存在满足条件的数字。
思考题:
假设要求只能使用 O(n) 的时间和额外 O(1) 的空间,该怎么做呢?
样例
输入:[1,2,1,1,3]
输出:1
class Solution {
public:
int moreThanHalfNum_Solution(vector<int>& nums) {
int cnt = 1, num = nums[0];
for(int i = 1; i < nums.size(); i++)
{
if(nums[i] == num) cnt++;
else cnt--;
if(cnt == 0)
{
num = nums[i];
cnt = 1;
}
}
return num;
}
};
53. 最小的k个数
输入n个整数,找出其中最小的k个数。
注意:
数据保证k一定小于等于输入数组的长度;
输出数组内元素请按从小到大顺序排序;
样例
输入:[1,2,3,4,5,6,7,8] , k=4
输出:[1,2,3,4]
要求前k个比较小的数。因为要求按顺序输出。就不太好用快排。可以用一个大顶堆。加入堆之后如果长度大于k就pop出堆顶。
class Solution {
public:
vector<int> getLeastNumbers_Solution(vector<int> input, int k) {
priority_queue<int> q;
for(int i : input)
{
q.push(i);
if(q.size() > k) q.pop();
}
vector<int> ret(k);
for(int i = k - 1; i >= 0; i--)
{
ret[i] = q.top();
q.pop();
}
return ret;
}
};
AcWing 54. 数据流中的中位数
如何得到一个数据流中的中位数?
如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。
如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
样例
输入:1, 2, 3, 4
输出:1,1.5,2,2.5
解释:每当数据流读入一个数据,就进行一次判断并输出当前的中位数。
这道题确实比较经典。
思路就是用大顶堆存储小的一半数。用小顶堆存储大的一半数。每次都默认插入大顶堆。如果大顶堆堆顶比小顶堆堆顶大了,就交换一下。如果大顶堆比小顶堆的个数大了1了,就把往小顶堆里放一个。
class Solution {
public:
priority_queue<int> mxheap;
priority_queue<int, vector<int>, greater<int>> mnheap;
void insert(int num){
mxheap.push(num);
if(mnheap.size() && mxheap.top() > mnheap.top())
{
int mnv = mnheap.top(), mxv = mxheap.top();
mnheap.pop();
mxheap.pop();
mnheap.push(mxv);
mxheap.push(mnv);
}
if(mxheap.size() > mnheap.size() + 1)
{
mnheap.push(mxheap.top());
mxheap.pop();
}
}
double getMedian(){
if(mxheap.size()+ mnheap.size() & 1) return mxheap.top();
else return (mxheap.top() + mnheap.top()) / 2.0;
}
};
55. 连续子数组的最大和
输入一个 非空 整型数组,数组里的数可能为正,也可能为负。
数组中一个或连续的多个整数组成一个子数组。
求所有子数组的和的最大值。
要求时间复杂度为O(n)。
样例
输入:[1, -2, 3, 10, -4, 7, 2, -5]
输出:18
其实这是一道dp。只不过用一个变量就行。sum就表示走到i这个位置最大的连续子数组和是多少。如果sum>0了,当前数就可以加到sum。否则当前数替换sum。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int sum = 0, mx = INT_MIN;
for(int i : nums)
{
if(sum > 0) sum += i;
else sum = i;
mx = max(mx, sum);
}
return mx;
}
};
56. 从1到n整数中1出现的次数
输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。
例如输入12,从1到12这些整数中包含“1”的数字有1,10,11和12,其中“1”一共出现了5次。
样例
输入: 12
输出: 5
数位统计问题。对于一个数abcdef。要看c这一位有多少个1。首先前两个数取0~ab-1的时候,后面两个数随便取。这就有ab*100种。前两个数取ab的时候。如果c=0,那肯定是啥都取不了了。如果c=1,后面只能取0—ef-1。如果c>1,后面两个数随便取,就有100种。
class Solution {
public:
int numberOf1Between1AndN_Solution(int n)
{
vector<int> digits;
while(n) digits.push_back(n % 10), n /= 10;
reverse(digits.begin(), digits.end());
int res = 0;
for(int i = 0; i < digits.size(); i++)
{
int left = 0, right = 0, power = 1;
for(int j = 0; j < i; j++) left = left * 10 + digits[j];
for(int j = i + 1; j < digits.size(); j++) right = right * 10 + digits[j], power *= 10;
res += left * power;
if(digits[i] == 1) res += right + 1;
else if (digits[i] > 1) res += power;
}
return res;
}
};
57. 数字序列中某一位的数字
数字以0123456789101112131415…的格式序列化到一个字符序列中。
在这个序列中,第5位(从0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数求任意位对应的数字。
样例
输入:13
输出:1
这是一道数位统计问题。考虑到个位数有9个,十位数有90个,百位数有900个。个位数占1位,十位数占两位,百位数占3位。根据这个规律就可以求出到底是几位数。然后三位数的开始数字是100,n每三位组成一个数。所以就有 n/i ( i 表示是 i 位数),这里要下取整。具体是这个数字里的第几位就是n%i,这里如果n=i就说明是最后一位。
class Solution {
public:
int digitAtIndex(int n) {
// 算出是几位数
long long l = 1, s = 9, base = 1;
while(n > l * s)
{
n -= l * s;
l ++;
s *= 10;
base *= 10;
}
// 算出是 l 位数里面的第几个
int number = base + (n + l - 1) / l - 1;
// 算是 number 这个数的第几位
int r = n % l ? n % l : l;
for(int i = 0; i < l - r; i++) number /= 10;
return number % 10;
}
};
58. 把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
例如输入数组[3, 32, 321],则打印出这3个数字能排成的最小数字321323。
样例
输入:[3, 32, 321]
输出:321323
注意:输出数字的格式为字符串。
这道题非常巧妙。就是定义比较函数。a<b <=> ab < ba。这个比较函数是满足反对称性和传递性的。所以按照这个比较函数得到的最后序列也是最小的。
class Solution {
public:
static bool cmp(int a, int b)
{
string as = to_string(a), bs = to_string(b);
return as + bs < bs + as;
}
string printMinNumber(vector<int>& nums) {
sort(nums.begin(), nums.end(), cmp);
string res;
for(int i : nums) res += to_string(i);
return res;
}
};
59. 把数字翻译成字符串
给定一个数字,我们按照如下规则把它翻译为字符串:
0翻译成”a”,1翻译成”b”,……,11翻译成”l”,……,25翻译成”z”。
一个数字可能有多个翻译。例如12258有5种不同的翻译,它们分别是”bccfi”、”bwfi”、”bczi”、”mcfi”和”mzi”。
请编程实现一个函数用来计算一个数字有多少种不同的翻译方法。
样例
输入:“12258”
输出:5
这是一道dp题目。f(i)表示到i这个位置一共有多少中翻译方法。f(i)可以由f(i-1)和f(i-2)转移过来的。前一个一定可以转移。后一个只有前两个字母满足条件的时候才能转移。
class Solution {
public:
int getTranslationCount(string s) {
int n = s.size();
vector<int> f(n + 1, 0);
f[0] = 1;
for(int i = 1; i <= n; i++)
{
f[i] += f[i - 1];
if(i > 1)
{
int pre = (s[i - 2] - '0') * 10 + (s[i - 1] - '0');
if(pre >= 10 && pre <= 25)
f[i] += f[i - 2];
}
}
return f[n];
}
};
60. 礼物的最大价值
在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。
你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格直到到达棋盘的右下角。
给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?
注意:
m,n>0
样例:
输入:
[
[2,3,1],
[1,7,1],
[4,6,1]
]
输出:19
解释:沿着路径 2→3→7→6→1 可以得到拿到最大价值礼物。
也是dp题目。
f(i, j)表示走到i,j最大值是多少。可以由左边和上面的最大值转移过来。
class Solution {
public:
int getMaxValue(vector<vector<int>>& grid) {
int n = grid.size();
if(!n) return 0;
int m = grid[0].size();
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
int up = 0;
int left = 0;
if(i > 0) up = grid[i - 1][j];
if(j > 0) left = grid[i][j - 1];
grid[i][j] += max(up, left);
}
}
return grid[n - 1][m - 1];
}
};
61. 最长不含重复字符的子字符串
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
假设字符串中只包含从’a’到’z’的字符。
样例
输入:“abcabc”
输出:3
这是一个双指针。如果加入字符之后出现了重复,就只能让左指针不断移动直至无重复。
class Solution {
public:
int longestSubstringWithoutDuplication(string s) {
int mp[256] = {0};
int n = s.size();
int res = 0;
for(int r = 0, l = 0; r < n; r++)
{
mp[s[r]]++;
while(mp[s[r]] > 1)
{
mp[s[l++]]--;
}
res = max(res, r - l + 1);
}
return res;
}
};
62. 丑数
我们把只包含质因子2、3和5的数称作丑数(Ugly Number)。
例如6、8都是丑数,但14不是,因为它包含质因子7。
求第n个丑数的值。
样例
输入:5
输出:5
注意:习惯上我们把1当做第一个丑数。
这道题挺好的。i2,i3,i5都指向1。下一个丑数肯定是这三个因子能够形成的最小数。生成下一个丑数之后,可以生成这个丑数的那个因子的坐标往前走一个。这样每一步都能保证三个因子产生的丑数一定包含下一个丑数。
class Solution {
public:
int getUglyNumber(int n) {
if(n <= 1) return 1;
int i = 0, j = 0, k = 0;
vector<int> f(1, 1);
while(--n)
{
long long t = min(f[i] * 2, min(f[j] * 3, f[k] * 5));
if(t == f[i] * 2) i++;
if(t == f[j] * 3) j++;
if(t == f[k] * 5) k++;
f.push_back(t);
}
return f.back();
}
};
63. 字符串中第一个只出现一次的字符
在字符串中找出第一个只出现一次的字符。
如输入"abaccdeff",则输出b。
如果字符串中不存在只出现一次的字符,返回#字符。
样例:
输入:“abaccdeff”
输出:‘b’
class Solution {
public:
char firstNotRepeatingChar(string s) {
int mp[256] = {0};
for(char ch : s) mp[ch] ++;
for(char ch : s) if(mp[ch] == 1) return ch;
return '#';
}
};
64. 字符流中第一个只出现一次的字符
请实现一个函数用来找出字符流中第一个只出现一次的字符。
例如,当从字符流中只读出前两个字符”go”时,第一个只出现一次的字符是’g’。
当从该字符流中读出前六个字符”google”时,第一个只出现一次的字符是’l’。
如果当前字符流没有存在出现一次的字符,返回#字符。
样例
输入:“google”
输出:“ggg#ll”
解释:每当字符流读入一个字符,就进行一次判断并输出当前的第一个只出现一次的字符。
重复字母是指前面所有字符的。所以记录字符出现的次数。队列中保存指出现一次的字母。如果出现重复了,就从队列中找到下一个无重复的数。
class Solution{
public:
Solution()
{
memset(cnt, 0, sizeof cnt);
}
int cnt[256];
queue<char> q;
//Insert one char from stringstream
void insert(char ch){
if(++cnt[ch] > 1)
{
while(q.size() && cnt[q.front()] > 1) q.pop();
}
else q.push(ch);
}
//return the first appearence once char in current stringstream
char firstAppearingOnce(){
if(q.empty()) return '#';
return q.front();
}
};
65. 数组中的逆序对
在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
输入一个数组,求出这个数组中的逆序对的总数。
样例
输入:[1,2,3,4,5,6,0]
输出:6
归并排序。合并a,b两个序列。如果a[i] > b[j]。说明a[i]以及以后所有的数都要比b[j]大,都是逆序对,加上这些个数就好。
class Solution {
public:
int tmp[100000];
int inversePairs(vector<int>& nums) {
return merge(nums, 0, nums.size() - 1);
}
int merge(vector<int>& nums, int l, int r)
{
if(l >= r) return 0;
int mid = l + r >> 1;
int cnt = merge(nums, l, mid) + merge(nums, mid + 1, r);
int i = l, j = mid + 1, id = 0;
while(i <= mid && j <= r)
{
if(nums[i] <= nums[j]) tmp[id++] = nums[i++];
else cnt += mid + 1 - i, tmp[id++] = nums[j++];
}
while(i <= mid) tmp[id++] = nums[i++];
while(j <= r) tmp[id++] = nums[j++];
for(int i = l, id = 0; i <= r; i++, id++) nums[i] = tmp[id];
return cnt;
}
};
66. 两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。
当不存在公共节点时,返回空节点。
样例
给出两个链表如下所示:
A: a1 → a2
↘
c1 → c2 → c3
↗
B: b1 → b2 → b3
输出第一个公共节点c1
先计算从两个头开始的长度。然后多的那边多走几步就好。然后一起走,走到一起的时候就说明是公共节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *findFirstCommonNode(ListNode *headA, ListNode *headB) {
int n1 = 0, n2 = 0;
auto p1 = headA, p2 = headB;
while(p1)
{
n1++;
p1 = p1->next;
}
while(p2)
{
n2++;
p2 = p2->next;
}
auto fast = headA, slow = headB;
if(n2 > n1) swap(fast, slow);
int diff = abs(n1 - n2);
while(diff-- && fast) fast = fast->next;
while(fast && slow && fast != slow)
{
fast = fast->next;
slow = slow->next;
}
return fast;
}
};
67. 数字在排序数组中出现的次数
统计一个数字在排序数组中出现的次数。
例如输入排序数组[1, 2, 3, 3, 3, 3, 4, 5]和数字3,由于3在这个数组中出现了4次,因此输出4。
样例
输入:[1, 2, 3, 3, 3, 3, 4, 5] , 3
输出:4
二分出上下边界。然后输出长度就好。
class Solution {
public:
int getNumberOfK(vector<int>& nums , int k) {
int n = nums.size();
if(!n) return 0;
int l = 0, r = n - 1;
while(l < r)
{
int mid = l + r >> 1;
if(nums[mid] >= k) r = mid;
else l = mid + 1;
}
int left = l;
if(nums[l] != k) return 0;
l = 0, r = n - 1;
while(l < r)
{
int mid = l + r + 1 >> 1;
if(nums[mid] <= k) l = mid;
else r = mid - 1;
}
int right = l;
return right - left + 1;
}
};