剑指offer
视频链接及牛客网刷题地址
剑指Offer(四):二维数组中的查找
添加链接描述
剑指Offer(十一):旋转数组的最小数字
添加链接描述
剑指Offer(二十一):调整数组顺序使奇数位于偶数前面
添加链接描述
剑指Offer(三十九):数组中出现次数超过一半的数字
添加链接描述
剑指Offer(四十二):连续子数组的最大和
添加链接描述
剑指Offer(四十五):把数组排成最小的数
添加链接描述
剑指Offer(五十一):数组中的逆序对
添加链接描述
剑指Offer(五十三):数字在排序数组中出现的次数
添加链接描述
剑指Offer(五十六):数组中只出现一次的数字
添加链接描述
剑指Offer(三):数组中重复的数字
添加链接描述
剑指Offer(六十六):构建乘积数组
添加链接描述
二. 数组(11道)
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
/*思路:从右上角开始扫描,判断当前数与目标值的大小关系。若大,则去除当前列,列行减1;若小,行号加1。*/
// 时间复杂度:O(n+m)
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
if(array.empty() || array[0].empty()) return false; // 矩阵不存在或矩阵为空
int i = 0, j = array[0].size()-1;
while(i < array[0].size() && j >= 0){ // 从右上角开始扫描
if(array[i][j] == target) return true;
else if(array[i][j] > target) j--; // 整数在第j列的左边,去除第j列
else i++; // 整数在第i行的下边,去除第i行
}
return false;
}
};
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组[3,4,5,1,2]为[1,2,3,4,5]的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
/*思路:去掉旋转数组最后的重复元素,二分*/
class Solution {
public:
int minNumberInRotateArray(vector<int> nums) {
int n = nums.size() - 1;
if(n < 0 ) return 0;
while(n && 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; // [l, mid],[mid+1, r]
if(nums[mid] < nums[0]) r = mid;
else l = mid + 1;
}
return nums[r];
}
};
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分。
/*思路:双指针,两个指针从左右两边往中间走,在没有相遇之前,左边遇到奇数往右走,右边遇到偶数往左走,当停下来时,交换*/
class Solution {
public:
void reOrderArray(vector<int> &array) {
if(array.empty() || array.size() == 1) return;
int i = 0, j = array.size()-1;
while(i < j){
while(i < j && array[i]%2==1) i++;
while(i < j && array[j]%2==0) j--;
if(i < j) swap(array[i], array[j]); // 两个指针停下来,交换
}
}
};
限制:保证奇数和奇数,偶数和偶数之间的相对位置不变。
只能用额外数组,空间换时间0(n),否则时间复杂度为0(n^2)
# -*- coding:utf-8 -*-
class Solution:
def reOrderArray(self, array):
# write code here
if not array:
return []
return [x for x in array if x&1] + [x for x in array if not x&1 ]
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
- 方法一:哈希表,空间换时间,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> nums) {
if(nums.empty()) return 0;
map<int, int> map;
for(int i = 0; i < nums.size(); i++){
map[nums[i]]++;
if(map[nums[i]] > nums.size()/2)
return nums[i];
}
}
};
- 方法二:时间复杂度O(n),空间复杂度O(1)
/*思路:遍历数组时保存两个值:一是数组中的数字,一是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。遍历结束后,所保存的数字即为所求。*/
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> nums) {
if(nums.empty()) return 0;
int cnt = 0, val = -1;
for(auto x : nums){ // 遍历每个元素,并记录次数;若与前一个元素相同,则次数加1,否则次数减1
if(!cnt) cnt = 1,val = x; // 更新当前元素,cnt设为1
else{
if(x == val) cnt++;
else cnt--;
}
}
cnt = 0; // 判断val是否符合条件,即出现次数大于数组长度的一半
for(auto x : nums){
if(x == val)
cnt++;
}
return cnt > nums.size()/2 ? val : 0;
}
};
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
/*思路:分析数组规律,以一个变量来做动态规划。
定义一个变量s,表示当前值x之前的子数组所有数之和,从前往后遍历:
(1) s > 0, 最大和为 s + x
(2) s <= 0, 最大和为 x,丢弃掉子数组s*/
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array) {
int res = INT_MIN;
int s = 0;
for(auto x : array){
if(s < 0) s = 0;
s += x;
res = max(res, s);
}
return res;
}
};
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
/*思路:自定义一种排序方式,判断所有数的顺序。
1.比a与b,转成字符串as与bs
2.比较asbs与bsas大小,小的放前面
3.依次输出所有字符串*/
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> numbers) {
sort(numbers.begin(), numbers.end(), cmp); // 自定义一种排序方式
string res;
for(auto x : numbers){
res += to_string(x);
}
return res;
}
};
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
/*思路:*/
统计一个数字在升序数组中出现的次数。
- 方法一:遍历一遍数组,计数,时间复杂度O(n)
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
if(data.empty()) return 0;
int cnt = 0;
for(int x : data){
if(x == k)
cnt++;
}
return cnt;
}
};
- 方法二:遍历一遍数组,存到哈希表,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
if(data.empty()) return 0;
map<int, int> hash;
for(int x : data) hash[x]++;
return hash[k];
}
};
- 方法三:二分查找k第一次出现的位置和最后一次出现的位置,时间复杂度O(logn)
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
if(data.empty()) return 0;
// 二分出k第一次出现的位置和最后一次出现的位置
int l = 0, r = data.size()-1;
while(l < r){
int mid = (l + r) >> 1;
if(data[mid] < k) l = mid + 1;
else r = mid;
}
if(data[l] != k) return 0;
int left = l;
l = 0, r = data.size()-1;
while(l < r){
int mid = (l + r + 1) >> 1;
if(data[mid] <= k) l = mid;
else r = mid - 1;
}
return r - left + 1;
}
};
扩展1: 0~(n-1)中缺失的数字。
一个长度为n-1的递增排序数组中的所有数字都唯一,并且每个数字都在范围0(n-1)之内,在范围0(n-1)之内的n个数字有且只有一个数字不在该数组中,找出这个数字。
思路:二分找到nums[i] == i的最后一个数字的位置,返回位置加一
class Solution {
public:
int getMissingNumber(vector<int>& nums) {
if(nums.empty()) return 0;
int l = 0, r = nums.size() - 1;
while(l < r){
int mid = l + r >> 1;
if (nums[mid] == mid) l = mid + 1;
else r = mid;
}
if(nums[r] == r) r ++ ; // 缺失的数字
return r;
}
};
扩展2: 数组中数值和下标相等的元素。
假设一个单调递增的数组里的每个元素都是整数并且是唯一的。
请编程实现一个函数找出数组中任意一个数值等于其下标的元素。
例如,在数组[-3, -1, 1, 3, 5]中,数字3和它的下标相等。
注意:如果不存在,则返回-1。
思路:二分到第一个nums[i] == i,左边nums[i] < i,右边nums[i] > i
class Solution {
public:
int getNumberSameAsIndex(vector<int>& nums) {
if(nums.empty()) return -1;
int l = 0, r = nums.size()-1;
while(l < r){
int mid = l + r >> 1;
if(nums[mid] < mid) l = mid + 1;
else r = mid;
}
if(nums[r] != r) return -1;
return r;
}
};
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
/*思路:哈希表,时空复杂度O(n)*/
class Solution {
public:
void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
map<int, int> hash;
vector<int> res;
for(int x : data) hash[x]++;
for(int x : data)
if(hash[x] == 1)
res.push_back(x);
*num1 = res[0], *num2 = res[1];
}
};
- 思路二:异或、位运算(最优,但不好想)
-
所有数异或得到 x^ y(x^x=0)
-
取 x与y中第k位为1的数
-
将数分为两个集合,第k位为1的集合和第k位不是1的集合,其中x y分别在这两个集合,且相同的元素是在同一个集合里面
于是将其转化成了求重复数字中的单个数值的问题 =>两个集合分别异或一遍
时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
int sum = 0;
for(int x : data) sum ^= x; // num1^num2
int k = 0;
while(!(sum >> k & 1)) k++; // 取x^y中第k位为1的数
int first = 0;
for(int x : data)
if(x >> k & 1)
first ^= x; // 第一个集合异或和即为第一个数
*num1 = first;
*num2 = sum^first; // num1^num2^num1 = num2
}
};
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
- 方法一:
给定的数组是乱序的,我们首先对数组排序,然后遍历数组对比一下当前值和下一个值,如果相等,则找到重复的数字。
时间复杂度O(nlogn)
class Solution {
public:
bool duplicate(int nums[], int length, int* duplication) {
if(length <= 0) return 0;
for(int i = 0; i < length; i++){
if(nums[i] < 0 || nums[i] >= length) return 0;
}
sort(nums, nums+length);
for(int i = 0; i < length; i++){
if(i+1 && nums[i] == nums[i+1]){
duplication[0] = nums[i];
return 1;
}
}
return 0;
}
};
- 方法二:哈希表,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
bool duplicate(int nums[], int length, int* duplication) {
if(length <= 0) return 0;
for(int i = 0; i < length; i++){
if(nums[i] < 0 || nums[i] >= length) return 0;
}
map<int, int> hash;
for(int i = 0; i < length; i++){
hash[nums[i]] ++;
if(hash[nums[i]] > 1){
duplication[0] = nums[i];
return 1;
}
}
return 0;
}
};
- 方法三:最优
如果数组中的元素没有重复的,那么排序之后数字i会出现在数组下标为i的位置。现在我们有重复的数字,所以有些位置可能存在多个数字,有一些位置可能没有数字。
首先从头至尾扫描数组,当扫描到位置i时,比较这个数字(m)是不是等于i,如果是,则接着扫描下一位;如果不是,则再拿它和第m个数字进行比较,如果相等,就找到了重复的数字;如果不相等,就交换两个数字,把m放到属于它的位置。接下来重复这个过程直到发现第一个重复的数字
时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
bool duplicate(int nums[], int length, int* duplication) {
if(length <= 0) return 0;
for(int i = 0; i < length; i++){
if(nums[i] < 0 || nums[i] >= length) return 0;
}
for(int i = 0; i < length; i++){
while(i!=nums[i] && nums[nums[i]]!=nums[i]) swap(nums[i], nums[nums[i]]);
if(nums[i]!=i && nums[nums[i]] == nums[i]){
duplication[0] = nums[i];
return 1;
}
}
return 0;
}
};
给定一个数组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] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)
对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。
/*思路:*/