劍指offer演算法題分析與整理(一)
下面整理一下我在刷劍指offer時,自己做的和網上大神做的各種思路與答案,自己的程式碼是思路一,保證可以通過,網友的程式碼提供出處連結。
目錄
1、陣列中的逆序對
在陣列中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個陣列中的逆序對的總數P。並將P對1000000007取模的結果輸出。 即輸出P%1000000007
思路:很明顯,暴力解答時間複雜度為O(n^2),不可取。一種很巧妙的方法是,利用歸併排序,在兩段相互比較排序的過程中,統計逆序對。
public class Solution {
public int InversePairs(int [] array) {
int N=array.length;
if(N==1) return 0;
int mid=(N-1)/2;
int [] leftArr=new int[mid+1];
int [] rightArr=new int[N-1-mid];
for(int i=0;i<=mid;i++) leftArr[i]=array[i];
for(int i=0;i<=N-2-mid;i++) rightArr[i]=array[i+mid+1];
int leftNum=InversePairs(leftArr)%1000000007;
int rightNum=InversePairs(rightArr)%1000000007;
int i=0,j=0,k=0,num=0;
while(i<=mid&&j<=N-2-mid){
if(leftArr[i]<rightArr[j]){
array[k++]=leftArr[i++]; }
else{
array[k++]=rightArr[j++];
num=(num+mid-i+1 )%1000000007;//當發生left大right小時,left大後面的肯定都比該right小要大,統計left裡面所有比right小要大的數
}
}
while(i<=mid){array[k++] = leftArr[i++];}
while(j<=N-2-mid){array[k++]=rightArr[j++];}
int sumNum=(leftNum+rightNum+num)%1000000007;
return sumNum;
}
}
下面貼一下我在寫的過程中出錯的一段
//這一段是錯的
while(i<=mid||j<=N-2-mid){
if(i==mid+1){array[k++]=rightArr[j++];}
else if(j==N-1-mid) {
array[k++] = leftArr[i++];
if (i - 1 == flag) { } //111
else { num += N - 1 - mid;}
}
else if(leftArr[i]<rightArr[j]){array[k++]=leftArr[i++];}
else if(leftArr[i]>rightArr[j]){
array[k++]=rightArr[j++];
if(i==flag){num++;}//222
else{ num+=j;}
flag=i;
}
}
分析上面對與錯的解答,兩者的區別是
- 前者是在發生左邊比右邊大時,統計左邊所有比右邊這個數大的對數
- 後者是在發生左邊比右邊大時,統計右邊所有比左邊這個數小的對數
在歸併排序的過程中統計逆序對,對於兩段已經排好序的leftArr和rightArr,我們採取的是從兩段的頭部開始比較,取較小的數填進Array中。既然是取小,就
- 一定不會發生多大(左)對同小(右)的局面(右邊的一小隻會對左邊的一大,而且按後者的統計方法,可能會發生左邊明明有比右邊大的,但卻沒有計數,如【1,2,3】對【0,4,5】2和3對0這兩隊就沒計入,如果會存在多大對同小,也就不會漏了)
- 但反而會發生一大(左)對多小(右)的局面,按後者的統計方法這時會重複統計左邊這一大與右邊好多小數之間的對數
因此後者在解決這兩個問題的路上疲於奔命,標記2處試圖解決上面第二點的重複問題,標記1處試圖解決一大(左)對多小(右)時,右邊的數比完了,在進行下一次迴圈往Array填數時會重複計算 一大(左)對右邊末位數 這一對。還有上面第一點提到的漏掉的問題,還沒改,已經改不下去了···
其實只要轉向就不會這麼折騰=_=
參考牛客網優質解答,網上還有許多從leftArr和rightArr的尾部開始比較,取較大的數填進Array中(當然是從它的尾部開始填),這時所有的情況與上面分析的相反,就應該採用後者的統計方法了。
2、二維陣列中的查詢
在一個二維陣列中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函式,輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。
思路一:我最開始想法是,該陣列中的每個數,它的下面和右邊的數都比它大,把下面和右邊的數當作它的分支,就是一個最小二叉堆(堆中的元素有很多重合的,因為重合的對應陣列中的同一個元素),採用深度優先遍歷。
public class Solution {
public boolean Find(int target, int [][] array) {
int row = array.length;//二維陣列的行數
if (row == 0) return false;
//if (array == null) return false;//!!!!判斷陣列是否為空,不能這樣
int columnMax = 0;
for (int m = 0; m < row; m++) {
if (array[m].length > columnMax) {
columnMax = array[m].length;}//m行對應的列數
}
int[][] flag=new int[row][columnMax];//避免把同一個元素比多次
int[] arr = new int[2 * row * columnMax];//類似於棧的功能,存數值的座標
int i, j, k = 0;
arr[2 * k] = 0;
arr[2 * k + 1] = 0;
while (k >= 0) {//當棧內還有元素時
i = arr[2 * k];
j = arr[2 * k + 1];
k--;//彈出棧頂的座標,準備比較
flag[i][j]=1;//每個數比較之後就被標記
if (array[i][j] < target) {//當前數比目標小,把它的分支壓入棧中
if (j + 1 < array[i].length&&flag[i][j+1]==0) {//判斷一下右邊的分支越界沒有
k++;
arr[2 * k] = i;
arr[2 * k + 1] = j + 1;
}
if (i + 1 < row&&flag[i+1][j]==0) {//判斷下邊的分支越界沒有
k++;
arr[2 * k] = i + 1;
arr[2 * k + 1] = j;
}
} else if (array[i][j] > target) {//當前數比目標大,它的分支就沒必要再參與比較了
for(int m=i;m<row;m++){
for(int n=j;n<array[m].length;n++){
flag[m][n]=1;
}
}
} else return true;
}
return false;
}
}
思路二:該二維陣列的性質決定了,一、它的左上右下對角線是遞增的;二、對於陣列中的一個數,它的左上角矩陣都比它小,右下角矩陣都比它大。可以先遍歷對角線(不是方針的可以先按對角線補齊,會有外延對角線段),找到對角線中關於目標值的左右兩個臨界點(沒有小點,說明目標值小於矩陣所有值,沒有大點就把外延線第一個點看作大點),小點的左上角矩陣全排除,大點的右下角矩陣全排除,剩下的兩個矩陣重複這個操作。初步想法,沒程式碼實現。
思路三:對每行或每列用二分查詢(總感覺怪怪的,因為只用到了二維陣列的一個特性);那如果對行與列都用二分查詢呢,那一次只能排除矩陣的四分之一,感覺效率不高。
思路四:參考牛客網@徘徊的路人甲,最簡單高效的神操作,我的思維侷限在從左上角開始了。左下和右上開始,豁然開朗!
public class Solution {
public boolean Find(int target, int [][] array) {
int rows = array.length;
int cols = array[0].length;
int i=rows-1,j=0;
while(i>=0 && j<cols){
if(target<array[i][j])
i--;
else if(target>array[i][j])
j++;
else
return true;
}
return false;
}
}
3、順時針列印矩陣
輸入一個矩陣,按照從外向裡以順時針的順序依次打印出每一個數字。
思路一:判斷矩陣是不是一列或者一行,寫出一列、一行、一圈的列印操作,再判斷列印完沒有,沒有就繼續從裡面重複這些操作。
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> A=new ArrayList();
int rows=matrix.length;
int cols=matrix[0].length;
int colsM=cols;
int rowsM=rows;
int i=0,j=-1;
while(A.size()<rowsM*colsM) {//判斷有沒有往裡面塞完
if(rows==1) {//一行
for (int m = 0; m < cols; m++) {
j++;
A.add(matrix[i][j]);
}
}
else if(cols==1){//一列
j++;
for (int m = 0; m < rows; m++) {
A.add(matrix[i][j]);
i++;
}
}
else {//一圈
for (int m = 0; m < cols; m++) {
j++;
A.add(matrix[i][j]);
}
for (int m = 0; m < rows - 1; m++) {
i++;
A.add(matrix[i][j]);
}
for (int m = 0; m < cols - 1; m++) {
j--;
A.add(matrix[i][j]);
}
for (int m = 0; m < rows - 2; m++) {
i--;
A.add(matrix[i][j]);
}
rows -= 2;
cols -= 2;
}
}
return A;
}
}
以下內容有一部分來自牛客網解答,其實while迴圈裡面還可以再簡化為
for (int m = 0; m < cols; m++) {
j++;
A.add(matrix[i][j]);
}
if(rows==1)break;//不管是不是隻有一行,第一個for迴圈都是一樣的
for (int m = 0; m < rows - 1; m++) {
i++;
A.add(matrix[i][j]);
}
if(cols==1)break;//不管是不是隻有一列,前兩個for迴圈都是一樣的
for (int m = 0; m < cols - 1; m++) {
j--;
A.add(matrix[i][j]);
}
for (int m = 0; m < rows - 2; m++) {
i--;
A.add(matrix[i][j]);
}
同樣是這種思路,上面的 i 和 j 在變化過程中我不直接給它們賦值,而是根據走多少步讓它們轉彎,讓它們在前一個的基礎上遞增或遞減;下面的另一種實現方式,它相當於將一張紙用左上和右下的兩個釘子訂起來,釘子決定了遍歷範圍。
int top = 0, left = 0, right = col-1, bottom = row-1;
while(top <= bottom && left<= right){
for(int i = left; i <= right; ++i) A.add(matrix[top][i]);
for(int i = top+1; i <= bottom; ++i) A.add(matrix[i][right]);
//不是單行或單列才有下面的
for(int i = right-1; i >= left && top < bottom; --i){
A.add(matrix[bottom][i]);
}
for(int i = bottom-1; i > top && right > left; --i){
A.add(matrix[i][left]);
}
++top; ++left; --right; --bottom;
}
在判斷什麼時候數才打印完了,還可以用層數判斷,雖然我覺得上面的更容易理解。
int layers = (Math.min(row,col)-1)/2+1;//這個是層數
for(int i=0;i<layers;i++){
for(int k = i;k<col-i;k++) A.add(array[i][k]);
for(int j=i+1;j<row-i;j++) A.add(array[j][col-i-1]);
for(int k=col-i-2;(k>=i)&&(row-i-1!=i);k--) A.add(array[row-i-1][k]);
for(int j=row-i-2;(j>i)&&(col-i-1!=i);j--) A.add(array[j][i]);
}
這幾種實現方式都是同一種思想。
思路二:列印完矩陣第一行後,將矩陣向左旋轉90度,重複該操作。但是這樣每打一行就要旋轉,很花時間,矩陣大了更嚴重。
public ArrayList<Integer> printMatrix(int[][] matrix) {
ArrayList<Integer> A = new ArrayList();
int row = matrix.length;
while (row != 0) {
for (int i = 0; i < matrix[0].length; i++) {
A.add(matrix[0][i]);
}
if (row == 1)
break;
matrix = turn(matrix);
row = matrix.length;
}
return A;
}
private int[][] turn(int[][] matrix) {
int col = matrix[0].length;
int row = matrix.length;
int[][] newMatrix = new int[col][row - 1];
for (int j = col - 1; j >= 0; j--) {
for (int i = 1; i < row; i++) {
newMatrix[col - 1 - j][i - 1] = matrix[i][j];
}
}
return newMatrix;
}
思路三:不用管每次橫著或者豎著要走幾步,每走一步就看看下一步還能不能走,不能走就及時修正方向,直到遍歷完整個陣列。不過這種方法每走一步就要判斷一下,牛客網顯示超時=_=!
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> A=new ArrayList();
int rows=matrix.length;
int cols=matrix[0].length;
int i=0,j=0;
int [][] flag=new int[rows][cols];//為1就是已經走過了,不能再走了
while(A.size()<rows*cols){
//從左到右走
while(i>=0&&i<rows&&j>=0&&j<cols&&flag[i][j]==0){ A.add(matrix[i][j]);flag[i][j]=1;j++; }
j--;//走出頭了,回退
i++;//換向
while(i>=0&&i<rows&&j>=0&&j<cols&&flag[i][j]==0){ A.add(matrix[i][j]);flag[i][j]=1;i++; }
i--;
j--;
while(i>=0&&i<rows&&j>=0&&j<cols&&flag[i][j]==0){ A.add(matrix[i][j]);flag[i][j]=1;j--; }
j++;
i--;
while(i>=0&&i<rows&&j>=0&&j<cols&&flag[i][j]==0){ A.add(matrix[i][j]);flag[i][j]=1;i--; }
i++;
j++;
}
return A;
}
}
同樣的思路,不一樣的實現方法。
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> A=new ArrayList();
int rows=matrix.length;
int cols=matrix[0].length;
int [][] direction={{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int i=0,j=0,d=0;
int [][] flag=new int[rows][cols];
while(A.size()<rows*cols){
A.add(matrix[i][j]);
flag[i][j]=1;
//列印完,標記完後,看看下一步能不能走,如果不能走就要換方向了
if(!(i+direction[d][0]>=0&&i+direction[d][0]<rows&&j+direction[d][1]>=0&&j+direction[d][1]<cols&&flag[i+direction[d][0]][j+direction[d][1]]==0)){
d++;d=d%4;
}
i+=direction[d][0];//按照給定的方向走
j+=direction[d][1];
}
return A;
}
}
4、翻轉單詞順序
例如:將“I am a student.”變為“student. a am I”。單詞之間可能有多個空格,句子頭和尾可能有多個空格。
思路一:從尾到頭遍歷,遇到空格段,取出來把它填入新字串,遇到單詞段,也取出來填入新的字串,最後返回該新字串。二是從頭到尾遍歷,遇到單詞段從右邊開始填入新字串。
public class Solution {
public String ReverseSentence(String str) {
StringBuffer A=new StringBuffer();
int len=str.length();
if(len==0){return str;}
int r=len-1,l=r;
while(l>=0){
//下面的迴圈判斷兩個條件前後不能換,不然java.lang.StringIndexOutOfBoundsException: String index out of range: -1
while(l>=0&&str.charAt(l)==' '){l--;}//找空格段
A.append(str.substring(l+1,r+1));//從l+1到r的字串,如果l=r那麼就為空
r=l;
while(l>=0&&str.charAt(l)!=' '){l--;}//找單詞段
A.append(str.substring(l+1,r+1));
r=l;
}
return A.toString();
}
}
以下內容來自牛客網,下面是C++從前到後遍歷的。而Java裡面String是不可變的,所以這樣頻繁的用+連線生成新的字串在Java裡不好。
class Solution {
public:
string ReverseSentence(string str) {
string res = "", tmp = "";
for(unsigned int i = 0; i < str.size(); ++i){
if(str[i] == ' ') res = " " + tmp + res, tmp = "";
else tmp += str[i];
}
if(tmp.size()) res = tmp + res;
return res;
}
};
思路二:兩次翻轉,一次全部翻轉,一次只翻轉單詞。
public class Solution {
public String ReverseSentence(String str) {
char[] chars = str.toCharArray();
reverse(chars,0,chars.length - 1);
int blank = -1;//用一個全域性變數來記錄段的起點終點
for(int i = 0;i < chars.length;i++){
if(chars[i] == ' '){ //對於單詞段,可以取出並進行翻轉;對於空格段,仍然更新blank,但不做任何操作
int nextBlank = i;
reverse(chars,blank + 1,nextBlank - 1);
blank = nextBlank;
}
}
if(chars[chars.length-1]!=' '){//當最後一個單詞後面沒有空格,需要在這裡將其翻轉
reverse(chars,blank + 1,chars.length - 1);
}
return new String(chars);
}
public void reverse(char[] chars,int low,int high){
while(low < high){
char temp = chars[low];
chars[low] = chars[high];
chars[high] = temp;
low++;
high--;
}
}
}
另一種C++版本如下
void ReverseWord(string &str, int s, int e){
while(s < e){swap(str[s++], str[e--]);}
}
string ReverseSentence(string str) {
ReverseWord(str, 0, str.size() - 1); //先整體翻轉
int s = 0, e = 0;
int i = 0;
while(i < str.size()){
while(i < str.size() && str[i] == ' '){i++;}//空格跳過
e = s = i; //記錄單詞的第一個字元的位置
while(i < str.size() && str[i] != ' '){//找單詞後的第一個空格位置
i++;
e++;
}
ReverseWord(str, s, e - 1); //區域性翻轉
}
return str;
}
5、刪除連結串列中重複的結點
在一個排序的連結串列中,存在重複的結點,請刪除該連結串列中重複的結點,重複的結點不保留,返回連結串列頭指標。
思路一:從頭節點開始遍歷,維護一個前點toa,一個現點a,一個後點b。當現點a與其next後點b的值相同時,後點往後移直到與現點的值不同,將前點的next指向後點,然後更新現點,將前點的next繼續賦給現點,始終保持前點的next是現點;當現點a與後點b不同時,整體更新,將現點a賦給前點toa,後點b賦給現點a。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {//有了有參的構造方法,就不能呼叫預設無參構造方法了,除非自己寫個無參的
this.val = val;
}
}
*/
public class Solution {
public ListNode deleteDuplication(ListNode pHead)//注意,傳入的是連結串列的第一個節點,不是空頭節點
{
ListNode toa=new ListNode(0);//這個0沒意義,隨便都可以
toa.next=pHead;
ListNode star=toa;//star是不變的空頭節點
ListNode a=toa.next;
while(a!=null){
ListNode b=a.next;
if(b!=null&&a.val==b.val){ //if裡面是改變連結關係
while(b!=null&&a.val==b.val){
b=b.next;
}
toa.next=b;
a=toa.next;
}
else{ //else裡面是向後推進
toa=a;
a=b;
}
}
return star.next;//返回也返回新連結串列的第一個節點
}
}
參考牛客網@Kevin_Xiu的答案的思想跟我的一樣,只是它更簡潔,少用了一個節點,它不需要節點b。
public static ListNode deleteDuplication(ListNode pHead) {
ListNode first = new ListNode(-1);//first就是star
first.next = pHead;
ListNode p = pHead; //p是現點a, p.next是後點b
ListNode last = first;//last是toa
while (p != null && p.next != null) {//這裡加了第二個判斷條件,現點p是最後一個點時,上面我的雖然會進入循壞,但不會改變原有的連結關係,所以這裡不進迴圈也可以
if (p.val == p.next.val) {
int val = p.val;
while (p!= null&&p.val == val)
p = p.next;
last.next = p;
} else {
last = p;
p = p.next;
}
}
return first.next;
}
思路二:前面的解答最開始我以為傳入的是連結串列的空頭節點,返回的也是空頭節點,結果執行1,1,1,4發現最開始的1刪不掉,所以傳入的是第一個節點。那麼就可以用遞迴的方法,每次遞迴,刪除目前向後的第一段連續的節點。保證下一次遞迴的第一個結點與之後的不重複。
來自牛客網@program
public class Solution {
public ListNode deleteDuplication(ListNode pHead) {
if (pHead == null || pHead.next == null) { // 只有0個或1個結點,則返回
return pHead; //遞迴一定要有這個,不然無窮遞迴了
}
if (pHead.val == pHead.next.val) { // 當前結點是重複結點
ListNode pNode = pHead.next;
while (pNode != null && pNode.val == pHead.val) {
// 跳過值與當前結點相同的全部結點,找到第一個與當前結點不同的結點
pNode = pNode.next;
}
return deleteDuplication(pNode); // 從第一個與當前結點不同的結點開始遞迴
else { // 當前結點不是重複結點
pHead.next = deleteDuplication(pHead.next); // 保留當前結點,從下一個結點開始遞迴
return pHead;
}
}
}
6、正則表示式匹配
請實現一個函式用來匹配包括’.’和’*’的正則表示式。模式中的字元’.’表示任意一個字元,而’*’表示它前面的字元可以出現任意次(包含0次)。 在本題中,匹配是指字串的所有字元匹配整個模式。例如,字串”aaa”與模式”a.a”和”ab*ac*a”匹配,但是與”aa.a”和”ab*a”均不匹配。
思路:從模式串開始遍歷,無非是四種情況:字母、點、字母*、點*。
- 前兩種好辦,與字串一一對著比,字母不同直接false,點都不用比直接算它相等。
- 模式串中的字母*,如果字串中對應的字母與該字母不同,那就當這個字母*不存在吧,模式串的遍歷向後跳兩步;如果字串中對應的字母與該字母相同,那麼就分別遞迴比較模式串字母*後面的新模式串與新字串的匹配情況,只要有一種匹配成功,就算整個都成功了。
- 模式串中的點*,可以看成是任意字母的任意個數(可以是0個)的組合,因此將點*後面的視作新模式串,將其分別與新字串遞迴匹配,只要有一種匹配成功,就算整個都成功了。
上面提到的新字串怎樣構造,在程式碼中詳細解釋。
還有,上面用遞迴,一個設定是:字串與模式串相匹配,當模式串為空時,如若字串不為空,則結果為false,若字串也為空,則結果為true。
public class Solution {
public boolean match(char[] str, char[] pattern){
if(pattern.length==0&&str.length!=0)return false;
if(pattern.length==0&&str.length==0)return true;
int j=0;//字串的遍歷指標
for(int i=0;i<pattern.length;i++){//以模式串為參考系,一步步遍歷模式串
//這是 字母* 的情況
if(pattern[i]!='.'&&pattern[i]!='*'&&i+1<pattern.length&&pattern[i+1]=='*') {
//如果 字母*(a*) 的這個字母與 字串 中對應的字母(a)相等(這時字串還沒遍歷完)
if(j >= 0 && j < str.length &&str[j] == pattern[i]) {
int i1=i,j1=j;
//生成新的模式串(就是字母*後面的)
char[] pattern1 = new char[pattern.length - i1-2];
for (int m = 0; m < pattern1.length; m++) {
pattern1[m] = pattern[i1+2];
i1++;
}
//找字串中,連著有幾個重複的a
while (j1 >= 0 && j1< str.length && str[j1] == pattern[i]){j1++;}
//如果有x個重複的a,下面就迴圈x+1次,生成x+1個新字串,分別與新模式串遞迴匹配
//這x+1次分別代表 a*等價於字串中的 空、一個a、兩個a、···、x個a
for(int n=j;n<=j1;n++) {
int j2=n;
char[] str1 = new char[str.length - n];
for (int m = 0; m < str1.length; m++) {//生成新字串
str1[m] = str[j2];
j2++;
}
if (match(str1, pattern1)) {//遞迴匹配,有一次成了,那就成了
return true;
}
}
return false;//沒一次成,那就返回false
}
else{ i++;}//a*這個a和字串中的b根本就不同,那就忽略a*吧||或者字串已經遍歷完了
}
//這是 .* 的情況
else if(pattern[i]=='.'&&i+1<pattern.length&&pattern[i+1]=='*'){
char[] pattern2 = new char[pattern.length - i-2];//生成新模式串
for (int m = 0; m < pattern2.length; m++) {
pattern2[m] = pattern[i+2];
i++;
}
for(int n=j;n<=str.length;n++) {//生成新字串
char[] str2 = new char[str.length - n];
int j3=n;
for (int m = 0; m < str2.length; m++) {
str2[m] = str[j3];
j3++;
}
if (match(str2, pattern2)) {//遞迴匹配
return true;
}
}
return false;//同理
}
//這是 字母 的情況
else if(pattern[i]!='.'){
if(j>=0&&j<str.length&&pattern[i]!=str[j]){return false;}//字串還沒遍歷完時,不匹配直接false
else{
j++;//匹配就看 模式串的下一個 與 字串的下一個||或者j已經超了字串的長度了,那這時很明顯應該返回false啊,別急,最後會處理
}
}
//這是 . 的情況
else if(pattern[i]=='.'){
j++;//都不用比了,預設是匹配的,直接看 模式串的下一個 與 字串的下一個
}
}
//其實前兩個if裡面的情況,遞迴了,都會在它裡面給出結果;如果沒發生遞迴,就看模式串遍歷完後,字串也相應的確認到了str.length,如果是,說明匹配成功,反之不成功
//但是這樣,就是要等到pattern完全遍歷完再給結果,可能str很短,pattern很長,str早就超出邊界範圍了,所以早就可以確定是false了,結果像你這樣搞,j還在一直加加加
//但是這種寫法(外面一個大for),就是要等整個大迴圈完了才好得出結果,當然,剛才說的上一點可以改進,如果str先超出邊界,可以直接返回false。但下面的這個最終判斷還是要有
if(j==str.length){return true;}
else{return false;}
}
}
上面我的解答還有一些可以改進的地方:
- 其實在前兩個if裡面沒必要構造那麼多新的陣列,既然是遞迴,其實兩個就夠了,我寫了那麼多讓他們分別遞迴匹配,其實在遞迴過程中有很多重複的判斷
- 前兩個if可以合併在一起,即.*的情況可視為a*中a與str中a匹配的情況
public class Solution {
public boolean match(char[] str, char[] pattern){
if(pattern.length==0&&str.length!=0)return false;
if(pattern.length==0&&str.length==0)return true;
int j=0;
for(int i=0;i<pattern.length;i++){
//a*與.*的情況
if(i+1<pattern.length&&pattern[i+1]=='*') {
//j沒越界時,a*與a相等,或.*
if((j >= 0 && j < str.length &&str[j] == pattern[i])||(j >= 0 && j < str.length &&pattern[i]=='.')) {
//構造新pattern
int i1=i+2;
char[] patternA2 = new char[pattern.length - i1];
for (int m = 0; m < patternA2.length; m++) {
patternA2[m] = pattern[i1];
i1++;
}
//此時往後截斷的pattern
int i2=i;
char[] patternNow=new char[pattern.length - i2];
for (int m = 0; m < patternNow.length; m++) {
patternNow[m] = pattern[i2];
i2++;
}
//構造新str
int j2 = j + 1;
char[] strA1 = new char[str.length - j2];
for (int m = 0; m < strA1.length; m++) {
strA1[m] = str[j2];
j2++;
}
//此時往後截斷的str
int j1=j;
char[] strNow=new char[str.length - j1];
for (int m = 0; m < strNow.length; m++) {
strNow[m] = str[j1];
j1++;
}
/*
if (match(strNow, patternA2)) {//視作a*或.*沒等價於str中任何字元
return true;
}
if (match(strA1, patternNow)) {//視作a*或.*等價於str中的a
return true;
}
*/
//這樣寫表達更簡潔
return match(strNow, patternA2)||match(strA1, patternNow);
}
else{ i++;}//j越界或a*與b不等
}
else if(pattern[i]!='.'){
//新增j越界就直接false
if((j>=0&&j<str.length&&pattern[i]!=str[j])||j>=str.length){return false;}
else{
j++;
}
}
else if(pattern[i]=='.'){
if(j>=str.length){return false;}//新增j越界就直接false
else{
j++;}
}
}
if(j==