剑指 offer (专项突击版)

剑指 Offer II 001. 整数除法

342342343

//方法一:
class Solution {
public:
    int divide(int a, int b) {
        // 考虑被除数为最小值的情况
        if (a == INT_MIN) {
            if (b == 1) {
                return INT_MIN;
            }
            if (b == -1) {
                return INT_MAX;
            }
        }
        // 考虑除数为最小值的情况
        if (b == INT_MIN) {
            return a == INT_MIN ? 1 : 0;
        }
        // 考虑被除数为 0 的情况
        if (a == 0) {
            return 0;
        }
        
        // 一般情况,使用二分查找
        // 将所有的正数取相反数,这样就只需要考虑一种情况
        bool rev = false;
        if (a > 0) {
            a = -a;
            rev = !rev;
        }
        if (b > 0) {
            b = -b;
            rev = !rev;
        }

        // 快速乘,内联函数
        //注意:此时的x,y都为负数!!!!
        auto quickAdd = [](int x, int y, int z) {
            //这里的z就表示y自身相加多少次
            // x 和 y 是负数,z 是正数
            // 需要判断 z * y >= x 是否成立
            int result = 0, add = y;
            //计算乘法,每次都对半处理
            while (z) {
                //z为奇数
                if (z & 1) {
                    // 需要保证 result + add >= x
                    if (result < x - add) {
                        return false;
                    }
                    result += add;
                }
                //z不等于1
                if (z != 1) {
                    // 需要保证 add + add >= x
                    if (add < x - add) {
                        return false;
                    }
                    add += add;
                }
                // 不能使用除法,z / 2
                z >>= 1;
            }
            return true;
        };
        
        int left = 1, right = INT_MAX, ans = 0;
        while (left <= right) {
            // 注意溢出,并且不能使用除法
            //获取两个数中间的数
            int mid = left + ((right - left) >> 1);
            //check用于判断 b * mid 后是否小于 a,确认小于就使用ans存储该mid然后继续进行二分,不小于就缩短范围
            //注意:此时的a,b都为负数!!!!
            bool check = quickAdd(a, b, mid);
            if (check) {
                ans = mid;
                // 注意溢出
                if (mid == INT_MAX) {
                    break;
                }
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        return rev ? -ans : ans;
    }
};

//方法二:
class Solution {
public:
    int divide(int a, int b) {
        if (a == INT_MIN && b == -1) return INT_MAX;

        int sign = (a > 0) ^ (b > 0) ? -1 : 1;

        if (a > 0) a = -a;
        if (b > 0) b = -b;
        
        int res = 0;
        while (a <= b) {
            int value = b;
            int k = 1;
            // 0xc0000000 是十进制 -2^30 的十六进制的表示
            // 判断 value >= 0xc0000000 的原因:保证 value + value 不会溢出
            // 可以这样判断的原因是:0xc0000000 是最小值 -2^31 的一半,
            // 而 a 的值不可能比 -2^31 还要小,所以 value 不可能比 0xc0000000 小
            while (value >= 0xc0000000 && a <= value + value) {
                value += value;
                // 代码优化:如果 k 已经大于最大值的一半的话,那么直接返回最小值
                // 因为这个时候 k += k 的话肯定会大于等于 2147483648 ,这个超过了题目给的范围
                if (k > INT_MAX / 2) return INT_MIN;
                k += k;
            }
            a -= value;
            res += k;
        }

        // bug 修复:因为不能使用乘号,所以将乘号换成三目运算符
        return sign == 1 ? res : -res;
    }
};

方法一:快速乘 + 二分查找。
方法二:成倍数相减。

剑指 Offer II 002. 二进制加法

4234234234

class Solution {
public:
    string addBinary(string a, string b) {
        string ans;

        reverse(a.begin(), a.end());
        reverse(b.begin(), b.end());

        int n = max(a.size(), b.size()), carry = 0;
        for (size_t i = 0; i < n; ++i) {
            carry += i < a.size() ? (a.at(i) == '1') : 0;
            carry += i < b.size() ? (b.at(i) == '1') : 0;
            ans.push_back((carry % 2) ? '1' : '0');
            carry /= 2;
        }
        if (carry) {
            ans.push_back('1');
        }
        
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

模拟法,没啥说的,自己搞一个二进制加法器。

剑指 Offer II 003. 前 n 个数字二进制中 1 的个数

4234234234

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> myVector(n + 1, 0);
        for (int i = 0; i <= n; i++) {
            myVector[i] = myVector[i >> 1] + (i & 1); //右移1位相当于i/2
        }
        return myVector;
    }
};

如果正整数 i 是一个偶数,那么 i 相当于将 i/2 左移一位的结果,因此偶数 i 和 i/2 的二进制形式 1 的个数是一样的,如果 i 是奇数,那么 i 相当于将 i/2 左移一位之后再将最右边的位设为 1 的结果,因此奇数 i 比 i/2 的二进制形式 1 的个数多 1 个 可以利用这个规律写出这个代码。

剑指 Offer II 004. 只出现一次的数字

4324234

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int a = 0, b = 0;
        for (auto x : nums) {
            a = ~b & (a ^ x);
            b = ~a & (b ^ x);
        }
        return a;
    }
};

经典三进制器。
4324234
4234234
4234234234
4234234

剑指 Offer II 005. 单词长度的最大乘积

4324234

class Solution {
public:
    int maxProduct(vector<string>& words) {
        vector<int> myVector(words.size(), 0);
        for (int i = 0; i < words.size(); i++) {
            for (int j = 0; j < words[i].size(); j++) {
                myVector[i] |= 1 << (words[i][j] - 'a');
            }
        }
        int maxNum = 0;
        for (int i = 0; i < myVector.size() - 1; i++) {
            for (int j = i + 1; j < myVector.size(); j++) {
                if ((myVector[i] & myVector[j]) == 0) {
                    maxNum = max(maxNum, (int)(words[i].size() * words[j].size()));
                }
            }
        }
        return maxNum;
    }
};

5435345
还有其优化版,这里就不过多赘述了。
45345345

剑指 Offer II 006. 排序数组中两个数字之和

5345345

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int i = 0, j = numbers.size() - 1;
        while (i < j) {
            if (numbers[i] + numbers[j] == target) {
                return {i, j};
            } else if (numbers[i] + numbers[j] > target) {
                j--;
            } else {
                i++;
            }
        }
        return {0};
    }
};

双指针遍历,没啥意思。

剑指 Offer II 007. 数组中和为 0 的三个数

5345345

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> myVector;
        if (nums.size() < 3) {
            return myVector;
        }
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size() - 2; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1, right = nums.size() - 1;
            while (left < right) {
                if (nums[i] + nums[left] + nums[right] == 0) {
                    myVector.push_back({nums[i], nums[left], nums[right]});
                    //去重
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    left++;
                    right--;
                } else if (nums[i] + nums[left] + nums[right] > 0) {
                    right--;
                } else {
                    left++;
                }
            }
        }
        return myVector;
    }
};

排序 + 三指针,固定一个指针,另外两个指针一个指向固定指针的下一个元素位置,一个指向最后一个元素,再暴力求和求解。

剑指 Offer II 008. 和大于等于 target 的最短子数组

434234

class Solution {
public:
    //双指针法,类似于滑动窗口
    int minSubArrayLen(int target, vector<int>& nums) {
        int sum = 0, minLength = INT_MAX;
        //定义两个变量i、j(类似于滑动窗口的感觉)
        for (int i = 0, j = 0; j < nums.size(); j++) {
            //扩大窗口
            sum += nums[j];
            while (i <= j && sum >= target) {
                //更新最小值
                minLength = min(minLength, j - i + 1);
                //缩小窗口
                sum -= nums[i++];
            }
        }
        //若所有数组和都小于target,则返回0,否则返回更新值
        return minLength == INT_MAX ? 0 : minLength;
    }
};

类似于滑动窗口,每次循环扩大窗口,当其中的值大于等于target时,缩小窗口,更新最小值,一值这样循环更新循环更新就行了。

剑指 Offer II 012. 左右两边子数组的和相等

5345345345

class Solution {
public:
    int pivotIndex(vector<int>& nums) {
        int frontNum = 0;
        int endNum = 0;
        for (int i = 0; i < nums.size(); i++) {
            endNum += nums[i];
        }
        for (int i = 0; i < nums.size(); i++) {
            endNum -= nums[i];
            if (frontNum == endNum) {
                return i;
            }
            frontNum += nums[i];
        }
        return -1;
    }
};

先求后序总和endNum,然后指针往后走,每次都减掉指向的这个数,再frontNum加当前指向的数,再每次进行判断就行,如果循环完了还没有返回,那就说明没找到,返回-1。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值