剑指offer之字符串字典序全排列

最近在牛客网上刷题,遇到一道剑指offer上的编程题还蛮有意思的,所以把它记录下来与大家分享,原题如下:

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

在网上查了以后发现这是一种特定的算法可以解决的问题,该算法称之为字典序全排列算法:

非递归算法

这种算法被用在了C++的STL库中。对给定的字符集中的字符规定了一个先后关系,在此基础上规定两个全排列的先后是从左到右逐个比较对应的字符的先后。

一个全排列可看做一个字符串,字符串可有前缀、后缀。生成给定全排列的下一个排列.所谓一个的下一个就是这一个与下一个之间没有其他的。这就要求这一个与下一个有尽可能长的共同前缀,也即变化限制在尽可能短的后缀上。

要找到一个字符串的下一个应如下四个步骤:

【例】 如何得到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的下一个排列

由此思想不难想到字典序全排列算法的代码实现为:

import java.util.*;
public class Solution {

public static ArrayList<String> Permutation(String str) {   
    ArrayList<String> res = new ArrayList<>();       
    if (str != null && str.length() > 0) {          
        char[] seq = str.toCharArray();          
        Arrays.sort(seq); //排列          
        res.add(String.valueOf(seq)); //先输出一个解       
        int len = seq.length;          
        while (true) {               
            int p = len - 1, q;           
            //(1)从后向前找一个seq[p - 1] < seq[p]           
            while (p >= 1 && seq[p - 1] >= seq[p]) --p;    
            if (p == 0) break; //已经是“最小”的排列,退出      
            //(2)从p向后找最后一个比seq[p]大的数             
            q = p; --p;               
            while (q < len && seq[q] > seq[p]) q++;       
            --q;                
            //(3)交换这两个位置上的值              
            swap(seq, q, p);           
            //(4)将p之后的序列倒序排列           
            reverse(seq, p + 1);              
            res.add(String.valueOf(seq));        
        }        
    }          
    return res;    
}  
   /*  
public static void reverse(char[] seq, int start) {    
    int len;     
    if(seq == null || (len = seq.length) <= start)       
        return;     
    for (int i = 0; i < ((len  - start) >> 1); i++) { 
        int p = start + i, q = len -1 - i;     
        if (p != q)             
            swap(seq, p, q);      
    }    
}     
      */
   
    public static void reverse(char[] seq, int start){
		 int len = seq.length-1;     
    if(seq == null || len  <= start)       
        return; 
    for(;start<len;start++,len--)
        swap(seq,start,len);
        
    }
    
  
    
public static void swap(char[] cs, int i, int j) {    
    char temp = cs[i];  
    cs[i] = cs[j];        
    cs[j] = temp;   
} 


public static void main(String[] args){
	String str = "abcd";
	ArrayList<String> array = Permutation(str);
	for(int i=0;i<array.size();i++){
		System.out.println(array.get(i));
	}
}
       
    
}

输出为:

abcde
abced
abdce
abdec
abecd
abedc
acbde
acbed
acdbe
acdeb
acebd
acedb
adbce
adbec
adcbe
adceb
adebc
adecb
aebcd
aebdc
aecbd
aecdb
aedbc
aedcb
bacde
baced
badce
badec
baecd
baedc
bcade
bcaed
bcdae
bcdea
bcead
bceda
bdace
bdaec
bdcae
bdcea
bdeac
bdeca
beacd
beadc
becad
becda
bedac
bedca
cabde
cabed
cadbe
cadeb
caebd
caedb
cbade
cbaed
cbdae
cbdea
cbead
cbeda
cdabe
cdaeb
cdbae
cdbea
cdeab
cdeba
ceabd
ceadb
cebad
cebda
cedab
cedba
dabce
dabec
dacbe
daceb
daebc
daecb
dbace
dbaec
dbcae
dbcea
dbeac
dbeca
dcabe
dcaeb
dcbae
dcbea
dceab
dceba
deabc
deacb
debac
debca
decab
decba
eabcd
eabdc
eacbd
eacdb
eadbc
eadcb
ebacd
ebadc
ebcad
ebcda
ebdac
ebdca
ecabd
ecadb
ecbad
ecbda
ecdab
ecdba
edabc
edacb
edbac
edbca
edcab
edcba



递归法

分别将每个位置交换到最前面位,之后全排列剩下的位。
import java.util.*;
public class zidianxu_digui {
	public ArrayList<String> Permutation(String str) {   
		ArrayList<String> res = new ArrayList<>();     
		if (str != null && str.length() > 0) {        
			PermutationHelper(str.toCharArray(), 0, res);      
			Collections.sort(res);       
			}         
		return res; 
			}     
	private static void PermutationHelper(char[] cs, int i, ArrayList<String> list) {  
		if(i == cs.length - 1) { //解空间的一个叶节点       
			list.add(String.valueOf(cs)); //找到一个解         
			} else {           
				for(int j = i; j < cs.length; ++j) {     
					if(j == i || cs[j] != cs[i]) {     
						swap(cs, i, j);       
						PermutationHelper(cs, i + 1, list);       
						swap(cs, i, j); //复位        
						}          
					}      
				}   
		} 
	
	public static void swap(char[] cs, int i, int j) { 
		char temp = cs[i];      
		cs[i] = cs[j];       
		cs[j] = temp;     } 


	public static void main(String[] args){
		String str = "abcde";
		zidianxu_digui text = new zidianxu_digui();
		ArrayList<String> array = text.Permutation(str);
		for(int i=0;i<array.size();i++){
			System.out.println(array.get(i));
		}
	}
}



### 蓝桥杯竞赛中回文字符串题目解题思路 #### 寻找最长回文子串 对于蓝桥杯1225——最长回文子串问题,采用暴力枚举的方法可以解决问题。通过双层循环遍历所有可能的子串组合,并逐一验证其是否构成回文结构[^1]。 ```python def is_palindrome(s): return s == s[::-1] def longest_palindromic_substring(text): max_length = 0 start_index = 0 for i in range(len(text)): for j in range(i, len(text)): substring = text[i:j+1] if is_palindrome(substring) and len(substring) > max_length: max_length = len(substring) start_index = i return text[start_index:start_index + max_length] ``` 此方法虽然直观易懂,但在处理较长字符串时效率较低,时间复杂度达到O(n^3),其中n代表字符串长度。 #### 构造最长回文串并考虑字典序最小化 针对特定条件下的构建最长回文串需求,如2023华为OD机试题所示,除了关注如何形成最大长度外还需兼顾字符排列顺序以满足字典序的要求[^2]。这里介绍一种基于贪心算法的思想来解决这个问题: - 统计各个字符出现次数; - 尽量多放置成对存在的相同字符于两侧; - 如果存在奇数次出现的字符,则可将其置于中心位置; - 对剩余未配对单个字符按升序加入到中间部分之前或之后,从而保证整体形成的回文串具有较小的字典序。 实现上述逻辑如下: ```python from collections import Counter def construct_longest_palindrome(s): char_counts = Counter(s.lower()) # 不区分大小写统计频率 palindrome_parts = [] center_char = '' for char, count in sorted(char_counts.items()): pairs_count = count // 2 * 2 if pairs_count > 0: palindrome_parts.append((char * (pairs_count // 2))) if count % 2 != 0 and not center_char: center_char = char result = ''.join(palindrome_parts) + center_char + ''.join(reversed(palindrome_parts)) return result.upper() # 返回大写的最终结果 ``` 该方案能够有效地找到符合条件的最大长度且回文串的同时保持尽可能低的字典序。 #### 计算不同类型的回文数量 当涉及到计算某个范围内不同类型(比如普通型、特殊模式等)的回文数目时,可以通过动态规划或者滑动窗口技术来进行优化求解。例如,在Offer第20题里提到的任务就是典型的应用场景之一[^3]。 为了提高性能表现,建议预先设定好边界情况以及初始化必要的辅助数组用于存储临时状态信息;接着逐步扩大考察范围直至覆盖整个待分析序列为止。 综上所述,面对不同的回文类编程挑战,可以根据具体要求选用合适的数据结构与算法策略加以应对。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值