《剑指 Offer》 查找:二分查找、哈希表、二叉搜索树

0前言

  • 11 旋转数组的最小数字
  • 53 数字在排序数组中出现的次数
  • 50 第一个只出现一次的字符

查找算法

二分查找

11 旋转数组的最小数字

题目描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。 
解题思路: 两个排序的数组,不能纯粹的二分查找(要求整体排序)。

1.取数组中间数,若中间的数大于等于数组第一个数,说明中间数位于前半个非递减数组中,因此最小值在数组的后半部分。 
2.若中间数小于等于数组最后一个数,说明中间数位于后半个非递减数组中,因此最小值在数组的前半部分 

3.直到指向数组前和后的指针下标相邻时,后面的指针对应数组的最小数字。 

循环

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if(rotateArray.empty())
            return 0;
        
        int low=0,high=rotateArray.size()-1,mid=low;
        // 特殊情况:1. 没有旋转 2. 出现重复
        while(rotateArray[low]>=rotateArray[high]){//确保是旋转的,如果没有旋转最小的就是第一个
            mid=low+(high-low)/2;
            //第一个指针low总是指向前面递增数组的元素,第二个指针high总是指向后面递增的数组元素。
            // 最终指向两个相邻的元素,而第二个指针指向的刚好是最小的元素,这就是循环的结束条件。
            if(high-low==1){
                mid=high;
                break;
            }
            if(rotateArray[mid]>=rotateArray[low])
                low=mid;// 这里没有mid+1因为low=mid第一个指针仍然位于前半数组中
            else if(rotateArray[mid]<=rotateArray[high])
                high=mid;// high=mid第二个指针仍位于后半数组中
        } 
        return rotateArray[mid];
    }
};

 法二:

public class Solution {
    public int minNumberInRotateArray(int [] array) {
        int low = 0 ; int high = array.length - 1;   
        while(low < high){
            int mid = low + (high - low) / 2;        
            if(array[mid] > array[high]){
                low = mid + 1;
            }else if(array[mid] == array[high]){
                high = high - 1;
            }else{
                high = mid;
            }   
        }
        return array[low];
    }
}

严重注意:high=mid没关系,但low=mid有可能导致死循环。因为low=high-1时low=mid,如果又进入low=mid分支相当于搜索区间没缩小。但high一定大于mid,所以不会造成死循环。 

根本原因:[4,6] array[low] = 4 ;array[mid] = 4 ; array[high] = 6 ;mid=(high+low)/2 偏向于左侧。

53 数字在排序数组中出现的次数

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

思路:排序数组,考虑二分查找。分别找到该数第一次和最后一次出现的位置。

方法一:

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        //搜索k-0.5和k+0.5这两个数应该插入的位置,然后相减即可。
        return fun(data, k+0.5) - fun(data, k-0.5) ;
    }
    
    int fun(const vector<int> & data, double num){
        int low = 0, high = data.size()-1,mid=low;
        while(low <= high){
            mid = low+(high-low)/2;
            if(data[mid] < num)
                low = mid + 1;// 这边用+1,1 最后low=high=mid,再来一次3>2.5,high-1;
            else if(data[mid] > num)
                high = mid - 1;
        }
        return low;// 不是用的mid
    }
};

方法二:(递归+循环)

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        int len=data.size();
        if(len==0)
            return 0;
        
        int firstk=getfirst(data,k,0,len-1);
        int lastk=getlast(data,k,0,len-1);
        if(firstk!=-1&&lastk!=-1){
            return lastk-firstk+1;
        }
        return 0;
    }
    // 递归
    int getfirst(vector<int> data ,int k,int low,int high){
        if(low>high)
            return -1;// 找不到
        int mid=low;
        mid=low+(high-low)/2;
        if(data[mid]==k){
            if(mid>=0&&data[mid-1]!=k)
                return mid;
            else
                high=mid-1;
        }
        else if(data[mid]>k)
            high=mid-1;
        else if(data[mid]<k)
            low=mid+1;
        return getfirst(data,k,low,high);//递归,最终要继续这个函数
    }
    // 循环
    int getlast(vector<int> data ,int k,int low,int high){
        int len=data.size();
        int mid=low;
        while(low<=high){//循环<=
            mid=low+(high-low)/2;
            if(data[mid]==k){// mid和k相等,
                if(mid+1<=len&&data[mid+1]==k)
                    low=mid+1;//循环里要让low一直向后走
                else
                    return mid;//终止条件
            }
            else if(data[mid]<k)
                low=mid+1;
            else if(data[mid]>k)
                high=mid-1;
        }
        return -1;//找不到,或错误输入
    }
    
};

哈希表

50 第一个只出现一次的字符

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

法一:利用map 

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        map<char,int> mp;// 构造map
        for(int i=0;i<str.size();i++)
            mp[str[i]]++;// 第一遍遍历,更新键值key与value
        for(int i=0;i<str.size();i++){
            if(mp[str[i]]==1)// 第二遍查找
                return i;
        }
        return -1;
    }
};

法二:数组 []

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        if(str.length()==0)
            return -1;
        int hash[256]={0};
        // 第一次遍历,key-value
        int i=0;
        while(str[i]!='\0'){
            hash[str[i]]++;
            i++;
        }
        // 第二次查找
        i=0;
        while(str[i]!='\0'){
            if(hash[str[i]]==1){
                return i;
            }
            i++;
        }
        return -1;
    }
};
发布了176 篇原创文章 · 获赞 84 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/try_again_later/article/details/90813179