<剑指offer>_有趣的数组

【数组相关的题目】

最近投简历,面试耽误不少进度,有些迷茫,不知道自己究竟适不适合程序员这行业,半道出家的娃只能且走且看,算法也只是突击了两个月,心里实在没地,觉得要是拿到那些CS大牛人不屑于顾的offer肯定欢天喜地 =_=
  I am a slow walker, but I never walk backward.

【8旋转数组中的最小数】

//题目:把一个数组最开始的若干数字搬到数组的末尾,我们称之为数组的旋转。输入一个递增的数组

//输出旋转数组的最小元素。例如:3,4,5,1,2是1,2,3,4,5,的一个旋转数组,该数组的最小值是1

//思路类似二分查找,旋转数组的性质就是前半序列大于后半序列。

//首先设置三个指针,pl,pm,ph头指针,中间指针和尾指针

//如果中间的数字大于头指针,那么证明最小的数在后半序列,在对后半序列进行递归

递归写法

int get_min_reverse_array(int a[], int low, int high)
{
	if (high - low == 1)
		return a[high];
	int mid = (low + high)/2;
	if (a[mid] > a[low])
		return get_min_reverse_array(a, mid, high);
	if (a[mid] < a[high])
		return get_min_reverse_array(a, low, mid);

}

//非递归写法

int solu2_get_min_reverse_array(int a[], int len)
{
	int low = 0;
	int high = len - 1;
	int mid;
	while(a[low] >= a[high])
	{
		if (high - low == 1)
		{
			mid = high;
			break;
		}
		mid = (low+high)/2;
		if (a[mid] > a[low])
			low = mid;
		if (a[mid] < a[high])
			high = mid;
	}
	return a[mid];
}

//另外,上面写法不完整,当碰见1 0 1 1 1的时候也就是三个指针的数相同时候,只能遍历找到最小的数

【31连续子数列的最大和】

//题目:输入一个整数,里面有正有负,数组中一个或者多个连续数形成一个子序列,

//求所有子数组中和的最大值,要求时间复杂度为O(n),这个需要慢慢分析,

//主要考虑前面累加的和为负值跟当前元素的关系,碰见负值如何处理

int get_max_subarray(int a[], int len)
{
	int sum = 0;
	int max_sub_sum = 0x80000000;//最小的负值
	for (int i = 0; i < len; i++)
	{
		if(sum <= 0)
			sum = a[i];
		else
			sum += a[i];
		if (sum > max_sub_sum)
			max_sub_sum = sum;	
	}
	return max_sub_sum;

}

【32 从1到n中1出现的次数】

//题目输入一个整数n,求从1到n这n个整数十进制表示中1出现的次数 //思路一,直接判断每个数的每一位,时间复杂度是nlgn

long long  count_1s(long n)
{
	long long cc = 0;
	while(n)
	{
		if (n%10 == 1)
			cc++;
		n = n/10;
	}
	return cc;
}
long long stupid_sum1s(long n)
{
	long long count = 0;
	while(n)
	{
		count += count_1s(n);
		n--;
	}
		
	return count;
}

//思路二 //@32 从1到n整数中出现1的次数,结论:假设abcde代表整数的每一位,c是百位,百位数为1的整数的总的个数分以下几种情况

//百位为0的话,例如13045,百位上是1的数是 xx100~xx199 (x范围00~12) 所以又13*100 = 1300

//百位为1的话,例如13145,百位上是1的数是 xx100~xx199 (x范围00~12) 再加上13100~13145 一共有1300+145+1个

//百位大于1的话,如13245,百位上是1的数是 xx100~xx199 (x范围00~13) 一共有(13+1)*100个

//理清楚了上面的思路在写就很容易了 

long long sum1s(long n)
{
	long long  count = 0;	
	int bit = 1;
	long before, current, remain;
	while (n/bit)
	{	
		before = n/(bit*10);
		current =(n/bit)%10;
		remain = n - (n/bit)*bit;
		if (current == 0)
			count += before*bit;
		if (current == 1)
			count +=(before*bit + remain + 1);
		if (current > 1)
			count += ((before + 1)*bit);

		bit *= 10;
	}
	return count;
}

#include <iostream>
using namespace std;
void test_sum1s()
{
	std::cout<<"13的1的数目:"<<sum1s(13)<<std::endl;
	std::cout<<"23的1的数目:"<<sum1s(23)<<std::endl;
	std::cout<<"33的1的数目:"<<sum1s(33)<<std::endl;
	std::cout<<"93的1的数目:"<<sum1s(93)<<std::endl;
	std::cout<<"93的1的数目:"<<sum1s(10000000)<<std::endl;
	std::cout<<"13的1的数目:"<<stupid_sum1s(13)<<std::endl;
	std::cout<<"23的1的数目:"<<stupid_sum1s(23)<<std::endl;
	std::cout<<"33的1的数目:"<<stupid_sum1s(33)<<std::endl;
	std::cout<<"93的1的数目:"<<stupid_sum1s(93)<<std::endl;
	std::cout<<"93的1的数目:"<<stupid_sum1s(10000000)<<std::endl;

}

【数字在排序中出现的次数】

//@38数字在排序数组中出现的次数,注意是排序,比如1,2,3,4,4,4,4,4,6,9,10和4,返回4的个数5
//使用二分查找,a[mid]不是4的话,跟一般的二分查找没区别,当a[mid]=4时候我们需要找到的是4的左右端点,看下
//规律,最左边的端点前面的数字比这个数小,最右边的端点右边的数字比这个数大

int get_last_k(int a[], int low, int high, int K)
{
	if(a == NULL || low > high)
		return -1;
	int mid = (low + high)>>1;
	if (a[mid] == K)
	{
		if (mid < high && a[mid+1] != K || mid == high)
			return mid;
		else
			low = mid + 1;
	}
	if (a[mid] > K)
		high = mid - 1;
	if(a[mid] < K)
		low = mid + 1;
	return get_last_k(a, low ,high , K);

}
int get_first_k(int a[], int low, int high, int K)
{
	if(a == NULL || low > high)
		return -1;
	int mid = (low + high)>>1;
	if (a[mid] == K)
	{
		if (mid > 0 && a[mid-1] != K || mid == 0)
			return mid;
		else
			high = mid - 1;
	}
	if (a[mid] < K)
		low = mid + 1;
	if (a[mid] > K)
		high = mid - 1;
	return get_first_k(a, low , high , K);
}
int get_num_of_k(int a[], int len, int K)
{
	int i = get_first_k(a,0,len-1,K);
	int j  = get_last_k(a, 0, len-1, K);
	if (i == -1 || j == -1)
		return -1;
	else
		return j - i + 1;
}

void test_get_num_of_K()
{
	int a[] = {1,2,3,4,4,4,5,6,7,8};
	int a1[] = {4,4,4,4,4,4,4,4,4,4};
	int a2[] = {0,1,3,5,7,8,9,89,98,32};
	cout<<get_num_of_k(a, sizeof(a)/sizeof(a[0]), 4)<<endl;
	cout<<get_num_of_k(a1, sizeof(a1)/sizeof(a1[0]), 4)<<endl;
	cout<<get_num_of_k(a2, sizeof(a2)/sizeof(a2[0]), 4)<<endl;

}

【数组中出现一次的两个数】 //@40数组中只出现一次的数,一个数组中,找出只出现一次的两个数,除这两个数以外,其余的数都出现两次。

//要求时间复杂度是O(n),空间复杂度是O(1)

//假设数组中只有一个这种元素,使用异或就可以搞定,因为数组中相同的元素异或为0,最终得到的结果肯定是那个出现一次的元素

//两个呢?两个最后异或的结果肯定不是0,是两个元素的结果,有什么规律呢?

//比如5,和7,0101^0111 = 0010,按照这个思路来说,0101和0111的二进制表示的倒数第二位肯定不一样

//这样,原数组中可以按照倒数第二位是1或者0来分成两组,两个组内再分别异或,得到的两个结果肯定是5和7

void find_num_appear_once(int a[], int len, int& diff_1, int& diff_2)
{
	if (a == NULL || len < 2 )
		return;	
	diff_1 = diff_2 = 0;
	int bit_to_shift = 0;
	int tmp_or_res = 0;
	for (int i = 0; i < len; i++)
		tmp_or_res ^= a[i];
	while((tmp_or_res & 1) == 0)
	{
		tmp_or_res = tmp_or_res>>1;
		bit_to_shift++;
	}
	tmp_or_res = 1<<bit_to_shift;
	for (int i = 0; i < len; i++)
	{
		if((a[i] & tmp_or_res) == 0)
			diff_1 ^= a[i];
		else
			diff_2 ^= a[i];
	}
}




void test_find_num_appear_once()
{
	int a, b;
	int array[] = {2,4,3,6,3,2,5,5};
	int array1[] = {4, 6};
	int array2[] = {4, 6, 1, 1, 1, 1};
	find_num_appear_once(array, 8, a, b);
	find_num_appear_once(array1, 2, a, b);
	find_num_appear_once(array2, 6, a, b);
}

点赞