welcome to my blog
剑指offer面试题38(java版):字符串的排列
题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
第四次做; 核心: 1)回溯; 2)使用HashSet在递归中去重, HashSet没有get方法, 得通过迭代器获取HashSet中的元素 3) 递归函数的循环主体中的sb.append(chs[index]); //选取的是索引index处的元素, 开始错写成i了
/*
排列用递归很好实现
回溯算法
*/
class Solution {
public String[] permutation(String s) {
if(s==null||s.length()==0)
return new String[]{};
char[] chs = s.toCharArray();
List<String> list = new ArrayList<>();
StringBuilder sb = new StringBuilder();
core(list, sb, chs, 0);
String[] strs = new String[list.size()];
Iterator<String> ite = list.iterator();
int i=0;
while(ite.hasNext()){
strs[i++] = ite.next();
}
return strs;
}
//递归函数逻辑: 获取所有的排列可能, 选择当前位置的元素, 处理完剩下所有位置的可能
private void core(List<String> list, StringBuilder sb, char[] chs, int index){
//base case
if(index==chs.length){
list.add(sb.toString());
return;
}
//用于去重
HashSet<Character> set = new HashSet<>();
for(int i=index; i<chs.length; i++){
//去重, 出现过的元素不再选取
if(set.contains(chs[i]))
continue;
set.add(chs[i]);
//改变现场
swap(chs, index, i);
sb.append(chs[index]); //确定索引index处的元素, 开始错写成i了
//新条件新递归
core(list, sb, chs, index+1);
//恢复现场
sb.deleteCharAt(sb.length()-1);
swap(chs, index, i);
}
}
private void swap(char[] arr, int i, int j){
char tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
第四次做; 回溯; 使用HashSet在base case中去重, HashSet没有get方法, 得通过迭代器获取HashSet中的元素
/*
排列用递归很好实现
回溯算法
*/
class Solution {
public String[] permutation(String s) {
if(s==null||s.length()==0)
return new String[]{};
char[] chs = s.toCharArray();
HashSet<String> set = new HashSet<>();
StringBuilder sb = new StringBuilder();
core(set, sb, chs, 0);
String[] strs = new String[set.size()];
Iterator<String> ite = set.iterator();
int i=0;
while(ite.hasNext()){
strs[i++] = ite.next();
}
return strs;
}
//递归函数逻辑: 获取所有的排列可能, 选择当前位置的元素, 处理完剩下所有位置的可能
private void core(HashSet<String> set, StringBuilder sb, char[] chs, int index){
//base case
if(index==chs.length){
String s = sb.toString();
if(!set.contains(s))
set.add(s);
return;
}
//
for(int i=index; i<chs.length; i++){
//改变现场
swap(chs, index, i);
sb.append(chs[index]); //确定索引index处的元素, 开始错写成i了
//新条件新递归
core(set, sb, chs, index+1);
//恢复现场
sb.deleteCharAt(sb.length()-1);
swap(chs, index, i);
}
}
private void swap(char[] arr, int i, int j){
char tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
笔记
- 将char[]转成String不能用ch.toString(), 而是用String.valueOf(ch)
- ch.toString()是调用ch这个对象的toString()方法, 并不是将ch的内容转成String
- 具体见Object.toString()方法
- str1.compareTo(str2) 比较两个字符串, 按照字典顺序进行排序, st1>str2返回一个大于0的整数
- ArrayList al al.set(index, element)
- ArrayList al al.get(index)
- 使用HashSet处理重复问题, hs.add(element),如果返回true表示添加元素成功, 如果返回false表示添加元素失败, 说明hs中已经存在该元素
- 其实List类中包含了contains方法,可以判断集合中是否已经存在某个元素, ArrayList当然也能用这个方法
- 可以使用Collections.sort(al)对al中的元素进行排序(根据元素的自然顺序 对指定列表按升序进行排序)
思路
- 使用递归函数每次处理一个位置
- 第一个位置有n种选择, 第二个位置有n-1种选择…
- 假设当前位置是index, 需要把其他位置的元素放到index上, 则可以将该元素和index位置上的元素交换. 这样原来index位置上的元素可以作为下一轮递归函数的index的候选之一
- 在一次循环中,交换完元素,调用递归函数,最后还需要再交换刚才的两个元素,相当于复原了当前递归函数中的str,在下一轮循环中考虑该index位置上的其他可能的选项
- 处理完所有位置后, 将结果添加到al中
第三次做
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Collections;
//
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> res = new ArrayList<>();
if(str==null || str.length()==0)
return res;
char[] ch = str.toCharArray();
Core(res, ch, 0);
Collections.sort(res);
return res;
}
public void Core(ArrayList<String> res, char[] ch, int index){
if(index==ch.length){
res.add(String.valueOf(ch));
return;
}
HashSet<Character> hs = new HashSet<>();
for(int i=index; i<ch.length; i++){
if(hs.contains(ch[i]))
continue;
hs.add(ch[i]);
//为index位置选择字符
swap(ch, i, index);
//递归处理剩下的位置
Core(res, ch, index+1);
//恢复
swap(ch, i, index);
}
}
public void swap(char[] ch, int i, int j){
char temp = ch[i];
ch[i] = ch[j];
ch[j] = temp;
}
}
第二遍做,精简了一下代码。 碰到一个问题:char[]转成String
- base case中将char[]转成String不能用Arrays.toString(ch). 可以用new String(ch)或者String.valueOf(ch)
import java.util.ArrayList;
import java.util.Collections;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> res = new ArrayList<>();
if(str==null || str.length()==0)
return res;
char[] ch = str.toCharArray();
Core(res,ch,0);
Collections.sort(res);
return res;
}
public void Core(ArrayList<String> res, char[] ch, int i){
//base case
if(i==ch.length && !res.contains(String.valueOf(ch))){
res.add(String.valueOf(ch));
return;
}
//
//for循环的作用:尝试ch[i]所有可能的选择
for(int k=i; k < ch.length; k++){
swap(ch,i,k);
Core(res, ch, i+1);
swap(ch,i,k);
}
}
public void swap(char[] ch, int i, int j){
char temp = ch[i];
ch[i] = ch[j];
ch[j] = temp;
}
}
第二遍做,思考过程比较慢; 避免重复的代码占用了较多额外空间,可以将最终的结果作为判断的基本单元,例如将最终结果abc加入HashSet,再次碰到abc时就不加入res了
- 暂时没有改成动态规划版本
- 关键的地方不仅仅是两次交换,还有就是当前位置的可选元素是从i到末尾,而不是0到末尾,因为i之前的位置的元素已经选好了,剩下可用的元素就是i到末尾的了
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Collections;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> res = new ArrayList<>();
if(str==null||str.length()==0)
return res;
StringBuilder sb = new StringBuilder();
char[] ch = str.toCharArray();
//可以先对字符数组排序,可能就不用对结果进行排序了, 试了试,并不行
//Arrays.sort(ch);
Core(res, ch, 0, sb);
Collections.sort(res);
return res;
}
public void Core(ArrayList<String> res, char[] ch, int i, StringBuilder sb){
//base case
if(i==ch.length){
res.add(sb.toString());
return;
}
//
HashSet<Character> hs = new HashSet<>();
for(int k=i; k<ch.length; k++){
//重复的元素不能在同一位置出现
if(hs.contains(ch[k]))
continue;
hs.add(ch[k]);
//当前位置用哪个字符,就把当前位置的字符和相应位置的字符交换,这样就不用单独开辟空间记录可用的char //
swap(ch, i, k);
sb.append(ch[i]);
Core(res,ch,i+1,sb);
//恢复现场
sb.deleteCharAt(sb.length()-1);//和昨天做的一道题很像
swap(ch,i,k);
}
}
public void swap(char[] ch, int i, int j){
char temp = ch[i];
ch[i] = ch[j];
ch[j] = temp;
}
}
import java.util.ArrayList;
import java.util.HashSet;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> al = new ArrayList<String>();
if(str.length() == 0)
return al;
char[] chArr = str.toCharArray();
HashSet<String> hs = new HashSet<String>();
PermutationCore(chArr, 0, al, hs);
bubble(al);
return al;
}
public void PermutationCore(char[] chArr, int index, ArrayList<String> al, HashSet<String> hs){
if(index == chArr.length && hs.add(String.valueOf(chArr)))
al.add(String.valueOf(chArr));
if(index < chArr.length){
for(int i=index; i < chArr.length; i++){
swap(chArr, index, i);
PermutationCore(chArr, index+1, al, hs);
swap(chArr, index, i);
}
}
}
public void swap(char[] chArr, int index, int i){
char temp = chArr[index];
chArr[index] = chArr[i];
chArr[i] = temp;
}
public void bubble(ArrayList<String> al){
for(int i=0; i<al.size(); i++){
for(int j=0; j<al.size()-i-1; j++){
if(al.get(j).compareTo(al.get(j+1)) > 0){
String temp = al.get(j);
al.set(j, al.get(j+1));
al.set(j+1, temp);
}
}
}
}
}
稍稍精简了下代码
- 使用了Collections.sort()
- 使用了al.contains()
import java.util.ArrayList;
import java.util.Collections;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> al = new ArrayList<String>();
if(str.length() == 0)
return al;
char[] chArr = str.toCharArray();
PermutationCore(chArr, 0, al);
Collections.sort(al);
return al;
}
public void PermutationCore(char[] chArr, int index, ArrayList<String> al){
if(index == chArr.length && !al.contains(String.valueOf(chArr)))
al.add(String.valueOf(chArr));
if(index < chArr.length){
for(int i=index; i < chArr.length; i++){
swap(chArr, index, i);
PermutationCore(chArr, index+1, al);
swap(chArr, index, i);
}
}
}
public void swap(char[] chArr, int index, int i){
char temp = chArr[index];
chArr[index] = chArr[i];
chArr[i] = temp;
}
}