《剑指OFFER》刷题笔记
<!doctype html>《剑指OFFER》刷题笔记
《剑指OFFER》刷题笔记
1.二维数组中的查找
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
- 暴力查找 复杂度O(nm)
- 对每行二分查找O(nlog(m))
- 二维数组中的一个数,小于它的数一定在其左边,大于它的数一定在其下边。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间,当前元素的查找区间为左下角的所有元素。复杂度O(n + m)
class Solution {
public:
bool Find(int target, vector<vector<int> > arr) {
int n = arr.size(), m = 0;
if(n) m = arr[0].size();
int x = n - 1, y = 0;
while(x >= 0 && y < m) {
if(arr[x][y] == target)
return true;
if(arr[x][y] < target)
++y;
else if(arr[x][y] > target)
--x;
}
return false;
}
};
2.替换空格
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
- 计算空格个数后从尾向前双指针遍历,空格替换为%20即可
xxxxxxxxxx
class Solution {
public:
void replaceSpace(char *str,int length) {
int cnt = 0;
for(int i = 0; i < length; ++i) {
if(str[i] == ' ')
++cnt;
}
int p = length + (cnt * 2);
str[p] = 0;
--p;
--length;
while(length >= 0) {
if(str[length] != ' ') {
str[p--] = str[length];
}
else {
str[p--] = '0';
str[p--] = '2';
str[p--] = '%';
}
--length;
}
}
};
3.从尾到头打印链表
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
- 递归/栈遍历返回
xxxxxxxxxx
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
void gao(vector<int> &ans, ListNode *now)
{
if(now == NULL)
return ;
gao(ans, now->next);
ans.push_back(now->val);
}
vector<int> printListFromTailToHead(ListNode* head) {
vector <int> ans;
gao(ans, head);
return ans;
}
};
4.重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
- 前序中左右,中序左中右,针对前序确定当前结点并对中序进行分割递归重建即可
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void gao(deque <int> pre, deque <int> vin, TreeNode *now) {
if(vin.size() == 0) {
now = NULL;
return ;
}
deque <int> fst, vfst;
now->val = pre[0];
pre.pop_front();
while(vin.front() != now->val) {
fst.push_back(pre.front());
vfst.push_back(vin.front());
pre.pop_front();
vin.pop_front();
}
vin.pop_front();
if(fst.size())
{
now->left = new TreeNode(0);
gao(fst, vfst, now->left);
}
if(pre.size())
{
now->right = new TreeNode(0);
gao(pre, vin, now->right);
}
}
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
TreeNode *head = NULL;
if(pre.size())
{
head = new TreeNode(0);
gao({pre.begin(), pre.end()},{vin.begin(), vin.end()}, head);
}
return head;
}
};
5.用两个栈实现队列
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
- in栈记录入队顺序,out栈记录出队顺序,当out为空时把in栈push人out栈中即可保证顺序。
xxxxxxxxxx
class Solution
{
public:
void push(int node) {
in.push(node);
}
int pop() {
if(out.empty())
{
while(!in.empty())
{
out.push(in.top());
in.pop();
}
}
int ret = out.top();
out.pop();
return ret;
}
private:
stack<int> in;
stack<int> out;
};
6.旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
- 对窗口进行二分查找,缩进查找于窗口首值小于窗口尾值(此时旋转点位于当前窗口内)。当首值等于尾值时,无法确定,只能暴力查找
xxxxxxxxxx
class Solution {
public:
int minNumberInRotateArray(vector<int> arr) {
if(!arr.size())
return 0;
int fst = 0, lst = arr.size();
int mid;
while(fst != lst)
{
mid = (fst + lst) / 2;
if(arr[mid] > arr[fst])
{
fst = mid;
}
else if(arr[mid] < arr[fst])
{
lst = mid;
}
else
{
int x = 0x3f3f3f3f;
for(int i = fst; i <= lst; ++i)
x = min(x, arr[i]);
return x;
}
}
return arr[fst];
}
};
7.斐波那契数列
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
n<=39
- 数组记录递推 dp[i] = dp[i - 1] + dp[i - 2]
xxxxxxxxxx
class Solution {
public:
typedef long long ll;
ll fib[50] = {0, 1, 1};
int Fibonacci(int n) {
for(int i = 2; i <= n; ++i) {
fib[i] = fib[i - 1] + fib[i - 2];
}
return fib[n];
}
};
8. 跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
- 计数dp,来源n-1与n-2
xxxxxxxxxx
const int MAXN = 1e5 + 7;
class Solution {
public:
typedef long long ll;
ll dp[MAXN] = {1};
int jumpFloor(int number) {
for(int i = 1; i <= number; ++i) {
if(i >= 1)
dp[i] += dp[i - 1];
if(i >= 2)
dp[i] += dp[i - 2];
}
return dp[number];
}
};
9.变态跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
- 计数dp 来源1- (i - 1)
xxxxxxxxxx
class Solution {
public:
int dp[10010] = {0}, sum[10010] = {0};
int jumpFloorII(int number) {
for(int i = 1; i <= number; ++i)
{
dp[i] = sum[i - 1] + 1;
sum[i] = sum[i - 1] + dp[i];
}
return dp[number];
}
};
10.矩形覆盖
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
- 计数dp int dp[MAXN] = {0, 1, 2, 3 }; dp[i] = dp[i - 1] + dp[i - 2];
xxxxxxxxxx
const int MAXN = 1e5 + 7;
class Solution {
public:
int dp[MAXN] = {0, 1, 2, 3 };
int rectCover(int number) {
for(int i = 4; i <= number; ++i)
{
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[number];
}
};
11.二进制中1的个数
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
- 减lowbit次数
xxxxxxxxxx
class Solution {
public:
int NumberOf1(int n) {
int ret = 0;
while(n)
{
n -= (n & -n);
++ret;
}
return ret;
}
};
12.数值的整数次方
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0
- 快速幂 负幂要取反变为1.0/ret
xxxxxxxxxx
class Solution {
public:
typedef long double ld;
double Power(double base, int exponent) {
ld ret = 1.0;
int f = 0;
if(exponent < 0) f = 1, exponent *= -1;
while(exponent) {
if(exponent & 1)
ret *= base;
base *= base;
exponent >>= 1;
}
return f ? 1.0 / ret : ret;
}
};
13.调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
- 队列记录并还原 时间O(n) 空间O(n)
- 冒泡上浮 时间O(n^2) 空间O(1)
xxxxxxxxxx
class Solution {
public:
void reOrderArray(vector<int> &arr) {
queue <int> qa, qb;
for(auto x : arr)
{
if(x & 1)
qa.push(x);
else
qb.push(x);
}
int p = 0;
while(!qa.empty())
{
arr[p++] = qa.front();
qa.pop();
}
while(!qb.empty())
{
arr[p++] = qb.front();
qb.pop();
}
return ;
}
};
14 链表中倒数第k个结点
输入一个链表,输出该链表中倒数第k个结点。
- 递归查询标记
xxxxxxxxxx
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
int cnt = 0;
ListNode *ret = NULL;
void get(ListNode *now, int k)
{
if(now == NULL || cnt >= k)
return ;
get(now->next, k);
++cnt;
if(cnt == k)
{
ret = now;
return ;
}
}
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
get(pListHead, k);
return ret;
}
};
15 反转链表
输入一个链表,反转链表后,输出新链表的表头。
- 循环头插
xxxxxxxxxx
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode *ret = NULL;
ListNode *tmp = NULL;
while(pHead != NULL)
{
tmp = pHead->next;
pHead->next = ret;
ret = pHead;
pHead = tmp;
}
return ret;
}
};
16 合并两个排序的链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
- 归并,两个合并到一个结束,再将另一个接在后面
xxxxxxxxxx
/*
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 = new ListNode(-1);
ListNode *now = head;
ListNode *tmp;
while(pHead1 != NULL && pHead2 != NULL)
{
if(pHead1->val <= pHead2->val)
{
tmp = pHead1->next;
pHead1->next = now->next;
now->next = pHead1;
now = now->next;
pHead1 = tmp;
}
else
{
tmp = pHead2->next;
pHead2->next = now->next;
now->next = pHead2;
now = now->next;
pHead2 = tmp;
}
}
while(pHead1 != NULL)
{
tmp = pHead1->next;
now->next = pHead1;
now = now->next;
now->next = NULL;
pHead1 = tmp;
}
while(pHead2 != NULL)
{
tmp = pHead2->next;
now->next = pHead2;
now = now->next;
now->next = NULL;
pHead2 = tmp;
}
return head->next;
}
};
树的子结构
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
- 暴力匹配 注意空树的特别处理
xxxxxxxxxx
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
typedef unsigned long long ull;
class Solution {
public:
bool ok = 0, rok = 1;
void check(TreeNode * now, TreeNode * prt2)
{
if(now == NULL || ok)
return ;
rok = 1;
rck(now, prt2);
if(rok)
ok = 1;
check(now->left, prt2);
check(now->right, prt2);
}
void rck(TreeNode *now, TreeNode *prt2)
{
if(!rok || prt2 == NULL)
return ;
if( (now == NULL && prt2 != NULL) || (now->val != prt2 -> val))
{
rok = 0;
return ;
}
rck(now -> left, prt2->left);
rck(now -> right, prt2->right);
}
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
if(pRoot2 == NULL)
return 0;
check(pRoot1, pRoot2);
return ok;
}
};
二叉树的镜像
题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
输入描述:
xxxxxxxxxx
二叉树的镜像定义:源二叉树
8
/ \
6 10
/ \ / \
5 7 9 11
镜像二叉树
8
/ \
10 6
/ \ / \
11 9 7 5
- 对所有节点进行递归反转
xxxxxxxxxx
/*
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== NULL)
return ;
swap(pRoot->left, pRoot->right);
Mirror(pRoot->left);
Mirror(pRoot->right);
}
};
顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
- 暴力模拟
- 设左上右下坐标,一次添加一个圈,对于一行的单独判断
xxxxxxxxxx
class Solution
{
private:
int n, m;
public;
bool inmap(int x, int y) { return x >= 0 && x < n && y >= 0 && y < m; }
vector<int> printMatrix(vector<vector<int> > arr)
{
n = arr.size(), m = n ? arr[0].size() : 0;
vector < vector <bool> > used(n, vector <bool> (m, 0));
vector <int> ans;
int x = 0, y = -1, cnt = 0;
while(cnt < n * m)
{
++y;
while(cnt < n * m)
{
ans.push_back(arr[x][y]), ++cnt;
used[x][y] = 1;
if(!inmap(x, y + 1) || used[x][y + 1])
break;
++y;
}
++x;
while(cnt < n * m)
{
ans.push_back(arr[x][y]), ++cnt;
used[x][y] = 1;
if(!inmap(x + 1, y) || used[x + 1][y])
break;
++x;
}
--y;
while(cnt < n * m)
{
ans.push_back(arr[x][y]), ++cnt;
used[x][y] = 1;
if(!inmap(x, y - 1) || used[x][y - 1])
break;
--y;
}
--x;
while(cnt < n * m)
{
ans.push_back(arr[x][y]), ++cnt;
used[x][y] = 1;
if(!inmap(x - 1, y) || used[x - 1][y])
break;
--x;
}
}
return ans;
}
};
xxxxxxxxxx
public ArrayList<Integer> printMatrix(int[][] matrix) {
ArrayList<Integer> ret = new ArrayList<>();
int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1;
while (r1 <= r2 && c1 <= c2) {
for (int i = c1; i <= c2; i++)
ret.add(matrix[r1][i]);
for (int i = r1 + 1; i <= r2; i++)
ret.add(matrix[i][c2]);
if (r1 != r2)
for (int i = c2 - 1; i >= c1; i--)
ret.add(matrix[r2][i]);
if (c1 != c2)
for (int i = r2 - 1; i > r1; i--)
ret.add(matrix[i][c1]);
r1++; r2--; c1++; c2--;
}
return ret;
}
包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
- 一个记录val一个记录mx
xxxxxxxxxx
const int MAXN = 1e6 + 7;
class Solution {
public:
struct node
{
int val, mx;
}stk[MAXN];
int tp;
Solution()
{
tp = -1;
}
void push(int value) {
stk[++tp].val = value;
stk[tp].mx = (tp == 0 ? stk[tp].val : std::min(stk[tp].val, stk[tp - 1].val) );
}
void pop() {
--tp;
}
int top() {
return stk[tp].val;
}
int min() {
return stk[tp].mx;
}
};
栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
- 根据入栈顺序模拟 全符合为真
xxxxxxxxxx
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
stack <int> STK;
int p = 0;
for(auto x : pushV)
{
STK.push(x);
while(p < popV.size() && STK.size() && popV[p] == STK.top())
{
++p;
STK.pop();
}
}
if(p == pushV.size() && STK.size() == 0)
return true;
return false;
}
};
从上往下打印二叉树
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
- BFS
xxxxxxxxxx
/*
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) {
vector <int> ret;
queue <TreeNode*> Q;
Q.push(root);
while(!Q.empty())
{
auto x = Q.front();
Q.pop();
if(x != NULL)
{
ret.push_back(x->val);
Q.push(x->left);
Q.push(x->right);
}
}
return ret;
}
};
二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
- 后序遍历特点:左右中,当前节点最后遍历,因此将最后节点拿出后看剩下序列能不能拆为两部分使得前部分小于当前节点,后部分大于当前结点即可(递归处理)
xxxxxxxxxx
class Solution {
public:
bool VerifySquenceOfBST(vector<int> sequence) {
if(sequence.size() == 0)
return false;
return check(sequence, 0, sequence.size() - 1);
}
bool check(vector <int> arr, int fst, int lst)
{
if(fst >= lst)
return true;
int x = arr[lst];
int rf = fst;
while(arr[rf] <= x && rf < lst)
++rf;
for(int i = rf; i < lst; ++i)
if(arr[i] <= x)
return false;
return check(arr, fst, rf - 1) && check(arr, rf, lst - 1);
}
};
二叉树中和为某一值的路径
输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
- DFS同时记录路径,对于形成路径长度为某值的记录
xxxxxxxxxx
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
const int MAXN = 1e5 + 7;
class Solution {
public:
vector < vector<int> > ans;
int road[MAXN] = {0};
int rsum;
void dfs(TreeNode *now, int step, int sum)
{
if(now == NULL)
return ;
road[step] = now->val;
sum += now->val;
if(now->left == NULL && now->right == NULL && sum == rsum)
{
vector <int> x;
for(int i = 0; i <= step; ++i)
x.push_back(road[i]);
ans.push_back(x);
return ;
}
dfs(now->left, step + 1, sum);
dfs(now->right, step + 1, sum);
}
bool cmp(vector <int> a, vector <int> b)
{
return a.size() >= b.size();
}
vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
rsum = expectNumber;
dfs(root, 0, 0);
return ans;
}
};
复杂链表的复制
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
- 待补充
二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
- DFS,记录头尾指针边遍历边更新
xxxxxxxxxx
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
TreeNode *ret = NULL, *nxt = ret;
void dfs(TreeNode *now)
{
if(now == NULL)
return ;
dfs(now->left);
if(ret == NULL)
{
ret = now;
nxt = ret;
}
else
{
now->left = nxt;
nxt->right = now;
nxt = nxt->right;
}
dfs(now->right);
}
TreeNode* Convert(TreeNode* pRootOfTree)
{
dfs(pRootOfTree);
return ret;
}
};
字符串的排列
题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:
xxxxxxxxxx
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
- DFS标记回溯搜索 注意要判断重复(SET维护)
xxxxxxxxxx
class Solution {
public:
bool used[20] = {0};
set <string> ans;
string rs, tmp;
void gao(int step)
{
if(step == rs.length())
{
ans.insert(tmp);
return ;
}
for(int i = 0; i < rs.length(); ++i)
{
if(!used[i])
{
tmp[step] = rs[i];
used[i] = true;
gao(step + 1);
used[i] = false;
}
}
}
vector<string> Permutation(string str) {
rs = str;
tmp = str;
if(str.length())
gao(0);
//sort(ans.begin(), ans.end());
return vector <string> {ans.begin(), ans.end()};
}
};
数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
- HASH记录次数 空间O(n) 时间O(n)
- 维护结果投票,当前值等于结果投一票,反之结果减一票,票数为零时更新答案为当前值。对答案验证 空间 O(1) 时间O(n)
xxxxxxxxxx
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> arr) {
int ret = 0, num = 0;
for(auto x : arr)
{
if(num == 0)
{
++num;
ret = x;
}
else
{
if(x == ret)
++num;
else
--num;
}
}
num = 0;
for(auto x : arr)
if(x == ret)
++num;
if(num * 2 <= arr.size())
ret = 0;
return ret;
}
};
最小的K个数
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
- Heap 维护
xxxxxxxxxx
class Solution
{
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k)
{
if(k > input.size())
return vector <int> {};
multiset <int> ST;
for(auto x : input)
{
ST.insert(x);
while(ST.size() > k)
{
auto x = --ST.end();
ST.erase(x);
}
}
vector <int> ret{ST.begin(), ST.end()};
return ret;
}
};
连续子数组的最大和
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
- 做前缀和,维护之前前缀和的最小值相减更新即可 空间O1 时间On
- 动态规划 dp[i] = max(arr[i] + dp[i - 1], arr[i]); 空间O1 时间On
xxxxxxxxxx
const int INF = 0x3f3f3f3f;
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> arr) {
int rx = 0;
int ret = -INF;
for(int i = 0; i < arr.size(); ++i)
{
if(i)
arr[i] += arr[i - 1];
ret = std::max(ret, arr[i] - rx);
rx = min(rx, arr[i]);
}
return ret;
}
};
xxxxxxxxxx
const int INF = 0x3f3f3f3f;
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> arr) {
int rx = 0;
if(!arr.size())
return 0;
int ret = arr[0];
int *dp = new int[10010];
dp[0] = arr[0];
for(int i = 1; i < arr.size(); ++i)
{
dp[i] = max(dp[i - 1] + arr[i], arr[i]);
ret = max(ret, dp[i]);
}
return ret;
}
};
整数中1出现的次数(从1到n整数中1出现的次
求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
- 待写
把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
- 冒泡两两比较 return a + b < b + a
xxxxxxxxxx
class Solution {
public:
string ret;
void get(int x)
{
if(x > 10)
get(x / 10);
ret = ret + char('0' + x % 10);
}
static bool cmp(string a, string b)
{
return a + b < b + a;
}
string PrintMinNumber(vector<int> numbers) {
vector <string> rs;
for(auto x : numbers)
{
ret = "";
get(x);
rs.push_back(ret);
}
sort(rs.begin(), rs.end(), cmp);
ret = "";
for(auto x : rs)
{
ret = ret + x;
}
return ret;
}
};
丑数
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
- 生成法,优先队列维护 On*logn
- 生成发,比较维护 On
xxxxxxxxxx
class Solution {
public:
int GetUglyNumber_Solution(int index) {
int i2 = 0, i3 = 0, i5 = 0;
vector <int> dp(index + 1, 0);
dp[0] = 1;
for(int i = 1; i < index; ++i)
{
int nxt2 = dp[i2] * 2, nxt3 = dp[i3] * 3, nxt5 = dp[i5] * 5;
dp[i] = min(nxt2, min(nxt3, nxt5));
if(dp[i] == nxt2)
++i2;
if(dp[i] == nxt5)
++i5;
if(dp[i] == nxt3)
++i3;
}
return dp[index - 1];
}
};
第一个只出现一次的字符
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
- 维护次数 位置判断
- 队列维护
xxxxxxxxxx
class Solution {
public:
vector <int> pos[256];
int FirstNotRepeatingChar(string str) {
for(int i = 0; i < str.size(); ++i)
{
pos[str[i]].push_back(i);
}
set <int> ans;
for(int i = 0; i < 256; ++i)
{
if(pos[i].size() == 1)
ans.insert(pos[i][0]);
}
if(ans.size())
return *ans.begin();
else
return -1;
}
};
数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
- 归并求逆序对
- 树状数组求逆序对
xxxxxxxxxx
const int MAXN = 2e5 + 100;
typedef long long ll;
class Solution {
public:
struct bit
{
ll c[MAXN], n;
bit(ll rn)
{
memset(c, 0, sizeof(c));
n = rn + 10;
}
ll lowbit(ll x)
{
return x & -x;
}
void updata(ll pos)
{
for( ;pos <= n; pos += lowbit(pos))
{
c[pos] += 1;
}
}
ll ask(ll pos)
{
ll ret = 0;
for(;pos; pos -= lowbit(pos))
ret += c[pos];
return ret;
}
};
vector <int> lsh;
int get(ll val) { return lower_bound(lsh.begin(), lsh.end(), val) - lsh.begin() + 1;}
int InversePairs(vector <int> data) {
bit T(data.size());
lsh = data;
sort(lsh.begin(), lsh.end());
lsh.erase(unique(lsh.begin(), lsh.end()), lsh.end());
ll ans = 0;
for(int i = data.size() - 1; i >= 0; --i)
{
ll t = get(data[i]);
ans += T.ask(t - 1);
T.updata(t);
}
return (ans % 1000000007);
}
};
两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。
- 分别记录两个链表的长度,让长的链表先后跑直到两链表长度相同,然后同时向后遍历,第一个相遇即为答案
xxxxxxxxxx
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
int x = 0, y = 0;
ListNode *tmp = pHead1;
while(tmp != NULL)
{
++x;
tmp = tmp->next;
}
tmp = pHead2;
while(tmp != NULL)
{
++y;
tmp = tmp->next;
}
while(x > y)
{
--x;
pHead1 = pHead1->next;
}
while(x < y)
{
--y;
pHead2 = pHead2->next;
}
while(true)
{
if(pHead1 == pHead2)
{
return pHead1;
}
pHead1 = pHead1->next;
pHead2 = pHead2->next;
}
}
};
数字在排序数组中出现的次数
统计一个数字在排序数组中出现的次数。
- 二分查找
xxxxxxxxxx
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
return upper_bound(data.begin(), data.end(), k) - lower_bound(data.begin(), data.end(), k);
}
};
二叉树的深度
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
- 递归计算 出口为空时返回0
xxxxxxxxxx
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
if(pRoot == NULL)
return 0;
return max(TreeDepth(pRoot->left) + 1, TreeDepth(pRoot->right) + 1);
}
};
平衡二叉树
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
- 平衡二叉树定义 任意节点左右子树高度值相差不超过一
xxxxxxxxxx
class Solution {
public:
bool ok = 1;
int dfs(TreeNode *now)
{
if(now == NULL || !ok)
return 0;
int x = dfs(now->left) + 1, y = dfs(now->right) + 1;
if(abs(x - y) > 1)
ok = 0;
return max(x, y);
}
bool IsBalanced_Solution(TreeNode* pRoot) {
dfs(pRoot);
return ok;
}
};
数组中只出现一次的数字
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
- 先异或一遍,结果为x ^ y再对最低位不同的分组,再单独异或分出x y
xxxxxxxxxx
class Solution {
public:
void FindNumsAppearOnce(vector<int> data,int *num1,int *num2) {
int rx = 0;
for(auto x : data)
rx ^= x;
vector <int> ra, rb;
int f = 0;
while(!(rx & 1))
{
rx >>= 1;
++f;
}
for(auto x : data)
{
if((x >> f) & 1)
ra.push_back(x);
else
rb.push_back(x);
}
*num1 = 0;
*num2 = 0;
for(auto x : ra)
*num1 ^= x;
for(auto x : rb)
*num2 ^= x;
}
};
和为S的连续正数序列
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
- 暴力 On^2
- 枚举前端点+二分 Onlogn
- 双指针 On
xxxxxxxxxx
class Solution {
public:
vector<vector<int> > FindContinuousSequence(int sum) {
int fst = 1, lst = 1, rsum = 1;
vector <vector<int>> ans;
while(lst <= sum)
{
if(rsum < sum)
{
++lst;
rsum += lst;
}
else if(rsum > sum)
{
rsum -= fst;
++fst;
}
else
{
vector <int> rs;
for(int i = fst; i <= lst; ++i)
rs.push_back(i);
if(rs.size() > 1)
ans.push_back(rs);
++lst, rsum += lst;
}
}c
return ans;
}
};
和为S的两个数字
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
- 排序数组 首位双指针逼近更新答案
xxxxxxxxxx
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> arr,int sum) {
int fst = 0, lst = arr.size() - 1;
int x = 10086, y = 10086;
while(fst < lst)
{
if(arr[fst] + arr[lst] == sum && arr[fst] * arr[lst] < x * y)
x = arr[fst], y = arr[lst];
else if(arr[fst] + arr[lst] > sum)
--lst;
else
++fst;
}
if(x == 10086 && y == 10086)
return vector <int> {};
return vector <int> {x, y};
}
};
左旋转字符串
汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
- 旋转0-n-1 n-str.length 0-str.length即可
xxxxxxxxxx
class Solution {
public:
void rvse(string &x, int fst, int lst)
{
while(fst <= lst)
swap(x[fst++], x[lst--]);
}
string LeftRotateString(string str, int n) {
if(str.length())
{
n %= str.length();
rvse(str, 0, n - 1);
rvse(str, n, str.length() - 1);
rvse(str, 0, str.length() - 1);
}
return str;
}
};
翻转单词顺序列
牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
- 针对每个单词先翻转,再对整个序列反转 同上
xxxxxxxxxx
class Solution {
public:
void rvs(string &x, int fst, int lst)
{
while(fst < lst)
swap(x[fst++], x[lst--]);
}
string ReverseSentence(string str) {
int la = 0;
for(int i = 0; i < str.length(); ++i)
{
if((i + 1 < str.length() && str[i + 1] == ' ') || i == str.length() - 1)
{
rvs(str, la, i);
la = i + 2;
}
}
rvs(str, 0, str.length() - 1);
return str;
}
};
扑克牌顺子
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
孩子们的游戏(圆圈中最后剩下的数)
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1
- 递归 / 递推 / 记忆化搜索
- 假设长度为X的序列上一次出队的是第Y个人 那么这次出队的应该是(Y+M) % N个同学 只考虑相对位置,并不需要知道具体序列,当只剩一个同学的时候出队是他自己
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if(n == 0)
return -1;
else if(n == 1)
return 0;
else
return (LastRemaining_Solution(n - 1, m) + m) % n;
}
};
求1+2+3+...+n
求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
- 不能用以上条件,所以不能用公式迭代等等实现,考虑条件短路运算符实现递归
class Solution {
public:
int Sum_Solution(int n) {
int sum = 0;
bool x = ( (n > 0) && (sum += Sum_Solution(n - 1) + n) > 0 );
return sum;
}
};
不用加减乘除做加法
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
- 位运算模拟进位
class Solution {
public:
int Add(int num1, int num2)
{
int ret = 0, sig = 0;
for(int i = 0; i < 32; ++i)
{
int x = (num1 >> i) & 1, y = (num2 >> i) & 1;
if(!(x && y) && (x || y))
{
if(!sig)
{
ret = ret | (1 << i);
sig = 0;
}
}
else if(x && y)
{
if(sig)
{
ret = ret | (1 << i);
}
else
sig = 1;
}
else
{
if(sig)
{
ret = ret | (1 << i);
sig = 0;
}
}
}
return ret;
}
};
把字符串转换成整数
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
- 常规模拟判断,对于不能用int的情况可以字符串比较
typedef long long ll;
class Solution {
public:
int StrToInt(string str) {
ll ret = 0;
ll sig = 1;
if(!str.length())
return ret;
int beg = 0;
if(str[beg] == '-')
sig = -1, beg ++;
else if(str[beg] == '+')
beg ++;
for(int i = beg; i < str.length(); ++i)
{
if(str[i] < '0' || str[i] > '9')
return 0;
ret = ret * 10 + (str[i] - '0');
}
ret = ret * sig;
if(ret != int(ret))
ret = 0;
return ret;
}
};
数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
- 考虑数据范围,换位法实现,O1复杂度
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)
{
while(numbers[i] != i)
{
if(numbers[numbers[i]] == numbers[i])
{
*duplication = numbers[i];
return true;
}
swap(numbers[i], numbers[numbers[i]]);
}
}
return false;
}
};
构建乘积数组
给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]A[1]...A[i-1]A[i+1]...A[n-1]。不能使用除法。(注意:规定B[0]和B[n-1] = 1)
- 构建前后缀积序列,注意位置偏移即可
class Solution {
public:
vector<int> multiply(const vector<int>& A) {
vector <int> pre(A.size() + 10, 0), aft(A.size() + 10, 0), ret;
pre[0] = 1;
aft[A.size() + 1] = 1;
for(int i = 1; i <= A.size(); ++i)
{
pre[i] = pre[i - 1] * A[i - 1];
}
for(int i = A.size(); i >= 1; --i)
{
aft[i] = aft[i + 1] * A[i - 1];
}
for(int i = 0; i < A.size(); ++i)
{
int val = pre[i] * aft[i + 2];
ret.push_back(val);
}
return ret;
}
};
正则表达式匹配
请实现一个函数用来匹配包括'.'和''的正则表达式。模式中的字符'.'表示任意一个字符,而''表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配
xxxxxxxxxx
表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
xxxxxxxxxx
字符流中第一个不重复的字符
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
- 用Q维护序列顺序,数组标记是否出现,时间On空间O1(256)
class Solution
{
public:
//Insert one char from stringstream
char cnt[256] = {0};
queue <char> Q;
void Insert(char ch)
{
Q.push(ch);
++cnt[ch];
}
//return the first appearence once char in current stringstream
char FirstAppearingOnce()
{
while(Q.size() && cnt[Q.front()] > 1)
Q.pop();
if(Q.size() == 0)
return '#';
return Q.front();
}
};
链表中环的入口结点
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
- 快慢指针找环,找到后哪一个指针从头开始走,再次相遇为入口节点
xxxxxxxxxx
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if(pHead == NULL || pHead->next == NULL)
return NULL;
ListNode *fast = pHead, *slow = pHead;
do
{
slow = slow->next;
fast = fast->next->next;
}while(slow != fast);
fast = pHead;
while(fast != slow)
{
fast = fast->next;
slow = slow->next;
}
return fast;
}
};
删除链表中重复的结点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
- 双指针比较计数,单独一个的用尾插法向后插,注意插完后要封口断链
xxxxxxxxxx
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if(pHead == NULL || pHead->next == NULL)
return pHead;
ListNode *nxt = new ListNode(-0x3f3f3f3f), *ret = nxt;
ListNode *tmp = pHead;
while(tmp != NULL)
{
tmp->val;
int p = 1;
while(tmp->next != NULL && tmp->next->val == pHead->val)
{
tmp = tmp->next;
++p;
}
if(p == 1)
{
nxt->next = tmp;
nxt = nxt->next;
}
tmp = tmp->next;
pHead = tmp;
nxt->next = NULL;
}
return ret->next;
}
};
二叉树的下一个结点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
- 考虑中序遍历特点 (左中右),如果有那就是右儿子的最左儿子
- 没右儿子,找到一个节点使得目标节点在其左子树内
xxxxxxxxxx
/*
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 {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if(pNode->right == NULL)
{
TreeLinkNode *lst = pNode, *now = pNode->next;
while(now != NULL && lst != now->left)
{
lst = now;
now = now->next;
}
return now;
}
else
{
TreeLinkNode *now = pNode->right;
while(now->left != NULL)
now = now->left;
return now;
}
}
};
对称的二叉树
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
- 双向搜索对于同样的位置是否相同 并运算,有一个不同的就整体不同
xxxxxxxxxx
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
bool check(TreeNode *fst, TreeNode *lst)
{
if(fst == NULL && lst == NULL)
return true;
else if(fst == NULL || lst == NULL)
return false;
if(fst->val != lst->val)
return false;
return check(fst->left, lst->right) && check(fst->right, lst->left);
}
bool isSymmetrical(TreeNode* pRoot)
{
return check(pRoot, pRoot);
}
};