手撕代码之“数组”

构建乘积数组

题目描述:
给定一个数组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];)
题目分析:
观察发现,B[i]的结果为A[0:length-1]中除去A[i]元素的乘积。

public class Solution {
    public int[] multiply(int[] A) {
        int[] B=new int[A.length];
        for(int i=0;i<B.length;i++){
            B[i]=1;
        }
        for(int i=0;i<B.length;i++){
            for(int j=0;j<A.length;j++){
                if(i==j){
                    continue;
                }
                B[i]*=A[j];
            }
        }
        return B;
    }
}

数组中重复的数字

题目描述:
长度为n的数组中元素大小均为0~n-1,判断该元素是否存在重复的元素,并且输出这个元素到一个数组duplication[ 0 ]
解题思路:
设置一个count[]数组专门用于记录每一个元素的数量,然后遍历这个count数组,如果找到count [ number[ i ]]>=2,则将该元素number[i]赋值给duplication[0],这其实就是java中使用数组来模拟输出元素。

public class Solution {
    // Parameters:
    //    numbers:  输入数组
    //    length:   输入数组的长度
    //    duplication: 输出数组,用于记录找到的任意一个重复的数字
    // Return value:  找了则为True,否则为False                    
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(length==0){
            return false;
        }
        int[] count=new int[length];
        for(int i=0;i<length;i++){
            count[numbers[i]]++;
        }
        for(int i=0;i<count.length;i++){
            if(count[numbers[i]]>=2 ){
                duplication[0]=numbers[i];
                return true;
            }
        }
        return false;
    }
}

和为sum的两个数字

题意描述:
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
解题思路:
由于数组已经是有序的,设置left指针指向数组头,right指针指向数组尾,两者相向夹逼,在夹逼的过程中,会出现以下三种情况。
(1)arr[left ]+arr[right ]==sum,表明找打,且两数相差越大,乘积越小,所以只要发现,则这个值必然符合乘积最小的要求。
(2)arr[left ]+arr[right ]>sum,此时需要增到其中一个加数,只能是left++(因为数组递增);
(3) arr[left ]+arr[right ]< sum,需要减少一个加数,只能是right–;

//按照上面的分析,这里的代码可以在找到符合要求的left和right直接添加进返回集合中,但是,这里给出一个判断的步骤。仅供参考。
import java.util.ArrayList;
public class Solution {
   public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
       ArrayList<Integer> res=new ArrayList<Integer>();
       if(array.length<2){
           return res;
       }
       int left=0;
       int right=array.length-1;
       int s=0;
       int minNum1=-1;//记录最小乘积对应的left;
       int minNum2=-1;//记录最小乘积对应的right;
       int min=Integer.MAX_VALUE;//初始最小值。
       while(left<right){
           s=array[left]+array[right];
           if(s<sum){
               left++;
           }else if(s>sum){
               right--;
           }else{
               if(array[left]*array[right]<=min){
                   min=array[left]*array[right];
                   minNum1=left;
                   minNum2=right;
               }
               left++;//继续寻找下一对
               right--;
           }
       }
       //只有最小的才进入结果集合。
       if(minNum1>=0 && minNum2>=0){
           res.add(array[minNum1]);
           res.add(array[minNum2]);
       }
       
       return res;
   }
}

数组中只有唯一一个元素重复

题目描述:
数组大小为n+1,其元素为1~n,其中只有一个元素重复了,其他元素均出现一次,请设计算法找出这个元素。
解题思路:
使用异或操作符来实现。它有三个性质
(1)n^n=0
(2)n^0=n
(3)满足交换律,abc=a(bc)
根据上面三个性质我们可以设计如下算法,假设n=1000,即在{1,2,3,…,n,n,…1000}构成的数组中找到n。

  • 设T=123…nn…1000=123…^1000(去除了重复的元素n)
  • 设Q=123…n…^1000(只含有一个n)
  • 根据运算规则,T^Q=n;
    根据上述公式我们可以编写如下代码
class Solution{
    public int finDifferent(int[] arr){
        int T=0;
        int Q=0;
        //T=1^2^3^...^n^n^...^1000
        for(int i=0;i<arr.length;i++){
            T ^= arr[i];
        }
        //Q=1^2^3^...^n^...^1000
        for(int i=1;i<arr.length;i++){
            Q^=i;
        }
        return T^Q;
    }
}

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

题目描述:
一个数组中,只有两个数字没有重复,其余的数字均重复一次,请设计算法找出这两个没有重复的数字。
解题思路:
这道题解题的思路和上一题的核心思想一样。我们以number=[1,2,2,3,3,4 ]为例进行说明。

  • 设T=122334=14
  • 若能number分成两个子数组a=[1, 3,3]和b=[4,2, 2],那么我们可以通过Ta=14(122)=4,Tb=14(4,3,3)=1
  • 关键在于如何将number划分为a和b两个数组?
    • 由于T=14(两个数不相同,则T必然不为0)转化成二进制后,至少有一个位(N)是1,并且1和4的二进制的第N位必然是不同的(根据运算规则),那么可以根据二进制的N位是否为1来将数组划分为两组。上个案例中划分后的可能结果是:a=[1, 3,3] b=[2, 4,4],最后133=1 244=4,获得最后的结果。
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int T=0;
        //所有数^的结果
        for(int i=0;i<array.length;i++){
            T^=array[i];
        }
        //找到T中的二进制中为1的位
        int index=findOneBit(T);
        //遍历数组,并分组
        num1[0]=0;
        num2[0]=0;//0^n=n
        for(int i=0;i<array.length;i++){
            if(isBitOne(array[i],index)){
                //a组,和T进行迭代^运算
                num1[0]^=array[i];
            }else{
                num2[0]^=array[i];
            }
        }
    }
    //找到num的二进制的位为1的索引,
    //如:00100 返回 2
    private int findOneBit(int num){
        int index=0;
        while(( num & 0x1)==0){
            num=num >> 1;
            index++;
        }
        return index;
    }
    //判断num的第index位是否为1
    private boolean isBitOne(int num,int index){
        //将num左移index位,然后& 0x1
        num=num >> index;
        return (num & 0x1)==1?true:false;
    }
}


有序数组统计个数

题目描述:
统计有序数组中指定值的个数,要求时间复杂位logn
解题思路:
看到logn,条件反射地想到二分法,只是要做一个小小的改进,在找到k==array[mid ]之后,不能立即返回,应该继续以这个点分别向左和向右继续寻找,当前count+1,无论向左还是向右,只要找到了count++
(1)向左的边界不能< 0,向右继续寻找的边界不能>arr.length-1


public class Solution {
    public int GetNumberOfK(int [] array , int k) {
       int count=0;
       int l=0;
       int r=array.length-1;
       while(l<=r){
           int mid=(l+r)/2;
           if(array[mid]>k){
               //左半区域
               r=mid-1;
           }else if(array[mid]<k){
               l=mid+1;
           }else{
               //当前mid符合,count+1;
               count++;
               //向mid的继续向左边扫描
               int tmp=mid-1;
               while(true){
                   if(tmp<0 || k!=array[tmp]){
                       break;
                   }
                   count++;
                   tmp--;
               }
               //向右边继续扫描寻找
               tmp=mid+1;
               while(true){
                   if(tmp>array.length-1 || k!=array[tmp]){
                       break;
                   }
                   count++;
                   tmp++;
               }
               return count;
           }
       }
       return 0;
    }
}

使奇数处于偶数的前面

题目描述:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解题思路:
参考插入排序的解法
(1)从第2个奇数开始,参考插入排序的方法,将该奇数插入到该奇数之前的第一个偶数位置处。
(2)插入过程,参考插入排序的代码。

public class Solution {
    public void reOrderArray(int [] array) {
        int len=array.length;
        int i=0;
        while(i<len){
            if(array[i]%2==0){
                i++;
            }else{
               //如果是奇数,array[j]为奇数
                if(i==0){
                    i++;
                    continue;
                }
                //从第二个数开始,将当前奇数插入到最前一个偶数位置
                //这段代码参考插入排序的代码
                int j=i;
                int tmp=array[i];
                while(j-1>=0 && array[j-1]%2==0){
                   array[j]=array[j-1];
                   j--;
                }
                //此时,j指向最前的一个偶数位置。当前奇数插入到该位置
                array[j]=tmp;
                //继续遍历
                i++;
            }
        }
    }
}

顺时针打印矩阵

题目描述:
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 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.
解题思路:
设置4个边界,up、down、left、right,分别完成up边界向右、right边界向下、down边界向左,left边界向上的遍历,每完成一个边界的遍历,对应的边界去除,同时需要满足left<=right;up<=down;程序以这种逐步缩小边界的方式完成顺时针的打印。
直接欣赏一段代码

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> res=new ArrayList<Integer>();
        //特判,行、列、本身为空,均返回空值。
        if(matrix.length==0 || matrix[0].length==0 || matrix==null){
            return res;
        }
       //定义四个边界left、right、up、down
        int left=0;
        int right=matrix[0].length-1;
        int up=0;
        int down=matrix.length-1;
        while(true){
            //最上边一行,向右扫描,col++;
            for(int col=left;col<=right;col++){
                res.add(matrix[up][col]);
            }
            //除去最上面一行,up--;
            up++;
            //不能超出边界
            if(up>down){
                break;
            }
            
            //向下扫描最右边的一列,row++
            for(int row=up;row<=down;row++){
                res.add(matrix[row][right]);
            }
            right--;
            if(right<left){
                break;
            }
            
            //向左扫描最后一行
            for(int col=right;col>=left;col--){
                res.add(matrix[down][col]);
            }
            down--;
            if(down<up){
                break;
            }
            
            //向上扫描最左边的一列
            for(int row=down;row>=up;row--){
                res.add(matrix[row][left]);
            }
            left++;
            if(left>right){
                break;
            }
        }
        return res;
    }
}

数组中出现超过一半的元素

题目描述:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
解题思路:
候选法则,核心原理:相同元素则+1,不同则-1,如果超过半数,那最后的计数器必然>1,
(1)初始化一个候选人,并给它一张票
(2)统计这个候选人是否重复出现在数组中,每出现一次,count+1;没有出现,表明票给了其他人,则count-1;
(3)当票数再次变为0时,就选举当前元素。
(4)检查最后的候选人出现的次数,如果超过半数,就输出该候选人,否则输出0;
看一段代码什么都明白,所有的文字解释显得苍白无力.

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        //采用候选人法
        int con=-1;//候选人
        int count=0;//票数
        //一张票只能给一个人。
        for(int i=0;i<array.length;i++){
            if(count==0){//票数为零了,则选举当前元素为候选人,并获得选票
                con=array[i];
                count++;
            }else{
                //有后选人获得选票
                if(con==array[i]){
                    count++;
                }else{
                    count--;
                }
            }
        }
        //经过一轮头投票之后,能找到候选人
        count=0;
        //统计该候选人的票数
        for(int i=0;i<array.length;i++){
            if(con==array[i]){
                count++;
            }
        }
        //超过半数输出候选人
        return count>array.length/2?con:0;
    }
}

top K 问题

题目描述:
从数组中找出最小的k个数
解题分析:
这是典型的top K问题,使用优先队列来模拟大顶堆,大顶堆有如下特点:每次出队的元素都是最大的元素,利用这个特性,我们可以设置一个容量为K的大顶堆,按照数组的顺序入队k个元素,然后后面的元素依次与大顶堆中的元素比较,把小的元素替大顶堆中最大的元素,这样操作下来,就能保证大顶堆中的元素都是相对较小的元素。
注意,在jdk1.8版本中,PriorityQueue默认是小顶对,可以在构造器传入一个容量和比较器,这个比较器可以使用lamaba表达式来实现,(o1,o2)->o2-o1为大顶堆。

import java.util.*;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
       ArrayList<Integer> result = new ArrayList<Integer>();
       int length = input.length;
       if(k > length || k == 0){
           return result;
       }
        //大顶堆:每次出出队列都是最大元素,(o1,o2)->(o2-o1);
        PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(k,(o1,o2)->o2-o1);
        for (int i = 0; i < length; i++) {
            if (maxHeap.size() != k) {
                maxHeap.offer(input[i]);
            }else{
                if(maxHeap.peek() > input[i]){
                    maxHeap.poll();
                    maxHeap.offer(input[i]);
                }
            }
        }
        for (Integer integer : maxHeap) {
            result.add(integer);
        }
        return result;
    }
}

连续子数组最大和

问题描述:
给定一个数组,求出一个连续子数组,使得它的和最大。
解题思路:

  • 方法1:
    动态规划,设dp[i]表示以i为尾元素的最大和,则动态规划的步骤为:
    (1)确定dp[i]表示的意义
    (2)确定base_case:dp[0]=array[0];
    (3)确定状态:最大和
    (4)枚举选择,选择最优的一种:选择1为:dp[i-1]为正数,即dp[i-1]+array[i];选择2为:dp[i-1]为负数,即dp[i]=array[i];最后求最大的max(dp[i-1]+array[i],array[i]);
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int[] dp=new int[array.length];
        //base_case;
        dp[0]=array[0];
        int res=array[0];
        for(int i=1;i<array.length;i++){
            //做出选择
            //选择1:array[i]是正数,添加array[i],则,dp[i]=dp[i-1]+array[i];
            //选择2:array[i]是负数;
            dp[i]=Math.max(dp[i-1]+array[i],array[i]);
            res=Math.max(res,dp[i]);
        }
        return res;
    }
}
  • 方法2:
    设置一个贡献值,每遍历一个元素,先试探的加上array[i], 如果和为负数,显然,以i结尾的元素对整个结果不作贡献,那设置这个元素的贡献值为0,否则有贡献,加上贡献值。
    最后判断整体的贡献值是否为0,如果是,则整个数组中最大的元素为最大子数组的和;
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int res=array[0];
        int con=0;//初始化贡献值
        for(int i=0;i<array.length;i++){
            if(con+array[i]<0){
                //负数贡献值为零,不计入
                con=0;
            }else{
                con+=array[i];
                res=Math.max(con,res);
            }
        }
        if(con!=0){
            //贡献不为0;
            return res;
        }
        //贡献为0
        res=array[0];
        for(int i=0;i<array.length;i++){
            res=Math.max(array[i],res);
        }
        return res;
    }
}

把数组中的数字组合成最小的数

题目描述:
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
解题思路:
本质上是一个排序的过程,只是排序的规则需要改变,例如,对于{a,b}而言,按照题目要求,有两种情况:
(1)“ab”>“ba” ,则排序顺序为:b a
(2)“ab”<“ba”,则排序顺序为:a b
(3) “ab”==“ba”,则排序的顺序任意。
在java中,java.util.Collections工具包有个sort(List,Comparator)方法,可以对list按照传入的Comparator进行比较,我们在这个Comparator中进行上述逻辑的判断;
Comparator的用法:
其内部有个int compare(O1,O2)方法,返回值决定O1和O2如何排序,如果返回<0的数,表明为false,不需要调整原来的顺序,若返回>0的数,需要调整原来的顺序即O2 O1;
直接代码

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Solution {
    public String PrintMinNumber(int [] numbers) {
        StringBuilder sb=new StringBuilder();
        ArrayList<Integer> list=new ArrayList<Integer>();
        for(int i=0;i<numbers.length;i++){
            list.add(numbers[i]);
        }
        Collections.sort(list,new Comparator<Integer>(){
            @Override
            public int compare(Integer o1,Integer o2){
                String str1=o1+""+o2;
                String str2=o2+""+o1;
                //a  b
                //ab>ba 则应该b a
                //ab<ba a b
                // ab==ba 0
                int res=str1.compareTo(str2);//str1-str2
                if(res>0){
                    return 1;//需要交换a 和 b
                }else if(res<0){
                    return -1;
                }else{
                    return 0;
                }
            }
        });
        for(Integer e:list){
            sb.append(e);
        }
        return sb.toString();
    }
}

数组中逆序对问题

题目描述
数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
解题思路:
根据归并排序的改进来求解,在归并排序的过程中,两个子数组[a, b,c] [d , e,f],如果a>d,则需要先将d放入缓存数组,同时(a,d)是一个逆序对,且b和c也能形成逆序对。
直接看代码中解释更容易理解

public class Solution {
    int res=0;
    public int InversePairs(int [] array) {
        if(array.length<2){
            return 0;
        }
        int[] tmp=new int[array.length];
        mergeSort(array,0,array.length-1,tmp);
        return res;
    }
    //递归划分,到l==r为止(只有一个元素)
    private void mergeSort(int[] array,int l,int r,int[] tmp){
        if(l>=r){
            return ;
        }
        //折半划分,先求出mid
        int mid=(l+r)/2;
        mergeSort(array,l,mid,tmp);
        mergeSort(array,mid+1,r,tmp);
        merge(array,l,mid,r,tmp);
    }
    private void merge(int[] array,int l,int mid,int r,int[] tmp){
        //合并的过程
        //[l,mid]和[mid+1,r]
        int i=l;
        int j=mid+1;
        int k=0;
        while(i<=mid && j<=r){
            if(array[i]>array[j]){
                tmp[k++]=array[j++];
                //核心部分,思考关键:
                //array[i]>array[j]说明了这两个元素是逆序的关系,并且
                //[l,mid]和[mid+1,r]已经排好序,即array[mid]>array[l],故array[1...mid]均符合逆序的关系
                //数量为mid-i+1
                res+=(mid-i+1);
                res=res%1000000007;
            }else{
                tmp[k++]=array[i++];
            }
        }
        //两部分必然有一部分先遍历完成,将剩余的部分复制tmp
        
         while(i<=mid){
            tmp[k++]=array[i++];
        }
        //右半部分没复制完成
        while(j<=r){
            tmp[k++]=array[j++];
        }
        //注意不是从0开始,而是从当前处理的子数组的l开始可能是l或者mid+1;
        k=0;
        int tmpLeft=l;
        while(tmpLeft<=r){
            array[tmpLeft++]=tmp[k++];
        }
    }
}

和为Sum的连续正整数序列

题目描述:
找出所有连续的和为Sum的正整数序列,输出结果按从小到大排列
解题思路:
双指针,由于是连续的正整数序列,所以为等差数列,差值为1,故curSum=( a 0 a_0 a0+ a n a_n an)*n/2;通过比较curSum和Sum的大小来移动窗口。分三种情况:
(1)但curSum==sum,找到,将窗口内的所有数据加入结果集合,同时窗口左移动。
(2)surSum< sum,窗口右边扩大,增加了一个元素,
(3)curSum>sum,左边窗口右移动,减少了一个元素,

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
       //利用双指针,连续的正整数[1,2,...n]
       //初始化窗口[1,2],至少含有两个元素。
        ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
        
        int left=1;
        int right=2;
        while(left<right){
           int curSum=(left+right)*(right-left+1)/2;//标准的求和公式
            ArrayList<Integer> list=new ArrayList<Integer>();
           if(curSum==sum){
               //将窗口内的数据全部加入进来
               for(int i=left;i<=right;i++){
                   list.add(i);
               }
               res.add(list);
               left++;
           }else if(curSum<sum){
               //右边窗口移动
               right++;
           }else{
               left++;
           }
        }
        return res;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值