数组相关算法(局部极小值、第一个缺失的正整数、元素最大间距离(分桶)、只出现1次的数、众数问题、前缀和的应用)

数组简介

  • 数组(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]);
        }
    }
}

更多内容请关注微信公众号:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值