編程合集: https://www.cnblogs.com/jssj/p/12002760.html
前言:不僅僅要實現,更要提升性能,精益求精,用盡量少的時間復雜度和空間復雜度解決問題。
【程序78】
實現獲取下一個排列的函數,算法需要將給定數字序列重新排列成字典序中下一個更大的排列。
如果不存在下一個更大的排列,則將數字重新排列成最小的排列(即升序排列)。
必須原地修改,只允許使用額外常數空間。
以下是一些例子,輸入位於左側列,其相應輸出位於右側列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
/** * 實現獲取下一個排列的函數,算法需要將給定數字序列重新排列成字典序中下一個更大的排列。 * 如果不存在下一個更大的排列,則將數字重新排列成最小的排列(即升序排列)。 * 必須原地修改,只允許使用額外常數空間。 * 以下是一些例子,輸入位於左側列,其相應輸出位於右側列。 * 1,2,3 → 1,3,2 * 3,2,1 → 1,2,3 * 1,1,5 → 1,5,1 */ public class Subject78 { public static void main(String[] args) { int[] arr = new int[]{2,3,1,3,3}; new Subject78().nextPermutation(arr); for (int i = 0; i < arr.length; i++) { System.out.print(arr[i]+" "); } } /** * 下一個最大值 * @param nums */ public void nextPermutation(int[] nums) { int lengths = nums.length; int size = -1; for (int i = lengths-1; i >= 0; i--) { if(i-1 >= 0 && nums[i-1] < nums[i]){ size = i-1; break; } } //如果沒有最大的值了 if(size == -1){ for (int i = 0 ,j= lengths-1; i <= j ; i++,j--) { int tmp = 0; tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } }else{ //處理size后邊的數據,重新整理成一個最小數組。 //找到比size位置大的數中的最小數。 int tmp = nums[size+1]; int sizeExchange = size+1; for (int i = size+1; i < lengths ; i++) { //這里可以優化 if(nums[i] <= tmp && nums[i] > nums[size]){ tmp = nums[i]; sizeExchange = i; } } nums[sizeExchange] = nums[size]; nums[size] = tmp; //剩余數據重新重小到大排序 for (int i = size+1 ,j= lengths-1; i <= j ; i++,j--) { int tmp0 = 0; tmp0 = nums[i]; nums[i] = nums[j]; nums[j] = tmp0; } } } }
時間復雜度:O(n)
運行結果:

【程序79】
給定一個只包含 '(' 和 ')' 的字符串,找出最長的包含有效括號的子串的長度。
import java.util.ArrayList; import java.util.List; /** * 給定一個只包含 '(' 和 ')' 的字符串,找出最長的包含有效括號的子串的長度。 */ public class Subject79 { public static void main(String[] args) { System.out.println(new Subject79().longestValidParentheses("(()()()(()))))))")); } public int longestValidParentheses(String s) { int lengths = s.length(); if(lengths <= 0){ return 0; } char[] arr = s.toCharArray(); List<Integer> list = new ArrayList<>(); /** * 將不可以匹配的括號留下,並且記錄位置。 */ for (int i = 0; i < arr.length; i++) { if('(' == arr[i]){ list.add(i); }else{ int size = list.size(); if(')' == arr[i] && list.size() > 0 && '(' == arr[list.get(size-1)]){ list.remove(size-1); }else{ list.add(i); } } } //獲取最大間隔時間 int maxLength = 0; for (int i = 0; i < list.size() ; i++) { if( i == 0 ){ maxLength = list.get(i); }else { int tmp = list.get(i) - list.get(i-1) -1; if(tmp > maxLength){ maxLength = tmp; } } } if(list.size() > 0){ int endLength = lengths - list.get(list.size()-1) -1; if(endLength > maxLength){ maxLength = endLength; } } else { maxLength = lengths; } return maxLength; } }
時間復雜度:O(n)
運行結果:

【程序80】
假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
( 例如,數組[0,1,2,4,5,6,7]可能變為[4,5,6,7,0,1,2])。
搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回-1。
你可以假設數組中不存在重復的元素。
你的算法時間復雜度必須是O(logn) 級別。
示例 1:
輸入: nums = [4,5,6,7,0,1,2], target = 0
輸出: 4
示例2:
輸入: nums = [4,5,6,7,0,1,2], target = 3
輸出: -1
/** * 假設按照升序排序的數組在預先未知的某個點上進行了旋轉。 * ( 例如,數組[0,1,2,4,5,6,7]可能變為[4,5,6,7,0,1,2])。 * 搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回-1。 * 你可以假設數組中不存在重復的元素。 * 你的算法時間復雜度必須是O(logn) 級別。 * * 示例 1: * 輸入: nums = [4,5,6,7,0,1,2], target = 0 * 輸出: 4 * * 示例2: * 輸入: nums = [4,5,6,7,0,1,2], target = 3 * 輸出: -1 */ public class Subject80 { int [] nums; int target; public static void main(String[] args) { int[] nums = new int[]{1}; System.out.println(new Subject80().search(nums,0)); } public int search(int[] nums, int target) { this.nums = nums; this.target = target; int n = nums.length; if (n == 0) return -1; if (n == 1) return this.nums[0] == target ? 0 : -1; /** * 找到旋轉節點 */ int rotate_index = find_rotate_index(0, n - 1); // if target is the smallest element if (nums[rotate_index] == target) return rotate_index; // if array is not rotated, search in the entire array if (rotate_index == 0) return search(0, n - 1); if (target < nums[0]) // search in the right side return search(rotate_index, n - 1); // search in the left side return search(0, rotate_index); } /** * 找旋轉節點 * @param left * @param right * @return */ public int find_rotate_index(int left, int right) { if (nums[left] < nums[right]) return 0; while (left <= right) { int pivot = (left + right) / 2; if (nums[pivot] > nums[pivot + 1]) return pivot + 1; else { if (nums[pivot] < nums[left]) right = pivot - 1; else left = pivot + 1; } } return 0; } /** * Binary search 二分查找法 * @param left * @param right * @return */ public int search(int left, int right) { while (left <= right) { int pivot = (left + right) / 2; if (nums[pivot] == target) return pivot; else { if (target < nums[pivot]) right = pivot - 1; else left = pivot + 1; } } return -1; } }
時間復雜度:O(logN)
運行結果:

【程序81】
給定一個按照升序排列的整數數組 nums,和一個目標值 target。找出給定目標值在數組中的開始位置和結束位置。
你的算法時間復雜度必須是O(log n) 級別。
如果數組中不存在目標值,返回[-1, -1]。
/** * 給定一個按照升序排列的整數數組 nums,和一個目標值 target。找出給定目標值在數組中的開始位置和結束位置。 * 你的算法時間復雜度必須是O(log n) 級別。 * 如果數組中不存在目標值,返回[-1, -1]。 */ public class Subject81 { public static void main(String[] args) { int[] arr = new int[]{1}; int[] result = new Subject81().searchRange(arr,1); for (int i = 0; i < result.length; i++) { System.out.print(result[i]+" "); } } /** * 二分查找法 * @param nums * @param target * @return */ public int[] searchRange(int[] nums, int target) { int left = 0; int right = nums.length-1; int pivot = -1; boolean flag = false; while (left <= right) { pivot = (left + right) / 2; if (nums[pivot] == target) { flag = true; break; } else { if (target < nums[pivot]) right = pivot - 1; else left = pivot + 1; } } if(!flag){ pivot = -1; } if(pivot != -1){ int leftTmp = pivot; int rightTmp = pivot; while(leftTmp >= 0){ leftTmp = leftTmp-1; if(leftTmp < 0 || nums[leftTmp] != target){ break; } } while(rightTmp <= nums.length-1){ rightTmp = rightTmp+1; if(rightTmp > nums.length-1 ||nums[rightTmp] != target){ break; } } return new int[]{leftTmp+1,rightTmp-1}; }else{ return new int[]{-1,-1}; } } }
時間復雜度:O(log2n)
運行結果:

【程序82】
給定一個排序數組和一個目標值,在數組中找到目標值,並返回其索引。如果目標值不存在於數組中,返回它將會被按順序插入的位置。
/** * 給定一個排序數組和一個目標值,在數組中找到目標值,並返回其索引。如果目標值不存在於數組中,返回它將會被按順序插入的位置。 */ public class Subject82 { public static void main(String[] args) { int[] arr = new int[]{1,3,4,5,6,7,9,10}; System.out.println(new Subject82().searchInsert(arr,8)); } public int searchInsert(int[] nums, int target) { if(nums.length < 0){ return 0; } int size = this.search(0,nums.length-1,nums,target); if(nums[size] == target){ return size; }else{ if(nums[size] > target){ return size; }else{ return size+1; } } } /** * Binary search 二分查找法 * @param left * @param right * @return */ public int search(int left, int right,int[] nums, int target) { while (left <= right) { int pivot = (left + right) / 2; if (nums[pivot] == target) return pivot; else { if (target < nums[pivot]) right = pivot - 1; else left = pivot + 1; } } if(right <= -1){ return left; } if(left >= nums.length){ return right; } return left <= right? left :right; } }
時間復雜度:O(logn)
運行結果:

【程序83】
判斷一個9x9 的數獨是否有效。只需要根據以下規則,驗證已經填入的數字是否有效即可。
數字1-9在每一行只能出現一次。
數字1-9在每一列只能出現一次。
數字1-9在每一個以粗實線分隔的3x3宮內只能出現一次。
/** * 判斷一個9x9 的數獨是否有效。只需要根據以下規則,驗證已經填入的數字是否有效即可。 * 數字1-9在每一行只能出現一次。 * 數字1-9在每一列只能出現一次。 * 數字1-9在每一個以粗實線分隔的3x3宮內只能出現一次。 */ public class Subject83 { public static void main(String[] args) { char[][] board = new char[][]{ {'.','.','.','.','5','.','.','1','.'}, {'.','4','.','3','.','.','.','.','.'}, {'.','.','.','.','.','3','.','.','1'}, {'8','.','.','.','.','.','.','2','.'}, {'.','.','2','.','7','.','.','.','.'}, {'.','1','5','.','.','.','.','.','.'}, {'.','.','.','.','.','2','.','.','.'}, {'.','2','.','9','.','.','.','.','.'}, {'.','.','4','.','.','.','.','.','.'}}; System.out.println( new Subject83().isValidSudoku(board)); } public boolean isValidSudoku(char[][] board) { int[] rowCnt = new int[9]; int[] colCnt = new int[9]; int[] boxCnt = new int[9]; for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { if ('.' == board[i][j]) { continue; } //處理成int型 int num = board[i][j] - 48; // 處理行 if ((rowCnt[i] >> num) % 2 == 1) { return false; } else { rowCnt[i] += 1 << num; } // 處理列 if ((colCnt[j] >> num) % 2 == 1) { return false; } else { colCnt[j] += 1 << num; } // 處理框 int boxNum = i / 3 * 3 + j / 3; if ((boxCnt[boxNum] >> num) % 2 == 1) { return false; } else { boxCnt[boxNum] = boxCnt[boxNum] + (1 << num); } } } return true; } }
時間復雜度:O(1)
運行結果:

【程序84】
編寫一個程序,通過已填充的空格來解決數獨問題。
一個數獨的解法需遵循如下規則:
數字1-9在每一行只能出現一次。
數字1-9在每一列只能出現一次。
數字1-9在每一個以粗實線分隔的3x3宮內只能出現一次。
空白格用'.'表示。
/** * 編寫一個程序,通過已填充的空格來解決數獨問題。 * 一個數獨的解法需遵循如下規則: * 數字1-9在每一行只能出現一次。 * 數字1-9在每一列只能出現一次。 * 數字1-9在每一個以粗實線分隔的3x3宮內只能出現一次。 * 空白格用'.'表示。 */ public class Subject84 { public static void main(String[] args) { char[][] board = new char[][]{ {'.','.','9','7','4','8','.','.','.'}, {'7','.','.','.','.','.','.','.','.'}, {'.','2','.','1','.','9','.','.','.'}, {'.','.','7','.','.','.','2','4','.'}, {'.','6','4','.','1','.','5','9','.'}, {'.','9','8','.','.','.','3','.','.'}, {'.','.','.','8','.','3','.','2','.'}, {'.','.','.','.','.','.','.','.','6'}, {'.','.','.','2','7','5','9','.','.'}}; new Subject84().solveSudoku(board); for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { System.out.print(board[i][j]+" "); } System.out.println(); } } // box size int n = 3; // row size int N = n * n; int [][] rows = new int[N][N + 1]; int [][] columns = new int[N][N + 1]; int [][] boxes = new int[N][N + 1]; char[][] board; boolean sudokuSolved = false; public boolean couldPlace(int d, int row, int col) { /* 檢查是否可以在(行,列)單元格中放置數字d */ int idx = (row / n ) * n + col / n; return rows[row][d] + columns[col][d] + boxes[idx][d] == 0; } public void placeNumber(int d, int row, int col) { /* 在(行,列)單元格中放置數字d */ int idx = (row / n ) * n + col / n; rows[row][d]++; columns[col][d]++; boxes[idx][d]++; board[row][col] = (char)(d + '0'); } public void removeNumber(int d, int row, int col) { /* 刪除一個無法找到解決方案的數字 */ int idx = (row / n ) * n + col / n; rows[row][d]--; columns[col][d]--; boxes[idx][d]--; board[row][col] = '.'; } public void placeNextNumbers(int row, int col) { /* 遞歸調用回溯函數 繼續放置數字 直到我們找到解決辦法 */ // 如果我們在最后一個牢房里 // 這意味着我們有辦法 if ((col == N - 1) && (row == N - 1)) { sudokuSolved = true; } // 如果還沒有 else { // 如果我們排在最后 // 到下一排 if (col == N - 1) backtrack(row + 1, 0); // go to the next column else backtrack(row, col + 1); } } public void backtrack(int row, int col) { /* 回溯 */ // 如果單元格是空的 if (board[row][col] == '.') { // 對從1到9的所有數字進行迭代 for (int d = 1; d < 10; d++) { if (couldPlace(d, row, col)) { placeNumber(d, row, col); placeNextNumbers(row, col); // 如果數獨問題解決了,就不必回頭了。 // 因為獨聯解決方案是有希望的 if (!sudokuSolved) removeNumber(d, row, col); } } } else placeNextNumbers(row, col); } public void solveSudoku(char[][] board) { this.board = board; // 初始化行、列和框 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { char num = board[i][j]; if (num != '.') { int d = Character.getNumericValue(num); placeNumber(d, i, j); } } } backtrack(0, 0); } }
時間復雜度:O(9!^9)
運行結果:

【程序85】
報數序列是一個整數序列,按照其中的整數的順序進行報數,得到下一個數。其前五項如下:
1. 1
2. 11
3. 21
4. 1211
5. 111221
1被讀作"one 1"("一個一") , 即11。
11 被讀作"two 1s"("兩個一"), 即21。
21 被讀作"one 2", "one 1"("一個二","一個一"), 即1211。
給定一個正整數 n(1 ≤n≤ 30),輸出報數序列的第 n 項。
/** * 報數序列是一個整數序列,按照其中的整數的順序進行報數,得到下一個數。其前五項如下: * 1. 1 * 2. 11 * 3. 21 * 4. 1211 * 5. 111221 * 1被讀作"one 1"("一個一") , 即11。 * 11 被讀作"two 1s"("兩個一"), 即21。 * 21 被讀作"one 2", "one 1"("一個二","一個一"), 即1211。 * 給定一個正整數 n(1 ≤n≤ 30),輸出報數序列的第 n 項。 */ public class Subject85 { public static void main(String[] args) { System.out.println(new Subject85().countAndSay(6)); } public String countAndSay(int n) { if(n == 1){ return "1"; }else{ String str = countAndSay(n-1); char[] chArr = str.toCharArray(); StringBuilder strTmp = new StringBuilder(""); char ch = chArr[0] ; int count = 0; for (int i = 0; i < chArr.length; i++) { if(ch == chArr[i]){ count++; }else{ strTmp.append(count).append(ch); ch = chArr[i]; count = 1; } } strTmp.append(count).append(ch); return strTmp.toString(); } } }
時間復雜度:O(n)
運行結果:

【程序86】
給定一個無重復元素的數組candidates和一個目標數target,找出candidates中所有可以使數字和為target的組合。
candidates中的數字可以無限制重復被選取。
說明:
所有數字(包括target)都是正整數。
解集不能包含重復的組合。
import java.util.*; /** * 給定一個無重復元素的數組candidates和一個目標數target,找出candidates中所有可以使數字和為target的組合。 * candidates中的數字可以無限制重復被選取。 * 說明: * 所有數字(包括target)都是正整數。 * 解集不能包含重復的組合。 */ public class Subject86 { private List<List<Integer>> res = new ArrayList<>(); private int[] candidates; private int len; public static void main(String[] args) { int[] candidates = new int[]{2,3,6,7}; List<List<Integer>> list = new Subject86().combinationSum(candidates,7); System.out.println(list); } private void findCombinationSum(int residue, int start, Stack<Integer> pre) { if (residue == 0) { // Java 中可變對象是引用傳遞,因此需要將當前 path 里的值拷貝出來 res.add(new ArrayList<>(pre)); return; } // 優化添加的代碼2:在循環的時候做判斷,盡量避免系統棧的深度 // residue - candidates[i] 表示下一輪的剩余,如果下一輪的剩余都小於 0 ,就沒有必要進行后面的循環了 // 這一點基於原始數組是排序數組的前提,因為如果計算后面的剩余,只會越來越小 for (int i = start; i < len && residue - candidates[i] >= 0; i++) { pre.add(candidates[i]); // 【關鍵】因為元素可以重復使用,這里遞歸傳遞下去的是 i 而不是 i + 1 findCombinationSum(residue - candidates[i], i, pre); pre.pop(); } } public List<List<Integer>> combinationSum(int[] candidates, int target) { int len = candidates.length; if (len == 0) { return res; } // 優化添加的代碼1:先對數組排序,可以提前終止判斷 Arrays.sort(candidates); this.len = len; this.candidates = candidates; findCombinationSum(target, 0, new Stack<>()); return res; } }
時間復雜度:O(2^n)
運行結果:

【程序87】
給定一個數組candidates和一個目標數target,找出candidates中所有可以使數字和為target的組合。
candidates中的每個數字在每個組合中只能使用一次。
說明:
所有數字(包括目標數)都是正整數。
解集不能包含重復的組合。
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Stack; /** * 給定一個數組candidates和一個目標數target,找出candidates中所有可以使數字和為target的組合。 * candidates中的每個數字在每個組合中只能使用一次。 * 說明: * 所有數字(包括目標數)都是正整數。 * 解集不能包含重復的組合。 */ public class Subject87 { private List<List<Integer>> res = new ArrayList<>(); public static void main(String[] args) { int[] candidates = new int[]{10,1,2,7,6,1,5}; List<List<Integer>> list = new Subject87().combinationSum2(candidates,7); System.out.println(list); } public List<List<Integer>> combinationSum2(int[] candidates, int target) { int len = candidates.length; if (len == 0) { return res; } // 優化添加的代碼1:先對數組排序,可以提前終止判斷 Arrays.sort(candidates); findCombinationSum(target, 0, new Stack<>(),candidates); return res; } private void findCombinationSum(int residue, int start, Stack<Integer> pre, int[] candidates) { if (residue == 0) { // Java 中可變對象是引用傳遞,因此需要將當前 path 里的值拷貝出來 List list= new ArrayList<>(pre); res.add(list); return; } // 優化添加的代碼2:在循環的時候做判斷,盡量避免系統棧的深度 // residue - candidates[i] 表示下一輪的剩余,如果下一輪的剩余都小於 0 ,就沒有必要進行后面的循環了 // 這一點基於原始數組是排序數組的前提,因為如果計算后面的剩余,只會越來越小 for (int i = start; i < candidates.length && residue - candidates[i] >= 0; i++) { if( i-1 >= 0 && candidates[i] == candidates[i-1]){ continue; } pre.add(candidates[i]); // 【關鍵】因為元素可以重復使用,這里遞歸傳遞下去的是 i 而不是 i + 1 findCombinationSum(residue - candidates[i], i, pre,this.copyArr2(candidates,i)); pre.pop(); } } public int[] copyArr2(int[] candidatesTmp,int index){ int[] candidates = new int[candidatesTmp.length-1]; for (int i = 0,j = 0; i < candidatesTmp.length; i++) { if(index == i){ continue; }else{ candidates[j++] = candidatesTmp[i]; } } return candidates; } }
時間復雜度:O(n!)
運行結果:

以上題目均來自:https://leetcode-cn.com/ ,如果你熱愛編碼,熱愛算法,該網站一定適合你。
