740. 删除并获得点数

整数数组操作获最大点数算法

中等

相关标签

相关企业

提示

给你一个整数数组 nums ,你可以对它进行一些操作。

每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。

开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

示例 1:

输入:nums = [3,4,2]
输出:6
解释:
删除 4 获得 4 个点数,因此 3 也被删除。
之后,删除 2 获得 2 个点数。总共获得 6 个点数。

示例 2:

输入:nums = [2,2,3,3,3,4]
输出:9
解释:
删除 3 获得 3 个点数,接着要删除两个 2 和 4 。
之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。
总共获得 9 个点数。

提示:

  • 1 <= nums.length <= 2 * 104
  • 1 <= nums[i] <= 104

代码:
 

class Solution {
public:
    int deleteAndEarn(vector<int>& nums) {
        int maxNum = 0;
        for (int num : nums) {
            maxNum = max(maxNum, num);
        }
        vector<int> vec(maxNum + 1);
        for (int num : nums) {
            vec[num] += num;
        }
        int n = vec.size();
        int first = vec[0];
        int second = max(vec[0], vec[1]);
        for (int i = 2; i < n; i++) {
            int temp = second;
            second = max(first + vec[i], second);
            first = temp;
        }
        return second;
    }
};

非常好的问题! 我们来 **使用 Visvalingam-Whyatt (VW) 算法**,从一个原始轮廓出发,**简化到指定数量的点(如 10 个点)**,保留其关键形状特征。 Visvalingam-Whyatt 的核心思想是: > 每个中间点都有一个“重要性”值,定义为它与其前后两点构成的三角形面积。面积越小,说明这个点越不重要,可以先删除。 我们将实现一个版本,**动态删除最不重要的点,直到满足目标点数**。 --- ### ✅ 目标 给定一个 OpenCV 轮廓 `contour`(shape: `(N, 1, 2)`),使用 Visvalingam-Whyatt 算法将其简化为 **恰好 `target_num_points` 个点**(例如 8、10、15 等)。 --- ### 🧩 完整 Python 实现代码 ```python import numpy as np def visvalingam_simplify(contour, target_num_points): """ 使用 Visvalingam-Whyatt 算法将轮廓简化为指定数量的点。 参数: contour: numpy array, shape (N, 1, 2), 来自 cv2.findContours target_num_points: int, 希望保留的关键点数量 (>=3) 返回: simplified: numpy array, shape (M, 1, 2), M <= target_num_points(尽可能接近) """ if len(contour) <= target_num_points: return contour.copy() # 转换为 (N, 2) 格式便于处理 points = np.array([pt[0] for pt in contour], dtype=np.float64) # shape: (N, 2) n = len(points) # 初始化索引链表(用于快速插入/删除模拟) prev_idx = [i - 1 for i in range(n)] # prev_idx[i] 是 i 的前一个有效点 next_idx = [i + 1 for i in range(n)] # next_idx[i] 是 i 的后一个有效点 active = [True] * n # 标记是否还“活着” # 计算每个点的重要性(以三角形面积表示) def compute_area(i): if not active[i] or prev_idx[i] == -1 or next_idx[i] == n: return float('inf') # 不可删(边界或无效) a = points[prev_idx[i]] b = points[i] c = points[next_idx[i]] # 向量叉积计算面积:|AB × AC| / 2,但我们只关心相对大小,省略除法 area = abs((b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])) return area # 初始化优先队列:(area, index) import heapq heap = [] areas = [0.0] * n for i in range(1, n - 1): # 首尾不能删! area = compute_area(i) areas[i] = area heapq.heappush(heap, (area, i)) remaining = n # 循环删除最不重要的点 while remaining > target_num_points and heap: area, idx = heapq.heappop(heap) if not active[idx]: continue # 已被删除 prev = prev_idx[idx] nxt = next_idx[idx] # 删除当前点 active[idx] = False # 更新邻居的链接关系 if prev != -1: next_idx[prev] = nxt if nxt != n: prev_idx[nxt] = prev # 更新受影响邻居的重要性(prev 和 nxt) for neighbor in [prev, nxt]: if 0 < neighbor < n - 1 and active[neighbor]: new_area = compute_area(neighbor) # 我们不更新堆中旧记录,而是插入新值,后面检查 active 即可 heapq.heappush(heap, (new_area, neighbor)) areas[neighbor] = new_area remaining -= 1 # 提取剩余的点(保持原始顺序) result = [] i = 0 while i < n: if active[i]: result.append(points[i]) if next_idx[i] == i + 1: i += 1 else: break # 如果只剩两个端点,强制加入中间一点避免退化 if len(result) < 3: # 插入一些中间点补足 full_indices = [j for j in range(n) if active[j]] if len(full_indices) == 2: mid = n // 2 if not active[mid]: result.insert(1, points[mid]) elif len(full_indices) > 2: pass # 应该不会发生 # 转回 OpenCV 格式: (M, 1, 2) result = np.array(result, dtype=np.int32).reshape(-1, 1, 2) return result ``` --- ### 🔍 使用示例 ```python import cv2 import numpy as np # 创建测试图像:一个带锯齿的矩形(模拟噪声轮廓) img = np.zeros((500, 500), dtype=np.uint8) cv2.rectangle(img, (100, 100), (400, 400), 255, 3) # 添加一些噪点边(非必须,只是为了展示简化效果) # 提取轮廓 contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) original_contour = contours[0] print(f"原始点数: {len(original_contour)}") # 简化为 8 个点 simplified_contour = visvalingam_simplify(original_contour, target_num_points=8) print(f"简化后点数: {len(simplified_contour)}") # 可视化对比 vis_img = np.zeros((500, 500, 3), dtype=np.uint8) cv2.drawContours(vis_img, [original_contour], -1, (0, 0, 255), 1) # 红色:原轮廓 cv2.drawContours(vis_img, [simplified_contour], -1, (0, 255, 0), 2) # 绿色:简化 # 显示关键点 for pt in simplified_contour[:, 0, :]: cv2.circle(vis_img, tuple(pt), 4, (255, 255, 0), -1) cv2.imshow("Original vs VW Simplified", vis_img) cv2.waitKey(0) cv2.destroyAllWindows() ``` --- ### ⚙️ 关键技术点解释 | 技术 | 作用 | |------|------| | **三角形面积作为权重** | 衡量某点对局部形状的贡献;平坦区域面积小,易被删 | | **双向链表模拟(prev/next)** | 实现高效删除操作,避免频繁数组拷贝 | | **最小堆(heapq)维护最小面积点** | 快速找到下一个删除的点,时间复杂度 ~O(N log N) | | **仅删除内部点** | 首尾点固定不动,保证起点和闭合性 | | **重新插入邻居更新** | 删除某点后,其邻居的“重要性”会变,需重新计算加入堆 | --- ### ✅ 优点总结 - ✅ **能精确控制输出点数** - ✅ **比 Douglas-Peucker 更自然地保留弯曲和角点** - ✅ **适合低点数压缩(如地图缩放)** - ✅ **逻辑清晰,可扩展性强** --- ### 💡 进阶建议 1. **闭合轮廓处理**:若输入是闭合环(首尾相连),可让 `prev_idx[0] = n-1`, `next_idx[-1] = 0`,形成循环结构。 2. **面积归一化**:对不同尺度图像,可对面积做归一化(除以平均边长平方)。 3. **结合 Douglas-Peucker 后处理**:进一步去除共线点。 --- ### 📦 替代方案:使用第三方库(推荐生产环境) ```bash pip install pysimplification ``` ```python from simplification.cutil import simplify_coords_vw # 转换格式 coords = [(p[0][0], p[0][1]) for p in original_contour] # 简化(注意:这里 tolerance 控制精度,不是点数) simplified_coords = simplify_coords_vw(coords, tolerance=10.0) # 再次采样到固定点数(如果需要) from scipy.interpolate import interp1d import numpy as np x = [p[0] for p in simplified_coords] y = [p[1] for p in simplified_coords] dist = np.sqrt(np.diff(x)**2 + np.diff(y)**2) cum_dist = np.concatenate(([0], np.cumsum(dist))) f_x = interp1d(cum_dist, x, kind='linear') f_y = interp1d(cum_dist, y, kind='linear') target_n = 8 uniform_dist = np.linspace(0, cum_dist[-1], target_n) final_x = f_x(uniform_dist) final_y = f_y(uniform_dist) final_pts = np.array([[int(x), int(y)] for x, y in zip(final_x, final_y)], dtype=np.int32).reshape(-1, 1, 2) ``` > 注意:`simplify_coords_vw` 不直接支持“目标点数”,但你可以通过调整 `tolerance` 配合重采样逼近目标。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值