【算法世界】(九)LeetCode -- Add Two Numbers by ListNode

一、问题展示

        正如题目所述,昨天我做了一道LeetCode上的算法题,题干大概是这样:

     

       如图,用两个非空的链表代表两个非负整数。在队列存储中数字是以倒置的顺序存储,并且链表的每一个节点里包含一个单个的数字,要求把两个这样的链表相加,返回一个新的链表,同时这个新的链表中的值,就是实际上连个非负整数的和,举例:

       输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)  -- 实际上就是 “342” + “465”

       输出: 7 -> 0 -> 8  -- 实际上就是 “342 + 465”的结果,即 “807”

                   

二、分析

      1.回顾《数据结构导论》中的链表

       对于这个问题,可以使用线性表中的单链表来解决这个问题。

       单链表就是线性表的数据元素用指针链接起来的存储结构,指针表示数据元素之间的逻辑关系,一个数据元素和一个指针组成单链表的一个节点。各个节点在内存中的存储位置并不一定连续,可存放在内存的不同位置。链表的节点可以重新连接,相当于火车编组。

       链表单个结点:

        

       可见,链表中的单个结点,由两部分组成,数据域存放该结点中的数据内容,指针域则用来指向后继结点的位置。

       单链结构图:

        

        如图,是单链表的结构图,其中head称为头指针变量,  而往往为了便于计算,在head头指针之后,紧接着的为“头结点”,如上所述的head之后阴影部分结点,从a1开始到an结束,是该链表的表结点,其中a1称为首届点,an称为尾结点。(头结点的数据域可以为空)

       对应这个问题,输入的内容:(2 -> 4 -> 3) +  (5 -> 6 -> 4),可以理解为两个链表之间对应元素的加法运算。


       2.链表在java上的映射

       在jdk封装的工具类中,LinkedList就是一个很好使用的链表类,但是在leetCode上面,题意中封装了链表类:

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}
        可见,和《数据结构导论》中用类C语言实现的类结构一模一样,变量val充当数据域Data,变量next充当指针,指向下一个NodeList。通过构造方法,为变量val赋值。


       3.在java中用链表表示(2 -> 4 -> 3)和(5 -> 6 -> 4)

       起初,自己还是比较懵的,毕竟在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也是这样。


      4.设计加法的算法

       由题意可知,最初在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 -> 6 -> 9)和(3 -> 4 -> 5)来debug了一下,并截图,如下:

      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. 



         



评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值