LeetCode_1_TwoSum
特点:数据无序、无重,确定有唯一解
-
直接双重循环暴力解决,时间 O n 2 O_{n^2} On2,空间 O 1 O_{1} O1。
-
每次都要遍历查找,因此可以通过空间换时间,缩短查找的时间开销,先遍历一次建立散列表(HashMap),再遍历一次利用散列表查找,时间 O n O_{n} On,空间 O n O_{n} On。(此外还有HashTable,HashSet)
class Solution { public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { map.put(nums[i], i); } for (int i = 0; i < nums.length; i++) { int numToFind= target - nums[i]; if (map.containsKey(numToFind) && map.get(numToFind) != i) { return new int[] { i, map.get(numToFind) }; } } throw new IllegalArgumentException("No two sum solution"); } }
-
补是相互概念。在第一次遍历建立散列表的时候,就可即时查找。在遍历到答案对中出现的第一个元素时,查早了,有补但还没插入,但不用慌,后续遍历到答案对的第二个元素时,直出解。
这里由于是先判断是否有补,再将元素插入散列表,因此,无需判断是否补为自身。
class Solution { public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int numToFind= target - nums[i]; if (map.containsKey(numToFind)) { return new int[] { map.get(numToFind), i }; } map.put(nums[i], i); } throw new IllegalArgumentException("No two sum solution"); } }
-
Java泛型的类型参数必须是non-primitive type。不能用 int,而是用 Integer,即其封装类型。(class, interface, array也可作为其类型参数)
LeetCode_167_TwoSum II
特点:数据升序、无重,确定有唯一解
-
相比于之前,数据变为有序,因此应考虑如何利用数据的顺序性带来的信息增益。
-
双指针法之对撞指针:
定义两个指针分别从数据始末相对遍历,左指针右移时,两数和增,右指针左移时,两数和减。依次搜索至解。 t : O n t:O_{n} t:On , s : O 1 s:O_{1} s:O1对撞指针的适用场景应是:数据必然是有序的,而其排列上,沿左右两个方向有明显的单调性,以及必定可通过对撞的方式求解(好像是句废话)。
class Solution { public int[] twoSum(int[] numbers, int target){ int i = 0; int j = numbers.length - 1; while (numbers[i] + numbers[j] != target){ if (numbers[i] + numbers[j] < target) i++; else j--; } return new int[] {i+1, j+1}; } }
-
二分查找:由于数据有序,故可考虑使用二分查找法。那么整体思路也就很简单,在遍历的同时查找。
class Solution { public int[] twoSum(int[] numbers, int target) { int n = numbers.length; for(int i=0;i<n-1;i++){ int pos = Arrays.binarySearch(numbers,i+1,n,target-numbers[i]); if(pos>0) return new int[]{i+1,pos+1}; } } }
-
二分查找源码实现:
public static int binarySearch(char[] a, int fromIndex, int toIndex, char key) { rangeCheck(a.length, fromIndex, toIndex); return binarySearch0(a, fromIndex, toIndex, key); } static void rangeCheck(int arrayLength, int fromIndex, int toIndex) { if (fromIndex > toIndex) { throw new IllegalArgumentException( "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); } if (fromIndex < 0) { throw new ArrayIndexOutOfBoundsException(fromIndex); } if (toIndex > arrayLength) { throw new ArrayIndexOutOfBoundsException(toIndex); } } private static int binarySearch0(char[] a, int fromIndex, int toIndex, char key) { int low = fromIndex; int high = toIndex - 1; while (low <= high) { int mid = (low + high) >>> 1; char midVal = a[mid]; if (midVal < key) low = mid + 1; else if (midVal > key) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found. }
-
计算机中的位操作是按补码进行的。
-
正数的二进制原码,反码,补码都是一样的,引入此概念只是为了限制负数。原码:正负数的二进制码,其中负数的最高位为1表示负号。反码:除符号位外,其余位取反即得反码。补码:反码+1得补码,其中定义 minValue = -1000_0000 = -128(以byte类型数为例)。参考资料
-
java中,<<为左移,低位补0,负数的符号位会被移出。>>为右移,正数高位补0,负数补1。>>>为无符号右移,高位补0。
-
java中,byte类型数在位移运算时,会自动被转换成 int 类型,再进行位移运算,最终得到一个 int 类型的数。参考资料
byte ori = -20; //补码 1110_1100 int a = (ori<<3); //a = -160 byte b = (byte)(ori<<3); //b = 96 int c = (ori>>>1); //c = 2147483638 byte d = (byte)(ori>>>1); //d = -10 byte e = (byte)((ori&(0xff))>>>1); //e = 118
-
很迷啊,源码使用的无符号右移到底有啥用?网上都是防溢出的做法。
LeetCode_653_TwoSum IV
特点:输入是二叉搜索树
- 最直观的想法,中序遍历一次BST建立有序数组,即可转化为 LeetCode_167_TwoSum II 的情况,之后双指针即可。脑补 t : O n , s : O n t:O_{n},s:O_{n} t:On,s:On
仔细想一下,既然已经有一个良好的搜索树结构,那就想办法利用这个优势。故可层序遍历BST,同时搜索是否有补。脑补 t : O n l o g n , s : O 1 t:O_{nlogn},s:O_{1} t:Onlogn,s:O1(如果是平衡二叉的话)。
好吧,这种做法确实利用了搜索树结构的优势,但本质就是最直接的暴力解法。- 好吧,solution 提供的解法1用了HashSet,核心思想与之前的一致,也就是时间换空间,
t
:
O
n
,
s
:
O
n
t:O_{n},s:O_{n}
t:On,s:On。这个应该比我的第一个想法要快(只遍历一次)。但这个好像没用到二叉搜索树的特点,随便一个二叉树都能用这种方法,佛了…。
class Solution { public boolean findTarget(TreeNode root, int k) { HashSet<Integer> set = new HashSet<>(); return find(root, k, set); } public boolean find(TreeNode root, int k, HashSet 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); } }
- 解法2跟解法1思路一致,只不过没用递归,而是额外维护一个队列,BFS遍历整个树。 t : O n , s : O n t:O_{n},s:O_{n} t:On,s:On,两者空间复杂度应该一样。
- 解法3,芜湖,跟我一开始想的一样!