算法复习二【二分查找、排序、双指针】

4、二分查找

在旋转数组中搜索

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

**思路:**有序的可以采用二分法,我们可以通过第一个数和最后一个数与中间数的关系去判断该数属于前面一段,还是后面一段

二分法思路:

while条件:start+1<end

第一个if:num[mid]==target,返回mid

第二个if:判断属于前大半段(num[start]<num[mid])说明前大半是顺序的

第三个if:判断属于后大半段(num[mid]<num[end])说明后大半是顺序的

最后两个if:是决定返回start还是end的值

public int search(int[] num, int target) {
        if (num==null || num.length==0) {
            return -1;
        }
		int start=0;
		int end=num.length-1;
		while(start+1<end){
			int mid=start+(end-start)/2;
			if (num[mid]==target) {
				return mid;
			}
			/**
			 * 前半部分占大部分
			 */
			if (num[start]<num[mid]) {
				if (target>=num[start]&&target<=num[mid]) {
					end=mid-1;
				}else{
					start=mid+1;
				}
			/**
			 * 后半部分占大部分
			 */
			}else{
				if (target>=num[mid]&&target<=num[end]) {
					start=mid+1;
				}else{
					end=mid-1;
				}
            }
		}
        if (num[start]==target) {
            return start;
        }
        if (num[end]==target) {
            return end;
        }
		return -1;
    }
在旋转数组中找最小数

输入:nums = [4,5,6,7,0,1,2]
输出:0

思路:上一题一样,顺序用二分法去搜索。首先要确定一个搜索的标准,可以用end最为一个标准数,如果小于middle的数,则说明,最小的在后半段,应该大于,那么说明最小的在前半段

public int findMin(int[] nums) {
        /**
		 * 特殊情况
		 */
		if (nums==null || nums.length==0) {
			return -1;
		}
		int start=0;
		int end=nums.length-1;
		int target=nums[end];
		int mid=0;
		while(start+1<end){
			mid=start+(end-start)/2;
			/**
			 * 后半部分占大部分,小的在前
			 */
			if (nums[mid]<=target) {
				end=mid;
			}else{
				start=mid;
			}
		}
		return Math.min(nums[start],nums[end]);
    }
砍树

题目:将不同长度的木头进行分段,段的大小一样,给定段数,求最大段的值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x3We3U5Z-1646056875844)(C:\Users\76532\Desktop\面试复习\算法\砍树题目.png)]

思路:将最长的木头去二分,得到每个中间值,去求可以分多少段,如果段数大于K,则继续往大的二分,如果段数小于K,则往小的二分,最后得到结果。

public class WoodCut{

	public static void main(String[] args){
		int[] l={232,124,456};
		int k=7;
		System.out.println(woodCut(l,k));
	}
	/**
	 * [woodCut description]
	 * @param  l [description] 各段木头的长度集合
	 * @param  k [description] 需要分成的段数
	 * @return   [description] 返回最长分成的长度
	 */
	public static int woodCut(int[] l,int k){
		/**
		 * 特殊情况
		 */
		if (l==null || l.length==0 || k<0) {
			return 0;
		}
		if (k==0) {
			return 0;
		}
		int left=0;
		int right=0;
		/**
		 * 取出最长的木头进行二分
		 */
		for (int m: l) {
			right= Math.max(right,m);
		}
		while(left+1<right){
			int mid=left+(right-left)/2;
			if (count(l,mid)>=k) {
				left=mid;
			}else{
				right=mid;
			}
		}
		if (count(l,right)>=k) {
			return right;
		}
		if (count(l,left)>=k) {
			return left;
		}
		return 0;

	}
    //求能分多少段
	public static int count(int[] l,int length){
		int count=0;
		for (int k: l) {
			count=count+(k/length);
		}
		return count;
	}
}

5、排序

冒泡排序思路:

双循环

大循环:每个数依次进行,也就是每次能找个最大的,n个数找n个就能排好序。

小循环:遍历,替换大的放后面

public void bubbleSort(int[] array){
    for(int i=0;i<array.length;i++){
        for(int j=1;j<array.length-i;j++){
            if(array[j-1]>array[j]){
                int temp=array[j-1];
                array[j-1]=array[j];
                array[j]=temp;
            }
        }
    }
}
选择排序思路:

双循环

大循环:一个数一个数去找到比它小的数替换

小循环:找出最小的,最后交换

选择是在最后才交换,比起冒泡交换次数少,提高效率

public void selectSort(int[] array){
    for(int i=0;i<array.length;i++){
        int pos=i;
        for(int j=i+1;j<array.length;j++){
            if(array[pos]>array[j]){
                pos=j;
            }
        }
        if(pos!=i){  
            int temp=array[i];
            array[i]=array[pos];
            array[pos]=temp;
        }
    }
}
插入排序思路:

双循环

大循环:逐个进行插入前面的数组

小循环:将插入部分的数组往后挪动

public void insertSort(int[] array){
    //比较的位置
    int j;
    for(int i=1;i<array.length;i++){
        j=i-1;
        while(j>=0 && array[i]<array[j]){
            array[j+1]=array[j];
            j--;
        }
        array[j+1]=array[i];
    }
}
重点:快速排序思路

是一个递归嵌套的结构

将由确定pivot的值,小的分左边,大的分右边,分成两个部分,然后不断嵌套去分,最后分成一个有序的队列。

public void quickSort(int[] array){
    int left=0;
    int right=array.length-1;
    quickSortImpl(left,right,array);
}
public void quickSortImpl(int start,int end,int[] array){
    if(left >right){
        return;
    }
    //不能直接使用start和end,因为他们是传进来的前后位,如果改变,后面就不知道怎么分了。
    int left=start;
    int right=end;
    //最好取中间值
    int pivot=array[left+(right-left)/2];
    while(left<=right){
        //left定位到大于pivot的地方
        while(left<=right && array[left]<pivot){
            left++;
        }
        //right定位到小于pivot的地方
        while(left<=right && array[right]>pivot){
            right--;
        }
        //将两个位置的数交换
        if(left<=right){
            int temp=array[left];
            array[left]=array[right];
            array[right]=temp;
            //交换后需要继续往下走
            left++;
			right--;
        }
    }
    quickSortImpl(start,right,array);
    quickSortImpl(left,end,array);
}

利用快速排序找第K个大的数字,可以通过分完后,left和right的位置去判断需要进入哪个函数,然后找到第K个大的数,不用全部排完

if (k<=left) {
    return QuickSortFind(start,right,nums,k);
}else{
    return QuickSortFind(left,end,nums,k);
}
重点:归并排序思路:

是一个递归嵌套的结构

先将数组进行分割成一个一个,然后再进行逐步合并

public static void mergeSort(int[] nums) {
		int[] temp = new int[nums.length];
		mergeSortImpl(0, nums.length - 1, nums, temp);
	}

	public static void mergeSortImpl(int start, int end, int[] array, int[] temp) {
		if (start >= end) {
			return;
		}
		int mid = start + (end - start) / 2;
		mergeSortImpl(start, mid, array, temp);
		mergeSortImpl(mid + 1, end, array, temp);
		merge(start, mid, end, array, temp);
	}

	public static void merge(int start, int mid, int end, int[] array, int[] temp) {
		int left = start;
		int right = mid + 1;
		int index = start;
        //如果两边都有数,将进行比较,放进结果数组
		while (left <= mid && right <= end) {
			if (array[left] < array[right]) {
				temp[index++] = array[left++];
			}
			if (array[left] > array[right]) {
				temp[index++] = array[right++];
			}
		}
        //如果只剩一边有数则直接放入
		while (left <= mid) {
			temp[index++] = array[left++];
		}
		while (right <= end) {
			temp[index++] = array[right++];
		}
        //将结果数组的值搬到原始数组
		for (int i = start; i <= end; i++) {
			array[i] = temp[i];
		}
	}

6、双指针

两个指针根据结果判断是要前移还是后移

需要的是一个有序的数组,可以减少循环次数

三数之和

思路:暴力法是三个循环

而使用双指针可减少一循环

大循环:取每个数与另外两个指针数的循环

小循环:通过判断相等、大于、小于移动指针

/**
 * 双指针操作
 * 一个在前,一个在后
 * 根据大小,选择调前,调后
 */
public List<List<Integer>> threeSum(int[] nums) {
		/**
		 * 特殊情况
		 */
		List<List<Integer>> result=new ArrayList<List<Integer>>();
		if (nums==null || nums.length<=2) {
			return result;
		}
		//排序
		Arrays.sort(nums);
    	//第一个循环:取每个数与另外两个指针数的循环
		for (int i=0;i<nums.length-2;i++) {
            //如果数相同可以跳过
			if (i>0 && nums[i]==nums[i-1]) {
				continue;
			}
            //两个指针
			int left=i+1;
			int right=nums.length-1;
            //循环:
			while(left<right){
                //判断是否相等结果,相等加入结果集合中
				if (nums[i]+nums[left]+nums[right]==0) {
					List<Integer> List=new ArrayList<Integer>();
					List.add(nums[i]);
					List.add(nums[left]);
					List.add(nums[right]);
					result.add(List);
					left++;
					right--;
                    //排除重复项
					while (left<=right && nums[left]==nums[left-1]) {
						left++;
					}
                    //排除重复项
					while (left<=right && nums[right]==nums[right+1]) {
						right--;
					}
                //如果小了,就left++,大就right--
				}else if (nums[i]+nums[left]+nums[right]<0) {
					left++;
				}else{
					right--;
				}
			}

		}
		return result;

    }
三角形计数

思路:暴力法是三个循环

而使用双指针可减少一循环

大循环:取每个数与另外两个指针数的循环

小循环:通过判断相等、大于、小于移动指针

注意:当三边大于时,left后面的数就都大于了,count只需要直接加right-left,然后把right调小

public class TriangleNumber{
	public int triangleNumber(int[] nums) {
		/**
		 * 特殊情况
		 */
		if (nums==null || nums.length<3) {
			return 0;
		}
        Arrays.sort(nums);
		int end=nums.length-1;
		int count=0;
		for (int i=end; i>=2; i--) {
			int left=0;
			int right=i-1;
			while(left<right){
				if (nums[left]+nums[right]<=nums[i]) {
					left++;
				}
				if (nums[left]+nums[right]>nums[i]) {
                    //后面所有的数都可构成
					count+=(right-left);
					right--;
				}
			}
		}
		return count;

    }
}
积雨问题

关键点:两边的哪个高,因为我们只需要知道操作低的那边

高的不动即可,通过移动低的去取得积水的值

public class Trap{
	public int trap(int[] height) {
		/**
		 * 特殊情况
		 */
		if (height==null || height.length==0) {
			return 0;
		}
        //x轴值
		int left=0;
		int right=height.length-1;
        //y轴值
		int leftHeight=height[left];
		int rightHeight=height[right];
        //积水量
		int sum=0;
		while(left<right){
            //因为右边比左边高,所以不用关心右边,先挪左边
			if (leftHeight<rightHeight) {
                //不可装水,往左诺,最高值变化
				if (leftHeight<height[left+1]) {
					leftHeight=height[left+1];
					left++;
                //可装水,往左诺,最高值不变
				}else{
					sum+=leftHeight-height[left+1];
					left++;
				}
			}else{
				if (rightHeight<height[right-1]) {
					rightHeight=height[right-1];
					right--;
				}else{
					sum+=rightHeight-height[right-1];
					right--;
				}
			}
		}
		return sum;

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值