经典算法题解析(二分查找、排序)

1、二分查找
给定一个 元素升序的、无重复数字的整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标(下标从 0 开始),否则返回 -1

    public int search (int[] nums, int target) {
        int l = 0;
        int r = nums.length - 1;
        //从首尾边界开始直到二者相遇
        while(l <= r){ 
            //每次检查的中点值
            // int m = l + (l - r);
            int m = (l + r) / 2; 
            if(nums[m] == target)
                return m;
            //目标值小于中间值,目标值一定在左区间,缩小右边界
            if(nums[m] > target) 
                r = m - 1;
            //目标值大于中间值,目标值一定在右区间,缩小左边界
            else 
                l = m + 1;
        }
        //未找到
        return -1; 
    }

解析:
划分左右区间,使目标值和中间值进行比较,从而判断目标值在哪个区间内,然后缩小区间继续划分重复操作直到找到目标值

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

方法一:暴力搜索

public boolean Search(int[][] arr,int target){
    row=arr.length;
    //总列数 arr[行][列]
    col=arr[0].length;
    for (int i=0;i<row;i++) {
        for (int j=0;j<col;j++) {
            if(arr[i]][j]==target){ 
                return true;
            }
        }   
    }
    return false;
}

解析:按照从上到下从左到右依次搜索,直到找到目标值

方法二:线性搜索

    public boolean Find(int target, int [][] array) {
        //优先判断特殊
        if(array.length == 0)  
            return false;
        int n = array.length;
        if(array[0].length == 0)  
            return false;
        int m = array[0].length;
        //从最左下角的元素开始往左或往上
        for(int i = n - 1, j = 0; i >= 0 && j < m; ){ 
            //元素较大,往上走
            if(array[i][j] > target)   
                i--;
            //元素较小,往右走
            else if(array[i][j] < target) 
                j++;
            else
                return true;
        }
        return false;
    }
}

解析:
因为此二维数组是行列递增,所以每一行的最后一位数字大于前面所有,每一列最后一位大于前面所有。将搜索起始位置设定在矩阵的右上角或者左下角,当起始为左下角时,若元素较大则往上走;元素较小则往右走,直到找到目标值

方法三:二分搜索

public boolean binarySearch(int[][] array) {
    for (int i = 0; i < m; i++) {
        int l = 0, r = n - 1;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (array[i][mid] == target) {
                return true;
            } else if (array[i][mid] < target) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
    }
    return false;
}

利用二分搜索,从上到下每一行依次进行二分搜索,直到找到目标值

3、寻找峰值
给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可

若数组相邻元素不相等,那么就有且仅有以上四种情况,每种情况都存在峰值

    import java.util.*;
public class Solution {
    public int findPeakElement (int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        //二分法
        while(left < right){ 
            //防止直接相加发生溢出
            int mid = ((right - left) >> 1) + left;
            //右边是往下,不一定有坡峰
            if(nums[mid] > nums[mid + 1])
                right = mid;
            //右边是往上,一定能找到波峰
            else
                left = mid + 1;
        }
        //其中一个波峰
        return right; 
    }
}

解析:
nums[mid] < nums[mid + 1]说明在“上坡”,则可以使left = mid + 1(mid肯定不是峰值),向“峰”处压缩
nums[mid] > nums[mid + 1]说明在“下坡”,则应该使right = mid(mid可能是峰值),往“峰”处压缩

4、寻找数组中的逆序对
方法一:暴力解法

public class Solution {

    public int InversePairs(int[] array) {
        int cnt = 0;
        int len = array.length;
        for (int i = 0; i < len - 1; i++) {
            for (int j = i + 1; j < len; j++) {
                if (array[i] > array[j]) {
                    cnt++;
                }
            }
        }
        return cnt;
    }
}

解析:
选择排序的方式使当前元素与其后面的元素依次进行比较

方法二:归并统计


public class Solution {
    int count = 0;
    public int InversePairs(int [] array) {
        // 长度小于2则无逆序对
        if(array.length < 2)
            return 0;
        // 进入归并
        mergeSort(array,0,array.length-1);
        return count;
    }

    public void mergeSort(int[] array,int left,int right){
        // 找分割点
        int mid = left+(right-left)/2;
        if(left < right){
            // 左子数组
            mergeSort(array,left,mid);
            // 右子数组
            mergeSort(array,mid+1,right);
            // 并
            merge(array,left,mid,right);
        }
    }

    public void merge(int[] array,int left,int mid,int right){
        // 创建临时数组,长度为此时两个子数组加起来的长度
        int[] arr =  new int[right-left+1];
        // 临时数组的下标起点
        int c = 0;
        // 保存在原数组的起点下标值
        int s = left;
        // 左子数组的起始指针
        int l = left;
        // 右子数组的起始指针
        int r = mid+1;
        while(l <= mid && r <= right ){
            // 当左子数组的当前元素小的时候,跳过,无逆序对
            if(array[l] <= array[r]){
                // 放入临时数组
                arr[c] = array[l];
                // 临时数组下标+1
                c++;
                // 左子数组指针右移
                l++;
            }else{ // 否则,此时存在逆序对
                // 放入临时数组
                arr[c] = array[r];
                // 逆序对的个数为    左子数组的终点- 当前左子数组的当前指针+1
                count += mid-l+1;
                count %= 1000000007;
                // 临时数组+1
                c++;
                // 右子数组的指针右移
                r++;
            }
        }

        // 左子数组还有元素时,全部放入临时数组
        while(l <= mid)
            arr[c++] = array[l++];
        // 右子数组还有元素时,全部放入临时数组
        while(r <= right)
            arr[c++] = array[r++];
        // 将临时数组中的元素放入到原数组的指定位置
        for(int num:arr){
            array[s++] = num;
        }
    }
}

解析:
利用归并排序,在合并的时候会进行比较判断,若左边元素小于右边元素则算作逆序对。
在合并 {4 ,5} {1 , 2} 的时候,首先我们判断 1 < 4,我们即可统计出逆序对为2,为什么呢?这利用了数组的部分有序性。因为我们知道 {4 ,5} 这个数组必然是有序的,因为是合并上来的。此时当 1比4小的时候,证明4以后的数也都比1大,此时就构成了从4开始到 {4,5}这个数组结束,这么多个逆序对(2个),此时利用一个临时数组,将1存放起来,接着比较2和4的大小,同样可以得到有2个逆序对,于是将2也放进临时数组中,此时右边数组已经完全没有元素了,则将左边剩余的元素全部放进临时元素中,最后将临时数组中的元素放进原数组对应的位置。

5、旋转数组的最小数字
有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值

方法一:暴力搜索

public int minNumberInRotateArray(int[] arr){
    if(arr.length<=0) return null;
    int res=arr[0];
    for(int i=1;i<arr.length;i++) {
        int res=math.min(arr[i],res);
    }
    return res;
}

解析:
从前向后遍历找出最小数字

方法二:二分法

public min minNumberInRotateArray(int[] arr){
    int left=0;
    int right=arr.length-1;
    while(left<right){
        int mid=(left+right)/2;
        if(arr[mid]>arr[right]){
            left=mid+1;
        //若区间中点值小于区间右界值,最小值一定在中点左边
        }else if(arr[mid]<arr[right]){
            right=mid;
        }else{
            // right=right-1;
            right--;
        }
    }
}

解析:
旋转后无序的点就是最小的数字,
1、若区间中点值大于区间右界值,最小值一定在中点右边
2、若区间中点值小于区间右界值,最小值一定在中点左边
3、若区间中点值等于区间右界值,无法判断,逐个缩小右界

6、快速排序

public class QuickSort{
    public static void main(String[] args) {
        int[] nums={7,4,5,2,8,0,11};
        System.out.println(Arrays.toString(nums));
        quickSort(nums,0,nums.length-1);
        System.out.println(Arrays.toString(nums));
    }
    public static void quickSort(int[] arr,int left,int right){
        if(left>right) return;
        int i,j,pivot;
        i=left;
        j=right;
        pivot=arr[left];
        while(i<j){
            while(i<j && arr[j]>=pivot) j--;
            while(i<j && arr[i]<=pivot) i++;
            if(i<j){
                swap(arr,i,j);
            }
        }
        swap(arr,left,j);
        quickSort(arr,left,j-1);
        quickSort(arr,j+1,right);
    }
    public static void swap(int[] arr,int left,int right){
        int temp=arr[left];
        arr[left]=arr[right];
        arr[right]=temp;
    }
}

解析:
1、pivot=arr[left]每一轮都以数组中第一个数作为轴点
2、while(i<j && arr[j]>=pivot) j--从最左开始每个数依次和轴点进行比较,要求左边所有数只要大于轴点就准备进行交换
3、while(i<j && arr[i]<=pivot) i++从最右开始每个数依次和轴点进行比较,要求右边所有数只要小于轴点就准备进行交换
4、swap(arr,i,j)左右进行交换,swap(arr,left,j)轴点归位
5、quickSort(arr,left,j-1)quickSort(arr,j+1,right)循环递归,快速排序的每一轮处理其实就是将这一轮的轴点归位,直到所有的数都归位为止

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352

推荐阅读更多精彩内容