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