剑指Offer______数组

248 阅读8分钟

3.数组中重复的数字

**题目描述:**在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如

Input:
{2, 3, 1, 0, 2, 5}
Output:
2

解题思路1\color{#008000}{解题思路1:} 因为数组中的数字都在0~n-1的范围内,所以,如果数组中没有重复的数,那当数组排序后,数字i将出现在下标为i的位置。现在我们重排这个数组,从头到尾扫描每个数字,当扫描到下标为i的数字时,首先比较这个数字(记为m)是不是等于i。如果是,则接着扫描下一个数字;如果不是,则再拿它和m 位置上的数字进行比较,如果它们相等,就找到了一个重复的数字(该数字在下标为i和m的位置都出现了),返回true;如果它和m位置上的数字不相等,就把第i个数字和第m个数字交换,把m放到属于它的位置。接下来再继续循环,直到最后还没找到认为没找到重复元素,返回false。

public boolean duplication(int[] numbers, int length, int[] duplication){
		//如果输入的数组为空,直接返回false
		if(numbers == null || length<=0)
		return false;
		//从头到尾扫描数组
		for(int i=0; i<length; i++){
			//如果数组不在1——n-1的范围内,直接返回false
			if(numbers[i]<0 || numbers[i]>=length)
			return false;
			//当数组中某个元素不等于其下标时,分为两种情况
			while(numbers[i]!=i){
				//如果这个元素与以这个元素值为下标的元素相等(元素本应该在的位置),则说明这个元素重复了
				if(numbers[i] == numbers[numbers[i]]){
					//把这个重复的元素存储在duplication[]数组中
					duplication[0] = numbers[i];
					return true;
				}else{  //若不相等,即交换,把元素放到与下标对应的位置
					int temp = numbers[i];
					numbers[i] = numbers[temp];
					numbers[temp] = temp;
				}
			}
		}
		return false;
	}

加入Scanner:

import java.util.Scanner;
public class Test {
   public static void getRepeateNum( int[] num) {
       int NumChange;
       System.out.println("重复数字是:");
       for(int index = 0; index < num.length; index++) {
           while(num[index] != index) {
               if(num[index] == num[num[index]]) {
                   System.out.print(num[index]+" ");
                   break;
               } else {
                   NumChange = num[num[index]];
                   num[num[index]] = num[index];
                   num[index] = NumChange;
               }
           }
       }
   }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int[] num = new int[5]; //数组长度可以自己定义
        System.out.println("请输入一组数据:");
        for(int i = 0; i < 5; i++) {
            num[i] = scanner.nextInt();
        }
        getRepeateNum(num);
    }
}

解题思路2\color{#008000}{解题思路2:} 利用HashMap

class Solution {
    public int findRepeatNumber(int[] nums) {
        if(nums==null||nums.length<=0){
     		return 0;
 		}
        
        HashMap<Integer,Integer> map=new HashMap<>();
        for(int i=0;i<nums.length;i++){
            if(!map.containsKey(nums[i])){
                map.put(nums[i],1);
            }else{
                int count=map.get(nums[i]);
                map.put(nums[i],++count);
                }
            int times=map.get(nums[i]);
            if(times>1){
                return nums[i];
            }
        }return 0;
    }
}

4.二维数组中的查找

**题目描述:**在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

解题思路:\color{#008000}{解题思路:} 选择从左下角开始搜寻,因为选择在左下角搜寻的话,如果目标值大于搜索值,那么就向右继续搜索,如果目标值小于搜索值,那么就向上继续搜索。可以根据target和当前元素的大小关系来缩小查找区间

public class Test {
public boolean Find(int target, int[][] matrix) {
    if (matrix == null || matrix.length == 0 || matrix[0].length == 0){
        return false;}
        int rows = matrix.length;//定义多维数组的行数
        cols = matrix[0].length;//定义多维数组的列数
        int r = 0, c = cols - 1; //从右上角开始
        while (r <= rows - 1 && c >= 0) {
            if (target == matrix[r][c]){
                return true;
            }else if (target > matrix[r][c]){
                r++;
            }else{
                c--;
    }
    return false;
    }
public static void main(String[] args) {
        Test test=new Test();
        int[][] matrix={{1,2,3},{4,5,6},{7,8,9}};//测试一个三行三列的数组,目标值为5
        System.out.println(test.Find(matrix, 5));
        }
    }

二维数组的声明:
type[][]数组名=newtype[行元素个数][列元素个数];\color{red}{type[ ][ ] 数组名=new type[行元素个数][列元素个数];}
声明同时赋值:
int[ ][ ] matrix={{1,2,3},{4,5,6},{7,8,9}};

二维数组的length()方法:
在二维数组中:
数组名.length表示二维数组的行数\color{red}{数组名.length表示二维数组的行数}
数组名[行下标].length表示该行中的元素个数(二维数组的列数)\color{red}{数组名[行下标] .length表示该行中的元素个数(二维数组的列数)}

11.旋转数组的最小数字

**题目描述:**把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1

解题思路1\color{#008000}{解题思路1:} 从头到尾遍历数组一次,找出最小的元素

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length==0){
            return 0;
        }
        int ans=array[0];
        for(int i=1;i<array.length;i++){
            ans=Math.min(ans,array[i]);
        }
        return ans;
    }
}

Math中的min方法是用来比较两个数大小的,返回较小的那个数值
用法:Math.min(a,b);\color{red}{用法:Math.min(a, b);}

解题思路2\color{#008000}{解题思路2:} 旋转数组实际上可以划分为两个有序的子数组,最小的元素刚好是这两个字数组的分界线,故旋转数组找最小元素值也可以采取二分查找的思路。通过二分查找,不断更新存在于两个非递减的排序子数组的下标

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length==0){
            return 0;
        }
        int left=0;
        int right=array.length-1;
        while(left<right-1){
            int mid=(left+right)/2;
            if(array[left]<=array[mid]){
                left=mid;   //说明mid所在位置是在第一个非递减子数组中
            }else if(array[right]>=array[mid]){
                right=mid;  //说明mid所在位置是在第二个非递减子数组中
            }
        }return array[right];
    }
}

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

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

解题思路1\color{#008000}{解题思路1:}类似于插入排序的思想,遇到奇数就将当前奇数向前移动,直到往前移动过程中遇到奇数时才停止移动。时间复杂度时O(n^2)

public class Solution {
    public void reOrderArray(int [] array) {
        for(int i=0;i<array.length;i++){
            if(array[i]%2 != 0){
                for(int j=i-1;j>=0;j--){	//遇到奇数向前移动
                    if(array[j]%2 == 0){
                        int temp=array[j];
                        array[j]=array[j+1];
                        array[j+1]=temp;
                    }else{
                        break;
                    }
                }
            }
        }
    }
}

解题思路2\color{#008000}{解题思路2:} 可以开辟2个新空间分别存储奇数、偶数值,最后将2个空间的值合并即可,时间复杂度是O(n),但空间复杂度比方法一大

public class Solution {
    public void reOrderArray(int [] array) {
    	int[] array1=new int[array.length]; //定义一个数组存放奇数
    	int[] array2=new int[array.length]; //定义一个数组存放偶数
        int j=0,k=0;	//两个新数组的下标
        int length1=0,length2=0;	//两个新数组的长度
    	
        for(int i=0;i<array.length;i++){
        	if(array[i]%2!=0){
            	array1[j++]=array[i]; //奇数存放到array1数组
            	length1++;
        	}else{
            	array2[k++]=array[i]; //偶数存放到array2数组
            	length2++;
        	}
    	}
        
        for(int i=0;i<length1;i++){
            array[i]=array1[i];		//奇数数组先放入原array数组中
        }
        
        for(int i=0;i<length2;i++){
            array[length1+i]=array2[i];		//偶数数组后放入原array数组中
        }
	}
}

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

**题目描述:**数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0
解题思路:\color{#008000}{解题思路:}使用Map集合

import java.util.*;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        
        HashMap<Integer,Integer> map=new HashMap<>();
        
        for(int i=0;i<array.length;i++){
            if(!map.containsKey(array[i])){ //之前map中没出现过array[i],现在第一次出现,则K-V中V值赋值1
                map.put(array[i],1);
            }else{
                int count=map.get(array[i]);
                map.put(array[i],++count); //之前map中出现过array[i],则++count,
            }
            int time=map.get(array[i]);
            if(time>array.length/2)
                return array[i];
        }
        return 0;
    }
}

i++是使用i之后再加1,++i是使用i之前先使i加一
a=i++;先把i的值赋给a之后i1\color{red}{a = i++;先把i的值赋给a之后i加1}
b=++i;先i1然后把值赋给b\color{red}{b = ++i;先i加1然后把值赋给b}

import java.util.*;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
            HashMap<Integer,Integer> map=new HashMap<>(); //存储每个数字出现的次数
        int target=0;	//用来存储出现次数最多的数字
        int sum=0;	//用来存储出现次数最多的数字的出现次数
        for(int i:array){
            map.put(i,map.getOrDefault(i,0)+1);	//更新当前位置数字出现的次数
            if(sum<map.get(i)){
                sum=map.get(i);
                target=i;
            }
        }if(sum>array.length/2){
            return target;
        }return 0;
    }
}

map.getOrDefault(key,defaultValue)\color{red}{map.getOrDefault(key, defaultValue)}
当Map集合中有这个key时,就获得这个key值对应的Value,如果没有就使用默认值defaultValue

42.连续子数组的最大和

**题目描述:**输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。例如数组:arr[ ]={1, 2, 3, -2, 4, -3 } 最大子数组为 {1, 2, 3, -2, 4} 和为8

解题思路1\color{#008000}{解题思路1:}

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array==null||array.length<=0){
            return 0;
        }
        int sum=0;	
        int Max=array[0];	//Max默认array的第一个元素
        for(int i=0;i<array.length;i++){
            sum += array[i];	// 这几行代码的过程就是:通过sum变量去统计当前连续子序列的和,
            Max=Math.max(Max,sum);	//统计完之后,更新Max的值,
            if(sum<0){	//最后判断是否更新sum的值
                sum=0;	
            }
        }
        return Max;
    }
}

解题思路2\color{#008000}{解题思路2:}

public class Solution{
  		 public int FindGreatestSumOfSubArray(int[] array) {
           int[] sum = new int[array.length]; // 用来去统计0-i位置的和
           sum[0] = array[0];
           for (int i = 1; i < array.length; i++) {
               sum[i] = sum[i - 1] + array[i];
           }
           int Max = sum[0]; // 默认第一个元素
           // i是终点,j是起点
           for (int i = 0; i < array.length; i++) {
               for (int j = 0; j <= i; j++) {
                   if (j == 0) {
                       Max = Math.max(Max, sum[i]); // 说明起点在0位置
                   } else {
                       Max = Math.max(Max, sum[i] - sum[j - 1]); // i-j的和就等于从起点到i位置之和减去从起点到j-1的位置之和
                   }
               }
           }
           return Max;
       }

45.把数组排成最小的数

**题目描述:**输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323

解题思路:\color{#008000}{解题思路:}

import java.util.*;
public class Solution {
    public String PrintMinNumber(int [] numbers) {
		if(numbers==null || numbers.length<=0)
            	return "";
        ArrayList<String> list = new ArrayList<String>();
        	for(int number:numbers)
            	list.add(String.valueOf(number));	//将数组里的数字存放到 ArrayList里面
        Collections.sort(list,new Comparator<String>(){
            @Override
            public int compare(String s1,String s2){
                String a=s1+s2;
                String b=s2+s1;
                return a.compareTo(b);
            }
        });
        StringBuilder ans= new StringBuilder();
        for(String str:list)
            ans.append(str);
        return ans.toString();
    }
}

String.valueOf(inti);\color{red}{String.valueOf(int i);}
将 int 变量 i 转换成字符串
compareTo()函数\color{red}{ compareTo()函数}
在基本数据中,compareTo()是比较两个Character 对象;在 Boolean中,是用boolean的实例于其它实例进行比较;在String 中,则是按照字典顺序进行比较,返回的值是一个int 型
toString()\color{red}{toString()}

51.数组中的逆序对

**题目描述:**在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。例如,在数组{7,5,6,4}中,一共存在5个逆序对,分别是(7,5)、(7,6)、(7,4)、(5,4)、(6,4)

解题思路:\color{#008000}{解题思路:} 归并排序

53.数字在排序数组中出现的次数
**题目描述:**统计一个数字在排序数组中出现的次数。
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2

解题思路1\color{#008000}{解题思路1:} 建立一个HashMap,存放各个数字出现的次数,然后看map中是否存在k即可

import java.util.*;
public class Solution {
    public int GetNumberOfK(int [] array , int k) {
       java.util.HashMap<Integer,Integer> map=new HashMap<>();
        for(int i=0;i<array.length;i++){
            if(!map.containsKey(array[i])){
                map.put(array[i],1);
            }
            else{
                int t=map.get(array[i]);
                map.put(array[i],t+1);
            }
        }
        if(map.containsKey(k)){
            return map.get(k);
        }
        return 0;
    }
}

解题思路2\color{#008000}{解题思路2:} 一般情况下,排序数组中的查找都可以通过二分查找来解决

通过二分查找查找k,分别找到第一次出现的位置和最后一次出现的索引位置。
通过k出现的第一和最后的位置直接计算出现次数即可,last - first +1
public class Solution {
    private int findFirstPosition(int[] array, int k) {	//查找k的起点位置
        int l = 0;
        int r = array.length - 1;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (array[mid] == k) {
                if(mid - 1 >= 0 && array[mid - 1] == k) {
                    // 说明mid当前的位置不是初始位置,k的初始位置是在l~mid-1区间
                    r = mid - 1;
                } else {
                    // 就可以说明mid位置的数字就是k的初始位置
                    return mid;
                }
            } else if (array[mid] > k) {
                r = mid - 1; // k是属于l~mid-1区间
            } else {
                l = mid + 1; // k是属于mid+1~r区间
            }
        }
        return l;
    }

    private int findLastPosition(int[] array, int k) {	//查找k的终点位置
        int l = 0;
        int r = array.length - 1;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (array[mid] == k) {
                if(mid + 1 < array.length && array[mid + 1] == k) {
                    // 说明mid当前的位置不是终止位置,k的初始位置是在mid+1~r区间
                    l = mid + 1;
                } else {
                    // 就可以说明mid位置的数字就是k的终止位置
                    return mid;
                }
            } else if (array[mid] > k) {
                r = mid - 1; // k是属于l~mid-1区间
            } else {
                l = mid + 1; // k是属于mid+1~r区间
            }
        }
        return l;
    }

    public int GetNumberOfK(int [] array , int k) {
        if (array.length == 0) {
            return 0;
        }
        int firstPosition = findFirstPosition(array, k);
        int lastPosition = findLastPosition(array, k);
        
        if (array[firstPosition] != k) {	//判断array里面是否存在k
            return 0;
        }
        return lastPosition - firstPosition + 1;
    }
}

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

**题目描述:**一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字
输入: nums = [2,4,3,6,3,2,5,5]
输出: [4,6]

解题思路:\color{#008000}{解题思路:}

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]。不能使用除法。

解题思路:\color{#008000}{解题思路:}
这道题就是要求结果数组上的每一个数,都是原数组除了这个位置的数的乘积; 先把每一个元素的左边的乘积和右边的乘积都算出来,然后新的数组的元素就等于这个元素左边的乘积加上右边的乘积;

public class Solution {
    public static int[] multiply(int[] A) {
        int[] f1 = new int[A.length]; // 0到i-1的乘积
        int[] f2 = new int[A.length]; // i+1到n-1的乘积

        int ans1 = 1; // 0-(i-1)的乘积
        int ans2 = 1; // (i+1)-n-1的乘积
        for (int i = 0, j = A.length - 1; i < A.length; i++, j--) {
            f1[i] = ans1;
            ans1 *= A[i];

            f2[j] = ans2;
            ans2 *= A[j];
        }
        int[] B = new int[A.length];
        for (int i = 0; i < A.length; i++) {
            B[i] = f1[i] * f2[i];
        }
        return B;
    }
}