|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "算法训练-数据结构" |
| 4 | +date: 2025-11-22T14:44:44+0800 |
| 5 | +description: "本文介绍了五种算法问题的解决方案:1)数组循环左移通过三次翻转实现;2)双指针法在有序数组中寻找第K大数;3)股票买卖问题通过记录最小价格和最大利润求解;4)双指针法计算容器最大储水量;5)最小堆方法寻找包含k个列表元素的最小区间。这些算法涵盖了数组操作、双指针技巧和堆数据结构应用,展示了不同场景下的高效解决方案。" |
| 6 | +keywords: "算法训练-数据结构" |
| 7 | +categories: ['未分类'] |
| 8 | +tags: ['算法', '数据结构'] |
| 9 | +artid: "154796251" |
| 10 | +arturl: "https://blog.csdn.net/weixin_55800388/article/details/154796251" |
| 11 | +image: |
| 12 | + path: https://api.vvhan.com/api/bing?rand=sj&artid=154796251 |
| 13 | + alt: "算法训练-数据结构" |
| 14 | +render_with_liquid: false |
| 15 | +featuredImage: https://bing.ee123.net/img/rand?artid=154796251 |
| 16 | +featuredImagePreview: https://bing.ee123.net/img/rand?artid=154796251 |
| 17 | +cover: https://bing.ee123.net/img/rand?artid=154796251 |
| 18 | +img: https://bing.ee123.net/img/rand?artid=154796251 |
| 19 | +--- |
| 20 | + |
| 21 | + |
| 22 | + |
| 23 | +# 算法训练-数据结构 |
| 24 | + |
| 25 | +**类型:数组+移位/排序** |
| 26 | +**1.数组元素循环左移p位后的结果。** |
| 27 | +循环左移和以下操作等价: |
| 28 | +1.通过中间元素对称翻转 |
| 29 | +2.将数组分为n-p个和p个两部分 |
| 30 | +3.分别翻转这两个数组即可 |
| 31 | + |
| 32 | +```c |
| 33 | +// 中心翻转 |
| 34 | +void Reverse(int R[], int left, int right) { |
| 35 | + while (left < right) { |
| 36 | + int temp = R[left]; |
| 37 | + R[left] = R[right]; |
| 38 | + R[right] = temp; |
| 39 | + left++; |
| 40 | + right--; |
| 41 | + } |
| 42 | +} |
| 43 | +//分组再翻转 |
| 44 | +void CyclicLeftShift(int R[], int n, int p) { |
| 45 | + Reverse(R, 0, n-1); // 全部逆置 |
| 46 | + Reverse(R, 0, p-1); // 前p个逆置 |
| 47 | + Reverse(R, p, n-1); // 后n-p个逆置 |
| 48 | +} |
| 49 | + |
| 50 | + |
| 51 | +``` |
| 52 | +
|
| 53 | +**2**.**两个整数递增有序序列A,B分别有n和m个元素,求第K大的数(1≤k≤n+m),要求算法有最佳时间复杂度** |
| 54 | +例子:输入A={1,3,4,5,6},B={3,4,5,6},K=4 |
| 55 | +思路: |
| 56 | +A、B递增,从大到小数到第k个即可。(时间复杂度o(k)) |
| 57 | +
|
| 58 | +```java |
| 59 | +public class KthLargestInTwoArrays { |
| 60 | + |
| 61 | + /** |
| 62 | + * 方法1:双指针合并 - 时间复杂度O(K),空间复杂度O(1) |
| 63 | + */ |
| 64 | + public static int findKthLargest1(int[] A, int[] B, int k) { |
| 65 | + int n = A.length, m = B.length; |
| 66 | + int i = n - 1, j = m - 1; // 从后往前遍历(因为要求第K大) |
| 67 | + int count = 0; |
| 68 | + int result = 0; |
| 69 | + // 两个数组都还没数完的情况 |
| 70 | + while (i >= 0 && j >= 0) { |
| 71 | + count++; |
| 72 | + if (A[i] >= B[j]) { |
| 73 | + if (count == k) return A[i]; |
| 74 | + i--; |
| 75 | + } else { |
| 76 | + if (count == k) return B[j]; |
| 77 | + j--; |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + // 如果其中一个数组遍历完 |
| 82 | + while (i >= 0) { |
| 83 | + count++; |
| 84 | + if (count == k) return A[i]; |
| 85 | + i--; |
| 86 | + } |
| 87 | + |
| 88 | + while (j >= 0) { |
| 89 | + count++; |
| 90 | + if (count == k) return B[j]; |
| 91 | + j--; |
| 92 | + } |
| 93 | + |
| 94 | + return -1; // k超出范围 |
| 95 | + } |
| 96 | +} |
| 97 | +
|
| 98 | +``` |
| 99 | + |
| 100 | +**类型:数学** |
| 101 | +**3.** 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 |
| 102 | +你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 |
| 103 | +返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 |
| 104 | +**思路:** |
| 105 | +相当于一个减法运算,得到最大差值。 |
| 106 | +所以找到最大的被减数和最小的减数即可。 |
| 107 | + |
| 108 | +```java |
| 109 | +public class StockProfit { |
| 110 | + public int maxProfit(int[] prices) { |
| 111 | + if (prices == null || prices.length == 0) { |
| 112 | + return 0; |
| 113 | + } |
| 114 | + //初始值-最低价格,遇到最低价格则更新 |
| 115 | + int minPrice = Integer.MAX_VALUE; |
| 116 | + int maxProfit = 0; |
| 117 | + |
| 118 | + for (int i = 0; i < prices.length; i++) { |
| 119 | + // 更新最低价格 |
| 120 | + if (prices[i] < minPrice) { |
| 121 | + minPrice = prices[i]; |
| 122 | + }else if (prices[i] - minPrice > maxProfit) { |
| 123 | + // 计算当前价格卖出能获得的利润,更新最大利润 |
| 124 | + maxProfit = prices[i] - minPrice; |
| 125 | + } |
| 126 | + } |
| 127 | + return maxProfit; |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +``` |
| 132 | + |
| 133 | +**类型 :数组+双指针** |
| 134 | +**4.** 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 |
| 135 | + |
| 136 | +找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。 |
| 137 | +思路: |
| 138 | + |
| 139 | +* 相当于求出宽*高,也就是(b-a)*min(b,a)的最大值。乘数越大越好 |
| 140 | +* 只需更换a,b的值就好,更换条件,比min(b,a)大则更换,否则不换 |
| 141 | + |
| 142 | +```java |
| 143 | + public class Solution { |
| 144 | + public int maxArea(int[] height) { |
| 145 | + int left = 0; // 左指针 |
| 146 | + int right = height.length - 1; // 右指针 |
| 147 | + int maxArea = 0; // 最大面积 |
| 148 | + |
| 149 | + while (left < right) { |
| 150 | + // 计算当前容器的面积 |
| 151 | + int currentWidth = right - left; |
| 152 | + int currentHeight = Math.min(height[left], height[right]); |
| 153 | + int currentArea = currentWidth * currentHeight; |
| 154 | + |
| 155 | + // 更新最大面积 |
| 156 | + maxArea = Math.max(maxArea, currentArea); |
| 157 | + |
| 158 | + // 移动较短的指针,希望找到更高的垂线 |
| 159 | + if (height[left] < height[right]) { |
| 160 | + left++; |
| 161 | + } else { |
| 162 | + right--; |
| 163 | + } |
| 164 | + } |
| 165 | + |
| 166 | + return maxArea; |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +``` |
| 171 | + |
| 172 | +**类型:列表 + 堆 遍历** |
| 173 | +**5.** 有 k 个 非递减排列 的整数列表。找到一个最小 区间,使得 k 个列表中的每个列表至少有一个数包含在其中。 |
| 174 | +**思路:** |
| 175 | +1.先随机取每个子列表的一个元素,存放在最小堆中,找出最大值和最小值组成区间 |
| 176 | +2.取出最小值,依次存入子列表的一个新值,更新最小区间 |
| 177 | + |
| 178 | +```java |
| 179 | +public int[] smallestRange(List<List<Integer>> nums) { |
| 180 | + // 最小堆,存储三元组[元素值, 列表索引, 元素在列表中的索引] |
| 181 | + // 堆按照元素值进行排序,最小的元素在堆顶 |
| 182 | + PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> a[0] - b[0]); |
| 183 | + |
| 184 | + int currentMax = Integer.MIN_VALUE; // 记录当前堆中所有元素的最大值 |
| 185 | + int k = nums.size(); // 列表的数量 |
| 186 | + |
| 187 | + // 初始化堆:将每个列表的第一个元素加入堆中 |
| 188 | + for (int i = 0; i < k; i++) { |
| 189 | + // 检查当前列表是否为空 |
| 190 | + if (!nums.get(i).isEmpty()) { |
| 191 | + int val = nums.get(i).get(0); // 获取当前列表的第一个元素 |
| 192 | + // 将三元组[元素值, 列表索引, 元素索引]加入堆 |
| 193 | + minHeap.offer(new int[]{val, i, 0}); |
| 194 | + // 更新当前最大值 |
| 195 | + currentMax = Math.max(currentMax, val); |
| 196 | + } |
| 197 | + } |
| 198 | + |
| 199 | + // 初始化最小区间的起点和终点,设置为一个非常大的范围 |
| 200 | + int start = -1000000, end = 1000000; |
| 201 | + int minRange = end - start; // 初始化最小区间长度 |
| 202 | + |
| 203 | + // 当堆中有k个元素时继续处理(确保每个列表至少有一个元素在考虑范围内) |
| 204 | + while (minHeap.size() == k) { |
| 205 | + // 从堆中取出当前最小的元素 |
| 206 | + int[] current = minHeap.poll(); |
| 207 | + int currentVal = current[0]; // 最小元素的值 |
| 208 | + int listIdx = current[1]; // 当前元素所在的列表索引 |
| 209 | + int elementIdx = current[2]; // 当前元素在列表中的索引 |
| 210 | + |
| 211 | + // 检查当前区间[currentVal, currentMax]是否比之前记录的最小区间更小 |
| 212 | + // 或者区间长度相同但起点更小(题目要求返回最小的区间) |
| 213 | + if (currentMax - currentVal < minRange || |
| 214 | + (currentMax - currentVal == minRange && currentVal < start)) { |
| 215 | + // 更新最小区间信息 |
| 216 | + minRange = currentMax - currentVal; |
| 217 | + start = currentVal; |
| 218 | + end = currentMax; |
| 219 | + } |
| 220 | + |
| 221 | + // 如果当前元素所在的列表还有下一个元素 |
| 222 | + if (elementIdx + 1 < nums.get(listIdx).size()) { |
| 223 | + // 获取下一个元素的值 |
| 224 | + int nextVal = nums.get(listIdx).get(elementIdx + 1); |
| 225 | + // 将下一个元素加入堆中 |
| 226 | + minHeap.offer(new int[]{nextVal, listIdx, elementIdx + 1}); |
| 227 | + // 更新当前最大值(因为新加入的元素可能比当前最大值更大) |
| 228 | + currentMax = Math.max(currentMax, nextVal); |
| 229 | + } else { |
| 230 | + // 如果当前列表已经遍历完,则退出循环 |
| 231 | + // 因为无法保证每个列表至少有一个元素在区间内了 |
| 232 | + break; |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | + // 返回找到的最小区间 |
| 237 | + return new int[]{start, end}; |
| 238 | + } |
| 239 | +main(){ |
| 240 | + Solution solution = new Solution(); |
| 241 | + // 创建测试数据 |
| 242 | + List<List<Integer>> nums = new ArrayList<>(); |
| 243 | + nums.add(Arrays.asList(4, 10, 15, 24, 26)); |
| 244 | + nums.add(Arrays.asList(0, 9, 12, 20)); |
| 245 | + nums.add(Arrays.asList(5, 18, 22, 30)); |
| 246 | + |
| 247 | + // 调用方法并输出结果 |
| 248 | + int[] result = solution.smallestRange(nums); |
| 249 | +} |
| 250 | + |
| 251 | +``` |
| 252 | + |
| 253 | + |
| 254 | + |
0 commit comments