杂乱算法题

本文主要探讨了《剑指Offer》中的几个经典算法问题,包括:计算二进制中1的个数,实现在数据流中找到中位数,识别丑数,不用传统运算符求1到n的和,以及解决跳跃游戏II。这些问题涵盖了二进制操作、数据流处理、数学逻辑及贪心算法等编程技巧。
摘要由CSDN通过智能技术生成

目录

剑指Offer(15)--二进制中1的个数

剑指Offer(41)--数据流中的中位数

剑指Offer(49)--丑数

剑指Offer(65)--不用加减乘除做加法


剑指Offer(15)--二进制中1的个数

题目: 

请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。

#include<iostream>
using namespace std;
/*
方法一:
首先把n和1做与运算,判断n的最低位是不是1.接着把1左移一位,再和
n做与运算,判断n的次低位是不是1.....,直到最后将1左移32位之后
变为0停住
*/
int NumberOf1(int n)
{
	int count = 0;
	unsigned int k = 1;
	while (k)
	{
		if (n&k)
			count++;
		k = k << 1;
	}
	return count;
}
/*
方法二:
把一个整数减去1,再和原整数做与运算,旧把该整数最右边的1变成0。
那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的
操作。
*/
int NumberOf1(int n)
{
	int count = 0;
	while (n)
	{
		++n;//只要n不为0,说明至少有一个1
		n = (n - 1)&n;
	}
	return count;
}

剑指Offer(41)--数据流中的中位数

题目:

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

数据流是指一直会有数据进行加入

#include<iostream>
#include<vector>
#include<algorithm>
#include<xfunctional>
using namespace std;
template<class T>
/*
维护一个大顶堆和一个小顶堆,且小顶堆中的最小元素都大于大顶堆
中的最大元素,且两个堆的数量相差最大为1,则中位数为小顶堆的
最小元素,也就是堆头;或者是大顶堆和小顶堆堆头的平均值。
两个堆中数据的数目之差不超过1,当大堆中的元素和小堆中的元素
相等时,加入小堆,否则加入大堆。能够保证大堆小堆相差数目为1
*/
class DynamicArray
{
public:
	void Insert(int num)
	{
        //偶数时的情况,应该往min里面插
		if (((min.size() + max.size()) & 1) == 0)
		{
			if (max.size()>0&&num<max[0])
			{
				max.push_back(num);
				//当已建堆的容器范围内有新的元素插入末尾后,
				//应当调用push_heap将该元素插入堆中。
				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];
				pop_heap(min.begin(), min.end(), greater<T>());
				min.pop_back();
			}
			max.push_back(num);
			push_heap(max.begin(), max.end(), less<T>());
		}
	}
	T GetMedian()
	{
		int size = min.size() + max.size();
		if (size == 0)
			throw exception("No numbers are avalible");
		T median = 0;
		if ((size & 1) == 1)
			median = min[0];
		else
			median = (min[0] + max[0]) / 2;
		return median;
	}
private:
	vector<T> min;
	vector<T> max;
};

剑指Offer(49)--丑数

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

#include<iostream>
#include<algorithm>
using namespace std;
//方法一:挨个判断每个整数是不是丑数
bool IsUgly(int number);
int GetUglyNumber(int index)
{
	if (index <= 0)
		return -1;
	int number = 0;//对从1开始的每个数进行判断
	int count = 0;//第几个丑数
	while (count < index)
	{
		++number;
		if (IsUgly(number))
			++count;
	}
	return number;
}
bool IsUgly(int number)
{
	while (number % 2 == 0)
		number /= 2;
	while (number % 3 == 0)
		number /= 3;
	while (number % 5 == 0)
		number /= 5;
	return (number == 1) ? true : false;
}
/*
方法二:以空间换取时间,创建数组保存已经找到的丑数
根据丑数的定义,丑数应该是另一个丑数乘以2、3、5的
结果,创建一个数组,里面是排好序的丑数
*/
int Min(int value1, int value2, int value3);
int GetUglyNumber2(int index)
{
	if (index <= 0)
		return -1;
	int *arr = new int[index];//创建丑数的集合,保存所有已找到的丑数
	arr[0] = 1;
	int next = 1;//下一个丑数的下标
	int *p2 = arr;
	int *p3 = arr;
	int *p5 = arr;
	while (next < index)
	{
		int min = Min(*p2*2, *p3*3, *p5*5);
		arr[next] = min;
		//只要小于,说明*p2*2的结果已经在数组中了
		while (*p2 * 2 <= min)
			++p2;
		while (*p3 * 3 <= min)
			++p2;
		while (*p5 * 5 <= min)
			++p5;
		++next;
	}
	int result = arr[next - 1];
	delete[] arr;
	return result;
}
int Min(int value1, int value2, int value3)
{
	int value = min(value1, value2);
	return min(value, value3);
}

剑指Offer(64)--求1+2+....+n

题目:

求1+2+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字即条件判断语句(A?B:C)。

#include<iostream>
#include<vector>
using namespace std;
class Count{
public:
	Count()
	{
		++N;
		Sum += N;
	}
	static void Reset() { N = 0; Sum = 0; }
	static unsigned int GetSum() { return Sum; }
private:
	static unsigned int N;
	static unsigned int Sum;
};
unsigned int Count::N = 0;
unsigned int Count::Sum = 0;
unsigned int ResultSum(unsigned int n)
{
	Count::Reset();
	Count *p = new Count[n];
	delete[]p;
	p = nullptr;
	return Count::GetSum();
}

剑指Offer(65)--不用加减乘除做加法

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

思路:三部曲

第一步:不考虑进位对每一位相加。和异或的结果是一样的,相同为0,不同为1。

第二步:进位,只有1加1的时候才产生进位,所以可以想象成两个数先做与运算,然后左移一位。

第三步:把前面两者的结果相加。

之后进行重复操作,直到不产生进位为止。

#include<iostream>
using namespace std;
int Add(int num1, int num2)
{
	//sum是只相加不产生进位,carry是进位
	int sum, carry;
	do
	{
		//相同为0,不同为1,起到了相加的效果
		sum = num1^num2;
		//只有相同位置都为1才需要有进位
		carry = (num1&num2) << 1;
		num1 = sum;
		num2 = carry;
	} while (num2 != 0);
	return num1;
}

衍生问题:二进制做乘法

其实就是乘数是几,就进行几次加法运算。

/*
* a: 被乘数
* b: 乘数
*/
int multiply(int a, int b){ 
    // 取绝对值      
    int multiplicand = a < 0 ? Add(~a, 1) : a;    
    int multiplier = b < 0 ? Add(~b , 1) : b;// 如果为负则取反加一得其补码,即正数      
    // 计算绝对值的乘积      
    int product = 0;    
    int count = 0;    
    while(count < multiplier) {        
        product = Add(product, multiplicand);        
        count = Add(count, 1);// 这里可别用count++,都说了这里是位运算实现加法      
    }    
    // 确定乘积的符号      
    if((a ^ b) < 0) {// 只考虑最高位,如果a,b异号,则异或后最高位为1;如果同号,则异或后最高位为0;            
        product = Add(~product, 1); //如果是异号,取反加一就是相应的负数 
    }    
    return product;
}

衍生问题:不使用新的变量,交换两个数的值

#include<iostream>
using namespace std;
int main(void)
{
	//基于加减法
	int a, b;
	a = a + b;
	b = a - b;
	a = a - b;
	//基于异或运算
	a = a^b;
	b = a^b;
	a = a^b;
	return 0;
}

跳跃游戏II

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

思路:贪心算法

//时间复杂度(n),空间复杂度O(1)
int jump(vector<int>& nums)
{
	const int length = nums.size();
	if (length == 1)
		return 0;
	int step = 0;//最小步数
	//[left,right]是当前能覆盖的区间
	int left = 0;
	int right = 0;
	while (left <= right)
	{
		++step;
		int oldRigth = right;
		for (int i = left; i <= oldRigth; ++i)
		{
			int newRight = i + nums[i];
			if (newRight >= length - 1)
				return step;
			if (newRight > right)
				right = newRight;
		}
		left = oldRigth + 1;
	}
	return -1;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值