Given a string S, check if the letters can be rearranged so that two characters that are adjacent to each other are not the same.

If possible, output any possible result.  If not possible, return the empty string.

Example 1:

Input: S = "aab"
Output: "aba"

Example 2:

Input: S = "aaab"
Output: ""

Note:

  • S will consist of lowercase letters and have length in range [1, 500].

这道题给了一个字符串,让我们重构这个字符串,使得相同的字符不会相邻,如果无法做到,就返回空串,题目中的例子很好的说明了这一点。如果先不考虑代码实现,让你来手动重构的话,该怎么做呢?其实就是把相同的字符分开。比如例子1中,两个a相邻了,所以把第二个a和后面的b交换位置,这样分开了相同的字符,就是最终答案了。再来看一个例子,比如 "aaabbc",当发现第二个字符也是 ‘a’ 的时候,就需要往后遍历找到第一个不是 ‘a’ 的字符,即 ‘b’,然后交换 ‘a’ 和 ‘b’ 即可,然后继续往后面进行同样的处理,当无法找到不同的字符后就返回空串。这种方法对有序的字符串S是可以的,虽然题目给的两个例子中字符串S都是有序的,实际上不一定是有序的。所以博主最先的想法是给数组排序呗,但是博主的这个解法跪在了这个例子上 "vvvlo",我们发现排序后就变成 "lovvv",这样上面提到的解法就跪了。其实这里次数出现多的字符串需要在前面,这样才好交换嘛。那么还是要统计每个字符串出现的次数啊,这里使用 HashMap 来建立字母和其出现次数之间的映射。由于希望次数多的字符排前面,可以使用一个最大堆,C++ 中就是优先队列 Priority Queue,将次数当做排序的 key,把次数和其对应的字母组成一个 pair,放进最大堆中自动排序。这里其实有个剪枝的 trick,如果某个字母出现的频率大于总长度的一半了,那么必然会有两个相邻的字母出现。这里博主就不证明了,感觉有点像抽屉原理。所以在将映射对加入优先队列时,先判断下次数,超过总长度一半了的话直接返回空串就行了。

好,最大堆建立好以后,此时难道还是应该使用上面所说的交换的方法吗?其实直接构建新的字符串要更加简单一些。接下来,每次从优先队列中取队首的两个映射对儿处理,因为要拆开相同的字母,这两个映射对儿肯定是不同的字母,可以将其放在一起,之后需要将两个映射对儿中的次数自减1,如果还有多余的字母,即减1后的次数仍大于0的话,将其再放回最大堆。由于是两个两个取的,所以最后 while 循环退出后,有可能优先队列中还剩下了一个映射对儿,此时将其加入结果 res 即可。而且这个多余的映射对儿一定只有一个字母了,因为提前判断过各个字母的出现次数是否小于等于总长度的一半,按这种机制来取字母,不可能会剩下多余一个的相同的字母,参见代码如下:

解法一:

class Solution {
public:
string reorganizeString(string S) {
string res = "";
unordered_map<char, int> m;
priority_queue<pair<int, char>> q;
for (char c : S) ++m[c];
for (auto a : m) {
if (a.second > (S.size() + ) / ) return "";
q.push({a.second, a.first});
}
while (q.size() >= ) {
auto t1 = q.top(); q.pop();
auto t2 = q.top(); q.pop();
res.push_back(t1.second);
res.push_back(t2.second);
if (--t1.first > ) q.push(t1);
if (--t2.first > ) q.push(t2);
}
if (q.size() > ) res.push_back(q.top().second);
return res;
}
};

下面这种解法的原理和上面的很类似,就是写法上很秀,堪比陈独秀。这里使用了一个长度为 26 的一位数组 cnt 来代替上面的 HashMap 进行统计字母的出现次数,然后比较秀的一点是,把上面的映射对儿压缩成了一个整数,做法是将次数乘以了 100,再加上当前字母在一位数字中的位置坐标i,这样一个整数就同时 encode 了次数和对应字母的信息了,而且之后 decode 也很方便。数组 cnt 更新好了后,需要排个序,这一步就是模拟上面解法中最大堆的自动排序功能。不过这里是数字小的在前面,即先处理出现次数少的字母。这里除了和上面一样检测次数不能大于总长度的一半的操作外,还有一个小 trick,就是构建字符串的时候,是从第二个位置开始的。这里构建的字符串是直接对原字符串S进行修改的,因为 cnt 数组建立了之后,字符串S就没啥用了。用一个变量 idx 来表示当前更新字母的位置,初始化为1,表示要从第二个位置开始更新。因为出现次数最多的字母一定要占据第一个位置才行,这就是留出第一个位置的原因。这里很叼的一点,就是隔位更新,这样能保证相同的字母不相邻,而且当 idx 越界后,拉回到起始位置0,这就有点遍历循环数组的感觉。举个栗子来说吧,比如 "aaabbc",更新顺序为:

_ c _ _ _ _

_ c _ b _ _

_ c _ b _ b

a c _ b _ b

a c a b _ b

a c a b a b

解法二:

class Solution {
public:
string reorganizeString(string S) {
int n = S.size(), idx = ;
vector<int> cnt(, );
for (char c : S) cnt[c - 'a'] += ;
for (int i = ; i < ; ++i) cnt[i] += i;
sort(cnt.begin(), cnt.end());
for (int num : cnt) {
int t = num / ;
char ch = 'a' + (num % );
if (t > (n + ) / ) return "";
for (int i = ; i < t; ++i) {
if (idx >= n) idx = ;
S[idx] = ch;
idx += ;
}
}
return S;
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/767

类似题目:

Rearrange String k Distance Apart

Task Scheduler

参考资料:

https://leetcode.com/problems/reorganize-string/

https://leetcode.com/problems/reorganize-string/discuss/113440/Java-solution-PriorityQueue

https://leetcode.com/problems/reorganize-string/discuss/113427/C%2B%2B-Greedy-sort-O(N)

https://leetcode.com/problems/reorganize-string/discuss/232469/Java-No-Sort-O(N)-0ms-beat-100

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Reorganize String 重构字符串的更多相关文章

  1. [LeetCode] 767. Reorganize String 重构字符串

    Given a string S, check if the letters can be rearranged so that two characters that are adjacent to ...

  2. [LeetCode] Decode String 解码字符串

    Given an encoded string, return it's decoded string. The encoding rule is: k[encoded_string], where ...

  3. [LeetCode] Scramble String 爬行字符串

    Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty substrin ...

  4. [LeetCode] Magical String 神奇字符串

    A magical string S consists of only '1' and '2' and obeys the following rules: The string S is magic ...

  5. [LeetCode] Rotate String 旋转字符串

    We are given two strings, A and B. A shift on A consists of taking string A and moving the leftmost ...

  6. LeetCode - Reorganize String

    Given a string S, check if the letters can be rearranged so that two characters that are adjacent to ...

  7. [LeetCode] Reverse String 翻转字符串

    Write a function that takes a string as input and returns the string reversed. Example: Given s = &q ...

  8. [leetcode]Weekly Contest 68 (767. Reorganize String&&769. Max Chunks To Make Sorted&&768. Max Chunks To Make Sorted II)

    766. Toeplitz Matrix 第一题不说,贼麻瓜,好久没以比赛的状态写题,这个题浪费了快40分钟,我真是...... 767. Reorganize String 就是给你一个字符串,能不 ...

  9. Leetcode 8. String to Integer (atoi) atoi函数实现 (字符串)

    Leetcode 8. String to Integer (atoi) atoi函数实现 (字符串) 题目描述 实现atoi函数,将一个字符串转化为数字 测试样例 Input: "42&q ...

随机推荐

  1. Laravel 模型事件入门

    Laravel 模型事件允许你监听模型生命周期内的多个关键点,甚至可以在阻止一个模型的保存或者删除. Laravel 模型事件文档 概述了如何使用钩子将对应事件与相关的事件类型关联起来,但是本文的主旨 ...

  2. Spring Boot with Spring-Data-JPA学习案例

    0x01 什么是Spring Boot? Spring Boot是用来简化Spring应用初始搭建以及开发过程的全新框架,被认为是Spring MVC的"接班人",和微服务紧密联系 ...

  3. java排序算法(九):归并排序

    java排序算法(九):归并排序

  4. java 函数初始化作用

    本人小白一枚,看java类的初始化的时候好晕的说,我觉着书上尽管说的对.但总认为有些信息没说出来,没说清楚,看了好多文章博客的,如今有些感悟,来小写下总结,也算是为以后再次复习种个好果子. 先摘一下书 ...

  5. va_list va_start va_end va_arg 解决变参问题

    解决参数个数不确定的问题. 头文件 #include<stdarg.h> VA_LIST 是在C语言中解决变参问题的一组宏,用于获取不确定个数的参数. #ifdef _M_ALPHA ty ...

  6. 【RabbitMQ系列】队列、绑定、交换器

    队列: 从概念上来讲,AMQP消息路由必须有三部分:交换器.队列和绑定.生产者把消息发布到交换器上:消息最终到达队列,并被消费者接收:绑定决定了消息如何从路由器路由到特定的队列. 消费者通过以下两种方 ...

  7. servlet3.0注解loadOnStartup不起作用解决方案

    多次尝试3.0在源码中直接用注解配置loadOnStartup=1,即web应用启动时创建servlet实例,发现不起作用,但是在web.xml配置则可以正常运行.先上源码. package lee; ...

  8. JavaScript(第二十三天)【事件入门】

    JavaScript事件是由访问Web页面的用户引起的一系列操作,例如:用户点击.当用户执行某些操作的时候,再去执行一系列代码.   一.事件介绍 事件一般是用于浏览器和用户操作进行交互.最早是IE和 ...

  9. 关于python词典(Dictionary)的get()用法

    先贴出参考链接:http://www.runoob.com/python/att-dictionary-get.html get()方法语法: dict.get(key, default=None) ...

  10. C语言——第一次作业(2)

    1.写程序证明p++等价于(p)++还是等价于*(p++)? #include <stdio.h> int main() { int *p,a=5; p = &a; printf( ...