求字符串的全排列git
設計一個算法,輸出一個字符串字符的全排列。
好比,String = "abc"
輸出是"abc","bac","cab","bca","cba","acb"github
從集合依次選出每個元素,做爲排列的第一個元素,而後對剩餘的元素進行全排列,如此遞歸處理;算法
好比:首先我要打印abc的全排列,就是第一步把a 和bc交換(獲得bac,cab),這須要一個for循環,循環裏面有一個swap,交換以後就至關於無論第一步了,進入下一步遞歸,因此跟一個遞歸函數, 完成遞歸以後把交換的換回來,變成原來的字串
遞歸方法1(July 方法):數組
abc 爲例子: 1. 固定a, 求後面bc的全排列: abc, acb。 求完後,a 和 b交換; 獲得bac,開始第二輪 2. 固定b, 求後面ac的全排列: bac, bca。 求完後,b 和 c交換; 獲得cab,開始第三輪 3. 固定c, 求後面ba的全排列: cab, cba 即遞歸樹: str: a b c ab ac ba bc ca cb result: abc acb bac bca cab cba
public static void Permutation(char[] s, int from, int to) { if(to<=1) return; if(from == to){ System.out.println(s); } else{ for(int i=from;i<=to;i++){ swap(s,i,from); Permutation(s,from+1,to); swap(s,from,i); } } } public static void swap(char[] s, int i, int j) { char temp = s[i]; s[i] = s[j]; s[j] = temp; }
遞歸方法2:
與上面算法區別:
本算法須要一個額外的存儲空間存放結果(buffer),固定第一個位置是哪一個元素的時候,是經過一個循環,而後看原始字符串上,每個位置是什麼元素。July的作法沒有結果的buffer,都是在一個字符串上進行的操做。第一個swap的做用就是,依次拿起始字符和後面的每個字符交換,這樣就能遍歷第一個位置上的全部可能字符app
n個數的全排列,一共有n!
種狀況. (n個位置,第一個位置有n種,當第一個位置固定下來以後,第二個位置有n-1種狀況...)測試
全排列的過程:spa
選擇第一個字符設計
得到第一個字符固定下來以後的全部的全排列code
選擇第二個字符
得到第一+ 二個字符固定下來以後的全部的全排列
從這個過程可見,這是一個遞歸的過程。
還有一點須要注意是:
以前遞歸過程選擇的字符,下一次不能再被選: 第一個位置選了a, 其餘位置就不能選a了
解決方法是1. 掃描以前選擇的字符 或者 2.建立一個與字符串等長的boolean數組,標記該位置對於的字符是否已經選擇。若選擇,則標記true; 若未選擇,則標記false.
public class Permutation { public static void permute(String str){ int length = str.length(); boolean[] used = new boolean[length]; StringBuffer output = new StringBuffer(length); permutation(str,length,output,used,0); } // @para // position : 下一個放置的元素位置,因此調入時候是0 // static void permutation(String str, int length, StringBuffer output, boolean[] used, int position){ // end of the recursion if(position == length){ System.out.println(output.toString()); return; } else{ for(int i=0;i<length;i++){ // skip already used characters if(used[i]) continue; // add fixed character to output, and mark it as used output.append(str.charAt(i)); used[i] = true; // permute over remaining characters starting at position+1 // recursion permutation(str,length,output,used,position+1); // remove fixed character from output and unmark it output.deleteCharAt(output.length()-1); used[i] = false; } } }
我的認爲這個算法不如第一個遞歸方法,由於須要額外的空間;可是兩者的時間複雜度是相同的,都是O(n!)
。
輸入三個字符 a、b、c
,則它們的組合有a
b
c
ab
ac
bc
abc
。固然咱們仍是能夠借鑑全排列的思路,利用問題分解的思路,最終用遞歸解決。不過這裏介紹一種比較巧妙的思路 —— 基於位圖。
假設原有元素n
個,最終的組合結果有2^n - 1
. 可使用2^n - 1
個位,1表示取該元素,0表示不取。 因此a
表示001
,取ab
是011。001,010,011,100,101,110,111
。對應輸出組合結果爲:a,b,ab,c,ac,bc,abc
。
所以能夠循環 1~2^n-1
(字符串長度),而後輸出對應表明的組合便可。
public static void Combination(char [] s){ if(s.length == 0){ return; } int len = s.length; int n = 1<<len; //從1循環到2^len-1 for(int i=0;i<n;i++){ StringBuffer sb = new StringBuffer(); //查看第一層循環裏面的任意一種取值當中的哪一位是1[好比ab,011], 若是是1,對應的字符就存在,打印當前組合。 for(int j=0;j<len;j++){ if( (i & (1<<j)) != 0) // 對應位上爲1,則輸出對應的字符 { sb.append(s[j]); } } System.out.print(sb + " "); } }
for(int j=0;j<len;j++){ if( (i & (1<<j)) != 0) }
j = 0, 1<<j 爲將第一位置1
j = 1, 1<<j 爲將第二位置1
j = 2, 1<<j 爲將第三位置1
Leetcode
Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
For example,
If n = 4 and k = 2, a solution is:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
基於位操做,這裏咱們主要藉助一個二進制操做 「 求最小的、比 x 大的整數 M,使得 M 與 x 的二進制表示中有相同數目的 1」,若是這個操做已知,那麼咱們能夠設置一個初始整數 bit,bit 的低位第 1~k 個二進制位爲 1,其他二進制位爲 0,bit 的二進制表示一種組合,而後調用上述操做求得下一個 bit,bit 的最大值爲:bit 從低位起第 n-k+1~n 位等於 1,其他位等於 0,即 (1<<n) - (1<<(n-k)
public static List<List<Integer>> combine(int n, int k) { if(n == 0 | k>n){ return null; } int len = n; int nbit = 1<<len; int kbit = 1<<k; int inbit = 1<<n - 1<<(n-k); List<List<Integer>> result = new ArrayList<List<Integer>>(); //從1循環到2^len-1 for(int i=kbit-1; i<= inbit; i = nextn(i)){ List<Integer> list = new ArrayList<Integer>(); for(int j=0;j<len;j++){ if( (i & (1<<j)) != 0) // 對應位j上爲1,則輸出對應的字符 { list.add(j+1); } } result.add(list); } return result; } // 返回最小的,比N大的整數M,使M與N的二進制有相同數目的1 public static int nextn(int k){ int x = k & (-k); int t = k+x; return t | ((k^t)/x)>>2; }
求整數的二進制表示中有多少個 1
應用了n&=(n-1)
能將 n 的二進制表示中的最右邊的 1 翻轉爲 0 的事實。只須要不停地執行 n&=(n-1)
,直到 n 變成 0 爲止,那麼翻轉的次數就是原來的 n 的二進制表示中 1 的個數,其代碼以下:
public int count1Bits(int n){ int count = 0; while(n!=0){ count++; n = n & (n-1); } return count; }
給定一個正整數 N,求最小的、比 N 大的正整數 M,使得 M 與 N 的二進制表示中有相同數目的 1
方法1: 簡單枚舉
從 N+1 開始枚舉,對每一個數都測試其二進制表示中的 1 的個數是否與 N 的二進制表示中 1 的個數相等,遇到第一次相等時就中止
public int GetNextN(int n){ int k = count1Bits(n); do{ n++; }while(count1Bits(n) != k); return n; }
方法2: O(1)
時間高效方法
參考
public int NextN(int n){ int x = n&(-n); int t = n + x; int ans = t | ((n^t)/x)>>2; return ans; }
想更一進步的支持我,請掃描下方的二維碼,你懂的~