剑指offer(31---40)

31、整数中1的个数

求出1到13的整数中1出现的次数,并算出100到1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

拿到这个问题可以想到一个不考虑时间效率的方法,也就是统计每一个数字中1的个数,每次通过对整数取余的方式判断个位是不是1,如果这个数大于10,除以10之后再判断个位数是不是1,但是这种方法运算效率不高,因此我们要考虑效率比较高的算法。
其实我们可以找规律,也就是比如:
我们针对一个数的百位为例,此时我们考虑百位为0,假如此时n为12014,它的百位为1与更高位有关,它的百位为1的情况则有:100到199,1100到1199,2100到2199,3100到3199.。。。。,11100到11199,一共有1200个,也就是更高位数字(12)乘以当前位数(100);
此时我们考虑百位为1,假如此时n为12114,它的百位为1与更高位和低位有关,它的百位为1的情况有:100到199,1100到1199,2100到2199,3100到3199.。。。,11100到11199,此时为12*100个,还有12100到12114,此时是115个,共是更高位数字(12)*当前位数+低位数字(114)+1=1315个;
此时我们考虑百位数字大于1,此时假如n为12314,它的百位为1与更高位有关,它的百位为1的情况有100到199,1100到1199,2100到2199,3100到3199.。。。。11100到11199,12100到12199,共1300个,即更高位(12)+1再乘以当前位数100。
此时我们实现代码,代码中i表示整数点代表位置点,即1代表个位,10代表十位:

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
        int count=0;//统计1的个数
        int i=1;//表示数值点用来进行位数的推移
        while(n/i)
        {
            int before=n/i/10;//更高位
            int after=n%i;//低位
            int cur=(n/i)%10;//当前处于中间的数,即每次判断他是0,1,还是大于1
            if(cur==0)
                count+=before*i;
            else if(cur==1)
                count+=before*i+after+1;
            else
                count+=(before+1)*i;
            i*=10;
        }
        return count;
    }
};
32、把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

由于我们最后返回的结果是一个字符串,那么要考虑将数组中所有的元素都要转为字符串格式,使用to_string函数,由于要排成最小数字,也就是例如a=2,b=21,分别转为字符串之后,a+b为221,b+a为212,也就是b+a<a+b,因此b要放在a前面,这个用一个函数cmp实现,然后我们对数组中的所有元素按照cmp的规则进行排序,然后将每个转为字符串按照顺序放到结果字符串result中即可。

class Solution {
private:
    static bool cmp(int a,int b)//定义比较规则,例如a=2,b=21,两者都转为字符串,a+b就为221,b+a就为212,则b+a应该在前面,a+b在后面
    {
        string before="";//存储a+b
        string after="";//存储b+a
        before+=to_string(a);
        before+=to_string(b);
        after+=to_string(b);
        after+=to_string(a);
        return before<after;
    }
public:
    string PrintMinNumber(vector<int> numbers) {
        string result="";
        if(numbers.empty())
            return result;
        //对numbers中的元素按照cmp定义的规则排序,排完序之后从头组合成字符串就是结果
        sort(numbers.begin(),numbers.end(),cmp);
        for(int i=0;i<numbers.size();++i)
        {
            result+=to_string(numbers[i]);
        }
        return result;
    }
};
33、丑数

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

我们拿到这个题,第一就是想到先实现一个判断丑数的方法,然后逐个数进行判断,找到第index个丑数即可,但是这种方法效率很低,因为是逐个判断每个数,因此要考虑另一种算法,也就是定义一个数组存放排序的丑数,一个数是丑数,那么它*2、3、5的结果都是丑数,此时这个数组中最大的丑数为M,要生成下一个丑数,就是这个数组中某一个丑数2、3、5的结果,若将每一个丑数都2,可能有小于M的一些丑数,因为数组中已经存在这些丑数(因为数组是排序的),就忽略,也会有大于M的一些丑数,在这里面第一个大于M的数的下标记为M2,同时关于3和5的情况也和上述一样,得到M3和M5,最后求M2、M3、M5代表的元素最小的即可。

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if(index<7)//1---6都是丑数
            return index;
        vector<int> arr(index);//存放排序的丑数,一共有index个
        arr[0]=1;//第一个丑数是1
        int m2=0,m3=0,m5=0;
        for(int i=1;i<index;++i)
        {
            arr[i]=min(arr[m2]*2,min(arr[m3]*3,arr[m5]*5));//每次都是取这三个里面最小的
            if(arr[m2]*2 == arr[i])//乘的结果任意一个在数组中有,说明比数组中最大的丑数小,向后走进行下一次循环
                m2++;
            if(arr[m3]*3 == arr[i])
                m3++;
            if(arr[m5]*5 == arr[i])
                m5++;
        }
        return arr[index-1];
    }
};
34、第一个只出现一次的字符

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)

这里使用哈希的思想,统计每个字符出现的次数并且使用一个容器按照对应位置存储,然后遍历这个容器,哪个次数为1就返回下标即可。

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        if(str.empty())
            return -1;
        unordered_map<char,int> m;
        for(int i=0;i<str.size();++i)
        {
            m[str[i]]++;
        }
        for(int i=0;i<str.size();++i)
        {
            if(m[str[i]]==1)
                return i;
        }
        return -1;
    }
};
35、数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

这个题拿到可能第一时间想到的是遍历数组,当前元素与后面的每一个元素相比,最后得到逆序对即可,但是这种方法它的时间复杂度为O(n^2),为了提高效率,这里我们采用归并排序的思想,以空间换时间,也就是比如7 5 4 6,我们将其划分为7 5 和4 6,然后进行:
(1)7 5求出逆序对(这里采用的是递归的方式求出这个子数组的逆序对)7>5,有一对;
(2)4 6求出逆序对(也是采用的递归的方式求出这个子数组的逆序对)6>4,有一对;
(3)7 5和4 6分别排序,为5 7和4 6;
(4)设两个指针分别指向两个子数组分别的最大值,i指向7,j指向6;
(5)比较i和j所指向的值,若array[i]>array[j],因为j指向的是当前子数组的最大值,因此这个子数组有几个元素,就与array[i]有几对逆序对(当前有两个元素4 6,逆序对加2,则此时逆序对一共是2+2=4),7>6比较完之后,将i指向的值放入辅助数组中,i向前走一步到5,此时辅助数组中有一个元素7;
(6)判断i和j指向的值,array[i]<array[j],因为i指向的是第一个子数组中最大的值,所以肯定没有和array[j]构成逆序对的,将j指向的值6放入辅助数组,j向前走一步到4,辅助数组此时元素为6 7;
(7)判断array[i]和array[j],5>4,由于第二个子数组中只有一个元素,逆序对+1,即为4+1=5对,将5放入辅助数组,第一个子数组遍历完毕,第二个只剩下4,将4放入辅助数组,结束。

class Solution {
public:
    int InversePairs(vector<int> data) {
        if(data.size()<=1) 
            return 0;//如果少于等于1个元素,直接返回0
        int* ret=new int[data.size()];
        //初始化该数组,该数组作为存放临时排序的结果,最后要将排序的结果复制到原数组中
        for(unsigned int i=0;i<data.size();i++)
            ret[i]=0;
        int count=MergeSort(data,ret,0,data.size()-1);
        delete[] ret;//销毁
        return count;
    }
private:
    int MergeSort(vector<int>& data,int*& ret,int start,int end)
    {
        if(start==end)
        {
            ret[start]=data[start];
            return 0;
        }
        //将数组拆分成两个子数组,分别求内部的逆序对数
        int length=(end-start)/2;
        //分别计算左边子数组和右边子数组
        int leftcount=MergeSort(data,ret,start,start+length)%1000000007;
        int rightcount=MergeSort(data,ret,start+length+1,end)%1000000007;
        //进行逆序计算
        int i=start+length;//左边数组的最后一个下标
        int j=end;//右边数组的下标
        int index=end;//辅助数组下标,从最后一个算
        int count=0;
        while(i>=start && j>=start+length+1)//因为两个子数组是向前遍历的,因此只要i不越界start,j不越界start+length+1
        {
            if(data[i]>data[j])
            {
                ret[index--]=data[i--];
                //统计长度
                count+=j-start-length;
                if(count>=1000000007)//数值过大求余
                    count%=1000000007;
            }
            else
            {
                ret[index--]=data[j--];
            }
        }
        for(;i>=start;--i)
        {
            ret[index--]=data[i];
        }
        for(;j>=start+length+1;--j)
        {
            ret[index--]=data[j];
        }
        //排序
        for(int i=start; i<=end; i++) {
            data[i] = ret[i];
        }
        //返回最终的结果
        return (count+leftcount+rightcount)%1000000007;
    }
};
36、两个链表的第一个公共结点

输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

如果有公共结点,说明后面就是使用公共的尾部,先计算两个链表的长度,哪个链表长,就从哪个链表开始走他们相减的大小,然后两者一起走,直到遇到相同的结点返回即可。

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
private:
    int GetLength(ListNode* head)
    {
        if(head==nullptr)
            return 0;
        ListNode* pCur=head;
        int sum=0;
        while(pCur)
        {
            sum++;
            pCur=pCur->next;
        }
        return sum;
    }
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        int len1=GetLength(pHead1);
        int len2=GetLength(pHead2);
        if(len1>len2)
        {
            for(int i=0;i<len1-len2;++i)
            {
                pHead1=pHead1->next;
            }
        }
        else
        {
            for(int i=0;i<len2-len1;++i)
            {
                pHead2=pHead2->next;
            }
        }
        while(pHead1!=nullptr)
        {
            if(pHead1==pHead2)
                return pHead1;
            pHead1=pHead1->next;
            pHead2=pHead2->next;
        }
        return nullptr;
    }
};
37、数字在排序数组中出现的次数

统计一个数字在排序数组中出现的次数。

首先我们知道他是排序数组,因此可以想到二分查找,如果一个元素重复了,一定是连续出现的(因为是排序数组),因此要想要知道某个数字出现的次数,要知道他第一次出现的下标和最后一次出现的下标,那么可以分别写得到第一次出现的k和最后一次出现的k的函数,例如要知道k第一次出现的下标,我们定义start和end,每次求出mid,如果此时mid所指的值不是k,那就要按照二分查找的方法来查找k,即如果arr[mid]大于k,就将end走到mid-1,如果arr[mid]<k,就将start走到mid+1,否则arr[mid]是k的情况下,若前面的元素也是k,就要继续向前找第一次出现的k,即end走到mid-1的位置,如果前面的元素不是k,说明arr[mid]就是第一次出现的k,循环直到start>end结束,返回-1;
当然获得最后一次出现的k的下标也是这种思想;
我们主函数中实现获得第一次和最后一次出现k的下标(在不是-1的情况下),然后最后一次-第一次+1就是k出现的次数。

class Solution {
private:
    int GetFirstK(vector<int> data,int k,int start,int end)
    {
       int length=data.size();
        int mid = (start+end)>>1;
       while(start <= end)
       {
           if(data[mid]>k)
           {
               end=mid-1;
           }
           //如果中间不是k
           else if(data[mid]<k)
               start = mid+1;
           else if(mid-1 >= 0 && data[mid-1] == k)
               end = mid-1;
           else
               return mid;
           mid=(start+end)>>1;
        }
       return -1;
    }
    int GetLastK(vector<int> data,int k,int start,int end)
    {
        int length = data.size();
        int mid = (start + end) >> 1;
        while(start <= end)
        {
            if(data[mid] > k)
            {
                end = mid-1;
            }
            else if(data[mid] < k){
                start = mid+1;
            }
            else if(mid+1 < length && data[mid+1] == k){
                start = mid+1;
            }
            else{
                return mid;
            }
            mid = (start + end) >> 1;
        }
        return -1;
    }
public:
    int GetNumberOfK(vector<int> data ,int k) {
        int len=data.size();
        if(len==0)
            return 0;
        int count=0;
        int first=GetFirstK(data,k, 0,data.size()-1);
        int last=GetLastK(data,k, 0,data.size()-1);
        if(first!=-1 && last!=-1)
            return last-first+1;
        return 0;
    }
};
38、二叉树的深度

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

如果根结点为空,返回0(这也是递归终止条件),否则就去左子树计算高度和去右子树计算高度(递归),最后返回较高子树的高度+1即可。

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {
        if(pRoot==nullptr)
            return 0;
        int leftDepth=TreeDepth(pRoot->left);
        int rightDepth=TreeDepth(pRoot->right);
        return leftDepth>rightDepth ? leftDepth+1 : rightDepth+1;
    }
};
39、平衡二叉树

判断一课树是否是平衡二叉树,我们第一时间想到的方法是分别计算出左右子树的高度,然后看其高度差的绝对值是否<=1,然后递归当左右子树都平衡时才算平衡,但是这种方法我们会重复遍历同一个结点,因为计算高度时会遍历响相应的结点,而判断平衡时又会遍历这些节点,因此我们要想另一种更加简便的算法
我们可以使用后序遍历的顺序来遍历,这样不会重复遍历同一个结点,也就是边判断平衡边保存当前的二叉树的高度,不用重复遍历,在遍历完某结点的左右孩子之后,根据高度来判断平衡;
实现代码:

class Solution {
private:
    bool _IsBalanced_Solution(TreeNode* pRoot,int* pDepth)//保存当前代表的二叉树的高度
    {
        if(pRoot==nullptr)
        {
            *pDepth=0;
            return true;
        }
        int leftDepth;
        int rightDepth;//左子树和右子树的高度
        if(_IsBalanced_Solution(pRoot->left,&leftDepth) && _IsBalanced_Solution(pRoot->right,&rightDepth))//左右子树都平衡
        //遍历过某结点的左右节点之后,根据高度判断是否平衡
        {
            int diff=leftDepth-rightDepth;
            if(diff<=1 && diff>=-1)
            {
                *pDepth=leftDepth>rightDepth? leftDepth+1:rightDepth+1;
                return true;
            }
        }
        return false;
    }
public:
    bool IsBalanced_Solution(TreeNode* pRoot) {
        int depth=0;
        return _IsBalanced_Solution(pRoot,&depth);
    }
};
40、数组中只出现一次的数字

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

遇到这个问题,我们可以先想一个简单的情景,就是一个整形数组中除了一个数字之外,其他的数字都出现了两次,写程序找出这一个只出现一次的数字,这样的问题,不难解决,就是任何一个数字异或自己都是0,0异或任何一个数等于那个数,因此我们可以依次异或数组中的每个数,最后的结果就是要找的那个数,那么相同的道理,我们也可以使用这种思想来解决这个问题,就是将数组分为两个子数组,然后将要找的两个数字分别放在一个子数组中,那么问题就在于如何让这两个数字分别放在两个子数组中,这里我们也可以采用异或的方法,即:
我们也是依次将数组中的每个数异或,最终得到的结果就是要找的两个数异或的结果(因为其他数都出现了两次,都异或掉了),那么我们就可以根据这个结果的二进制的某一位是否是1来使两个数分别在一个子数组中(因为某一位是1表示这两个数的那一位一定一个是0,一个是1,因此可以区分),可以将那一位二进制位是1的数放在第一个子数组,将那一位二进制位是0的数放在第二个子数组(出现两次的数一定在一个子数组中,因为他们那一位二进制位一定相同,要么都是1,要么都是0),然后两个子数组分别再依次异或就可以得到这两个数。
实现代码:

class Solution {
private:
    unsigned int FindFirstBit1(int num)//找num的哪一二进制位是1,用index表示
    {
        unsigned int index=0;
        while(((num&1)==0) && (index < 8*sizeof(int)))//找哪一位是1,并且index不能超过32位
        {
            index++;
            num = num>>1;
        }
        return index;
    }
private:
    bool Isbit1(int num,unsigned int index)//判断num的index位是否是1
    {
        num = num>>index;
        return (num&1);
    }
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        if(data.size()==0 || data.size() <2)
            return;
        int ret=0;//用来进行异或,并且保存异或的结果
        //第一次数组依次异或
        for(int i=0;i<data.size();++i)
        {
            ret^=data[i];
        }
        //得到异或的结果,就是要找的两个数的异或的结果
        //此时找到这个结果的哪一二进制位是1
        unsigned int index=FindFirstBit1(ret);
        //然后开始遍历数组,开始用num1和num2异或并且保存两个结果,num1与index位是1的异或,num2与index位是0的异或
        *num1=0;
        *num2=0;
        for(int i=0;i<data.size();++i)
        {
            if(Isbit1(data[i],index))
                *num1^=data[i];
            else
                *num2^=data[i];
        }

    }
};
Stkcd [股票代码] ShortName [股票简称] Accper [统计截止日期] Typrep [报表类型编码] Indcd [行业代码] Indnme [行业名称] Source [公告来源] F060101B [净利润现金净含量] F060101C [净利润现金净含量TTM] F060201B [营业收入现金含量] F060201C [营业收入现金含量TTM] F060301B [营业收入现金净含量] F060301C [营业收入现金净含量TTM] F060401B [营业利润现金净含量] F060401C [营业利润现金净含量TTM] F060901B [筹资活动债权人现金净流量] F060901C [筹资活动债权人现金净流量TTM] F061001B [筹资活动股东现金净流量] F061001C [筹资活动股东现金净流量TTM] F061201B [折旧摊销] F061201C [折旧摊销TTM] F061301B [公司现金流1] F061302B [公司现金流2] F061301C [公司现金流TTM1] F061302C [公司现金流TTM2] F061401B [股权现金流1] F061402B [股权现金流2] F061401C [股权现金流TTM1] F061402C [股权现金流TTM2] F061501B [公司自由现金流(原有)] F061601B [股权自由现金流(原有)] F061701B [全部现金回收率] F061801B [营运数] F061901B [资本支出与折旧摊销比] F062001B [现金适合比率] F062101B [现金再投资比率] F062201B [现金满足投资比率] F062301B [股权自由现金流] F062401B [企业自由现金流] Indcd1 [行业代码1] Indnme1 [行业名称1] 季度数据,所有沪深北上市公司的 分别包含excel、dta数据文件格式及其说明,便于不同软件工具对数据的分析应用 数据来源:基于上市公司年报及公告数据整理,或相关证券交易所、各部委、省、市数据 数据范围:基于沪深北证上市公司 A股(主板、中小企业板、创业板、科创板等)数据整理计算
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值