#数据结构与算法学习笔记#剑指Offer26:字符串的排列升级版 + 去重全排列问题3种解法 + 字典序算法 + 测试用例(Java、C/C++)

版权声明:本文为博主NJU_ChopinXBP原创文章,发表于CSDN,仅供交流学习使用,转载请私信或评论联系,未经博主允许不得转载。感谢您的评论与点赞。 https://blog.csdn.net/qq_20304723/article/details/82348807

2018.9.3

这道题比书上的题目还要复杂一些,解法总体思路差不多。花的时间比较长,因为是第一次接触全排列问题,初步实现+调试+改进花了不少时间,做完参考其他优秀的解法,又花时间进行了理解和学习,同时又从网上了解了其他很多有意思的全排列问题的解决方法,算法真是博大精深啊。题目主要复杂在哪里呢?1.字符重复,2.字典排序。因此这题可以简称为去重字典序全排列问题。

顾名思义,全排列就是一串字符的所有有序组合。生成全排列的算法有很多,这里给出三种比较常用的实现方法。解题思路:

方法一:交换递归法

将每次递归分为序列的第一个结点与后面其他结点,每次递归将第一个结点依次与其他结点交换,交换至末尾时对本次递归结果进行打印,每次执行算法后需要对交换进行复位以便进行下一次交换。

例如对于序列abc,第一层第一次交换为a与a交换,第二层第一次为b与b交换,第三层第一次为c与c交换,交换结束,打印abc;倒退一层,第二层第二次为b与c交换,第三层第一次为b与b交换,交换结束,打印acb;倒退两层,第一层第二次为a与b交换……最后打印出来的序列为abc acb bac bca cba cab。这就是原书题目的标准解答,也是一次全排列的实现过程了。

全排列是打印出来了,可是注意到后两次打印并不满足字典序,事实上因为是隔位交换,也不可能保证字典序。同时也无法保证字符重复造成的排列重复。因此还需要进行改进。

改进方案1:

对于字典序:可在排列之后利用Collections.sort对容器进行排序,字符串默认字典序。

对于字符重复:可在每次打印前对容器用contains进行字符重复的判断,如重复则不打印。

改进方案2:

先用TreeSet临时保存,可以保证排序和去重。(TreeSet可以理解成数组实现的二叉搜索树的容器,可以直接用addAll添加到ArrayList中)

方法二:字典序排列算法

一个全排列可看做一个字符串,字符串可有前缀、后缀。 生成给定全排列的下一个排列。所谓一个的下一个就是这一个与下一个之间没有其他的。这就要求这一个与下一个有尽可能长的共同前缀,也即变化限制在尽可能短的后缀上。例如839647521是1—9的排列。1—9的排列最前面的是123456789,最后面的987654321,从右向左扫描若都是增的,就到了987654321,也就没有下一个了。否则找出第一次出现下降的位置。

字典序排列算法分步如下,例如:

1.先对字符数组进行初始排序并打印初始序列

2.从尾部向前找第一个由大变小的数字(较大者)的位置lIndex

3.从i开始向后找到第一个小于lIndex-1的值的位置rIndex

4.将lIndex-1与rIndex-1位置对应的值进行交换,也即将从后往前第一个由大变小的数字(较小者)与从前往后最后一个大于前者的值

5.将lIndex-1后的序列进行翻转

6.打印这个序列,继续进行循环

如果直接理解起来晦涩难懂,可以参考一个例子——如何得到346987521的下一个 :

1.从尾部往前找第一个P(i-1) < P(i)的位置 3 4 6 <- 9 <- 8 <- 7 <-5 <- 2 <- 1 最终找到6是第一个变小的数字,记录下6的位置i-1

2.从i位置往后找到最后一个大于6的数 3 4 6 -> 9 -> 8 -> 7 5 2 1 最终找到7的位置,记录位置为m

3.交换位置i-1和m的值 3 4 7 9 8 6 5 2 1

4.倒序i位置后的所有数据 3 4 7 1 2 5 6 8 9

则347125689为346987521的下一个排列。

方法三:深度遍历搜索(回溯法)

对于一个假想的图来说,这是一次DFS;对于一个初始的字符串来说,不断遍历回溯的算法。每次遍历到尾进行回溯,将可能的所有排序进行存储。同方法一的改进方法,可以用TreeSet进行临时存储保证去重和字典序,下面的参考实现中用了HashSet,可以去重,最后还是要靠Collections.sort排序。

对于DFS不够熟悉可以参考之前写过的——#数据结构与算法学习笔记#PTA18:图(邻接表)+DFS(深度优先搜索)+BFS(广度优先搜索)(C/C++)


题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。(长度不超过9(可能有字符重复),字符只包括大小写字母。)

例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。


Java实现(三种方法):

/**
 * 
 * @author ChopinXBP
 * 输入一个字符串,按字典序打印出该字符串中字符的所有排列。(长度不超过9(可能有字符重复),字符只包括大小写字母。)
 * 例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
 * 
 */

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;


public class StringPermutation_26 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//ArrayList<String> input = Permutation(new String("bbacC"));
		//ArrayList<String> input = Permutation(new String("aa"));
		//ArrayList<String> input = Permutation(new String("a"));
		//ArrayList<String> input = Permutation(new String("abbc"));
		ArrayList<String> input = Permutation(new String("abc"));
		for(int i = 0; i < input.size(); i++){
			System.out.print(input.get(i) + " ");
		}
	}
	
	////////////////////////////方法1:交换递归法/////////////////////////////////
	
    public static ArrayList<String> Permutation(String str) {
    	//可以先用TreeSet临时保存,保证排序和去重
    	ArrayList<String> result = new ArrayList<>();		//对连续测试用例不能用全局变量
    	
    	if(str == null || str.equals("")) return result;
    	if(str.length() == 1){
    		result.add(str);
    		return result;
    	}
    	
    	StringBuilder newstr = new StringBuilder(str);
    	//回溯法对字符串排列进行遍历,最后排序
    	Solution(newstr, 0,result);
    	Collections.sort(result);
    	
    	return result;
    }


    //回溯法求全排列
    public static void Solution(StringBuilder newstr, int begin, ArrayList<String> result){
    	if(begin == newstr.length()){
    		//递归从最后两个字母交换开始打印
    		//去重方法:打印前用contains进行判断
    		if(!result.contains(newstr.toString())) result.add(newstr.toString());
    	}
    	
		for (int i = begin; i < newstr.length(); i++) {
			// 将begin与i对应字符进行交换
			char tmp = newstr.charAt(i);
			newstr.setCharAt(i, newstr.charAt(begin));
			newstr.setCharAt(begin, tmp);

			// 递归交换,从后往前
			Solution(newstr, begin + 1, result);

			// 复位回交换前
			tmp = newstr.charAt(i);
			newstr.setCharAt(i, newstr.charAt(begin));
			newstr.setCharAt(begin, tmp);

		}
    }

    
    ////////////////////////////方法2:字典序排列算法/////////////////////////////////
    //1.先对字符数组进行初始排序并打印初始序列
    //2.从尾部向前找第一个由大变小的数字(较大者)的位置lIndex
    //3.从i开始向后找到第一个小于lIndex-1的值的位置rIndex
    //4.将lIndex-1与rIndex-1位置对应的值进行交换,也即将从后往前第一个由大变小的数字(较小者)与从前往后最后一个大于前者的值
    //5.将lIndex-1后的序列进行翻转
    //6.打印这个序列,继续进行循环    

	public ArrayList<String> Permutation2(String str) {
		ArrayList<String> list = new ArrayList<String>();
		if (str == null || str.length() == 0) {
			return list;
		}
		char[] chars = str.toCharArray();
		Arrays.sort(chars);					//先对字符数组进行初始排序
		list.add(String.valueOf(chars));
		int len = chars.length;
		while (true) {
			int lIndex = len - 1;
			int rIndex;
			//从尾部向前找第一个由大变小的数字(较大者)的位置lIndex
			while (lIndex >= 1 && chars[lIndex - 1] >= chars[lIndex]) {
				lIndex--;
			}
			if (lIndex == 0)
				break;
			rIndex = lIndex;
			//从i开始向后找到第一个小于lIndex-1的值的位置rIndex
			while (rIndex < len && chars[rIndex] > chars[lIndex - 1]) {
				rIndex++;
			}
			//将lIndex-1与rIndex-1位置对应的值进行交换,也即将从后往前第一个由大变小的数字(较小者)与从前往后最后一个大于前者的值
			swap(chars, lIndex - 1, rIndex - 1);
			//将lIndex-1后的序列进行翻转
			reverse(chars, lIndex);

			list.add(String.valueOf(chars));
		}

		return list;
	}

	private void reverse(char[] chars, int k) {
		if (chars == null || chars.length <= k)
			return;
		int len = chars.length;
		for (int i = 0; i < (len - k) / 2; i++) {
			int m = k + i;
			int n = len - 1 - i;
			if (m <= n) {
				swap(chars, m, n);
			}
		}

	}

	private void swap(char[] cs, int i, int j) {
		char temp = cs[i];
		cs[i] = cs[j];
		cs[j] = temp;
	}
	
	

	////////////////////////////方法3:深度遍历搜索(回溯法)/////////////////////////////////
	private char[] seqs;
	private Integer[] book;
	// 用于结果去重
	private HashSet<String> result3 = new HashSet<String>();

	public ArrayList<String> Permutation3(String str) {
		ArrayList<String> arrange = new ArrayList<String>();
		if (str == null || str.isEmpty())
			return arrange;
		char[] strs = str.toCharArray();
		seqs = new char[strs.length];
		book = new Integer[strs.length];
		for (int i = 0; i < book.length; i++) {
			book[i] = 0;
		}
		dfs(strs, 0);
		arrange.addAll(result3);
		Collections.sort(arrange);
		return arrange;
	}

	private void dfs(char[] arrs, int step) {
		// 走完所有可能 记录排列
		if (arrs.length == step) {
			String str = "";
			for (int i = 0; i < seqs.length; i++) {
				str += seqs[i];
			}
			result3.add(str);
			return; // 返回上一步
		}
		// 遍历整个序列,尝试每一种可能
		for (int i = 0; i < arrs.length; i++) {
			// 是否走过
			if (book[i] == 0) {
				seqs[step] = arrs[i];
				book[i] = 1;
				// 下一步
				dfs(arrs, step + 1);
				// 走完最后一步 后退一步
				book[i] = 0;
			}
		}
	}
	
	
}

C++实现参考(方法1):

class Solution {
public:
    vector<string> Permutation(string str) {
         
        vector<string>array;
         
        Permutation(array,str,0);
        sort(array.begin(),array.end());
        return array;
    }
    void Permutation(vector<string>&array,string str,int begin)
        {
        if(begin==str.size()-1)//每一轮递归到最后一个元素是压入
        {
            array.push_back(str);
        }
        for(int i=begin;i<str.size();++i)
            {
            if(i!=begin&&str[i]==str[begin])continue;
            swap(str[begin],str[i]);
            Permutation(array,str,begin+1);
            swap(str[begin],str[i]);//这一步很重要  例如 abc 第一次a,b交换  如果不二次交换 第二次再循环将b,c交换而不是a,c交换
               
        }
    }
};

C++实现参考(C++STL求下一个全排列的函数next_permutation):

class Solution {
public:
    vector<string> Permutation(string str) {
        vector<string> vec;
        if(str.size()==0) return vec;
        sort(str.begin(),str.end());
        do{
            vec.push_back(str);
        }while(next_permutation(str.begin(),str.end()));
        return vec;
    }
};

// ====================测试代码====================
void Test(char* pStr)
{
    if(pStr == NULL)
        printf("Test for NULL begins:\n");
    else
        printf("Test for %s begins:\n", pStr);

    Permutation(pStr);

    printf("\n");
}

int _tmain(int argc, _TCHAR* argv[])
{
    Test(NULL);

    char string1[] = "";
    Test(string1);

    char string2[] = "a";
    Test(string2);

    char string3[] = "aa";
    Test(string3);

    char string4[] = "abc";
    Test(string4);

    char string5[] = "abbc";
    Test(string5);

    char string6[] = "abbcC";
    Test(string6);

    return 0;
}

#Coding一小时,Copying一秒钟。留个言点个赞呗,谢谢你#

猜你喜欢

转载自blog.csdn.net/qq_20304723/article/details/82348807
今日推荐