小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列?
在看到这个题目时,我们可以想到一个类似比较简单的一个题目,就是和为s的两个数(在递增序列中),一般这种题目我们第一时间可以想到的方法就是固定一个数字,在其他数字中遍历看是否有何这个数相加为s的数,但是这种方式的时间复杂度为O(n^2),因此我们要考虑效率比较高的方法,可以定义一个指针i指向数组第一个数,一个指针j指向数组最后一个元素,如果arr[i]+arr[j]的结果是s,直接返回,如果大于s,则j向前走一步,否则i向后走一步,实现代码如下:
class Solution {
public:
vector FindNumbersWithSum(vector array,int sum) {
vector result;
if(array.size()<2)//说明只有一个或者0个数
return result;
int begin=0;
int end=array.size()-1;
while(begin < end)
{
int cursum = array[begin]+array[end];
if(cursum == sum)
{
//第一组就是乘积最小的
result.push_back(array[begin]);
result.push_back(array[end]);
return result;
}
else if(cursum < sum)
begin++;
else
end--;
}
return result;
}
};
而这里这个问题是求和为s的连续正数序列
也可以利用上述的思想,我们初始化small为1,big为2,此时介于small和big之间的序列就是{1,2},序列和curname为3,如果从small到big的序列和curname等于s,直接将small到big的序列插入result,如果从small到big的序列和curname大于s,我们可以从序列中去掉较小的值,也就是增大small,此时curname就要减小small,一直增大small直到curname与sum相等,否则如果从small到big的序列和小于s,我们可以从序列中增大big,让这个序列包含更多的值,此时curname也要加上向后扩充的值(每次big++就是向后扩充一个,因为是连续的序列)这个序列至少有两个数字,因此small一直增加到(1+s)/2为止,因此可以实现代码:
class Solution {
public:
vector > FindContinuousSequence(int sum) {
vector> result;//存每一个结果序列
if(sum<3)//说明少于两个数,无法计算
return result;
int small=1;//先让small初始化为1
int big=2;
int curname=small+big;//每次比较的就是curname和sum
int middle=(1+sum)/2;
while(small tmp;//存这一个序列
for(int i=small;i<=big;++i)
{
tmp.push_back(i);
}
result.push_back(tmp);
}
//说明curname与sum不相等
//如果curname大于sum,说明要增大small(去掉较小的),也就是只要curname一直小于sum,就要一直增大small,只要small没有到(1+sum)/2
while(curname>sum && small tmp;//存这一个序列
for(int i=small;i<=big;++i)
{
tmp.push_back(i);
}
result.push_back(tmp);
}
}
//说明curname小于sum,big向后扩充,curname也向后加
big++;
curname+=big;
}
return result;
}
};
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
这个题就是我们上面41题说的比较简单的那种情况,使用双指针的方法,这里要注意的是找到的第一组就是乘积最小的(使用数学证明,这里就不多说了)
实现代码:
class Solution {
public:
vector FindNumbersWithSum(vector array,int sum) {
vector result;
if(array.size()<2)//说明只有一个或者0个数
return result;
int begin=0;
int end=array.size()-1;
while(begin < end)
{
int cursum = array[begin]+array[end];
if(cursum == sum)
{
//第一组就是乘积最小的
result.push_back(array[begin]);
result.push_back(array[end]);
return result;
}
else if(cursum < sum)
begin++;
else
end--;
}
return result;
}
};
汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。
看到这个题,很多人会想到那个经典的调整单词顺序问题,也就是将类似于“I am a student”转为“student a am I”,这个问题一般采用的就是先将字符串整体逆转,变为“tneduts a ma I”,然后每个单词的顺序即可,此时实现它的代码为:
void Reverse(string& str, int begin, int end)
{
if (str.size() == 0)
return;
while (begin < end)
{
char tmp = str[begin];
str[begin] = str[end];
str[end] = tmp;
begin++;
end--;
}
}
string ReverseSentence(string& str)
{
if (str.size() == 0)
return nullptr;
Reverse(str, 0, str.size()-1);//先整体逆转
//翻转每个单词
int begin = 0;//指向单词的起始位置
int end = 0;//指向单词的终点位置
while (str[begin] != '\0')
{
if (str[begin] == ' ')//先将该单词前面的空格处理掉
{
begin++;
end++;
}
else if (str[end] == ' ' || str[end] == '\0')//如果end遇到空格或者\0,说明begin到end-1是一个单词
{
Reverse(str, begin, --end);
begin = ++end;//begin走到end+1的位置
}
else//说明还不是一个单词,end继续往后走
end++;
}
return str;
}
那么这里我们要处理的是左旋字符串的问题,可以从上面的问题得到启发,也就是比如要将abcXYZdef左移三个,成为XYZdefabc,也就是我们将abc和XYZdef作为两部分,然后整体逆转,然后分别逆转即可,也就是先逆转为fedZYXcba,然后分别逆转成为XYZdefabc,
实现代码:
class Solution {
public:
string LeftRotateString(string str, int n) {
if(str.size()
43题已经介绍过了,这里就不多做介绍了,在牛客网上实现的代码为:
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
把这5个牌当成一个数组中的元素,要连成一个顺子(大小王暂且当做0,用来填补需要的牌),首先我们可以将数组排好序,此时如果有0(大小王),就一定在数组的最前面,统计0的个数,剩下的元素如果有对子,就直接返回false,因为肯定不能构成顺子了,然后countGap统计相邻两个元素之间差几个数,如果0的个数大于或者等于countGap,则表示可以填补完空缺的元素,返回true,否则返回false。
实现代码:
class Solution {
public:
bool IsContinuous( vector numbers ) {
if(numbers.size()<5)
return false;
//先给数组排序,按顺序
sort(numbers.begin(),numbers.end());
int countZero=0;//计算0的个数
int countGap=0;//计算排序后的数组相邻两个数之间间隔几个数
for(int i=0;i=countGap)
return true;
return false;
}
};
这个就是经典的约瑟夫环问题,即将0,1,2…n-1这n个数围成一个圈,从0开始删除第m个数(下一次从删除的那个数的下一个开始计算要删除的数),求剩下的最后一个数,例如0,1,2,3,4这5个数,从0开始删除第3个数,则第一次删除2,剩0,1,3,4,再从3开始删除第三个数,即第二次删除0,剩1,3,4,再从1开始删除第三个数,即第三次删除4,剩1,3,再从1开始删除第三个数,即第四次删除1,最后剩下的数是3。
由于这里要将元素围成一个圈,因此我们第一时间想到的是使用一个数据结构来模拟这个圆圈,想到使用环形链表(如果可以使用模板库中的数据结构,就使用std::list来实现环形链表,但是要注意list本身不是一个环形链表,因此当迭代器走到链表末尾时,要将迭代器移到链表的头部),使用it迭代器用来表示当前要删除的元素的位置,next迭代器表示删除的元素的下一个元素的位置,每次it走到m-1处(要是走到末尾了就直接赋为begin()),然后保存它的下一个元素的迭代器next,然后删除it所在的元素,it走到next,循环直到环形链表只剩下1个元素即可。
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if(n<1 || m<1)
return -1;
list L;
for(int i=0;i::iterator it=L.begin();//迭代器,指向要删除的元素
while(L.size()>1)
{
for(int i=1;i::iterator next=++it;//由于++it会产生副作用,因此下一次要先--it,然后删除,然后it走到next处
if(next == L.end())
next=L.begin();
--it;
L.erase(it);
it=next;
}
return *(it);
}
};
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
这个题不难实现,但是这里有众多要求,因此我们不能使用循环,而要使用递归还要考虑终止条件,因此也不能使用,而我们可以想象实现n次累加操作就可以实现,因此可以使用构造函数,构造n个对象,调用了n次构造函数,在构造函数中实现累加相关的代码,记得要将成员变量设置为static的,这样才能保存上一次的结果,实现代码:
class H{
public:
H()
{
++n;
sum += n;
}
static int GetSum()
{
return sum;
}
static void Reset()
{
n=0;
sum=0;
}
private:
static int n;
static int sum;//使用static,表示共用这一个对象,可以保存上一次的结果
};
int H::n=0;
int H::sum=0;//初始化
class Solution {
public:
int Sum_Solution(int n) {
H::Reset();//由于有多个测试用例,所以在下一次输入用例时都要重新将n和sum置为0
H* a=new H[n];
delete[]a;
a=nullptr;
return H::GetSum();
}
};
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
这个题不能用四则运算,因此就考虑到使用位运算,如果两个数的二进制位相加,第一步我们先不记进位,直接相加,那么例如5+17也就是00101+10001,此时不记进位,就为10100为12,其实就相当于异或操作,第二步就是记上进位,想要记上进位,只有1+1时才会有进位,因此只有哪一位二进制位相与为1时才能表示这一位要进位,因此这里要记上进位就将两个数相与,进位是想高位进位,因此相与后的结果向左移动一位,即(5&7)<<1=00010,第三步就是将前两步继续重复,直到没有进位,实现代码:
class Solution {
public:
int Add(int num1, int num2)
{
int sum;
int carry;
do{
sum=num1^num2;
carry=(num1&num2)<<1;
num1=sum;//等于一步步将结果加给num1
num2=carry;
}while(num2!=0);
return num1;
}
};
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
这个问题就是模拟实现一个atoi函数,我们知道字符转为整数使用num=num*10+(*str-‘0’)即可,但是我们还要考虑一些条件,例如:
(1)如果当前字符<'0’或者>‘9’,就是不合法的,num直接为0,循环结束;
(2)考虑正负数,这个在遍历第一个字符时就要确定;
(3)考虑溢出条件,如果是正数,不能大于0x7FFFFFFF,如果是负数,绝对值不能大于0x80000000;
(4)我们要对非法输入进行处理,atoi函数中处理是使用一个全局变量来区别,因此这里我们实现的时候也要定义一个全局变量g_Status来区分当前的输入是否是合法输入。
class Solution {
//这个问题要考虑的方面挺多的,首先要知道atoi这个函数处理非法输入是怎么处理的,也就是str为空或者为"0"时是如何处理的
//其实atoi在处理时使用一个全局变量,如果输入为"0",不用设置,如果为非法输入,要进行设置
//接下来还要考虑字符串中是否含有"+"/"-"这样的字符,如果有,要考虑正负数
//还有如果字符串中某个字符<="0"或者>="9",直接为0,结束循环
//还要考虑溢出问题,也就是如果是正数大于0x7FFFFFFF或者为负数大于0x80000000,也直接为0,循环结束
public:
enum Status{k_Valid=0,k_Invalid};//用来表示是否是非法输入
int g_Status=k_Valid;//用这个全局变量来表示是否是非法输入
int StrToInt(string str) {
g_Status=k_Invalid;//表明此时还是非法输入
long long result=0;//结果
const char* Cstr=str.c_str();
if(Cstr!=nullptr && *Cstr!='\0')
{
//考虑正负数的情况
int flag=1;//表示为正数
if(*Cstr=='+')
Cstr++;
else if(*Cstr=='-')
{
Cstr++;
flag = -1;//表示为负数
}
//开始转换
while(*Cstr!='\0')
{
//继续考虑是否有<='0'和>='9'的情况
if(*Cstr>='0' && *Cstr<='9')//说明合法
{
g_Status=k_Valid;
result=result*10+(*Cstr-'0');
Cstr++;
//考虑溢出问题
if(((flag>0) && (result>0x7FFFFFFF)) || ((flag<0) && (result>0x80000000)))
{
g_Status=k_Invalid;//不合法
result=0;
break;
}
}
else//说明不合法
{
g_Status=k_Invalid;
result=0;
break;
}
}
if(g_Status==k_Valid)
{
result=flag*result;//将正负数区别好
}
}
return (int)result;
}
};
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
拿到这个题,我们第一时间可以想到利用哈希的方式来实现,也就是这个unordered_map中与数组中的元素建立对应关系,并且其对应的属性要么是true,要么是false,遍历元素,如果出现过当前数字,直接返回true,如果没有出现过,加入unordered_map
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
unordered_map m;
for (int i = 0; i
但是这种方式使用了额外的空间,我们如果不想使用额外的空间,就可以例如:
数字都是在0到n-1,假设没有重复数字,那么将这个数组排完序,每个数都在唯一的位置,并且numbers[i]=i;
扫描整个数组,假设numbers[i]=i,即这个数字在他该在的位置,继续扫描下一个;
如果numbers[i]!=i,我们应该将numbers[i]放在他该放在的位置,即numbers[numbers[i]];
此时如果numbers[i]==numbers[numbers[i]],说明重复了,就找到了重复的数字,如果numbers[i]!=numbers[numbers[i]],就将numbers[i]放在numbers[numbers[i]]上去,处理numbers[numbers[i]],直到重复或者和i相等。
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
if(numbers==nullptr || length<=0)
return false;
for(int i=0;ilength-1)
return false;
}
for(int i=0;i