数组简介
- 数组(array)
- java : [], ArrayList
- C++ : STL vector, []
- C: 只有[]
- 理解:输入的数组通常理解为集合,我们自己可以排序,查找
- 注意
- C++ STL中vector的一种实现
- 数组下标是一种特殊的hash…做计数
- 理解数组与map
- 给数组“顺序”
局部极小值
- 一个给定的不包含相同元素的整数数组,每个,局部极小值的定义是一个值比左右相邻的(如果存在)都小的值,求它的一个局部最小值
分析:
- 局部最小值的存在性,全部数组的最小值显然是一个解。 O(n)?
- 我们规定数组下标a[1…n],并定义a[0] = a[n + 1] = ∞, 我们有a[1] < a[0], a[n] < a[n + 1]
- 结论: 子数组a[x…y] 若 a[x] < a[x – 1] , a[y] < a[y + 1],则它包含一个局部极小值
二分法
- mid = (x + y) / 2,二分,两个子数组a[x…mid], a[mid + 1…y]
- 若a[mid] < a[mid + 1], 则子数组a[x…mid]满足a[x] < a[x - 1], a[mid] < a[mid + 1]
- 反之a[mid] > a[mid + 1], 则子数组a[mid + 1…y]满足a[mid + 1] < a[mid], a[y] < a[y + 1]
- 复杂度 O(logn)
第一个缺失的正整数
- 给一个数组,找到从1开始第一个不在里面的正整数。
- 例如[3,4,-1,1]输出2。
- 分析: 数组下标从0开始
- 让a[i] == i + 1
- 每次循环
- 要么i + 1
- 要么n – 1
- 要么有一个数
- 被放到正确的位置
代码实现
- 排序复杂度 O(logn)
class Solution{
public:
int firstMissingPositive(int A[],int n){
//(0...i) is (1...i)
for(int i=0;i<n;){
if(A[i]==i+1){
++i;
}else if((A[i]<=i)||(A[i]>n)||A(A[A[i]-1]==A[i])){
//小于等于i 大于n(最大值) 重复A[2]=5 A[5-1]=A[4]=A[2]
A[i]=A[--n];//删除A[i],并把结尾的数交换到i的位置
}else{
// A[2] A[5-1]=A[4]=A[2]
swap(A[i],A[A[i]-1]);
}
}
}
}
元素最大间距离
- 给定一个整数数组(n > 1),求把这些整数表示在数轴上,相邻两个数差的最大值。
分析
- 显然排序是一个思想。有更好的方法么
- 最大值x, 最小值y, 如果x == y显然答案是0
- 把数放进(n + 1)个桶
- 每个桶大小是d = (x – y) / (n + 1) (浮点数)
- 每个桶区间是[y + i * d, y + (i + 1) * d) (i=0,1,…n)
- 注意是左闭右开的区间,最后一个桶是双闭区间
- 最小的数在0号桶里,最大的数在n号桶里
- 第一个桶非空,最后一个桶非空
- 中间有空桶,空桶左右两侧肯定有元素
- 最大间隙出现在一个非空桶的最大值和下一个非空桶的最小值之间
- 如何判断数r在哪个桶里?
- (r – y) * (n + 1) / (x – y) (整数运算),注意r == x的时候,答案取n
- 记录每个桶的最大值和最小值即可,时间空间都是O(n)
代码实现(Java)
public int maxGap(int[] nums){
if(nums==null || nums.length<2){
return 0;
}
int len = nums.length;
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
//扫描一遍数组,得到最大值max,最小值min 复杂度O(n)
for(int i=0;i<len;i++){
min = Math.min(min,nums[i]);
max = Math.max(max,nums[i]);
}
if(min==max){//说明数组中所有值相等
return 0;
}
//开辟len+1段分区
boolean[] hasNum = new boolean[len + 1];
int maxs = new int[len+1];
int mins = new int[len+1];
int bid = 0;
for(int i=0;i<len;i++){
bid = bucket(nums[i],len,min,max);//算出桶号
mins[bid] = hasNum[bid] ? Math.min(mins[bid],nums[i]):nums[i];
maxs[bid] = hasNum[bid] ? Math.max(mins[bid],nums[i]):nums[i];
hasNum[bid] = true;
}
int res = 0;
int lastMax = 0;
int i = 0;
while(i<=len){
if(hasNum[i++]){//找到第一个不为空的通
lastMax = maxs[i-1];
break;
}
}
for(;i<=len;i++){
if(hasNum[i]){
res = Math.max(res,mins[i]-lastMax);
lastMax = maxs[i];
}
}
return res;
}
//使用long类型是为了防止相乘时溢出
public int bucket(long num,long len,long min,long max){
return (int)((num - min)*len/(max-min));
}
只出现1次的数
- 一个数组,所有元素都出现了两次,只有两个数只出现了一次,求这两个数。
分析
- 异或:相同为0,不同为1
- 所有数做异或,则出现两个次的数相抵消,那么最终的结果就是那两个出现一次的数x和y的异或结果,即x xor y ,且这个值非0
- 既然x xor y非0,我们可以找到二进制表示中某一个为1的位(bit)(例如最低位),把所有的数按这位为1和为0分开。
- 在该位为0和为1的数中,各有一个数只出现一次。 (一个是x,另一个是y)
代码+分析
- 第一步:我们需要找到一个条件,给这两个出现过一次的数找出可以区分的条件。相同的数异或等到的结果0,那么整个序列异或的结果就是这两个出现过一次的数的异或。
public static Integer findOnlyNum1(int[] array){
int result = 0;
for(int i = 0 ;i<array.length;i++){
result^=array[i];
}
return result;
}
- 第二步:找出他们的不同之处,前面我们讲过,异或按位操作是相同的为0 ,不同的为1,那么这两个数异或的结果转换成2进制时,低位出现第一个1是就可以区分他们了。
String binaryResult = Integer.toBinaryString(result);
int index = binaryResult.length() - (binaryResult.lastIndexOf("1")+1);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x9BZcai2-1591929215819)(http://i.imgur.com/9RyJ7gk.png)]
- 第三步:在index位为1的分为一组,为0的分为一组,将序列划分为两个序列:
int result1 = 0;
int result2 = 0;
for(int i =0;i<array.length;i++){
if(((array[i]>>index)&1)==1){
result1^= array[i];
}else{
result2^=array[i];
}
}
代码(C++)
#include <stdio.h>
#include <stdlib.h>
int find_first_1_bit( int res ) // 每次移位(>>1)后若不能%2 != 0 ,则找到
{
int n = 0; // 从第0位开始
while( res )
{
if( res % 2 == 1 )
{
break;
}
else
{
res = res >> 1;
n++;
}
}
return n;
}
int judge_N_bit( int num, int N ) // N 从0开始
{
return ( num & ( 1 << N ) ); // 取第N位
}
int main()
{
long int n, i;
int *num, res, N, t1, t2;
while( scanf("%ld", &n) != EOF )
{
num = ( int* )malloc( sizeof( int ) * n );
scanf("%d", &res);
num[0] = res;
for( i = 1; i < n; i++ )
{
scanf("%d", &num[i]);
res ^= num[i];
}
//
// 第一次出现1的位置!
N = find_first_1_bit( res );
t1 = 0;
t2 = 0;
for( i = 0; i < n; i++ )
{
if( judge_N_bit( num[i], N ) ) // 若第N位为1
{
t2 ^= num[i];
}
else // 为0
{
t1 ^= num[i];
}
}
if( t1 < t2 )
{
printf("%d %d\n", t1, t2);
}
else
{
printf("%d %d\n", t2, t1);
}
free( num );
}
return 0;
}
众数问题
- 找出超过一半的数
分析
- 众数出现的次数大于其他所有数出现次数之和
- 每次扔掉两个不同的数,众数不变
- 如果扔掉一个众数,和一个非众数
- 如果扔掉两个非众数
- 如何实现?和x不同就扔掉,表示扔掉了一个x和一个y?
int count = 0, x;
for (int i = 0; i < n; ++i)
if (count == 0) {x = a[i]; count = 1;}
else if (x == a[i]) ++count;
else --count;
//注意有的题目要数一下x出现次数是否确实超过一半。(众数可能不存在)
代码实现
- 方法1:hashmap
- 通过遍历数组,将数组每个数都通过hashmap来统计其出现的个数,如果某个数个数超过一半,则为众数。时间空间复杂度均为O(n)
- 方法2:Moore Voting Algorithm
- 众数存在的情况下,每次扔掉两个不同的数,众数不变,最终剩下的数一定是众数。
- 扔掉一个众数和一个非众数,众数不变
- 扔掉两个非众数,众数不变
- 时间复杂度O(n),空间复杂度O(1)
- 众数存在的情况下,每次扔掉两个不同的数,众数不变,最终剩下的数一定是众数。
#include <iostream>
#include <vector>
#include <map>
#include <math.h>
using namespace std;
class Solution {
public:
// hash_map method
int majorityElement1(vector<int> &num) {
int n =num.size();
if(n==1) return num[0];
map<int,int> m;
for(vector<int>::iterator it=num.begin();it!=num.end();it++){
m[*it]+=1;
if(m[*it] > floor(n/2))
return *it;
}
return -1;
}
// moore voting algorithm
int majorityElement2(vector<int> &num){
int n=num.size();
if(n==1) return num[0];
int count=0;
int x;
for(int i=0;i<n;i++){
if(count==0){
x=num[i];
count=1;
}
else if(x==num[i])
++count;
else
--count;
}
count=0;
for(int i=0;i<n;i++){
if(num[i]==x)
count++;
}
if(count>floor(n/2))
return x;
else
return -1;
}
};
int main()
{
int A[]={2,3,4,5,2,6,2};
int n=sizeof(A)/sizeof(A[0]);
vector<int> nums(A,A+n);
Solution s;
cout<<s.majorityElement1(nums)<<endl;
cout<<s.majorityElement2(nums)<<endl;
return 0;
}
前缀和的应用
/*
* 题目描述:给定一个数组a[N],我们希望构造数组b[N],
* 其中b[i]=a[0]*a[1]*...*a[N-1]/a[i]。
* 在构造过程:不允许使用除法;要求:O(1)空间复杂度和O(n)时间复杂度;
* 除遍历计数器与a[N] b[N]外,不可使用新的变量(包括栈临时变量、对空间和全局静态变量等);
*/
class ConstructeAarry
{
public void ConstructeAarrySolution(double[] nums)
{
int length = nums.Length;
double[] result = new double[length];//存放结果
//先计算后缀积
for (int i = length - 1; i >= 0; i--)
{
result[i] = nums[i] * (i == length - 1 ? 1 : result[i + 1]);
}
//再计算前缀积,就会得出结果
double j=1.0;
for (int i = 0; i < length; j *= nums[i++])
{
result[i] = j * (i == length - 1 ? 1 : result[i + 1]);
}
}
}
更多内容请关注微信公众号: