33.搜索旋转排序数组
一看到这种排序数组 + 搜索的题目,我们应当直接联想到二分查找(专门针对于有序的Logn快速查找)。
二分查找基本思路就是通过有序性使得每次搜索都可以丢弃一半的元素,从而达到高效的搜索策略。
但是这道题有点区别在于,虽说他是有序的,但是他是经过了一定的旋转,所以导致不是完全的有序,应该说是一种部分的有序。
我们在进行二分查找的时候,将数组一分为二以后,一定会有一半是完全有序,一半是部分有序(也可能刚好完全有序)。
例如:
4,5,6,7,0,1,2
left = 0 right = 6 -> mid = 3
此时就是以7位分界线,将数组分成
左)[4,5,6,7]
右)[0,1,2] 两个部分。
这时候会出现以下情况
1.左数组为完全有序(num[left] <= nums[mid])
1.1num[mid] < target,说明target在右数组中。(left = mid + 1)
1.2num[left] <= target < num[mid],说明在左数组中。(right = mid - 1)
1.3num[left] > target,说明target在右数组中。(left = mid + 1)
2.左数组为部分有序(num[left] > nums[mid])
2.1num[mid] < target < num[left],说明在右数组中(left = mid + 1)
2.2num[left] <= target,说明在左数组中。(right = mid - 1)
2.3num[mid] > target,说明在左数组中。(right = mid - 1)
//升序数组在某个点反转了(二分搜素)
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
//防止int溢出
int mid = left + (right - left) / 2;
if(nums[mid] == target) {
return mid;
}
//此时将数组一分为二,必定一遍有序,一遍无序(如果都有序则,直接进行二分查找)
if(nums[left] <= nums[mid]) { //说明左半边有序
if(nums[mid] > target) {
if(nums[left] > target) { //说明在右侧无序部分
left = mid + 1;
}else {
right = mid - 1;
}
}else {
left = mid + 1;
}
}else { //说明左半边无序
if(nums[mid] < target) {//比右边界大,比左边界小 (说明在右半边有序区间内部)
if(nums[left] > target) {
left = mid + 1;
}else { //比右边界大,比左边界也大
right = mid - 1;
}
}else { //比右边界小
right = mid - 1;
}
}
}
return -1;
}
34.在排序数组中查找元素的第一个和最后一个的位置
该题目也是一个二分查找的题目,主要思路就是编写一个方法,用于查询第一位(最后一位)的下标即可。
//升序数组在某个点反转了(二分搜素)
public int[] searchRange(int[] nums, int target) {
int[] result = {-1,-1};
result[0] = findIndex(nums,target,true);
result[1] = findIndex(nums,target,false);
return result;
}
public int findIndex(int[] nums, int target, boolean isFirst) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] == target) {
if(isFirst) {
//第一次出现
if(mid == 0 || (mid > 0 && nums[mid] != nums[mid - 1])) {
return mid;
}else {
right = mid - 1;
}
}else {
//最后一次出现
if(mid == nums.length - 1 || (mid < nums.length - 1 && nums[mid] != nums[mid + 1])) {
return mid;
}else {
left = mid + 1;
}
}
}else if(nums[mid] > target){
right = mid - 1;
}else {
left = mid + 1;
}
}
return -1;
}
35.搜索插入位置
该题是如果碰到目标值则放回下表,没碰到则返回插入位置(即第一个大于target的下标)。
今天这个部分基本都是二分查找,上面两道也是,这道题虽然是个简单题,但是也不算太简单。
主要思路:
1.查询到很简单,不讲。
2.查找不到的话,最后left 和 right 一定会定位到两个相邻元素中,作为第一大于和第一小于(如果是边界情况那么两个会重合)
此时left为第一小于,right为第一大于,但这两个值我们还没有搜索过是否 = target。
搜索完毕以后,确认都不 = target,那么此时出循环的 left 和 right 位置会互相交换(这里不好理解,可以跟着代码写一下变换过程)。
所以此时我们应该返回 left(第一个大于target的位置)。
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] == target) {
return mid;
}
if(nums[mid] > target) {
right = mid - 1;
}else {
left = mid + 1;
}
}
return left;
}
38.外观数列
感觉挺蠢的一道题,主要思路就是记录重复字符的次数和字符,然后成对添加成一个字符串。
public String countAndSay(int n) {
String start = "1";
for(int i=1; i<n; i++) {
char prev = ' ';
int times = 0;
StringBuilder stringBuilder = new StringBuilder();
for(int j=0; j<start.length(); j++) {
if(prev == ' ') {
prev = start.charAt(j);
times++;
continue;
}
if(prev != start.charAt(j)) {
stringBuilder.append(times);
stringBuilder.append(prev);
times = 1;
prev = start.charAt(j);
}else {
times++;
}
}
if(times != 0) {
stringBuilder.append(times);
stringBuilder.append(prev);
}
start = stringBuilder.toString();
}
return start;
}
39.组合总和
简单的回溯算法,该题特点在于允许数组元素被重复使用
//数组元素可被多次使用
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
List<List<Integer>> lists = new ArrayList<>();
backTrace(lists,new ArrayList<>(),candidates,0,target,0);
return lists;
}
public void backTrace(List<List<Integer>> lists, List<Integer> list, int[] candidates, int index, int target, int nowValue) {
if(nowValue == target) {
lists.add(new ArrayList<>(list));
return;
}
for(int i=index; i<candidates.length; i++) {
if(nowValue + candidates[i] <= target) {
list.add(candidates[i]);
backTrace(lists,list,candidates,i,target,nowValue + candidates[i]);
list.remove(list.size() - 1);
}else {
break;
}
}
}
40.组合总和2
和39类似,只不过添加了一个不允许重复使用同一个数组的一个元素(但是允许重复元素,只不过不允许重复使用)。
加一个剪枝条件即可
//数组元素可被多次使用
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
List<List<Integer>> lists = new ArrayList<>();
backTrace(lists,new ArrayList<>(),candidates,0,target,0);
return lists;
}
public void backTrace(List<List<Integer>> lists, List<Integer> list, int[] candidates, int index, int target, int nowValue) {
if(nowValue == target) {
lists.add(new ArrayList<>(list));
return;
}
for(int i=index; i<candidates.length; i++) {
//说明出现重复
if(i > index && candidates[i] == candidates[i - 1]) {
continue;
}
if(nowValue + candidates[i] <= target) {
list.add(candidates[i]);
backTrace(lists,list,candidates,i + 1,target,nowValue + candidates[i]);
list.remove(list.size() - 1);
}else {
break;
}
}
}