剑指offer(41---50)

41、和为s的连续正数序列

小明很喜欢数学,有一天他在做数学作业时,要求计算出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<int> FindNumbersWithSum(vector<int> array,int sum) {
        vector<int> 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<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int>> 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<middle)
        {
            if(curname == sum)//相等,就是一条路径
            {
                vector<int> 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<middle)
            {
                curname-=small;//small变了,curname也要变
                ++small;
                if(curname==sum)
                {
                     vector<int> 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;  
    }
};
42、和为s的两个数字

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

这个题就是我们上面41题说的比较简单的那种情况,使用双指针的方法,这里要注意的是找到的第一组就是乘积最小的(使用数学证明,这里就不多说了)
实现代码:

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        vector<int> 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;
    }
};
43、左旋转字符串

汇编语言中有一种移位指令叫做循环左移(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()<n)
            return "";
        //先整体逆转
        reverse(str.begin(),str.end());
        
        //然后先逆转前str.size()-n个字符
        reverse(str.begin(),str.begin()+str.size()-n);
        
        //最后逆转后n个字符
        reverse(str.begin()+str.size()-n,str.end());
        return str;
    }
};
44、反转单词顺序列

43题已经介绍过了,这里就不多做介绍了,在牛客网上实现的代码为:

45、扑克牌顺子

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<int> numbers ) {
        if(numbers.size()<5)
            return false;
        //先给数组排序,按顺序
        sort(numbers.begin(),numbers.end());
        int countZero=0;//计算0的个数
        int countGap=0;//计算排序后的数组相邻两个数之间间隔几个数
        for(int i=0;i<numbers.size()-1;++i)
        {
            if(numbers[i]==0)//这个一定要放在第一个判断,因为排序后的数组0一定在前面
            {
                countZero++;
                continue;
            }
            if(numbers[i]==numbers[i+1])//对子的情况
                return false;
            countGap += numbers[i+1]-numbers[i]-1;//计算间隔几个数
        }
        if(countZero>=countGap)
            return true;
        return false;
    }
};
46、孩子们的游戏

这个就是经典的约瑟夫环问题,即将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<int> L;
        for(int i=0;i<n;++i)
        {
            L.push_back(i);
        }
        list<int>::iterator it=L.begin();//迭代器,指向要删除的元素
        while(L.size()>1)
        {
            for(int i=1;i<m;++i)//从1开始报数,报到m-1
            {
                it++;//走到要删除的元素
                if(it==L.end())
                    it=L.begin();
            }
            //保存一下要删除的元素的下一个,it走到这里继续循环
            list<int>::iterator next=++it;//由于++it会产生副作用,因此下一次要先--it,然后删除,然后it走到next处
            if(next == L.end())
                next=L.begin();
            --it;
            L.erase(it);
            it=next;
        }
        return *(it);
    }
};
47、求1+2+3+…+n

求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();
    }
};
48、不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

这个题不能用四则运算,因此就考虑到使用位运算,如果两个数的二进制位相加,第一步我们先不记进位,直接相加,那么例如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;
    }
};
49、把字符串转换为整数

将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为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;
    }
};
50、数组中重复的数字

在一个长度为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<int,bool> m;
        for (int i = 0; i <length; i++) 
        {
            if (m[numbers[i]] == true) 
            {
                duplication[0] = numbers[i];
                return true;
            }
            m[numbers[i]] = true;
        }
        return false;     
    }
};

但是这种方式使用了额外的空间,我们如果不想使用额外的空间,就可以例如:
数字都是在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;i<length;++i)
        {
            if(numbers[i]<0 || numbers[i]>length-1)
                return false;
        }
        for(int i=0;i<length;++i)
        {
            while(numbers[i]!=i)//将numbers[i]放在numbers[numbers[i]],看numbers[i]与numbers[numbers[i]]是否相等,如果相等,表示重复
            {
                if(numbers[i]==numbers[numbers[i]])
                {
                    *duplication=numbers[i];
                    return true;
                }
                //不相等,将numbers[i]放在numbers[numbers[i]]上
                swap(numbers[i],numbers[numbers[i]]);
            }
        }
        return false;
    }
};
深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值