剑指offer 6

目录

 

11 二进制中的1的个数3/11

12 数值的整数次方

19 顺时针打印矩阵

27 字符串的排列【暂时有疑问】

33 丑数3/15

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

41 和为S的正数序列

42 和为S的两个数字

43 左旋转字符串

44 翻转单词顺序列

45 扑克牌顺子


11 二进制中的1的个数3/11

参考:https://www.cnblogs.com/iwiniwin/p/11058255.html

【位运算】

1 与1相与完,1左移一位

class Solution {
public:
     int  NumberOf1(int n) {
         int unit = 1, count = 0;//初始化unit为000(中间省略32-4个0)1
         while(unit != 0){//退出循环条件:unit左移到33位溢出,32位数扫描了一遍
             if((unit & n) != 0)//【易错:&优先级低于!=】相与不等于0,说明对应位置为1
                 count++;//计数加一
             unit <<= 1;//将unit左移
         }
         return count;
     }
};

2 与n-1相与,相当于把最右边的1变成0,有多少个1,就能变多少次【优秀】

class Solution {
public:
     int  NumberOf1(int n) {
         //对于数值n,将n - 1后再和n相与,
         //得到的值相当于将n从右边数的第一个1变成0。
         //n的二进制表示中有多少个1,就能变多少次。
         //实现代码如下,时间复杂度优化为O(n中1的个数)
         int cnt = 0;
         while( n != 0){
             cnt++;
             n = (n - 1) & n;
         }
         return cnt;//【易错,竟然把这里写成了n】
     }
};

12 数值的整数次方

参考:https://www.nowcoder.com/questionTerminal/1a834e5e3e1a4b7ba251417554e07c00?f=discussion

 

class Solution {
public:
    double Power(double base, int exponent) {
    
        if(base == 0.0)//基数为0,结果为0
            return 0.0;
        double res = 1.0;
        int e = exponent > 0 ? exponent: -exponent;//次方为正还是负
        for(int i = 1; i <= e; i++){
            res *= base;//base相乘e次
        }
        return exponent > 0? res: 1/res;//根据次方正负输出结果
    }
};

19 顺时针打印矩阵

参考:https://www.cnblogs.com/silentteller/p/11918473.html

参考:https://blog.csdn.net/qq_43461641/article/details/90199866

思路:求矩阵行数row,列数col

从左到右,从上到下,从右到左,从下到上

走完一圈之后行和列-1,到达对角线的下一个,

关键在于判断边界:

从左到右,列数<=col

从上到下,行数<=row

从右往左,列数<=col,行数<=row

从下往上,列数<=col,行数<=row

27 字符串的排列【暂时有疑问】

【全排列】【递归】

参考:https://blog.csdn.net/lr604844811/article/details/99710445

参考:https://blog.csdn.net/lr604844811/article/details/99710445

最小的k个数

参考:https://blog.csdn.net/daaikuaichuan/article/details/85239149

参考:https://www.cnblogs.com/silentteller/p/11959368.html

维护一个最大堆;

利用优先级队列实现最大堆;

【最大堆】【优先级队列】

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        if(input.size() == 0 || k == 0 || k > input.size())
            return res; //输入数组为空,或k==0 或k超出数组范围
        for(int num:input){
            if(q.size() < k)
                q.push(num);//将前k个数存入最大堆
            else{//k之后元素到来时
                if(num < q.top()){//将它与堆顶(最大值)做比较
                    q.pop();//小于栈顶元素,删除栈顶元素
                    q.push(num);//将新元素压入堆中
                }
            }
        }
        while(!q.empty()){//循环终止条件:队列取出了所有元素
            res.push_back(q.top());//将队列里元素保存到vector中
            q.pop();//删除堆顶元素
        }
        //reverse(res.begin(), res.end());
        return res;
    }
private:
    vector<int> res;
    priority_queue<int> q;//优先级队列模拟堆来处理。
};

整数中1出现的次数

参考:https://www.cnblogs.com/aimi-cn/p/11510770.html

参考:https://www.cnblogs.com/wmx24/p/8901808.html

引用:

总结一下以上的算法,可以看到,当计算右数第 i 位包含的 1 的个数时:

  1. 取第 i 位左边(高位)的数字x,乘以 10的i−1次方,得到基础值 a。
  2. 取第 i 位数字,计算修正值:
  • 如果大于 1,则结果为 a+10i−1,(x+1)*10^(i-1)。
  • 如果小于 1,则结果为 a。
  • 如果等于 1,则取第 i 位右边(低位)数字,设为 b,最后结果为 a+b+1。

关于a+8的解释:https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6?f=discussion

+8是在当前位>=2的时候,当前位出现1的次数与高位数+1相关。相反,当前位<=2时,当前位出现1的次数与高位数和余数相关。

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
        int count = 0;
        for (int i = 1; i <= n; i *= 10) {//i每次*10
            int a = n / i,b = n % i;//
            count += (a + 8) / 10 * i + ((a % 10 == 1) ? b + 1 : 0);
        }
        return count;
    }
};

33 丑数3/15

参考:https://blog.csdn.net/qq_38790716/article/details/89052721

【穷举】

思路:

维护三个队列,分别为2的倍数,3的倍数,5的倍数,然后从三个队列中选择最小值;

如果从2的队列选择最小值,则指针向下一个移动(t++),模拟出队的操作

class Solution {
public://
    int GetUglyNumber_Solution(int index) {
        if (index < 7)//丑数序列为1234567
            return index;
        vector<int> res(index);//【易错】必须初始化数组大小index个,否则返回时候会出现数组越界问题
        res[0] = 1;
        int t2 = 0, t3 = 0, t5 = 0, i;
        for (i = 1; i < index; ++i)//【易错】i从1开始
        {
            //从三个队列中选择最小值
            res[i] = min(res[t2] * 2, min(res[t3] * 3, res[t5] * 5));
            //判断哪一个队列的元素出队,然后t++实现出队操作
            if (res[i] == res[t2] * 2)t2++;
            if (res[i] == res[t3] * 3)t3++;
            if (res[i] == res[t5] * 5)t5++;
        }
        return res[index - 1];
    }
};

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

参考:https://www.nowcoder.com/questionTerminal/1c82e8cf713b4bbeb2a5b31cf5b0417c?f=discussion

思路:利用哈希表

字母a-z链接:对应的ASCII码值为97-122,A-Z对应的ASCII码为65-90,每个字母的key=int(word)-65,

数组中具体记录的内容是该字母出现的次数,最终遍历一遍字符串,找出第一个数组内容为1的字母就可以了,时间复杂度为O(n)

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        for(int i = 0; i < str.size(); ++i){
            m[str[i]]++;//遍历字符串,在hash表中统计各字母出现次数
        }
        for(int i = 0; i < str.size(); ++i){
            if(m[str[i]] == 1)
                return i;//扫描直接访问hash表获得次数
        }
        return -1;
    }
private:
    map<char, int> m;//哈希表,key为字母,value为字母出现次数
};

41 和为S的正数序列

参考:https://www.nowcoder.com/questionTerminal/c451a3fd84b64cb19485dad758a55ebe?f=discussion

参考:https://www.cnblogs.com/silentteller/p/12061071.html

思路:左右两端点left、right,由于序列连续且等差,则求和公式为sum =(right +left)*(left right之间个数)/2

如果sum= 目标值,将当前序列保存到临时数组,临时数组保存到二维向量结果集res中;right++

如果sum >目标值,left++

如果sum < 目标值,right++

直到所有的都求出来

还有种思路是求中间值和序列长度,也是根据等差数列求和的性质,有点麻烦

【滑动窗口】【穷举】【求和】

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        if(sum == 0)
            return res;//【易错】一定要注意返回值类型
        int left = 1;//左端起点
        int right = 2;//右端起点,相当于动态窗口的两边,根据其窗口内的值的和来确定窗口的位置和大小

        while(left < right){
            //由于序列是连续的,差为1,求和公式是(a0+an)*n/2
            if((left + right) *(right - left + 1) / 2 == sum){
                 //和与目标值相等,那么就将窗口范围的所有数添加进临时一维向量
                vector<int> temp;//保存满足条件的一个序列
                for(int i = left; i <= right; ++i)
                    temp.push_back(i);
                res.push_back(temp);//保存到结果集
                right++;//右侧端点往右挪
            }
            else if((left + right) *(right - left + 1) / 2 > sum){
                //如果当前窗口内的值之和大于sum,那么左边窗口右移一下
                left++;
            }
            else{
           //如果当前窗口内的值之和小于sum,那么右边窗口右移一下
                 right++;
            }
        }
        return res;
    }
private:
    vector<vector<int> > res;//保存结果的二维向量
};

42 和为S的两个数字

参考:https://www.cnblogs.com/silentteller/p/12061216.html

思路:左指针left,指向第一个元素;右指针right,指向末尾元素;和sum= left + right ;

如果sum ==目标值,输出left right

如果sum < 目标值,left++

如果sum > 目标值, right--

当left right距离越远的时候乘积越小。联想1*15 和8*8,联想小学时候,边长L相等面积S最大的时候边=根号S

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        if(array.size() == 0)
            return res;
        int left = 0;//左指针初始化指向第一个元素
        int right = array.size()-1;//右指针初始化指向最后一个元素
        while(left < right){//循环终止条件:左右指针相遇
            if(array[left] + array[right] == sum){
                //和为目标值,将left元素值和right元素值加入结果数组
                res.push_back(array[left]);
                res.push_back(array[right]);
                break;//出口
            }
            else if(array[left] + array[right] > sum){
                //和大于目标值,右指针左移
                right--;
            }
            else{//和小于目标值,左指针右移
                left++;
            }
        }
        return res;
    }
private:
    vector<int> res;//保存结果的数组
};

43 左旋转字符串

参考:https://www.cnblogs.com/silentteller/p/12069702.html

参考:https://www.nowcoder.com/questionTerminal/12d959b108cb42b1ab72cef4d36af5ec?f=discussion

思路:

str: abc12345 循环左移3位后输出12345abc

先整体翻转变成54321cba

在找到len - n(8-3 = 5)的地方分割成两部分,分别翻转这两部分,即分别翻转54321和cba变成12345和abc

【reverse】

节选一下《STL源码剖析一书》关于reverse的概念:将序列[first,last)的元素在原容器中颠倒重排。

左开右闭区间,拿54321cba举例子,str.begin()指向第一个元素5, str.size() - n指向c,但是右半边闭区间,所以旋转的是54321。str.end()指向a后面的空字符,根据左闭右开区间,旋转cba。

class Solution {
public:
    string LeftRotateString(string str, int n) {
        reverse(str.begin(), str.end());
        reverse(str.begin(), str.begin() + str.size() - n);
        reverse(str.begin() + str.size() - n, str.end());
        return str;
    }
};

或者自己写reverse

链接:https://www.nowcoder.com/questionTerminal/12d959b108cb42b1ab72cef4d36af5ec?f=discussion
来源:牛客网

 class Solution {
public:
    void fun(string &s,int start,int end)
    {
        char temp;
        while(start<end)
        {
            temp=s[start];
            s[start]=s[end];
            s[end]=temp;
            start++;
            end--;
        }
    }
    string LeftRotateString(string str, int n) {
        int len=str.length();
        if(0==len || 0==n)
            return str;
        string &temp=str;
        fun(temp,0,n-1);
        fun(temp,n,len-1);
        fun(temp,0,len-1);
        return str;
    }
};

字符串拼接

链接:https://www.nowcoder.com/questionTerminal/12d959b108cb42b1ab72cef4d36af5ec?f=discussion
来源:牛客网

class Solution {
public:
    string LeftRotateString(string str, int n) {
        int len = str.length();
        if(len == 0) return "";
        n = n % len;//移动n位对字符串长度取余数
        str += str;//str扩大到原来两倍
        return str.substr(n, len);//从取余的地方去len(字符串长度)的子字符串
    }
};

44 翻转单词顺序列

参考:https://www.cnblogs.com/silentteller/p/12069918.html

思路:

     翻转整个句子,然后,依次翻转每个单词。

    依据空格来确定单词的起始和终止位置

【注意】结尾单词单独处理是因为结尾没有空字符,不能再根据str[index] == ' '判断最后一个单词的结尾,而且最后一个单词翻转的范围也因此改变。这里就体现了STL左开右闭区间的优越性。

【reverse】

class Solution {
public:
    string ReverseSentence(string str) {
        myReverse(str, 0, str.size()-1);//将整个个字符串翻转一次
        int index = 0;//单词长度
        int l = 0;//单词左端开始的地方
        while(index < str.size()){
            if(str[index] == ' '){//根据空格位置划分单词,将单词翻转
                myReverse(str, l, index-1);
                l = index + 1;
            }
            else if(index == str.size()-1){最后一个单词单独进行反转
                myReverse(str, l, index);
                break;//【出口】最后一个单词翻转完毕
            }
            index++;//不是空格,index继续右移
        }
        return str;
    }
    void myReverse(string &str, int l, int r){
        if(l > str.size()-1 || r < 0)
            return;
        while(l < r){
            swap(str[l], str[r]);
            l++;
            r--;
        }
    }
};

45 扑克牌顺子

参考:https://www.cnblogs.com/silentteller/p/12073082.html

必须满足两个条件

1. 除0外没有重复的数

2. max - min < 5

思路:

1、排序 

2、计算所有相邻数字间隔总数 

3、计算0的个数 

4、如果2、3相等,就是顺子 

5、如果出现对子,则不是顺子

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        if(numbers.size() == 0)
            return false;
        sort(numbers.begin(), numbers.end());//排序
        int countZero = 0;
        for(auto i:numbers){
            if(i == 0)
                countZero++;//找出数组中0的个数
        }
        int left = countZero;//左指针指向0之后的第一个元素
        int right = left+1;//右指针指向左指针之后的那个元素
        int countGap = 0;//gap保存左右两个元素之间的差值
        while(right < numbers.size()){//循环终止条件:到达数组结尾
            if(numbers[left] == numbers[right])
                return false;//出口A,出现相同数字,对子
            countGap += (numbers[right] - numbers[left] - 1);//【易错】求差值
            left = right;//左右指针各自往右挪一位
            right++;
        }
        return countGap <= countZero ? true : false;//出口B,如果gap<=0的个数,可以成为顺子,否则不行
    }
};

另一种思路:

链接:https://www.nowcoder.com/questionTerminal/762836f4d43d43ca9deb273b3de8e1f4?f=discussion
来源:牛客网

//考略到顺子的特性,最大值和最小值之差绝对为4,
//然而又有大小王的存在,所以a[4]-a[jokers] <=4
class Solution {
public:
   bool IsContinuous( vector<int> numbers ) {
        int len=numbers.size();
        if(len!=5)  return false;
        sort(numbers.begin(),numbers.end());
        int jokers=0;//计算王的数目
        for(int i=0;i<5&&numbers[i]==0;i++){
            jokers++;
        }
        if(jokers>4) return false;
     
        for(int i=jokers+1;i<5;i++){
            if(numbers[i]==numbers[i-1])//判断对子的存在
                return false;
        }
        int dis=numbers[4]-numbers[jokers];
        if(dis<=4)   return true;
        return false;
    }
};

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值