有一個XxY的網格,一個機器人只能走格點且只能向右或向下走,要從左上角走到右下角。請設計一個算法,計算機器人有多少種走法。
給定兩個正整數int x,int y,請返回機器人的走法數目。保證x+y小於等於12。
class Robot {
// public int times = 0;
// public int times1 = 0;
/*** 遞歸解法 **/
public int countWays1(int x, int y) {
if (x < 0 || y < 0)
return 0;
if (x == 0 || y == 0)
return 1;
// times1 ++;
return countWays1(x - 1, y) + countWays1(x, y - 1);
}
/** 優化的遞歸解法 **/
public int countWays(int x, int y) {
if (x < 0 || y < 0)
return 0;
int[][] counts = new int[x + 1][y + 1];
counts[0][0] = 1;
return countWays(x, y, counts);
}
private int countWays(int x, int y, int[][] counts) {
if (x < 0 || y < 0)
return 0;
if (counts[x][y] <= 0) {
counts[x][y] = countWays(x - 1, y, counts) + countWays(x, y - 1, counts);
// times ++;
}
return counts[x][y];
}
}
2、Minimum Path Sum (矩陣路徑最小和問題)(leetcode 65)Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.
Note: You can only move either down or right at any point in time.
即給定一個m*n的數組矩陣,矩陣中所有值都是非負的;從左上角開始往右或者往下走,記錄最終到達右下角的路徑上所有元素之和的最小值問題;public class Solution {
public int minPathSum(int[][] grid) {
// 創建一個sum數組來記錄每個位置的sum值,這樣可以減少遞歸中的重複計算
int[][] sum = new int[grid.length][grid[0].length];
// 進行初始化賦值
for (int[] sums : sum)
Arrays.fill(sums, -1);
return minPath(grid, 0, 0, sum);
}
private int minPath(int[][] grid, int x, int y, int[][] sum) {
// 因爲下面需要用到Math.min獲取到最小值,故而這裏使用Integer.MAX_VALUE來表示超出邊界值
if (x == grid.length || y == grid[0].length)
return Integer.MAX_VALUE;
if (sum[x][y] == -1) {
int next = Math.min(minPath(grid, x + 1, y, sum),
minPath(grid, x, y + 1, sum));
// 注意處理bottom位置,即右下角位置兩個方向走都超出了邊界,因而最後Math.min獲得的值都是Integer.MAX_VALUE,這裏需要將其置爲0
next = (next == Integer.MAX_VALUE) ? 0 : next;
sum[x][y] = grid[x][y] + next;
}
return sum[x][y];
}
}
二、求子集問題
2、獲得集合中的所有子集:
問題描述:
請編寫一個方法,返回某集合的所有非空子集。
給定一個int數組A和數組的大小int n,請返回A的所有非空子集。保證A的元素個數小於等於20,且元素互異。各子集內部從大到小排序,子集之間字典逆序排序,見樣例。
[123,456,789]
返回:{[789,456,123],[789,456],[789,123],[789],[456,123],[456],[123],{}}
問題分析:
注意子集問題不要忘了空集{},空集是每個集合的子集;
遞歸法:子集問題可以很簡單地轉化爲遞歸問題;假設A數組中有n個元素,前n-1個元素的排列組合的集合P(n - 1)已經獲得,則獲得n個元素的排列組合問題,即轉化爲最後一個元素A[n]是否存在的問題;若不存在,則P'(n) = P(n - 1);若存在,則P''(n) = P(n - 1) + A[n],即P(n -1)中每個集合中加上A[n];則原問題轉化爲P(n)=P(n-1) + P''(n)的問題;用遞歸很容易實現;
迭代法:遞歸的時間複雜度爲O(2^n);這裏可以採用數學中組合方法來取巧,在構造一個集合時,每個元素無非存在兩種狀態:存在(記爲1)和不存在(記爲0),則可以用一個二進制數來記錄每一個子集合;該二進制數的取值範圍顯然是0--2^n;遍歷該範圍,將二進制轉換成爲集合即可;
代碼:
遞歸法:
class Subset {
/*@param: A--原始集合; n--數組大小*/
public ArrayList<ArrayList<Integer>> getSubsets(int[] A, int n) {
if (A == null || A.length != n) // 輸入不合法情況
return null;
return getAllSubSets(A, 0);
}
// 遞歸函數,index表示當前遞歸的深度
private ArrayList<ArrayList<Integer>> getAllSubSets(int[] A, int index) {
ArrayList<ArrayList<Integer>> subSets;
if (A.length == index){ // 達到遞歸底層
subSets = new ArrayList<>();
subSets.add(new ArrayList<Integer>()); // 注意加上{}空集合
} else {
// 獲得P(n - 1)集合
subSets = getAllSubSets(A, index + 1);
// 獲得集合中當前元素
int item = A[index];
// 獲取P(n - 1) + a(n)的集合
ArrayList<ArrayList<Integer>> addedSets = new ArrayList<>();
// 遍歷所有的P(n-1)中子元素,加上a(n)
for (ArrayList<Integer> subSet : subSets) {
// 新創建一個ArrayList,用來保存添加item後的結果
ArrayList<Integer> tempList = new ArrayList<>(subSet);
tempList.add(item);
addedSets.add(tempList);
}
// P(n)即是P(n - 1) + addedSets
subSets.addAll(addedSets);
}
return subSets;
}
}
迭代法:
class Subset {
public ArrayList<ArrayList<Integer>> getSubsets(int[] A, int n) {
if (A == null || A.length != n)
return null;
ArrayList<ArrayList<Integer>> subSets = new ArrayList<>();
// 獲得最大數值
int max = 1 << n;
// int max = (int)Math.pow(2, n);
// 遍歷所有可能的結果
for (int i = 0; i < max; i ++) {
subSets.add(getSetsFromNum(A, i));
}
return subSets;
}
// 將二進制數值轉化成爲集合形式
private ArrayList<Integer> getSetsFromNum(int[] A, int num) {
ArrayList<Integer> subSet = new ArrayList<>();
int index = 0;
// 遍歷A數組中每個元素對應的位置,若該位置上num二進制爲1,則添加到結果集合中;
for (int k = num; k > 0; k >>= 1, index ++) {
if ((k & 1) == 1)
subSet.add(A[index]);
}
return subSet;
}
}
三、排列組合問題:
編寫一個方法,確定某字符串的所有排列組合。
給定一個string A和一個int n,代表字符串和其長度,請返回所有該字符串字符的排列,保證字符串長度小於等於11且字符串中字符均爲大寫英文字符,排列中的字符串按字典序從大到小排序。(不合並重復字符串)
"ABC"
返回:["CBA","CAB","BCA","BAC","ACB","ABC"]
代碼:
public class Permutation {
public ArrayList<String> getPermutation(String A) {
// 判斷字符串A是否合法
if (A == null)
return null;
ArrayList<String> resultList = getThePermutation(A);
// 進行字典序排序(從大到小)
Collections.sort(resultList, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if (o1.compareTo(o2) > 0) return -1;
if (o1.compareTo(o2) < 0) return 1;
return 0;
}
});
return resultList;
}
// 遞歸解法
private ArrayList<String> getThePermutation(String A) {
ArrayList<String> resultList = new ArrayList<>();
// 返回結果值
if (A.length() == 0) {
resultList.add("");
return resultList;
}
char item = A.charAt(0); // 獲得字符串的首字符
A = A.substring(1); // 移除字符串的首字符
ArrayList<String> preList = getThePermutation(A); // 獲得p(n - 1)
// 往P(n-1)中每個字符串中插入item字符,形成新的排列組合
for (String str : preList) {
for (int i = 0; i <= str.length(); i++) { // 注意是<=號
String newStr = insertIntoStr(str, item, i);
resultList.add(newStr);
}
}
return resultList;
}
// 往字符串的指定位置插入字符
private String insertIntoStr(String str, char item, int index) {
String start = str.substring(0, index);
String end = str.substring(index, str.length());
return start + item + end;
}
}<span style="widows: auto; font-family: 微軟雅黑; background-color: inherit;"> </span>
在數組A[0..n-1]中,有所謂的魔術索引,滿足條件A[i]=i。給定一個升序數組,元素值各不相同,編寫一個方法,判斷在數組A中是否存在魔術索引。請思考一種複雜度優於o(n)的方法。
給定一個int數組A和int n代表數組大小,請返回一個bool,代表是否存在魔術索引。
[1,2,3,4,5]
返回:false
public class MagicIndex {
public boolean findMagicIndex(int[] A, int n) {
// 直接二分查找
if (A == null || A.length == 0)
return false;
int start = 0;
int end = A.length -1;
while (start <= end) {
int mid = (end - start) / 2 + start;
// 找到魔術索引
if (A[mid] == mid)
return true;
if (A[mid] < mid) // 在右子序列
start = mid + 1;
else
end = mid - 1;
}
return false;
}
}
在數組A[0..n-1]中,有所謂的魔術索引,滿足條件A[i]=i。給定一個不下降序列,元素值可能相同,編寫一個方法,判斷在數組A中是否存在魔術索引。請思考一種複雜度優於o(n)的方法。
給定一個int數組A和int n代表數組大小,請返回一個bool,代表是否存在魔術索引。
[1,1,3,4,5]
返回:true
public class MagicIndex {
public boolean findMagicIndex(int[] A, int n) {
if (A == null || A.length == 0)
return false;
return findMagicIndex(A, 0 , A.length - 1);
}
private boolean findMagicIndex(int[] A, int start, int end) {
if (start > end)
return false;
boolean result = false;
int mid = (end - start) / 2 + start;
if (A[mid] == mid)
return true;
if (A[mid] < mid) {
result = findMagicIndex(A, start, A[mid]) ||
findMagicIndex(A, mid + 1, end);
} else {
result = findMagicIndex(A, start, mid - 1) ||
findMagicIndex(A, A[mid], end);
}
return result;
}
}