1,两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
自主独立写法,只考虑回答正确
思路:两个for循环遍历把所有的两数之和来与targer比较,没有考虑性能优化,只负责回答正确
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i+1; j < nums.length; j++) {
if(target == (nums[i] +nums[j])){
return new int[]{i,j} ;
}
}
}
return null ;
}
执行用时:54 ms, 在所有 Java 提交中击败了**27.36%的用户
内存消耗:41.9 MB, 在所有 Java 提交中击败了16.24%**的用户
通过测试用例:57 / 57
时间复杂度:O(n^2)
考虑时间复杂度,用到Map
思路:用map中的键值对来进行存储,只用遍历一次
public int[] twoSum(int[] nums, int target) {
int[] ints = new int[2];
if (nums == null || nums.length==0){
return ints ;
}
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int temp = target - nums[i] ;
//到map中匹配另一半,匹配成功则找到了两数
if (map.containsKey(temp)){
ints[0] = i ;
ints[i] = map.get(temp) ;
}
//将数值的值和下标都存储到map中
map.put(nums[i] , i) ;
}
return ints ;
}
执行用时:5 ms, 在所有 Java 提交中击败了**48.60%的用户
内存消耗:42.6 MB, 在所有 Java 提交中击败了5.03%**的用户
通过测试用例:57 / 57
时间复杂度:O(n)
继续优化采用双指针思想
思路:在用while循环对数值从头尾两边进入map
public int[] twoSum(int[] nums, int target) {
int star = 0 , end = nums.length -1 ;
HashMap<Integer, Integer> map = new HashMap<>();
while (star < end){
if (map.containsKey(target - nums[star])){
return new int[]{star , map.get(target -nums[star])} ;
}else {
map.put(nums[star] , star) ;
}
if (map.containsKey(target - nums[end])){
return new int[]{end , map.get(target -nums[end])} ;
}else {
map.put(nums[end] , end) ;
}
star++ ;
end-- ;
}
return null ;
}
执行用时:0 ms, 在所有 Java 提交中击败了**100.00%的用户
内存消耗:41.7 MB, 在所有 Java 提交中击败了34.61%**的用户
通过测试用例:57 / 57
优点:提升了速度
一些问题
- 为什么要用map,不用set等其他集合
2,两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
常规方法:迭代方法
实现思路:
我们可以遍历两个链表,并且同时从头开始对每个节点进行加法运算。由于链表是按照逆序存储的,因此我们可以从链表头开始遍历,将每个节点的值相加,然后构造一个新的链表来存储结果。如果两个链表的长度不相等,我们可以认为长度短的链表的缺失位为0
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//创建一个零节点
ListNode listNode = new ListNode(0);
ListNode p = l1 ,q = l2 ,curr = listNode ;
//进位数,用来记录进位
int carry = 0 ;
//循环两个链表直到两个都为空为止
while (p!=null || q!=null){
//将俩个链表的数相加
int a = (p !=null)? p.val : 0 ;
int b = (q != null) ? q.val :0 ;
int sum = a + b + carry ;
//保存进位
carry = sum / 10 ;
//创建新节点用来存储相加后的数,并接在curr节点后面
curr.next = new ListNode(sum % 10);
curr = curr.next ;
//在将初始的l1,l2移到下一个节点
if (p != null) p = p.next ;
if (q !=null) q = q.next ;
}
//最后carry进位,在往后接一个节点
if (carry>0){
curr.next = new ListNode(carry) ;
}
return listNode.next ;
}
算法时间复杂度:O(max(m,n)),取决于两个链表的最大长度
方法二:递归方法
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
return addNewTwoNumbers(l1 ,l2 ,0) ;
}
private ListNode addNewTwoNumbers(ListNode l1, ListNode l2 , int carry){
//设置递归结束条件
if (l1==null && l2==null && carry==0)
return null ;
int sum = (l1 !=null ? l1.val :0) + (l2 !=null ? l2.val :0) + carry;
carry = sum / 10 ;
ListNode node = new ListNode(sum % 10);
node.next = addNewTwoNumbers(l1!=null ? l1.next:null , l2!=null ? l2.next:null ,carry) ;
return node;
}
一些问题
- 迭代和递归的区别(chatgpt)
迭代是指通过**循环结构**
来反复执行一段代码,以达到某种目的。迭代的优点是能够明确控制循环次数,能够在循环过程中方便地修改循环变量的值,并且能够在循环结束后直接返回结果。缺点是代码结构可能比较繁琐,容易出现深度嵌套,不易读写和维护。
递归是指通过**函数的调用**
来反复执行一段代码,以达到某种目的。递归的优点是能够简化算法的实现,使代码更加清晰易读,并且能够处理一些复杂的问题,如树的遍历、图的搜索等。缺点是递归需要消耗大量的系统资源,容易导致堆栈溢出等问题,并且可能会出现死循环等错误。