第二题两数之和,用到了链表ListNude,于是首先了解了下链表。
链表是由一个个结点组装起来的,其中每个结点都有指针成员变量指向列表中的下一个结点,结点可以在运行时动态生成,由于不必按照顺序存储,链表在插入时可以达到O(1)的复杂度,相较于线性表顺序表要快得多(链表的提出也主要在于插入和删除的时间复杂度为线性的,而链表的操作可以是常数时间的复杂度),但是查找每一个结点或特定编号的结点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别为O(log n)和O(1)。链表有许多类型,包括单向链表,双向链表和循环链表,在这里我只着重说下单向链表。
结点包括一个存储数据的数据域和一个存储下一结点位置的指针域:
1 struct ListNode 2 { 3 int val; 4 ListNode *next; 5 ListNode(int x) : val(x), next(NULL) {} 6 };
可以看出结点ListNode被定义为结构变量,节点内存储了两个变量,val和next,val是这个结点本身的值,而next为指向下一个结点的指针,若next为空指针NULL,也就是说当前结点为链表中的最后一个结点。head指针指向第一个结点成为头结点。
应该注意的是,在这道题目中我们要将链表结点逆序存储才能很方便的计算加法的值,至于时间与空间复杂度,由于进行加法运算需要用到每一个结点的值,因此时间复杂度即为O(max(m,n)),m与n分别为两个链表的长度,空间复杂度也是O(max(m,n)),而生成的新链表的长度最多为max(m,n)+1。
双向链表:结点中除了数据域以外,还有两个链域,一个存储直接后继结点地址,称为右链域,一个存储直接前驱结点地址,称为左链域。
循环链表:与单链表一样,唯一的不同在于最后一个结点的指针指向该循环链表的第一个节点即头结点的位置,形成一个环形。
附上代码如下:
1 class Solution { 2 public: 3 ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { 4 ListNode *p = new ListNode(0); //新建结点p 5 ListNode *pnew = p; 6 int sum = 0; 7 while(l1 != NULL || l2 != NULL) 8 { 9 sum /= 10; 10 if(l1 != NULL) 11 { 12 sum += l1->val; 13 l1 = l1->next; 14 } 15 if(l2 != NULL) 16 { 17 sum += l2->val; 18 l2 = l2->next; 19 } 20 pnew->next = new ListNode(sum % 10); 21 pnew = pnew->next; 22 } 23 if(sum / 10 == 1) 24 pnew->next = new ListNode(sum / 10);25 return p->next; //输出结点p之后的值 26 } 27 };
其中在以上代码编译过程中,一直没有搞懂为什么输出是[7,0,8],后来想清楚了,p->next指向下一个结点,但是下一个结点不光包含自己的值,还有下一个节点的位置,于是返回p->next其实就是返回p结点以后的所有的结点的值。另外试着使用printf输出p->next但是失败了,输出的应该是一个地址,但是输出p->val结果是正确的,想来也是,p->val本身就是p结点的取值,而p->next是地址,printf()是一种格式化输出函数,它只能输出式子本身代表的内容,但是return提供的是终止函数的一种方式,同时可以带一个返回值,因此p->next在这里返回的不是地址而是它实际指向地址的内容。