剑指Offer第一章 整数 1-整数除法

剑指Offer第一章 整数 01

  • 第一章 整数
    • 1.1整数的基础知识
      • 面试题1:整数除法
      • Code

第一章 整数

1.1整数的基础知识

整数是一种基本的数据类型。编程语言可能会提供占据不同内存空间的整数类型, 每种类型能表示的整数范围也不同。例如,Java中有4种不同的整数类型,分别为:

byte:(8位)(-27次方~27次方-1short:(16位)(-215次方~215次方-1int:(32位)(-231次方~231次方-1long:(32位)(-263次方~263次方-1

Java中的整数类型都是有符号整数,即如果整数的二进制表示的最高位为0则表示其为正数,如果整数的二进制 的最高位为1则表示其为负数。有些语言(如C/C++)支持无符号整数。无符号整数无论二进制表示的最高位是0还是1,都表示其为一个正数。无符号的32位整数的范围是0~2的32次方-1。

通常,变成语言中的整数运算都遵循四则运算规则,可以使用任意嵌套的小括号。需要注意的是,由于整数的范围限制,如果计算机过超出范围则会产生溢出。产生溢出时运行不会出错,但结果计算会出乎意料。如果除数为0,那么整数的除法在运行时将报错。

面试题1:整数除法

题目: 输入2个int型整数,它们进行除法计算并返回商,要求不得使用乘号*、除号/及求余符号%。当发生溢出时,返回最大的整数值。假设除数不为0。例如,输入15和2,输出15/2的结果,即7。

分析: 这个题目限制我们不能使用称号和除号进行运算。一个直观的解法是基于减法实现除法。例如, 为了求得15/2的商,可以不断地从15里减去2,当减去7个2之后余数是1,此时不能再减去更多的2,因此15/2的商是7,我们可以用一个循环实现这个过程。但这个直观的解法存在一个问题。当被除数很大但除数很小时,减法操作执行的次数会很多。例如,求(2的31次方-1)/1,减1的操作将执行2的32次方-1次,需要很长的时间。如果被除数是n,那么这种解法的时间复杂度为O(n)。我们需要对这种解法进行发优化。

但这个直观的解法稍作调整。当被除数大于除数时,继续比较判断被除数是否大于除数的2倍,如果是,则继续判断被除数是否大于除数的4倍、8倍等。如果被除数最多大于除数的2的k次方倍,那么将被除数减去除数的2的k次方倍,然后将剩余的被除数重复前面的步骤。由于每次将除数翻倍,因此优化后的时间复杂度是O(logn)。

下面以15/2为例子讨论计算的过程。15大于2,也大于2的2倍(即4),还大于2的4倍(即8),但是小于2的8倍(即16)。于是先将15减去8,还剩余7。由于减去的是除数的4倍,减去这部分对应的商是4。接下来对剩余的7和除数2进行比较,7大于2,大于2的2倍(即4),但小于2的4倍(即8),于是将7-4,还剩余3。这一次减去的是除数2的2倍,对应的商事2。然后对剩余的3和除数2进行比较,3大于2,但是小于2的2倍(即4),于是将3减去2,还剩余1。这一次减去的是除数的1倍,对应的商是1。最后剩余的数字是1,比除数小,不能再减去除数了。于是15/2的商是4+2+1,即7。

上述讨论假设被除数和除数都是正整数。如果有负数则可以将它们先转换为正数,计算整数的除法之后再根据需要调整商的正负号。例如,如果计算15/2,则可以先计算15/2,得到的商是7。由于被除数和除数中有一个负数,因此商应该是负数,于是商应该是-7。

将负数转换成整数存在一个小问题。对于32位的整数而言,最小的整数是-2的31次方,最大的数是2的31次方-1。因此。如果将-2的31次方转换位正数则会导致溢出。由于将任意正数转换位负数都不会溢出,因此可以先将正数都转换位负数,用前面优化之后的减法计算两个负数的除法,然后根据需要调整商的正负号。

最后讨论可能的溢出。由于是整数的除法并且除数不等于0,因此商的绝对值一定小于或等于被除数的绝对值。因此,int型整数的除法只有一种情况会导致溢出,即(-2的31次方)/(-1)。这是因为最大的正数位2的31次方-1,2的31次方超出了正数的范围。

再全面地分析了使用减法实现除法的细节之后,我们可以开始编写代码。参考代码如下:

Code

package chapter_01;

/**
 * 整数整除
 *
 * @author : lx
 * @date : 2021/08/15 下午 11:22
 */
public class Question01 {

	/**
	 * 最小int型整数
	 */
	int MIN_INT_VALUE = 0x80000000;

	/**
	 * 最小int型整数的一半
	 */
	int MIN_INT_VALUE_HALF = 0xc0000000;

	/**
	 * 开放用于调用进行除法的方法
	 *
	 * @param dividend
	 * @param divisor
	 * @return
	 */
	public int divide(int dividend, int divisor) {
		// 最小数除以-1的反转处理
		if (dividend == MIN_INT_VALUE && divisor == -1) {
			return Integer.MAX_VALUE;
		}
		int negative = 2;
		// 被除数大于0,反转为负数
		if (dividend > 0) {
			negative--;
			dividend = -dividend;
		}
		// 除数大于0,反转为负数
		if (divisor > 0) {
			negative--;
			divisor = -divisor;
		}
		// 做实际除法计算:被除数与除数有且只有一个被反转,计算结果反转,否则,直接使用计算结果
		// 即对负数除法的计算
		int result = divideCore(dividend, divisor);
		return negative == 1 ? -result : result;

	}

	/**
	 * 进行除行为的核心方法
	 *
	 * @param dividend
	 * @param divisor
	 * @return
	 */
	private int divideCore(int dividend, int divisor) {
		// 初始化结果为0
		int result = 0;
		// 当被除数小于等于除数时循环执行
		while (dividend <= divisor) {
			// 临时变量存储除数
			int value = divisor;
			// 初始化商为1
			int quotient = 1;
			// 当除数小于最小int型整数的一半时 并且 被除数小于等于2倍的除数,因为此题不允许使用*号所以自己加自己
			while (value >= MIN_INT_VALUE_HALF && dividend <= value + value) {
				// 商值加上商值重新赋值给 变量商
				quotient += quotient;
				// 除数加上除数重新赋值给 变量临时变量value(上面有提到,临时变量value实际存储的是除数的值)
				value += value;
			}
			// 结果加上商值重新赋值给 变量结果(result)
			result += quotient;
			// 被除数减去除数重新赋值给 变量被除数
			dividend -= value;
		}
		return result;
	}

	public static void main(String[] args) {
		System.out.println(new Question01().divide(15, 2));
		// 控制台打印结果 : 7
	}
}

上述代码中的0x80000000为最小的int型整数,即-2的31次方,0xc0000000是它的一半,即-2的30次方。

函数divideCore使用减法实现两个负数的除法。当除数和被除数中有一个负数时,商为负数。因此,在使用函数divideCore计算商之后,需要再根据除数和被除数的负数的个数调整商的正负号。

你可能感兴趣的:(剑指Offer数据结构与算法,java,算法,数据结构)