剑指offer(五)

41. 和为S的连续正数序列

和为S的连续正数序列_牛客题霸_牛客网 (nowcoder.com)

法一

暴力数一数。

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int>> ans;
        for(int i = 1;i <= sum / 2  ;i++){
            vector<int> vec;
            int temp = 0;
            for(int j = i ;j < sum ;j++ ){
                temp += j;
                vec.push_back(j);
                if(temp == sum){
                    ans.push_back(vec);
                    break;
                }
            }
        }
        return ans;
    }
};

法二

滑动窗口

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int>> ans;
        int low = 1, high = 2;  // 初始窗口
        while(low < high){
            int temp = (low + high) * (high - low + 1) / 2;  //等差数列求和公式,(首项 + 尾项)* 项数 / 2
            if(temp == sum){  // 窗口内正好是符合条件的一列数
                vector<int> vec;
                for(int i = low;i<=high;i++) vec.push_back(i);
                ans.push_back(vec);
                low++;
            }else if(temp < sum){  // 窗口内值太小,需要大一点,所以窗口右端右移动
                high++;
            }
            else{   // 窗口太大,需要小一点,就窗口左端左移,减少一个数
                low++;
            }
        }
        return ans;
    }
};

42. 和为S的两个数字

和为S的两个数字_牛客题霸_牛客网 (nowcoder.com)

法一

经典两数之和。

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        unordered_set<int> st;
        for(auto& num : array){
            if(st.find(sum - num) != st.end()) return {num,sum - num};
            st.insert(num);
        }
        return {};
    }
};

法二

由于是升序数据,可以利用滑动窗口。

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        int low = 0, high = array.size() - 1;
        while(low <= high){
            int temp = array[low] + array[high]; 
            if( temp == sum) return {array[low],array[high]};
            else if(temp < sum) low++;
            else high--;
        }
        return {};
    }
};

43. 左旋转字符串

左旋转字符串_牛客题霸_牛客网 (nowcoder.com)

法一

经典方法,三次反转。

class Solution {
public:
    string LeftRotateString(string str, int n) {
        if(n == 0 || str.size() == 0) return str; 
        n = n % str.size();
        reverse(str.begin(),str.begin() + n);
        reverse(str.begin()+ n ,str.end());
        reverse(str.begin(),str.end());
        return str;
    }
};

法二

拼接以后,直接截取后半部分。

class Solution {
public:
    string LeftRotateString(string str, int n) {
        int len = str.size();
        if(len == 0 || n == 0) return str;
        n %= len;
        str += str;
        return str.substr(n,len);
    }
};

44. 反转单词序列

法一

题目怎么说,我就怎么写。说是反转,我就直接倒着数。不过倒过来倒过去的看顺序还是有点麻烦。

class Solution {
public:
    string ReverseSentence(string str) {
        string ans,temp;
        for(int i = str.size() - 1; i>=0 ;i--){
            if(str[i] == ' '){
                ans = ans + temp + " ";
                temp = "";
            }else{
                temp = str[i] + temp;
            }
        }
        if(temp.size()) ans += temp;
        return ans;
    }
};

法二

看到反转,倒叙可以想到栈;然后单词之间是用空格分割的可以想到利用流的特性完成句子的分割。

class Solution {
public:
    string ReverseSentence(string str) {
        string temp;
        istringstream is(str);
        stack<string> st;
        while(is>>temp){
            st.push(temp);
        }
        string ans;
        while(!st.empty()){
            temp = st.top();
            st.pop();
            ans += temp;
            ans += ' ';
        }
        return ans.substr(0,ans.size() - 1);
    }
};

法三

其实,如果是python或者JAVA可以直接用split分割之后整体反转一下再加起来就行了。C++没有split可以自己手搓一个。

class Solution {
public:
    vector<string> split(const string& input,const char& reg){
        stringstream ss(input);
        vector<string> ans;
        string token;
        while(getline(ss,token,reg)){
            ans.push_back(token);
        }
        return ans;
    }
    string ReverseSentence(string str) {
        vector<string> vec = split(str,' ');
        reverse(vec.begin(),vec.end());
        string ans;
        for(auto& s : vec){
            ans += s;
            ans += " ";
        }
        return ans.substr(0,ans.size() - 1);
    }
};

45. 扑克牌顺子

扑克牌顺子_牛客题霸_牛客网 (nowcoder.com)

法一

顺子的特点:

  • 除了0以外,不能有相同的牌。
  • 两张牌之间的间隔必须为1,如果不为1需要用0补齐。需要补的0的个数记为inner。
  • 如果0的个数小于inner,则可以补成顺子,否则不行。
class Solution {
public:
    bool IsContinuous(vector<int>& numbers) {
        sort(numbers.begin(),numbers.end());
        int zero = 0, inner = 0;
        for(int i = 0 ;i<numbers.size() - 1;i++){
            if(numbers[i] == 0) zero++;
            else if(numbers[i] == numbers[i + 1]) return false;
            else
                inner += numbers[i + 1] - numbers[i] - 1;
        }
        if(zero < inner) return false;
        return true;
    }
};
  • 虽然顺子里只有两张王(只有两个0),但有个样例是[1,0,0,5,0]如果输出为false无法通过。所以代码中不需要添加对0个数是否小于2的判断。
  • ps. 以前做过一次类似的,通过率一直差一点百思不得其解,做这个的时候突然顿悟,当时没想到不能有重复的牌。

法二

原理同上,换种写法。

class Solution {
public:
    bool IsContinuous(vector<int>& numbers) {
        sort(numbers.begin(),numbers.end());
        int zero = 0;
        int i = 0;
        while(numbers[i] == 0) zero++, i++;
        int inner = 0;
        for(;i<numbers.size() - 1;i++){
            if(numbers[i] == numbers[i + 1]) return false;
            inner += numbers[i + 1] - numbers[i] - 1;
        }
        if(zero < inner) return false;
        return true;
    }
};

46. 孩子们的游戏(圆圈中最后剩下的数)

孩子们的游戏(圆圈中最后剩下的数)_牛客题霸_牛客网 (nowcoder.com)

法一

题目怎么说,那就怎么写。

  • n个人就搞一个容量为n的数组。
  • 每m个重新开始报数,也需要一个变量来记录是否到了m个。到了的话,就把vec[m]设为-1表示孩子出局了。
  • 报数就用一个i变量来记录,如果vec[i]==-1表示这个孩子出局了,直接跳过。由于要从0开始报数,所以i初始化为1。
class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        vector<int> vec(n,0);
        int i = -1 ,cnt = n;
        int step = 0;
        while(cnt > 0){
            i++;
            if(i >= vec.size()) i = 0;
            if(vec[i] == -1) continue;
            step++;
            if(step == m){
                vec[i] = -1;
                step = 0;
                cnt--;
            }
        }
        return i;
    }
};

法二

利用约瑟夫环的公式。

约瑟夫问题–五种变式 - 小又又

class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        int pos = 0;
        for(int i = 2;i<=n;i++)
            pos = (pos + m) % i;
        return pos;
    }
};

47. 求1+2+3+…+n

求1+2+3+…+n_牛客题霸_牛客网 (nowcoder.com)

初看好简单,仔细一看不能使用乘除和判断,也就是说只能用加法和条件判断,故不能用求和公式,那就基本只能用位运算进行条件判断了。

  • 原式 = n +( 0 +1 + 2 + 3 + …)
  • 与运算有“短路”作用
class Solution {
public:
    int Sum_Solution(int n) {
        n && ( n += Sum_Solution(n - 1));
        return n;
    }
};

48. 不用加减乘除做加法

一眼位运算。

  • 异或运算可以得到两数相加后各二进制位的非进位信息。(两数不同则是1,不同的话肯定一个0一个1,结果肯定是1)
  • 与运算可以得到进位信息。(与运算只有在两个数都是1的时候才会是1,即进位)
class Solution {
public:
    int Add(int num1, int num2) {
        int carry = num2;
        int sum = num1;
        while(carry){
            int temp = sum ^ carry;
            carry = (sum & carry) <<1;
            sum = temp;
        }
        return sum;
    }
};

49. 把字符串转换成整数

把字符串转换成整数_牛客题霸_牛客网 (nowcoder.com)

题目怎么说就怎么写,一位一位的数,遇见不对的直接返回0。

class Solution {
  public:
    int StrToInt(string str) {
        int i = 0;
        bool flag = true;
        int ans = 0;
        if (str[i] == '+') i++;
        else if (str[i] == '-') {
            flag = false;
            i++;
        }
        while (i < str.size()) {
            if ('0' <= str[i] && str[i] <= '9') {
                ans *= 10;
                ans += str[i] - '0';
                i++;
                // cout << ans << endl;
            } else {
                return 0;
            }
        }
        if (!flag) ans = -ans;
        return ans;
    }
};

50. 数组中重复的数字

数组中重复的数字_牛客题霸_牛客网 (nowcoder.com)

题中给出的代码模板有一种C的味道。

法一

找第一个重复的数字,那就从头开始数,一边数一边用unordered_map记下来。

class Solution {
public:
    bool duplicate(int numbers[], int length, int* duplication) {
        unordered_map<int,int> mp;
        for(int i = 0;i<length;i++){
            if(mp.count(numbers[i])){
                duplication[0] = numbers[i];
                return true;
            }
            mp[numbers[i]]++;
        }
        return false;
    }
};

法二

用vector来实现一个简化的哈希表也是个不错的方法

class Solution {
public:
    bool duplicate(int numbers[], int length, int* duplication) {
        vector<bool> vec(length,false);
        for(int i = 0;i<length;i++){
            if(vec[numbers[i]] == false) vec[numbers[i]] = true;
            else{
                duplication[0] = numbers[i];
                return true;
            }
        }
        return false;
    }
};

法三

  • 数组中所有数都在0~n-1范围内。
  • 在遍历过程中,将对应索引位置的数+n。同时,只要遍历过程中遇见一个位置的数>=n就表明,该位置对应的数重复了。
  • 可以这么做的原因是数组中所有数的范围是0~n-1,所以只要在遍历过程中对length取余就可以得到原始数据。而原始数据 + n相当于添加标志位类比于法二中的辅助数组。
  • 举例,{1,2,1,3}中,n = 4。
    • 第一次循环index = 1,num[1] = 2 + 4 = 6。
    • 第二次循环index = 2, num[2] = 1 + 4 = 5。
    • 第三次循环index = 1, num[1] > length。所以1重复了。
class Solution {
public:
    bool duplicate(int numbers[], int length, int* duplication) {
        for(int i = 0;i<length;i++){
            int index = numbers[i] % length;  // 获取原始数值
            if(numbers[index] >= length){
                duplication[0] = index;
                return true; 
            }
            numbers[index] += length;
        }
        return false;
    }
};
  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

记与思

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值