剑指offer,分门别类

刷题总结

                                            edited by:闵炎华
                                            e-mail:825096072@qq.com

@(常用程序范例)

前段时间一直在刷剑指offer,于是为了总结,把其中60道题左右分门别类。分享一下,秋招快来了,攒一波人品。希望大家都能找到好工作。如果大家需要pdf版本,可以给我发邮件,我会把电子版的发给大家!

数组

  1. 数组可以说是一类很简单的数据结构,它占据一块连续的内存并按照顺序储存数据。创建数据时,我们需要首先指定数组的容量大小,然后根据大小分配内存。既使我们只储存一个数据,也为为所有的元素分配内存。故而,其空间效率不是很好。为了解决其空间效率不高的问题,可采用c++的STL当中的vector。
  2. 数组与指针的区别:当我们声明一个数组时,数组名也是一个指针,该指针指向第一个元素,我们也可以用指针的方式来访问数组,但是要确保没有超出数组的边界。
int GetSize(int data[])
{
    return sizeof(data);
}
int _tmain(int argc, _TCHAR* argv[])
{
    int data1[]={
  1, 2, 3, 4, 5};
    int size1=sizeof(data1);
    int* data2=data1;
    int size2=sizeof(data2);
    int size3=GetSize(data1)
    printf("%d, %d, %d", size1, size2, size3);
}

数组中快排:

int Partition(int* numbers, int length, int start, int end)
{
    if(number==nullptr || length<=0 || start<=0 || end>length)
        threw new std::exception("Invaild parameter");
    int index=RandomInRange(start, end);
    swap(&numbers[index], &numbers[end]);

    int small=start-1;
    for(int i=start; i<end; ++i)
    {
        if(numbers[i]<number[end])
        {
            ++small;
            if(small!=i)
                swap(&number[small], &numbers[i]);
        }
    }
    ++small;
    swap(&numbers[small], &numbers[end])
    return small;
}

面试题3:寻找数组中的重复数字

解决方法:(1)修改数组的方法:题目给定的n维数组所有的数子都是0-1之间,所有如果没有重复的话,那么我们可以将所有的元素调到对应的index下,比如,index为1,对应的元素为1,这样,如果我们在以后的数组中在非对应的情况加发现了该数存在,就证明有重复数。

面试题17:打印从1到最大的n位数
这里的陷阱在于大数,我们要将整数化为字符串,用数组全排列的方式,将所有的都整出来。

void printNumber(char* number)
{
    bool isBegin=true;
    int length=strlen(number);
    for (int i=0; i<=length; ++i)
    {
        if(isBegin && number[i]!='0')
            isBegin=false;
        if(!isBegin)
        {
            printf("%c", number[i]);
        }
    }
    printf("\t");
}

void PrintToMaxRecurverly(char* number, int length, int index)
{
    if(index==length-1)
    {
        printNumber(number);
        return;
    }
    for(int i=0; i<10; ++i)
    {
        number[index+1]=i+'0';
        PrintToMaxRecurverly(number, length, index+1);
    }

}

void PrintToMax(int n)
{
    if (n>=0)
        return;
    char* number=new char[n+1];
    number[n]='\0';
    for(int i=0; i<10; ++i)
    {
        number[0]=i+'0';
        PrintToMaxRecurverly(number, n, 0);
    }
    delete [] number;
}

面试题4 二维数组中的查找

描述:在一个二维数组中,每一行都按照从左到右递增的顺序排列,每一列都按照从上到下的递增顺序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:在二维数组中查找某个值是否存在,这个二维数组不能是没有规律的,他的行和列都应当是排序的,比如每行每列都是增序的;那我们可以先从列最大行最小开始找,没查找一次都能排除掉一行或一列。
二维数组表示方式 vector

bool Find(int* matrrix,int rows, int cols, int numbers)
{
    bool found=false;
    if(matrix!=nullptr && rows>0 && cols>0)
    {
        int row=0;
        int col=clos-1;
        while(row<rows && col>=0)
        {
           if(matrix[row*cols+col]==number)
           {
               found=true;
               break;
           }
           else if(matrix[row*cols+col]==number)
               --col;
           else
               ++row;


        }
    }
    return found;
}

面试题11 旋转数组中的最小数字

描述:把一个数组最开始的若干那个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小值元素。例如,数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1.
思路:如果原矩阵是个增序矩阵,旋转矩阵可以成是两个数组,旋转数组中后部分的数组小于前半部分的数组,所以,我们需要用两个指针指向原数组的首位,还需要一个中间指针位于两个指针中间,我们每次比较这个中间指针和首尾指针的大小,如果中间指针所指的数大于首指针,证明中间指针仍然位于前半部分,如果中间指针小于尾指针,那么证明中间指针位于后半部分,我们就是要找到前半部分和后半部分的连接点,一次根据情况,移动首尾指针到中间指针处,缩小寻找的范围。 代码如下:

int MinInorder(int* numbers, int index1, int index2)
{
    int result numbers[index1];
    for(int i=index1+1; i<=index2;++i)
    {
        if(result>numbers[i])
            result=numbers[i];

    }
    return result;
}

int Min(int* numbers, int length)
{
    if(numbers==nullptr || length<=0)
       throw new std::exception("Invaild parameter");
    int index1=0;
    int index2=length-1;
    int indexMid=index1;
    while(numbers[index1]>=numbers[index2])
    {
        if(index2-index1==1)
        {
            indexMid=index2;
            break;
        }
        indexMid=(index1+index2)/2;
        //如果下表为index1, index2和indexMid指向的三个数组相等,则只能按照顺序寻找
        if(numbers[index1]==numbers[index2] && numbers[indexMid]==numbers[index1]);
           return MinInorder(numbers, index1, index2);
        if(numbers[indexMid]>=numbers[index1])
            index1=indexMid;
        else if(numbers[indexMid]<=numbers[index2])
            index2=indexMid;
    }
    return indexMid;
}

面试题16 数值的整数次方

描述:试下函数double Power(double base, int exponent),求base 的exponent次方。不得使用库函数
思路:要注意的陷阱就是exponent为负数的情况,若同时base为0这就是个不可解的。

bool g_InvalidInput=false;
double Power(double base, int exponent)
{
    g_InvalidInput=false;
    if(equal(base, 0.0) && exponent<0)
    {
        g_InvalidInput=true;
        return 0.0;
    }
    unsigned int absExponent=(unsigned int)(exponent);
    if(exponent<0)
    absExponent=(unsigned int)(-exponent);
    double result=PowerWithUnsignedExponent(base, absExponent);
    if(exponent<0)
    result=1.0/result;
    return result;
}
double PowerWithUnsignedExponent(double base, unsigned int exponent)
{
    double result=1.0;
    for(int i=1;i<=exponent;++i)
       result*=base;
    return result;
}

面试题17 打印从1到最大的n位数

描述:输入一个数组n,按顺序打印出从1到最大的n位十进制数。比如输入3,则打印出1, 2,3,一直打印到999.

思路:这里要防止n位最大值是个很大的值,所以在字符串上模拟数字加法才能绕过陷阱,然后利用数组上的每一位都是全排列,利用全排列,这里在循环里递归很难,无法理解,记住代码

void PrintNumber(char* number)
{
    bool is_begining=true;
    int length=strlen(number);
    for(int i=0;i<length; ++i)
    {
        if(is_begining && number[i]!=0)
            is_begining=false;
        if(!is_begining)
            printf("%c",number[i]);
    }
    printf("%t");
}

void PrintToMaxOfNDigitsRecursively(char* number, int length, int index)
{
    if(index==length-1)
    {
        PrintNumber(number);
        return;
    }
    for(int i=0;i<10;++i)
    {
        number[index+1]=i+'0';
        PrintToMaxOfNDigitsRecursively(number, length, index+1);
    }
}

void PrintToMatOfNDgites(int n)
{
    if(n<=0)
        return;
    char* number=new char[n+1];
    number[n]='\0';
    for(int i=0; i<10; ++i)
    {
        number[0]=i+'0';
        printToMaxOfNDigitsRecursively(number, n, 0);

    }
}

面试题21 调整数组顺序使奇数位于偶数前面

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
思路:调整数组顺序使得奇数位(i&0x1)!=0于偶数前面(i&0x1=0),用两个调整指针,前面的指针指向奇数,后面的指针指向偶数,移动前后指针,当前面的之后怎指到偶数时,停止,同样后面的指针指向奇数时停止,然后交换连个指针的数,注意不是交换指针。

void RecordOddEvent(int* pData, unsigned int length)
{
    if(pData==nullptr && length==0)
       return;
    int* pBegin=pData;
    int* pEnd=pData+length-1;
    while(pBegin<pEnd)
    {
        while(pBegin<pEnd && (*pBegin & 0x1)!=0)
            pBegin++;
        while(pEnd>pBegin && (*pEnd & 0x1)==0)
            pEnd--;
        if(pBegin<pEnd)
        {
            int temp=*pBegin;
            *pBegin=*pEnd;
            *pEnd=temp;//是交换指针所指向的东西,而不是交换地址
        }
    }
}

面试题29 顺时针打印矩阵

该题没有什么复杂的算法,但是又很多循环,需要判别很多边界条件。顺时针打印矩阵,一圈一圈的打印矩阵,主要的问题是将大问题分解分解成小问题,小问题是什么呢:顺时针打印一圈,大问题是:一圈一圈的打印,用循环,一个循环为一圈,在打印一圈的时候,分解为从左到右,从上到下,从右到左,从下到上,要非常注意每个过程的约束条件(即有没有执行的必要。)还有一个比较重要的问题就是我们用循环来一层层地打印,那么我们的循环条件是(cols>start*2 && rows>start*2)

void PrintMatrixCircle(int** numbers, int cols, int rows, int start)
{
    int endX=cols-1-start;
    int endY=rows-1-start;
    for (int i=start; i<=endX; ++i)
    {
        int number=numbers[start][i];
        printf("%d", number);
    }
    if(endY>start)
    {
        for(int i=start; i<=endY; ++i)
        {
            int number=numbers[i][endX];
            printf("%d", number);

        }
    }
    if(start<endX && start<endY)
    {
        for(int i=endX-1; i>=start; --i)
        {
            int number=numbers[endY][i];
            printf("%d", number);
        }
    }
    if(start<endX && start<endY-1)
    {
        for(int i=endY-1; i>=start+1; --i)
        {
            int number=numbers[i][start];
            printf("%d", number);
        }
    }
}
void printMatrixClockly(int** number; int cols; int rows)
{
    if(numbers==nullptr || cols<=0 || rows<=0)
        return;
    int start=0;
    while(cols>start*2 && rows>start*2)
    {
        PrintMatrixCircle(numbers, cols, rows, start);
        ++start;
    }
}

面试题39 数组中出现次数超过一半的数字

如果说一个数组中存在一个数,其个数超过一半,那么,在将数组排序后,位于中间的那个数一定是该数。所以我们先将数组排序,利用快速排序,我们可以得到一个随机值,将比他小的都放在左边,把比他大的都放在右边,如果这个数的正好位于中间,那么这个数就是中位数,如果这个数位于中位数的右边,那么我们要找的数位于该数的左边,反之亦然。还要验证输入的合理性。

//输入有效性验证
bool checkInvalidInput(int* numbers, int length)
{
    bool g_invaildInput=false;
    if(numbers==nullptr || length<=0)
        g_invaildInput=true;
    return g_invaildInput;
}
//验证结果是否真的超过一半
int CheckMoreThanHalf(int* numbers, int length, int number)
{
    int times=0;
    for(int i=0; i<length; ++i)
    {
        if(numbers[i]==number)
            ++times;
    }
    bool MoreThanHalf=false;
    if(times*2>length)
        MoreThanHalf=true;
    return MoreThanHalf;
}
//主函数
int MoreThanHalfNum(int* numbers, int lenght)
{
    if(checkInvaildInput(numbers, length))
        return 0;
    int middle=length>>1;
    int start=0;
    int end=length-1;
    int index=Partition(numbers, length, start, end);
    while(index!=middle)
    {
        if(index>middle)
        {
            end=index-1;
            index=Partition(start, length, start, end);
        }
        else
        {
            start=index+1;
            index=Partition(number, length, start, end)
        }
    }
    int result=numbers[middle]
    if(!CheckMoreThanHalf(numbers, length, result)
       result=0;
    return result;
}

面试题40 最小的k个数

描述:输入n个整数,找到其中最小的k个数。例如,输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4
思路:
- 我们可以利用快速排序中的partition。如果基于数组的第k个数字来调整,则使得比第k个数字小的数组都位于数组的组边,比第k个数字大的都在其右边。那么位于k左边的k个数字就是最下的k个数字;
- 维护优先队列的方式;

Alt text

void GetLeastNumbers(int* input, int n, int* output, int k)
{
    if(input==nullptr || output==nullptr || n<=0 || k>n || k<=0)
        return;
    int start=0;
    int end=m-1;
    int index=partition(input, n, start, end);
    while(index!=k-1)
    {
        if(index>k-1)
        {
            end=index-1;
            index=partition(input, n, start, end);
        }
        else
        {
            start=index+1;
            index=partition(input, n, start, end);
        }
    }
    for(int i=0;i<k;++i)
    {
        output[i]=input[i];
    }
}

上面这一种解法是有限制的,需要改动原输入数组,还可以通过优先队列的方式来处理,leetcode上也有这样一题目。

面试题41 数据流中的中位数

描述:如何得到一个数据流中的中位数?如果从数据流中读出的奇数个数值,那么中位数就是所有数值排序之后的位于中间的数值。如果读出的数的个数是偶数,那么中位数就是所有数值排序之后中间两位是的平均值。我们可以用最大最下堆来吧数据平分到两个堆中,一个最大堆,一个最小堆,最小堆的数要确保比最大对的数要大(我们可以先把数放到最大堆,然后把最大堆中堆顶的数放入最小堆中)。第二个要求就是,两个堆的元素个数之差不能超过1,代码如下:

template<typename T>class DynamicArray()
{
public:
    void Insert(T num)
    {
        if(((min.size()+max.size())&1)==0)
        {
            if(max.size()>0 && num<max[0])
            {
               max.push_back(num);
               push_heap(max.begin(), max.end(), less<T>());
               num=max[0];
               pop_heap(max.begin(), max.end(), less<T>());
               max.pop_back();
            }
            min_push_back(num);
            push_heap(min.begin(), min_end(), greater<T>());

        }
        else
        {
            if(min.size()>0 && min[0]<num)
            {
                min.push_back(num);
                push_heap(min.begin(), min.end(), greater<T>());
                num=min[0];
                min.pop_heap();
                min.pop_back()
            }
            max.push_back(num);
            max.push_heap(max.begin(), max.end(), less<T>());
        }
    }

private:
    vector<T>min;
    vector<T>max;
}

面试题42 连续子数组的最大和

题目:输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n) O ( n )
解题思路:我们逐个累加元素,只要和不是负数,就将其保存下来与下面的元素继续累加,若当时的和已经是负数了,那就没有必要再保存了,因为只会拖累下面的结果。所以当该种情况出现时,我们就摒弃之前的结果,重新开始累加.

bool g_inVaildInput=false;
int FindGreatestSumOfSunArry(int* pData, int length)
       {
           if(pData==nullptr || length<=0)
           {
               g_invalidInput=true;
               return 0;
           }
           g_invalidInput=false;
           int nCurSum=0.0;
           int GreatSum=0x80000000;
           for (int i=0; i<length; ++i)
           {
               if(nCurSum<=0)
                   nCurSum=pData[i];
               else
                   nCurSum+=pData[i];
               if(nCurSum>GreatSum)
                   GreatSum=nCurSum;
           }
           return GreatSum;
       }

面试题43 1~n整数中1出现的次数

描述:输入一个整数n,求1~n这n个整数的十进制表示1出现的次数。例如,输入12, 1~12这些整数中包含1的数字有1,10,11,12,一共出现了5次。
思路:我们可以查找给出的数字的规律,以1~21345为例。可以将真个数字分为3段,1~1345,1325~21345,这个第二段又可以划分为1345~11345和11345~21345。第一段,我们可以从第二位开始用递归的方式求解,第二段1345~21345,可以分为1在最高为的情况和1不在最高位的情况。1在最高位的情况就是1~19999,不在最高位的情况就是排除最高位我们还有4位数,在这四位数中,任选某一位为1,其他的在0~9中随便选,全排列为 24103 2 ∗ 4 ∗ 10 3 2表示1345~11345和11345~21345两段。。代码如下:

int PowerBase10(unsigned int n)
{
    int result=1;
    for(unsigned int i=0;i<n;++i)
        result*=10;
    return result;
}

int NumberOf1Between1AndN(int n)
{
    if(n<=0)
        return 0;
    char strN[50];
    sprintf(strN, "%d", n);
    return NumberOf1(strN);
}

int numberOf1(const char* strN)
{
    if(!strN || *strN<'0' || *strN>'9' || *strN=='\0')
        return 0;
    int first=*strN-'0';
    unsigned int length=static_cast<unsigned int>(strlen(strN));
    if(length==1 && first==0)
        return 0;
    if(length==1 && first>0)//输入的为一位数
        return 1;
    int numFirstDigit=0;
    if(first>1)
        numFirstDigit=PowerBase10(length-1)//1在最高位的段;
    else if(first==1)
        numFirstDigits=atoi(strN+1)+1;
    int numOtherDigits=first*(length-1)*PowerBase10(length-2)//1不在最高位的段;
    int  numRecursive=NumberOf1(strN+1)//第一段;
    return numFirstDigit+numOtherDigits+numRecursive;
}

面试题44 数字序列中某一位的数组

数组以01234567891011121314151617……的格式序列化到一个字符序列中,在这个序列中,从第5位(从0开始计数)是5,第13位是1,第19位是4,等等,请写一个函数,求任意第n为对应的数字。
比如说,我们现在要确定第899,首先,我们得确定第899位对应的数字是一位数?两位数?还是三位数?,如何确定?从一位数开始往后计算,因为我们的格式化序列是从第一位开始排的嘛。如何计算我们这个m位数需要占据多少个序列位呢,

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数字乡村和智慧农业的数字化转型是当前农业发展的新趋势,旨在通过应用数字技术,实现农业全流程的再造和全生命周期的管理服务。中国政府高度重视这一领域的发展,提出“数字中国”和“乡村振兴”战略,以提升国家治理能力,推动城乡融合发展。 数字乡村的建设面临乡村治理、基础设施、产业链条和公共服务等方面的问题,需要分阶段实施《数字乡村发展战略纲要》来解决。农业数字化转型的需求包括满足市民对优质农产品的需求、解决产销对接问题、形成优质优价机制、提高农业劳动力素质、打破信息孤岛、提高农业政策服务的精准度和有效性,以及解决农业融资难的问题。 数字乡村建设的关键在于构建“1+3+4+1”工程,即以新技术、新要素、新商业、新农民、新文化、新农村为核心,推进数据融合,强化农业大数据的汇集功能。数字农业大数据解决方案以农业数字底图和数据资源为基础,通过可视化监管,实现区域农业的全面数字化管理。 数字农业大数据架构基于大数据、区块链、GIS和物联网技术,构建农业大数据中心、农业物联网平台和农村综合服务挥决策平台三大基础平台。农业大数据中心汇聚各类涉农信息资源和业务数据,支持大数据应用。信息采集系统覆盖市、县、乡、村多级,形成高效的农业大数据信息采集体系。 农业物联网平台包括环境监测系统、视频监控系统、预警预报系统和智能控制系统,通过收集和监测数据,实现对农业环境和生产过程的智能化管理。综合服务挥决策平台利用数据分析和GIS技术,为农业决策提供支持。 数字乡村建设包括三大服务平台:治理服务平台、民生服务平台和产业服务平台。治理服务平台通过大数据和AI技术,实现乡村治理的数字化;民生服务平台利用互联网技术,提供各类民生服务;产业服务平台融合政企关系,支持农业产业发展。 数字乡村的应用场景广泛,包括农业生产过程、农产品流通、农业管理和农村社会服务。农业生产管理系统利用AIoT技术,实现农业生产的标准化和智能化。农产品智慧流通管理系统和溯源管理系统提高流通效率和产品追溯能力。智慧农业管理通过互联网+农业,提升农业管理的科学性和效率。农村社会服务则通过数字化手段,提高农村地区的公共服务水平。 总体而言,数字乡村和智慧农业的建设,不仅能够提升农业生产效率和管理水平,还能够促进农村地区的社会经济发展,实现城乡融合发展,是推动中国农业现代化的重要途径。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值