剑指offer 数组(矩阵)

数组的考点

a. 时间复杂度和空间复杂度分析;

b. 八大排序算法;

c. 双指针的思想;

d. 二分查找;

e. 动态规划;

f. 深度优先遍历。

一维

0.数

0.1 求两个数的最大公因数:欧几里得算法;

0.2 求两个数是不是互素;

0.3 求小于N的随机选择两个互异的正整数互素的概率;

1. 排序及数组规律

快速排序(双指针的思想),归并排序,堆排序,时间复杂度及分析。

 int Partition(int[] input,int k,int low,int high){
        int pivotkey = input[k-1];
        swap(input,k-1,low);
        while(low < high){
            while(low < high && input[high] >= pivotkey)
                high--;
            swap(input,low,high);
            while(low < high && input[low] <= pivotkey)
                low++;
            swap(input,low,high);
        }
        return low;
    }
 
    private void swap(int[] input, int low, int high) {
        int temp = input[high];
        input[high] = input[low];
        input[low] = temp;
    }
基于Partition函数实现的快排

public void QuickSort(int[] input,int len,int start,int end){
		if(start==end){
			return;
		}
		int index=Partition(input,len,start,end);
		if(index>start)
			QuickSort(input,len,start,index-1);
		if(index<end)
			QuickSort(input,len,index+1,end);
	}

1.1 题目(面试题29):数组中出现次数超过一半的数字。

解法一:基于Partition函数的实现方法

<pre name="code" class="java">
 
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array.length == 0 || array==null)  
            return array;  
        int low = 0;  
        int high = array.length-1;  
        int mid=array.length>>1;
        int index = Partition(array,mid,low,high);  
        while(index != mid){  
            if (index > mid) {               
                index = Partition(input,mid,low,index-1);  
            }else{  
                index = Partition(input,mid,index+1,high);  
            }  
        }  
        return array[middle];  
    }
}

解法二:根据数组特点的O(n)算法

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array.length == 0 || array==null)  
            return 0;  
        int temp=array[0];
        int times=1;
        for(int i=1;i<array.length;i++){
            if(array[i]==temp)
                times++;
            else if(times!=0)
                times--;
            else{
                temp=array[i];
                times=1;
            }
        }
        if(!checkMoreThanHalf(array,temp))
            temp=0;
        return temp;
    }
    
    private boolean checkMoreThanHalf(int[] array, int num){
        int count=0;
        for(int i=0;i<array.length;i++){
            if(array[i]==num)
                count++;
            if(count*2>array.length)
                return true;
        }
        return false;
    }
}

1.2 题目(面试题30):数组中最小的k个数。

解法一:快排或者堆排序,O(nlongn)的时间复杂度;

解法二:O(n)的时间复杂度,但是会修改输入的数组;

解法三:维护一个大小为k的大顶堆,特别适合海量数据的处理。

//解法二的代码
import java.util.ArrayList;
public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList aList = new ArrayList();
        if(input.length == 0 || k > input.length || k <= 0)
            return aList;
        int low = 0;
        int high = input.length-1;
        int index = Partition(input,k,low,high);
        while(index != k-1){
            if (index > k-1) {
                index = Partition(input,k,low,index-1);
            }else{
                index = Partition(input,k,index+1,high);
            }
        }
        for (int i = 0; i < k; i++)
            aList.add(input[i]);
        return aList;
    }
}

1.3 题目(面试题36):数组中的逆序对。考察归并排序。

import java.util.Arrays;  
   
publicclass mergingSort {  
   
inta[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,99,98,54,56,17,18,23,34,15,35,25,53,51};  
   
publicmergingSort(){  
    sort(a,0,a.length-1);  
    for(int i=0;i<a.length;i++)  
       System.out.println(a[i]);  
}  
   
publicvoid sort(int[] data, int left, int right) {  
    // TODO Auto-generatedmethod stub  
    if(left<right){  
        //找出中间索引  
        int center=(left+right)/2;  
        //对左边数组进行递归  
        sort(data,left,center);  
        //对右边数组进行递归  
        sort(data,center+1,right);  
        //合并  
        merge(data,left,center,right);         
    }  
   
}  
   
publicvoid merge(int[] data, int left, int center, int right) {  
    // TODO Auto-generatedmethod stub  
    int [] tmpArr=newint[data.length];  
    int mid=center+1;  
    //third记录中间数组的索引  
    int third=left;  
    int tmp=left;  
    while(left<=center&&mid<=right){  
        //从两个数组中取出最小的放入中间数组  
        if(data[left]<=data[mid]){  
            tmpArr[third++]=data[left++];  
        }else{  
            tmpArr[third++]=data[mid++];  
        }  
   
    }  
   
    //剩余部分依次放入中间数组  
    while(mid<=right){  
        tmpArr[third++]=data[mid++];  
    }  
   
    while(left<=center){  
        tmpArr[third++]=data[left++];  
    }  
   
    //将中间数组中的内容复制回原数组  
    while(tmp<=right){  
        data[tmp]=tmpArr[tmp++];  
    }  
    System.out.println(Arrays.toString(data));  
}  
}

2.快排双指针思想

2.1 题目(面试题14):调整数组顺序使奇数位于偶数前面

解法一:插入排序的思路,O(n*n)的时间复杂度,O(1)的空间复杂度;

解法二:空间换时间的思路,分别记录奇数和偶数,然后合并,时间和空间复杂度都是O(n),但是需要遍历两次,这种解法可以保证每一部分的顺序不变;

解法三:快排的思路,即用双指针,O(n)的时间复杂度,O(1)的空间复杂度,但是这种解法可能改变奇偶部分的顺序,需要跟面试官沟通好;

解法四:如果能够考虑到扩展性,就牛逼了,因为分奇偶只是一个规则,可以有很多其他规则。

2.2 题目(面试题41):和为s的两个数字VS和为s的连续整数序列

import java.util.ArrayList;  
public class Solution {  
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {  
           int len=array.length;  
            if((array==null)&&(len==0))  
                return null;  
            int result=999999;  
            int r1=0,r2=0,a=0;  
            for(int i=0;i<len-1;i++){  
                //if(array[i]<sum){  
                    int temp=array[i];  
                    for(int j=i+1;j<len;j++){  
                        if(array[j]==sum-temp){  
                            a=temp*array[j];  
                            if(a<result){  
                                result=a;  
                                r1=array[i];  
                                r2=sum-r1;  
                            }  
                            break;  
                        }  
                    }  
                //}  
            }  
            ArrayList<Integer> res=new ArrayList<Integer>();  
            if((r1!=0)&&(r2!=0))  
            {res.add(r1);  
            res.add(r2);  
            }  
            return res;  
    }      
}  

解法一:双指针

2.3 快排的优化方法

思路一:中轴的选择

解法一:三平均分区法,选择待排数组最左边,最右边,最中间的三个元素中间值作为中轴;

解法二:随机化快排法,随机选取一个元素作为中轴,依赖随机函数;

思路二:分区大小定策略,因为快排采用了分治技术,当数据集较小的时候效果不好,考虑分区策略;

解法一:数据集小的时候采用堆排序;

解法二:对几乎排序完成的有序数列,采用插入排序;

思路三:分区方案,当有很多重复元素的时候

解法一:分成三块儿,而不是两部门,减少递归调用深度;

思路四:并行快排,因为快排采用了分治技术

解法一:因为创建一个线程所需要的时间要远远大于两个元素比较和交换的时间,根据待排数组的数目判断是否创建新线程。

3. 二分查找

3.1 题目(面试题38):数字在排序数组中出现的次数

解法一:双指针;

解法二:二分查找加强版,找到重复数字的第一个和最后一个。

3.2 题目(面试题8):旋转数组的最小数字,输入是递增排序数组的一个旋转,输出是最小数。

解法一:严格递增严格旋转的情况,严格递增非严格旋转的情况,非严格递增的情况,双指针思想的二分查找。

import java.util.Arrays;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array==null || 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){
                mid=end;
                break;
            }
            mid=(start+end)/2;
            if(array[mid]==array[start] && array[mid]==array[end]){
                return minInOrder(array,start,end);
            }
            if(array[mid]>=array[start])
                start=mid;
            else if(array[mid]<=array[end])
                end=mid;
        }
        return array[mid];
    }
    private int minInOrder(int[] array,int start,int end){
        int result=array[start];
        for(int i=start+1;i<=end;i++){
            if(array[i]<result){
                result=array[i];
                break;
            }
        }
        return result;
    }
}

4. 动态规划

4.1 题目(面试题31):连续子数组的最大和。

解法一:暴力枚举解法;

解法二:二分递归解法;

解法三:动态规划解法高级。

public class Solution {  
    public int FindGreatestSumOfSubArray(int[] array) {  
        if(array==null || array.length==0)  
            return 0;  
        int tempsum=array[0];  
        int sum=array[0];  
        for(int i=1;i<array.length;i++){  
            tempsum=(tempsum<0)?array[i]:tempsum+array[i];  
            sum=sum<tempsum?tempsum:sum;  
        }  
        return sum;  
    }  
}  

4.2 背包问题(0/1背包):

public static int knapSack(int[] val, int[] wt, int w){
		int len=val.length;
		int[][] v=new int[len+1][w+1];
		for(int col=0;col<=w+1;col++){
			v[0][col]=0;
		}
		for(int row=0;row<len+1;row++){
			v[row][0]=0;
		}
		for(int row=1;row<len+1;row++){
			for(int col=1;col<w+1;col++){
				if(wt[row-1]<col){
					v[row][col]=Math.max(val[row-1]+v[row-1][col-wt[row-1]], v[row-1][col]);
				}
				else{
					v[row][col]=v[row-1][col];
				}
			}
		}
		
		for(int[] rows:v){
			for(int col:rows){
				System.out.format("%5d", col);
			}
			System.out.println();
		}
		
		return v[len][w];
	}

4.3 硬币问题

4.4 最长非降子序列的长度

public static int lic(int[] nums){
		int len=nums.length;
		int[] d=new int[len];
		for(int i=1;i<len;i++){
			int num=1;
			for(int j=0;j<i;j++){
				if(nums[j]<=nums[i]){
					num=(num<=(d[j]+1))?d[j]+1:num;
				}
			}
			d[i]=num;
		}
		return d[len-1];
	}

4.5 斐波那契序列(面试题8)

解法一:递归,效率很低,可以用数来说明有很多重复调用;

解法二:递推,记录之前两次的值,递推,DP的思想。

5. 辅助数据结构

5.1 题目(面试题65):滑动窗口的最大值。

5.2 题目(面试题33):把数组排成最小的数(大数问题,需要辅助的数据结构字符串)

public static String PrintMinNumber(int [] numbers) {  
        int n;  
        String s="";  
        ArrayList<Integer> list= new ArrayList<Integer>();  
        n=numbers.length;  
        for(int i=0;i<n;i++){  
            list.add(numbers[i]);        
        }  
        Collections.sort(list, new Comparator<Integer>(){  
    
            public int compare(Integer str1,Integer str2){  
                String s1=str1+""+str2;  
                String s2=str2+""+str1;  
                return s1.compareTo(s2);  
            }  
        });  
    
        for(int j:list){  
            s+=j;  
        }  
        return s;  
    }  
 

5.3 题目(面试题52):构建乘积数组

解法一:借助二维矩阵优化

6. 位运算

6.1 题目(面试题40):数组中只有一个出现一次的数字,其他的数字都出现了两次,请查找;

解法一:利用HashMap统计次数,然后查找;

解法二:异或运算(性能最好)

public static int onceInt(int[] nums){
		int num=0;
		for(int i=0;i<nums.length;i++){
			num=nums[i]^num;
		}
		return num;
	}
6.2 题目(面试题41):数组中有两个只出现一次的数字,其他的数字都出现了两次,请查找;

public static int isBit1(int num,int bitnum){
		num=num>>bitnum;
		return (num & 1);
	}
	
	public static int FindFirstBitIs1(int num){
		int bitnum=0;
		while((num&0)==0){
			num=num>>1;
				bitnum++;
		}
		return bitnum;
	}
6.3 题目(面试题51):数组中重复的数字

解法一:先排序后遍历;

解法二:利用HashMap统计次数,但是空间复杂度是O(n);

解法三:冒泡排序过程的优化,就是说必须得理解8大排序的精华;

矩阵

1. 查找

题目(面试题3):二维数组中的查找。矩阵中每一行按照从左到右递增的顺序排序,每一列按照从上到下递增的顺序牌靴。

解法一:蛮力寻找,时间复杂度O(mn),但是没有考虑数组有序的特性。

解法二:二分查找,时间复杂度O(m+n),双指针的思想。

public class Solution {
    public boolean Find(int [][] array,int target) {
		if(array==null)
            return false;
        int r=array.length-1;
        int c=0;
        while(r>=0 && c<array[0].length){
            if(array[r][c]==target){
                return true;
            }
            else if(array[r][c]>target){
                r--;
            }
            else{
                c++;
            }
        }
        return false;
    }
}

2. 遍历

题目(面试题20):顺时针打印矩阵。

3.回溯法(深度优先)

题目(面试题66):矩阵中的路径。

题目(面试题67):机器人的运动范围。

题目:走迷宫(多少种走法)。

海量数据

题目(面试题64):数据流中的中位数。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值