剑指 Offer II 001. 整数除法
//方法一:
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. 二进制加法
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 的个数
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. 只出现一次的数字
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;
}
};
经典三进制器。
剑指 Offer II 005. 单词长度的最大乘积
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;
}
};
还有其优化版,这里就不过多赘述了。
剑指 Offer II 006. 排序数组中两个数字之和
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 的三个数
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 的最短子数组
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. 左右两边子数组的和相等
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。