在编程面试中(虽然我没有面试经验),一个常见的任务就是将字符串或整数列出所有可能的排列。
有没有一个实现示例和解决此类问题的逻辑呢?
我看过一些代码片段,但它们没有很好的注释/解释,因此很难跟踪。
在编程面试中(虽然我没有面试经验),一个常见的任务就是将字符串或整数列出所有可能的排列。
有没有一个实现示例和解决此类问题的逻辑呢?
我看过一些代码片段,但它们没有很好的注释/解释,因此很难跟踪。
首先:当然会有 递归 的味道!
既然你想知道原理,我尽力用人类语言解释了它。我认为大多数情况下递归非常简单,你只需要掌握两个步骤:
用人类语言说:
简而言之:
- 1个元素的排列是1个元素。
- 元素集合的排列是每个元素与其余元素的每个排列连接在一起组成的列表。
例如:
如果集合只有一个元素 ->
返回它本身。
perm(a) -> a如果集合有两个字符:对于其中的每个元素:返回该元素,并连接其余元素的排列,如下所示:
perm(ab) ->
a + perm(b) -> ab
b + perm(a) -> ba
继续:对于集合中的每个字符:返回与其余集合的排列连接在一起的字符。
perm(abc) ->
a + perm(bc) --> abc, acb
b + perm(ac) --> bac, bca
c + perm(ab) --> cab, cba
perm(abc...z) -->
a + perm(...), b + perm(....)
....
我在http://www.programmersheaven.com/mb/Algorithms/369713/369713/permutation-algorithm-help/上找到了伪代码:
makePermutations(permutation) {
if (length permutation < required length) {
for (i = min digit to max digit) {
if (i not in permutation) {
makePermutations(permutation+i)
}
}
}
else {
add permutation to list
}
}
C#
以下内容来自 http://radio.weblogs.com/0111551/stories/2002/10/14/permutations.html,这是一个比较详细的例子(由于标记为C#),函数接受一串字符并输出该字符串所有可能的排列组合。例如,如果提供了“ABC”,则应该输出:
ABC、ACB、BAC、BCA、CAB和CBA。
代码:
class Program
{
private static void Swap(ref char a, ref char b)
{
if (a == b) return;
var temp = a;
a = b;
b = temp;
}
public static void GetPer(char[] list)
{
int x = list.Length - 1;
GetPer(list, 0, x);
}
private static void GetPer(char[] list, int k, int m)
{
if (k == m)
{
Console.Write(list);
}
else
for (int i = k; i <= m; i++)
{
Swap(ref list[k], ref list[i]);
GetPer(list, k + 1, m);
Swap(ref list[k], ref list[i]);
}
}
static void Main()
{
string str = "sagiv";
char[] arr = str.ToCharArray();
GetPer(arr);
}
}
Swap(ref list[k], ref list[i]);
)是不必要的。 - dance2die(a[x], a[y]) = (a[y], a[x]);
- Darrenstatic IEnumerable<IEnumerable<T>>
GetPermutations<T>(IEnumerable<T> list, int length)
{
if (length == 1) return list.Select(t => new T[] { t });
return GetPermutations(list, length - 1)
.SelectMany(t => list.Where(e => !t.Contains(e)),
(t1, t2) => t1.Concat(new T[] { t2 }));
}
例子:
IEnumerable<IEnumerable<int>> result =
GetPermutations(Enumerable.Range(1, 3), 3);
输出 - 一个整数列表的列表:
{1,2,3} {1,3,2} {2,1,3} {2,3,1} {3,1,2} {3,2,1}
由于该功能使用了LINQ,因此需要.NET 3.5或更高版本。
const string s = "HALLOWEEN";
var result = GetPermutations(Enumerable.Range(0, s.Length), s.Length).Select(t => t.Select(i => s[i]));
- Pengyang我找到了解决方案。它是用Java编写的,但我已将其转换为C#。希望它能帮助你。
以下是C#代码:
static void Main(string[] args)
{
string str = "ABC";
char[] charArry = str.ToCharArray();
Permute(charArry, 0, 2);
Console.ReadKey();
}
static void Permute(char[] arry, int i, int n)
{
int j;
if (i==n)
Console.WriteLine(arry);
else
{
for(j = i; j <=n; j++)
{
Swap(ref arry[i],ref arry[j]);
Permute(arry,i+1,n);
Swap(ref arry[i], ref arry[j]); //backtrack
}
}
}
static void Swap(ref char a, ref char b)
{
char tmp;
tmp = a;
a=b;
b = tmp;
}
递归并不是必需的,这里提供了关于该解决方案的良好信息。
var values1 = new[] { 1, 2, 3, 4, 5 };
foreach (var permutation in values1.GetPermutations())
{
Console.WriteLine(string.Join(", ", permutation));
}
var values2 = new[] { 'a', 'b', 'c', 'd', 'e' };
foreach (var permutation in values2.GetPermutations())
{
Console.WriteLine(string.Join(", ", permutation));
}
Console.ReadLine();
我已经使用这个算法多年了,它的时间和空间复杂度都是O(N),用于计算每个排列。
public static class SomeExtensions
{
public static IEnumerable<IEnumerable<T>> GetPermutations<T>(this IEnumerable<T> enumerable)
{
var array = enumerable as T[] ?? enumerable.ToArray();
var factorials = Enumerable.Range(0, array.Length + 1)
.Select(Factorial)
.ToArray();
for (var i = 0L; i < factorials[array.Length]; i++)
{
var sequence = GenerateSequence(i, array.Length - 1, factorials);
yield return GeneratePermutation(array, sequence);
}
}
private static IEnumerable<T> GeneratePermutation<T>(T[] array, IReadOnlyList<int> sequence)
{
var clone = (T[]) array.Clone();
for (int i = 0; i < clone.Length - 1; i++)
{
Swap(ref clone[i], ref clone[i + sequence[i]]);
}
return clone;
}
private static int[] GenerateSequence(long number, int size, IReadOnlyList<long> factorials)
{
var sequence = new int[size];
for (var j = 0; j < sequence.Length; j++)
{
var facto = factorials[sequence.Length - j];
sequence[j] = (int)(number / facto);
number = (int)(number % facto);
}
return sequence;
}
static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
private static long Factorial(int n)
{
long result = n;
for (int i = 1; i < n; i++)
{
result = result * i;
}
return result;
}
}
O(N-1)
,交换的时间复杂度为O(N)
,总时间复杂度为O(N)
。我仍在生产环境中使用这个算法,但进行了重构,只生成一个排列,如:GetPermutation(i)
,其中0 <= i <= N!-1
。如果有更好性能的算法,我会很高兴使用它,所以请随意提供更好的参考,大多数替代方案在内存中使用O(N!)
,所以您也可以检查一下。 - Najeraclass Program
{
public static void Main(string[] args)
{
Permutation("abc");
}
static void Permutation(string rest, string prefix = "")
{
if (string.IsNullOrEmpty(rest)) Console.WriteLine(prefix);
// Each letter has a chance to be permutated
for (int i = 0; i < rest.Length; i++)
{
Permutation(rest.Remove(i, 1), prefix + rest[i]);
}
}
}
这是一个略有修改的 C# 版本,可以在任何类型的数组中生成所需的排列。
// USAGE: create an array of any type, and call Permutations()
var vals = new[] {"a", "bb", "ccc"};
foreach (var v in Permutations(vals))
Console.WriteLine(string.Join(",", v)); // Print values separated by comma
public static IEnumerable<T[]> Permutations<T>(T[] values, int fromInd = 0)
{
if (fromInd + 1 == values.Length)
yield return values;
else
{
foreach (var v in Permutations(values, fromInd + 1))
yield return v;
for (var i = fromInd + 1; i < values.Length; i++)
{
SwapValues(values, fromInd, i);
foreach (var v in Permutations(values, fromInd + 1))
yield return v;
SwapValues(values, fromInd, i);
}
}
}
private static void SwapValues<T>(T[] values, int pos1, int pos2)
{
if (pos1 != pos2)
{
T tmp = values[pos1];
values[pos1] = values[pos2];
values[pos2] = tmp;
}
}
Permutations(vals).ToArray()
的事情,那么你最终会得到 N 个对同一数组的引用。如果你想要能够存储结果,你必须手动创建一个副本。例如:Permutations(values).Select(v => (T[])v.Clone())
。 - Pharap首先,集合有排列,而不是字符串或整数,因此我假设您的意思是“字符串中的字符集合”。
请注意,大小为n的集合具有n!个n排列。
以下伪代码(来自维基百科),使用k = 1…n!将给出所有排列:
function permutation(k, s) {
for j = 2 to length(s) {
swap s[(k mod j) + 1] with s[j]; // note that our array is indexed starting at 1
k := k / j; // integer division cuts off the remainder
}
return s;
}
def permutation(k, s):
r = s[:]
for j in range(2, len(s)+1):
r[j-1], r[k%j] = r[k%j], r[j-1]
k = k/j+1
return r
k := k / j;
是什么意思。 - Shawn Kovac我认为FBryant87的方法很简单易懂。然而,像其他“解决方案”一样,它无法提供包含相同数字的整数的所有排列方式,例如656123。请看下面这行代码:
var tail = chars.Except(new List<char>(){c});
使用Except将导致所有出现的内容都被删除,例如当c = 6时,两个数字被删除,我们留下了5123。由于我尝试的解决方案都没有解决这个问题,所以我决定基于FBryant87的代码来尝试解决它。以下是我的解决方案:
private static List<string> FindPermutations(string set)
{
var output = new List<string>();
if (set.Length == 1)
{
output.Add(set);
}
else
{
foreach (var c in set)
{
// Remove one occurrence of the char (not all)
var tail = set.Remove(set.IndexOf(c), 1);
foreach (var tailPerms in FindPermutations(tail))
{
output.Add(c + tailPerms);
}
}
}
return output;
}
void Main()
{
string word = "abc";
WordPermuatation("",word);
}
void WordPermuatation(string prefix, string word)
{
int n = word.Length;
if (n == 0) { Console.WriteLine(prefix); }
else
{
for (int i = 0; i < n; i++)
{
WordPermuatation(prefix + word[i],word.Substring(0, i) + word.Substring(i + 1, n - (i+1)));
}
}
}
这里是一个纯函数式的F#实现:
let factorial i =
let rec fact n x =
match n with
| 0 -> 1
| 1 -> x
| _ -> fact (n-1) (x*n)
fact i 1
let swap (arr:'a array) i j = [| for k in 0..(arr.Length-1) -> if k = i then arr.[j] elif k = j then arr.[i] else arr.[k] |]
let rec permutation (k:int,j:int) (r:'a array) =
if j = (r.Length + 1) then r
else permutation (k/j+1, j+1) (swap r (j-1) (k%j))
let permutations (source:'a array) = seq { for k = 0 to (source |> Array.length |> factorial) - 1 do yield permutation (k,2) source }
通过改变交换(swap)以利用CLR数组的可变性,可以大大提高性能,但是在某些情况下,与源数组相关的实现在线程安全方面可能是值得考虑的。 此外,对于具有超过16个元素的数组,int必须被替换为具有更高/任意精度的类型,因为17的阶乘会导致int32溢出。