计算11的N次方,N非常大

11的N次方

最近在网上看到一道题,感觉还蛮有意思的,题目要求要用程序实现11的N次方,这个N可能会很大,所以结果要用字符串表示。

在写程序之前我们先把测试用例写出来,这里从11的0次方到21次方都有测试到:

func TestPowOfEleven(t *testing.T) {
   var checkList = []string{"1", "11", "121", "1331", "14641", "161051", "1771561", "19487171", "214358881", "2357947691", "25937424601", "285311670611",
      "3138428376721", "34522712143931", "379749833583241", "4177248169415651", "45949729863572161", "505447028499293771", "5559917313492231481", "61159090448414546291",
      "672749994932560009201", "7400249944258160101211"}
   for index, value := range checkList {
      if value == PowerOfEleven(index) {
         t.Logf("Power Of %v is right", index)
      } else {
         t.Logf("Power Of %v is wrong, right value is %s, calculate value is %s", index, value, PowerOfEleven(index))
      }
   }
}

其实11的N次方是有一定的数学规律的,它满足帕斯卡三角形(杨辉三角),其实101的幂,1001的幂,10001的幂,也都是满足的。感兴趣可以研究一下。相关资料在拓展阅读中。

这道题的基本思路是快速幂+大数相乘。因为N很大的话数字是装不下的,只能用字符串表示,用字符串的话就用大数相乘,这是一个比较好的解决方法。N次方的话,为了优化计算速度,就要用快速幂的思想,把时间复杂度降低到log(N).话不多说,看看代码:

//计算11的N次方主要函数
func PowerOfEleven(n int) string {
   if n == 0 {
      return "1"
   }
   if n == 1 {
      return "11"
   }
   var baseNums = "11"
   var res = "1"
   for n > 0 {
      if n&1 == 1 {
         res = bigNumMultiplication(res, baseNums)
      }
      baseNums = bigNumMultiplication(baseNums, baseNums)
      n >>= 1
   }
   return res
}

// 大数相乘
func bigNumMultiplication(nums1, nums2 string) string {
	bytes1, bytes2 := []byte(nums1), []byte(nums2)
	len1, len2 := len(bytes1), len(bytes2)
	var erveryLevels []string
	for i := len1 - 1; i >= 0; i-- {
		n1 := int(bytes1[i] - '0')
		flag := false
		flagNum := 0
		var tempRes []byte
		for k := 0; k < len1-1-i; k++ {
			tempRes = append(tempRes, '0')
		}
		for j := len2 - 1; j >= 0; j-- {
			n2 := int(bytes2[j] - '0')
			temp := n1 * n2
			if flag {
				temp += flagNum
				flag = false
				flagNum = 0
			}
			if temp >= 10 {
				flag = true
				flagNum = temp / 10
			}
			tempRes = append([]byte{byte(temp%10 + '0')}, tempRes...)
		}
		if flag {
			tempRes = append([]byte{byte(flagNum%10 + '0')}, tempRes...)
		}
		erveryLevels = append(erveryLevels, string(tempRes))
	}
	res := "0"
	for _, value := range erveryLevels {
		res = bigNumsAdd(res, value)
	}
	return res
}

// 大数相加
func bigNumsAdd(nums1, nums2 string) string {
	if len(nums1) == 0 || nums1 == "" || nums1 == "0" {
		return nums2
	}
	if len(nums2) == 0 || nums2 == "" || nums2 == "0" {
		return nums1
	}
	nums1Bytes, nums2Bytes := []byte(nums1), []byte(nums2)
	index1, index2 := len(nums1Bytes)-1, len(nums2Bytes)-1
	flag := false
	var res []byte
	for index1 >= 0 || index2 >= 0 {
		if index1 < 0 {
			n2 := int(nums2Bytes[index2] - '0')
			if flag {
				n2++
				flag = false
			}
			if n2 >= 10 {
				flag = true
			}
			res = append([]byte{byte(n2%10 + '0')}, res...)
			index2--
			continue
		}
		if index2 < 0 {
			n1 := int(nums2Bytes[index1] - '0')
			if flag {
				n1++
				flag = false
			}
			if n1 >= 10 {
				flag = true
			}
			res = append([]byte{byte(n1%10 + '0')}, res...)
			index1--
			continue
		}
		n1 := int(nums1Bytes[index1] - '0')
		n2 := int(nums2Bytes[index2] - '0')
		temp := n1 + n2
		if flag {
			temp++
			flag = false
		}
		if temp >= 10 {
			flag = true
		}
		res = append([]byte{byte(temp%10 + '0')}, res...)
		index1--
		index2--
	}
	if flag {
		res = append([]byte{'1'}, res...)
	}
	return string(res)
}

代码写完,跑跑单测,都跑过了,太nice了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sC3IXXr3-1646739797316)(/Users/didi/Library/Application Support/typora-user-images/image-20220308173732978.png)]

优化

细细想来,其实这个算法还是有可以优化的地方的,主要体现在大数相乘上面,如果我要计算的N次方还没有到计算机数字表示不了的时候,我们是不是可以用数字来直接参与计算和表示呢?其实是可以的。我们知道计算机可以表示的最大的数应该是无符号int64整数,在go种是uint64,也就是1 << 64 - 1.那么我们怎么计算小于1 << 64 - 1的11的最大次幂呢?这个思路还是比较简单的,因为数字表示不下的时候,会溢出,无论计算机的溢出策略是怎样的。这个时候必定存在一个N,使得在计算机中11的N + 1次方 / 11的N次方 != 11.所以下面代码就可以计算出这个11的最大次幂:

func calculateMaxPowerOf11() int {
   res, tempRes := uint64(1), uint64(11)
   num := 0
   for tempRes/res == 11 {
      res = tempRes
      tempRes *= 11
      num++
   }
   return num
} 

这里计算出来的是18.所以在N小于等于18之前用数字计算,得到结果之后转成字符串就好。下面代码是数字快速幂计算方式:

// 计算可用数字表示的最大的11的N次方
func calculatePowerOf11InNum(n int) uint64 {
   if n == 0 {
      return 1
   }
   if n == 1 {
      return 11
   }
   var res uint64 = 1
   baseNum := uint64(11)
   for n != 0 {
      if n&1 == 1 {
         res *= baseNum
      }
      n >>= 1
      baseNum *= baseNum
   }
   return res
}

可以通过下面代码验证18这个数的正确性:

func TestCalculateMaxPowerOf11(t *testing.T) {
   fmt.Println(calculatePowerOf11InNum(18))
   fmt.Println(calculatePowerOf11InNum(19))
   fmt.Println(calculatePowerOf11InNum(19) / calculatePowerOf11InNum(18))
   fmt.Println(calculatePowerOf11InNum(17))
   fmt.Println(calculatePowerOf11InNum(18))
   fmt.Println(calculatePowerOf11InNum(18) / calculatePowerOf11InNum(17))
}

结果是这样的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HxyWjWwk-1646739797318)(/Users/didi/Library/Application Support/typora-user-images/image-20220308174702643.png)]

可以看到19次方除18次方结果不是11,而18次方除17次方是11。是因为在19次方这里就发生了溢出。

基于这一点,我们可以把PowerOfEleven函数优化成下面这样子:

func PowerOfEleven(n int) string {
   if n == 0 {
      return "1"
   }
   if n == 1 {
      return "11"
   }
   if n <= calculateMaxPowerOf11() {
      res := calculatePowerOf11InNum(n)
      return strconv.FormatUint(res, 10)
   }
   var baseNums = "11"
   var res = "1"
   for n > 0 {
      if n&1 == 1 {
         res = bigNumMultiplication(res, baseNums)
      }
      baseNums = bigNumMultiplication(baseNums, baseNums)
      n >>= 1
   }
   return res
}

再跑一次单测:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mo9yA13h-1646739797318)(/Users/didi/Library/Application Support/typora-user-images/image-20220308174900228.png)]

完美通过,nice!!今天的分享到这里结束啦,感谢大家!

拓展阅读

  1. 杨辉三角:https://baijiahao.baidu.com/s?id=1607063654204393620&wfr=spider&for=pc
  2. 快速幂:https://zhuanlan.zhihu.com/p/95902286
  3. 大数相乘:https://blog.csdn.net/u010983881/article/details/77503519

个人推广

以下是笔者公众号,欢迎多多关注,一起成长。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UIbKGoSa-1646739797318)(/Users/didi/Library/Application Support/typora-user-images/image-20220220020251529.png)]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值