算法 - 两数之和

289 阅读6分钟

两数之和 1(简单难度)

给定一个整数数组 nums  和一个整数目标值 target,请你在该数组中找出 和为目标值 的那   两个   整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 示例 2:

输入:nums = [3,2,4], target = 6 输出:[1,2] 示例 3:

输入:nums = [3,3], target = 6 输出:[0,1]

分析

数组中任意两个数的值等于目标值,则返回他们的下标:第一反应的解法,双层遍历,暴力破解法,时间复杂度为 O(n^2)。考虑到能不能优化呢?能否 O(n)就能解出呢? 首先考虑个问题,目标值 target - 当前值 nums[i] = nums[j],如存在这样的组合,只要计算出 i,j 就行了,只要保证 nums[i]在数据结构中存在唯一性就行了,有什么数据结构是唯一性的呢?js 中对象的 key 值就是唯一性,那么只需要将 nums[i]存为对象 key 的值,对应的索引存为对象的 value 值,每次判断 target - nums[i] 是否是对象的 key 值,如果是则前面出现过,将对应的索引返回即可。

代码实现

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (nums, target) {
  //  缓存nums长度
  const len = nums.length;
  // 初始化map
  const map = new Map();

  // 遍历nums
  for (let i = 0; i < len; i++) {
    // 如果当前对象 目标值 - 当前值 已经存入对象中,说明前面已经出现过,返回对应下标
    if (map.has(target - nums[i])) {
      return [map.get(target - nums[i]), i];
    }
    // 存储当前值为集合key,下标为集合值
    map.set(nums[i], i);
  }
};

链表两数相加 2(中等难度)

给你两个   非空 的链表,表示两个非负的整数。它们每位数字都是按照   逆序   的方式存储的,并且每个节点只能存储   一位   数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

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

addtwonumber1.jpeg

输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807. 示例 2:

输入:l1 = [0], l2 = [0] 输出:[0] 示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9] 输出:[8,9,9,9,0,0,0,1]

分析

从题目中两个链表中的数字都是按照逆序排列的,所以只要对两个链表重头到尾遍历,对应节点两两相加,大于 9 进一位即可。步骤如下:

  1. 创建一个 dummy 的头结点链表,用于存储相加后的值;
  2. 遍历两个链表 l1 || l2,取每一次遍历的节点值相加,如果大于 9,进一位,并将个位数的值存储起来;
  3. 继续遍历下一个的 l1 || l2 节点,取出他们的值进行第二步操作,直到遍历结束;
  4. 遍历完成后,进位数还存在,则将进位数放到链表最末尾;

l1_l2.jpg

代码实现

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function (l1, l2) {
  // 新建头结点
  let dummy = new ListNode();
  // 当前节点指向头结点
  let cur = dummy;
  // 初始化进位数,默认为0
  let carry = 0;
  // 遍历两个链表
  while (l1 || l2) {
    // 获取链表1的值,若不存在设为0
    let val1 = l1 ? l1.val : 0;
    // 获取链表2的值,若不存在设为0
    let val2 = l2 ? l2.val : 0;
    // 求出当前两个链表之和,包括进位数
    const sum = val1 + val2 + carry;
    // 如果sum小于10,不进位,当前节点直线新建的节点,否则进位,当前节点指向十位数
    cur.next = new ListNode(sum % 10);
    // 判断是否进位,进位值为1, 否则为0
    carry = sum < 10 ? 0 : 1;

    // 链表1存在,则链表1向下遍历
    l1 && (l1 = l1.next);
    // 链表2存在,则链表2向下遍历
    l2 && (l2 = l2.next);

    // 存储和的链表继续向下走
    cur = cur.next;
  }

  // 如果还存在进位数,则需要创建一个节点存储该进位数
  if (carry === 1) {
    cur.next = new ListNode(carry);
  }

  return dummy.next;
};

两数相加 3(中等难度)

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

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

进阶:

如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。

示例:

输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) 输出:7 -> 8 -> 0 -> 7

分析

相比上一题区别是上一题是逆序的链表,本题最高位为链表的开始位置,即两个正序列表相加。结合上一题的思路,我们只需要将链表先反转后,再根据上一题思路来。考虑到进阶提示:不能对列表的节点进行翻转,那么该如何处理呢? 可以借鉴栈来存储链表每个元素,然后在出栈,就可以从最后一个节点值开始相加。然后进行翻转即可,步骤如下:

  1. 从头结点分别遍历 l1、l2,将每次遍历的节点值分别入栈 stack1、stack2;
  2. 初始化头结点cur、上一个节点pre、进位数carry;
  3. 同时遍历 stack1、stack2 且进位数不为 0;
  4. 对 stack1 出栈,栈为空设置值为 0,否则为出栈的值: val1 = stack1.pop() || 0;对 stack2 出栈,栈为空设置值为 0,否则为出栈的值: val2 = stack1.pop() || 0;
  5. 求和: sum = val1 + val2 + carry, 如果 sum < 10,不进位 carry = 0, 否则进位 carry = 1;
  6. 创建新节点,节点值为 sum % 10;
  7. 反转设置:当前节点的下一个节点指向 pre: cur.next = pre; pre 的下一个节点指向当前节点:pre.next = cur;
  8. 继续遍历下一个节点:cur = cur.next, 重复遍历完 stack1,stack2 且 carry = 0;
  9. 返回反转后的节点:pre;

代码实现

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * 分析:
 *  1、数字最高位在开始位置,故想要数字相加的话,需要翻转链表,题目要求不能翻转链表,
 *      故可以考虑先存放于栈中,每次出栈就是从最低位开始。
 *  2、相加的数大于9,不进位,否则进一位。
 *  3、依次遍历到结束。得到的链表是逆序的,需要翻转。
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function (l1, l2) {
  // 存放l1的链表值的栈
  const stack1 = [];
  // 存放l2的链表值的栈
  const stack2 = [];

  // 遍历链表1,将每个节点的值存放到stack1中,分别入栈
  while (l1) {
    stack1.push(l1.val);
    l1 = l1.next;
  }

  // 遍历链表2,将每个节点的值存放到stack2中,分别入栈
  while (l2) {
    stack2.push(l2.val);
    l2 = l2.next;
  }

  // 定义头结点
  let cur = new ListNode();
  // 定义上一个节点
  let pre = null;
  // 定义进位
  let carry = 0;
  // 遍历栈stack1、stack2,分别出栈
  while (stack1.length || stack2.length || carry !== 0) {
    // 栈1不为空,获取出栈值,否则值为0
    const val1 = stack1.length ? stack1.pop() : 0;
    // 栈2不为空,获取出栈值,否则值为0
    const val2 = stack2.length ? stack2.pop() : 0;
    // 求和,值1 + 值2 + 进位数
    const sum = val1 + val2 + carry;
    // 定义节点
    cur = new ListNode(sum % 10);
    // 计算进位数
    carry = sum < 10 ? 0 : 1;
    // 反转链表
    // 当前节点的下一个节点指向pre
    cur.next = pre;
    // 上一个节点指向当前节点
    pre = cur;

    // 继续下一个节点
    cur = cur.next;
  }

  // 返回反转后的链表
  return pre;
};