二分查找:
69. x的平方根(e)
重点:1. 用x/m<m而不是m*m>x防止溢出;2. 用右中位数防止无限循环
744. 寻找比目标字母大的最小字母(e)
重点:字母顺序可以循环(a>z),且letters数组是有序的。因此,最后退出循环的时候还要判断:
return letters[l] > target ? letters[l] : letters[0];
540. 有序数组的单一元素(m)
重点:题目中要求要O(logn)的复杂度,所以应该用二分;如果不要求,用位运算(O(n))。
1) 二分中的判断,主要是判断的是num[mid]和num[mid+1]是否相等,就可以判断单个数字已经出现在前部分还是还没有出现(前提就是一定保证mid始终指向的是索引为偶数的位)
public int singleNonDuplicate(int[] nums) {
if (nums.length <= 0 || nums == null) {
return -1;
}
if (nums.length == 1) {
return nums[0];
}
int l = 0, r = nums.length-1;
// 有要求的数字的一方一定是奇数个
while (l < r) {
// mid最开始肯定是第偶数个,所以判断mid和mid+1
// 在只出现一次的数字之前,两个相同数字的下标一定是{偶数,奇数};在一次的数字之后,下标一定是{奇数,偶数}
int mid = l + (r - l) / 2;
// 保证mid只指向偶数,这样就只需要判断相同数的位置,就可以判断单个数字在前部分还是后部分
if (mid % 2 == 1) mid--;
if (nums[mid] == nums[mid + 1]) {
l = mid + 2;
}
else {
r = mid;
}
}
return nums[l];
}
278. 第一个错误的版本(e)
153. 寻找旋转排序数组中的最小值
重点:循环内的判断条件
if (nums[mid] > nums[r]) {
l = mid + 1;
}
else {
r = mid;
}
34. 在排序数组中查找元素的第一个和最后一个位置(m)
重点:两次二分查找。分别查找该数第一次出现的位置和target+1应该出现的位置(该数-1就是所求的end)
public int[] searchRange(int[] nums, int target) {
int[] res = new int[2];
if (nums.length <= 0 || target < nums[0] || target > nums[nums.length-1]) {
res[0] = -1;
res[1] = -1;
return res;
}
int first = findIndex(nums,target);
int last = findIndex(nums,target+1);
if (first == nums.length || nums[first] != target) {
return new int[]{-1,-1};
}
return new int[]{first,last-1};
}
/**
* 求target第一次出现的位置
* @param nums
* @param target
* @return
*/
private int findIndex(int[] nums, int target) {
// r的初始值不是length-1,而是length,因为若不存在可能会返回最后一个
int l = 0 , r = nums.length;
while (l < r) {
int mid = l + (r - l) / 2;
if (nums[mid] < target) {
l = mid + 1;
}
else {
r = mid;
}
}
return l;
}
分治:
分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
可以分成i的左边和i的右边这种子问题的用分治。
参数中一定要能表示开始和结束(比如241题,substring函数可以截取字符串,潜在的表明开始和结束;但是95题不行,所以需要一个helper函数用来递归)。
241. 为表达式设计优先级(m)(记住!!!!!)
思路:
带备忘录(HashMap)的分治算法,提高对重复子问题的运算效率。
例如2*3-4*5
1. 遇到第一个符号*:左[2],右[3-4*5]
此时左边直接返回,右边(3-4*5)递归:
2. 遇到第一个符号-:左[3],右[4*5]
左边直接返回,右边(4*5)递归:
3. 遇到第一个符号*,左4右5,结果20,直接返回到2,(3-4*5)结果【1】-17;
4. 此时2有了第一个结果结果【1】,继续遍历,遇到第二个符号*:左[3-4],右[5]
左边(3-4)递归,右边直接返回:
5. 遇到第一个符号-,左3右4,结果-1,直接返回到4,(3-4*5)结果【2】-5;
6. 结果【1】和结果【2】返回给1,得到对应的结果【1】和结果【2】-34和-10;
7. 继续遍历(2*3-4*5),遇到第二个符号-:左[2*3],右[4*5],结果【3】-12;
8. 继续遍历(2*3-4*5),遇到第三个符号*:左[2*3-4],右[5]
右边直接返回,左边(2*3-4)递归。。。同上
private HashMap<String, List<Integer>> hm = new HashMap<String, List<Integer>>();
public List<Integer> diffWaysToCompute(String input) {
if(hm.containsKey(input))
return hm.get(input);
List<Integer> res = new ArrayList<Integer>();
for (int i=0; i<input.length(); i++) {
char ch = input.charAt(i);
if (ch=='+' || ch=='-' || ch=='*') {
for (Integer l : diffWaysToCompute(input.substring(0, i)))
for (Integer r : diffWaysToCompute(input.substring(i + 1))) {
if (ch == '+')
res.add(l + r);
else if (ch == '-')
res.add(l - r);
else
res.add(l * r);
}
}
}
if (res.size() == 0) res.add(Integer.valueOf(input));
hm.put(input, res);
return res;
}
96. 不同的二叉搜索树(m)
作者:LeetCode
链接:https://leetcode-cn.com/problems/unique-binary-search-trees/solution/bu-tong-de-er-cha-sou-suo-shu-by-leetcode/
给定一个有序序列 1 ... n,为了根据序列构建一棵二叉搜索树。我们可以遍历每个数字 i,将该数字作为树根,1 ... (i-1) 序列将成为左子树,(i+1) ... n 序列将成为右子树。于是,我们可以递归地从子序列构建子树。
在上述方法中,由于根各自不同,每棵二叉树都保证是独特的。
问题是计算不同二叉搜索树的个数。为此,我们可以定义两个函数:
G(n): 长度为n的序列的不同二叉搜索树个数。
F(i, n): 以i为根的不同二叉搜索树个数。
可见, G(n) 是我们解决问题需要的函数。
举例而言,F(3, 7),以 3 为根的不同二叉搜索树个数。为了以 3 为根从序列 [1, 2, 3, 4, 5, 6, 7] 构建二叉搜索树,我们需要从左子序列 [1, 2] 构建左子树,从右子序列 [4, 5, 6, 7] 构建右子树,然后将它们组合(即笛卡尔积)。
巧妙之处在于,我们可以将 [1,2] 构建不同左子树的数量表示为 G(2), 从 [4, 5, 6, 7]` 构建不同右子树的数量表示为 G(4)。这是由于 G(n) 和序列的内容无关,只和序列的长度有关。于是,F(3,7) =G(2)⋅G(4)。
public int numTrees(int n) {
if (n <= 1) {
return 1;
}
int[] G = new int[n+1];
G[0] = 1;
G[1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
G[i] += G[j-1] * G[i-j];
}
}
return G[n];
}
95. 不同的二叉搜索树II(m)
思路:由上!!
我们从序列 1 ..n
中取出数字 i
,作为当前树的树根。于是,剩余 i - 1
个元素可用于左子树,n - i
个元素用于右子树。
现在,我们对序列 1 ... i - 1 重复上述过程,以构建所有的左子树;然后对 i + 1 ... n 重复,以构建所有的右子树。
这样,我们就有了树根 i 和可能的左子树、右子树的列表。
public List<TreeNode> generateTrees(int n) {
if (n <= 0) {
return new LinkedList<TreeNode>();
}
return geneHelper(1,n);
}
private LinkedList<TreeNode> geneHelper(int start, int end) {
LinkedList<TreeNode> res = new LinkedList<>();
if (start > end) {
res.add(null);
return res;
}
for (int i = start; i <= end; i++) {
LinkedList<TreeNode> left = geneHelper(start,i-1);
LinkedList<TreeNode> right = geneHelper(i+1,end);
// connect left and right trees to the root i
// 其实是一种笛卡尔乘积,G(i-1)*G(n-i)
for (TreeNode lnode : left) {
for (TreeNode rnode : right) {
TreeNode currentRoot = new TreeNode(i);
currentRoot.left = lnode;
currentRoot.right = rnode;
res.add(currentRoot);
}
}
}
return res;
}