专题【二分查找】刷题日记【练习题】

题目列表

练习

410. 分割数组的最大值
875. 爱吃香蕉的珂珂
LCP 12. 小张刷题计划
1482. 制作 m 束花所需的最少天数
1011. 在 D 天内送达包裹的能力
240. 搜索二维矩阵 II
74. 搜索二维矩阵
162. 寻找峰值
349. 两个数组的交集
611. 有效三角形的个数
1027. 最长等差数列
2602. 使数组元素全部相等的最少操作次数
378. 有序矩阵中第 K 小的元素
153. 寻找旋转排序数组中的最小值
1004. 最大连续1的个数 III


2024.04.30

410. 分割数组的最大值

题目

给定一个非负整数数组 nums 和一个整数 k ,你需要将这个数组分成 k 个非空的连续子数组。

设计一个算法使得这 k 个子数组各自和的最大值最小。

示例 1:

输入:nums = [7,2,5,10,8], k = 2
输出:18
解释:
一共有四种方法将 nums 分割为 2 个子数组。
其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。

示例 2:

输入:nums = [1,2,3,4,5], k = 2
输出:9

示例 3:

输入:nums = [1,4,4], k = 3
输出:4

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 106
  • 1 <= k <= min(50, nums.length)

思路

  1. 动态规划
    状态转移方程:dp[i][j] = max(dp[l][j-1], sumPre[i] - sumPre[l]); dp[i][j] 表示以下标i结尾,分割成j个子数组的和的最大值最小值。
    初始化:因为求最小,所以填充最大。如果k=1就是数组不变,那就是所有元素和
    遍历:按照从左到右填表
    在这里插入图片描述

  2. 二分查找
    思路本质就是去找到满足条件的最小值,能二分的基础是,如果x满足条件,那么比x(大OR小)的也满足条件
    核心点有三个

  3. 左边界是啥

  4. 右边界是啥

  5. 能不能快速验证(各种题的核心区别)

答案

// 动态规划
class Solution {
    public int splitArray(int[] nums, int k) {
        int length = nums.length;
        // k+1是因为k是个数,不是下标,有个对应关系,否则处理时候需要-1费脑子
        int[][] dp = new int[length][k + 1];
		// 初始化
        for (int i = 0; i < length; i++) {
            Arrays.fill(dp[i], Integer.MAX_VALUE);
        }

        dp[0][1] = nums[0];
        int[] sumPre = new int[length];
        sumPre[0] = nums[0];
        // 前缀和数组为了快速计算某一个range的和
        for (int i = 1; i < length; i++) {
            sumPre[i] = sumPre[i - 1] + nums[i];
        }
        for (int i = 1; i < length; i++) {
            dp[i][1] = sumPre[i];
        }

        // dp[i][j] = max(dp[l][j-1], sumPre[i] - sumPre[l]);
        // 有限固定k,即按照上图,从上往下
        for (int i = 2; i < k + 1; i++) {
        	// 从上往下,所以j=i-1
            for (int j = i - 1; j < length; j++) {
            	// 遍历所有的可能,i - 2是因为至少要分割成i段,除去最后的一个,和自身要有的一段,剩下的每段至少有一个元素
            	// 举个栗子,i = 5,表示要分割成5段,除去l < j最后一个元素,自己这一段,那么至少还要有三段,也就是三个元素,这个时候l = 5-2 = 3
            	// 减去的2就是自己这一段和最后一个元素那一段 
                for (int l = i - 2; l < j; l++) {
                    dp[j][i] = Math.min(dp[j][i], Math.max(dp[l][i - 1], sumPre[j] - sumPre[l]));
                }
            }
        }

        return dp[length - 1][k];
    }
}
// 二分查找
class Solution {
    public int splitArray(int[] nums, int k) {
        int left = 0, right = 0;
        for (int i = 0; i < nums.length; i++) {
        	// 因为是求最大值的最小,所以left最小也得是元素中的最大值,即按照元素个数分割
            left = Math.max(left, nums[i]);
            // 最大值就是分割成一个数组,那就是所有数字的和
            right += nums[i];
        }
		
		// 二分
        while (left <= right) {
            int middle = left + (right - left)/2;
            if (check(nums, middle, k)) {
            	// 满足要找所有满足的值的最小值,所以满足之后继续向左
                right = middle - 1;
            } else {
                left = middle + 1;
            }
        }
        // 最后两个指针重合的时候,如果重合在要找的值上,则left不动。
        // 重合在要找的值左边,left要右移一位
        // 不会重合在要找的值右边,考虑两个元素时候,只有两种情况
        // 两个都满足,且左边是最低要求。左边不满足,右边满足。
        // 两个都满足,且左边是最低要求。(为什么左边最低要求,因为如果满足,左边是不动的,右边会动,假设第一次重合在一个不是最小的满足值,则right变小,left不变。最坏的情况就是上来left就是满足的,由于left一直不变,所以一定能被保有) middle就是left(left + (left + 1))/2 == left,所以,不会重合在值右边
        // 左边不满足,右边满足。middle是left,所以right不变
        return left;
    }
	
	// 每道题不同的核心点
    public boolean check(int[] nums, int x, int m) {
        int cnt = 1;
        int sum = 0;
		
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
            // x是预期的目标值(数组求和的最大值),如果超了,那就加一。而且由于这个元素没算,所以要重新赋值给sum
            if (sum > x) {
                sum = nums[i];
                cnt++;
            }
        }
		// 看看满不满足m个的条件
        return cnt <= m;
    }
}

875. 爱吃香蕉的珂珂

题目

珂珂喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 h 小时后回来。

珂珂可以决定她吃香蕉的速度 k (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 k 根。如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。

珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。

返回她可以在 h 小时内吃掉所有香蕉的最小速度 k(k 为整数)。

示例 1:

输入:piles = [3,6,7,11], h = 8
输出:4

示例 2:

输入:piles = [30,11,23,4,20], h = 5
输出:30

示例 3:

输入:piles = [30,11,23,4,20], h = 6
输出:23

提示:

  • 1 <= piles.length <= 104
  • piles.length <= h <= 109
  • 1 <= piles[i] <= 109

思路

同上一题的二分

答案

class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        int right = 0;
        for (int i = 0; i < piles.length; i++) {
        	// 最快就是每一堆吃一次
            right = Math.max(right, piles[i]);
        }
        // 最慢就1个一个吃
        int left = 1;

        while (left <= right) {
        	// 放越界,其实没必要
            int middle = left + (right - left) / 2;
            if (check(piles, h, middle)) {
                right = middle - 1;
            } else {
                left = middle + 1;
            }
        }
        return left;
    }

    public boolean check(int[] nums, int h, int k) {
        long cnt = 0;
        for (int i = 0; i < nums.length; i++) {
            int num = nums[i];
            // 到底吃几个小时,如果num <= k那就是1小时,所以num+k -1,减一防止num=k时候变成2
            cnt += (num + k - 1) / k;
        }
        return cnt <= h;
    }
}

LCP 12. 小张刷题计划

题目

为了提高自己的代码能力,小张制定了 LeetCode 刷题计划,他选中了 LeetCode 题库中的 n 道题,编号从 0 到 n-1,并计划在 m 天内按照题目编号顺序刷完所有的题目(注意,小张不能用多天完成同一题)。

在小张刷题计划中,小张需要用 time[i] 的时间完成编号 i 的题目。此外,小张还可以使用场外求助功能,通过询问他的好朋友小杨题目的解法,可以省去该题的做题时间。为了防止“小张刷题计划”变成“小杨刷题计划”,小张每天最多使用一次求助。

我们定义 m 天中做题时间最多的一天耗时为 T(小杨完成的题目不计入做题总时间)。请你帮小张求出最小的 T是多少。

示例 1:

输入:time = [1,2,3,3], m = 2
输出:3
解释:第一天小张完成前三题,其中第三题找小杨帮忙;第二天完成第四题,并且找小杨帮忙。这样做题时间最多的一天花费了 3 的时间,并且这个值是最小的。

示例 2:

输入:time = [999,999,999], m = 4
输出:0
解释:在前三天中,小张每天求助小杨一次,这样他可以在三天内完成所有的题目并不花任何时间。

限制:

  • 1 <= time.length <= 10^5
  • 1 <= time[i] <= 10000
  • 1 <= m <= 1000

思路

同之前

答案

class Solution {
    public int minTime(int[] time, int m) {
        int left = 0, right = 0;
        for (int t : time) {
        	// 最坏就每天只能1,所以要求和
            right += t;
        }

        while (left <= right) {
            int middle = (left + right) / 2;
            if (check(time, m, middle)) {
                right = middle - 1;
            } else {
                left = middle + 1;
            }
        }
        return left;
    }

    public boolean check(int[] nums, int m, int k) {
        int cnt = 1;
        int max = 0;
      
        int sum = 0;
        for (int num : nums) {
            sum += num;

            max = Math.max(max, num);
			// 这个体现,每天的一次求助。因为过程中不可能直到最大值,所以最后算
            if (sum > max + k) {
                cnt++;
                // 没算上,所以重新赋值
                sum = num;
                // 没算上,所以重新赋值
                max = num;
           
            }
        }
        return cnt <= m;
    }
}

1482. 制作 m 束花所需的最少天数

题目

给你一个整数数组 bloomDay,以及两个整数 m 和 k 。

现需要制作 m 束花。制作花束时,需要使用花园中 相邻的 k 朵花 。

花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好 可以用于 一束 花中。

请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1 。

示例 1:

输入:bloomDay = [1,10,3,10,2], m = 3, k = 1
输出:3
解释:让我们一起观察这三天的花开过程,x 表示花开,而 _ 表示花还未开。
现在需要制作 3 束花,每束只需要 1 朵。
1 天后:[x, _, _, _, _] // 只能制作 1 束花
2 天后:[x, _, _, _, x] // 只能制作 2 束花
3 天后:[x, _, x, _, x] // 可以制作 3 束花,答案为 3

示例 2:

输入:bloomDay = [1,10,3,10,2], m = 3, k = 2
输出:-1
解释:要制作 3 束花,每束需要 2 朵花,也就是一共需要 6 朵花。而花园中只有 5 朵花,无法满足制作要求,返回 -1 。

示例 3:

输入:bloomDay = [7,7,7,7,12,7,7], m = 2, k = 3
输出:12
解释:要制作 2 束花,每束需要 3 朵。
花园在 7 天后和 12 天后的情况如下:
7 天后:[x, x, x, x, _, x, x]
可以用前 3 朵盛开的花制作第一束花。但不能使用后 3 朵盛开的花,因为它们不相邻。
12 天后:[x, x, x, x, x, x, x]
显然,我们可以用不同的方式制作两束花。

示例 4:

输入:bloomDay = [1000000000,1000000000], m = 1, k = 1
输出:1000000000
解释:需要等 1000000000 天才能采到花来制作花束

示例 5:

输入:bloomDay = [1,10,2,9,3,8,4,7,5,6], m = 4, k = 2
输出:9

提示:

  • bloomDay.length == n
  • 1 <= n <= 10^5
  • 1 <= bloomDay[i] <= 10^9
  • 1 <= m <= 10^6
  • 1 <= k <= n

思路

同之前,细节写在代码里

答案

class Solution {
    public int minDays(int[] bloomDay, int m, int k) {
        if (bloomDay.length / k < m) {
            return -1;
        }
        int left = Integer.MAX_VALUE, right = 0, max = 0;
        for (int i : bloomDay) {
        	// 最少是每个花期都一样,都是最个数
            left = Math.min(left, i);
            // 最大就是等到最长那个
            max = Math.max(i, max);
        }

        right = max;

        while (left <= right) {
            int middle = (left + right) / 2;
            if (check(bloomDay, m, k, middle)) {
                right = middle - 1;
            } else {
                left = middle + 1;
            }
        }
        // 这里最后left可能没找到
        return left > max ? -1 : left;
    }

    public boolean check(int[] bloomDay, int m, int k, int x) {
        int cnt = 0;
        int c = 0;
        for (int i : bloomDay) {
        	// x是天数,进入if说明花开了
            if (i <= x) {
                c++;
                // 连续数量满足k,就形成了一束花
                if (c == k) {
                    c = 0;
                    cnt++;
                    if (cnt >= m) {
                        return true;
                    }
                }
            } else {
            	// 没进入重置连续数量
                c = 0;
            }

        }
        return cnt >= m;
    }
}

1011. 在 D 天内送达包裹的能力

题目

传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。

传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。

返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。

示例 1:

输入:weights = [1,2,3,4,5,6,7,8,9,10], days = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10
请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。

示例 2:

输入:weights = [3,2,2,4,1,4], days = 3
输出:6
解释:
船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示:
第 1 天:3, 2
第 2 天:2, 4
第 3 天:1, 4

示例 3:

输入:weights = [1,2,3,1,1], days = 4
输出:3
解释:
第 1 天:1
第 2 天:2
第 3 天:3
第 4 天:1, 1

提示:

  • 1 <= days <= weights.length <= 5 * 104
  • 1 <= weights[i] <= 500

思路

同之前

答案

class Solution {
    public int shipWithinDays(int[] weights, int days) {
        int max = 0, sum = 0;
        for (int i : weights) {
            max = Math.max(max, i);
            sum += i;
        }
        // 要想完成传送,运输能力一定得能运最大包裹
        // 最快就是一次全运完,所以求和
        int left = max, right = sum;
        while (left <= right) {
            int middle = (left + right) / 2;
            if (check(weights, days, middle)) {
                right = middle - 1;
            } else {
                left = middle + 1;
            }
        }
        return left;
    }

    public boolean check(int[] weights, int days, int weight) {
        int cnt = 1;
        int sum = 0;
        for (int i : weights) {
            sum += i;
            // 和第一题一样
            if (sum > weight) {
                cnt++;
                sum = i;
            }
        }
        return cnt <= days;
    }
}

240. 搜索二维矩阵 II

题目

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

每行的元素从左到右升序排列。
每列的元素从上到下升序排列。

示例 1:
在这里插入图片描述

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true

示例 2:
在这里插入图片描述

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= n, m <= 300
  • -109 <= matrix[i][j] <= 109
  • 每行的所有元素从左到右升序排列
  • 每列的所有元素从上到下升序排列
  • -109 <= target <= 109

思路

一列一列搜,加一点截断优化

答案


class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length;
        int n = matrix[0].length;

        int mLeft = 0, mRight = n - 1;

        for (int i = 0; i < n; i++) {
        	// 截断优化
            if (matrix[0][i] > target || matrix[m-1][i] < target) {
                continue;
            }
            mLeft = 0;
            mRight = m - 1;
            while (mLeft <= mRight) {
                int middle = (mLeft + mRight) / 2;
                int value = matrix[middle][i];
                if (target == value) {
                    return true;
                } else if (target > value) {
                    mLeft = middle + 1;
                } else {
                    mRight = middle - 1;
                }
            }
        }

        return false;
    }
}

74. 搜索二维矩阵

题目

给你一个满足下述两条属性的 m x n 整数矩阵:

  • 每行中的整数从左到右按非严格递增顺序排列。
  • 每行的第一个整数大于前一行的最后一个整数。
    给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。

示例 1:
在这里插入图片描述

输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true

示例 2:
在这里插入图片描述

输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 100
  • -104 <= matrix[i][j], target <= 104

思路

同上,区别是,这个是连续的,所以先横着或者竖着搜,在交叉搜

答案

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length;
        int n = matrix[0].length;

        int left = 0, right = m - 1;
        // 先竖着找
        while (left <= right) {
            int middle = (left + right) / 2;
            int element = matrix[middle][0];
            if (element == target) {
                return true;
            } else if (element < target) {
                left = middle + 1;
            } else {
                right = middle - 1;
            }
        }
        
        // 找不到会找到第一个比目标值大的,所以这里做一个减一。找到就直接返回了
        int mIndex = left - 1;
        if (mIndex < 0 || mIndex > m - 1) {
            return false;
        }

        left = 0;
        right = n - 1;
        while (left <= right) {
            int middle = (left + right) / 2;
            int element = matrix[mIndex][middle];
            if (element == target) {
                return true;
            } else if (element < target) {
                left = middle + 1;
            } else {
                right = middle - 1;
            }
        }
        return false;
    }
}

162. 寻找峰值

题目

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

示例 1:

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。

示例 2:

输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5
解释:你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。

提示:

  • 1 <= nums.length <= 1000
  • -231 <= nums[i] <= 231 - 1
  • 对于所有有效的 i 都有 nums[i] != nums[i + 1]

思路

实际上是找上升沿的点,如果nums[middle] > nums[middle + 1] 则表示右侧是下降沿,所以right过来

答案

class Solution {
    public int findPeakElement(int[] nums) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int middle = left + (right - left) / 2;
            // 兜底,因为认为边界为负无穷,所以认为middle大于右边
            if (middle == nums.length - 1 || nums[middle] > nums[middle + 1]) {
                right = middle - 1;
            } else {
                left = middle + 1;
            }

        }
        return left;
    }
}

349. 两个数组的交集

题目

给定两个数组 nums1 和 nums2 ,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

思路

排序双指针就不说了,直接排序+二分

答案

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        // 为了在多的里面二分,可以快一点
        if (m < n) {
            return intersection(nums2, nums1);
        }

        sorted(nums1, 0, nums1.length - 1);
        sorted(nums2, 0, nums2.length - 1);

        int[] result = new int[n];
        int index = 0;
        int left = 0;
        for (int i = 0; i < n; i++) {
            int current = nums2[i];
            int right = nums1.length - 1;
            // 提前搜索结束
            if (left > right) {
                break;
            }
            boolean find = false;
            while (left <= right) {
                int middle = (left + right) / 2;
                int element = nums1[middle];
                // 这里是为了避免重复元素,找到最右边的那个
                if (current == element) {
                    left = middle + 1;
                    // 为了标记是否找到了,并且避免重复
                    find = true;
                } else if (current < element) {
                    right = middle - 1;
                } else {
                    left = middle + 1;
                }
            }
            // 这里是为了避免重复元素
            if (find && nums1[right] == current) {
                result[index++] = current;
                left = right + 1;
            }
        }
        return Arrays.copyOf(result, index);
    }
	
	// 锻炼下快排
    public void sorted(int[] nums, int left, int right) {
        if (left > right) {
            return;
        }
        int min = left, mid = min + 1, max = right;
        int target = nums[min];
        while (mid <= max) {
            int element = nums[mid];
            if (element < target) {
                swap(nums, min++, mid++);
            } else if (element == target) {
                mid++;
            } else {
                swap(nums, mid, max--);
            }
        }
        sorted(nums, left, min - 1);
        sorted(nums, max + 1, right);
    }

    public void swap(int[] nums, int m, int n) {
        int t = nums[m];
        nums[m] = nums[n];
        nums[n] = t;
    }
}

611. 有效三角形的个数

题目

给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。

示例 1:

输入: nums = [2,2,3,4]
输出: 3
解释:有效的组合是:
2,3,4 (使用第一个 2)
2,3,4 (使用第二个 2)
2,2,3

示例 2:

输入: nums = [4,2,3,4]
输出: 4

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 1000

思路

  1. 整体思路是拿着两个边,找满足条件的第三个边的数量
  2. 在找第三个边的时候,可以通过排序+二分加快查找速度

答案

class Solution {
    public int triangleNumber(int[] nums) {
        if (nums.length < 3) {
            return 0;
        }
        // 排序
        sorted(nums, 0, nums.length - 1);
        int result = 0;
        for (int i = 0; i < nums.length; i++) {
            // 第一个边
            int first = nums[i];
            // 不重复
            for (int j = i + 1; j < nums.length - 1; j++) {
                // 第二个边
                int second = nums[j];
              	// 找第三个边
                int left = j + 1;
                int right = nums.length - 1;
                while (left <= right) {
                    int middle = (left + right) / 2;
                    int current = nums[middle];
                    // 找到满足条件的最大边长,这里已经满足,所以继续找能满足的,所以往大了找
                    if (first + second > current) {
                        left = middle + 1;
                    } else {
                        right = middle - 1;
                    }
                }
                result += (right - j);
            }
        }
        return result;
    }
	// 练习下排序
    public void sorted(int[] nums, int left, int right) {
        if (left > right) {
            return;
        }
        int element = nums[left];
        int min = left, mid = min + 1, max = right;
        while (mid <= max) {
            int current = nums[mid];
            if (current < element) {
                swap(nums, min++, mid++);
            } else if (current == element) {
                mid++;
            } else {
                swap(nums, mid, max--);
            }
        }
        sorted(nums, left, min - 1);
        sorted(nums, max + 1, right);
    }

    public void swap(int[] nums, int origin, int target) {
        int t = nums[origin];
        nums[origin] = nums[target];
        nums[target] = t;
    }
}

1027. 最长等差数列

题目

思路

答案

2602. 使数组元素全部相等的最少操作次数

题目

给你一个正整数数组 nums 。
同时给你一个长度为 m 的整数数组 queries 。第 i 个查询中,你需要将 nums 中所有元素变成 queries[i] 。你可以执行以下操作 任意 次:

  • 将数组里一个元素 增大 或者 减小 1 。

请你返回一个长度为 m 的数组 answer ,其中 answer[i]是将 nums 中所有元素变成 queries[i] 的 最少 操作次数。
注意,每次查询后,数组变回最开始的值。

示例 1:

输入:nums = [3,1,6,8], queries = [1,5]
输出:[14,10]
解释:第一个查询,我们可以执行以下操作:
-将 nums[0] 减小 2 次,nums = [1,1,6,8] 。
-将 nums[2] 减小 5 次,nums = [1,1,1,8] 。
-将 nums[3] 减小 7 次,nums = [1,1,1,1] 。
第一个查询的总操作次数为 2 + 5 + 7 = 14 。
第二个查询,我们可以执行以下操作:
-将 nums[0] 增大 2 次,nums = [5,1,6,8] 。
-将 nums[1] 增大 4 次,nums = [5,5,6,8] 。
-将 nums[2] 减小 1 次,nums = [5,5,5,8] 。
-将 nums[3] 减小 3 次,nums = [5,5,5,5] 。
第二个查询的总操作次数为 2 + 4 + 1 + 3 = 10 。

示例 2:

输入:nums = [2,9,6,3], queries = [10]
输出:[20]
解释:我们可以将数组中所有元素都增大到 10 ,总操作次数为 8 + 1 + 4 + 7 = 20 。

提示:

  • n == nums.length
  • m == queries.length
  • 1 <= n, m <= 105
  • 1 <= nums[i], queries[i] <= 109

思路

  1. 排序,然后用queries中的元素挨个找,然后计算差值

答案

class Solution {
    public List<Long> minOperations(int[] nums, int[] queries) {
        sorted(nums, 0, nums.length - 1);
        long[] sums = new long[nums.length + 1];
        long sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
            sums[i + 1] = sum;
        }

        List<Long> result = new ArrayList<>(queries.length);
        
        for (int query : queries) {
            long r = 0;
            int index = findMaxMin(nums, query);
            // 小于部分的和
            r = (long) index * query - sums[index];
            // 大于等于部分的和
            r += sums[nums.length] - sums[index] - (long)(nums.length - index) * query;
            result.add(r);
        }
        return result;
    }
	// 找到大于和小于目标值的分界线,返回第一个大于目标值的数
    public int findMaxMin(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int middle = (left + right) / 2;
            int current = nums[middle];
            if (target == current) {
                return middle;
            } else if (target < current) {
                right = middle - 1;
            } else {
                left = middle + 1;
            }
        }
        return left;
    }
	// 锻炼排序
    public void sorted(int[] nums, int left, int right) {
        if (left > right) {
            return;
        }
        int element = nums[left];
        int min = left, mid = min + 1, max = right;
        while (mid <= max) {
            int target = nums[mid];
            if (target == element) {
                mid++;
            } else if (element > target) {
                swap(nums, min++, mid++);
            } else {
                swap(nums, mid, max--);
            }
        }

        sorted(nums, left, min - 1);
        sorted(nums, max + 1, right);
    }

    public void swap(int[] nums, int o, int t) {
        int temp = nums[o];
        nums[o] = nums[t];
        nums[t] = temp;
    }
}

378. 有序矩阵中第 K 小的元素

题目

给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。

你必须找到一个内存复杂度优于 O(n2) 的解决方案。

示例 1:

输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
输出:13
解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13

示例 2:

输入:matrix = [[-5]], k = 1
输出:-5

提示:

  • n == matrix.length
  • n == matrix[i].length
  • 1 <= n <= 300
  • -109 <= matrix[i][j] <= 109
  • 题目数据 保证 matrix 中的所有行和列都按 非递减顺序 排列
  • 1 <= k <= n2

进阶:

  • 你能否用一个恒定的内存(即 O(1) 内存复杂度)来解决这个问题?
  • 你能在 O(n) 的时间复杂度下解决这个问题吗?这个方法对于面试来说可能太超前了,但是你会发现阅读这篇文章( this paper )很有趣。

思路

答案

class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int n = matrix.length;
        int left = matrix[0][0];
        int right = matrix[n - 1][n - 1];

        while (left <= right) {
            int middle = left + (right - left) / 2;
            // 这里不直接返回是因为有可能找到多个满足的。比如示例1,13、14都满足,但是14不在矩阵里
            if (check(matrix, k, middle, n) == 0) {
                right = middle - 1;
            } else if (check(matrix, k, middle, n) < 0) {
            	// 不满足就调大一点。不满足说明middle太小了,调大点
                left = middle + 1;
            } else {
                right = middle - 1;
            }
        }
        return left;
    }
	// 看下如果结果是middle能不能满足条件
    public int check(int[][] matrix, int k, int mid, int n) {
        int i = 0;
        int j = n - 1;

        int num = 0;
        while (i < n && j >= 0) {
            if (matrix[i][j] <= mid) {
                num += j + 1;
                i++;
            } else {
                j--;
            }
        }
        return num == k ? 0 : num < k ? -1 : 1;
    }

}

153. 寻找旋转排序数组中的最小值

题目

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

提示:

  • n == nums.length
  • 1 <= n <= 5000
  • -5000 <= nums[i] <= 5000
  • nums 中的所有整数 互不相同
  • nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

思路

官方题解

答案

class Solution {
    public int findMin(int[] nums) {
        int n = nums.length - 1;
        
        int left = 0, right = n;
        while (left < right) {
            int middle = (left + right) >> 1;
            if (nums[middle] < nums[right]) {
                right = middle;
            } else {
                left = middle + 1;
            }
        }
        return nums[left];
    }
}

1004. 最大连续1的个数 III

题目

给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。

示例 1:

输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。

示例 2:

输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。

提示:

  • 1 <= nums.length <= 105
  • nums[i] 不是 0 就是 1
  • 0 <= k <= nums.length

思路

前缀和统计0的数量
只要范围内0的数量小于k就是长度
因为不知道以哪个结尾,所以要遍历下

答案

class Solution {
    public int longestOnes(int[] nums, int k) {
        int n = nums.length;
        int[] sums = new int[n + 1];
        int sum = 0;

        for (int i = 0; i < n; i++) {
            sum += (1 - nums[i]);
            sums[i + 1] = sum;
        }

        int result = 0;

        for (int r = 0; r < n; r++) {
            int left = 0;
            int right = r;
            while (left <= right) {
                int middle = (left + right) >> 1;
                // 二分找到合适的位置
                if (sums[r + 1] - sums[middle] > k) {
                    left = middle + 1;    
                } else {
                    right = middle - 1;
                }
            }
            result = Math.max(result, r - left + 1);
        }

        return result;
    }
}
  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值