Two Sum (I ~ IV)

来源:互联网 发布:淘宝猛犸象牙是真的吗 编辑:程序博客网 时间:2024/05/28 15:06

Two Sum I    

题目:Given an array of integers, return indices of the two numbers such that they add up to a specific target.

           You may assume that each input would have exactly one solution, and you may not use the same element twice.

Example:

Given nums = [2, 7, 11, 15], target = 9,Because nums[0] + nums[1] = 2 + 7 = 9,return [0, 1].
Code:

class Solution {    public int[] twoSum(int[] nums, int target) {       Map<Integer, Integer> map = new HashMap<>();       for (int i = 0; i < nums.length; i++) {         int complement = target - nums[i];         if (map.containsKey(complement)) {             return new int[] { map.get(complement), i };         }         map.put(nums[i], i);       }       throw new IllegalArgumentException("No two sum solution");    }}

这种解法Leetcode上运行耗时8ms,这是一种典型的以空间换取时间的解法,最关键的是引入了Map<Integer, Integer> map这个结构。

Complexity Analysis:

  • Time complexity : O(n)O(n). We traverse the list containing nn elements only once. Each look up in the table costs only O(1)O(1) time.

  • Space complexity : O(n)O(n). The extra space required depends on the number of items stored in the hash table, which stores at most nn elements.


Two Sum II - Input array is sorted

题目:Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number.

The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.

You may assume that each input would have exactly one solution and you may not use the same element twice.

Input: numbers = {2, 7, 11, 15}, target = 9
Output: index1 = 1, index2 = 2

Code:

class Solution {    public int[] twoSum(int[] numbers, int target) {        int[] indice = new int[2];        if (numbers == null || numbers.length < 2) return indice;        int left = 0, right = numbers.length - 1;        while (left < right) {            int v = numbers[left] + numbers[right];            if (v == target) {                indice[0] = left + 1;                indice[1] = right + 1;                break;            } else if (v > target) {                right --;            } else {                left ++;            }        }        return indice;    }}

如果忽略掉数组是有序的这一条件,当然可以用Two Sum I的解法,使用这种解法在Leetcode上运行耗时5ms。而在有序数组中找到符合条件的数,首先想到的就是二分查找法,该算法本质上就是二分查找法的变形,时间复杂度为O(logn),比起第一种算法的O(n)是有很大的提高的,而且它的空间复杂度是O(1),在leetcode上运行耗时1ms。

Two Sum IV - Input is a BST

Given a Binary Search Tree and a target number, return true if there exist two elements in the BST such that their sum is equal to the given target.

Example 1:

Input:     5   / \  3   6 / \   \2   4   7Target = 9Output: True

Example 2:

Input:     5   / \  3   6 / \   \2   4   7Target = 28Output: False

这里将Leetcode上三种解法概括总结一下,原文请参考https://leetcode.com/problems/two-sum-iv-input-is-a-bst/description/

Definition for a binary tree node:
public class TreeNode {      int val;      TreeNode left;      TreeNode right;      TreeNode(int x) { val = x; } }

解法一:
public class Solution {    public boolean findTarget(TreeNode root, int k) {        Set < Integer > set = new HashSet();        return find(root, k, set);    }    public boolean find(TreeNode root, int k, Set < Integer > set) {        if (root == null)            return false;        if (set.contains(k - root.val))            return true;        set.add(root.val);        return find(root.left, k, set) || find(root.right, k, set);    }}

这种解法的思想与Two Sum I相同,只不过Two Sum I的input是一个数组,而它的input是一棵二叉搜索树,由于这里只要求返回存不存在这样一个target k,所以只需要Set<Integer>set这一个辅助结构,它的时间复杂度O(n), 空间复杂度O(n)。Leetcode上实测解法一的效率是最好的33ms。

解法二:
public class Solution {    public boolean findTarget(TreeNode root, int k) {        Set < Integer > set = new HashSet();        Queue < TreeNode > queue = new LinkedList();        queue.add(root);        while (!queue.isEmpty()) {            if (queue.peek() != null) { //判断队列的头结点                TreeNode node = queue.remove();                if (set.contains(k - node.val))                    return true;                set.add(node.val);                queue.add(node.right);                queue.add(node.left);            } else //叶子结点的左右指针仍然会入队列,所以要remove                queue.remove();        }        return false;    }}
解法二与解法一的思路完全相同, 区别在于树的层次遍历算法, 解法二没有使用递归, 而是使用了一个辅助结构Queue<TreeNode>queue,Leetcode上该算法实际运行时间为39ms,解法二的时间复杂度为O(n),空间复杂度虽然也是O(n),但是实际数量级是2n(多了一个queue结构),因此除非题目明确要求不允许用递归,否则不推荐解法二。
解法三:


public class Solution {    public boolean findTarget(TreeNode root, int k) {        List < Integer > list = new ArrayList();        inorder(root, list);        int l = 0, r = list.size() - 1;        while (l < r) {            int sum = list.get(l) + list.get(r);            if (sum == k)                return true;            if (sum < k)                l++;            else                r--;        }        return false;    }    public void inorder(TreeNode root, List < Integer > list) {        if (root == null)            return;        inorder(root.left, list);        list.add(root.val);        inorder(root.right, list);    }}


解法三的算法思路其实就是Two Sum II,因为这里的input是BST,所以我们用中序遍历法可以很轻易的将这个input的BST变成一个有序数组,然后再使用折半查找法,这里多了一个辅助结构List<Integer>list,存放遍历后的BST元素,所以空间复杂度为O(n),Leetcode上运行耗时38ms,其时间复杂度应为O(n+logn),也就是O(n),耗时略大于解法一,但是在一个数量级上,解法三包含了好几种算法知识:树的中序遍历,折半查找,因此理解学习解法三对复习算法知识还是很有好处的。
Two Sum III – Data structure design 

题目:Design and implement a TwoSum class. It should support the following operations: add and find.

add - Add the number to an internal data structure.
find - Find if there exists any pair of numbers which sum is equal to the value.

For example:

add(1); add(3); add(5);find(4) -> truefind(7) -> false

网上搜索最多的是以下这种解法:

public class TwoSum {            private HashMap<Integer, Integer> elements = new HashMap<Integer, Integer>();       public void add(int number) {//O(1)   if (elements.containsKey(number)) {        elements.put(number, elements.get(number) + 1);    } else {elements.put(number, 1);    }      }       public boolean find(int value) {//O(n) for (Integer i : elements.keySet()) {      int target = value - i;      if (elements.containsKey(target)) {          if (i == target && elements.get(target) < 2) {      continue;           }   return true;      } } return false;      }}

存储输入数字的结构elements其key为输入数字(从解法看隐含输入数字是可以重复的,题目这一点上没有明确),value为该数字在elements中出现的次数,很容易看出add(number)的时间复杂度是O(1)。find(value)函数有一些绕人,e.g: value = 8, i = 4, target = 4, 则如果element[4] = 2,即数字4出现了两次,表明找到了和等于value的两个数,这两个数是一样的,如果element[4] = 1,表明数字4只出现了一次,不可能由它相加得到value 8,需要继续遍历elements数组寻找正确的数字对,当然如果i 不等于target,则表明已经找到了i + target = value。皓神有一个C++版本的,思想与此相同,不知道是否是此种解法的源出处,贴出他的url:
https://github.com/haoel/leetcode/blob/master/algorithms/cpp/twoSum/twoSum.III.cpp

这种解法代码写起来很简单,但其实想明白不容易,需要深厚的算法功底和一定的技巧,总觉得这非常人所能为,由于这是一道付费的题,我练习Leetcode纯粹只是为了巩固算法,不愿意花那个钱,所以没有练习机会,从网上对这道题的反馈来看,似乎add(number)的复杂度为O(n),则无法通过,要想让add(number)的复杂度降下来,那么首先想到的自然是用树作为存储结构,这里TreeNode的定义与Two Sum IV相同:
public class TwoSumIII {         private TreeNode root;                  public void add(int number) {             if (root == null) {                 root = new TreeNode(number);                 return;             }             else if (root.val > number) {                 root.left = add(root.left, number);             }             else                 root.right = add(root.right, number);             return;         }         public boolean find(int value) {             //any solution from Two Sum IV         }}

第一步建立二叉搜索树,使用的是标准算法,时间复杂度O(logN),二叉搜索树建立好之后,可以使用Two Sum IV中的任意一种解法来实现find,这也是我将Two Sum III放到Two Sum IV后的原因,个人猜测这种解法也是Leetcode出题的初衷。因为I~IV基本是一个循序渐进的过程,Two Sum I教会我们用辅助结构,其实也就是空间换时间的方法来解决这类问题,这是Two Sum问题的基本解决思路。Two Sum II则说明当输入数据是有序数组时如何优化我们的解法,Two Sum IV进一步将输入数据的结构进行了变换,输入数据这时是一个BST结构,本质上是一棵树,但它又可以看成是有序的,顺便考察树的遍历算法,Two Sum III则是对这些内容的一个综合。
       关于Two Sum III网上清一色都是解法一的思路,无意搜到这样一个解法:http://www.cnblogs.com/lichen782/p/4348567.html,就是二叉搜索树的思路,他的运行时间是300ms,仔细分析他的算法感觉有些繁琐,建立二叉树就可以了,再同时维护一个双链表似乎有些不必要,但我只是静态分析代码,并没有实际测试,也许实际运行中有其他tricky,所以他要这样做。
原创粉丝点击