《剑指offer》41~45

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

#include<iostream>
#include<vector>
#include<queue>
#include<functional>
using namespace std;

//题目:数据流的中位数
/* 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,
 * 那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
*/

/*思路:
 * 获得一个stream的中位数,要求每次的响应速度要快,而不能每次都去重新找,这是很影响效率的
 * 借助一个最大堆和一个最小堆来实现是比较好的选择
 * 维护一个最大堆和最小堆,最小堆中任意一个数都大于最大堆中最大的数
 * 假设总数为偶数时,新数据会被插入到最小堆中,总数变为奇数,那么此时中位数就是最小堆的头
 * 总数为奇数时,新数据插入到最大堆,总数变为偶数,中位数就是最大堆和最小堆的头元素的平均数
 * 根据当前两堆的总元素和,来确定中位数是一个数还是两个数的均值
 * 情况1:总数为偶
 * 按理说此时应该将元素插入到最小堆,但是如果此时该元素比最大堆的头元素小,就需要把这个元素插入到最大堆的合适位置来保证顺序
 * 然后把最大堆头元素拿出来放到插入到最小堆去,这样才能保证最小堆的任意一个数都大于最大堆头元素,保证当前最小堆的头元素就是中位数
 *
 * 情况2:总数为奇
 * 当总元素个数为奇数时,需要将新元素插入到最大堆,但新元素可能比最小堆的堆顶大,如果发生这种情况,需要将最小堆的头元素拿出来,把新数据插入到最小堆的对应位置
 * 再把最小堆的头元素插入最大堆的适当位置
 * */

//leetcode:https://leetcode-cn.com/problems/find-median-from-data-stream/submissions/
//			https://leetcode-cn.com/problems/find-median-from-data-stream/
//牛客:https://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?tpId=13&&tqId=11216&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
class MedianFinder {
    priority_queue<int> big_heap;
    priority_queue<int, vector<int>, greater<int>> small_heap;
public:

    /** initialize your data structure here. */
    MedianFinder() {}
    void addNum(int num) {
        if (big_heap.size() == small_heap.size()){

            if (big_heap.size()>0 && num<big_heap.top()){
                big_heap.push(num);
                //保存最大堆的堆顶元素
                num=big_heap.top();
                //将最大堆堆顶元素弹出
                big_heap.pop();
            }
            //此时元素总数变为奇数,中位数即为最小堆堆顶元素
            small_heap.push(num);
        }
        else{
            if (small_heap.size()>0 && small_heap.top()<num){
                small_heap.push(num);
                //保存最小堆的堆顶元素
                num = small_heap.top();
                //将最小堆中的堆顶元素弹出
                small_heap.pop();
            }
            //将num插入最大堆并重新排序,此时中位数就是(big_heap[0]+small_heap[0])/2
            big_heap.push(num);
        }
    }
    double findMedian() {
        if (big_heap.size() == small_heap.size())
            return (double)(big_heap.top() + small_heap.top())*1.0 / 2;
        else
            return (double)(small_heap.top());
    }
};

int main()
{
    MedianFinder *m=new MedianFinder;
    m->addNum(1);
    cout<<m->findMedian()<<endl;
    m->addNum(4);
    cout<<m->findMedian()<<endl;
    m->addNum(2);
    cout<<m->findMedian()<<endl;
    m->addNum(3);
    cout<<m->findMedian()<<endl;
    m->addNum(5);
    cout<<m->findMedian()<<endl;
    return 0;
}

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

#include<iostream>
#include<vector>
using namespace std;

/*题目:连续子数组的最大和
 * 输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
 * 要求时间复杂度为O(n)。
*/

/*思路:
 * 这道题比较简单,这里只做简单的讲解
 * 可以这样去想,先设置一个初始maxSum为int的最小取值,而curSum即当前最大和设置为0,当前即所有元素都不选,子数组为空时的元素和
 * 随后开始更新,只要当前的和不小于0,就说明比初始的maxSum大,就有保留并继续去加的必要,
 * 一旦当前和小于0,就需要抛弃掉当前下标以前的数组,因为这部分相加之后得到的是负数,不如不要
 * 每次遍历都需要更新maxSum值
 *
 * 其实,这个实质还是在用双指针来实现,只不过在这里有一个衡量指标就是0,比0小就直接抛弃掉
 * 每次进入curSum<0的部分其实就是在更新子数组的左指针,而else里则是在更新子数组的右指针
 * 然后每次更新后要更新maxSum
 * */

//leetcode:https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/
//			https://leetcode-cn.com/problems/maximum-subarray/
//牛客:https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
int FindGreatestSumOfSubArray(vector<int> array) {
    if(array.size()==0)
        return 0;
    int curSum=0;
    int maxSum=0x80000000;
    for(int i=0;i<array.size();i++){
        if(curSum<0){
            curSum=array[i];
        }
        else
            curSum+=array[i];
        if(curSum>maxSum)
            maxSum=curSum;
    }
    return maxSum;
}

int main()
{
    int a[]={-2,1,-3,4,-1,2,1,-5,4};
    vector<int> v(a,a+9);
    int res=FindGreatestSumOfSubArray(v);
    cout<<res<<endl;
    return 0;
}

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

#include<iostream>
using namespace std;

//题目:1~n中整数出现的次数
//输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
//例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

//leetcode:https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof
//			https://leetcode-cn.com/problems/number-of-digit-one/
//牛客:https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6?tpId=13&&tqId=11184&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

/*思路1:
* 
* 对于1出现的次数进行统计,首先要保证条理,按位来统计是一个不错的选择,分段来统计也可以
* 这里介绍剑指offer和leetcode上的两种思路
* 剑指offer上的方法,基于递归来进行分段统计,但实质还是对每一个分段上的数字进行按位统计,只不过这里只分了最高位和非最高位
* 每次递归时都会砍掉当前的最高位,比如初始n为12345,则进入下次递归时n为2345
* 当n为个位数时,直接返回1,否则
* temp表示当前位的基数,即个十百千万...

* 首先获取最高位数字first,计算在当前分段(n%temp~n )中1出现在最高位的次数
* 若该值为1,比如12345其首位为1,则2345~12345之间最高位出现1的次数firstNumOf1为n%temp+1,这个1对应的数字为10000
* 若该值大于1,比如22345,其首位为2,所有最高位为1的5位数都被2345~22345所包括,此时最高位出现1的次数为temp

* 随后再计算当前分段(n%temp~n)出现在非最高位的1的次数,为first * (len - 1)*(temp / 10)
* 这里再举刚才的例子,12345,otherNumOf1是2345-12345之间除最高位之外出现1的次数
* 为什么是first*(len-1)*(temp/10)?
* first决定了将这些数分成多少块,如果数是12345,这部分求的就是2345-12345中1的个数
* 如果数是22345,这部分求的就是2345-12345和12345-22345两部分,且这两部分中1出现在非最高位的次数是完全一样的
* 因为每一位都可能出现1,所以共有len-1种可能,而每一种可能下其余位置都可以取0-9这10个数
* 所以总数如公式表达,这有点类似一个简单的带约束的排列组合

* 最后,调用函数进入递归,countDigitOne1(n%temp)表示的是1-2345上1的次数
* 三部分相加即是最终的结果,函数会一直递归,让每一位都“做一次最高位”
*/




int getLength(int n)
{
	int len = 0;
	while (n != 0)
	{
		len++;
		n /= 10;
	}
	return len;
}
int countDigitOne1(int n) {
	if (n<1)
		return 0;
	int len = getLength(n);
	if (len == 1)
		return 1;
	int temp = pow(10, len - 1);
	int first = n / temp;//获取最高位数字
						 //当前最高位的值不同,最高位出现1的次数也不同
						 //最高位为1,则最高位出现1的个数为n除temp的余数+1,以12345为例
						 //最高位出现1的个数为12345%10000+1=2346
						 //如果最高位大于1,则最高位出现1的个数为temp
	int firstNumOf1 = first == 1 ? n % temp + 1 : temp;
	// 在介于n%tmp到n之间的数字中,除了最高位为1,其余各个数字分别为1的总数和
	//举个例子,一个数12345,则firstNumOf1就是2345-12345之间最高位出现1的次数
	//otherNumOf1是2345-12345之间除最高位之外出现1的次数,countDigitOne1表示的是1-2345上1的次数
	//为什么是first*(len-1)*(temp/10)?
	//first决定了将这些数分成多少块,如果数是12345,这部分求的就是2345-12345中1的个数
	//如果数是22345,这部分求的就是2345-12345和12345-22345两部分
	//因为每一位都可能为1,所以有len-1种可能,而每一种可能下其余为止都可以取0-9这10个数
	//所以总数如公式表达,这里求的是1的个数,不是数字的个数,要注意
	int otherNumOf1 = first * (len - 1)*(temp / 10);
	return firstNumOf1 + otherNumOf1 + countDigitOne1(n%temp);
}



/*思路2:
* 这里再来说说第二种思路,其实实质上和上面的是一样的,只是这次变成了从最低位开始统计,而且无需递归,逻辑也更加清晰
* 举个例子,计算从1到12340中1的次数
* 首先从最低位开始,每次计算要统计当前位的值、高一位的值以及低一位的值
* 计算规则为:
		current = (n / i) % 10; //当前数字
		before = n / (i * 10); //高位数字
		after = n - (n / i)*i; //低位数字
* 对于12340,最低位为0,此时若更高位置的数值不变,即该数字最高三位始终保持为123,则最低位出现1的次数为before*i,这里的i和上面的temp意义一样
* 对于该数,计算得此值为4,分别为12301 12311 12321 12331这四个数
* 下一步,更新i值,进入下一次循环,此时current值为4,after为0,before为3
* 若更高位数值不变,即最高两位数字始终为12,则当前位置(十位)出现1的次数为(before + 1) * i,计算得次数为40
* 为什么current的值不同,会影响计数?举个例子,对于数字101,十位取1的次数为10,有10 11 12 13 14 15 16 17 18 19这10种情况;
* 对于数字111,十位取1的次数为12,包括10 11 12 13 14 15 16 17 18 19 以及110 111
* 而对于121,十位取1的次数则为20,包括10~19以及110~119
* 这里一定要注意,其实说白了还是受分段的影响
* 回到刚才的问题,继续计算直到循环退出,最终得到数值
* 思路1和思路2既有相同的地方,如按位统计的思想,也有不同的地方,在第一种思路中,分段的意图比较明显,而思路二中则在没有强调(实际上蕴含了)
* 分段的情况下更好地实现了计数
* 总体来说,思路2是更先进的,但思路1对于理解问题也是有帮助的
*/

int countDigitOne2(int n)
{
	int count = 0;//1的个数
	int i = 1;//当前位
	int current = 0, after = 0, before = 0;
	while ((n / i) != 0) {
		current = (n / i) % 10; //当前数字
		before = n / (i * 10); //高位数字
		after = n - (n / i)*i; //低位数字
							   //如果为0,出现1的次数由高位决定,等于高位数字 * 当前位数
		if (current == 0)
			count += before * i;
		//如果为1,出现1的次数由高位和低位决定,高位*当前位+低位+1
		else if (current == 1)
			count += before * i + after + 1;
		//如果大于1,出现1的次数由高位决定,//(高位数字+1)* 当前位数
		else
			count += (before + 1) * i;
		//前移一位
		i = i * 10;
	}
	return count;
}

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

#include<iostream>
#include<string>
using namespace std;

/*题目:数字序列中某一位的数值
* 数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
* 请写一个函数,求任意第n位对应的数字。
*/

//leetcode:https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof
//			https://leetcode-cn.com/problems/nth-digit/
//牛客:无

/*思路:
* 首先对这个序列进行分析01234567891011121314151617181912021...90919293949596979899100101102...9989991000...
* 可以看到1位数0-9共10个,占据了1-10位
* 两位数10-99共90个,占据了11-190位
* 三位数100-999共900个,占据了191-2890位
* 以此类推
* 简单介绍变量len和nextSize,len类似于一个计数器最终用于记录第n位是几位数,初始时为1
* nextSize用于表示(当前位数+1)的数字共占据多少位(多少下标),由len计算而来

* 我们首先得知道第n位到达是个几位数
* 这可以通过不断得更新和减掉X位数占据的位数来最终确定,举个例子,求第200位
* 首先减掉10,更新nextSize值为180
* 再减掉180,n变为10,此时更新nextSize为1800,对应的三位数,而n<nextSize,说明第200位是一个三位数

* 随后再进一步确定n在len位数的第几个数字的下标中包括,并拿出这个数字,这里为了方便最终取值,把这个数字转换成了字符串
* 这个数字值为pow(10, len-1) + (n - 1) / len,根据位数计算而来,pow(10, len-1)为基数,即len位数的第一个数
* 每个len位数占据len位下标,而n从1开始,下标从0开始,所以需要减去1,得到这个数字是len位数的第几个(非最高位的值)
* 加上基数即可知道这个数具体为多少,将该数转化为字符串
* 回顾前面的例子,n=200,我们已经知道了这个数是三位数,而n此时只剩下10,代入上式可知,下标n所在的数为103

* 最后,再计算具体属于这个数的第几位,由n - (n - 1) / len * len - 1来计算,将字符转回数字
*/

int findNthDigit(int n) {
	if (n < 10) {
		return n;
	}
	int len = 1;//单个数字的长度
	long long nextSize = pow(10, len-1) * len * 9;//长度为len + 1的数字所构成的字符串的长度
	//第一步:确定n所处的数字的位数
	while (n > nextSize) {
		n -= nextSize;
		len += 1;
		nextSize = pow(10, len-1) * len * 9;//长度为len + 1的数字所构成的字符串的长度
	}
	//第二步:确定n是位数为len + 1的数字中的第几个数字,并且转换为字符串
	string resStr = to_string((long long)pow(10, len-1) + (n - 1) / len);
	//返回n在这个数字中对应的位,(比如当n == 15,求得len = 1,n = 6, resStr = “12”,然后取出“12”字符串的第二位)
	//注意返回的int数字,并不是返回字符'k',需要减去'0'
	return resStr[n - (n - 1) / len * len - 1] - '0';
}

面试题45:把数组排成最小的数

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;

/*题目:把数组排成最小的数
* 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
*/

//leetcode:https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/
//牛客:https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&&tqId=11185&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

/*思路:
* 这道题难度不大,其重点就在于通过何种方式来确定各数字的位置,来组成最小数字
* 这里通过转换成字符串,借助大于小于号的重载来实现,因为直接比较数字会有很多情况处理,且过程复杂
* 而转换成字符串比较时只需要做两次拼接,来比较即可
* 借助sort函数,根据比较的结果完成排序
* 比较过程由compare函数来实现,其实质是:如果两个字符串s1和s2,s1+s2小于s2+s1,就把s1放在s2前面
* 最终将排好序的字符串数组拼接,转换回int即可
*/

static bool compare(string s1, string s2)
{
	string str1 = s1 + s2;
	string str2 = s2 + s1;
	return str1<str2;
}


string PrintMinNumber(vector<int> numbers) {
	if (numbers.size() == 0)
		return "";
	vector<string> strs;
	for (int i = 0; i<numbers.size(); i++)
	{
		string tmp = to_string(numbers[i]);
		strs.push_back(tmp);
	}
	sort(strs.begin(), strs.end(), compare);
	string res;
	for (int i = 0; i<strs.size(); i++)
		res += strs[i];
	return res;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
经导师精心导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值