Leetcode笔记-2.两数相加

0.问题重述

给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)

输出:7 -> 0 -> 8

原因:342 + 465 = 807

1.问题分析

题目将所有的数据储存为链表,实际上是简化了计算,并且本身是逆序存储,符合从个位到十位再往上的加法原理,进位也比较好处理。所以就对两个链表进行遍历,新的链表储存对应位置的和。设置一个bool值,如果进位就将bool值更新。若是某个链表最终遍历结束,而另一个链表还未结束,就将多出来的部分全部加进去。

2.代码

class Solution {
public:
	ListNode * addTwoNumbers(ListNode* l1, ListNode* l2) {
		
		ListNode *head = new ListNode(0);
		ListNode *p = head;
		ListNode *res = nullptr;

		bool flag = true;
		while (l1&&l2) {
			int t = (l1->val + l2->val);
			if (flag == false) {
				t++;
				flag = true;
			}
			if (t >= 10) {
				flag = false;
			}
			res = new ListNode(t % 10);
			p->next = res;
			p = res;
			l1 = l1->next;
			l2 = l2->next;
		}
		if (!l1 && !l2) {
			if (flag == false) {
				res = new ListNode(1);
				p->next = res;
				p = res;
			}
			p->next = nullptr;
			head = head->next;
			return head;
		}
		if (!l1) {
			while (l2) {
				int t = l2->val;
				if (flag == false) {
					t++;
					flag = true;
				}
				if (t >= 10) {
					flag = false;
				}
				res = new ListNode(t%10);
				p->next = res;
				p = res;
				l2 = l2->next;
			}
			if (flag == false) {
				res = new ListNode(1);
				p->next = res;
				p = res;
			}
			p->next = nullptr;
			head = head->next;
			return head;
		}
		if (!l2) {
			while (l1) {
				int t = l1->val;
				if (flag == false) {
					t++;
					flag = true;
				}
				if (t >= 10) {
					flag = false;
				}
				res = new ListNode(t%10);
				p->next = res;
				p = res;
				l1 = l1->next;
			}
			if (flag == false) {
				res = new ListNode(1);
				p->next = res;
				p = res;
			}
			p->next = nullptr;
			head = head->next;
			return head;
		}
		return head;
	}
};

在写代码的时候遇到了如下几个问题:

  • 没有考虑两个长度相同链表的进位问题。比如说5+5=10,在链表遍历完成后应该再判断一次进位。
  • 没有考虑两个不同长度的链表多次进位问题。比如说1+99,在单独对第二个链表进行操作的时候仍然进行了两次进位。
  • 多余考虑了0的情况。原来的算法在一开始对首位开始为0进行了判断,如果是0,直接返回另一个链表。但这实际上是不对的,因为有可能是1230的情况。另外,即使0在开头,也不用去额外判断,因为0对加法没有影响。所以无需额外进行0的判断。

3.代码优化

看了leetcode的官方解答也是采取的这种思路,但是网上其他的解答有的就精简许多。基本上只有了1/4的代码量…
我还是太vegetable了。

class Solution {
public:
	ListNode * addTwoNumbers(ListNode* l1, ListNode* l2) {

		ListNode *head = new ListNode(0);
		ListNode *p = head;
		int sum = 0;
		while (l1 || l2) {
			if (l1) {
				sum += l1->val;
				l1 = l1->next;
			}
			if (l2) {
				sum += l2->val;
				l2 = l2->next;
			}
			p->next = new ListNode(sum % 10);
			sum /= 10;
			p = p->next;
		}
		if (sum) {
			p->next= new ListNode(1);
		}
		return head->next;
	}
};

他将两个链表判空放在一起,不管哪个空了都无所谓,反正sum的处理都在最后。并且只用两个指针,一个头指针,一个是下一位的指针。

所有相同的部件都可以拿出来共同使用

复杂度分析

时间复杂度:O(\max(m, n))O(max(m,n)),假设 mm 和 nn 分别表示 l1l1 和 l2l2 的长度,上面的算法最多重复 \max(m, n)max(m,n) 次。

空间复杂度:O(\max(m, n))O(max(m,n)), 新列表的长度最多为 \max(m,n) + 1max(m,n)+1。

4.Java解法

Java解法的思路与C++一致。算法上并无差别。

但是仔细思考后还是存在一些问题,因为我开始学习的语言是C++,指针在其中占据了非常重要的位置。特别是在链表相关的题目里,但是Java包括之后我们要讨论的Python,都不存在指针,那么对于这两种语言而言,是怎么处理链表这种数据结构的呢?

实际上Java内部,除了基本的数据类型外,均为引用,栈变量指向了堆里的变量,本身可理解为指针。我们可以看到有时在新建一个数据的时候,本身采用的代码就是:

ListNode next = new ListNode(0);

int[] a=new int[10];

实际上就是堆上有一个数据实例,栈上的变量指向了该实例。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode result = new ListNode(0);
        ListNode first = l1;
        ListNode second = l2;
        int val,rest = 0;
        boolean isHead = true;
        ListNode tmp = result;
        while(null != first && null != second) {
            val = first.val + second.val + rest;
            if(isHead) {
                result.val = val % 10;
                isHead = false;
            } else {
                ListNode next = new ListNode(val % 10);
                tmp.next = next;
                tmp = next;
            }
            rest = val / 10;
            first = first.next;
            second = second.next;
        }
        // 到这里,first和second至少有一个为null
        if(null != first || null != second) {
            ListNode t = null != first ? first : second;
            tmp.next = addTwoNumbers(new ListNode(rest),t); // 递归执行剩下的
        } else {
            // 都为空
            if(0 != rest) {
                tmp.next = new ListNode(rest);
         }
        }
        return result;
    }
}

5.Python解法

注意在写的时候还是没考虑到一个条件,最后的判断条件不能是首数字为0,因为有可能没有进位,应该直接设置一个进位符,根据进位符进行判断。

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
		head=ListNode(0)
		res=head
		c=0
		while (l1||l2):
			x=l1.val if l1 else 0
			y=l2.val if l2 else 0
			s=x+y+c
			c=s//10;
			res.next=ListNode(s%10)
			res=res.next
			if(l1.next!=None):l1.next
			if(l2.next!=None):l2.next
		if (c>0) :
			res.next=ListNode(1)
		return head->next

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法。 LeetCode的动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存中间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法与数据结构基础》等,来深入理解这种算法思想。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值