list转字符串_剑指offer 38——字符串的排列
本題主要在于對回溯的理解,優化時可以結合 java 特性,以及排列的一些知識。
原題
輸入一個字符串,打印出該字符串中字符的所有排列。
你可以以任意順序返回這個字符串數組,但里面不能有重復元素。
示例:
輸入:s = "abc" 輸出:["abc","acb","bac","bca","cab","cba"]限制:
1 <= s 的長度 <= 8原題url:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof
解題
回溯
回溯算法的基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。
大家在解決經典的八皇后問題時,大多都會采用回溯進行解決。
本問題其實就是求所有字符的排列組合,針對這種問題,也可以利用回溯進行解決,但要求不能重復,因此需要進行剪枝。
比如字符串 abc ,如果讓我們求所有排列,肯定是:
從上面,你可以總結出,正常的回溯,就是先走一條路,當結束后,退回上一步繼續走,反復執行,直至退無可退,結束流程。
我們可以發現,最終是沒有可以選擇的余地,這在程序里可以理解為,運行到下一位時,不能使用之前使用過的數據,因此會涉及到字符交換。
但因為會進行回溯,所以數字可以在回溯后再換回去,從而不影響下一次的回溯。
那什么叫剪枝呢?就是要排除一些情況,針對本題,就是要排除重復的情況。
也就是在同一位置,不能出現兩次相同的字符,因為第 2 次出現時,之前肯定已經針對這種情況,所有路線都已經走過了。
因此可以聯想到使用集合,存儲當前位置出現過的字符,如果重復,就可以直接跳過。
接下來我們看看代碼:
class Solution {char[] array;List<String> result = new LinkedList<>();public String[] permutation(String s) {array = s.toCharArray();// 回溯backtrack(0);// 賦值給數組String[] resultArray = new String[result.size()];int index = 0;for (String str : result) {resultArray[index] = str;index++;}return resultArray;}private void backtrack(int index) {// 如果是最后一個位置,就可以添加進result中if (index == array.length - 1) {StringBuilder sb = new StringBuilder();for (char temp : array) {sb.append(temp);}result.add(sb.toString());return;}Set<Character> set = new HashSet<>();for (int i = index; i < array.length; i++) {// 保證不會重復if (set.contains(array[i])) {continue;}set.add(array[i]);// 交換兩者的位置swap(index, i);// 固定下一個位置,繼續尋找backtrack(index + 1);// 還原兩者的位置swap(i, index);}}private void swap(int index, int newIndex) {char temp = array[index];array[index] = array[newIndex];array[newIndex] = temp;} }提交OK。
分析一下復雜度:
- 時間復雜度 O(N!) : 這個比較好理解,長度為 N 的字符串,需要計算的次數是: N * (N - 1) * (N - 2) * ... * 2 * 1,結果也就是 N! 。
- 空間復雜度 O(N^2) : 需要借助的額外空間,也就是那個保證不會重復所使用到的set,它所存儲的總量,最差情況下,長度為 N 的字符串中,所有字符各不相同,也就需要 N + (N - 1) + (N - 2) * ... * 2 * 1,結果也就是 N^2。
java 優化
針對上面代碼中出現的 char[] 轉 String,可以使用String.valueOf(char[])方法進行優化,因為該方法,最終會使用System.arrayCopy方法,該方法屬于native方法,更加高效。
至于最終,將 list 轉 array 的過程,可以用list.toArray(String[])做寫法上的簡化,性能上倒并沒有什么提升。
優化后的代碼為:
class Solution {char[] array;List<String> result = new LinkedList<>();public String[] permutation(String s) {array = s.toCharArray();// 回溯backtrack(0);// 賦值給數組return result.toArray(new String[result.size()]);}private void backtrack(int index) {// 如果是最后一個位置,就可以添加進result中if (index == array.length - 1) {result.add(String.valueOf(array));return;}Set<Character> set = new HashSet<>();for (int i = index; i < array.length; i++) {// 保證不會重復if (set.contains(array[i])) {continue;}set.add(array[i]);// 交換兩者的位置swap(index, i);// 固定下一個位置,繼續尋找backtrack(index + 1);// 還原兩者的位置swap(i, index);}}private void swap(int index, int newIndex) {char temp = array[index];array[index] = array[newIndex];array[newIndex] = temp;} }繼續優化
其實到了,如果想進一步優化的話,可以針對 list 轉 array 這里。
因為我們使用的是 LinkedList,內部存儲的 String 對象在物理上是不連續的,在最后遍歷時會相對比較耗時。
如果我們一開始就可以求出所有該字符串所能獲得的所有不重復字符串的總個數的話,就可以提前構造一個 array,不需要在最后又遍歷一次 list 了。
那么如何求出有重復字符的所有排列呢?假設是字符串aabbc,其求法為:
接下來看看代碼:
class Solution {char[] array;String[] result;int resultIndex = 0;public String[] permutation(String s) {array = s.toCharArray();// 求出一共有多少種可能int totalCount = calculate();result = new String[totalCount];// 回溯backtrack(0);// 賦值給數組return result;}private int calculate() {// 各字符出現的次數,默認只會出現26個英文字母int[] countArray = new int[26];for (char temp : array) {countArray[temp - 'a'] += 1;}// 統計總次數int length = array.length;int totalCount = 1;for (int count : countArray) {if (count == 0) {continue;}// 求排列totalCount *= cc(length, count);length -= count;}return totalCount;}private int cc(int total, int count) {// 如果count超過total的一半,則換成 (total - count),因為在排列中,C(5, 4) = C(5, 1)if (count > total / 2) {count = total - count;}// 分別求分子、分母int result = 1;int result1 = 1;for (int i = 0; i < count; i++) {result *= (total - i);result1 *= (count - i);}return result / result1;}private void backtrack(int index) {// 如果是最后一個位置,就可以添加進result中if (index == array.length - 1) {result[resultIndex++] = String.valueOf(array);return;}// 默認只會出現26個英文字母boolean[] exists = new boolean[26];for (int i = index; i < array.length; i++) {// 保證不會重復if (exists[array[i] - 'a']) {continue;}exists[array[i] - 'a'] = true;// 交換兩者的位置swap(index, i);// 固定下一個位置,繼續尋找backtrack(index + 1);// 還原兩者的位置swap(i, index);}}private void swap(int index, int newIndex) {char temp = array[index];array[index] = array[newIndex];array[newIndex] = temp;} }提交OK,其執行時間最短,因此認為優化是有效的。
總結
以上就是這道題目我的解答過程了,不知道大家是否理解了。本題主要在于對回溯的理解,優化時可以結合 java 特性,以及排列的一些知識。
有興趣的話可以訪問我的博客或者關注我的公眾號、頭條號,說不定會有意外的驚喜。
https://death00.github.io/
公眾號:健程之道
總結
以上是生活随笔為你收集整理的list转字符串_剑指offer 38——字符串的排列的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中cmd全称_【转】Pyth
- 下一篇: tomcat.exe java home