目录
3.search-in-rotated-sorted-array
4.search-in-rotated-sorted-array-ii
1.search-insert-position
题目:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。下面给出几个样例:[1,3,5,6], 5 → 2;[1,3,5,6], 2 → 1;[1,3,5,6], 7 → 4;[1,3,5,6], 0 → 0
分析:二分查找。注意while循环中“<=”以及是return low还是return high,当low == high时,如果目标值比中间值更大,low会赋值为mid + 1,刚好是目标值该插入的位置;如果目标值比中间值更小,此时改变的是high值而low值不变,low刚好是目标值该插入的位置,所以应该返回low。
public int searchInsert(int[] A, int target) {
int low = 0,high = A.length - 1;
while(low <= high){
int mid = (low + high) >> 1;
if(A[mid] == target)
return mid;
else if(target > A[mid])
low = mid + 1;
else
high = mid - 1;
}
return low;
}
2.search-for-a-range
题目:给出一个有序数组,请在数组中找出目标值的起始位置和结束位置,你的算法的时间复杂度应该在O(log n)之内,如果数组中不存在目标,返回[-1, -1].例如:给出的数组是[5, 7, 7, 8, 8, 10],目标值是8,返回[3, 4].
分析:先用二分查找找到其中一个目标值的位置,然后再往两边扩大范围即可。
public int[] searchRange(int[] A, int target) {
int[] range = new int[2];
int low = 0,high = A.length - 1,mid = 0;
while(low <= high){
mid = (low + high) >> 1;
if(A[mid] == target)
break;
if(A[mid] > target)
high = mid - 1;
else
low = mid + 1;
}
if(low > high){//没有目标值
range[0] = -1;range[1] = -1;
}
else{
range[0] = mid;range[1] = mid;
while(range[0] > 0 && A[range[0]] == A[range[0]-1])
range[0]--;
while(range[1] < A.length - 1 && A[range[1]] == A[range[1]+1])
range[1]++;
}
return range;
}
3.search-in-rotated-sorted-array
题目:给出一个转动过的有序数组,你事先不知道该数组转动了多少(例如,0 1 2 4 5 6 7可能变为4 5 6 7 0 1 2).在数组中搜索给出的目标值,如果能在数组中找到,返回它的索引,否则返回-1。假设数组中不存在重复项。
分析:因为数组中不存在重复项,将数组从中间分开必定有一边是有序的,我们可以根据目标值是否在有序的那边,来判断目标值在哪边,从而将原问题规模缩小成原来的一半,时间复杂度为O(logn).
public int search(int[] nums, int target) {
int low = 0,high = nums.length - 1;
while(low <= high){
int mid = (low + high) >> 1;
if(nums[mid] == target)
return mid;
//前半部分有序
// 注意'=',虽然没有重复元素,但有可能mid和low为同一个索引
if(nums[mid] >= nums[low]){
if(nums[low] <= target && nums[mid] > target)
high = mid - 1;
else
low = mid + 1;
}
else{//后半部分有序
if(nums[mid] < target && nums[high] >= target)
low = mid + 1;
else
high = mid - 1;
}
}
return -1;
}
4.search-in-rotated-sorted-array-ii
题目:继续思考题目 "Search in Rotated Sorted Array":如果数组种允许有重复元素怎么办?会影响时间复杂度吗?是怎样影响时间复杂度的,为什么?编写一个函数判断给定目标值是否在数组中。
分析:数组中允许有重复元素,对于a[low] == a[mid]的情况,如数组[1,3,1,1,1]中查找目标值3,在这种情况下,是无法判断目标值在坐半边还是右半边,只能low++去掉一个干预项,在最差的情况下时间复杂度会退化成O(n).
类似题找旋转数组中的最小数字见剑指offer面试题11 https://blog.csdn.net/Nibaby9/article/details/104126765
public int search(int[] A, int target) {
if(A.length == 0)
return -1;
int low = 0,high = A.length - 1;
while(low <= high){
int mid = (low + high) / 2;
if(A[mid] == target)
return mid;
if(A[mid] > A[low]){//左半部分有序
if(target < A[mid] && target >= A[low])
high = mid - 1;
else
low = mid + 1;
}
else if(A[mid] < A[low]){//右半部分有序
if(target > A[mid] && target <= A[high])
low = mid + 1;
else
high = mid - 1;
}
else
low++;
}
return -1;
}
5.word-search
题目:给出一个二维字符数组和一个单词,判断单词是否在数组中出现,单词由相邻单元格的字母连接而成,相邻单元指的是上下左右相邻。同一单元格的字母不能多次使用。例如:给出的字符数组=[↵ ["ABCE"],↵ ["SFCS"],↵ ["ADEE"]↵]单词 ="ABCCED", -> 返回 true,单词 ="SEE", ->返回 true,单词 ="ABCB", -> 返回 false.
分析:考查深度优先搜索和回溯。首先遍历 board 的所有元素,找到和 word 第一个字母相同的元素,然后从该位置开始深度遍历,依次看它的上下左右是否符合下一个元素的匹配。注意同一单元格的字母不能多次使用,所以我们需要对访问过的元素进行标记,另外还需要注意是否越边界。
public boolean exist(char[][] board, String word) {
if(board.length == 0)
return false;
if(word.length() == 0)
return true;
int row = board.length,col = board[0].length;
boolean[][] visited = new boolean[row][col];
//依次从每个点开始深度遍历
for(int i = 0;i < row;i++){
for(int j = 0;j < col;j++){
if(board[i][j] == word.charAt(0))
if(DFS(board,word,i,j,1,visited))
return true;
}
}
return false;
}
private boolean DFS(char[][] board, String word, int i, int j, int index, boolean[][] visited) {
if(index == word.length())
return true;
visited[i][j] = true;//试探
//往左
if(i-1 >= 0 && !visited[i-1][j] && board[i-1][j] == word.charAt(index)){
if(DFS(board,word,i-1,j,index+1,visited))
return true;
}
//往右
if(i+1 < board.length && !visited[i+1][j] && board[i+1][j] == word.charAt(index)){
if(DFS(board,word,i+1,j,index+1,visited))
return true;
}
//往上
if(j-1 >= 0 && !visited[i][j-1] && board[i][j-1] == word.charAt(index)){
if(DFS(board,word,i,j-1,index+1,visited))
return true;
}
//往下
if(j+1 < board[0].length && !visited[i][j+1] && board[i][j+1] == word.charAt(index)){
if(DFS(board,word,i,j+1,index+1,visited))
return true;
}
visited[i][j] = false;//回溯
return false;
}
6.combinations
题目:给出两个整数n和k,返回从1到n中取k个数字的所有可能的组合。例如:如果n=4,k=2,结果为[↵ [2,4],↵ [3,4],↵ [2,3],↵ [1,2],↵ [1,3],↵ [1,4],↵]
分析:回溯法。每次只考虑第n个数字取还是不取,如果取就把问题转化为从1到n-1中取k-1个数字,若不取则从1到n-1中取k个数字。所以只要遍历n到1,依次考虑是否取,然后当取0个数字时,就得到了其中一种组合,另外,取的数量不可能大于数字量本身,可根据这个进行剪枝。
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
backtrack(result,cur,n,k);
return result;
}
private void backtrack(List<List<Integer>> result, List<Integer> cur, int n, int k) {
if(k == 0){
result.add(new ArrayList<>(cur));
return;
}
if(n < k)//剪枝
return;
for(int i = n;i >= 1;i--){
cur.add(i);
backtrack(result,cur,i-1,k-1);
cur.remove(cur.size() - 1);
}
}
7.combination-sum
题目:给出一组候选数C和一个目标数T,找出候选数中加起来和等于T的所有组合。C中的数字在组合中可以被无限次使用。注意:题目中所有的数字(包括目标数T)都是正整数;你给出的组合中的数字 (a 1, a 2, … , a k) 要按非递增排序 (ie, a 1 ≤ a 2 ≤ … ≤ a k);结解集中不能包含重复的组合。例如:给定的候选数集是[2,3,6,7],目标数是7,解集是:[7],[2, 2, 3]
分析:回溯法。依次考虑候选数中的每个数是否加入到解集中,这样就把问题转化成了另一个子问题了,因为候选数中允许多次选择,所以递归还是考虑是否加入自己本身。另外,如果候选数中的数已经排好序,会方便我们剪枝,如果当前数已经大于目标数,再加入更大的数也不可能得到目标数。
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
if(candidates.length == 0)
return result;
List<Integer> cur = new ArrayList<>();
Arrays.sort(candidates);
trackback(candidates,target,result,cur,0);
return result;
}
private void trackback(int[] candidates, int target, List<List<Integer>> result, List<Integer> cur,int index) {
if(target == 0){
result.add(new ArrayList<>(cur));
return;
}
if(index == candidates.length || candidates[index] > target)//剪枝
return;
for(int i = index;i < candidates.length;i++){
cur.add(candidates[i]);//试探
trackback(candidates,target - candidates[i],result,cur,i);
cur.remove(cur.size() - 1);//回溯
}
}
8.combination-sum-ii
题目:给出一组候选数C和一个目标数T,找出候选数中起来和等于T的所有组合。C中的每个数字在一个组合中只能使用一次。注意:题目中所有的数字(包括目标数T)都是正整数;组合中的数字 (a 1, a 2, … , a k) 要按非递增排序 (ie, a 1 ≤ a 2 ≤ … ≤ a k);结果中不能包含重复的组合。例如:给定的候选数集是[10,1,2,7,6,1,5],目标数是8,解集是:[1, 7],[1, 2, 5],[2, 6],[1, 1, 6]
分析:本题中候选数中每个数字只能使用一次,而且候选数集中可能会有重复的数字,所以我们只要在上题的基础上修改递归(递归考虑下一个数是否加入)和去重即可。
public ArrayList<ArrayList<Integer>> combinationSum2(int[] num, int target) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
ArrayList<Integer> list = new ArrayList<>();
Arrays.sort(num);
combinationSum(num, result, list, 0, target);
return result;
}
private void combinationSum(int[] num, ArrayList<ArrayList<Integer>> result, ArrayList<Integer> list, int index, int target) {
if(target == 0){
result.add(new ArrayList<>(list));
return;
}
if(target < 0)
return;
for(int i = index;i < num.length;i++){
if (i > index && num[i] == num[i - 1]) continue; // 去重
list.add(num[i]);
combinationSum(num,result,list,i+1,target - num[i]);
list.remove(list.size()-1);
}
}