二分搜索的例子

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Miaoshuowen/article/details/100832509

二分搜索常见的应用场景
1.在有序序列中查找一个数
2.并不一定非要在有序序列中才能得到应用

二分搜索常见的考察点
1.对于边界条件的考察以及代码实现的能力
2.在有序循环数组中进行二分搜索

二分搜索常见题目的变化
1.给定处理或查找的对象不同
2.判断条件不同
3.要求返回的内容不同

二分搜索的重要提醒
mid= (left+right)/2 -->可能溢出
更安全的写法:mid=left+(right-left)/2

例1:

给定一个无序数组arr,已知任意两个相邻的两个元素,值都不重复。请返回任意一个局部最小的位置。
所谓局部最小的位置是指,如果arr[0]<arr[1],那么位置0就是一个局部最小的位置。如果arr[N-1](也就是arr最右的数)小于arr[N-2]那么位置N-1也是局部最小的位置。如果位置i既不是最右也不是最左位置。那么只要满足arr[i]同时小于它左右两侧的值即(arr[i-1]和arr[i+1]),那么位置i也是一个局部最小的位置。

解:
本题依然可以用二分搜索来实现时间复杂度为O(logN)
1.arr为空或长度为0,返回-1,表示局部最小位置不存在。
2.如果arr长度为1,返回0,因为此时0是局部最小位置。
3.如果arr长度大于1

代码:

	public int getLessIndex(int[] arr) {
		if (arr == null || arr.length == 0) {
			return -1;
		}
		if (arr.length == 1 || arr[0] < arr[1]) {
			return 0;
		}
		if (arr[arr.length - 1] < arr[arr.length - 2]) {
			return arr.length - 1;
		}
		int left = 1;
		int right = arr.length - 2;
		int mid = 0;
		while (left < right) {
			mid = (left + right) / 2;
			if (arr[mid] > arr[mid - 1]) {
				right = mid - 1;
			} else if (arr[mid] > arr[mid + 1]) {
				left = mid + 1;
			} else {
				return mid;
			}
		}
		return left;
	}

例2:

给定一个有序数组arr,在给定一个整数num,请在arr中找到num这个数出现的最左边的位置。

假设:
1 2 3 3 3 3 4 4 4 4 4 4 4 4 4
res=-1(最后一个num的位置) num=3

解:
首先找到中间的数,右边都是大于3的数、更新res,直接在左部分继续进行搜索,找到3之后,继续在左部分搜索

代码:

	public int findPos(int[] arr, int n, int num) {
		if (arr == null || n == 0) {
			return -1;
		}
		int left = 0;
		int right = arr.length - 1;
		int mid = 0;
		int res = -1;
		while (left <= right) {
			mid = (left + right) / 2;
			if (arr[mid] < num) {
				left = mid + 1;
			} else if (arr[mid] > num) {
				right = mid - 1;
			} else {
				res = mid;
				right = mid - 1;
			}
		}
		return res;
	}

例3:

给定一个有序循环数组arr,返回arr中的最小值。有序循环数组是指,有序数组左边任意长度的部分放到右边去,右边的部分拿到左边来。比如数组[1,2,3,3,4],其有序循环数组是[4,1,2,3,3]。

解:
下标:L=0,R=N-1,M=L+R/2
如果arr[L]<arr[R] ?这说明整体是有序的,直接返回arr[L]即可
如果arr[L]>=arr[R],说明包含循环,此时看arr[L]>arr[M],说明最小值只有可能出现在L到M范围上,因为只有arr[M]只有是循环过的数时才有可能发生arr[L]>arr[M],当arr[M]>arr[R]时M到R是循环部分,所以从M到R开始二分查找,当满足arr[L]>=arr[R] ,arr[L]<=arr[M] ,arr[M]<=arr[R],此时只能用遍历的方式在arr[R]到arr[L]查找最小值。

代码:

	public int getMin(int[] arr, int n) {
		if (arr == null || arr.length == 0) {
			return -1;
		}
		int low = 0;
		int high = arr.length - 1;
		int mid = 0;
		while (low < high) {
			if (low == high - 1) {
				break;
			}
			if (arr[low] < arr[high]) {
				return arr[low];
			}
			mid = (low + high) / 2;
			if (arr[low] > arr[mid]) {
				high = mid;
				continue;
			}
			if (arr[mid] > arr[high]) {
				low = mid;
				continue;
			}
			while (low < mid) {
				if (arr[low] == arr[mid]) {
					low++;
				} else if (arr[low] < arr[mid]) {
					return arr[low];
				} else {
					high = mid;
					break;
				}
			}
		}
		return Math.min(arr[low], arr[high]);
	}

例4:

给定一个有序数组arr,其中不含有重复元素,请找到满足arr[i]==i条件的最左的位置。如果所有位置上的数都不满足条件,返回-1。
解:
arr[0]>N-1,返回-1;
arr[N-1]<0,返回-1;
arr[M]>M,位置增量是1,所以只需在0到M上搜索;
arr[M]<M,只需在M+1到N的范围继续二分;
arr[M] ==M,res=M记录,然后继续在0到M-1二分;

代码:

	public int findPos(int arr[], int n) {
		if (arr == null || n == 0) {
			return -1;
		}
		int left = 0;
		int right = n - 1;
		int res = -1;
		while (left <= right) {
			if (arr[left] > left || arr[right] < right) {
				break;
			}
			int mid = (left + right) / 2;
			if (arr[mid] < mid) {
				left = mid + 1;
			} else if (arr[mid] > mid) {
				right = mid - 1;
			} else {
				res = mid;
				right = mid - 1;
			}
		}
		return res;
	}

例5:

给定一个完全二叉树的头节点head,返回这棵树的节点个数,如果完全二叉树的节点为N,请实现时间复杂度低于O(N)的解法。

解:
首先找到二叉树最左的节点,目的是统计完全二叉树的高度,最左的几点肯定是在完全二叉树最后一层,然后找到二叉树头节点的右子树的最左节点,如果这个节点能够到达最底层,说明头节点的左子树是一个满的二叉树,可以通过公式算出左子树的节点数,剩下的节点数可以递归的算出右子树的节点个数;如果头节点的右子树的最左节点不能到达最后一层,说明右子树是满的二叉树(高度-1),通过公式直接求得右子树的节点数。然后递归额算出左子树的节点数

遍历的方式,时间复杂度为O(N)
最优解的过程,时间复杂度为O(logN^2)

代码:

	public int count(TreeNode head) {
		if (head == null) {
			return 0;
		}
		return bs(head, 1, mostleftLevel(head, 1));
	}

	private int bs(TreeNode node, int l, int h) {
		if (l == h) {
			return 1;
		}
		if (mostleftLevel(node.right, l + 1) == h) {
			return (1 << (h - 1)) + bs(node.right, l + 1, h);
		} else {
			return (1 << (h - 1)) + bs(node.left, l + 1, h);
		}
	}

	private int mostleftLevel(TreeNode node, int level) {
		while (node != null) {
			level++;
			node = node.left;
		}
		return level - 1;
	}

例6:

如果更快的求一个整数K的N次方。如果两个整数相乘并得到结果的时间复杂度为O(1),得到整数K的N次方的过程请实现时间复杂度O(logN)的方法。
解:
10^75=10 ^1001011(75的二进制表达)
=10 ^64* 10 ^8* 10 ^2 *10 ^1(二进制相应的位置上是1)
=10 ^1000000 * 10 ^1000 *10 ^10 *10 ^1(二进制表达)

思想原型来自二分搜索

代码:

	public int getPower(int a, int n) {
		BigInteger res = BigInteger.valueOf(1);
		BigInteger tmp = BigInteger.valueOf(a);

		for (; n != 0; n >>= 1) {
			if ((n & 1) != 0) {
				res = res.multiply(tmp);
			}
			tmp = tmp.multiply(tmp);
			res = res.mod(BigInteger.valueOf(1000000007));
			tmp = tmp.mod(BigInteger.valueOf(1000000007));
		}
		return res.mod(BigInteger.valueOf(1000000007)).intValue();
	}

猜你喜欢

转载自blog.csdn.net/Miaoshuowen/article/details/100832509
今日推荐