3. LintCode 二分法经典题目

3. LintCode 二分法题目

2. LintCode双指针题目(二)
整理自九章。

  • 第一境界
    • 写出不会死循环的二分法
    • 递归与非递归的权衡
  • 第二境界
    1. 在排序的数据集上进行二分
    2. 找到满足某个条件的第一个位置或者最后一个位置
  • 第三境界
    • 在未排序的数据集上进行二分
    • 保留有解的一半,或者去掉无解的一半
  • 第四境界
    • 在答案集上进行二分
    • 二分答案并验证答案偏大还是偏小

LintCode 457:经典二分查找问题

https://www.lintcode.com/problem/classical-binary-search

描述

在一个排序数组中找一个数,返回该数出现的任意位置,如果不存在,返回-1。

样例
输入:nums = [1,2,2,4,5,5], target = 2
输出:1 或者 2
    
输入:nums = [1,2,2,4,5,5], target = 6
输出:-1
解题思路

经典二分查找题目~~。

AC代码
public int findPosition(int[] nums, int target) {
	// 空数组直接返回-1;
    if (nums.length < 1) {
        return -1;
    }
    
    int start = 0, end = nums.length - 1;
    while (start + 1 < end) {
        int mid = start + (end - start) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            start = mid;
        } else {
            end = mid;
        }
    }

    if (nums[start] == target) {
        return start;
    }
    if (nums[end] == target) {
        return end;
    }

    return -1;

}

LintCode 460:在排序数组中找最接近的K个数

https://www.lintcode.com/problem/find-k-closest-elements

描述

给一个目标数target,一个非负整数K,一个按照升序排列的数组A。在A中找到与target最接近的K个整数。
返回这K个数并按照与target的接近程度从小到大排序,如果接近程度相当,那么小的数排在前面。

  1. k是一个非负整数,并且总是小于已排序数组的长度。
  2. 给定数组的长度是正整数, 不会超过 10^4104
  3. 数组中元素的绝对值不会超过 10^4104
样例
输入: A = [1, 2, 3], target = 2, k = 3
输出: [2, 1, 3]

输入: A = [1, 4, 6, 8], target = 3, k = 3
输出: [4, 1, 6]
解题思路

找到最接近target的一个数,然后向两边扩展,找到k个即可。二分法 + 双指针

AC代码
public int[] kClosestNumbers(int[] A, int target, int k) {
    // right为最接近target的坐标,left为right-1,从两边扩展找到K个最接近的数
    int right = findUpperClosest(A, target);
    int left = right - 1;
	// ans保存答案
    int[] ans = new int[k];
    for (int i = 0; i < k; i++) {
        // 左指针更靠近target则添加左指针上的数
        if (isLeftClosest(A, target, left, right)) {
            ans[i] = A[left--];
        } else {
            // 右指针更靠近target则添加右指针上的数
            ans[i] = A[right++];
        }
    }
    return ans;
}

// 返回左右指针谁更靠近target
private boolean isLeftClosest(int[] a, int target, int left, int right) {
    // 判断临界条件
    if (left < 0) {
        return false;
    }
    if (right > a.length - 1) {
        return true;
    }
    // 判断左右指针谁更靠近target
    return Math.abs(a[left] - target) <= Math.abs(a[right] - target) ? true : false;
}

// 找到最接近target的坐标
private int findUpperClosest(int[] a, int target) {
    // 就是经典二分查找
    int left = 0, right = a.length - 1;
    while (left + 1 < right) {
        int mid = (left + right) / 2;
        if (a[mid] == target) {
            return mid;
        } else if (a[mid] < target) {
            left = mid;
        } else {
            right = mid;
        }
    }
    // 返回最接近的坐标
    return Math.abs(a[left] - target) <= Math.abs(a[right] - target) ? left : right;    
}

LintCode 159:寻找旋转排序数组中的最小值

https://www.lintcode.com/problem/find-minimum-in-rotated-sorted-array

描述

假设一个排好序的数组在其某一未知点发生了旋转(比如0 1 2 4 5 6 7 可能变成4 5 6 7 0 1 2)。你需要找到其中最小的元素
你可以假设数组中不存在重复元素

样例
输入:[4, 5, 6, 7, 0, 1, 2]
输出:0
解释:
数组中的最小值为0

输入:[2,1]
输出:1
解释:
数组中的最小值为1
解题思路

根据题目可以找到旋转后数组的特点。

  • 如果这个数小于它前面的数,那它就是最小的那个,可以直接返回答案。
  • 如果它大于数组第一个元素,那么最小的元素肯定在它的右边。
  • 小于数组第一个元素,则最小的元素肯定在它的左边。

由此可不断二分找到答案。

AC代码
public int findMin(int[] nums) {
    // 没有旋转直接返回最小元素,也就是nums[0]
    if (nums[0] < nums[nums.length - 1]) {
        return nums[0];
    }
	
    int left = 0, right = nums.length - 1;
    while (left + 1 < right) {
        int mid = left + (right - left) / 2;
		// 小于了前面的数表示就是最小数
        if (nums[mid] < nums[mid - 1]) {
            return nums[mid];
        }
		// 大于首元素,left往右移动
        if (nums[mid] > nums[0]) {
            left = mid;
        } else if (nums[mid] < nums[nums.length - 1]) {
            // 小于首元素,right往左移动
            right = mid;
        } 
    }

    return Math.min(nums[left], nums[right]);        
}

LintCode 75:寻找峰值

https://www.lintcode.com/problem/find-peak-element

描述

你给出一个整数数组(size为n),其具有以下特点:

  • 相邻位置的数字是不同的
  • A[0] < A[1] 并且 A[n - 2] > A[n - 1]

假定_P_是峰值的位置则满足A[P] > A[P-1]A[P] > A[P+1],返回数组中任意一个峰值的位置。

  • 数组保证至少存在一个峰
  • 如果数组存在多个峰,返回其中任意一个就行
  • 数组至少包含 3 个数
样例
输入:  [1, 2, 1, 3, 4, 5, 7, 6]
输出:  1 or 6
解释:
返回峰顶元素的下标

输入: [1,2,3,4,1]
输出:  3
解题思路

观察题目数据可以得知:

  • 位置P满足,A[P] > A[P - 1] && A[P] > A[P + 1],那P就是峰值
  • 若 A[P - 1] > A[P] > A[P + 1],又因为A[0] < A[1],所以在0 ~ P中肯定有峰值
  • 若 A[P - 1] < A[P] < A[P + 1],又因为A[n - 2] > A[n - 1],所以在P至末尾肯定有峰值

由此可以二分查找得到答案

AC代码
public int findPeak(int[] A) {
                
        int left = 0, right = A.length - 1;
        while (left + 1 < right) {
            int mid = left + (right - left) / 2;
            if (A[mid] > A[mid - 1]) {
                // 满足A[P] > A[P - 1] && A[P] > A[P + 1],即为峰值
                if (A[mid] > A[mid + 1]) {
                    return mid;
                }
                // A[P - 1] < A[P] < A[P + 1],左指针右移
                left = mid;
            } else {
                // 右指针左移
                right = mid;
            }
        }
		// 返回峰值
        return Math.max(A[left], A[right]);
}

LintCode 183:木材加工

https://www.lintcode.com/problem/wood-cut

描述

有一些原木,现在想把这些木头切割成一些长度相同的小段木头,需要得到的小段的数目至少为 k。当然,我们希望得到的小段越长越好,你需要计算能够得到的小段木头的最大长度。
木头长度的单位是厘米。原木的长度都是正整数,我们要求切割得到的小段木头的长度也要求是整数。无法切出要求至少 k 段的,则返回 0 即可。
挑战:O(n log Len), Len为 n 段原木中最大的长度

样例
输入:
L = [232, 124, 456]
k = 7
输出: 114
Explanation: 我们可以把它分成114cm的7段,而115cm不可以

输入:
L = [1, 2, 3]
k = 7
输出: 0
说明:很显然我们不能按照题目要求完成。
解题思路

题目中有提到用O(n log Len)的算法,能够实现logN的算法之一便是二分法,所以可以考虑二分。
二分枚举木头长度,从0到长度最长的木头长度,从答案验证是否能够切成K段。能够切成K段左指针右移,不能右指针左移。

AC代码
public int woodCut(int[] L, int k) {
    // 临界直接返回
    if (L.length < 1){
        return 0;
    }
    // 找到长度最长的木头
    int maxLen = L[0];
    for (int i = 1; i < L.length; i++) {
        maxLen = Math.max(maxLen, L[i]);
    }
	// 二分枚举木头长度
    int ans = 0;
    int left = maxLen / k, right = maxLen;
    while (left + 1< right) {
        int mid = left + (right - left) / 2;
        if (check(L, mid, k)) {
            left = mid;
        } else {
            right = mid;
        }
    }
    if (check(L, right, k)) {
        return right;
    }
    return left;

}

// 判断是否能够切成K段len
public boolean check(int[] L, int len, int k) {
    int count = 0;
    for (int length : L) {
        count += length / len
    }    
    return count >= k ? true : false;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值