LeetCode随缘刷题之Java经典面试题将一个字符串数组进行分组输出,每组中的字符串都由相同的字符组成
今天给大家分享一个Java经典的面试题,题目是这样的:
本题是LeetCode题库中的49题。
将一个字符串数组进行分组输出,每组中的字符串都由相同的字符组成
举个例子:输入[“eat”,“tea”,“tan”,“ate”,“nat”,“bat”]
输出[[“ate”,“eat”,“tea”],[“nat”,“tan”],[“bat”]]
刚见到这个题,我的第一反应是正则。通过正则表达式加上循环,来筛选判断两个字母是否全部由相同字符组成。结果发现并没有想象中的简单。
于是我转换思路,想到用数组的方式来求,即将每个元素都转化为char类型数组,先判断长度是否相等。然后比较,也就是下面StringArrays
类中的compareReverse
方法。
但是这样效率实在不高,而且还要循环一次。因为会有包含关系的出现(比如abc
就包含abb
),这就需要我们再反向调用一次,这样才得到了最终方法compare
。
后来我想到用排序也可以实现啊,将字符串拆分排序后再合并,如果两个字符串相同,那肯定都由相同的字符组成。于是就有了2.0版本compareNow
:
package day_12_02.zuoye;
import java.util.Arrays;
/**
* @author soberw
*/
public class StringArrays {
/**
* 给定两个字符串,判定第二个串中的字符是否在第一个字符串中全部存在
* contains()当且仅当此字符串包含指定的char值序列时才返回true。
*
* @param a 第一个字符串
* @param b 第二个字符串
* @return boolean
*/
private boolean compareReverse(String a, String b) {
//记录次数
int count = 0;
char[] newB = b.toCharArray();
for (char bb : newB) {
if (a.contains(String.valueOf(bb))) {
count++;
}
}
//相等说明全部包含在内
if (count == a.length()) {
return true;
}
return false;
}
/**
* 对字符串排序
*
* @param str 字符串
* @return 排序后的
*/
private String sortString(String str) {
char[] s = str.toCharArray();
Arrays.sort(s);
return String.valueOf(s);
}
/**
* 判断两个字符串是否由相同字符组成,切长度相同
*
* @param m 第一个字符串
* @param n 第二个字符串
* @return boolean
*/
public boolean compare(String m, String n) {
if (m.length() != n.length()) {
return false;
}
//避免出现子包含情况
return compareReverse(m, n) && compareReverse(n, m);
}
/**
* 对compare方法的改进
*
* @param m 第一个字符串
* @param n 第二个字符串
* @return boolean
*/
public boolean compareNow(String m, String n) {
if (m.length() != n.length()) {
return false;
}
return sortString(m).equals(sortString(n));
}
}
想法实现一
解决了比较问题,于是迎来了第一次测试,一开始我只想到走二维数组来实现,加双重循环判断放入。
package day_12_02.zuoye;
import java.util.Arrays;
/**
* 对StringArrays的测试
*
* @author soberw
*/
public class SATest {
public static void main(String[] args) {
StringArrays sa = new StringArrays();
String[] str = {"eat", "tea", "tan", "ate", "nat", "bat"};
String[][] newStr = new String[str.length][str.length];
for (int i = 0; i < str.length; i++) {
for (int j = 0; j < str.length; j++) {
if (sa.compare(str[i], str[j])) {
newStr[i][j] = str[j];
}
}
}
for (String[] s : newStr) {
System.out.println(Arrays.toString(s));
}
}
}
结果是这样的 我直接凌乱。。。(虽然真的分好组了)
想法不成立。
其实原因很简单,数组局限性太多,必须指定长度,而且会有默认初始值null,当然最后可以去重得到最终结果。但是我实在是嫌麻烦(二维数组去重…)于是果断更换思路。
想法实现二
我们都知道数组是指定长度的,那有没有不指定长度的动态的呢?当然,Java给我们提供了很多,如set,map,list,vector等等。 目前我的想法是创建一个动态的二维数组,和一个临时数组。通过循环,先取出一个单词放在临时数组中,并将原数组该位置值变为null,然后拿临时数组的元素和原数组的所有非null元素比较(通过compareNow
方法),匹配一个放入一个,并将原数组对应元素赋值为null,循环的最后将临时数组放入二维数组中,依次循环下去,直到原数组值全部变为null。
下面实施,我决定选用Vector来存放数据:
package day_12_02.zuoye;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
/**
* 对StringArrays的测试
*
* @author soberw
*/
public class StringArr {
public static void main(String[] args) {
StringArrays sa = new StringArrays();
//初始数组
Vector<String> v = new Vector<>(List.of("eat", "tea", "tan", "ate", "nat", "bat"));
//保存数据
Vector<Vector<String>> saveV = new Vector<>();
//暂存数组
Vector<String> ver = new Vector<>();
while (true) {
//开始时清空
ver.clear();
for (int j = 0; j < v.size(); j++) {
if (v.get(j) != null) {
ver.add(v.get(j));
v.set(j, null);
break;
}
}
if (ver.size() == 0) {
break;
}
for (int k = 0; k < v.size(); k++) {
if (v.get(k) != null) {
if (sa.compareNow(ver.get(0), v.get(k))) {
ver.add(v.get(k));
v.set(k, null);
}
}
}
System.out.println(ver);
saveV.add(ver);
}
System.out.println(Arrays.toString(saveV.toArray()));
}
}
运行结果:
这是怎么回事,为什么上面输出正确,最后却为空呢。想一想,我终于明白为什么了。saveV.add(ver);这段代码只是添加了ver变量的引用,也就是说ver和saveV的数据都在同一地址单元内。而我每次循环开始都ver.clear()
将ver内的数据清空,那么对应的saveV中的数据也被清空了。
那么如何解决呢,很简单,每次循环重新new一个数组就好了:
package day_12_02.zuoye;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
/**
* 对StringArrays的测试
*
* @author soberw
*/
public class StringArr {
public static void main(String[] args) {
StringArrays sa = new StringArrays();
//初始数组
Vector<String> v = new Vector<>(List.of("eat", "tea", "tan", "ate", "nat", "bat"));
//保存数据
Vector<Vector<String>> saveV = new Vector<>();
while (true) {
//暂存数组
Vector<String> ver = new Vector<>();
for (int j = 0; j < v.size(); j++) {
if (v.get(j) != null) {
ver.add(v.get(j));
v.set(j, null);
break;
}
}
if (ver.size() == 0) {
break;
}
for (int k = 0; k < v.size(); k++) {
if (v.get(k) != null) {
if (sa.compareNow(ver.get(0), v.get(k))) {
ver.add(v.get(k));
v.set(k, null);
}
}
}
System.out.println(ver);
saveV.add(ver);
}
System.out.println(Arrays.toString(saveV.toArray()));
}
}
结果完美找出:
想法成立。
想法实现三
那么这就是最好的解决方案了吗?我认为当然不是。虽然实现了但我觉得还是太麻烦。还要声明函数,还要加循环判断,还有反复声明数组存放,如果面试时这样写,先不说面试官看了直摇头,就这代码量和需要考虑的点,等你还在改bug呢,其他同学早就交卷了。。。
于是催生出了想法三:
充分利用集合的不重复性,先将愿数组所有元素字符排序,放入集合,这样集合就能筛选出所有重复的。
然后只需要把这个集合遍历一遍,与原数组所有元素比较,相同则放入一个临时集合中。最后统一保存在二维集合中去。
开始实施:
package day_12_02.zuoye;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* @author soberw
*/
public class StrArr {
/**
* 对字符串排序
*
* @param str 字符串
* @return 排序后的
*/
private static String sortStr(String str) {
char[] s = str.toCharArray();
Arrays.sort(s);
return String.valueOf(s);
}
public static void main(String[] args) {
//原始数组
String[] str = {"ate", "tae", "tan", "ant", "eat", "bat"};
//中间集合
Set<String> set = new HashSet<>();
//存放最终结果
Set<Set<String>> setLast = new HashSet<>();
for (int i = 0; i < str.length; i++) {
set.add(sortStr(str[i]));
}
for (String s : set) {
//暂存数据
Set<String> seter = new HashSet<>();
//排序后相等的元素放入临时集合
for (int i = 0; i < str.length; i++) {
if (s.equals(sortStr(str[i]))) {
seter.add(str[i]);
}
}
setLast.add(seter);
}
System.out.println(setLast);
}
}
运行结果:
想法成立。
想法实现四
可以看到代码量惊人的减少了,但想法三中设置的中间集合让我灵光一现。我们都知道数组是通过索引下标来走的:0,1,2,3…索引是不可控的,我想着能不能控制索引,将索引的值改为将要判断的值呢?这样就不用通过中间集合了,可以直接通过索引值调用元素了。于是我想到了,Java给我们提供了Map
,你可以理解为键值对的一种集合。
实现如下:
package day_12_02.zuoye;
import java.util.*;
/**
* @author soberw
*/
public class SATrue {
public static void main(String[] args) {
String[] strs = {"ate", "tae", "tan", "ant", "eat","bat"};
System.out.println(new SATrue().grouper(strs));
}
public List<List<String>> grouper(String[] strs) {
if (strs == null || strs.length == 0) {
return new ArrayList<>();
}
Arrays.sort(strs);
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] c = str.toCharArray();
Arrays.sort(c);
String sortedStr = String.valueOf(c);
if (!map.containsKey(sortedStr)) {
map.put(sortedStr, new ArrayList<>());
}
map.get(sortedStr).add(str);
}
return new ArrayList<>(map.values());
}
}
运行结果:
想法成立。
相比于想法三又简化了一步,这是我目前能够想到的最优解了。
一个看似简单的题目,实现起来却并不简单。刚开始我以为本题难点在于筛选出相同的字符组成的元素,结果等真正实现却发现,难点在于如何把它们表现在二维数组中,即如何分组显示。
如果你们有更好的想法,欢迎评论区留言。
LeetCode随缘刷题之Java经典面试题将一个字符串数组进行分组输出,每组中的字符串都由相同的字符组成的更多相关文章
- LeetCode随缘刷题之最短补全词
package leetcode.day_12_10; import org.junit.Test; /** * 给你一个字符串 licensePlate 和一个字符串数组 words ,请你找出并返 ...
- Leetcode随缘刷题之寻找两个正序数组的中位数
我一上来没读清题,想着这题这么简单,直接就上手写了: package leetcode.day_12_05; import java.util.ArrayList; import java.util. ...
- LeetCode随缘刷题之最长回文子串
这一题我用的相对比较笨的方法. 相对于大佬们用的动态规划法,比较复杂.但却更容易理解,我主要是通过记录下标来确定最长回文串的. package leetcode.day_12_06; /** * 给你 ...
- LeetCode随缘刷题之字符串转换整数
package leetcode.day_01_29; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 请你 ...
- LeetCode随缘刷题之转化成小写字母
这道题应该是最简单的一道题了把,简直在侮辱我. package leetcode.day_12_12; /** * 709. 转换成小写字母 * 给你一个字符串 s ,将该字符串中的大写字母转换成相同 ...
- LeetCode随缘刷题之截断句子
这道题相对比较简单.正好最近学到StringBuilder就用了. package leetcode.day_12_06; /** * 句子 是一个单词列表,列表中的单词之间用单个空格隔开,且不存在前 ...
- LeetCode随缘刷题之无重复字符的最长子串
欢迎评论区交流. package leetcode.day_12_04; /** * 给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度. * <p> * 示例1: * &l ...
- LeetCode随缘刷题之赎金信
欢迎评论区讨论. package leetcode.day_12_04; /** * 为了不在赎金信中暴露字迹,从杂志上搜索各个需要的字母,组成单词来表达意思. * * 给你一个赎金信 (ransom ...
- LeetCode随缘刷题之两数相加
逐步解释,有说错的地方欢迎指正. package leetcode.day_12_03; /** * 给你两个非空 的链表,表示两个非负的整数.它们每位数字都是按照逆序的方式存储的,并且每个节点只能存 ...
随机推荐
- [学习笔记] SpringBoot 之 Helloworld
创建项目 IDEA / File / New / Project / Spring Initalizr / Next Group: com.wu 公司官网域名反写 Artifact: hellowor ...
- Fiddler Everywhere简单使用
重装了电脑,想装Fiddler,结果官网下载的包变成了Fiddler Everywhere,体验了下,与老版本相比,操作流程上并无太大变化,但是安装包明显变大了,而且需要登录才能操作,建议还是用老版本 ...
- Shell 里空语句怎么写 - 半角的冒号
Python 里的空语句写作pass for x in range(10): pass Shell 里的空语句写作 : #!/bin/bash for x in {1..10} do #echo $x ...
- js对cookie的操作:读、写、删
js读写cookie //JS操作cookies方法!//写cookiesfunction setCookie(name,value){var Days = 30;var exp = new Date ...
- 一键AI着色,黑白老照片画面瞬间鲜活
很多老照片或者电影受时代技术所限制,只能以黑白形式保存:经过编辑后的黑白视频和图片早已丢失彩色原图,这对于保存者来说都十分遗憾.如何能将单一乏味.陈旧斑驳的黑白照片变成鲜活亮丽的彩色照片,从照片中重新 ...
- lua中的三目运算符
开头先说结论 1.简单版三目运算符(需要自我保证"b"不为"false") a and b or c 2.通用版三目运算符 (a and {b} or {c}) ...
- 【Java】单例设计模式
文章目录 单例设计模式 什么是设计模式 单例设计模式 实现 饿汉式 懒汉式 饿汉式与懒汉式的区别 饿汉式 懒汉式 单例模式的应用场景 单例设计模式 什么是设计模式 设计模式是在大量的实践中总结和理论化 ...
- C++ 从&到&&
人类发展史,就是不断挖坑.填坑的过程. 语言发展史也是如此! 任何一门设计合理的语言,给你的限制或提供的什么特性,都不是没有代价的. C的指针 指针:pointer 指针的思想起源于汇编.指针思想是编 ...
- 《剑指offer》面试题45. 把数组排成最小的数
问题描述 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个. 示例 1: 输入: [10,2] 输出: "102" 示例 2: 输入: ...
- 《剑指offer》面试题46. 把数字翻译成字符串
问题描述 给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 "a" ,1 翻译成 "b",--,11 翻译成 "l",--,25 ...