徒手挖地球十二週目
NO.36 有效數獨 中等
思路一:暴力法 依次遍歷完每一行,每一列,每一個一個子數獨。
public boolean isValidSudoku(char[][] board) {
if (board==null||board.length<9)return false;
Set<Character> set=new HashSet<>();
//遍歷每一行判斷是否有重複
for (int i=0;i<9;i++){
//遍歷完一行之後清空set
set.clear();
for (int j=0;j<9;j++){
//如果這個元素是1-9,則判斷是否重複元素
if (board[i][j]>='1'&&board[i][j]<='9'){
if (set.contains(board[i][j])){
return false;
}else {
set.add(board[i][j]);
}
}
}
}
//遍歷每一列判斷是否有重複元素
for (int j=0;j<9;j++){
set.clear();
for (int i=0;i<9;i++){
if (board[i][j]>='1'&&board[i][j]<='9'){
if (set.contains(board[i][j])){
return false;
}else {
set.add(board[i][j]);
}
}
}
}
//遍歷每一個3*3矩陣是否有重複元素
for (int k=0;k<3;k++){
for (int m=0;m<3;m++){
set.clear();
for (int i=0;i<3;i++){
for (int j=0;j<3;j++){
if (board[i+(3*m)][j+(3*k)]>='1'&&board[i+(3*m)][j+(3*k)]<='9'){
if (set.contains(board[i+(3*m)][j+(3*k)])){
return false;
}else {
set.add(board[i+(3*m)][j+(3*k)]);
}
}
}
}
}
}
return true;
}
時間複雜度:對所有元素進行三次遍歷,O(3n),在這個題目的約束下(都是9*9的數獨),對81個元素三次遍歷,時間複雜度可以看做常數次,即O(1)。
思路二:優化暴力法,一次遍歷 空間換時間,定義27個數組(9行+9列+9個子數獨)。只需要遍歷一次,將每個元素與其對應的行數組、列數組、子數獨數組中判斷是否出現重複即可。
判斷元素所屬的行和列是很容易的,如何判斷每個元素屬於第幾個子數獨需要思考:box_index=(i/3)*3+j/3
public boolean isValidSudoku(char[][] board) {
//用三個二維數組分別分別記錄9行、9列、9個子數獨中的元素,用來判斷是否重複
int[][] rows=new int[9][9];
int[][] cols=new int[9][9];
int[][] boxes=new int[9][9];
//遍歷數獨中每個元素
for (int i=0;i<9;i++){
for (int j=0;j<9;j++){
char c = board[i][j];
//如果被遍歷元素是數字,則添加到其對應的數組中進行記錄
if (c>='1'&&c<='9'){
int box_index=(i/3)*3+j/3;
rows[i][c-'0'-1]++;
cols[j][c-'0'-1]++;
boxes[box_index][c-'0'-1]++;
//判斷是否出現重複
if (rows[i][c-'0'-1]>1||cols[j][c-'0'-1]>1||boxes[box_index][c-'0'-1]>1)return false;
}
}
}
return true;
}
時間複雜度:數獨中9*9個元素遍歷一次,O(n),但是在這個題目的約束下(都是9*9的數獨),對81個元素一次遍歷,時間複雜度可以看做常數次,即O(1)。
思路三:一次遍歷,移位編碼,位圖法 思路二相對於思路一雖然只需要一次遍歷,但是空間上付出的“代價”讓人不爽,可以藉助位圖進行優化。這裏就不贅述各種算術運算符的作用了,有需要的請百度,都比我說得好。
很容易發現,導致空間浪費的原因是我們申請了int(32位)類型的數組,但是每個元素最大隻需要表示到2,也就是每個元素數值部分實際上只需要2位即可。那麼我們很容易想到:申請byte(8位)類型就好了。這確實是一種方法,但是我們使用位圖這種數據結構來完成,節省空間的同時,還可以使算法的速度得到優化。
我們需要申請一個int類型的數組map,但是我們將每個32位元素的前0-8位分別表示同一數字出現在第幾行中,9-17位分別表示同一數字出現在第幾列中,18-26位分別表示數字出現在第幾個子數獨中,對應位上0表示該數字未出現過,1表示該數字出現過。上述情況可以使用左移運算來實現,還需要使用按位與運算實現判斷某行、某列、某子數獨是否存在重複情況,使用按位或運算將數字加入對應的數組元素。
public boolean isValidSudoku(char[][] board) {
int[] map=new int[9];
for (int row=0;row<9;row++){
for (int col=0;col<9;col++){
char c = board[row][col];
if (c!='.'){
//0-8位表示行號,9-17位表示列號,18-26位表示第幾個子數獨
int index=1<<(0+row)|1<<(9+col)|1<<(18+row/3*3+col/3);
//按位與等於0說明該數字不曾在同一行、列、子數獨中出現過
if ((map[c-'0'-1]&index)==0){
//按位或將數字加入數組對應元素
map[c-'0'-1]|=index;
}else {
return false;
}
}
}
}
return true;
}
時間複雜度:數獨中9*9個元素遍歷一次,O(n),但是在這個題目的約束下(都是9*9的數獨),對81個元素一次遍歷,時間複雜度可以看做常數次,即O(1)。
NO.38 外觀數列 簡單
這道題讀題花了我好一會兒,最後還是在別人的幫助下理解題意。。。這個外觀數列的意思是:第一項 “1”;第二項描述第一項:1個1,“11”;第三項描述第二項:2個1,“21”;第四項描述第三項:1個2和1個1,“1211”;第五項描述第四項:1個1和1個2和2個1,“111221”。。。。
思路一:按序生成每個數列 按順序依次生成每個“外觀數列”,其實就是BF。
public String countAndSay(int n) {
//第一項外觀數列爲"1"
if (n==1)return "1";
String s="1";
//按序生成n個外觀數列,從第二項開始
for (int i=0;i<n-1;i++){
//temp是當前生成的外觀數列、c初始化爲前一項的第一個字符、count記錄前一項連續相等的的字符
StringBuilder temp=new StringBuilder();
char c = s.charAt(0);
int count=1;
//遍歷前一項外觀數列的每個字符
for (int j=1;j<s.length();j++){
//如果連續的字符相等,則count計數器增加
if (c==s.charAt(j)){
count++;
}else {
//如果連續字符不相等,則將連續字符數量和字符拼接至temp
temp.append(count).append(c);
//c更新爲當前遍歷的字符,計數器重置爲1
c=s.charAt(j);
count=1;
}
}
//注意,很容易遺漏的一步:將最後未拼接的字符加入temp
temp.append(count).append(c);
//更新s
s=temp.toString();
}
return s;
}
時間複雜度不會算。。。
NO.39 組合總數 中等
思路一:深度遍歷,回溯法 深度遍歷得到”全排列“的過程中回溯剪枝。目標值作爲根節點,每個分做減法,如果目標值被減爲0則結算此分支路徑上的減數集合,如果目標值被減爲負數則剪枝即可。
去重:每個分支節點上的減數的下標不能比本分支上一層節點的減數的下標小。即,上層節點的減數下標爲3,則同分支下一層節點的減數下標要從3開始。(這樣每個分支就不會重複使用之前使用過的元素)
List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if (candidates==null)return res;
dfs(target,0,new Stack<Integer>(),candidates);
return res;
}
//深度遍歷
private void dfs(int target, int index, Stack<Integer> pre, int[] candidates) {
//等於零說明結果符合要求
if (target==0){
res.add(new ArrayList<>(pre));
return;
}
//遍歷,index爲本分支上一節點的減數的下標
for (int i=index;i<candidates.length;i++){
//如果減數大於目標值,則差爲負數,不符合結果
if (candidates[i]<=target){
pre.push(candidates[i]);
//目標值減去元素值
dfs(target-candidates[i],i,pre, candidates);
//每次回溯將最後一次加入的元素刪除
pre.pop();
}
}
}