面试题3 数组中重复的数字
面试题4 二维数组中的查找
JZ13调整数组顺序使奇数位于偶数前面
题目描述
输入一个整数数组
,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变
。
题解
若函数的类型为void func_name(array&)
,返回void
,想让我们不开辟额外数组来解决
,使用in-place就地算法
。
方法一:使用辅助数组
但是如果空间要求不高的话,我们还是可以开辟个额外数组,接下来的做法就非常简单了,遍历一次数组,遇到奇数直接放入新开的数组中,再遍历一次数组,遇到偶数就继续放入新开的数组。最后再进行一次copy。
class Solution {
public:
void reOrderArray(vector<int> &array) {
vector<int> arr;
for (const int v : array) {
if (v&1) arr.push_back(v); // 奇数,计算机默认存储二进制补码形式,二进制最后一位是1,一定是奇数
}
for (const int v : array) {
if (!(v&1)) arr.push_back(v); // 偶数
}
copy(arr.begin(), arr.end(), array.begin());
}
};
注:
copy
的用法
时间复杂度:O(n)
空间复杂度:O(n)
方法二: 不开辟额外数组:双指针和快慢指针
- 双指针:
参考
考虑定义双指针 i , j 分列数组左右两端,循环执行:
- 指针 i 从左向右寻找偶数;
- 指针 j 从右向左寻找奇数;
- 将 偶数 nums[i]和 奇数 nums[j] 交换。可始终保证: 指针 i 左边都是奇数,指针 j 右边都是偶数 。
class Solution {
public int[] exchange(int[] nums) {
int left = 0;
int right = nums.length-1;
while(left < right){
while(left<right && (nums[left]&1) == 1) left++;
while(left<right && (nums[right]&1) == 0) right--;
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
return nums;
}
}
时间复杂度:O(n),
空间复杂度:O(1)
- 快慢指针
因为要求数组后面是偶数,所以快指针指向的应该是偶数,若快指针指向的是奇数,则就和慢指针指向的数交换。
class Solution {
public int[] exchange(int[] nums) {
int fast = 0;
int slow = 0;
while(fast < nums.length){
if((nums[fast]&1) ==1) swap(nums, slow++, fast);
fast++;
}
return nums;
}
void swap(int[] nums, int slow, int fast){
int temp = nums[slow];
nums[slow] = nums[fast];
nums[fast] = temp;
}
}
时间复杂度:O(n),
空间复杂度:O(1)
JZ19 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例1
输入
[[1,2],[3,4]]
返回值
[1,2,4,3]
题解:
简单来说,就是一圈一圈的
不断地收缩矩阵的边界
代码:
java
class Solution {
public int[] spiralOrder(int[][] matrix) {
if(matrix.length == 0) return new int[0];
int left = 0;
int right = matrix[0].length-1;
int top = 0;
int bottom = matrix.length-1;
int[] res = new int[(right+1)*(bottom+1)];
int x = 0;
while(true){
for(int i =left; i <= right; i++) res[x++] = matrix[left][i];
if(++top > bottom) break;
for(int i = top; i <= bottom; i++) res[x++] = matrix[i][right];
if(--right < left) break;
for(int i = right; i >=left; i--) res[x++] = matrix[bottom][i];
if(--bottom < top) break;
for(int i= bottom; i >= top; i--) res[x++] = matrix[i][left];
if(++left > right) break;
}
return res;
}
}
C++
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
vector<int> ret;
if(matrix.empty()) return ret;
int up=0;
int down=matrix.size()-1;
int left=0;
int right=matrix[0].size()-1;
while(true){//一圈一圈的打印,一次循环打印一圈;
//最上面一行;第一步从左到右;
for(int col=left;col<=right;col++){
ret.push_back(matrix[up][col]);
}
//向下逼近,
up++;
if(up>down) break;
//最右面一列,第二步从上向下;
for(int row=up;row<=down;row++){
ret.push_back(matrix[row][right]);
}
//向左逼近;
right--;
if(right<left) break;
//最下面一行;第三步从右向左;
for(int col=right;col>=left;col--){
ret.push_back(matrix[down][col]);
}
//向上逼近;
down--;
if(up>down) break;
//最左面一列;第四部从下向上;
for(int row=down;row>=up;row--){
ret.push_back(matrix[row][left]);
}
//向右逼近
left++;
if(left>right) break;//这是遍历一圈后,剩下的左边列标大于右边列标,即一列都没有,第一步都进行不下去,即退出
}
return ret;
}
};
JZ28 数组中出现次数超过一半的数字
题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0
。
示例1
输入
[1,2,3,2,2,2,5,4,2]
返回值
2
注:
数组中一个元素出现的次数超过数组长度的一半,这个数成为数组的众数
。由于出现的次数超过数组长度的一半
,所以数组的众数有的话,那么就只有一个
【隐藏点】。
方法一:哈希法
根据题目意思,显然可以先遍历一遍数组,在map中存每个元素出现的次数,然后再遍历一次数组,找出众数。
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.size()==0) return 0;
map<int, int> hashmap;
for(int& val:numbers){//范围for语句的声明的变量是对迭代器元素的复制,若需改变其值,需用引用类型,反之不需要但是也可用引用。即,全用引用即可。
++hashmap[val];
}
for(int& val:numbers){
if(hashmap[val]>numbers.size()/2)
return val;
}
return 0;
}
};
时间复杂度:O(n)
空间复杂度:O(n)
方法二:排序法
由于众数出现的次数超过数组长度的一半,所以如果将数组排序,如果众数存在,则众数肯定在数组中间。
排序后,去中间元素,然后判断一下即可。
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.size()==0) return 0;
sort(numbers.begin(), numbers.end());
int ret=numbers[numbers.size()/2];
int cnt=0;
for(int& val:numbers){
if(val==ret) ++cnt;
}
if(cnt>numbers.size()/2) return ret;
else return 0;
}
};
时间复杂度:O(nlongn)
空间复杂度:O(1)
方法三:摩尔投票法(最优解)
假如数组中存在众数,那么众数一定大于数组的长度的一半。
思想就是:如果两个数不相等,就消去这两个数,最坏情况下,每次消去一个众数和一个非众数,那么如果存在众数,最后留下的数肯定是众数。
由推论二可知,通过遍历,每轮假设发生 票数和 =0 都可以 缩小剩余数组区间
。当遍历完成时,最后一轮假设的数字即为可能的众数
。
缩小剩余数组区间的原理
:假设遍历的两个数票数和为0,若这两个数都不是众数,即众数一个也没消耗掉,剩余数组的众数当然不变;若两个数其中一个是众数,则一个众数一个非众数相互抵消,剩余数组的众数当然也不变。
具体思想参考
具体做法:
若是众数 票数+1,不是-1;
1、初始化:假设众数ret = -1, 候选人的投票数votes= 0
2、遍历数组,如果votes=0, 表示没有假设众数,则选取当前数为假设众数,并++votes
3、否则votes!=0, 表示有候选人,如果当前数==ret ,则++votes,否则–votes
直到数组遍历完毕,最后检查ret 是否为众数
java
class Solution {
public int majorityElement(int[] nums) {
int x = 0;//众数
int votes = 0;
for(int val : nums){
if(votes == 0) x=val;
if(val == x) ++votes;
else --votes;
}
int count = 0;
for(int val : nums){
if(val == x) ++count;
}
if(count > nums.length/2) return x;
else return 0;
}
}
C++
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.size()==0) return 0;
int ret=-1;//初始化众数ret.
int votes=0;
for(int i=0;i<numbers.size();i++){
if(votes==0){//votes==0说明前面以抵消,需要重新假设众数
ret=numbers[i];//随着不断的抵消,如果有众数,一定在留下的数中。
++votes;
}
else{
if(ret==numbers[i]) ++votes;
else --votes;
}
}
int cnt=0;
for(int& val:numbers){
if(val==ret) ++cnt;
}
if(cnt>numbers.size()/2) return ret;
else return 0;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
面试题:66. 构建乘积数组
给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。
示例:
输入: [1,2,3,4,5]
输出: [120,60,40,30,24]
题解:
方法一:左右乘积列表
- 我们可以将上面的关于 b[i] 的公式拆分为两部分:left[i] = a[0] * a[1] … a[i - 1] 和 right[i] = a[i + 1] … a[n - 1]。
- 所以可以得出:b[i] = left[i] * right[i]。其中 b、left、right 的长度都等于输入的数组 a 的长度。
- 我们从前往后填充 left 数组,从后往前填充 right 数组,最后就能计算出 b 的每一位。
特别的left[0]=1;right[n-1]=1:
代码:
Java
class Solution {
public int[] constructArr(int[] a) {
int length = a.length;
if(length <=0) return new int[0];
int[] left = new int[length];
int[] right = new int[length];
int[] B = new int[length];
left[0] = 1;
right[length-1] = 1;
for(int i = 1; i<length ; i++){
left[i]= a[i-1]*left[i-1];
}
B[length-1] = left[length-1] * right[length-1];
for(int i = length-2 ; i >= 0 ; i--){
right[i] = a[i+1] * right[i+1];
B[i] = left[i] * right[i];
}
return B;
}
}
C++
class Solution {
public:
vector<int> constructArr(vector<int>& a) {
int len = a.size();
if(len==0) return vector<int>();
if(len==1) return vector<int>(1, -10086);
vector<int> b(len,1);
vector<int> left(len,1);
vector<int> right(len,1);
left[0] = 1;
right[len-1] = 1;
for(int i=1; i<len; i++){
left[i] = left[i-1]*a[i-1];
}
b[len-1] = left[len-1] * right[len-1];
for(int i=len-2; i>=0; i--){
right[i] = right[i+1]*a[i+1];
b[i] = left[i] * right[i];
}
return b;
}
};
时间复杂度 O(N): 其中 N 为数组长度,两轮遍历数组 a ,使用 O(N)时间。
空间复杂度 O(N) : 创建了3个数组
方法二:空间复杂度 O(1)的方法
参考
由于输出数组不算在空间复杂度内,那么我们可以将 L 或 R 数组用输出数组来计算。先把输出数组当作 L 数组来计算,然后再动态构造 R 数组得到结果。让我们来看看基于这个思想的算法。
class Solution {
public int[] productExceptSelf(int[] nums) {
int length = nums.length;
int[] answer = new int[length];
// answer[i] 表示索引 i 左侧所有元素的乘积
// 因为索引为 '0' 的元素左侧没有元素, 所以 answer[0] = 1
answer[0] = 1;
for (int i = 1; i < length; i++) {
answer[i] = nums[i - 1] * answer[i - 1];
}
// R 为右侧所有元素的乘积
// 刚开始右边没有元素,所以 R = 1
int R = 1;
for (int i = length - 1; i >= 0; i--) {
// 对于索引 i,左边的乘积为 answer[i],右边的乘积为 R
answer[i] = answer[i] * R;
// R 需要包含右边所有的乘积,所以计算下一个结果时需要将当前值乘到 R 上
R *= nums[i];
}
return answer;
}
}
时间复杂度:O(N),其中 N 指的是数组 nums 的大小。分析与方法一相同。
空间复杂度:O(1),输出数组不算进空间复杂度中,因此我们只需要常数的空间存放变量。
知识点:
1、摩尔投票法:
具体思想参考