正如题目所述,昨天我做了一道LeetCode上的算法题,题干大概是这样:
如图,用两个非空的链表代表两个非负整数。在队列存储中数字是以倒置的顺序存储,并且链表的每一个节点里包含一个单个的数字,要求把两个这样的链表相加,返回一个新的链表,同时这个新的链表中的值,就是实际上连个非负整数的和,举例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) -- 实际上就是 “342” + “465”
输出: 7 -> 0 -> 8 -- 实际上就是 “342 + 465”的结果,即 “807”
对于这个问题,可以使用线性表中的单链表来解决这个问题。
单链表就是线性表的数据元素用指针链接起来的存储结构,指针表示数据元素之间的逻辑关系,一个数据元素和一个指针组成单链表的一个节点。各个节点在内存中的存储位置并不一定连续,可存放在内存的不同位置。链表的节点可以重新连接,相当于火车编组。
链表单个结点:
可见,链表中的单个结点,由两部分组成,数据域存放该结点中的数据内容,指针域则用来指向后继结点的位置。
单链结构图:
如图,是单链表的结构图,其中head称为头指针变量, 而往往为了便于计算,在head头指针之后,紧接着的为“头结点”,如上所述的head之后阴影部分结点,从a1开始到an结束,是该链表的表结点,其中a1称为首届点,an称为尾结点。(头结点的数据域可以为空)
对应这个问题,输入的内容:(2 -> 4 -> 3) + (5 -> 6 -> 4),可以理解为两个链表之间对应元素的加法运算。
在jdk封装的工具类中,LinkedList就是一个很好使用的链表类,但是在leetCode上面,题意中封装了链表类:
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
可见,和《数据结构导论》中用类C语言实现的类结构一模一样,变量val充当数据域Data,变量next充当指针,指向下一个NodeList。通过构造方法,为变量val赋值。
起初,自己还是比较懵的,毕竟在LinkedList中,通过.add操作,就可以把链表中的元素加到链表当中,在经历过尝试之后,原来是酱紫:
ListNode n;
ListNode idx = new ListNode(2);
n = idx;
n.next = new ListNode(4);
n= n.next;
n.next = new ListNode(3);
n = n.next;
ListNode m;
ListNode idxs = new ListNode(5);
m = idxs;
m.next = new ListNode(6);
m= m.next;
m.next = new ListNode(4);
m = m.next;
这样,就把(2 -> 4 -> 3)和(5 -> 6 -> 4)装到了自己封装的链表类当中了,真实不易,请思考如何理解声明一个链表的原理:
ListNode n; ListNode idx = new ListNode(2); n = idx;这三句,对应的实际操作,应该是这样:
如上,之后的n.next = new ListNode(4); 则对应着下图的数据结构:
最终执行完n.next = new ListNode(5); n = n.next; 对应的数据结构为:
同理,ListNode链表m也是这样。
由题意可知,最初在n(2 -> 4 -> 3)中,实际表示的数字是“342”,相当于表示到链表中之后倒置了。然而结合链表的特性,从head头指针触发,是从左到右顺序扫描每一个链表节点的,同时结合在实际的运算中,低位的两个数类似“8”和“7”相加,得到的结果是“15”,此时低位上的数字是“5”,而那个“1”则被向前进了一位,结合这个倒叙的链表,思路渐渐清晰。
1.分析下标:
n:(2 -> 4 -> 3)
m:(5 -> 6 -> 4)
两个链表,下标分别记作:1,2,3。
2.从左到右,依次相加,同时声明变量carry,用于存储这次计算中,是否有进位,如果有,将carry置为1,如果没有置为0。在下次进位之后的加法计算中,仍然会用carry做求和运算。将每次求得的对应位的值,放到一个新的ListNode当中。
3.遍历m和n的长度,一旦为null之后,则停止遍历。
public static ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummyHead = new ListNode(0);
ListNode p = l1, q = l2, curr = dummyHead;
int carry = 0;
while (p != null || q != null) {
int x = (p != null) ? p.val : 0;
int y = (q != null) ? q.val : 0;
int sum = carry + x + y;
carry = sum / 10;
curr.next = new ListNode(sum % 10);
curr = curr.next;
if (p != null) p = p.next;
if (q != null) q = q.next;
}
if (carry > 0) {
curr.next = new ListNode(carry);
}
return dummyHead.next;
}
1.加数“961”
2.加数”543”
3.结果“1504”
注意:1.如果m和n,是长度不同的ListNode,比如(2 -> 4 -> 3)和(5 -> 6),实际上就是“342 + 65”,结合到代码中,此处要补全位数,让二者位数相同,(5 -> 6 -> 0)。
总结:
1.对于链表的操作理解,重要一点,自己要能抽象出一个指针,每一次计算完一次对应结点的操作之后,指针右移,计算next之后的结点的算数运算。了解了基本写法,跑一跑代码,通过断点调试,就都明白了。
2.这部分代码已经上传至我的github(https://github.com/zhangzhenhua92/vincent-leetcode/blob/master/leetcode02_AddtwoNum.txt),期间借鉴了国外高人的写法,站在他们的肩膀上,才能拓宽我的视野。
That's all.