剑指offer :数组类题目汇总

3.数组中的重复数字:

           在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

         解题思路:一维数组在内存中占据连续的空间,可以根据下标定位对应的元素。从头扫到尾,只要当前元素值与下标不同,就做一次判断,numbers[i]与numbers[numbers[i]],相等就认为找到了重复元素,返回true,否则就交换两者,继续循环,交换时切忌先将numbers[numbers[i]]值进行保存(因为numbers[i值变会影响numbers[numbers[i]])。

public class Solution {
    // Parameters:
    //    numbers:     an array of integers
    //    length:      the length of array numbers
    //    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
    //                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
    //    这里要特别注意~返回任意重复的一个,赋值duplication[0]
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        int temp;
        if(length<=1)
            return false;
        for(int i=0 ;i<length;i++){
            while(numbers[i]!=i){
                if(numbers[i] == numbers[numbers[i]]){
                    duplication[0]=numbers[i];
                    return true;
                }else{
                   temp = numbers[numbers[i]] ;
                   numbers[numbers[i]] =numbers[i] ;
                   numbers[i] = temp;
                 }
            }
        }
    return false;
    }
}

 

39.数组中出现次数超过一半的数字:

         数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

        解题思路:数组中出现次数超过数组长度的一半,如果数组进行了排序,那排序后位于数组中间的数字即为所寻数字(也可能出现数组中没有这样的数字,注意考虑特殊情况。),数组的排序可以利用java中的sort函数,注意导入包。该方法改变了原有数组。

import java.util.*;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array.length == 0)
            return 0;
        Arrays.sort(array);
        int result =  array[array.length/2] ;
        int times = 0;
        for(int i=0 ; i< array.length ; i++){
            if(result==array[i])
                times++;
        }
        if(times>array.length/2)
            return result;
        else
            return 0;
      }
}

        如果不修改数组,在遍历的时候保存两个值,一个为数组中的数字,另一个为次数,下一个数字如果和当前数字相同,则次数加一,否则减一,次数为0 ,保存下一个值,并开始重新计数。最后一个把次数设为1的数字即为所找数。

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array.length == 0)
            return 0;
        int result = array[0];
        int time =1;
        for(int j=0 ; j< array.length ; j++){
            if(time==0){
                result = array[j];
                time=1;
            }else if(result==array[j])
                time++;
            else
                time--;
        }
        int times = 0;
        for(int i=0 ; i< array.length ; i++){
            if(result==array[i])
                times++;
        }
        if(times>array.length/2)
            return result;
        else
            return 0;
      }
}

 

56.数组中数字出现的次数:

1)数组中只出现一次的两个数字:

        一个整型数组中除两个数字之外,其余数字都出现了两次,请写程序找出这两个只出现一次的数字。要求时间复杂度为n,空间复杂度为1.

       解题思路:数字与自己的异或运算的结果为0,根据这一特性, 所有数字的异或结果为两个只出现一次的数字的异或结果,寻找到结果中第一个为1的位数,本按照该位是否为1把数组分成两个数组,每个数组的异或结果即为两个只出现一次的数字。

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int resultOR = 0;
        //异或结果
        for(int i=0;i<array.length;i++)
            resultOR^=array[i];
        //寻找第一个为1的位
        int flag =1;
        int index = 0;
        while(true){
            if((resultOR & flag)!=0){
                index = flag;
                break;
            }
          flag = flag<<1;
        }
         num1[0]=0;
         num2[0]=0;
        //按照第index位是否为1分为两个数组
        for(int i=0;i<array.length;i++){
            if((array[i] & index)!=0)
                num1[0]^=array[i];
            else
                num2[0]^= array[i];
        }
    }
}

2)数组中唯一只出现一次的数字:

          在一个数组中除一个数字只出现一次之外,其他数字都出现了三次,请找出那个只出现一次的数字。

         解题思路:如果一个数字出现三次,则二进制表示的每一位也出现3次,将所有出现三次的数字的二进制表示的每一位都分别加起来,那么每一位都可以被3整除。把数组中所有数字的二进制表示的每一位相加,如果某一位的和能被3整除,那么只出现一次的数字的二进制表示中对应的那一位为0,否则为1.

public class Solution {
    public void findForm3(int [] a) {
        int [] bits = new int[32];
        //将数组中的数的二进制表示的每一位相加
        for(int i =0;i<a.length;i++){
            for(int j =0;j<32;j++){
                bits[j] = bits[j] + ((a[i]>>>j) & 1);
            }
        }
         //结果中某一位能被3整除,则出现一次的数组该位为0,否则为1
        int res =0;
        for(int i =0;i<32;i++){
            if(bits[i]%3!=0){
                res = res | (1<<i);
            }
        }
       return res;
    }
}

 

53.在排序数组中查找数字:

1)数字在排序数组中出现的次数:

         统计一个数字在排序数组中出现的次数。

         解题思路:最直接的方法,从头到尾扫描整个数组,统计数字出现的次数,这样的时间复杂度为O(n)。

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
       int times=0;
       for(int i= 0;i<array.length;i++){
           if(array[i]==k)
               times++;
       }
        return times;
    }
}

       利用二分查找法,找到数字第一次出现和最后一次出现的位置,这样的时间复杂度为O(logn)。以寻找第一次出现位置为例,如果中间数字和k相同,则如果中间值为数组的开始,或者中间数字的前面一个不是k,则中间值即为第一次出现k的位置;如果中间值比k大,则第一次出现位置在前半部分;如果中间值比k小,则第一次出现位置在后半段。

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
       if(array.length==0)
           return 0;
        int first = firstK(array ,0,array.length-1,k);
        int last = lastK(array ,0,array.length-1,k);
        if(first!=-1 && last!=-1)
            return (last-first+1);
        else
            return 0;
    }
    //第一次出现的位置
    public int firstK(int [] array , int start,int end, int k){
        if(start>end)
            return -1;
        int mid = (start+end)/2;
        if(array[mid]==k){
            if(( mid==0 || array[mid-1]!=k && mid>0))
                return mid;
            else 
                end = mid-1;
        }
        else if(array[mid]>k)
            end = mid-1;
        else
            start = mid+1;
        return firstK(array ,start,end,k);
    }
    //最后一次出现的位置
     public int lastK(int [] array , int start,int end, int k){
        if(start>end)
            return -1;
        int mid = (start+end)/2;
        if(array[mid]==k){
            if(mid==array.length-1 || (array[mid+1]!=k && mid<array.length-1))
                return mid;
            else 
                start = mid+1;
        }
         else if(array[mid]>k)
            end = mid-1;
        else
            start = mid+1;
        return lastK(array ,start,end,k);
    } 
}

 

2)0-n-1中缺失的数字:

         一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0-n-1之内,在范围0-n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

        解题思路:最直观的解决方案,利用公式n(n-1)/2,求出所有数字之和,然后求出数组中所有数字的和,两者之差即为不在数组中的数字,这样的时间复杂度为O(n)。细究题目可以发现,该问题可以转化为在排序数组中找出第一个值和下标不相同的元素,对于排序数组,可以使用二分查找的方式。如果中间元素的值和下标不相同,并且它前面一个元素的值和下标相同,则该元素即为缺失数字,如果前一个元素的值和下标也不同,则下一次考察数组的左半边;如果中间元素的值和下标元素的值相同,则考察数组的右半边,结束条件为left<=right。

import java.util.*;
public class Solution {
    public int GetMissingNumber(int[] array){ 
        if (array == null || array.length == 0) {
            return -1; 
        } 
        int start = 0; 
        int end = array.length - 1; 
        while (start <= end) { 
            int mid = (start + end) / 2; 
            if (mid >= 0 && array[mid] != mid) { 
                if (mid == 0 || array[mid - 1] == mid - 1) {
                    return mid; 
                } else { 
                    end = mid - 1; 
                } 
            } else if (array[mid] == mid) { 
                start = mid + 1;
            }
        } 
        if (start == array.length) 
            return array.length; 
        return -1; 
    }
}

3)数组中数值和下标相等的元素:

       假设一个单调递增的数组里的每个元素都是整数并且是唯一的,找出数组中任意一个数值等于其下标的元素。

       解题思路:最直观的解决方案,从头到尾扫描一遍数组,逐一检查数值与下标是否相等,这样时间复杂度为O(n)。对于排序数组,可以使用二分查找的方式。如果第i个数字的值等于i,输出即可;当第i个数字的值大于i,那么它右边的数字都大于对应的下标,考虑左边数组即可;当第i个数字的值小于i,那么它左边的数字都小于对应的下标,考虑右边数组即可,结束条件为left<=right。

import java.util.*;
public class Solution {
     public int GetNumberSameAsIndex(int[] array){ 
         if (array == null || array.length == 0) {
             return -1; 
         } 
         int start = 0; 
         int end = array.length-1; 
         while (start <= end) {
             int mid = (start + end) / 2;
             if (array[mid] == mid) { 
                 return mid; 
             } 
             if (array[mid] > mid) { 
                 end = mid - 1;
             } 
             if (array[mid] < mid) { 
                 start = mid + 1; 
             } 
         } 
         return -1; 
     }
}

 

4.二维数组中的查找:

         在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

        解题思路:由于题目中的矩阵是有序的,选取数组中右上角的数字开始进行判断,逐步缩小查找范围。对于二维数组,object [][] array ;array.length 就是行数;array [0].length 就是列数。

public class Solution {
    public boolean Find(int target, int [][] array) {
        Boolean flag = false;
        int rows = array.length;
        int colums = array[0].length;
        if( rows>0 && colums>0){
            int row =0;
            int colum = colums-1;
            while(row<rows && colum>=0){
                if(target==array[row][colum]){
                    flag = true;
                    break;
                }else if(target>array[row][colum]){
                    row++;
                }else{
                    colum--;
                }
            }
            
        }
        return flag;
    }
}

21.调整数组顺序使奇数位于偶数前面

          输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

          解题思路:要想保证交换顺序后奇数与偶数的相对位置不变,只能在相邻的位置进行交换,从数组的第一个元素开始,每一次将最后一个位置的元素固定,类似于排序算法。或者可以新建两个list集合,分别将奇数和偶数放到集合中,最后在进行合并。

public class Solution {
    public void reOrderArray(int [] array) {
        for(int i = 0; i<array.length-1;i++){
                for(int j = 0; j<array.length-i-1;j++){
                    if(array[j]%2==0 && array[j+1]%2!=0){
                        int temp = array[j];
                        array[j] =array[j+1];
                        array[j+1] =temp;
                    }
            }
        }
    }
}

 

29.顺时针打印矩阵

          输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 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.

       解题思路:矩阵顺时针打印,需要考虑终止条件以及最后一圈的情况,可能只有一行,或只有一列或者只有一个数字,在打印时需考虑上下左右的情况,设置打印条件(尤其是最后的向上打印)

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> result = new ArrayList<Integer>();
        int rows = matrix.length;
        int colums = matrix[0].length;
        if(rows==0 && colums==0)
            return null;
        int left = 0, right = colums-1,up = 0,down = rows-1;
        while(left<=right && up<=down){
            for(int i=left;i<=right;i++)
                result.add(matrix[up][i]);
            if(down>up){//第二步
                for(int j=up+1;j<=down;j++)
                    result.add(matrix[j][right]);
                if(right>left){//第三步
                    for(int k=right-1;k>=left;k--)
                        result.add(matrix[down][k]);
                     if(down-1>up){//第四步
                         for(int t=down-1;t>up;t--)
                            result.add(matrix[t][left]);
                }}}
            left++; right--; up++; down--;
        }
        return result;
    }
}

 

11.旋转数组的最小数字:

           把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

         解题思路:利用二分查找的思想,当两个辅助变量相遇时,结束循环,第二个变量所指即为最小的元素,注意考虑特殊情况,(1)当旋转数组和本身数组相同时,第一个数字即为最小的元素,因此mid初始化设置为0;(2)如果数组中有相同的数字,start、end、mid下标所指的值恰巧相等,此时需要顺序查找的方法来寻找最小的值。

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length==0)
            return 0;
        int start = 0;
        int end = array.length-1;
        int mid = start;
        while(array[start]>=array[end]){
            if(end-start ==1)
                return array[end];
            mid = (end+start)/2;
            //如果数组中有相同的数字,例如{1,1,1,0,1}
            if(array[mid]==array[end] && array[mid]==array[start]){
                int result = array[0];
                for(int i=0;i<array.length;i++){
                    if(result>array[i])
                        result = array[i];
                }
                return result;
            }
            if(array[mid]>=array[end])
                start = mid;
            else if(array[mid]<=array[start])
                end = mid;
        }
        return array[mid];
    }
}

 

42.连续子数组的最大和:

          输入一个整型数组,数组里有整数也有负数,数组中的一个或者连续多个整数组成一个子数组,求所有子数组的和的最大值,要求时间复杂度为n。

         解题思路:设置结果为数组的第一个值,依次扫描数组进行累加,当数组中的元素值大于累加结果,重新进行累加;当累计结果比要输出的结果大时,更新输出结果。


public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array.length==0)
            return 0;
        else{
            int total=array[0],maxSum=array[0];
            for(int i=1;i<array.length;i++){
                if(total>=0)
                    total+=array[i];
                else
                    total=array[i];
                if(total>maxSum)
                    maxSum=total;
            }
            return maxSum;
        }    
    }
 }

 

51.数组中的逆序对:

          在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

         解题思路:采用归并排序的方式,时间复杂度为N(nlogn),先把数组分隔成子数组,统计出子数组内部的逆序对数目,然后再统计出两个相邻子数组之间的逆序对数目。用两个辅助变量指向数组的末尾,每次比较其数字大小,如果左数组数字大于右数组数字,逆序对的数目=右数组中剩余数字的个数。

public class Solution {
    public int InversePairsCore(int [] array,int [] copy,int start,int end){
        if(start==end)
            return 0;
        int mid = (start+end)/2;
        int left = InversePairsCore(array,copy,start,mid)%1000000007;
        int right = InversePairsCore(array,copy,mid+1,end)%1000000007;
        int i = mid,j = end;//前半段末尾,后半段末尾
        int index = end;//copy数组末尾
        int count=0;
        while(i>=start && j>=mid+1){
            if(array[i]>array[j]){
                copy[index--]=array[i--];
                count+=j-mid;
                if(count>=1000000007)//数值过大求余                
                    count%=1000000007;
            }else
                copy[index--]=array[j--];
        }
        for(;j>=mid+1;j--)
            copy[index--]=array[j];
        for(;i>=start;i--)
            copy[index--]=array[i];
         for(int s=start;s<=end;s++)
            array[s] = copy[s];
        return (count+left+right)%1000000007;
    }
    public int InversePairs(int [] array) {
        if(array ==null || array.length ==0)
            return 0;
        int [] copy = new int[array.length];
        for(int i = 0; i<array.length;i++)
            copy[i]=array[i];
        int count = InversePairsCore(array,copy,0,array.length-1);
        return count;
    }
}


 

66.构建乘积数组:

           给定一个数组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的每一位即为所有数的乘积除以A中所对应的那一位的值;不可以用除法,最直观的解法是用连乘n-1个数来得到每一位的结果,这样的时间复杂度为O(n^2),更简单的方法对于求第i位的结果,可以分为两部分,先求出前i-1位的结果,然后再乘以第i位后的乘积。后面的乘积可以构建辅助变量,指向数组的最后一位,依次向前乘。

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
        int [] B = new int[A.length];
        if(A.length ==0 || A ==null)
            return B;
        if(A.length ==1)
            return A;
        B[0]=1;
        for(int i=1;i<A.length;i++)
           B[i] = B[i-1]*A[i-1];
        int temp =1;
        for(int i = A.length-2 ;i>=0;i--){
            temp *= A[i+1];
            B[i] = B[i]*temp;
        }
        return B;
    }
}

 

61.扑克牌顺子:

         从扑克牌中随机抽取五张牌,判断是不是一个顺子,即这五张牌是不是连续的,2-10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字。

       解题思路:把扑克牌抽象成数组,首先将数组排序,其次统计数组中0的个数,最后统计排序之后的数组中相邻数字之前的空缺总数,如果空缺总数小于或者等于0的个数,那么,这个数组就是连续的,否则不连续。注意,如果5张牌中存在相等的两个数,则不可能成为顺子。

import java.util.*;
public class Solution {
    public boolean isContinuous(int [] numbers) {
        boolean result = false;
        if(numbers==null || numbers.length<=0)
            return result;
        Arrays.sort(numbers);
        int numOfZero = 0;
        for(int i =0;i<numbers.length;i++){
            if(numbers[i]==0)
                numOfZero++;
        }
        int min = numOfZero;
        int max = min+1;
        int dif =0;
        while(max<numbers.length){
            if(numbers[min]==numbers[max])
                return result;//存在对子
            dif += numbers[max]-numbers[min]-1;//差距
            min = max;
            max++;
        }
        if(dif<=numOfZero)
            result = true;
        return result;           
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值