《剑指OFFER》刷题笔记
未完待更新
1.二维数组中的查找
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
-
暴力查找 复杂度O(nm)
-
对每行二分查找O(nlog(m))
-
二维数组中的一个数,小于它的数一定在其左边,大于它的数一定在其下边。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间,当前元素的查找区间为左下角的所有元素。复杂度O(n + m)
#include <bits/stdc++.h>
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即可
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。
-
递归/栈遍历返回
/**
* 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栈中即可保证顺序。
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。
-
对窗口进行二分查找,缩进查找于窗口首值小于窗口尾值(此时旋转点位于当前窗口内)。当首值等于尾值时,无法确定,只能暴力查找
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]
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
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)
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];
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次数
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
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)
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个结点。
-
递归查询标记
/*
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 反转链表
输入一个链表,反转链表后,输出新链表的表头。
-
循环头插
/*
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 合并两个排序的链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
-
归并,两个合并到一个结束,再将另一个接在后面
/*
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:我们约定空树不是任意一个树的子结构)
-
暴力匹配 注意空树的特别处理
/*
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;
}
};
二叉树的镜像
题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
输入描述:
二叉树的镜像定义:源二叉树 8 / \ 6 10 / \ / \ 5 7 9 11 镜像二叉树 8 / \ 10 6 / \ / \ 11 9 7 5
-
对所有节点进行递归反转
/*
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.
-
暴力模拟
-
设左上右下坐标,一次添加一个圈,对于一行的单独判断
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;
}
};
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
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就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
-
根据入栈顺序模拟 全符合为真
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
/*
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。假设输入的数组的任意两个数字都互不相同。
-
后序遍历特点:左右中,当前节点最后遍历,因此将最后节点拿出后看剩下序列能不能拆为两部分使得前部分小于当前节点,后部分大于当前结点即可(递归处理)
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同时记录路径,对于形成路径长度为某值的记录
/*
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,记录头尾指针边遍历边更新
/*
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。
输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
-
DFS标记回溯搜索 注意要判断重复(SET维护)
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)
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 维护
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
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;
}
};
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
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
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(需要区分大小写).
-
维护次数 位置判断
-
队列维护
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
-
归并求逆序对
-
树状数组求逆序对
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);
}
};
两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。
-
分别记录两个链表的长度,让长的链表先后跑直到两链表长度相同,然后同时向后遍历,第一个相遇即为答案
/*
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;
}
}
};
数字在排序数组中出现的次数
统计一个数字在排序数组中出现的次数。
-
二分查找
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
/*
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);
}
};
平衡二叉树
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
-
平衡二叉树定义 任意节点左右子树高度值相差不超过一
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
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
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,输出两个数的乘积最小的。
-
排序数组 首位双指针逼近更新答案
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即可
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对一一的翻转这些单词顺序可不在行,你能帮助他么?
-
针对每个单词先翻转,再对整个序列反转 同上
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;
}
};
本文提供《剑指Offer》的刷题笔记,涵盖数组、链表、二叉树等多种数据结构的算法题解析,包括查找、排序、递归、动态规划等核心算法技巧。
6万+

被折叠的 条评论
为什么被折叠?



