开始学算法1===>二分查找+双指针暴力算法(LeetCode刷题!!!)

二分查找+双指针暴力算法(LeetCode刷题!!!)

  • 跟着carl哥的第一天
    • 二分算法
      • 先看第一道题
      • 再看第二道题
      • 最后一道二分
    • 最后小菜:普通双指针算法的使用
  • 自我总结

跟着carl哥的第一天

今天仔细学了二分查找,跟双指针算法。通过这篇文章可以让你更快认识到二分查找的魅力,以及双指针算法的思想。

二分算法

先上模板

int left = 0; //定义左边界
int right = nums.length - 1; //定义右边界
int mid = 0;
while(left <= right){ //设置循环,直到left > right
	mid = left + ((right - left) / 2); //防止溢出,等同于(left + right) / 2
	if(nums[mid] == target) {
		return mid; //数组中找到目标值,直接返回下标
	} else if (target < nums[mid]) { //目标值在左边界到中间的位置
		right = mid - 1; //所以现在数组范围为[left, mid - 1]
	} else { //target在中间到右边界的位置
		left = mid + 1;
	}
}
return left; //重点!!! 先记住一个点 因为求mid时是向下取整,所以到最后都是left = mid。

先看第一道题

leetcode704.二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例1:

输入: nums = [-1,0,3,5,9,12], target = 9     
输出: 4       
解释: 9 出现在 nums 中并且下标为 4 

示例2:

输入: nums = [-1,0,3,5,9,12], target = 2     
输出: -1        
解释: 2 不存在 nums 中因此返回 -1 

提示:

  • 你可以假设 nums 中的所有元素是不重复的。

  • n 将在 [1, 10000]之间。

  • nums 的每个元素都将在 [-9999, 9999]之间。

    (大家可以想一下,二分查找首先是个什么东西,有一个目标值要查找,然后定位到是在左区域还是右区域,直至到区域缩小至1~2个元素)


答案:::

public class test01 {

    //定义输入输出
    public static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    public static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));

	//二分查找
    private static int search(int[] nums, Integer target) {

        //设置两个变量 分别表示左右两个边界
        int left = 0;
        int right = nums.length-1;

        //循环
        while(left <= right) {
            //设置中间值,用来定位target属于左边的一半还是右边的一半
            int mid = (left + right) / 2;
            //如果属于左边的一半 则右边界right移动到刚刚的中间值mid
            if (target <= nums[mid]) {
                right = mid;
            //如果属于右边的一半,则把左边界前移到中间值+1(因为上边是<=,所以nums[mid]属于左边界)
            //则左边界变成mid+1
            } else {
                left = mid + 1;
            }
        }
        //如果到最后没有找到target,则返回-1
        if (nums[left] != target)
            return -1;
        return left;
    }

    public static void main(String[] args) throws IOException {

        //读入一行,并按照空格拆分
        String s = in.readLine();
        String[] str = s.split(" ");
        //获取数组的长度
        int n = str.length;

        //读入一个数字,默认读入String 需要转为int
        Integer target = Integer.parseInt(in.readLine());

        //转为int数组
        int[] nums = new int[n];
        for (int i = 0; i < n; i ++) {
            nums[i] = Integer.parseInt(str[i]);
        }

		//输出答案并冲刷管道,关闭管道
        int count = search(nums, target);
        out.write(count);
        out.flush();
        out.close();
    }
}

再看第二道题

leetcode35.搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。

示例 1:

输入: [1,3,5,6], 5
输出: 2

示例 2:

输入: [1,3,5,6], 2
输出: 1

示例 3:

输入: [1,3,5,6], 7
输出: 4

示例 4:

输入: [1,3,5,6], 0
输出: 0

思路:其实这道题不是很难,但是要你进一步的理解二分查找 (偷偷说一句,直接用模板就可以ac了,都不需要改代码。。。。) 大家可以试试


答案:

public class test03 {

    public static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    public static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    public static void main(String[] args) throws IOException {

        String[] arr = in.readLine().split(",");
        Integer target = Integer.parseInt(in.readLine());
        int n = arr.length;
        Integer[] nums = new Integer[n];
        for (int i = 0; i < n; i ++) {
            nums[i] = Integer.parseInt(arr[i]);
        }

        int count = searchInsert(nums, target);
        out.write(count);
        out.flush();
        out.close();
    }

    private static int searchInsert(Integer[] nums, Integer target) {

        int left = 0;
        int right = nums.length - 1;
        int mid = 0;
        while(left <= right) {
            mid = (left + right) / 2;
            if (target == nums[mid])
                return mid;
            if (target < nums[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }

        return left;
    }
}

这里需要大家理解的就是为什么直接用模板就能ac?为什么目标值找不到插的位置也跟找到目标值的做法一样, 不需要额外添加判定条件? 为什么返回值是left?

先聊一下没有找到target时,while循环到最后是什么样子的。
很容易想象,经过每次while循环,都会使得[left,right]的左闭右闭区间中的元素减少。
那就有一个问题,减少到最后会是什么情况?
那就是进入最后一次while循环前,[left,right]的左闭右闭区间中只有一个或者两个元素,即
left和right的位置有且仅有2种情况

  • left = right
  • left = right - 1

给大家一个数组[1, 3]。然后target分别等于0跟2。大家拿纸去推导一下就懂了,一定要自己去手动推导!!!

  • 当target=0; 此时left = right -1, 然后还需最后一次while
  • 当target=2,一次while循环后,left = right =1,并且变成了一个元素的情况,还要一次while,一共是2个while

以上就解释清楚了,在进入最后一次while循环前,数组会变为一个或两个元素。
然后当while到尽头的时候 往往只剩下一个left(由于向下取整所以mid=left),那为什么找不到target的值时,总是能插入到正确的位置呢?

主要就是nums[mid]与target的位置关系了
target在nums[mid]的哪一边 如果是左边的话就占着mid当前的下标(0 因为到最后都可以看成数组里只有一个元素mid,且mid永远等于left),如果是在右边的话就用left+1来表示target的位置
看图展示!!!!
开始学算法1===>二分查找+双指针暴力算法(LeetCode刷题!!!)_第1张图片

最后一道二分

leetcode34.在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 (时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn) !!!)
如果数组中不存在目标值 target,返回 [-1, -1]。

示例1:

  • 输入:nums = [5,7,7,8,8,10], target = 8
  • 输出:[3,4]

示例2:

  • 输入:nums = [5,7,7,8,8,10], target = 6
  • 输出:[-1,-1]

示例3:

  • 输入:nums = [], target = 0
  • 输出:[-1,-1]

思路:
我们要想一下有什么情况:(开始位置设为左边界leftBorder, 结束位置设为右边界rightBorder)

  1. leftBorder rightBorder值有一个为-2 。 如: [1,3] target = 4
  2. leftBorder rightBorder值都不为-2
  3. (3)target在数组范围内,但是数组中没有target。 (这个与条件1不同的是因为在范围内所以leftBorder或者rightBorder会被赋值) 如[1,3] target = 2

答案:

public class test04 {

    public static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    public static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    public static void main(String[] args) throws IOException {

        String[] arr = in.readLine().split(",");
        Integer target = Integer.parseInt(in.readLine());
        int n = arr.length;
        Integer[] nums = new Integer[n];
        for (int i = 0; i < n; i ++) {
            nums[i] = Integer.parseInt(arr[i]);
        }

        int[] result = searchRange(nums, target);
        out.write(result.toString());
        out.flush();
        out.close();
    }

    private static int[] searchRange(Integer[] nums, Integer target) {

        //给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
        //判定条件:(1)leftBorder rightBorder值有一个为-2     [1,3] target = 4
                //(2)leftBorder rightBorder值都不为-2
                //(3)target在数组范围内,但是数组中没有target
                //          (这个与条件1不同的是因为在范围内所以leftBorder或者rightBorder会被赋值) 如[1,3] target = 2
        int leftBorder = findLeftBorder(nums, target);
        int rightBorder = findRightBorder(nums, target);

        //条件(1)
        if (leftBorder == -2 || rightBorder == -2)
            return new int[]{-1, -1};

        //条件(2) [1, 2]    target = 2 =======> rightBorder = 2, leftBorder = 0
        //关于left/rightBorder的取值,大家可以联想一下上一道题leetcode35找不到target的情况
        //寻找左边界是找到target的左边一个数,也就是上一题中最后一个循环 target < nums[mid]的时候
        //同理右边界就是找到target的右边的一个数。
        if (rightBorder - leftBorder > 1)
            return new int[]{leftBorder + 1, rightBorder - 1};

		//条件三
        return new int[]{-1, -1};

    }

    //找右边界
    private static int findRightBorder (Integer[] nums, Integer target) {

        int left = 0;
        int right = nums.length - 1;
        int mid = 0;
        int rightBorder = -2;
        //找出target的右边界(不取target)
        while(left <= right) {

            mid = left + ((right - left) / 2);
            if (target >= nums[mid]) {

                left = mid + 1;
                rightBorder = left;
            } else {

                right = mid -1;
            }
        }

        return rightBorder;
    }

    private static int findLeftBorder (Integer[] nums, Integer target) {

        int left = 0;
        int right = nums.length - 1;
        int mid = 0;
        int leftBorder = -2;
        //找出target的左边界(不取target)
        while(left <= right) {

            mid = left + ((right - left) / 2);
            if (target <= nums[mid]) {

                right = mid - 1;
                leftBorder = right;
            } else {

                left = mid + 1;
            }
        }

        return leftBorder;
    }
}

最后小菜:普通双指针算法的使用

leetcode27.移动元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例1:

  • 输入:nums = [3,2,2,3], val = 3
  • 输出:2, nums = [2,2]
  • 说明::函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例2:

  • 输入:nums = [0,1,2,2,3,0,4,2], val = 2
  • 输出:5, nums = [0,1,4,0,3]
  • 解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

思路:
我们只需要有一个标记,当第i位的值不等于val时,直接从标记位开始把第i位的值赋值给标记位所在数组位置。


答案:

public class test02 {

    public static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    public static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    public static void main(String[] args) throws IOException {

        String str = in.readLine();
        Integer val = Integer.parseInt(in.readLine());
        String[] s = str.split(",");
        int n = s.length;
        Integer[] nums = new Integer[n];
        for (int i = 0; i < n; i ++) {

            nums[i] = Integer.parseInt(s[i]);
        }
        int len = removeElement(nums, val);
        out.write(len);
        out.flush();
        out.close();
    }

    private static int removeElement(Integer[] nums, Integer val) {

        //设置一个标记
        int flag = 0;
        for (int i = 0; i < nums.length; i ++) {

            //当第i位的值不等于val时,直接从标记位开始把第i位的值赋值给标记位所在数组位置
            if (nums[i] != val)
                nums[flag ++] = nums[i];
        }

        //输出标记位
        return flag;
    }
}

自我总结

学会了二分查找的基本应用和双指针算法的基本思路。
还是有点不熟悉,打算再做几道二分!!!
兄弟萌冲啊!

大概就是这样,大家懂了吗 有什么不懂的评论区评论或者私信我吧!!

你可能感兴趣的:(跟着carl哥学算法,算法,leetcode,java,数据结构)