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了:
优化
细细想来,其实这个算法还是有可以优化的地方的,主要体现在大数相乘上面,如果我要计算的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))
}
结果是这样的:
可以看到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
}
再跑一次单测:
完美通过,nice!!今天的分享到这里结束啦,感谢大家!
拓展阅读
- 杨辉三角:baijiahao.baidu.com/s?id=160706…
- 快速幂:zhuanlan.zhihu.com/p/95902286
- 大数相乘:blog.csdn.net/u010983881/…
个人推广
以下是笔者公众号,欢迎多多关注,一起成长。