数据结构之数组

数组可以说是最简单的一种数据结构,它占据一块连续的内存并按照顺序存储数据。创建数组时,我们需要首先指定数组的容量大小,然后根据大小分配内存。即使我们只在数组中存储一个数字,也需要为所有的数据预先分配内存。因此数组的空间效率不是很好 , 经常会有空闲的区域没有得到充分利用。
深入理解数组题目

1、二维数组中的查找

题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的­一个二维数组和 一个整数,判断数组中是否含有该整数。

思路:首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在的列;如果该数字小于要查找的数字,剔除这个数字所在的行。
这里写图片描述

bool FindInSortedMatrix(int *Matrix , int rows , int columns , int number)//地址 行 列 数
{
    //防御性编程,空,且小于0,直接退出
    if(Matrix == NULL )
        return false;
    //二维数组
    if(rows > 0 && columns >0){
        int row = 0;
        int column = columns - 1;//从右上角开始
        while(row < rows && column >= 0){//终止条件
            if(Matrix[row * columns + column] == number)
                return true;
            else if(Matrix[row*columns + column] > number)
                column--;//减少一列
            else
                row++;//减少一行
        }
    }
    return false;//没有找到
}

2、合并两个已经排好序的数组

这种题目都是使用类似的方法,还有合并字符串,都是从尾部往前面遍历,这样可以减少移动的次数,达到O(n)。

题目:两个已经排好序的数组A1和A2 , 内存在A1的末尾有足够的多余空间容纳A2.请实现一个函数,把A2中的所有数字插入到A1且所有数字排序。
例如a[10] = {4,5 6};b[3] = {1,2,3};合并后a[10] = {1,2,3,4,5,6}

思路:从后往前面比较然后复制,这样每个数字只需要复制一遍。
注意:判断a先放完的情况。

//a a中数字个数 b b中数字个数 题目已经确保a中内存空间足够
void MergeNumber(int *a , int aNumOfDigit ,  int *b , int bNumOfDigit)
{
    int newindex = aNumOfDigit + bNumOfDigit - 1;//新数组索引尾端
    int aindex = aNumOfDigit - 1;//a数组索尾端
    int bindex = bNumOfDigit - 1;//b数组索引尾端

    while(bindex >= 0){//b复制到a
        if(aindex < 0)//a先排列完成了
            a[newindex--] = b[bindex--];
        if(a[aindex] > b[bindex])
            a[newindex--] = a[aindex--];
        else
            a[newindex--] = b[bindex--];
    }
}

int main(void)
{
    int a[10] = {2 ,5 ,6 ,8};
    int b[5] = {1,4,7,8,9};
    MergeNumber(a , 4 , b , 5);//这就是a先排完的情况
    return 0;
}

这里写图片描述

3、求旋转数组的最小数字

这里写图片描述

4、斐波那契数列数列求解

这里写图片描述
解法一:通过递归求解,在计算的过程中效率很低下,因为会计算很多重复的项。当n很大的时候,会重复计算很多项,导致递归层次越来越大,更有甚者可能导致栈溢出的错误情况。因为每个进程的栈空间都是有限的,过度的函数调用,可能导致栈空间用完。
这里写图片描述
解法二:通过循环解决,这里的思路就是每次求得Fn = Fn_1 + Fn_2之后,通过更新Fn_1Fn_2,一直迭代到最后即可。这种方法避免了栈溢出以及节点重复求的过程。很有效了,斐波那契数列必须使用这种方法求解,时间复杂度是O(n)。

//递归求斐波那契数列第n项
long long Fibonacci_Recursion(unsigned int n)
{
    if(n <= 1)
        return n;
    return Fibonacci_Recursion(n-1) + Fibonacci_Recursion(n-2);
}

//非递归求斐波那契数列第n项
long long Fibonacci_Unrecursion(unsigned int n)
{
    unsigned int Fn = 0;
    unsigned int Fn_1 = 1;
    unsigned int Fn_2 = 0;
    if( n <= 1)
        return n;
    for(int i = 1 ; i < n ; i++){
        Fn = Fn_1 + Fn_2;//计算Fn

        Fn_2 = Fn_1;//更新Fn_2和Fn_1
        Fn_1 = Fn;
    }
    return Fn;
}

int main()
{
    cout << Fibonacci_Recursion(20) << endl;
    cout << Fibonacci_Unrecursion(20) << endl;
    return 0;
}

这里写图片描述

5、斐波那契数列数列等效题目

6、位运算题目

解决这种题目的关键在于把一个整数减去1在和原来的整数位与,得到的结果相当于把整数二进制中最右边的1变成了0。由此可以得出二进制数中,有多少个1。

1、求二进制中1的个数
思路1:通过将一个flag=1一步一步左移并位与,求出1的个数。
思路2:通过将最右边1变成0的做法求解,这种思路很牛逼,我们都应该采用这种做法。

int NumberOf1_1(int num)
{
    int count = 0;
    unsigned int flag = 1;//左移动,全部补0,对于逻辑右移,负数补1,正数补0
    while(flag){
        if(num & flag)
            count++;
        flag <<= 1;
    }
    return count;
}

int NumberOf1_2(int num)
{
    int count = 0;
    while(num){
        count++;
        num = (num-1)&num;//将最右边1变成0,再重复判断
    }
    return count;
}

2、用一条语句判断一个整数是不是2的整数次方
思路:2的整数次方,则其二进制中只有一个1,那么判断很容易了。

bool JudgePowerOf2(int num)//是2的k次方,则二进制只有一个1,所以转换成判断是否只有一个1
{
    assert(num != 0);//num不能等于0,这个是防御性编程
    if((num-1) & num)//是0,则返回真
        return false;
    else
        return true;
}

3、输入两个整数m和n,计算需要改变m的二进制表示中的多少位才可以得到n
思路:先异或,求出不同的位数,然后求出结果中1的个数。

int ChangeTimes(int m , int n)
{
    int temp = m^n;
    return NumberOf1_2(temp);
}

7、数值的整数次方

这里写图片描述
此题注意:
1、base是浮点数,浮点数应该如何和0.0比较大小。
2、exponent是整数,可以为正、0、负。

布尔类型如何和0比较:

   if(flag)
    //表示flag为真

    if(!flag)
    //表示flag为假

整数类型如何和0比较:

   if(flag != 0)
    //表示flag为真

    if(flag == 0)
    //表示flag为假

浮点数如何和0比较:
小数的表示都有误差,判断两个小数是否相等,智能判断他们之差的绝对值是不是在一个很小的范围内。如果相差很小即认为相等,否则不等。
因为计算机表示浮点数都有一个精度限制。对于超出了精度限制的浮点数,计算机会把它们的精度以外的小数部分截断。因此,原来不相等的两个浮点数在计算机中可能就酿成相等的了。

    float a = 2.111111112;
    float a = 2.111111118;

理论上,这两个数是不相等的,但是在32为机器上是相等的(原因:在32位机器上,float保存6为小数,所以相等了。
所以,对于浮点数比较,是这样规定的:如果两个同符号浮点数之差绝对值小于或等于某一个可接受的误差(即精度),就认为两个浮点数相等。
指针如何和0比较:

   if(p == NULL)
    //p为空

    if(p != NULL)
    //p不为空

宏定义求绝对值:括号一定不要吝啬,否则宏定义展开可能因为优先级出现BUG,你都不知道在那里。#define ABS(x) ( (x) > 0 ? (x) : -(x) )假如最外一层括号没有加。
那么if( ABS(2-0) < 0.000001)应该为假,但是返回则为真。因为展开为 2 > 0 ? 2 : -2 < 0.00001,运算符优先级为> < 大于 ?:,所以上式子等效为2 > 0 ? 2 : 1,于是返回2,时钟为真,和我们需要的刚好相反。

对于宏定义,以及边界判断,必须十分注意,否则可能出现bug,并且不是别人希望看到的结果。

#define ABS(x) ( (x) > 0 ? (x) : -(x) )//外出括号,一定不能少
bool g_InvalidInput = false;
static bool equel2double(double num1 , double num2)
{
    if(ABS(num1 -num2) < 0.0000001)//比精度还小,那么相等
        return true;
    else
        return false;
}
double Power(double base , int exp)
{
    double result = 1.0;//初始为0

    //base=0,是无效输入
    if(equel2double(base , 0.0)){//base等于0
        g_InvalidInput = true;//无效输入
        return 0.0;
    }
    if(exp == 0)//任何数的0次方等于1
        return 1;
    else if(exp > 0 ){//大于0
        for(int i = 0 ; i < exp ; i++)
            result *= base;
        return result;
    }else{
        for(int i = 0 ; i < ABS(exp) ; i++)//小于0
            result *= base;
        return 1.0/result;
    }
}

8、打印1到最大的n位数

这里写图片描述
注意:并没有告诉我们n到底有多大,所以必须考虑大数问题。
这里主要考虑通过字符串模拟加法,然后打印出对应的字符串即可。这是一个很聪明的方法,这就解决的大数的问题。
方法:
1、分配字符串,并通过字符串'0'初始化,注意添加字符串结尾'\0'
2、先将字符串转换成数字,然后模拟字符串加法,注意循环判断,在将数字转换成字符串。每次都判断是否到达第n位了。
3、输出,找出从非'0'的地方开始输出。

#include<stdlib.h>
#include<memory.h>

/*
通过字符串模拟自加1操作,每加一次都必须循环检测是否需要进位的问题
*/
bool Increment(char *num)
{
    bool isOverflow = false;
    int len = strlen(num);
    int i  = 0 ;
    for(i = 0  ; i < len ; i++)//将字符转换成数子
        num[i] = num[i] - '0';

    num[len-1]++;
    for(i = len -1 ; i >= 0 ; i--){
        if(num[i] >= 10){//如果最低位需要进位
            if(i == 0){//如果第0位需要进位
                isOverflow = true;//结束
                num[i]--;//
            }else{
                num[i] -= 10;//减去10
                num[i-1] += 1;//进位,然后重复计算倒数第二位是否需要进位
            }
        }else
            break;
    }

    //整数转化为字符
    for(i = 0 ; i < len ; i++)
        num[i] = num[i] + '0';

    return isOverflow;
}
void PrintNum(char *num)
{
    int i = 0 ;
    while(num[i++] == '0');//找到非0的位置
    printf("%s " , num + i - 1);//打印数字
}
//打印n位数,利用字符串搞定,n表示数字的位数
void PrintToMaxOfNDigits(int n)
{
    if(n < 0)
        return ;
    char *number  = (char *)malloc(n + 1);//留个结束符
    memset(number , '0' , n);//全部初始化为字符0
    number[n] = '\0';//存储结束符
    while( Increment(number) != true)
        PrintNum(number);
}
int main(void)
{
    PrintToMaxOfNDigits(3);
    return 0;
}

这里写图片描述

9、调整数组顺序使奇数位于偶数前面

这里写图片描述
这种题目都和快速排序的思想类似,两个指针,一个指向头,一个指向尾,达到条件然后交换对应的值,继续直到两个指针交叉为止。

//将数组重新排序,奇数放前面,偶数放后面
void Reorder(int *pData , unsigned int length)
{
    //防御性编程
    if(pData == NULL || length == 0)
        return ;
    int *pStart = pData;
    int *pEnd = pData + length -1;
    while(pStart < pEnd){
        while( pStart < pEnd &&( *pStart & 0x1) != 0 )//对应区间找到第一个偶数
            pStart++;
        while( pStart < pEnd &&( *pEnd & 0x1) == 0 )//对应区间找到第一个奇数
            pEnd--;
        if(pStart < pEnd){
            int temp = *pStart;
            *pStart = *pEnd;
            *pEnd = temp;
        }
    }
}

10、和为s的两个数字

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

/*
前后进行夹逼查找即可快速找出。
*/
class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        vector<int> res;
        int i = 0 ;
        int j = array.size() - 1;
        while(j > i){//交叉则没有找到
            if(array[i] + array[j] == sum){
                //cout << array[i] << " " << array[j] << endl;
                res.push_back(array[i]);
                res.push_back(array[j]);
                break;
            }
            if(array[i] + array[j] > sum)
                j--;
            else
                i++;
        }
        return res;
    }
};

/*
typedef vector<int> vi;
class Solution {
public:
    vi FindNumbersWithSum(const vi& a,int sum) {
        vi res;
        int n = a.size();
        int i = 0, j = n - 1;
        while(i < j){
            if(a[i] + a[j] == sum){
                res.push_back(a[i]);
                res.push_back(a[j]);
                break;
            }
            while(i < j && a[i] + a[j] > sum) --j;
            while(i < j && a[i] + a[j] < sum) ++i;
        }
        return res;
    }
};
*/

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

题目描述:
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

class Solution {
public:
    /*
用两个数字begin和end分别表示序列的最大值和最小值,
首先将begin初始化为1,end初始化为2.
如果从begin到end的和大于s,我们就从序列中去掉较小的值(即增大begin),相反,只需要增大end。
终止条件为:一直增加begin到(1+sum)/2为止。

上述方法叫做双指针技术,就是相当于有一个窗口,窗口的左右两边就是两个指针,
我们根据窗口内值之和来确定窗口的位置和宽度,滑动窗口方法。
    */
    vector< vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int> > res;
        vector<int> a;
        if(sum < 3)//小于3,则没有连续正数
            return res;
        int small = 1;//初始化为1
        int big = 2;//初始化为2
        int total = small + big;
        while(small <= (sum-1)/2 ){//因为至少两个连续数字,所以small最大为(sum-1)/2
             if(total == sum){//如果相等,则压入,否则更新数据
                 for(int i = small ; i <= big ; i++)
                         a.push_back(i);
                 res.push_back(a);
                 a.clear();
                 total -= small++;//滑动窗口一下
             }else if(total < sum)//小于,则需要增加big
                 total += ++big;//++big 并更新连续和
             else //大于,则需要增加small
                 total -= small++;//small++ 并更新连续和
        }
        return res;
    }
    /*
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int> > allRes;
        int phigh = 2,plow = 1;

        while(phigh > plow){
            int cur = (phigh + plow) * (phigh - plow + 1) / 2;
            if( cur < sum)
                phigh++;

            if( cur == sum){
                vector<int> res;
                for(int i = plow; i<=phigh; i++)
                    res.push_back(i);
                allRes.push_back(res);
                plow++;
            }

            if(cur > sum)
                plow++;
        }

        return allRes;
    }
    */
};

12、不用四则符号的加法

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

class Solution {
public:
    /*
    通过异或和与循环解决
    1、两个数字异或,加了非进位
    2、两个数据与,非0则左移移位,若此值不为0,则继续重复1和2直到为0.

    5-101  17-10001
    101^10001 = 10100
    101&10001 = 10 
    10100+10 = 10110 = 22(这里应该重复1和2步)
    */
    int Add(int num1, int num2)
    {
        int sum = 0 , temp  = 0;
        do{
            sum = num1 ^ num2;//1、加非进位
            temp = (num1 & num2) << 1;//2、有进位

            num1 = sum;//开始重复1和2 加非进位和进位的。
            num2 = temp;
        }while(num2 != 0);
        return num1;
    }
};

/*
交换a和b
*/
a = a + b;
b = a - b;
a = a - b;

a = a ^ b;
b = a ^ b;//本身和本身异或位0,0和任何异或为本身。
a = a ^ b;

13、

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有时需要偏执狂

请我喝咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值