题目
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
题解
使用Go语言实现
将两个链表合并成一个,并且注意按十进制进位即可,注意以下几点:
- 不要改变原链表,需要额外建立一个索引指向原链表头
- 两个原链表的长度都不定,遍历时,如果某一个结束,另一个还有,则需要将已经结束的按0值处理
- 进位时,最终的一个1不要忘记
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
var lsum *ListNode = new(ListNode)
var lz *ListNode = lsum
var ls *ListNode = lsum
var lx *ListNode = l1
var ly *ListNode = l2
var num1, num2, sum, inc int
for {
if lx == nil && ly == nil {
if inc != 0 {
lz = new(ListNode)
lz.Val = inc
ls.Next = lz
ls = lz
}
break;
}
if lx != nil {
num1 = lx.Val
} else {
num1 = 0
}
if ly != nil {
num2 = ly.Val
} else {
num2 = 0
}
lz = new(ListNode)
ls.Next = lz
ls = lz
sum = num1 + num2 + inc
if sum < 10 {
lz.Val = sum
inc = 0
} else {
lz.Val = sum - 10
inc = 1
}
if lx != nil {
lx = lx.Next
}
if ly != nil {
ly = ly.Next
}
}
return lsum.Next
}
一些可以用到的工具函数
//打印链表元素
func listPrint(List *ListNode) {
for lx := List; lx != nil; lx = lx.Next {
fmt.Printf("%d ", lx.Val)
}
fmt.Println()
}
//按照指定的数列创建链表
func listNew(nums ...int) *ListNode {
var list *ListNode
var lp *ListNode
var lx *ListNode
for i, x := range nums {
if 0 == i {
list = new(ListNode)
list.Val = x
list.Next = nil
lp = list
} else {
lx = new(ListNode)
lx.Val = x
lx.Next = nil
lp.Next = lx
lp = lx
}
}
return list
}
知识点
1、申请内存空间
Go使用new分配内存,返回指针类型,另外new创建的内存,不需要free/delete释放,Go会自动释放内存空间
type ListNode struct {
Val int
Next *ListNode
}
var lsum *ListNode = new(ListNode)
2、函数返回结构体指针
如下几种返回方式,都不会存在问题,Go对于堆和栈的处理,程序员无需关心
//函数中使用new来申请空间,然后将指针返回
func listCreate1() *ListNode {
var list = new(ListNode)
return list
}
//函数中创建空结构体,然后将指针返回
func listCreate2() *ListNode {
var list = &ListNode {}
return list
}
//函数中创建并初始化结构体,然后返回结构体指针
func listCreate3() *ListNode {
var list = &ListNode {Val:100, Next:nil}
return list
}
//函数中创建并初始化结构体的另一种方式,然后返回结构体指针
func listCreate4() *ListNode {
var list = &ListNode {100, nil}
return list
}
返回结果
四种返回方式返回的地址
0xc000006030
0xc000006038
0xc000006040
0xc000006048
注意这边函数中返回的是一个局部变量的地址,在C++中,返回局部变量的地址这种行为是非常可怕的,这个指针就会是一个野指针。而GO语言中支持这种语法,显然是有它的道理的。我这边参考了一篇博客:函数返回局部变量地址,他是这么说的:
go语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis),当发现变量的作用域没有跑出函数范围,就可以在栈上,反之则必须分配在堆。所以不用担心会不会导致memory leak,因为GO语言有强大的垃圾回收机制。go语言声称这样可以释放程序员关于内存的使用限制,更多的让程序员关注于程序功能逻辑本身。
所以函数内部局部变量,无论是动态new出来的变量还是创建的局部变量,它被分配在堆还是栈,是由编译器做逃逸分析之后做出的决定
所以,说到这里,来总结一下GO的一个特点:
GO程序员不需要关心定义的这个局部变量具体是在堆上,还是在栈上,即使是new出来的也不一定在堆上,GO编译器会对该变量做逃逸分析来确定变量的存放位置,即使是在堆上分配了,GO也有垃圾回收机制不用我们去主动释放空间,作为程序员我们只要考虑上层的这些逻辑就可以了,不需要关心底层具体的分配问题。
3、函数返回结构体本身
函数返回结构体时,是将结构体的值拷贝给接收者
func listCreate5() ListNode {
var list = ListNode {100, nil}
fmt.Printf("list %p\n", &list)
return list
}
var list5 = listCreate5()
fmt.Printf("list5 %p\n", &list5)
返回结果
list 0xc0000323b0
list5 0xc0000323a0
4、可变参函数
//定义可变参函数,注意list...int表示一个参数列表和参数类型,内部实际上这是一个切片
func listPrint(name string, list...int) {
for _, x := range list {
fmt.Printf("%d ", x)
}
fmt.Printf("This is %s list\n", name)
}
//第一种调用方式
listPrint("chenlei", 1,2,3,4,5,6,7)
//第二种调用方式,注意直接传切片给可变参列表是不行的,但是可以使用...的方式
var nums = [] {1,2,3,4,5,6,7}
listPrint("chenlei", nums...)