文章目录
剑指offer 01——二维数组的查找
题目描述
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1, 2, 8, 9],
[2, 4, 9, 12],
[4, 7,10,13],
[6, 8 11,15]
]
给定 target = 7,返回 true。
给定 target = 3,返回 false。
解题思路
二分法
右上角向左下角查找
大于查找值:x-1
小于查找值:y+1
代码
bool Find(int target, vector<vector<int> > array) {
if(array.size() == 0 || array[0].size() == 0)
return false;
int x_len = array.size();
int y_len = array[0].size();
for(int x = 0, y = y_len - 1; x < x_len && y >= 0;){
if(array[x][y] == target)
return true;
else if(array[x][y] > target)
--y;
else
++x;
}
return false;
}
剑指offer 06——旋转数组的最小数字
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。输入
[3,4,5,1,2]
返回值
1
解题思路
二分法
每次取中间位置与末尾位置相比
1.mid > r 说明位于 mid+1 到 r 之间
2.mid < r 说明位于 l 到 mid 之间
3.mid = r ,r–,进一步判断
代码
int minNumberInRotateArray(vector<int> rotateArray) {
if(rotateArray.size() == 0)
return 0;
int l = 0, r = rotateArray.size() - 1;
while(l < r) {
int mid = l + ((r - l) >> 1);
if(rotateArray[mid] > rotateArray[r])
l = mid + 1;
else if(rotateArray[mid] < rotateArray[r])
r = mid;
else
r--;
}
return rotateArray[l];
}
剑指offer 13——调整数组顺序使奇数位于偶数前面
题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
输入
[1,2,3,4]
返回值
[1,3,2,4]
解题思路
out-place操作:新定义一个vector,遍历两次容器,第一次放进奇数,第二次放进偶数
in-place操作:双指针方法,头指针指向第一个奇数,尾指针向后遍历,需要初始化操作一下,容器的头位置必须为奇数
代码
//out-place操作
vector<int> reOrderArray(vector<int>& array) {
// write code here
vector<int> res;
for(int i = 0; i < array.size(); ++i){
if(array[i] & 1)
res.push_back(array[i]);
}
for(int i = 0; i < array.size(); ++i){
if(! (array[i] & 1))
res.push_back(array[i]);
}
return res;
}
剑指offer 19——顺时针打印矩阵
题目描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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.
输入
[[1,2],[3,4]]
返回值
[1,2,4,3]
解题思路
循环打印步骤为下图所示:
打印过程注意边界条件即可
代码
void print(vector<vector<int>> &matrix, vector<int> &res, int l_x, int r_x, int l_y, int r_y){
//第一步:
for(int j = l_y; j <= r_y; ++j) res.push_back(matrix[l_x][j]);
//第二补:
for(int i = l_x + 1; i <= r_x; ++i) res.push_back(matrix[i][r_y]);
//第三步:
int high = r_x - l_x;
if(high)
for(int j = r_y - 1; j >= l_y; --j) res.push_back(matrix[r_x][j]);
//第四步:
int width = r_y - l_y;
if(width)
for(int i = r_x - 1; i > l_x; --i) res.push_back(matrix[i][l_y]);
}
vector<int> printMatrix(vector<vector<int> > matrix) {
vector<int> res;
if(!matrix.size() || !matrix[0].size())
return res;
int l_x = 0, l_y = 0;
int r_x = matrix.size() - 1;
int r_y = matrix[0].size() - 1;
while(l_x <= r_x && l_y <= r_y) {
print(matrix, res, l_x++, r_x--, l_y++, r_y--);
}
return res;
}
剑指offer 34——第一个只出现一次的字符
题目描述
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)
输入
“google”
返回值
4
解题思路
使用数组作为辅助容器
1.第一次遍历找出每个字符出现的次数
2.第二次遍历找到目标字符
代码
int FirstNotRepeatingChar(string str) {
//定义数组存储字符出现的次数
int array[128] = {0};
for(char c : str) {
++array[c];
}
//查找出现一次的数组
for(int i = 0; i < str.size(); ++i) {
if(array[str[i]] == 1)
return i;
}
return -1;
}
剑指offer 35——数组中的逆序对
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
输入
[1,2,3,4,5,6,7,0]
返回值
7
解题思路
利用归并排序的思想
对于 (m, n)和 (i, j)两个有序的区间,若m>i, 则 (m+1, n)都大于i
并且内层的逆序对不影响外层的逆序对
具体逻辑见代码
代码
int InversePairs(vector<int> data) {
int ret = 0;
merge_sort(data, ret, 0, data.size()-1);
return ret;
}
//归并排序(递归)
void merge_sort(vector<int> &data, int &ret, int l, int r) {
if(l >= r)
return;
int mid = l + ((r-l)>>1);
merge_sort(data, ret, l, mid);
merge_sort(data, ret, mid+1, r);
merge(data, ret, l, mid, r);
}
//子区间合并(计算逆序对的部分)
void merge(vector<int> &data, int &ret, int l, int mid, int r) {
vector<int> tmp(r - l + 1);
int i = l, j = mid + 1, k = 0;
while(i <= mid && j <= r) {
if(data[i] > data[j]){
tmp[k++] = data[j++];
//计算逆序对
ret += (mid - i + 1);
ret %= 1000000007;
}
else {
tmp[k++] = data[i++];
}
}
while(i <= mid)
tmp[k++] = data[i++];
while(j <= r)
tmp[k++] = data[j++];
//排序结果写回到原数组
for(k = 0, i = l; i <= r; ++i, ++k) {
data[i] = tmp[k];
}
}
剑指offer 41——和为S的连续正数序列
题目描述
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输入
9
返回值
7[[2,3,4],[4,5]]
解题思路
滑动窗口
定义滑动窗口的开始/结束位置,以及当前窗口内的数字之和
代码
vector<int> GetRes(int l, int r){
vector<int> res;
for(int i = l; i <= r; ++i)
res.push_back(i);
return res;
}
vector<vector<int>> FindContinuousSequence(int sum) {
vector<vector<int>> res;
int l = 1, r = 1, tmp = 1;
while(r < sum){
//形成一个窗口,窗口右移
if(tmp == sum) {
res.push_back(GetRes(l, r));
++r;
tmp += r;
}
//窗口之和过大
else if(tmp < sum){
++r;
tmp += r;
}
//窗口之和过小
else{
tmp -= l;
++l;
}
}
return res;
}
剑指offer 42——和为S的两个数字
题目描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
输入
[1,2,4,7,11,15],15
返回值
[4,11]
解题思路
双指针
左指针指向0,右指针指向size()-1
定义保存当前乘积的变量
代码
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
pair<int, int> res;
if(array.size() <= 1) return vector<int>();
//当前乘积的最小值
int tmp = INT_MAX;
int l = 0, r = array.size() - 1;
while(l < r) {
if(array[l] + array[r] == sum) {
if(array[l] * array[r] < tmp){
res = {l, r};
tmp = array[l] * array[r];
}
//不要忘了这一步
++l, --r;
}
else if(array[l] + array[r] < sum)
++l;
else
--r;
}
if(res.first == res.second) return vector<int>();
return vector<int>{array[res.first], array[res.second]};
}
剑指offer 45——扑克牌顺子
题目描述
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。
输入
[0,3,2,6,4]
返回值
true
解题思路
1.对牌进行排序
2.除了0,找出最大的和最小的数,差值小于5说明可以组成顺子
3.如果响铃的两张牌相等,说明不能组成顺子
代码
bool IsContinuous( vector<int> numbers ) {
if(numbers.size() < 5) return false;
sort(numbers.begin(), numbers.end());
int i = 0, sz = numbers.size();
for(int j = 0; j < sz; ++j) {
if(numbers[j] == 0) {
++i;
continue;
}
if(j < sz - 1 && numbers[j] == numbers[j+1])
return false;
}
return (numbers[sz-1] - numbers[i]) < 5;
}
剑指offer 50——数组中重复的数字
题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任一一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1
输入
[2,3,1,0,2,5,3]
返回值
2或3
解题思路
1.使用map存储数字出现的次数
2.当前数字出现了2次,跳出循环
代码
int duplicate(vector<int>& numbers) {
// write code here
map<int, int> mp;
for(int k : numbers) {
++mp[k];
if(mp[k] == 2)
return k;
}
return -1;
}
剑指offer 51——构建乘积数组
题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任一一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1
输入
[1,2,3,4,5]
返回值
[120,60,40,30,24]
解题思路
1.B[i] = left[i] * right[i]
2.先求出left[i],
3。再求出right[i],边求边算B[i],因为A[i]已经构建好了
代码
vector<int> multiply(const vector<int>& A) {
vector<int> B(A.size(), 1);
for(int i = 1; i < A.size(); ++i) {
//left[i] 用B[i]代替
B[i] = B[i-1] * A[i-1];
}
int tmp = 1;
for(int j = A.size()-2; j >= 0; --j) {
//right[i]使用tmp来代替
tmp *= A[j+1];
B[j] *= tmp;
}
}
剑指offer 64——滑动窗口的最大值
题目描述
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
窗口大于数组长度的时候,返回空输入
[2,3,4,2,6,2,5,1],3
返回值
[4,4,6,6,6,5]
解题思路
1.暴力方法
代码
vector<int> maxInWindows(const vector<int>& num, unsigned int size) {
vector<int> ret;
if(num.size() < size) return ret;
int len = num.size();
for(int i = 0; i + size - 1 < len; ++i){
int j = i + size -1;
int max_val = num[j];
for(int k = i; k < j; ++k) {
max_val = max(num[k], max_val);
}
ret.push_back(max_val);
}
return ret;
}
剑指offer 65——矩阵中的路径
题目描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如下图矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
输入
[[a,b,c,e],[s,f,c,s],[a,d,e,e]],“abcced”
返回值
true
解题思路
- dfs + 剪枝操作
代码
int rows, cols;
bool dfs(vector<vector<char>> &matrix, string word, int i, int j, int k) {
//剪枝操作
if(i >= rows || i < 0 || j >= cols || j < 0 || matrix[i][j] != word[k])
return false;
if(k == word.size() - 1) return true;
//当前相等,不能在进入这个格子
matrix[i][j] = '\0';
bool res = dfs(matrix, word, i+1, j, k + 1) || dfs(matrix, word, i-1, j, k + 1) ||
dfs(matrix, word, i, j+1, k + 1) ||dfs(matrix, word, i, j-1, k + 1);
//回溯
matrix[i][j] = word[k];
return res;
}
bool hasPath(vector<vector<char> >& matrix, string word) {
// write code here
rows = matrix.size();
cols = matrix[0].size();
//每一个格子都可以作为起点
for(int i = 0; i < rows; ++i) {
for(int j = 0; j < cols; ++j) {
if(dfs(matrix, word, i, j, 0)) return true;
}
}
return false;
}
剑指offer 66——机器人的运动范围
题目描述
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
输入
5,10,10
返回值
21
解题思路
dfs
代码
using v = vector<int>;
using vv = vector<v>;
int dir[5] = {-1, 0, 1, 0, -1};
int check(int n)
{
int sum = 0;
while(n){
sum += (n%10);
n /= 10;
}
return sum;
}
void dfs(int x, int y, int sho, int r, int c, int &ret, vv &mark){
//检查下标 是否已经被访问
if(x < 0 || x >= r || y < 0 || y >= c || mark[x][y] == 1)
return;
//当前坐标不满足条件
if(check(x) + check(y) > sho)
return;
mark[x][y] = 1;
ret += 1;
for(int i = 0; i < 4 ;++i){
dfs(x+dir[i], y+dir[i+1], sho, r, c, ret, mark);
}
}
int movingCount(int threshold, int rows, int cols) {
if(threshold <= 0){
return 0;
}
vv mark(rows, v(cols, -1));
int ret = 0;
dfs(0, 0, threshold, rows, cols, ret, mark);
return ret;
}