剑指offer刷题记录--算法--搜索算法

1. JZ53 数字在升序数组中出现的次数

在这里插入图片描述

1. 自己的二分查找,直接查找k

题目很简单,使用二分查找即可,我的想法很普通,直接找k的边界,然后相减,求得出现的次数。

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        if(data.size()==0) return 0;
        int left = 0;
        int right = data.size()-1;
        int mid = (left+right)/2;
        int Res = 0;
        while(data[mid]!=k){
            if(data[mid]>k){
                right = mid-1;
                mid = (left+right)/2;
            }
            else if(data[mid]<k){
                left = mid+1;
                mid = (left+right)/2;
            }
            if(left>right) return 0;
        }
        int i=mid;
        int j=mid;
        for(; i>=left || j<=right; ){
            if(data[i]==k)  --i;
            if(data[j]==k)  ++j;
            if(data[i]!=k && data[j]!=k) break;
        }
        return j-i-1;
    }
};

2. 官方查找k±0.5的Trick(推荐)

在这里插入图片描述

由于数据都是整数,所以可以找浮点数k+0.5和k-0.5,注意返回的都是左边界,然后相减即可。

class Solution {
public:
    int BiSearch(vector<int> data, float k){
        int left = 0;
        int right = data.size()-1;
        int mid = 0;
        while(left<=right){
            mid = (left+right)/2;
            if(data[mid]>k)        right = mid-1;class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {      
        if(array.size()==0 || array[0].size()==0) return false;
        int mid = 0;
        for(int row=0; row<array.size(); ++row){
            int left = 0;
            int right = array[0].size()-1;
            if(array[row][left]<=target && array[row][right]>=target){
                while(left<=right){
                    mid = (left+right)/2;
                    if(array[row][mid]>target) right = mid-1;
                    else if(array[row][mid]<target) left = mid+1;
                    else return true;
                }
            }
        }
        return false;
    }
};
            else if(data[mid]<k)    left = mid+1;
        }
        return left;  //返回left或者right都对
    }
    
    int GetNumberOfK(vector<int> data ,int k) {
        return BiSearch(data, k+0.5) - BiSearch(data, k-0.5);
    }
};

正常的二分查找:
在这里插入图片描述

3. 本题小结

  1. 二分查找的时间复杂度为 l o g ( N ) log(N) log(N)
  2. 可以查找刚好大于且刚好小于待查数据的位置,因为查不到,所以最后的结果是刚好左右边界都越界,但是返回left或者right都对,相减即可。
    在这里插入图片描述

2. JZ11 旋转数组的最小数字

1. 官方解法(推荐,唯手熟尔)

循环条件中不能加等号,相等才跳出的,证明找到了,所以返回data[right]和data[left]都对。

思想还是二分法查找,

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        int left = 0;
        int right = rotateArray.size() - 1;
        while(left < right){  //这个不能加等号,l=r就证明找到了最小值,应直接输出
            int mid = (left + right) / 2;
            //最小的数字在mid右边
            if(rotateArray[mid] > rotateArray[right]) 
                left = mid + 1;
            //无法判断,一个一个试
            else if(rotateArray[mid] == rotateArray[right]) 
                right--;
            //最小数字要么是mid要么在mid左边
            else 
                right = mid;
        }
        return rotateArray[left];  //返回right也行,因为都是相等时才跳出的
    }
};

2. 暴力解法

找到第一个data[i]>data[i+1]的,return data[i+1]
时间复杂度 O ( N ) O(N) O(N),空间复杂度 O ( 1 ) O(1) O(1)

class Solution {
public:   
    int minNumberInRotateArray(vector<int> rotateArray) {
        int min = rotateArray[0];
        for(int i=0;i<rotateArray.size()-1;++i){
            if(rotateArray[i]>rotateArray[i+1]){
                return rotateArray[i+1];
            }
        }
        return min;
    }
};

3. 本题小结

  1. 主要是含有相等元素的时候不好处理,只能一个个找,唯手熟尔。

3. JZ44 数字序列中某一位的数字

在这里插入图片描述
题目意思就是数据就是按照0123456789101112…这样来排列的,求第n位,不需要原数据,直接可以求第n位。
在这里插入图片描述

1. 位数相减

统计累积到n位数总共有多少位,如果超过了n,则在n位数范围内。

class Solution {
public:
//     num是内置的字符串
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int整型 
     * @return int整型
     */
    int findNthDigit(int n) {
        // write code here
        int digit = 0;
        long long sum = 0;
        if(n<10) return n;
        while(true){
            ++digit;
            //计算该位数的数字区间的总数字位数
            if(digit==1) sum=10;
            else sum=digit * (9 * pow(10, digit-1));
            //找到了第n位是多少位数的区间
            if(n-sum<0){
                unsigned int quotient = n/digit;
                unsigned int remainder = n%digit;
                unsigned int target_num = pow(10, digit-1) + quotient;
                string str{to_string(target_num)};
                int result = str[remainder]-'0';
                return result;
            }
            else n-=sum;
        }
    }
};

2. 补0法(推荐)

简单易操作。记住15这个例子。
在这里插入图片描述

class Solution {
public:
    int findNthDigit(int n) {
        //记录n是几位数
        int i = 1; 
        while(i * pow(10, i) < n)  //每次加完0之后都是i*pow(10,i)个数
        {
            //前面添0增加的位
            n += pow(10, i);  //加10^i而非(i-1)
            i++;
        }
        //根据除法锁定目标数字,根据取模锁定位置
        return to_string(n / i)[n % i] - '0';
    }
};

1. 本题小结

  1. 推荐补零法,好操作。添加0的个数需要琢磨。

4. JZ4 二维数组中的查找

在这里插入图片描述

1. 我的暴力做法

没有什么技巧,判断每行第一个和最后一个是否符合要求,如果min<=target && max>=target就对这行进行二分查找,如果找到就true,如果没找到就继续下一个循环,若到最后都没找到的话,就false。
对于下图所示的情况,还是基本上每行都要找,效率不高。
在这里插入图片描述

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {      
        if(array.size()==0 || array[0].size()==0) return false;
        int mid = 0;
        for(int row=0; row<array.size(); ++row){
            int left = 0;
            int right = array[0].size()-1;
            if(array[row][left]<=target && array[row][right]>=target){
                while(left<=right){
                    mid = (left+right)/2;
                    if(array[row][mid]>target) right = mid-1;
                    else if(array[row][mid]<target) left = mid+1;
                    else return true;
                }
            }
        }
        return false;
    }
};

2. 官方分析法

根据分析,每行第一个最小,最后一个最大,那么左上角的最小,右下角的最大,可以得到:
左下角>左上角,左下角<右下角
官方思路从左下角开始找,若该值<target,则列数++,若该值>target,则行数–,最后在不越界的情况下如果找到相等的,则true,否则false。
如动图所示
我仿照这个思想从右上角开始找,若该值<target,则行数++,若该值>target,则列数–

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {      
        if(array.size()==0 || array[0].size()==0) return false;
        int row = 0;
        int colum = array[0].size()-1;
        while(row<=array.size()-1 && colum >=0){
            if(array[row][colum]<target) ++row;
            else if(array[row][colum]>target) --colum;
            else return true;
        }
        return false;
    }
};

3. 本题小结

  1. 本题使用了数组递增的特性,我理解实际上就是用这个特性找找到这个值的最短路径。

5. JZ38 字符串的排列

在这里插入图片描述

1. 自己的递归

思想:
对于每个段,交换第一个和后面的一个,然后对交换后的后面的进行递归,直到递归到只有一个字符时,然后就开始返回,每次都返回递归的子段的的结果vector,返回回来后哦,将第一个和所有结果相加,再返回。
时间复杂度 O ( N ! ) O(N!) O(N!),因为对每个子段都要递归,空间复杂度也为 O ( N ! ) O(N!) O(N!)

class Solution {
public:
    vector<string> Recursion(string str){
        if(str.size()==1) return vector<string>{str};
        vector<string> Res{};
        for(int i=0; i<str.size();++i){
            string TmpStr = str;  //回溯
            if(TmpStr[0]==TmpStr[i] && i!=0) continue;  //与首字母相同的不交换
            if(TmpStr[i]==TmpStr[i-1]) continue;  //连续与前面相同的不交换
            //交换首字母与后面的,然后递归后面的
            auto Tmp = TmpStr[0];
            TmpStr[0] = TmpStr[i];
            TmpStr[i] = Tmp;
            vector<string> TmpRes = Recursion(TmpStr.substr(1, TmpStr.size()-1));
            for(auto each:TmpRes)
                Res.push_back(TmpStr[0]+each);
        }
        return Res;
    }
    
    vector<string> Permutation(string str) {
        vector<string> Res{};
        if(str.size()==0) return Res;
        return Recursion(str);
    }
};

2. 官方

官方解析太绕了,又是递归又是回溯,还要排序。

class Solution {
public:
    void recursion(vector<string> &res, string &str, string &temp, vector<int> &vis){
        //临时字符串满了加入输出
        if(temp.length() == str.length()){ 
            res.push_back(temp);
            return;
        }
        //遍历所有元素选取一个加入
        for(int i = 0; i < str.length(); i++){ 
            //如果该元素已经被加入了则不需要再加入了
            if(vis[i]) 
                continue;
            if(i > 0 && str[i - 1] == str[i] && !vis[i - 1])
                //当前的元素str[i]与同一层的前一个元素str[i-1]相同且str[i-1]已经用过了
                continue;
            //标记为使用过  
            vis[i] = 1;  
            //加入临时字符串
            temp.push_back(str[i]); 
            recursion(res, str, temp, vis);
            //回溯
            vis[i] = 0; 
            temp.pop_back();
        }
    }
    
    vector<string> Permutation(string str) {
        //先按字典序排序,使重复字符串相邻
        sort(str.begin(), str.end()); 
        //标记每个位置的字符是否被使用过s
        vector<int> vis(str.size(), 0); 
        vector<string> res;
        string temp;
        //递归获取
        recursion(res, str, temp, vis); 
        return res;
    }
};

3. 调函数next_permutation(begin, end)

这个确实厉害,但是不是别人想考的。

class Solution {
public:
    vector<string> Permutation(string str) {
        vector<string> result;
        if (str.size() == 0) return result;
        sort(str.begin(), str.end());  //先按照字典排序
        do {
            result.push_back(str);
        }while (next_permutation(str.begin(), str.end())); //得到下一个排序
        return result;
    }
};

4. 本题小结

  1. 大多数思想是使用递归,后面的再细看吧。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值