数组(九)-- LC[316][321][402] 去除重复字母

news2024/9/20 12:32:41

1 移掉 K 位数字

1.1 题目描述

        题目链接:https://leetcode.cn/problems/remove-k-digits/

1.2 思路分析

        这道题让我们从一个字符串数字中删除 k 个数字,使得剩下的数最小。也就说,我们要保持原来的数字的相对位置不变。

        以题目中的 n u m = 1432219 , k = 3 num = 1432219,k = 3 num=1432219k=3 为例,我们需要返回一个长度为 4 的字符串,问题在于: 我们怎么才能求出这四个位置依次是什么呢?

        暴力法的话,我们需要枚举 C n ( n − k ) C_n^(n - k) Cn(nk) 种序列(其中 n n n 为数字长度),并逐个比较最大。这个时间复杂度是指数级别的,必须进行优化。

        一个思路是:

  • 从左到右遍历
  • 对于每一个遍历到的元素,我们决定是丢弃还是保留

        问题的关键是:我们怎么知道,一个元素是应该保留还是丢弃呢?

        这里有一个前置知识:对于两个数 123a456 和 123b456,如果 a > b, 那么数字 123a456 大于 数字 123b456,否则数字 123a456 小于等于数字 123b456。也就说,两个相同位数的数字大小关系取决于第一个不同的数的大小。

        因此我们的思路就是:

  • 从左到右遍历
  • 对于遍历到的元素,我们选择保留。
  • 但是我们可以选择性丢弃前面相邻的元素。
  • 丢弃与否的依据如上面的前置知识中阐述中的方法。

        以题目中的 n u m = 1432219 , k = 3 num = 1432219,k = 3 num=1432219k=3 为例的图解过程如下:

        由于没有左侧相邻元素,因此没办法丢弃。

        由于 4 比左侧相邻的 1 大。如果选择丢弃左侧的 1,那么会使得剩下的数字更大(开头的数从 1 变成了 4)。因此我们仍然选择不丢弃。

        由于 3 比左侧相邻的 4 小。 如果选择丢弃左侧的 4,那么会使得剩下的数字更小(开头的数从 4 变成了 3)。因此我们选择丢弃。

        后面的思路类似,这里就不继续分析啦。

        然而需要注意的是,如果给定的数字是一个单调递增的数字,那么我们的算法会永远选择不丢弃。这个题目中要求的,我们要永远确保丢弃 k 个矛盾。

        一个简单的思路就是:

  • 每次丢弃一次,k 减去 1。当 k 减到 0 ,我们可以提前终止遍历。
  • 而当遍历完成,如果 k 仍然大于 0。不妨假设最终还剩下 x 个需要丢弃,那么我们需要选择删除末尾 x 个元素。

        上面的思路可行,但是稍显复杂。

        我们需要把思路逆转过来。刚才我的关注点一直是丢弃,题目要求我们丢弃 k 个。反过来说,不就是让我们保留 n − k n - k nk 个元素么?其中 n 为数字长度。 那么我们只需要按照上面的方法遍历完成之后,再截取前 n − k n - k nk 个元素即可。

        按照上面的思路,我们来选择数据结构。由于我们需要保留和丢弃相邻的元素,因此使用栈这种在一端进行添加和删除的数据结构是再合适不过了,我们来看下代码实现。

class Solution(object):
    def removeKdigits(self, num, k):
        stack = []
        remain = len(num) - k
        for digit in num:               # 构建单调递增的数字串
            while k and stack and stack[-1] > digit:
                stack.pop()
                k -= 1
            stack.append(digit)
        return ''.join(stack[:remain]).lstrip('0') or '0'      

        提示: 如果题目改成求删除 k 个字符之后的最大数,我们只需要将 stack[-1] > digit 中的大于号改成小于号即可

2 去除重复字母

2.1 题目描述

        题目链接:https://leetcode.cn/problems/remove-duplicate-letters/

2.2 思路分析

        与上面题目不同,这道题没有一个全局的删除次数 k。而是对于每一个在字符串 s 中出现的字母 c 都有一个 k 值。这个 k 是 c 出现次数 - 1。

        沿用上面的知识的话,我们首先要做的就是计算每一个字符的 k,可以用一个字典来描述这种关系,其中 key 为 字符 c,value 为其出现的次数。

        具体算法:

  • 建立一个字典。其中 key 为 字符 c,value 为其出现的剩余次数。
  • 从左往右遍历字符串,每次遍历到一个字符,其剩余出现次数 - 1.
  • 对于每一个字符,如果其对应的剩余出现次数大于 1,我们可以选择丢弃(也可以选择不丢弃),否则不可以丢弃。
  • 是否丢弃的标准和上面题目类似。如果栈中相邻的元素字典序更大,那么我们选择丢弃相邻的栈中的元素。

        还记得上面题目的边界条件么?如果栈中剩下的元素大于 n − k n−k nk,我们选择截取前 n − k n - k nk 个数字。然而本题中的 k 是分散在各个字符中的,因此这种思路不可行的。

        不过不必担心。由于题目是要求只出现一次。我们可以在遍历的时候简单地判断其是否在栈上即可。

class Solution:
    def removeDuplicateLetters(self, s) -> int:
        stack = []
        remain_counter = collections.Counter(s)

        for c in s:
            if c not in stack:
                while stack and c < stack[-1] and  remain_counter[stack[-1]] > 0:
                    stack.pop()
                stack.append(c)
            remain_counter[c] -= 1
        return ''.join(stack)

        查询给定字符是否在一个序列中存在的方法。根本上来说,有两种可能:

  • 有序序列: 可以二分法,时间复杂度大致是 O ( N ) O(N) O(N)
  • 无序序列: 可以使用遍历的方式,最坏的情况下时间复杂度为 O ( N ) O(N) O(N)。我们也可以使用空间换时间的方式,使用 N N N 的空间 换取 O ( 1 ) O(1) O(1) 的时间复杂度。
    由于本题中的 stack 并不是有序的,因此我们的优化点考虑空间换时间。而由于每种字符仅可以出现一次,这里使用 hashset 即可。
class Solution:
    def removeDuplicateLetters(self, s) -> int:
        stack = []
        seen = set()
        remain_counter = collections.Counter(s)

        for c in s:
            if c not in seen:
                while stack and c < stack[-1] and  remain_counter[stack[-1]] > 0:
                    seen.discard(stack.pop())
                seen.add(c)
                stack.append(c)
            remain_counter[c] -= 1
        return ''.join(stack)

3 拼接最大数

3.1 题目描述

        题目链接:https://leetcode.cn/problems/create-maximum-number/

3.2 思路分析

        和第一道题类似,只不不过这一次是两个数组,而不是一个,并且是求最大数。

        最大最小是无关紧要的,关键在于是两个数组,并且要求从两个数组选取的元素个数加起来一共是 k。

        然而在一个数组中取 k 个数字,并保持其最小(或者最大),我们已经会了。但是如果问题扩展到两个,会有什么变化呢?

        实际上,问题本质并没有发生变化。 假设我们从 nums1 中取了 k1 个,从 num2 中取了 k2 个,其中 k1 + k2 = k。而 k1 和 k2 这 两个子问题我们是会解决的。由于这两个子问题是相互独立的,因此我们只需要分别求解,然后将结果合并即可。

        假如 k1 和 k2 个数字,已经取出来了。那么剩下要做的就是将这个长度分别为 k1 和 k2 的数字,合并成一个长度为 k 的数组合并成一个最大的数组。

        以题目的 nums1 = [3, 4, 6, 5] nums2 = [9, 1, 2, 5, 8, 3] k = 5 为例。 假如我们从 num1 中取出 1 个数字,那么就要从 nums2 中取出 4 个数字。

        运用第一题的方法,我们计算出应该取 nums1 的 [6],并取 nums2 的 [9,5,8,3]。 如何将 [6] 和 [9,5,8,3],使得数字尽可能大,并且保持相对位置不变呢?

        实际上这个过程有点类似归并排序中的治,而上面我们分别计算 num1 和 num2 的最大数的过程类似归并排序中的分。

        我们将从 num1 中挑选的 k1 个数组成的数组称之为 A,将从 num2 中挑选的 k2 个数组成的数组称之为 B,

def merge(A, B):
    ans = []
    while A or B:
        bigger = A if A > B else B
        ans.append(bigger[0])
        bigger.pop(0)
    return ans

        这里需要说明一下。 在很多编程语言中:如果 A 和 B 是两个数组,当前仅当 A 的首个元素字典序大于 B 的首个元素,A > B 返回 true,否则返回 false。比如:

A = [1,2]
B = [2]
A < B # True

A = [1,2]
B = [1,2,3]
A < B # False

        以合并 [6] 和 [9,5,8,3] 为例,图解过程如下:

        具体算法:

  • 从 nums1 中 取 m i n ( i , l e n ( n u m s 1 ) ) min(i, len(nums1)) min(i,len(nums1))个数形成新的数组 A(取的逻辑同第一题),其中 i i i 等于 0,1,2, … k。
  • 从 nums2 中 对应取 m i n ( j , l e n ( n u m s 2 ) ) min(j, len(nums2)) min(j,len(nums2)) 个数形成新的数组 B(取的逻辑同第一题),其中 j j j 等于 k − i k - i ki
  • 将 A 和 B 按照上面的 merge 方法合并

        上面我们暴力了 k 种组合情况,我们只需要将 k 种情况取出最大值即可。

class Solution:
    def maxNumber(self, nums1, nums2, k):

        def pick_max(nums, k):
            stack = []
            drop = len(nums) - k
            for num in nums:
                while drop and stack and stack[-1] < num:
                    stack.pop()
                    drop -= 1
                stack.append(num)
            return stack[:k]

        def merge(A, B):
            ans = []
            while A or B:
                bigger = A if A > B else B
                ans.append(bigger[0])
                bigger.pop(0)
            return ans

        return max(merge(pick_max(nums1, i), pick_max(nums2, k-i)) for i in range(k+1) if i <= len(nums1) and k-i <= len(nums2))

小结

        这四道题都是删除或者保留若干个字符,使得剩下的数字最小(或最大)或者字典序最小(或最大)。而解决问题的前提是要有一定数学前提。而基于这个数学前提,我们贪心地删除栈中相邻的字符。如果你会了这个套路,那么这四个题目应该都可以轻松解决。


参考

  • 不用字符的最小子序列:https://leetcode.cn/problems/smallest-subsequence-of-distinct-characters/solutions/290204/yi-zhao-chi-bian-li-kou-si-dao-ti-ma-ma-zai-ye-b-6/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/417271.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习第J5周:DenseNet+SE-Net实战

目录 一、介绍 二、前期准备 二、模型 三、训练运行 3.1训练 3.2指定图片进行预测 &#x1f368; 本文为[&#x1f517;365天深度学习训练营]内部限免文章&#xff08;版权归 *K同学啊* 所有&#xff09; &#x1f356; 作者&#xff1a;[K同学啊] &#x1f4cc; 本周任务&…

网络安全之防火墙

目录 网络安全之防火墙 路由交换终归结底是联通新设备 防御对象&#xff1a; 定义&#xff1a; 防火墙的区域划分&#xff1a; 包过滤防火墙 --- 访问控制列表技术 --- 三层技术 代理防火墙 --- 中间人技术 --- 应用层 状态防火墙 --- 会话追踪技术 --- 三层、四层 UTM…

【手把手带你五分钟手机端注册使用GPT的强力对手Claude,免费,无任何成本】

前言 今天刷到了号称是媲美GPT-4的Claude介绍&#xff0c;无需魔法&#xff0c;无任何成本即可以使用&#xff0c;果断尝试注册使用&#xff0c;效果确实不错&#xff0c;关键是免费无成本&#xff01;&#xff01;&#xff01; Claude使用的是Constitutional AI模型。ChatGPT…

亚马逊平台快速消耗滞销品的七大方式

一、亚马逊后台直接进行清仓 1、卖家和商品的资格 在管理多余库存页面上&#xff0c;可以查看亚马逊根据买家需求和其他因素推荐了哪些符合要求的商品参加清仓计划。商品当前价格下的消息将显示商品是否符合清仓促销要求(通过创建清仓促销提交)或清仓店铺要求(通过创建销售提…

Windos下设置java项目开机自启动

这里是将java项目注册为Windows服务实现开机自启动。 查看.NET framework版本 因为使用winsw工具运行时需要使用.NET framework,基本上现在的win10系统带自带有.NET framework4.0&#xff0c;为了选择合适的版本&#xff0c;我们可以查看本机.NET Framework版本&#xff0c;根…

差速巡线机器人设计-良好(80+)的报告-2023

如何提分&#xff1f;将一篇报告提升20分以上呢&#xff1f;差速巡线机器人设计-及格&#xff08;60&#xff09;的报告-2023_zhangrelay的博客-CSDN博客姓名&#xff1a; 学号&#xff1a; 实践项目1名称&#xff1a;差速巡线机器人设计 60分&#xff1a;缺乏思考、没有对比、…

恒生电子面试题总结

CPU突然飙升&#xff0c;如何排查 1.监控cpu运行状态&#xff0c;显示进程运行信息列表 top -c 2. 按CPU使用率排序&#xff0c;键入大写的P P 3.用 top -Hp 命令查看占用 CPU 最高的线程 上一步用 top命令找到了那个 Java 进程。那一个进程中有那么多线程&#xff0c;不可…

[oeasy]python0132_[趣味拓展]emoji_表情符号_抽象话_由来_流汗黄豆

emoji表情符号 回忆上次内容 上次了解了unicode 和 utf-8 unicode是字符集utf-8是一种可变长度的编码方式utf-8是实现unicode的存储和传输的现实的方式 "拜"字 unicode编码是0x62dcutf-8字节形式是b"\xe6\x8b\x9c" 如果我想看看 b"\x62\xdc"用…

准确率、精确率、召回率、F1score和混淆矩阵

准确率和PR、confusion matrix的概念初次接触是在六年前&#xff0c;2017着手在做激光雷达点云处理的相关事宜&#xff0c;六年时光不长&#xff0c;却有很多事情发生。 精确率 precision 也叫查准率&#xff0c;即正确预测为正的占全部预测为正的比例(不准错&#xff0c;宁愿…

图解redis发布和订阅

目录 1.什么是发布订阅 1.1概念 1.2发布订阅过程 1.3发布订阅分为两类 2. 频道的订阅与退订 2.1subcribe 2.2退订频道 3. 模式的订阅和退订 3.1模式的订阅 3.2punsubscribe 4.频道和模式的发布 4.1频道的发布 4.2模式的发布 1.什么是发布订阅 1.1概念 1.发布订阅…

【电源专题】案例:充电芯片如何配置NTC偏置网络设定充电温度区间

背景 充电芯片是需要检测电池内部的NTC电阻来得到电池此时的温度,然后根据温度来判断自己是否要进行充电。因此在导入充电芯片过程中,我们需要设置NTC的偏置网络来设定能充电的温度范围。如下图所示为SGM41523芯片的典型应用图: RT1和RT2为NTC的偏置网络。 在规格书的更详细…

【U8+】修改用友U8+填制凭证界面字体大小

【问题描述】 在使用用友U8软件填制凭证功能时&#xff0c; 觉得【填制凭证】界面字体太小&#xff0c;看着不方便。 想要进行调整。 【解决方法】 1、打开填制凭证界面&#xff0c; 点击最上方【选项】按钮&#xff1b; 2、在弹出的凭证选项设置窗口中&#xff0c; 找到【凭…

PHP语言请求示例,电商商品详情接口(item_get-根据ID取商品详情)代码封装教程

item_get-根据ID取商品详情接口 通过代码封装该接口可以拿到商品标题&#xff0c;商品价格&#xff0c;商品促销信息&#xff0c;商品优惠价&#xff0c;商品库存&#xff0c;sku属性&#xff0c;商品图片&#xff0c;desc图片&#xff0c;desc描述&#xff0c;sku图片&#xf…

抓包工具Wireshark安装与使用

windows下安装 下载安装包 Npcap wireshark依赖于Npcap或者Winpcap软件捕获网络实时数据。这里选择Npcap。下载地址&#xff1a;https://npcap.com/#download。Wireshark Wireshark是一个开源的网络数据包分析器。该分析器尽可能详细地展示捕获的包数据。下载地址&#xff1a…

C++ 数组、指针、数组指针、指针数组、多级指针、STL-map、结构体 的 初始化 及其 初始化赋值

C 数组、指针、数组指针、指针数组、多级指针、STL-map、结构体 的 初始化 及其 初始化赋值C 数组、指针、数组指针、指针数组、多级指针、STL-map、结构体 的 初始化 及其 初始化赋值C 数组、指针、数组指针、指针数组、多级指针数组一维数组初始化&#xff1a;二维数组初始化…

8.1 假设验证的基本概念

学习目标&#xff1a; 要学习假设检验的基本概念&#xff0c;我会按照以下步骤进行&#xff1a; 了解假设检验的基本概念&#xff1a;假设检验是一种统计推断方法&#xff0c;用于判断某个假设是否成立。一般来说&#xff0c;假设检验包括原假设和备择假设两个假设&#xff0c…

语雀笔记备份导出

参考: https://www.cnblogs.com/ssslinppp/p/17020303.htmlhttps://github.com/yuque/yuque-exporterhttps://zhuanlan.zhihu.com/p/582287220https://www.yuque.com/duzh929/blog/ocffqghttps://www.yuque.com/hijiaobu/datalife/onf6sy#BKajf 现在需要超级管理员,若是没有超级…

JDK8新特性 (Lambda表达式和Stream流式编程)

目录 一&#xff1a;JDK8新特性 1. Java SE的发展历史 2. 了解Open JDK 和 Oracle JDK 3. JDK 8新特性 3.1 Lambda表达式&#xff08;重点&#xff09; 3.2 接口的增强 3.3 函数式接口 3.4 方法引用 3.5 集合之Stream流式操作&#xff08;重点&#xff09; 3.6 新的时…

Windows wsl连接网络代理

使用 WSL 访问网络应用程序 | Microsoft Learn 为 WSL2 一键设置代理 - 知乎 (zhihu.com) 介绍 本文介绍开通了Windows WSL子系统之后&#xff0c;怎么在两者之间进行网络通讯&#xff1b;对在windows系统中开启了代理以后&#xff0c;如何在WSL中设置网络代理问题进行了详细…

光萤CEO陈海洲:平台模式将成为户用分布式光伏市场的主流 | 爱分析调研

近两年来&#xff0c;随着国家“双碳”目标的确立&#xff0c;清洁能源迎来重要发展机遇&#xff0c;其中户用分布式光伏因其对土地资源占用少、离用电侧距离近以及与国家乡村振兴战略共振的效果而受到显著的政策倾斜性支持。2020-2022年户用分布式光伏新增装机量持续攀升&…