主页 > 游戏开发  > 

算法的解题模式Ⅲ

算法的解题模式Ⅲ
7. 前K大(小)元素(Top ‘K’ Elements) 方法一:排序法

思路:先对整个数组进行排序,然后取排序后数组的前(后) K 个元素。

方法二:堆(优先队列)法

思路:使用一个最小堆来维护前 K 小(大)的元素。遍历数组,当堆的大小小于 K 时,直接将元素加入堆;当堆的大小达到 K 时,如果当前元素比堆顶元素小(大),则替换堆顶元素并调整堆。

力扣相关例题:

215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

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

class Solution { public int findKthLargest(int[] nums, int k) { Arrays.sort(nums); return nums[nums.length-k]; } }

347. 前 K 个高频元素 - 力扣

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

import java.util.*; class Solution { // 该方法用于找出数组 nums 中出现频率前 k 高的元素 public int[] topKFrequent(int[] nums, int k) { // 第一步:统计每个元素的出现频率 // 创建一个 HashMap 用于存储元素及其出现的频率 // 键为数组中的元素,值为该元素出现的次数 Map<Integer, Integer> map = new HashMap<>(); // 遍历数组 nums 中的每个元素 for (int num : nums) { // 使用 getOrDefault 方法获取元素的当前频率,如果元素不存在则默认为 0 // 然后将频率加 1 并更新到 map 中 map.put(num, map.getOrDefault(num, 0) + 1); } // 第二步:使用优先队列(小顶堆)来维护前 k 个高频元素 // 创建一个优先队列(小顶堆),队列中存储的元素是长度为 2 的数组 // 数组的第一个元素是元素本身,第二个元素是该元素的出现频率 // 优先队列按照元素的频率从小到大排序 PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]); // 遍历 map 中的每个键值对 for (Map.Entry<Integer, Integer> entry : map.entrySet()) { // 如果优先队列的大小小于 k,直接将当前元素及其频率加入队列 if (pq.size() < k) { pq.add(new int[]{entry.getKey(), entry.getValue()}); } // 如果优先队列的大小已经达到 k,并且当前元素的频率大于队列中最小频率的元素 else if (entry.getValue() > pq.peek()[1]) { // 移除队列中频率最小的元素 pq.poll(); // 将当前元素及其频率加入队列 pq.add(new int[]{entry.getKey(), entry.getValue()}); } } // 第三步:从优先队列中取出前 k 个高频元素 // 创建一个长度为 k 的数组用于存储结果 int[] ans = new int[k]; // 遍历优先队列,将队列中的元素依次取出 for (int i = 0; i < k; i++) { // 取出队列头部元素的第一个值(即元素本身)存入结果数组 ans[i] = pq.poll()[0]; } // 返回结果数组 return ans; } }

373. 查找和最小的 K 对数字 - 力扣

给定两个以 非递减顺序排列 的整数数组 nums1 和 nums2 , 以及一个整数 k 。

定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。

请找到和最小的 k 个数对 (u1,v1),  (u2,v2)  ...  (uk,vk) 。

import java.util.ArrayList; import java.util.List; import java.util.PriorityQueue; class Solution { /** * 此方法用于找出两个数组 nums1 和 nums2 中元素两两组合形成的和最小的前 k 对组合 * @param nums1 第一个整数数组 * @param nums2 第二个整数数组 * @param k 要找出的和最小的组合对数 * @return 包含和最小的前 k 对组合的列表 */ public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) { // 创建一个优先队列(最小堆),用于存储索引对 // 优先队列的大小初始化为 k // 优先队列的比较器根据索引对对应元素在 nums1 和 nums2 中的和来排序 // 和较小的索引对排在前面 PriorityQueue<int[]> pq = new PriorityQueue<>(k, (o1, o2) -> { return nums1[o1[0]] + nums2[o1[1]] - nums1[o2[0]] - nums2[o2[1]]; }); // 用于存储最终结果的列表,每个元素是一个包含两个整数的列表,表示一对组合 List<List<Integer>> ans = new ArrayList<>(); // 获取 nums1 数组的长度 int m = nums1.length; // 获取 nums2 数组的长度 int n = nums2.length; // 将 nums1 中前 Math.min(m, k) 个元素与 nums2 中第一个元素组成的索引对加入优先队列 // 因为对于每个 nums1 中的元素,先与 nums2 中最小的元素(即索引为 0 的元素)组合,可能得到较小的和 for (int i = 0; i < Math.min(m, k); i++) { // 索引对的第一个元素是 nums1 的索引,第二个元素是 nums2 的索引 pq.offer(new int[]{i, 0}); } // 循环 k 次,每次从优先队列中取出和最小的索引对,并将对应的元素组合加入结果列表 // 同时,如果取出的索引对在 nums2 中的索引可以向后移动(即不越界),则将新的索引对加入优先队列 while (k-- > 0 && !pq.isEmpty()) { // 从优先队列中取出和最小的索引对 int[] idxPair = pq.poll(); // 创建一个新的列表,用于存储当前取出的索引对对应的元素组合 List<Integer> list = new ArrayList<>(); // 将 nums1 中对应索引的元素加入列表 list.add(nums1[idxPair[0]]); // 将 nums2 中对应索引的元素加入列表 list.add(nums2[idxPair[1]]); // 将当前元素组合加入最终结果列表 ans.add(list); // 检查当前取出的索引对在 nums2 中的索引是否可以向后移动一位 if (idxPair[1] + 1 < n) { // 如果可以移动,将新的索引对(nums1 索引不变,nums2 索引加 1)加入优先队列 pq.offer(new int[]{idxPair[0], idxPair[1] + 1}); } } // 返回最终结果列表 return ans; } } 8. 区间重叠(Overlapping Intervals)

区间重叠模式用于合并或处理数组中的重叠区间。

在按起始时间排序的区间数组中,两个区间[a, b]和[c, d]重叠的条件是b >= c(即第一个区间的结束时间大于或等于第二个区间的起始时间)。

示例:输入:

list1 = [[1, 3], [5, 7]]list2 = [[2, 4], [6, 8]]

 输出:true

解释:list1 中的 [1, 3] 与 list2 中的 [2, 4] 重叠,list1 中的 [5, 7] 与 list2 中的 [6, 8] 重叠,所以返回 true。

解题思路

我们可以使用双重循环遍历两个区间列表,对于 list1 中的每个区间和 list2 中的每个区间,判断它们是否重叠。判断两个区间 [a1, b1] 和 [a2, b2] 重叠的条件是 Math.max(a1, a2) <= Math.min(b1, b2)。

力扣相关例题:

56. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

import java.util.Arrays; class Solution { /** * 该方法用于合并重叠的区间。给定一个二维数组 intervals,其中每个元素是一个长度为 2 的一维数组, * 表示一个区间的起始和结束位置。方法会将重叠的区间进行合并,并返回合并后的区间数组。 * @param intervals 包含多个区间的二维数组 * @return 合并重叠区间后的二维数组 */ public int[][] merge(int[][] intervals) { // 初始化一个二维数组 res,用于存储合并后的区间,其初始大小为 intervals 的长度 int[][] res = new int[intervals.length][2]; // 获取 intervals 数组的长度,即区间的数量 int n = intervals.length; // 对 intervals 数组按照每个区间的起始位置进行升序排序 // 使用 Lambda 表达式 (a, b) -> a[0] - b[0] 作为比较器 // 若 a[0] - b[0] < 0,则 a 排在 b 前面;若 a[0] - b[0] > 0,则 b 排在 a 前面 Arrays.sort(intervals, (a, b) -> a[0] - b[0]); // 初始化 index 为 -1,用于标记 res 数组中当前存储合并后区间的位置 int index = -1; // 遍历 intervals 数组中的每个区间 for (int i = 0; i < intervals.length; i++) { // 获取当前遍历到的区间 int[] cur = intervals[i]; // 如果 index 为 -1,说明 res 数组还没有存储任何区间,或者当前区间的起始位置大于 res 中最后一个区间的结束位置 // 意味着当前区间与之前合并的区间不重叠 if (index == -1 || cur[0] > res[index][1]) { // 增加 index,为存储新的区间做准备 index++; // 将当前区间直接存入 res 数组的 index 位置 res[index] = cur; } else { // 若当前区间与 res 中最后一个区间重叠,则更新 res 中最后一个区间的结束位置 // 取当前区间的结束位置和 res 中最后一个区间的结束位置的较大值 res[index][1] = Math.max(cur[1], res[index][1]); } } // 由于 res 数组的初始大小可能大于实际合并后的区间数量,使用 Arrays.copyOf 方法截取有效部分 // 从 res 数组中复制从索引 0 到 index + 1 的元素,得到最终合并后的区间数组 return Arrays.copyOf(res, index + 1); } }

57. 插入区间

给你一个 无重叠的 ,按照区间起始端点排序的区间列表 intervals,其中 intervals[i] = [starti, endi] 表示第 i 个区间的开始和结束,并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval = [start, end] 表示另一个区间的开始和结束。

在 intervals 中插入区间 newInterval,使得 intervals 依然按照 starti 升序排列,且区间之间不重叠(如果有必要的话,可以合并区间)。

返回插入之后的 intervals。

注意 你不需要原地修改 intervals。你可以创建一个新数组然后返回它。

import java.util.ArrayList; import java.util.List; class Solution { /** * 该方法用于将一个新的区间插入到已有的区间数组中,并合并可能重叠的区间。 * * @param intervals 已有的区间数组,每个区间由一个长度为 2 的整数数组表示,分别为区间的起始和结束位置。 * @param newInterval 要插入的新的区间,同样由一个长度为 2 的整数数组表示。 * @return 插入并合并重叠区间后的区间数组。 */ public int[][] insert(int[][] intervals, int[] newInterval) { // 用于存储最终结果的列表,因为最终结果的长度不确定,所以使用动态数组 ArrayList List<int[]> result = new ArrayList<>(); // 标记新的区间是否已经被添加到结果列表中 boolean added = false; // 用于遍历原区间数组的索引 int i = 0; // 原区间数组的长度 int n = intervals.length; // 开始遍历原区间数组 while (i < n) { // 获取当前遍历到的区间 int[] interval = intervals[i]; // 情况 1:当前区间在新插入区间的左侧,没有重叠 if (interval[1] < newInterval[0]) { // 直接将当前区间添加到结果列表中 result.add(interval); // 移动到下一个区间 i++; } // 情况 2:当前区间在新插入区间的右侧,没有重叠 else if (interval[0] > newInterval[1]) { // 如果新的区间还未添加到结果列表中,则先添加新的区间 if (!added) { result.add(newInterval); // 标记新的区间已添加 added = true; } // 将当前区间添加到结果列表中 result.add(interval); // 移动到下一个区间 i++; // 由于后续的区间都在新插入区间的右侧,直接将剩余的所有区间添加到结果列表中 while (i < n) { result.add(intervals[i]); i++; } } // 情况 3:当前区间与新插入区间有重叠 else { // 合并当前区间和新插入区间,更新新插入区间的起始位置为两者起始位置的最小值 newInterval[0] = Math.min(newInterval[0], interval[0]); // 更新新插入区间的结束位置为两者结束位置的最大值 newInterval[1] = Math.max(newInterval[1], interval[1]); // 移动到下一个区间 i++; } } // 处理所有区间都被合并的情况或原区间数组为空的情况 // 如果新的区间还未添加到结果列表中,则将其添加进去 if (!added) { result.add(newInterval); } // 将存储结果的列表转换为二维数组并返回 return result.toArray(new int[result.size()][]); } }

435. 无重叠区间

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。

注意 只在一点上接触的区间是 不重叠的。例如 [1, 2] 和 [2, 3] 是不重叠的。

import java.util.Arrays; import java.util.Comparator; class Solution { /** * 该方法用于计算为了使给定的区间数组中的区间互不重叠,需要移除的区间的最小数量。 * * @param intervals 一个二维数组,其中每个一维数组表示一个区间,一维数组的长度为 2, * 分别代表区间的起始位置和结束位置。 * @return 为使区间互不重叠需要移除的区间的最小数量。 */ public int eraseOverlapIntervals(int[][] intervals) { // 首先对区间数组按照每个区间的结束位置进行升序排序 // 使用匿名内部类实现 Comparator 接口,重写 compare 方法来定义排序规则 // 这里通过 p[1] - q[1] 确保结束位置小的区间排在前面 Arrays.sort(intervals, new Comparator<int[]>() { @Override public int compare(int[] p, int[] q) { return p[1] - q[1]; } }); // 初始化变量 right 为排序后第一个区间的结束位置 // 用于记录当前已选择区间的最右边界 int right = intervals[0][1]; // 初始化变量 count 为 0,用于记录需要移除的区间数量 int count = 0; // 从第二个区间开始遍历排序后的区间数组 for (int i = 1; i < intervals.length; i++) { // 如果当前区间的起始位置小于已选择区间的最右边界 // 说明当前区间与已选择的区间重叠 if (intervals[i][0] < right) { // 由于要使区间互不重叠,所以需要移除当前重叠的区间 // 因此移除区间的数量加 1 count++; } else { // 如果当前区间与已选择的区间不重叠 // 则更新已选择区间的最右边界为当前区间的结束位置 right = intervals[i][1]; } } // 返回需要移除的区间的最小数量 return count; } } 9. 变形二分查找(Modified Binary Search)

变形二分查找是在基本二分查找的基础上,根据具体问题的需求对查找条件和边界处理进行调整,以解决一些更复杂的查找问题,例如在旋转排序数组中查找元素。

在处理涉及排序或旋转数组,且需要查找特定元素的问题时,可使用该模式。

示例问题: 在一个旋转排序数组中查找元素。

示例: 输入:nums = [4, 5, 6, 7, 0, 1, 2],target = 0 输出:4 解释: 进行二分查找时,额外增加一个判断,以确定数组的哪一半是有序的。 然后检查目标元素是否在有序的那一半范围内。 如果在,就在这一半中查找;否则,在另一半中查找。

力扣相关题目:

33. 搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

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

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

class Solution { /** * 在旋转排序数组中搜索目标值。旋转排序数组是指一个原本有序的数组在某个点上进行了旋转, * 例如 [0,1,2,4,5,6,7] 旋转后可能变为 [4,5,6,7,0,1,2]。 * * @param nums 旋转排序数组 * @param target 要搜索的目标值 * @return 若目标值存在于数组中,返回其索引;否则返回 -1 */ public int search(int[] nums, int target) { // 初始化左指针,指向数组的起始位置 int left = 0; // 初始化右指针,指向数组的末尾位置 int right = nums.length - 1; // 当左指针小于右指针时,持续进行二分查找 while (left < right) { // 计算中间位置的索引 int mid = (left + right) / 2; // 如果中间位置的元素等于目标值,直接返回中间位置的索引 if (nums[mid] == target) return mid; // 判断左半部分是否有序 if (nums[left] <= nums[mid]) { // 如果目标值在左半部分的有序区间内 if (target >= nums[left] && target < nums[mid]) { // 缩小右边界,在左半部分继续查找 right = mid - 1; } else { // 目标值不在左半部分的有序区间内,缩小左边界,在右半部分查找 left = mid + 1; } } else { // 左半部分无序,说明右半部分有序 // 如果目标值在右半部分的有序区间内 if (target > nums[mid] && target <= nums[right]) { // 缩小左边界,在右半部分继续查找 left = mid + 1; } else { // 目标值不在右半部分的有序区间内,缩小右边界,在左半部分查找 right = mid - 1; } } } // 当 left 和 right 相遇时,检查该位置的元素是否等于目标值 if (nums[left] == target) return left; // 若不相等,说明目标值不在数组中,返回 -1 else return -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) 的算法解决此问题。

class Solution { /** * 该方法用于在旋转排序数组中查找最小值。旋转排序数组是指一个原本有序的数组在某个点上进行了旋转, * 例如 [0,1,2,4,5,6,7] 旋转后可能变为 [4,5,6,7,0,1,2]。 * * @param nums 旋转排序数组 * @return 数组中的最小值 */ public int findMin(int[] nums) { // 二分查找的区间定义说明: // 这里将二分区间设置为 [0, n - 2],使用循环不变量来辅助查找。 // 循环不变量为:l - 1 位置代表最小值左边的元素,r + 1 位置代表最小值或者最小值右边的元素。 // 通过不断调整 l 和 r 的位置,最终让 l 指向最小值的位置。 // 获取数组的长度 int n = nums.length; // 初始化左指针 l 为 0,右指针 r 为 n - 2 // 因为二分区间为 [0, n - 2] int l = 0, r = n - 2; // 开始二分查找,当左指针小于等于右指针时,继续循环 while (l <= r) { // 计算中间位置的索引,使用 l + (r - l) / 2 避免 (l + r) 可能导致的整数溢出问题 int mid = l + (r - l) / 2; // 将中间位置的元素 nums[mid] 与数组最后一个元素 nums[n - 1] 进行比较 if (nums[mid] < nums[n - 1]) { // 如果 nums[mid] 小于 nums[n - 1],说明最小值在 mid 及其左边 // 因此更新右指针 r 为 mid - 1,缩小查找范围到左半部分 r = mid - 1; } else { // 如果 nums[mid] 大于等于 nums[n - 1],说明最小值在 mid 的右边 // 因此更新左指针 l 为 mid + 1,缩小查找范围到右半部分 l = mid + 1; } } // 当循环结束时,l 指向的就是最小值的位置 // 返回该位置的元素 return nums[l]; } }

240. 搜索二维矩阵 II

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

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

class Solution { /** * 该方法用于在一个特殊的二维矩阵中查找目标值。这个二维矩阵具有以下特性: * 每行元素从左到右递增排序,每列元素从上到下递增排序。 * * @param matrix 给定的二维矩阵 * @param target 要查找的目标值 * @return 如果矩阵中存在目标值,返回 true;否则返回 false */ public boolean searchMatrix(int[][] matrix, int target) { // 获取矩阵的行数 int n = matrix.length; // 如果矩阵的行数为 0,说明矩阵为空,直接返回 false if (n == 0) return false; // 获取矩阵的列数 int m = matrix[0].length; // 从矩阵的右上角开始查找,初始化行索引 i 为 0,列索引 j 为最后一列的索引 for (int i = 0, j = m - 1; i < n && j >= 0;) { // 如果当前位置的元素等于目标值,说明找到了目标值,返回 true if (matrix[i][j] == target) return true; // 如果当前位置的元素大于目标值,由于每列元素从上到下递增, // 所以目标值不可能在当前列的下方,向左移动一列继续查找 if (matrix[i][j] > target) j--; // 如果当前位置的元素小于目标值,由于每行元素从左到右递增, // 所以目标值不可能在当前行的左边,向下移动一行继续查找 else i++; } // 遍历完整个矩阵都没有找到目标值,返回 false return false; } }

标签:

算法的解题模式Ⅲ由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“算法的解题模式Ⅲ