文章目录
001
- 这题不能用乘除,我们用加减,每次将除数翻倍,与被除数比较大小,若被除数小于此时的翻倍结果,则让被除数减去这个未翻倍前的除数,假设是k*除数,记录此时的商加等于k;
- 这种算法首先要保证被除数和除数符号相同,我们先把它们都转换成负数(负数转正数有可能溢出)。
- int型的范围是-2 31~-2 31-1,当被除数是-2 31,除数是-1时,得到的结果超出范围,要返回最大的整数值 Integer.VALUE。
- 在十六进制中负数的二进制原码的最高位是符号位,后面的31位为序号位,不是值位。1后面的000 0000 0000 0000 0000 0000 0000 0000,表示序号1,表示负数中,从小到大的第一位。
- 由于int的最小值为-231,排在负数从小到大的序号1,所以int i = 0x80000000 输出为 - 231
比如:我们来看看0xFFFFFFFF,原码 1111 1111 1111 1111 1111 1111 1111 1111,最高位为1 ,为负数,序号位为第231-1位 (111 1111 1111 1111 1111 1111 1111 1111= 231-1, 所以0xFFFFFFFF为负数从小到大 第2 31-1位 ,即 -2 31+2 31-1= -1 ;
再比如求-2 31的一半,它也就是负数中从小到大第2 30个,原码为 -231+230 = -230,为0xc0000000。
- 由于int的最小值为-231,排在负数从小到大的序号1,所以int i = 0x80000000 输出为 - 231
int divide(int dividend, int divisor) { //被除数,除数
if (dividend == 0x80000000 && divisor == -1) {
return INT_MAX;
}
int flag = 2; //标记最终结果的正负,等于1为负
if (dividend > 0) {
flag--;
dividend = -dividend;
}
if (divisor > 0) {
flag--;
divisor = -divisor;
}
int result = 0; //最终结果;
int quotient; //暂时记录的倍数
int value; //记录翻倍的除数
while (dividend <= divisor) {
value = divisor;
quotient = -1;//考虑到结果可能为-2的31次方,所以用负数存
while (value >= 0xc0000000 && dividend <= value + value) {
value += value;
quotient += quotient;
}
result += quotient;
dividend -= value;
}
if (flag != 1)
result = -result;
return result;
}
002
字符二进制加法,可以参考10进制加法直接计算,从低位开始取出字符相加,记录进位。
每次将计算结果添加到字符中。
c++提供字符操作函数,字符串后面可以直接用+号连接其他字符或串, (串).size()可以获得串长,这个长度不包括末尾表示符。以及reverse(串)函数反转字符串,cout<<(串)可以直接打印串。
string addBinary(string a, string b){
int Ai, Bj;
int i = a.size()-1, j = b.size()-1;
int carry = 0;
int sum;
string result="";
while (i >= 0 || j >= 0){
if (i >= 0)
Ai = (int)a[i--] - '0';
else
Ai = 0;
if (j >= 0)
Bj = (int)b[j--] - '0';
else
Bj = 0;
sum = Ai + Bj + carry;
if (sum >= 2)
{
carry = 1;
sum -= 2;
}
else
carry = 0;
result = result + (char)('0'+sum);
}
if (carry == 1)
result = result+'1';
reverse(result.begin(),result.end());
return result;
}
003二进制1的个数
vector <int>countBits(int n) {
vector <int> bit(n+1);
bit[0] = 0;
for (int i = 0; i <= n; i++)
bit[i] = bit[i / 2] + i % 2;
//bit[i] = bit[i >> 1] + (i &1) ;位运算效率更高
return bit;
}
004只出现一次的数字
方法1哈希
int singleNumber(vector<int>& nums) {
unordered_map<int,int> sum;
int i;
for (i = 0; i < nums.size(); i++) {
++sum[nums[i]];
}
for (i = 0; i < nums.size(); i++)
if (sum[nums[i]] == 1)
return nums[i];
}
004 方法二 除余
将所有数据的相同二进制位相加,只出现一次的数据的i位上1or0和总和%3相反
int singleNumber2(vector<int>& nums) {
int bit[32] = { 0 };
for (int num = 0; num < nums.size(); num++) {
for (int i = 0; i < 32; i++) {
bit[i] += (nums[num] >> (31 - i)) & 1;//判断该数字的倒数i位为0还是1
}
}
int result=0;
for (int i = 0; i < 32; i++)
result = (result << 1) + bit[i] % 3;
return result;
}
005 无重复字符的字符串长度最大乘积
int maxProduct(vector<string>& words) {
int i,j;
vector<int> flag(words.size(),0);
int result = 0;
for (i = 0; i < words.size(); i++) {
for (j =0; j < words[i].size(); j++) {
flag[i] |= 1 << (words[i][j] - 'a');
//注意是将1向左移动到位置上,再或运算
//保证与拿来的flag不会变,而且修改对应位置为1
}
}
for (i = 0; i < words.size(); i++) {
for (j = i + 1; j < words.size(); j++) {
if ((flag[i] & flag[j]) == 0)
result = max(result, (int)(words[i].size() * words[j].size()));
}
}
return (result > 0 ? result : NULL);
}
006 排序数组中两个数字之和
vector<int> twoSum(vector<int>& numbers, int target) {
vector<int> result;
int i, j;
i = 0, j = numbers.size()-1;
while (numbers[i] + numbers[j] != target && i < j) {
if (numbers[i] + numbers[j] > target)
--j;
else
++i;
}
result.push_back(i);
result.push_back(j);
return result;
}
007 数组中和为0的三个数
类似于006,先排序,然后选定一个数字target,在序列中找相加等于-target的两个数
vector<vector<int>> threeSum(vector<int>& nums) {
int i, j, k;
vector<vector<int>> result;
sort(nums.begin(), nums.end()); //从小到大排序
//nums[k]为选定的值
if (nums.size() >= 3) {
for (k = 0; k < nums.size(); k++) {
//nums[k]也要去重
while (k > 0 && k < nums.size() - 1 && nums[k] == nums[k - 1])
++k;
i = k + 1;
j = nums.size() - 1;
while (i < j) {
if (nums[i] + nums[j] == -nums[k]) {
result.push_back({ nums[k],nums[i],nums[j] });
//寻找时去重
while (i < j - 1 && nums[i] == nums[i + 1])
++i;
while (i < j - 1 && nums[j] == nums[j - 1])
--j;
++i;
--j;
}
else if (nums[i] + nums[j] > -nums[k])
--j;
else
++i;
}
}
}
return result;
}
008
思路,从第一个数据开始,i存头,j存尾,
如果不满足条件,就把j往后搜
若满足条件,把i+1
要注意题目是 连续的数组,
int minSubArrayLen(int target, vector<int>& nums) {
int i, j; //i存高位,j低位
int result = 66536; //结果
int sum; //当前数据和
if (nums.size() != 0) {
i = 0;
j = i;
sum = nums[i];
while (i<nums.size()&&j < nums.size()) {
if (sum < target){
++j;
if (j >= nums.size()) {
if (i == 0)
result = 0;
break;
}
sum += nums[j];
}
else {
result = min(result,j - i + 1);
sum -= nums[i];
++i;
}
}
}
return result;
}
009
思路类似于008
从头开始扫描,若ij<target,result++ ,需要注意的是,i=j时,用其中一个乘以1,不要重复乘
每次满足条件,j往后移动一个,sum=sumnums[j]
不满足条件,i移动, sum=sum/nums’[i]
int numSubarrayProductLessThanK1(vector<int>& nums, int k) {
int i, j;
int sum;
int result = 0;
i = 0;
j = i;
sum = nums[i];
while (i < nums.size()) {
if (sum < k) { //满足条件
if (j + 1 < nums.size()) {
++result;
++j;
sum = sum * nums[j];
}
else {
result += (j - i) * (j - i + 1) / 2 + 1;
break;
}
}
else {
++i;
sum = nums[i];
j = i;
}
}
return result;
}
009 优化
对现有的乘积小于k的序列ABCD,我们可以统计已D为 末尾的个数,这样我们只需要把j从头到尾走一遍,不需要回溯,观察易得以nums[j]为结尾的满足条件序列为j-i+1
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
int i, j, sum;
int result = 0;
i = 0,j = i,sum = 1;
for (j = 0; j < nums.size(); j++) {
sum *= nums[j];
while (i <= j && sum >= k) {
sum /= nums[i++];
}
if (i <= j)
result += j - i + 1;
}
return result;
}
010 和为k的子数组
只适用于正数
同样是双指针,用i,j指向头和尾,当sum>k时,i向后移,缩小移动窗口,直到不满足移动条件,
当sum<k时,j向后移动,增大移动窗口,直到不满足移动条件
当sum=k时,i和j都向后移动一位
int subarraySum1(vector<int>& nums, int k) {
int i, j, sum, result;
i = 0;
j = 0;
sum = nums[i];
result = 0;
while (i < nums.size() && j < nums.size()) {
if (sum < k) {
if(nums[j]>=0)
++j;
else
if (j >= nums.size())
break;
else
sum += nums[j];
}
else if (sum > k) {
sum -= nums[i];
++i;
if (i >= nums.size())
break;
}
else if (sum == k) {
result++;
sum -= nums[i];
++i;
++j;
if (i >= nums.size()||j>=nums.size())
break;
sum += nums[j];
}
}
return result;
}
正负数都有的正确题解
-
用sum表示包括当前数字在内的前面所有数字之和,连续的数组相加为k,说明sum[j]-sum[i]=k;
-
那我们对每个数字求前缀之和,然后找到前面已经得到的数据前缀之和中有多少个与之相差k的数字(位置不同), n表示前面有n个这样的nums[i],
-
注意我们要将hash(0)初始化为1,因为对第一个nums[j]-k=0的数据来说,它到数组起点的是满足条件的。
-
那么以num[j]结尾的满足条件的子数组有hash(sum[j]-k) = n 个,就可以得到以当前数字结尾的满足k的连续子数组有多少个
-
所以我们需要一个哈希数组,键为前缀和,值为出现了几次,每次拿到新的数据时,它前面的数据都是已知的
-
c++基础知识 map容器
相当于python的字典,有键和值
创建:map<int,int> newmap [= {{1:2} {2:3}};
插入:newmap[K]= n, 创建了键为k的map对,值为n,默认为0;最好是用insert
修改:newmap[k]= n,
查询:n= newmap.count(k),返回k出现的次数
int subarraySum(vector<int>& nums, int k) {
int sum, result;
map<int, int> preHash = { {0,1} };
result = 0, sum = 0;
for (int num : nums) {
sum += num;
//先看hash(sum-k)是否存在,再修改result
result += preHash.count(sum - k) ? preHash[sum - k] : 0;
preHash.count(sum) ? ++preHash[sum] : preHash[sum] = 1;
}
return result;
}
011 0和1个数相同的最长子数组
- 数组中只有0和1,我们把所有的0改为-1,求0和1相等的子数组就转化为求和为0的子数组
- 这和010题有些类似,不过010求的是这样的子数组个数,而011求最长子数组的长度,那我们稍微改变一下,hash表不存出现次数,而是存第一次出现的位置,这样,当再次求得sum-k时,都拿来和第一次出现的位置计算出长度,与当前已有的maxLen, 比较取最大,直到数组遍历结束,
- 那么我们分析开始遍历前应该初始化hash(0)=-1
int findMaxLength(vector<int>& nums) {
int i=0, sum=0, maxLen=0;
map<int, int> indexHash{ {0,-1} };
for (i = 0; i < nums.size(); i++) {
sum += (nums[i] == 0) ? -1 : 1;
if (indexHash.count(sum))
maxLen = max(maxLen, i - indexHash[sum]);
else
indexHash[sum]= i;
}
return maxLen;
}
012 左右两边子数组的和相等
int pivotIndex(vector<int>& nums) {
int i = 0, sum = 0,count = 0;
for (i = 0; i < nums.size(); i++)
sum += nums[i];
for (i = 0; i < nums.size(); i++) {
if (count == (sum - count - nums[i]))
return i;
count += nums[i];
}
return -1;
}
013 二维子矩阵的和
- 观察可得二维子矩阵[r1,c1,r2,c2]的和可由r2c2的二维前缀和减去r2c1,r1c2的前缀和,再加上重复减掉的r1c1前缀和,我们在初始化数组时就将每个位置的前缀和求出来,那么调用二维子矩阵函数的时间复杂度就降到了1
- 求前缀和,我们可以拿当前位置的同列不同行的presum+当前这一行到这里的和sum
int preSum[200][200];
void NumMatrix(vector<vector<int>>& matrix) {
if (matrix.size() > 0) {
preSum[0][0] = matrix[0][0];
for (int j = 1; j < matrix[0].size(); j++) //第一行
preSum[0][j] = preSum[0][j - 1] + matrix[0][j];
for (int i = 1; i < matrix.size(); i++) //第一列
preSum[i][0] = preSum[i - 1][0] + matrix[i][0];
//行
for (int i = 1; i < matrix.size(); i++)
{ //列
for (int j = 1; j < matrix[0].size(); j++) {
preSum[i][j] = matrix[i][j] + preSum[i-1][j]
+ preSum[i][j-1] - preSum[i-1][j-1];
}
}
}
}
int sumRegion(int row1, int col1, int row2, int col2) {
int result = preSum[row2][col2];
if (row1 > 0)
result -= preSum[row1 - 1][col2];
if(col1>0)
result -= preSum[row2][col1-1];
if (row1 > 0 && col1 > 0)
result += preSum[row1 - 1][col1 - 1];
return result;
}
014 字符串中的变位词
这里涉及到C++两个容器可以直接比较是否相等
- 把s1中字符出现的次数用哈希表存起来,到s2中找到一个这样的子串,它的字符哈希表和s1一样
- 对s2的子串,将它出现的字符在s1中-1,处理完后若s1的哈希表全部为0,说明匹配成功
- 我们每次都是处理s2中等长的子串,可以用一个固定长度窗口,每次匹配不成功时窗口向后移动一位,对移出窗口的字符+1,进入窗口的字符-1
bool checkInclusion(string s1, string s2) {
vector count(26, 0), zero(26.0);
int i;
if (s1.length() > s2.length())
return false;
for (i = 0; i < s1.length(); i++) { //初始化哈希表
++count[s1[i] - 'a'];
--count[s2[i] - 'a'];
}
if (count==zero) //匹配成功
return true;
//从s2中s1长度处开始搜索,这里i每次都是指向滑动窗口的下一位
for (i = s1.length(); i < s2.length(); i++) {
//失败,移动窗口向后走
--count[s2[i] - 'a']; //新加入的s2[i+1]
++count[s2[i - s1.length()] - 'a']; //被删掉的s2[i - s1.size()+1]
if (count == zero) //匹配成功
return true;
}
return false;
}
015 字符串中的所有变位词
014稍微修改下就可以
vector<int> findAnagrams(string s, string p) {
vector<int> count(26, 0), zero(26.0), result;
int i;
if (s.length() > p.length())
return result;
for (i = 0; i < s.length(); i++) { //初始化哈希表
++count[s[i] - 'a'];
--count[p[i] - 'a'];
}
if (count == zero) //匹配成功
result.emplace_back(0);
//从s2中s1长度处开始搜索,这里i每次都是指向滑动窗口的下一位
for (i = s.length(); i < p.length(); i++) {
//失败,移动窗口向后走
--count[p[i] - 'a']; //新加入的s2[i]
++count[p[i - s.length()] - 'a']; //被删掉的s2[i - s1.size()]
if (count == zero) //匹配成功
result.emplace_back(i - s.length()+1);
}
return result;
}
016 不含重复字符的最长字符串
同样用哈希表存子串中出现的字符,新的字符在哈希表中不存在,从第一个字符开始,如果没有重复,移动右指针,如果有重复,移动左指针
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> count;
int result = 0;
int i = 0, j = 0;
if (s.length() <= 0)
return 0;
for (i; i < s.length(); i++) {
while (j < s.length() && !count.count(s[j]))
count[s[j++]] = 1;
result = max(result, j - i);
count.erase(s[i]); //注意这里要删除,因为我们判断有无重复的条件是这个字符在哈希表中是否存在
}
return result;
}
017 含有所有字符的最短子字符串(s,t)
用哈希表存t中的字符,并记录好出现次数
在s中遍历,
若当前字符在哈希表中,则计数-1,不在哈希比中的字符不处理
若哈希表所有值小于等于0,找到一个满足条件的子串
左指针向后移动一位,对滑出窗口的字符,若存在哈希表中,则计数+1
若处理后不满足条件,右指针向后移动一位
// 哈希表判断函数
bool lessORequalZero(map<char, int> count) {
for (int i = 0; i < count.size(); i++) {
if (count[i] > 0)
return false;
}
return true;
}
string minWindow(string s, string t) {
map<char, int> count;
string result;
int min = 65536;
int i = 0,start=0;
if (s.length() < t.length())
return result;
for (int i = 0; i < t.length(); i++) {
count[t[i]]++;
count[s[i]]--;
}
if (lessORequalZero(count)) return s.substr(0, t.length());
for (int i = t.length(); i < s.length(); i++) {
if (count.count(s[i]))
count[s[i]]--;
while (lessORequalZero(count)) {
if (min > i - start + 1) {
min = i - start + 1;
result = s.substr(start, i - start + 1);
}
if (count.count(s[start]))
count[s[start]]++;
start++;
}
}
return result;
}
018 有效的回文
- 双指针,指向头和尾,
若相等,向中间移动,
若不相等,不是回文串
不是大小写字母和数字则移动指针,i指针始终要小于j - 这里用到了两个c++库函数
isalnum(char a)判断是否是字母或十进制数字,#include<cctype>
tolower/toupper(char a)字母转小写/大写,#include<cctype>
bool isPalindrome(string s) {
int i= 0, j = s.length()-1;
while (i < j) {
while (i < j && !isalnum(s[i]))
i++;
while (i<j && !isalnum(s[j]))
j--;
if (tolower(s[i]) != tolower(s[j]))
return false;
else {
i++;
j--;
}
}
return true;
}
019 最多删除一个字符得到回文
同样设置头尾指针:
若相等,向中间靠拢
若不相等,那么可能的结果一定是从这两个中间删掉一个
判断两种情况是否能得到回文串(删掉后判断剩下的是不是回文串) ,可以结合上一题,写一个判断i到j是否为回文串的函数来调用
bool iTOj(string s, int i, int j) {
while (i < j) {
if (s[i] != s[j])
return false;
else {
++i;
--j;
}
}
return true;
}
bool validPalindrome(string s) {
int i = 0, j = s.length() - 1;
if (iTOj(s,0,s.length())) return true;
while (i < j) {
if (s[i] == s[j]) {
++i;
--j;
}
else {
if (iTOj(s, i + 1, j) || iTOj(s, i, j - 1)) return true;
else return false;
}
}
return true;
}
020 回文子字符串的个数
这道题要统计回文子串个数,也就是每个字符作为中心的回文串总和
要注意的是回文串长度有可能是奇数也有可能是偶数
为了避免重复,在对每个字符是s[i]做统计时,统计以s[i]为中心和s[i] s[i+1]为中心两种情况总和,需要一个函数来统计
int countCenter(string s, int i, int j) {
int count = 0;
while (i >= 0 && j <= s.length() - 1) {
if (s[i] != s[j])
return count;
++count;
--i;
++j;
}
return count;
}
int countSubstrings(string s) {
int result = 0;
for (int i = 0; i < s.length(); i++)
{
result += countCenter(s, i, i);
result+= countCenter(s, i, i+1);
}
return result;
}