3-1 數組中重復的數字
每遍歷數組中的一個數字,就讓其歸位(放置在正確的數組下標)。當在歸位的過程中,發現該數組下標所存放的數字和當前要歸位的數字相同時,則發生了重復,返回該數字。
空間復雜度O(1),時間復雜度O(n)。
public class FindDuplicateNum_3 {
public static boolean findDuplicateNum(int[] arr, int length, int[] dup) {
if (arr == null || length <= 0) {
return false;
}
//時間復雜度O(n)
for (int i = 0; i < length; i++) {
//每個數字最多交換2次
while (arr[i] != i) {
if (arr[i] == arr[arr[i]]) {
dup[0] = arr[i];
return true;
}
swap(arr, i, arr[i]);
}
}
return false;
}
private static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
3-2 不修改數組找出重復數字
空間復雜度O(1),采用類似二分查找的算法,時間復雜度O(nlogn)。
思路:將1~ n上的數字划分成兩塊:1~ m和m+1~ n,然后統計數組中該區間上的數字個數,如果數字個數大於區間長度,則發生了重復,然后在該區間上繼續二分,直至區間長度等於1。
//不修改數組找出重復數字
public static int findDuplicateNumNoEdit(int[] arr, int length) {
if (arr == null || length <= 0) {
return -1;
}
int start = 1;
int end = length - 1;
while (start <= end) {
int mid = start + ((end - start) >> 1);
int count = getCount(arr, length, start, mid);
//System.out.println(mid+" "+count);
if (start == end) {
if (count > 1) {
return start;
} else {
return -1;
}
}
if (count > (mid - start + 1)) {
end = mid;
} else {
start = mid + 1;
}
}
return -1;
}
private static int getCount(int[] arr, int length, int start, int end) {
int count = 0;
for (int i = 0; i < length; i++) {
if (arr[i] >= start && arr[i] <= end) {
count++;
}
}
return count;
}
4 二維數組查找
從左下或者右上角開始查找,每次判斷可以剔除一行或者是一列,時間復雜度O(n+m)
public static boolean Find(int target, int[][] array) {
/*左下查找*/
int rows = array.length;
if (rows == 0) {
return false;
}
int columns = array[0].length;
if (columns == 0) {
return false;
}
int column = 0;
int row = rows - 1;
//注意數組邊界
while (row >= 0 && column < columns) {
if (target == array[row][column]) {
return true;
} else if (target < array[row][column]) {
row--;
} else {
column++;
}
}
return false;
}
5 替換空格
先統計出字符串中的空格數量,然后計算出替換后的字符串長度,從后往前遍歷字符串,依次填充。
public static String replaceBlankSpace_2(String str) {
if (str == null) {
return null;
}
char[] chars = str.toCharArray();
int count = 0;
for (int i = 0; i < chars.length; i++) {
if (chars[i] == ' ') {
count++;
}
}
int newLength = chars.length + (count << 1);
int p1 = chars.length - 1;
int p2 = newLength - 1;
char[] newChars = new char[newLength];
while (p1 >= 0) {
if (chars[p1] == ' ') {
newChars[p2--] = '0';
newChars[p2--] = '2';
newChars[p2--] = '%';
p1--;
} else {
newChars[p2--] = chars[p1--];
}
}
return String.valueOf(newChars);
}
6 從尾到頭打印鏈表
使用棧,遍歷一遍鏈表,將鏈表中的節點壓棧,然后輸出棧中的節點
import java.util.Stack;
public class FromHeadtoTailPrintLinkedList_6 {
static class ListNode {
int key;
ListNode next;
public ListNode(int key) {
this.key = key;
}
}
public static void fromHeadtoTailPrintLinkedListByStack(ListNode head) {
if (head == null) {
return;
}
Stack<ListNode> stack = new Stack();
while (head != null) {
stack.push(head);
head = head.next;
}
while (!stack.isEmpty()) {
System.out.print(stack.pop().key + " ");
}
}
public static void fromHeadtoTailPrintLinkedListByRecursion(ListNode head) {
if (head == null) {
return;
}
fromHeadtoTailPrintLinkedListByStack(head.next);
System.out.print(head.key + " ");
}
public static void main(String[] args) {
ListNode head = new ListNode(0);
head.next = new ListNode(1);
head.next.next = new ListNode(2);
head.next.next.next = new ListNode(3);
fromHeadtoTailPrintLinkedListByStack(head);
System.out.println();
fromHeadtoTailPrintLinkedListByRecursion(head);
}
}
7 重建二叉樹
根據前序和中序遍歷,可以確定每顆子樹根節點所在的位置,然后根據根節點,划分左右子樹,之后再分別在左右子樹中重復之前的划分過程。(遞歸實現)
public static Node constructBinaryTreeByPreInOrder(int[] preOrder, int[] inOrder, int preOrder_start,
int inOrder_start, int length) {
if (length == 0) {
return null;
}
int rootInOrderIndex = 0;
for (int i = inOrder_start; i < inOrder_start + length; i++) {
if (preOrder[preOrder_start] == inOrder[i]) {
rootInOrderIndex = i;
break;
}
}
int left_length = rootInOrderIndex - inOrder_start;
int right_length = length - left_length - 1;
//根節點
Node root = new Node(preOrder[preOrder_start]);
//構建左子樹
root.left = constructBinaryTreeByPreInOrder(preOrder, inOrder, preOrder_start + 1,
inOrder_start, left_length);
//構建右子樹
root.right = constructBinaryTreeByPreInOrder(preOrder, inOrder, preOrder_start + left_length + 1,
rootInOrderIndex + 1, right_length);
return root;
}
8 二叉樹的下一個節點
分三種情況:
- 當前節點有右子樹,下一個節點是右子樹中最左的節點
- 無右子樹
- 父節點的左孩子是當前節點,下一個節點是父節點
- 遍歷該節點的父節點,直到父節點的左孩子是當前節點,下一個節點是父節點
public class TreeNode {
public int value;
public TreeNode left;
public TreeNode right;
public TreeNode parent;
public TreeNode(int value) {
this.value = value;
}
}
public static TreeNode findNextNode(TreeNode treeNode) {
//當前節點有右子樹,下一個節點是右子樹中最左的節點
if (treeNode.right != null) {
TreeNode cur = treeNode.right;
while (cur.left != null) {
cur = cur.left;
}
return cur;
} else {
//無右子樹
TreeNode par = treeNode.parent;
//父節點的左孩子是當前節點,下一個節點是父節點
if (par.left == treeNode) {
return par;
} else {
//遍歷該節點的父節點,直到父節點的左孩子是當前節點,下一個節點是父節點
while (par.left != treeNode) {
par = par.parent;
treeNode = treeNode.parent;
}
return par;
}
}
}
9 用兩個棧實現隊列
delteHead和getHead操作:只有stackPop為空時,才能往里面壓入數據
import java.util.Stack;
public class TwoStackToQueue<T> {
private Stack<T> stackPush;
private Stack<T> stackPop;
public TwoStackToQueue() {
stackPush = new Stack<T>();
stackPop = new Stack<T>();
}
public void appendTail(T node) {
stackPush.push(node);
}
public T deleteHead() {
if (stackPush.isEmpty() && stackPop.isEmpty()) {
throw new RuntimeException("Queue is empty!");
} else {
if (stackPop.isEmpty()) {
while (!stackPush.isEmpty()) {
stackPop.push((stackPush.pop()));
}
}
return stackPop.pop();
}
}
public T getHead(){
if (stackPush.isEmpty() && stackPop.isEmpty()) {
throw new RuntimeException("Queue is empty!");
} else {
if (stackPop.isEmpty()) {
while (!stackPush.isEmpty()) {
stackPop.push((stackPush.pop()));
}
}
return stackPop.peek();
}
}
public static void main(String[] args) {
TwoStackToQueue twoStackToQueue = new TwoStackToQueue();
twoStackToQueue.appendTail(1);
twoStackToQueue.appendTail(2);
twoStackToQueue.appendTail(3);
twoStackToQueue.appendTail(4);
System.out.println(twoStackToQueue.deleteHead());
System.out.println(twoStackToQueue.deleteHead());
System.out.println(twoStackToQueue.deleteHead());
twoStackToQueue.appendTail(5);
System.out.println(twoStackToQueue.deleteHead());
System.out.println(twoStackToQueue.getHead());
System.out.println(twoStackToQueue.deleteHead());
System.out.println(twoStackToQueue.deleteHead());
}
}
用兩個隊列實現棧
引入隊列queue1和queue2,每次pop操作,就將queue1中的節點都放入queue2中,直至queue1中的節點個數為1,然后再將queue1的節點poll,之后,再交換queue1和queue2中的值。peek操作類似。
import java.util.LinkedList;
import java.util.Queue;
public class TwoQueueToStack<T> {
private Queue<T> queue1;
private Queue<T> queue2;
public TwoQueueToStack() {
queue1 = new LinkedList<T>();
queue2 = new LinkedList<T>();
}
public void push(T node) {
queue1.add(node);
}
public T pop() {
if (queue1.isEmpty() && queue2.isEmpty()) {
throw new RuntimeException("the stack is empty!");
}
while (queue1.size() != 1) {
queue2.add(queue1.poll());
}
T node = queue1.poll();
Queue<T> queue = queue1;
queue1 = queue2;
queue2 = queue;
return node;
}
public T peek() {
if (queue1.isEmpty() && queue2.isEmpty()) {
throw new RuntimeException("the stack is empty!");
}
T node = null;
while (!queue1.isEmpty()) {
node = queue1.poll();
queue2.add(node);
}
Queue<T> queue = queue1;
queue1 = queue2;
queue2 = queue;
return node;
}
public static void main(String[] args) {
TwoQueueToStack twoQueueToStack = new TwoQueueToStack();
twoQueueToStack.push(1);
twoQueueToStack.push(2);
twoQueueToStack.push(3);
System.out.println(twoQueueToStack.pop());
twoQueueToStack.push(4);
System.out.println(twoQueueToStack.peek());
System.out.println(twoQueueToStack.pop());
System.out.println(twoQueueToStack.pop());
System.out.println(twoQueueToStack.pop());
System.out.println(twoQueueToStack.pop());
}
}
10 斐波那契數列
1.遞歸,時間復雜度O(2^n^)
2.循環,時間復雜度O(n
public class Fibonacci_10 {
public static int calFibonacciRecursive(int n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
return calFibonacciRecursive(n - 1) + calFibonacciRecursive(n - 2);
}
public static int calFibonacciNoRecursive(int n) {
int[] res={0,1};
if(n<2){
return res[n];
}
int f1=0;
int f2=1;
int f=0;
for (int i = 2; i <=n; i++) {
f=f1+f2;
f1=f2;
f2=f;
}
return f;
}
public static void main(String[] args) {
System.out.println(calFibonacciRecursive(10));
System.out.println(calFibonacciNoRecursive(20));
}
}
應用
11 旋轉數組的最小數字
解法:
- 暴力,時間復雜度O(n)
- 二分查找,時間復雜度O(logn)。使用兩個指針p1,p2,然后根據計算的mid值來移動p1,p2。
- 當arr[p1]== arr[mid] == arr[p2]時,無法判斷p1和p2屬於哪個遞增子數組,直接調用getMin,進行順序查找。
- 當arr[mid]>=arr[p1]時,mid屬於p1所在的遞增子數組,令p1=mid,繼續二分。
- 當arr[mid]<=arr[p2]時,處理過程和2類似。
三種輸入情況:
- 1,2,3,4,5
- 3,4,5,1,2
- 1,1,1,0,1
import org.jetbrains.annotations.NotNull;
public class FindMinNumberInRotateArray_11 {
public static int findByDichotomy(int[] arr) {
if (arr[0] < arr[arr.length - 1]) {
return arr[0];
}
int p1 = 0;
int p2 = arr.length - 1;
int mid = 0;
int min = arr[0];
while (arr[p1] >= arr[p2]) {
if (p1 + 1 == p2) {
min = arr[p2];
break;
}
mid = p1 + ((p2 - p1) >> 1);
if (arr[mid] == arr[p1] && arr[mid] == arr[p2]) {
return getMin(arr, p1, p2);
}
if (arr[mid] >= arr[p1]) {
p1 = mid;
} else if (arr[mid] <= arr[p2]) {
p2 = mid;
}
}
return min;
}
private static int getMin(int[] arr, int p1, int p2) {
int min = arr[p1];
for (int i = p1 + 1; i < p2; i++) {
if (min > arr[i]) {
min = arr[i];
}
}
return min;
}
public static int findByForce(int[] arr) {
int min = arr[0];
for (int i = 1; i < arr.length; i++) {
if (min > arr[i]) {
min = arr[i];
}
}
return min;
}
public static void main(String[] args) {
int[] arr = {3, 4, 5, 1, 2};
int[] arr1 = {1, 2, 3, 4, 5};
int[] arr2 = {1, 0, 1, 1, 1};
int[] arr3 = {1};
System.out.println(comparator(arr));
System.out.println(comparator(arr1));
System.out.println(comparator(arr2));
System.out.println(comparator(arr3));
}
@NotNull
public static String comparator(int[] arr) {
return findByDichotomy(arr) == findByForce(arr) ? "true" : "false";
}
}
12 矩陣中的路徑
思路:
回溯法,字符的遍歷過程如下所示
public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
if (matrix == null || str == null || rows <= 0 || cols <= 0) {
return false;
}
//標記數組,用來記錄該字符是否訪問過
boolean[][] mark = new boolean[rows][cols];
char[][] chars = toArray(matrix, rows, cols);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (process(chars, str, 0, mark, i, j)) {
return true;
}
}
}
return false;
}
//將一維數組轉換成二維數組
public char[][] toArray(char[] matrix, int rows, int cols) {
char[][] chars = new char[rows][cols];
for (int i = 0, index = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
chars[i][j] = matrix[index++];
}
}
return chars;
}
//遞歸函數
public boolean process(char[][] chars, char[] str, int pathLength, boolean[][] mark, int row, int column) {
//遍歷的路徑長度和字符串長度相等,說明,之前的字符都已經成功匹配,返回true
if (pathLength == str.length) {
return true;
}
//數組下標越界、字符不匹配、字符已經訪問過,都返回false
if (row < 0 || column < 0 || row >= chars.length || column >= chars[0].length
|| chars[row][column] != str[pathLength] || mark[row][column]) {
return false;
}
//字符已訪問,標記為true
mark[row][column] = true;
//遞歸遍歷該字符傍邊的字符,匹配成功,則路徑長度加1
if (process(chars, str, pathLength + 1, mark, row - 1, column) ||
process(chars, str, pathLength + 1, mark, row + 1, column) ||
process(chars, str, pathLength + 1, mark, row, column - 1) ||
process(chars, str, pathLength + 1, mark, row, column + 1)) {
return true;
}
//該字符旁邊的字符都不匹配,則說明這條路不符合,還原,將字符的遍歷標記設置為false
mark[row][column] = false;
return false;
}
13 機器人的運動范圍
思路:圖的深度優先遍歷
public int movingCount(int threshold, int rows, int cols) {
//標記數組
boolean[][] mark = new boolean[rows][cols];
//存儲每個位置的數位和
int[][] matrix = new int[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = getValue(i) + getValue(j);
}
}
return process(threshold, matrix, mark, 0, 0, rows, cols);
}
private int process(int threshold, int[][] matrix, boolean[][] mark, int i, int j, int rows, int cols) {
int count = 0;
//遞歸終止條件
if (i < 0 || j < 0 || i >= rows || j >= cols || matrix[i][j] > threshold || mark[i][j]) {
return 0;
}
//將訪問過的位置標記為true
mark[i][j] = true;
//訪問當前位置,加1,然后繼續遍歷該位置傍邊的位置,累加起來,最終的返回值就是所能到達的格子數
count = 1 + process(threshold, matrix, mark, i - 1, j, rows, cols) + process(threshold, matrix, mark, i + 1, j, rows, cols) +
process(threshold, matrix, mark, i, j - 1, rows, cols) + process(threshold, matrix, mark, i, j + 1, rows, cols);
return count;
}
//計算一個整數的數位之和
public int getValue(int num) {
int res = 0;
int tmp = 0;
while (num / 10 > 0) {
tmp = num / 10;
res += num - tmp * 10;
num = tmp;
}
res += num;
return res;
}
14 剪繩子
public class cutRope_14 {
// 思路:
// f(n)=max(f(i)*f(n-i)),0<i<n
// f(n)表示把繩子剪成若干段后各段乘積的最大值
//1.遞歸
public int cutRope(int target) {
if (target < 2) {
return 0;
}
if (target == 2) {
return 1;
}
if (target == 3) {
return 2;
}
int max = 0;
for (int i = 1; i <= (target - 1) / 2; i++) {
max = Math.max(max, process(i) * process(target - i));
}
return max;
}
public int process(int target) {
//遞歸終止條件
if (target < 4) {
return target;
}
int max = 0;
for (int i = 1; i <= (target - 1) / 2; i++) {
max = Math.max(max, process(i) * process(target - i));
}
return max;
}
//2.動態規划,時間復雜度O(n^2),由遞歸轉化而來
public int cutRopeDP(int target) {
if (target < 2) {
return 0;
}
if (target == 2) {
return 1;
}
if (target == 3) {
return 2;
}
int[] dp = new int[target + 1];
dp[1] = 1;
dp[2] = 2;
dp[3] = 3;
for (int i = 4; i <= target; i++) {
for (int j = 1; j <= i / 2; j++) {
dp[i] = Math.max(dp[i], dp[j] * dp[i - j]);
}
}
return dp[target];
}
// 3.貪心,時間復雜度O(1)
// n>4時,划分出盡可能多的3,因為3(n-3)>=2(n-2)
// n=4時,2*2 > 3*1,所以當划分出1和3時,要轉變成2和2
// n<4時,特殊情況,單獨處理
public int cutRopeGreedy(int target) {
if (target < 2) {
return 0;
}
if (target == 2) {
return 1;
}
if (target == 3) {
return 2;
}
int timeOf3 = target / 3;
if (target - timeOf3 * 3 == 1) {
timeOf3--;
}
int timeOf2 = (target - timeOf3 * 3) / 2;
int res = (int) (Math.pow(3, timeOf3) * Math.pow(2, timeOf2));
return res;
}
//測試
public static void main(String[] args) {
cutRope_14 cutRope_14 = new cutRope_14();
System.out.println(cutRope_14.cutRope(14));
System.out.println(cutRope_14.cutRopeDP(14));
System.out.println(cutRope_14.cutRopeGreedy(14));
}
}
15 二進制中1的個數
public class BinaryNumber_15 {
public int NumberOf1(int n) {
//數字在計算機中以二進制形式存儲,負數在計算機中以補碼存儲,int類型的數據占4個字節
//為了防止負數右移出現死循環的情況,可以把1每次左移一位,然后和n比較
int res = 0;
int flag = 1;
while (flag != 0) {
if ((n & flag) != 0) {
res++;
}
flag = flag << 1;
}
return res;
}
public int NumberOf1Improve(int n) {
//(n-1)&n 每次運算的結果將n中二進制表示最右邊的1變為0
int res = 0;
while (n != 0) {
n=(n-1)&n;
res++;
}
return res;
}
public static void main(String[] args) {
BinaryNumber_15 binaryNumber_15 = new BinaryNumber_15();
int res = binaryNumber_15.NumberOf1(-8);
System.out.println(res);
}
}
16 數值的整數次方
base=0,exponent<0是非法輸入,給用戶提示輸入錯誤
提高運算效率:
public class NumberExponent_16 {
public double Power(double base, int exponent) {
//非法輸入
if (base == 0 && exponent < 0) {
throw new RuntimeException("input number error!");
}
double res = 1;
double tmp = exponent;
if (exponent < 0) {
exponent = -exponent;
}
//O(n)
for (int i = 1; i <= exponent; i++) {
res = res * base;
}
if (tmp < 0) {
res = 1 / res;
}
return res;
}
public double PowerImprove(double base, int exponent) {
//非法輸入
if (base == 0 && exponent < 0) {
throw new RuntimeException("input number error!");
}
double res = 1;
double tmp = exponent;
if (exponent < 0) {
exponent = -exponent;
}
if (exponent % 2 == 0) {
//O(n/2)
for (int i = 1; i <= exponent / 2; i++) {
res = res * base;
}
res = res * res;
} else {
for (int i = 1; i <= (exponent - 1) / 2; i++) {
res = res * base;
}
res = res * res * base;
}
if (tmp < 0) {
res = 1 / res;
}
return res;
}
}
17 打印從1到最大的n位數
本質上是0-9的全排列順序輸出問題,用遞歸實現。
大數問題,一般使用字符串來表示數字。
public class PrintMaxNumber_17 {
//n沒有限定范圍,大數問題,需要用字符串來表示
public void print(int n) {
if (n <= 0) {
throw new RuntimeException("error input!");
}
char[] nums=new char[n];
for (int i = 0; i < 10; i++) {
//數字轉字符,'0' + i 是 i 的ascii碼
nums[0]=(char)('0'+i);
process(nums,0,n);
}
}
public void process(char[] nums,int index,int len){
if(index==len-1){
print(nums);
return;
}
for (int i = 0; i < 10; i++) {
nums[index+1]=(char)('0'+i);
process(nums,index+1,len);
}
}
private void print(char[] nums) {
//標記位,用來判斷數字0之前是否有非零數字出現過
int flag=0;
String str="";
for (int i = 0; i < nums.length; i++) {
if(nums[i]!='0'){
flag=1;
str+=nums[i];
}
if(nums[i]=='0'&&flag==1){
str+=nums[i];
}
}
System.out.print(str+" ");
}
public static void main(String[] args) {
PrintMaxNumber_17 printMaxNumber_17=new PrintMaxNumber_17();
printMaxNumber_17.print(3);
}
}
18 刪除鏈表中的節點
鏈表中刪除節點的兩種方法:
public class DeleteNode_18 {
static class Node {
int value;
Node next;
public Node(int value) {
this.value = value;
}
}
//O(1)
public static Node deleteNode(Node head, Node deleteNode) {
//要刪除節點的下一個節點不為空時,用下一個節點的值替代當前節點,然后將當前節點指向下一個節點的節點,O(1)
if (deleteNode.next != null) {
deleteNode.value = deleteNode.next.value;
deleteNode.next = deleteNode.next.next;
} else {
//鏈表中只有一個節點
if (head == deleteNode) {
head = null;
} else {
//要刪除節點的下一個節點為空,即鏈表中最后一個節點,O(n)
Node cur = head;
while (cur.next != deleteNode) {
cur = cur.next;
}
cur.next = null;
}
}
return head;
}
public static void printNode(Node head) {
while (head != null) {
System.out.print(head.value + " ");
head = head.next;
}
}
public static void main(String[] args) {
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
node1.next = node2;
node2.next = node3;
Node head = deleteNode(node1, node3);
printNode(head);
}
}
19 正則表達式匹配
當模式中的第二個字符不是 “*” 時:
1、如果字符串第一個字符和模式中的第一個字符相匹配,那么字符串和模式都后移一個字符,然后匹配剩余的。
2、如果 字符串第一個字符和模式中的第一個字符不匹配,直接返回 false。
而當模式中的第二個字符是 “*” 時:
如果字符串第一個字符跟模式第一個字符不匹配,則模式后移 2 個字符,繼續匹配。如果字符串第一個字符跟模式第一個字符匹配,可以有 3 種匹配方式:
1、模式后移 2 字符,相當於 x * 被忽略;
2、字符串后移 1 字符,模式后移 2 字符;
3、字符串后移 1 字符,模式不變,即繼續匹配字符下一位,因為 * 可以匹配多位;
這里需要注意的是:Java 里,要時刻檢驗數組是否越界。
public class RegularExpressionMatch_19 {
public boolean match(char[] str, char[] pattern) {
if (str == null || pattern == null) {
return false;
}
int strIndex = 0;
int patternIndex = 0;
return matchCore(str, strIndex, pattern, patternIndex);
}
public boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex) {
//有效性檢驗:str到尾,pattern到尾,匹配成功
if (strIndex == str.length && patternIndex == pattern.length) {
return true;
}
//pattern先到尾,匹配失敗
if (strIndex != str.length && patternIndex == pattern.length) {
return false;
}
//模式第2個是*,且字符串第1個跟模式第1個匹配,分3種匹配模式;如不匹配,模式后移2位
if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') {
if ((strIndex != str.length && pattern[patternIndex] == str[strIndex])
|| strIndex != str.length && (pattern[patternIndex] == '.')) {
return matchCore(str, strIndex, pattern, patternIndex + 2)// 模式后移2,視為x*匹配0個字符
|| matchCore(str, strIndex + 1, pattern, patternIndex + 2)// 視為模式匹配1個字符
|| matchCore(str, strIndex + 1, pattern, patternIndex);// *匹配1個,再匹配str中的下一個
} else {
return matchCore(str, strIndex, pattern, patternIndex + 2);
}
}
//模式第2個不是*,且字符串第1個跟模式第1個匹配,則都后移1位,否則直接返回false
if ((strIndex != str.length && pattern[patternIndex] == str[strIndex])
|| (strIndex != str.length && pattern[patternIndex] == '.')) {
return matchCore(str, strIndex + 1, pattern, patternIndex + 1);
}
return false;
}
public static void main(String[] args) {
RegularExpressionMatch_19 regularExpressionMatch_19 = new RegularExpressionMatch_19();
boolean res = regularExpressionMatch_19.match("".toCharArray(), ".*".toCharArray());
System.out.println(res);
}
}
20 表示數值的字符串
使用正則表達式進行匹配
[] : 字符集合
() : 分組
? : 重復 0 ~ 1 次+ : 重復 1 ~ n 次
* : 重復 0 ~ n 次
. : 任意字符
\\. : 轉義后的 .
\\d : 數字
import java.util.ArrayList;
import java.util.List;
public class JudgeNumber_20 {
public boolean judge(String str) {
if (str == null || str.length() == 0)
return false;
return str.matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
}
public static void main(String[] args) {
JudgeNumber_20 judgeNumber_20 = new JudgeNumber_20();
String[] strings = {"+100", "5e2", "-123", "3.1416", "-1E-16",
"12e", "1a3.14", "1.2.3", "+-5", "12e+4.3" };
for (String str : strings) {
System.out.println(str + " " + judgeNumber_20.judge(str));
}
}
}
21 調整數組順序使奇數位於偶數前面
使用雙指針begin和end
begin=0,end=arr.length-1
public class OddEvenNumber_21 {
public void adjust(int[] arr) {
int begin = 0;
int end = arr.length - 1;
while (begin < end) {
while (judgeOddEven(arr[begin]) && begin < end) {
begin++;
}
while (!judgeOddEven(arr[begin]) && begin < end) {
end--;
}
swap(arr, begin, end);
}
}
public boolean judgeOddEven(int i) {
return i % 2 == 0;
}
public void swap(int[] arr, int begin, int end) {
int tmp = arr[begin];
arr[begin] = arr[end];
arr[end] = tmp;
}
public void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
OddEvenNumber_21 oddEvenNumber_21 = new OddEvenNumber_21();
int[] arr = new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 20);
}
oddEvenNumber_21.printArray(arr);
oddEvenNumber_21.adjust(arr);
oddEvenNumber_21.printArray(arr);
}
}
22 鏈表中倒數第k個節點
設置兩個指針p1、p2,p1=p2=head
讓p1先走k-1步,然后p1和p2同時走,p1走到鏈表尾結點,則p2正好走到倒數第k個節點
代碼魯棒性:
- 輸入的鏈表頭指針為null
- k=0
- 鏈表中節點個數小於k
public class TheLastKthNode_22 {
static class ListNode {
int value;
ListNode next;
ListNode(int value) {
this.value = value;
}
}
public static ListNode theLastKthNode(ListNode head, int k) {
if(head==null){
throw new RuntimeException("Error,head is null!");
}
if(k==0){
throw new RuntimeException("Error,the value of k is 0!");
}
ListNode p1 = head;
ListNode p2 = head;
int i=0;
while (i != (k - 1)) {
if(p1.next==null){
throw new RuntimeException("Error,the number of ListNode is less than k!");
}
p1 = p1.next;
i++;
}
while (p1.next != null) {
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
public static void main(String[] args) {
ListNode listNode1 = new ListNode(1);
ListNode listNode2 = new ListNode(2);
ListNode listNode3 = new ListNode(3);
ListNode listNode4 = new ListNode(4);
ListNode listNode5 = new ListNode(5);
ListNode listNode6 = new ListNode(6);
listNode1.next = listNode2;
listNode2.next = listNode3;
listNode3.next = listNode4;
listNode4.next = listNode5;
listNode5.next = listNode6;
ListNode listNode=theLastKthNode(listNode1,8);
System.out.println(listNode.value);
}
}
23 鏈表中環的入口節點
思路:
- 先判斷鏈表是否存在環,使用快慢指針,快指針一次走兩步,慢指針一次走一步,兩個指針相遇,則說明鏈表有環,記錄下相遇時候的節點LoopNode
- 計算環中的節點個數,從LoopNode節點出發,再次回到LoopNode,就得到了環中節點的個數k
- 設置兩個指針p1和p2,讓p1先走k步,然后p1和p2同時走,相遇時候的節點EntryNode即為環的入口節點
public class LoopOfLinkedList_23 {
static class LinkedList {
int value;
LinkedList next;
LinkedList(int value) {
this.value = value;
}
}
public static LinkedList findLoopNode(LinkedList head) {
if (head == null) {
throw new RuntimeException("head is null!");
}
LinkedList p1 = head.next;
LinkedList p2 = head;
LinkedList loopNode = null;
while (p1.next != null) {
p1 = p1.next;
if (p1.next != null) {
p1 = p1.next;
}
p2 = p2.next;
// System.out.println(p1.value + " " + p2.value);
if (p1 == p2) {
loopNode = p1;
break;
}
}
if (loopNode == null) {
return null;
}
int count = 1;
LinkedList tmpList=loopNode;
while (loopNode.next != tmpList) {
count++;
// System.out.println(count);
loopNode = loopNode.next;
}
p1 = p2 = head;
while (count-- > 0) {
p1 = p1.next;
}
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
public static void main(String[] args) {
LinkedList linkedList1 = new LinkedList(1);
LinkedList linkedList2 = new LinkedList(2);
LinkedList linkedList3 = new LinkedList(3);
LinkedList linkedList4 = new LinkedList(4);
LinkedList linkedList5 = new LinkedList(5);
LinkedList linkedList6 = new LinkedList(6);
linkedList1.next = linkedList2;
linkedList2.next = linkedList3;
linkedList3.next = linkedList4;
linkedList4.next = linkedList5;
linkedList5.next = linkedList6;
linkedList6.next = linkedList3;
LinkedList linkedList = findLoopNode(linkedList1);
System.out.println(linkedList.value);
}
}
24 反轉鏈表
- 非遞歸:使用一個newList節點來記錄逆向之后的頭結點
- 遞歸:每次遞歸,head.next要設置為null
public class ReverseLinkedList {
static class LinkedList {
int value;
LinkedList next;
LinkedList(int value) {
this.value = value;
}
}
//非遞歸
public static LinkedList reverse(LinkedList head) {
LinkedList newList = new LinkedList(-1);
while (head != null) {
LinkedList next = head.next;
head.next = newList.next;
newList.next = head;
head = next;
}
return newList.next;
}
//遞歸
public static LinkedList reverseByRecursive(LinkedList head){
if(head==null||head.next==null){
return head;
}
LinkedList next=head.next;
head.next=null;
LinkedList newHead=reverseByRecursive(next);
next.next=head;
return newHead;
}
public static void main(String[] args) {
LinkedList linkedList1 = new LinkedList(1);
LinkedList linkedList2 = new LinkedList(2);
LinkedList linkedList3 = new LinkedList(3);
LinkedList linkedList4 = new LinkedList(4);
LinkedList linkedList5 = new LinkedList(5);
LinkedList linkedList6 = new LinkedList(6);
linkedList1.next = linkedList2;
linkedList2.next = linkedList3;
linkedList3.next = linkedList4;
linkedList4.next = linkedList5;
linkedList5.next = linkedList6;
LinkedList reverseHead = reverse(linkedList1);
while (reverseHead!=null){
System.out.println(reverseHead.value);
reverseHead=reverseHead.next;
}
}
}
25 合並兩個排序的鏈表
public class MergeSortedLinkedList_25 {
public static LinkedList mergeSortedLinkedList(LinkedList head1, LinkedList head2) {
LinkedList head = new LinkedList(-1);
LinkedList cur = head;
while (head1 != null && head2 != null) {
if (head1.value <= head2.value) {
cur.next = head1;
head1 = head1.next;
} else {
cur.next = head2;
head2 = head2.next;
}
cur = cur.next;
}
if (head1 != null) {
cur.next = head1;
}
if (head2 != null) {
cur.next = head2;
}
return head.next;
}
public static LinkedList mergeSortedLinkedListByRecursive(LinkedList head1, LinkedList head2) {
if (head1 == null) {
return head2;
}
if (head2 == null) {
return head1;
}
if (head1.value <= head2.value) {
head1.next = mergeSortedLinkedListByRecursive(head1.next, head2);
return head1;
} else {
head2.next = mergeSortedLinkedListByRecursive(head1, head2.next);
return head2;
}
}
}
26 樹的子結構
public class SubTree_26 {
static class BinaryTreeNode {
double value;
BinaryTreeNode left;
BinaryTreeNode right;
BinaryTreeNode(int value) {
this.value = value;
}
}
//遍歷所有根節點值相同的子樹
public static boolean hasSubTree(BinaryTreeNode binaryTreeNode1, BinaryTreeNode binaryTreeNode2) {
boolean result = false;
if (binaryTreeNode1 != null && binaryTreeNode2 != null) {
if (binaryTreeNode1.value == binaryTreeNode2.value) {
result = judge(binaryTreeNode1, binaryTreeNode2);
}
if (!result) {
result = hasSubTree(binaryTreeNode1.left, binaryTreeNode2);
}
if (!result) {
result = hasSubTree(binaryTreeNode1.right, binaryTreeNode2);
}
}
return result;
}
//判斷根節點相同的子樹是否完全一樣
public static boolean judge(BinaryTreeNode binaryTreeNode1, BinaryTreeNode binaryTreeNode2) {
if (binaryTreeNode2 == null) {
return true;
}
if (binaryTreeNode1 == null) {
return false;
}
if (!Equals(binaryTreeNode1.value, binaryTreeNode2.value)) {
return false;
} else {
return judge(binaryTreeNode1.left, binaryTreeNode2.left) && judge(binaryTreeNode1.right, binaryTreeNode2.right);
}
}
//計算機表示小數(float、double)存在誤差,不能直接用等號判斷兩個小數是否相等。如果兩個小數的差的絕對值很小,
// 小於0.0000001,則認為相等
public static boolean Equals(double a, double b) {
if (Math.abs(a - b) < 0.0000001) {
return true;
} else {
return false;
}
}
}
27 二叉樹的鏡像
前序遍歷,遞歸依次交換左右節點
public class BinaryTreeMirror_27 {
class BinaryTreeNode {
int value;
BinaryTreeNode left;
BinaryTreeNode right;
}
public void mirrorRecursive(BinaryTreeNode root) {
if (root == null) {
return;
}
if (root.left == null && root.right == null) {
return;
}
BinaryTreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
if (root.left != null) {
mirrorRecursive(root.left);
}
if (root.right != null) {
mirrorRecursive(root.right);
}
}
}
28 對稱的二叉樹
思路:比較二叉樹的根左右和根右左遍歷的序列,來進行判斷
public class BinaryTreeSymmetry_28 {
class BinaryTreeNode {
int value;
BinaryTreeNode left;
BinaryTreeNode right;
}
public boolean isSymmetry(BinaryTreeNode root) {
return process(root, root);
}
public boolean process(BinaryTreeNode root1, BinaryTreeNode root2) {
if (root1 == null && root2 == null) {
return true;
}
if (root1 == null || root2 == null) {
return false;
}
if (root1.value != root2.value) {
return false;
}
return process(root1.left, root2.right) && process(root1.right, root2.left);
}
}
29 順時針打印矩陣
每次打印一個圈,可以用遞歸或者循環實現
public class ClockwisePrintMatrix_29 {
public static void clockwisePrintMatrix(int[][] arr) {
if (arr == null) {
return;
}
print(arr, 0, 0, arr.length - 1, arr[0].length - 1);
}
public static void print(int[][] arr, int leftX, int leftY, int rightX, int rightY) {
//遞歸終止條件
if (leftX > rightX || leftY > rightY) {
return;
}
//單行和單列需要單獨處理,否則會輸出重復的序列
if (leftX == rightX) {
for (int i = leftY; i <= rightY; i++) {
System.out.print(arr[leftX][i] + " ");
}
} else if (leftY == rightY) {
for (int i = leftX; i <= rightX; i++) {
System.out.print(arr[i][leftY] + " ");
}
//其他情況,順時針轉圈打印,注意邊界的處理
} else {
for (int i = leftY; i < rightY; i++) {
System.out.print(arr[leftX][i] + " ");
}
for (int i = leftX; i <= rightX; i++) {
System.out.print(arr[i][rightY] + " ");
}
for (int i = rightY - 1; i >= leftY; i--) {
System.out.print(arr[rightX][i] + " ");
}
for (int i = rightX - 1; i > leftX; i--) {
System.out.print(arr[i][leftY] + " ");
}
print(arr, ++leftX, ++leftY, --rightX, --rightY);
}
}
public static void main(String[] args) {
int[][] arr = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 16}
};
int[][] arr1 = {
{1, 2, 3, 4},
};
int[][] arr2 = {
{1}, {2},{3}, {4},
};
clockwisePrintMatrix(arr);
System.out.println();
clockwisePrintMatrix(arr1);
System.out.println();
clockwisePrintMatrix(arr2);
/* result:
* 1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10
* 1 2 3 4
* 1 2 3 4
* */
}
}
30 包含min函數的棧
使用兩個棧:dataStack和minStack。
dataStack存儲實際的數據
minStack存儲當前棧內元素最小的數據
import java.util.Stack;
public class MinStack_30 {
Stack<Integer> dataStack;
Stack<Integer> minStack;
public MinStack_30() {
dataStack = new Stack<>();
minStack = new Stack<>();
}
public void push(int data) {
dataStack.push(data);
if (minStack.isEmpty()) {
minStack.push(data);
} else {
int min = minStack.peek();
if (data < min) {
min = data;
}
minStack.push(min);
}
}
public int pop() {
int data = dataStack.pop();
minStack.pop();
return data;
}
public int min() {
return minStack.peek();
}
public static void main(String[] args) {
MinStack_30 minStack = new MinStack_30();
minStack.push(3);
System.out.println(minStack.min());
minStack.push(4);
System.out.println(minStack.min());
minStack.push(2);
System.out.println(minStack.min());
minStack.push(1);
System.out.println(minStack.min());
minStack.pop();
System.out.println(minStack.min());
}
}
31 棧的壓入、彈出序列
題目描述
輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否為該棧的彈出順序。假設壓入棧的所有數字均不相等。
例如序列 1,2,3,4,5 是某棧的壓入順序,序列 4,5,3,2,1 是該壓棧序列對應的一個彈出序列,但 4,3,5,1,2 就不可能是該壓棧序列的彈出序列。
先往棧壓入pushSequence的第一個數字pushSequence[0],然后將棧頂的數字和popSequence[index]的數字進行比較,相同就彈出棧頂元素,popIndex++;否則,pushIndex++,壓入pushSequence[pushIndex]到棧。
import java.util.Stack;
public class SequenceStack_31 {
public static boolean judgeSequenceStack(int[] pushSequence, int[] popSequence) {
//使用stack模擬棧的壓入彈出操作
Stack<Integer> stack = new Stack<>();
int n = pushSequence.length;
if (pushSequence == null || popSequence == null || n == 0) {
return false;
}
//pushIndex:壓棧序列的下標;popIndex:出棧序列的下標
for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) {
stack.push(pushSequence[pushIndex]);
//棧頂元素和出棧序列下標的元素一樣時,彈出棧頂元素,繼續比較下一個出棧序列元素
while (!stack.isEmpty() && popIndex < n && stack.peek() == popSequence[popIndex]) {
stack.pop();
popIndex++;
}
}
return stack.isEmpty();
}
public static void main(String[] args) {
int[] pushSequence = {1, 2, 3, 4, 5};
int[] popSequence1 = {4, 5, 3, 2, 1};
int[] popSequence2 = {4, 3, 5, 1, 2};
System.out.println(judgeSequenceStack(pushSequence, popSequence1));
System.out.println(judgeSequenceStack(pushSequence, popSequence2));
}
}
32-1 從上到下打印二叉樹
題目描述
從上往下打印出二叉樹的每個節點,同層節點從左至右打印。
例如,以下二叉樹層次遍歷的結果為:1,2,3,4,5,6,7
思路:使用隊列(先進先出),先將根節點壓入隊列,然后開始遍歷隊列,每彈出一個節點就輸出節點的值,然后將該節點的左右孩子壓入隊列,重復此過程直到隊列為空。
import java.util.LinkedList;
import java.util.Queue;
public class PrintBinaryTree_32 {
public static class BinaryTree {
int value;
BinaryTree left;
BinaryTree right;
BinaryTree(int value) {
this.value = value;
}
}
public static void printBinaryTree(BinaryTree root) {
if (root == null) {
return;
}
Queue<BinaryTree> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
BinaryTree binaryTree = queue.poll();
if (binaryTree.left != null) {
queue.add(binaryTree.left);
}
if (binaryTree.right != null) {
queue.add(binaryTree.right);
}
if (!queue.isEmpty()) {
System.out.print(binaryTree.value + ",");
} else {
System.out.print(binaryTree.value);
}
}
}
public static void main(String[] args) {
BinaryTree root = new BinaryTree(8);
BinaryTree left1 = new BinaryTree(6);
BinaryTree right1 = new BinaryTree(10);
BinaryTree left21 = new BinaryTree(5);
BinaryTree right21 = new BinaryTree(7);
BinaryTree left22 = new BinaryTree(9);
BinaryTree right22 = new BinaryTree(11);
root.left = left1;
root.right = right1;
left1.left = left21;
left1.right = right21;
right1.left = left22;
right1.right = right22;
printBinaryTree(root);
}
}
32-2 分行從上到下打印二叉樹
使用map來記錄節點對應的層數,從0開始計數。
public static void printBinaryTree_2(BinaryTree root) {
if (root == null) {
return;
}
Queue<BinaryTree> queue = new LinkedList<>();
int index = 0;
Map<BinaryTree, Integer> map = new HashMap();
map.put(root, index);
queue.add(root);
int pre = index;
while (!queue.isEmpty()) {
BinaryTree binaryTree = queue.poll();
index = map.get(binaryTree);
index++;
if (binaryTree.left != null) {
map.put(binaryTree.left, index);
queue.add(binaryTree.left);
}
if (binaryTree.right != null) {
map.put(binaryTree.right, index);
queue.add(binaryTree.right);
}
if (pre != map.get(binaryTree)) {
System.out.println();
pre = map.get(binaryTree);
System.out.print(binaryTree.value);
} else {
if (map.get(binaryTree) == 0) {
System.out.print(binaryTree.value);
} else {
System.out.print(" " + binaryTree.value);
}
}
}
}
32-3 之字形打印二叉樹
使用兩個棧,一個棧存儲當前層的節點,另一個棧存儲下一次的節點。設置一個標志位flag,用於調整每一層節點的存儲順序。
public static void printBinaryTree_3(BinaryTree root) {
if (root == null) {
return;
}
Stack<BinaryTree> stack1 = new Stack<>();
Stack<BinaryTree> stack2 = new Stack<>();
//記錄節點對應的層數
Map<BinaryTree, Integer> map = new LinkedHashMap<>();
int index = 0;
//根節點
map.put(root, index);
stack1.push(root);
int pre = index;
//標記變量
boolean flag = true;
/*每次處理一層,使用兩個棧來存儲節點*/
while (!stack1.isEmpty() || !stack2.isEmpty()) {
if (flag) {
while (!stack1.isEmpty()) {
BinaryTree node = stack1.pop();
index = map.get(node);
if (pre != index) {
System.out.println();
pre = index;
}
System.out.print(node.value + " ");
index++;
if (node.left != null) {
stack2.push(node.left);
map.put(node.left, index);
}
if (node.right != null) {
stack2.push(node.right);
map.put(node.right, index);
}
}
} else {
while (!stack2.isEmpty()) {
BinaryTree node = stack2.pop();
index = map.get(node);
if (pre != index) {
System.out.println();
pre = index;
}
System.out.print(node.value + " ");
index++;
if (node.right != null) {
stack1.push(node.right);
map.put(node.right, index);
}
if (node.left != null) {
stack1.push(node.left);
map.put(node.left, index);
}
}
}
flag = !flag;
}
}
33 二叉搜索樹的后序遍歷序列
題目描述
輸入一個整數數組,判斷該數組是不是某二叉搜索樹的后序遍歷的結果。假設輸入的數組的任意兩個數字都互不相同。
例如,下圖是后序遍歷序列 1,3,2 所對應的二叉搜索樹。
遞歸,后序遍歷根節點在最后面,而二叉搜索樹的特征是“左小右大”,以此為條件,來依次遍歷左右子樹。
import class_06.Edge;
public class PostOrderSequence_32 {
public static boolean judge(int[] arr) {
if (arr.length == 0) {
return false;
}
return process(arr, 0, arr.length - 1);
}
public static boolean process(int[] arr, int start, int end) {
if (end - start <= 0) {
return true;
}
// System.out.println(start);
// System.out.println(end);
int root = arr[end];
int cur = start;
while (cur < end && arr[cur] < root) {
cur++;
}
for (int i = cur; i < end; i++) {
if (arr[i] < root) {
return false;
}
}
return process(arr, start, cur - 1) && process(arr, cur, end - 1);
}
public static void main(String[] args) {
int[] arr1 = {5, 7, 6, 9, 11, 10, 8};
int[] arr2 = {7, 4, 6, 5};
int[] arr3 = {9, 11, 10, 8};
System.out.println(judge(arr1));
System.out.println(judge(arr2));
System.out.println(judge(arr3));
}
}
34 二叉樹中和為某一值的路徑
遞歸遍歷
import java.util.ArrayList;
public class SumToPath_34 {
public static class BinaryTree {
int value;
BinaryTree left;
BinaryTree right;
BinaryTree(int value) {
this.value = value;
}
}
public static void sumToPath(BinaryTree binaryTree, int target) {
process(binaryTree, target, "");
}
public static void process(BinaryTree binaryTree, int target, String str) {
if (binaryTree == null) {
return;
}
str = str +binaryTree.value+"-";
target = target - binaryTree.value;
if (target == 0 && binaryTree.left == null && binaryTree.right == null) {
System.out.println(str);
}
process(binaryTree.left, target, str);
process(binaryTree.right, target, str);
}
/* private static ArrayList<ArrayList<Integer>> listAll = new ArrayList<ArrayList<Integer>>();
private static ArrayList<Integer> list = new ArrayList<Integer>();
public static ArrayList<ArrayList<Integer>> FindPath(BinaryTree root, int target) {
if (root == null) return listAll;
list.add(root.value);
target -= root.value;
if (target == 0 && root.left == null && root.right == null)
listAll.add(new ArrayList<Integer>(list));
FindPath(root.left, target);
FindPath(root.right, target);
list.remove(list.size() - 1);
return listAll;
}*/
public static void main(String[] args) {
BinaryTree root = new BinaryTree(8);
BinaryTree left1 = new BinaryTree(6);
BinaryTree right1 = new BinaryTree(10);
BinaryTree left21 = new BinaryTree(5);
BinaryTree right21 = new BinaryTree(7);
BinaryTree left22 = new BinaryTree(9);
BinaryTree right22 = new BinaryTree(1);
root.left = left1;
root.right = right1;
left1.left = left21;
left1.right = right21;
right1.left = left22;
right1.right = right22;
sumToPath(root, 19);
/* ArrayList<ArrayList<Integer>> res = FindPath(root, 19);
for (int i = 0; i < res.size(); i++) {
System.out.println(res.get(i));
}*/
}
}
35 復雜鏈表的復制
public class CopyLinkedList_35 {
public static class ComplexListNode {
int value;
ComplexListNode next;
ComplexListNode random;
public ComplexListNode(int value) {
this.value = value;
}
}
public static ComplexListNode copy(ComplexListNode head) {
if (head == null) {
return null;
}
//第一步,復制節點
ComplexListNode cur = head;
while (cur != null) {
ComplexListNode copynode = new ComplexListNode(cur.value);
copynode.next = cur.next;
cur.next = copynode;
cur = copynode.next;
}
//第二步,建立random連接
cur = head;
while (cur != null) {
ComplexListNode copynode = cur.next;
if (cur.random != null) {
copynode.random = cur.random.next;
}
cur = copynode.next;
}
//第三步,拆分
cur = head;
ComplexListNode copyHead = head.next;
while (cur.next != null) {
ComplexListNode next = cur.next;
cur.next = next.next;
cur = next;
}
return copyHead;
}
}
36 二叉搜索樹與雙向鏈表
二叉搜索樹的中序遍歷是一個從小到大的有序序列,因此,只需在二叉樹的中序遞歸遍歷上做修改。
public class BinaryTreeLinkedList_36 {
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
//pLast 用於記錄當前鏈表的末尾節點。
private TreeNode pLast = null;
public TreeNode Convert(TreeNode root) {
if (root == null)
return null;
// 如果左子樹為空,那么根節點root為雙向鏈表的頭節點
TreeNode head = Convert(root.left);
if (head == null)
head = root;
// 連接當前節點root和當前鏈表的尾節點pLast
root.left = pLast;
if (pLast != null)
pLast.right = root;
pLast = root;
Convert(root.right);
return head;
}
}
37 序列化二叉樹
import java.util.LinkedList;
import java.util.Queue;
public class SerializationAndUnserializationBinaryTree_37 {
public static class BinaryTree {
int value;
BinaryTree left;
BinaryTree right;
BinaryTree(int value) {
this.value = value;
}
}
public static String serialization(BinaryTree root) {
if (root == null) {
return "$,";
}
String res = root.value + ",";
res += serialization(root.left);
res += serialization(root.right);
return res;
}
public static BinaryTree unSerialization(String str) {
String[] strings = str.split(",");
Queue<String> queue = new LinkedList();
for (int i = 0; i < strings.length; i++) {
queue.add(strings[i]);
}
return unSerializationByPreOrder(queue);
}
public static BinaryTree unSerializationByPreOrder(Queue<String> queue) {
String str = queue.poll();
if (str == "$") {
return null;
}
BinaryTree head = new BinaryTree(Integer.valueOf(str));
head.left = unSerializationByPreOrder(queue);
head.right = unSerializationByPreOrder(queue);
return head;
}
}
38 字符串的排列
思路:
- 求所有可能出現在第一個位置的字符
- 固定第一個字符,求后面所有字符的全排列,遞歸求解
public class PrintStringSequence_38 {
public static void printStringSequence(char[] chs, int index) {
if (index == chs.length - 1) {
System.out.print(String.valueOf(chs) + " ");
return;
}
for (int i = index; i < chs.length; i++) {
//確定第一個字符,求后面字符串的全排列
swap(chs, index, i);
printStringSequence(chs, index + 1);
//全排列完了,交換回來,保持原有順序不變
swap(chs, index, i);
}
}
private static void swap(char[] chs, int index, int i) {
char c = chs[index];
chs[index] = chs[i];
chs[i] = c;
}
public static void main(String[] args) {
char[] chs="abc".toCharArray();
printStringSequence(chs,0);
/*
* result:abc acb bac bca cba cab
* */
}
}
擴展:
八皇后問題是一個以國際象棋為背景的問題:如何能夠在 8×8 的國際象棋棋盤上放置八個皇后,使得任何一個皇后都無法直接吃掉其他的皇后?為了達到此目的,任兩個皇后都不能處於同一條橫行、縱行或斜線上。八皇后問題可以推廣為更一般的 n 皇后擺放問題:這時棋盤的大小變為 n×n,而皇后個數也變成 n 。當且僅當n = 1 或 n ≥ 4 時問題有解 。
遞歸求解:
public class EightQueens_38_2 {
public static int count = 0;
public static void process(int[] ColumnIndex, int index) {
if (index == ColumnIndex.length - 1) {
for (int i = 0; i < ColumnIndex.length; i++) {
for (int j = 0; j < ColumnIndex.length; j++) {
//判斷兩個棋子是否在哎同一對角線上
if ((i != j) && (Math.abs(i - j) == Math.abs(ColumnIndex[i] - ColumnIndex[j]))) {
return;
}
}
}
count++;
return;
}
for (int i = index; i < ColumnIndex.length; i++) {
swap(ColumnIndex, index, i);
process(ColumnIndex, index + 1);
swap(ColumnIndex, index, i);
}
}
private static void swap(int[] ColumnIndex, int index, int i) {
int tmp = ColumnIndex[index];
ColumnIndex[index] = ColumnIndex[i];
ColumnIndex[i] = tmp;
}
public static void main(String[] args) {
int[] ColumnIndex = {0, 1, 2, 3, 4, 5, 6, 7};
process(ColumnIndex, 0);
System.out.println(count);
}
}
回溯法:
import java.util.Arrays;
public class QueenSolution {
//修改棋盤的大小,可模擬其他皇后類似問題
//模擬一個8X8的棋盤,0代表沒有放置,1代表放置了一個皇后
private int[][] board = new int[8][8];
//解法的數量
private int total = 0;
/**
* 放置皇后的時候從第0行開始,依次放置一個
* 如果放置成功,那么繼續放置下一行。(0-7)
* 當要放置k=8的時候說明已經全部放置完
* 畢了,找到了一個對應的解
*
* @param k 放置第幾個皇后,K從0開始
*/
//放置第K個皇后
public void putQueen(int k) {
int max = board.length;
//放置第8個,說明棋盤已經放置完畢了,輸出結果。
if (k >= max) {
//找到一個解,打印出來。
total++;
System.out.println(String.format("=============%s===============", total));
for (int i = 0; i < max; i++) {
System.out.println(Arrays.toString(board[i]));
}
System.out.println("=============================");
} else {
for (int i = 0; i < max; i++) {
if (check(k, i)) {
board[k][i] = 1;
putQueen(k + 1);
//回溯
board[k][i] = 0;
}
}
}
}
/**
* 皇后放置的時候是從上到下每一行放置的,所以不用檢查改行以及之后的行
* 所以只用檢查列以及左上右上對角線
*
* @param row 檢查的對應行
* @param col 檢查的對應列
* @return 返回改點是否滿足可以放置一個皇后
*/
private boolean check(int row, int col) {
//檢查列是否有皇后
for (int i = 0; i < row; i++) {
if (board[i][col] == 1) {
return false;
}
}
//檢查左上對角線是否有皇后
for (int m = row - 1, n = col - 1; m >= 0 && n >= 0; m--, n--) {
if (board[m][n] == 1) {
return false;
}
}
//檢查右上對角線是否有皇后
for (int m = row - 1, n = col + 1; m >= 0 && n < board[0].length; m--, n++) {
if (board[m][n] == 1) {
return false;
}
}
return true;
}
public static void main(String[] args) {
QueenSolution solution = new QueenSolution();
solution.putQueen(0);
}
}
39 數組中出現次數超過一半的數字
public class MoreThanHalfNumber_39 {
// 摩爾投票算法 , 能夠在 O (n) 的時間和 O (1) 的空間解決問題
public static int getMoreThanHalfNumber(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int majority = nums[0];
int count = 0;
for (int i = 0; i < nums.length; i++) {
count = nums[i] == majority ? count + 1 : count - 1;
if (count == 0) {
majority = nums[i];
count = 1;
}
}
count = 0;
for (int i = 0; i < nums.length; i++) {
count = majority == nums[i] ? count + 1 : count;
}
return count > nums.length / 2 ? majority : -1;
}
}
擴展:找出所有出現次數大於 n/3 次的元素
最多只有兩個,使用投票算法,將可能符合要求的兩個元素找出來,然后再看它們是否都超過了 n/3, 來判斷是否選擇它們。
public static List<Integer> getMoreThan3_Number(int[] nums) {
if (nums == null || nums.length == 0) {
return new ArrayList<>();
}
int[] majority = new int[2];
int count1 = 0, count2 = 0;
//初始化要查找的兩個不同的元素
majority[0] = nums[0];
majority[1] = -1;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != majority[0]) {
majority[1] = nums[i];
break;
}
}
//摩爾投票算法
for (int i = 0; i < nums.length; i++) {
if (nums[i] == majority[0]) {
count1++;
} else if (nums[i] == majority[1]) {
count2++;
} else if (count1 == 0) {
majority[0] = nums[i];
count1 = 1;
} else if (count2 == 0) {
majority[1] = nums[i];
count2 = 1;
} else {
count1--;
count2--;
}
}
count1 = 0;
count2 = 0;
for (int i = 0; i < nums.length; i++) {
count1 = majority[0] == nums[i] ? count1 + 1 : count1;
count2 = majority[1] == nums[i] ? count2 + 1 : count2;
}
List<Integer> list = new ArrayList<>();
//最后再遍歷一次數組,確認元素是否超過n/3次
if (count1 > nums.length / 3) {
list.add(majority[0]);
}
if (count2 > nums.length / 3) {
list.add(majority[1]);
}
return list;
}
40 最小的k個數
使用最大堆來存儲數據,維持最大堆的節點個數為k個。
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
public class KMinNumbers_40 {
public static ArrayList<Integer> getKMinNumbers(int[] nums, int k) {
if (k > nums.length || k <= 0) {
return new ArrayList<>();
}
//用最大堆來存儲
PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
//最大堆中的結點個數超過k個,就移除堆頂結點
for (int i = 0; i < nums.length; i++) {
maxHeap.add(nums[i]);
if (maxHeap.size() > k) {
maxHeap.poll();
}
}
return new ArrayList<>(maxHeap);
}
}
41-1 數據流中的中位數
public static class Median {
private PriorityQueue<Integer> minHeap;
private PriorityQueue<Integer> maxHeap;
public Median() {
//小根堆
minHeap = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
//大根堆
maxHeap = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
}
public void add(int number) {
if (maxHeap.isEmpty()) {
maxHeap.add(number);
} else {
if (maxHeap.peek() >= number) {
maxHeap.add(number);
} else if (minHeap.isEmpty()) {
minHeap.add(number);
} else if (minHeap.peek() <= number) {
minHeap.add(number);
} else {
maxHeap.add(number);
}
}
modifyHeap();
}
//兩個堆的節點個數之差大於2,就動態調整堆
public void modifyHeap() {
if (maxHeap.size() == minHeap.size() + 2) {
minHeap.add(maxHeap.poll());
}
if (minHeap.size() == maxHeap.size() + 2) {
maxHeap.add(minHeap.poll());
}
}
//取中位數,兩個堆的節點個數加起來是偶數,中位數就是兩個堆頂節點值相加除2;如果是奇數的話,中位數是節點個數多的堆的堆頂節點值。
public double getMeDian() {
int minHeapSize = minHeap.size();
int maxHeapSize = maxHeap.size();
double median = 0;
if ((minHeapSize + maxHeapSize) % 2 == 0) {
median = (minHeap.peek() + maxHeap.peek()) / 2.0;
} else {
median = minHeapSize > maxHeapSize ? minHeap.peek() : maxHeap.peek();
}
return median;
}
}
41-2 字符流中第一個不重復的字符
題目描述
請實現一個函數用來找出字符流中第一個只出現一次的字符。例如,當從字符流中只讀出前兩個字符 "go" 時,第一個只出現一次的字符是 "g"。當從該字符流中讀出前六個字符“google" 時,第一個只出現一次的字符是 "l"。
使用隊列來存儲字符,因為隊列先進先出的特性,所以,可以從第一個字符開始判斷。然后使用一個int[256]的數組,來存儲字符出現的個數。
import java.util.LinkedList;
import java.util.Queue;
public class FindFirstChar_41_2 {
private Queue<Character> queue = new LinkedList<>();
private int[] chars = new int[256];
//Insert one char from stringstream
public void Insert(char ch) {
chars[ch]++;
queue.add(ch);
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce() {
while (!queue.isEmpty()) {
if(chars[queue.peek()]==1){
return queue.peek();
}else {
queue.poll();
}
}
return '#';
}
}
42 連續子數組的最大和
思路:
動態規划
import java.lang.reflect.Array;
public class MaxSum_42 {
public int FindGreatestSumOfSubArray(int[] arr) {
int max = Integer.MIN_VALUE;
int sum = 0;
for (int i = 0; i < arr.length; i++) {
if (sum < 0) {
max = arr[i] > max ? arr[i] : max;
sum = arr[i];
} else {
sum += arr[i];
max = sum > max ? sum : max;
}
}
return max;
}
//動態規划
public int FindGreatestSumOfSubArray_2(int[] array) {
int[] s = array.clone();
int sum = 0;
for (int i = 0; i < array.length; i++) {
if (sum < 0) {
s[i] = array[i] > s[i] ? array[i] : s[i];
sum = array[i];
} else {
sum += array[i];
s[i] = sum > s[i] ? sum : s[i];
}
}
int max = s[0];
for (int i = 0; i < s.length; i++) {
max = s[i] > max ? s[i] : max;
}
return max;
}
public static void main(String[] args) {
MaxSum_42 maxSum_42 = new MaxSum_42();
int[] arr = {6, -3, -2, 7, -15, 1, 2, 2};
System.out.println(maxSum_42.FindGreatestSumOfSubArray(arr));
}
}
43 從 1 到 n 整數中 1 出現的次數
需要使用long類型的變量,使用int類型的變量在計算過程中會溢出。
public int countDigitOne(int n) {
long sum = 0;
long num = 1;
long preNumber = 0;
long curNumber = 0;
long sufNumber = 0;
while (num <= n) {
sufNumber = n % num;
curNumber = n % (num * 10) / num;
preNumber = n / (num * 10);
if (curNumber > 1) {
sum += num * (preNumber + 1);
} else if (curNumber == 1) {
sum += num * preNumber + sufNumber + 1;
} else {
sum += num * preNumber;
}
num = num * 10;
}
int res=(int)sum;
return res;
}
}
44 數字序列中某一位數字
public class NumSequence_44 {
public int getDigitAtIndex(int index) {
if (index < 0) {
return -1;
}
int digit = 1;
while (true) {
if (index < getDigitSum(digit)) {
return digitAtIndex(index, digit);
} else {
index -= getDigitSum(digit);
digit++;
}
}
}
//計算index的下標
private int digitAtIndex(int index, int digit) {
int begin = getBeginNumber(digit);
int shiftNumber = index / digit;
String number = (begin + shiftNumber) + "";
int count = index % digit;
return number.charAt(count) - '0';
}
//得到digit位開始的數字
private int getBeginNumber(int digit) {
if (digit == 1) {
return 0;
}
return (int) Math.pow(10, digit - 1);
}
//得到digit位的數字位數的總和
public int getDigitSum(int digit) {
if (digit == 1) {
return 10;
} else {
return digit * 9 * (int) (Math.pow(10, digit - 1));
}
}
public static void main(String[] args) {
NumSequence_44 numSequence_44 = new NumSequence_44();
System.out.println(numSequence_44.getDigitAtIndex(999));
}
}
45 把數組排成最小的數
輸入一個正整數數組,把數組里所有數字拼接起來排成一個數,打印能拼接出的所有數字中最小的一個。例如輸入數組 {3,32,321},則打印出這三個數字能排成的最小數字為 321323。
可以看成是一個排序問題,在比較兩個字符串 S1 和 S2 的大小時,應該比較的是 S1+S2 和 S2+S1 的大小,如果S1+S2 < S2+S1,那么應該把 S1 排在前面,否則應該把 S2 排在前面。
import java.util.Arrays;
import java.util.Comparator;
public class MinNumber_45 {
public String PrintMinNumber(int[] numbers) {
if (numbers == null || numbers.length == 0) {
return "";
}
String[] strings = new String[numbers.length];
for (int i = 0; i < numbers.length; i++) {
strings[i] = numbers[i] + "";
}
Arrays.sort(strings, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return (o1 + o2).compareTo(o2 + o1);
}
});
/*lambda表達式
Arrays.sort(strings, (a, b) -> (a + b).compareTo(b + a));*/
String res = "";
for (int i = 0; i < strings.length; i++) {
res += strings[i];
}
return res;
}
}
46 把數字翻譯成字符串
題目描述:
給定一個數字,按照如下規則翻譯成字符串:1 翻譯成“a”,2 翻譯成“b”... 26 翻譯成“z”。一個數字有多種翻譯可能,例如 12258 一共有 5 種,分別是abbeh,lbeh,aveh,abyh,lyh。實現一個函數,用來計算一個數字有多少種不同的翻譯方法。
public class NumberToString_46 {
public int numDecodings(String s) {
if (s == null || s.length() == 0) {
return 0;
}
return digui(s, 0);
}
private int digui(String s, int start) {
if (s.length() == start) {
return 1;
}
if (s.charAt(start) == '0') {
return 0;
}
//遞歸的遞推式應該是如果index的后兩位小於等於26,
// digui(s, start) = digui(s, start+1)+digui(s, start+2)
// 否則digui(s, start) = digui(s, start+1)
int ans1 = digui(s, start + 1);
int ans2 = 0;
if (start < s.length() - 1) {
int ten = (s.charAt(start) - '0') * 10;
int one = s.charAt(start + 1) - '0';
if (ten + one <= 26) {
ans2 = digui(s, start + 2);
}
}
return ans1 + ans2;
}
//動態規划
public int numDecodingsByDP(String s) {
if (s == null || s.length() == 0) {
return 0;
}
int len = s.length();
int[] dp = new int[len + 1];
dp[len] = 1;
if (s.charAt(len-1) == '0') {
dp[len - 1] = 0;
} else {
dp[len - 1] = 1;
}
for (int i = len - 2; i >= 0; i--) {
if (s.charAt(i) == '0') {
dp[i] = 0;
continue;
}
if (((s.charAt(i) - '0') * 10 + (s.charAt(i+1)-'0')) <= 26) {
dp[i] = dp[i + 1] + dp[i + 2];
} else {
dp[i] = dp[i + 1];
}
}
return dp[0];
}
public static void main(String[] args) {
String s = "12258";
System.out.println(new NumberToString_46().numDecodingsByDP(s));
//res:5
}
}
47 禮物的最大價值
public class MaxGiftValue_47 {
//遞歸
public int getMost(int[][] board) {
if (board == null || board.length == 0 || board[0].length == 0) {
return 0;
}
return process(board, 0, 0);
}
public int process(int[][] board, int i, int j) {
int res = board[i][j];
if (i == board.length - 1 && j == board[0].length - 1) {
return res;
}
if (i == board.length - 1) {
return res + process(board, i, j + 1);
}
if (j == board[0].length - 1) {
return res + process(board, i + 1, j);
}
return res + Math.max(process(board, i + 1, j), process(board, i, j + 1));
}
//動態規划(DP)
public int getMostByDP(int[][] board) {
if (board == null || board.length == 0 || board[0].length == 0) {
return 0;
}
int rows = board.length;
int columns = board[0].length;
int[][] dp = new int[rows][columns];
dp[0][0] = board[0][0];
for (int i = 1; i < rows; i++) {
dp[i][0] = dp[i - 1][0] + board[i][0];
}
for (int j = 1; j < columns; j++) {
dp[0][j] = dp[0][j - 1] + board[0][j];
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < columns; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + board[i][j];
}
}
return dp[rows - 1][columns - 1];
}
}
48 最長不含重復字符的子字符串
題目描述:
輸入一個字符串(只包含 a~z 的字符),求其最長不含重復字符的子字符串的長度。例如對於 arabcacfr,最長不含重復字符的子字符串為 acfr,長度為 4。
思路:
動態規划,f(i)表示以第i個字符為結尾的不包含重復字符的子字符串的最長長度。如果第i個字符之前沒有出現過,那么f(i)=f(i-1)+1;如果第i個字符出現過,先計算兩個同樣字符之間的距離d,如果d>f(i-1),說明第i個字符沒有在f(i-1)的最長子字符串中出現過,f(i)=f(i-1)+1;如果d<=f(i-1),則說明第i個字符在f(i-1)的最長子字符串中出現過,f(i)=d.
public class MaxLengthSubString_48 {
public int maxLengthSubString(String s) {
if (s == null || s.length() == 0) {
return 0;
}
//int [26] 用於字母 ‘a’ - ‘z’ 或 ‘A’ - ‘Z’
//int [128] 用於 ASCII 碼
//int [256] 用於擴展 ASCII 碼
//ASCII碼128位,初始化長度為128的數組,賦值為-1,表示當前字符沒有出現
int[] pre = new int[128];
for (int i = 0; i < pre.length; i++) {
pre[i] = -1;
}
int maxLen = 0;
int curLen = 0;
for (int i = 0; i < s.length(); i++) {
int c = s.charAt(i);
int preIndex = pre[c];
if (preIndex == -1 || i - preIndex > curLen) {
curLen++;
maxLen = Math.max(curLen, maxLen);
} else {
maxLen = Math.max(curLen, maxLen);
int d = i - preIndex;
curLen = d;
}
pre[c] = i;
}
return maxLen;
}
public static void main(String[] args) {
System.out.println(new MaxLengthSubString_48().maxLengthSubString("arabcacfr"));
}
}
49 丑數
題目描述
把只包含因子 2、3 和 5 的數稱作丑數(Ugly Number)。例如 6、8 都是丑數,但 14 不是,因為它包含因子 7。習慣上我們把 1 當做是第一個丑數。求按從小到大的順序的第 N 個丑數。
鏈接:https://www.nowcoder.com/questionTerminal/6aa9e04fc3794f68acf8778237ba065b?f=discussion
來源:牛客網通俗易懂的解釋:
首先從丑數的定義我們知道,一個丑數的因子只有 2,3,5,那么丑數 p = 2 ^ x * 3 ^ y * 5 ^ z,換句話說一個丑數一定由另一個丑數乘以 2 或者乘以 3 或者乘以 5 得到,那么我們從 1 開始乘以 2,3,5,就得到 2,3,5 三個丑數,在從這三個丑數出發乘以 2,3,5 就得到 4,6,10,6,9,15,10,15,25 九個丑數,我們發現這種方 *** 得到重復的丑數,而且我們題目要求第 N 個丑數,這樣的方法得到的丑數也是無序的。那么我們可以維護三個隊列:
(1)丑數數組: 1
乘以 2 的隊列:2
乘以 3 的隊列:3
乘以 5 的隊列:5
選擇三個隊列頭最小的數 2 加入丑數數組,同時將該最小的數乘以 2,3,5 放入三個隊列;
(2)丑數數組:1,2
乘以 2 的隊列:4
乘以 3 的隊列:3,6
乘以 5 的隊列:5,10
選擇三個隊列頭最小的數 3 加入丑數數組,同時將該最小的數乘以 2,3,5 放入三個隊列;
(3)丑數數組:1,2,3
乘以 2 的隊列:4,6
乘以 3 的隊列:6,9
乘以 5 的隊列:5,10,15
選擇三個隊列頭里最小的數 4 加入丑數數組,同時將該最小的數乘以 2,3,5 放入三個隊列;
(4)丑數數組:1,2,3,4
乘以 2 的隊列:6,8
乘以 3 的隊列:6,9,12
乘以 5 的隊列:5,10,15,20
選擇三個隊列頭里最小的數 5 加入丑數數組,同時將該最小的數乘以 2,3,5 放入三個隊列;
(5)丑數數組:1,2,3,4,5
乘以 2 的隊列:6,8,10,
乘以 3 的隊列:6,9,12,15
乘以 5 的隊列:10,15,20,25
選擇三個隊列頭里最小的數 6 加入丑數數組,但我們發現,有兩個隊列頭都為 6,所以我們彈出兩個隊列頭,同時將 12,18,30 放入三個隊列;
……………………
疑問:
1. 為什么分三個隊列?
丑數數組里的數一定是有序的,因為我們是從丑數數組里的數乘以 2,3,5 選出的最小數,一定比以前未乘以 2,3,5 大,同時對於三個隊列內部,按先后順序乘以 2,3,5 分別放入,所以同一個隊列內部也是有序的;
2. 為什么比較三個隊列頭部最小的數放入丑數數組?
因為三個隊列是有序的,所以取出三個頭中最小的,等同於找到了三個隊列所有數中最小的。
實現思路:
我們沒有必要維護三個隊列,只需要記錄三個指針顯示到達哪一步;“|” 表示指針,arr 表示丑數數組;
(1)1
|2
|3
|5
目前指針指向 0,0,0,隊列頭 arr [0] * 2 = 2, arr [0] * 3 = 3, arr [0] * 5 = 5
(2)1 2
2 |4
|3 6
|5 10
目前指針指向 1,0,0,隊列頭 arr [1] * 2 = 4, arr [0] * 3 = 3, arr [0] * 5 = 5
(3)1 2 3
2| 4 6
3 |6 9
|5 10 15
目前指針指向 1,1,0,隊列頭 arr [1] * 2 = 4, arr [1] * 3 = 6, arr [0] * 5 = 5
………………
public class UglyNumber_49 {
public int getUglyNumber(int index) {
if(index==0){
return 0;
}
int res = 0;
int count = 0;
int i = 1;
while (true) {
if (judge(i)) {
count++;
if (count == index) {
res = i;
break;
}
}
i++;
}
return res;
}
private Boolean judge(int num) {
while (num % 2 == 0) {
num = num / 2;
}
while (num % 3 == 0) {
num = num / 3;
}
while (num % 5 == 0) {
num = num / 5;
}
if (num == 1) {
return true;
}
return false;
}
public int getUglyNumberByDP(int index) {
if(index==0){
return 0;
}
int i2 = 0, i3 = 0, i5 = 0;
int[] dp = new int[index];
dp[0] = 1;
for (int i = 1; i < index; i++) {
int next2 = dp[i2] * 2, next3 = dp[i3] * 3, next5 = dp[i5] * 5;
dp[i] = Math.min(next2, Math.min(next3, next5));
if (dp[i] == next2) {
i2++;
}
if (dp[i] == next3)
i3++;
if (dp[i] == next5)
i5++;
}
return dp[index - 1];
}
public static void main(String[] args) {
int num = new UglyNumber_49().getUglyNumber(1500);
System.out.println(num);
int num_1 = new UglyNumber_49().getUglyNumberByDP(1500);
System.out.println(num_1);
}
}
50-1 第一個只出現一次的字符位置
題目描述
在一個字符串中找到第一個只出現一次的字符,並返回它的位置。
Input: abacc
Output: b
使用數組cnts來記錄字符出現的次數,遍歷兩遍,時間復雜度O(n)
public class FirstNotRepeatingChar_50 {
public int FirstNotRepeatingChar(String str) {
//cnts,下標表示字符的ascii碼,值表示字符出現的次數。
int[] cnts = new int[256];
for (int i = 0; i < str.length(); i++) {
cnts[str.charAt(i)]++;
}
for (int i = 0; i < str.length(); i++) {
if (cnts[str.charAt(i)] == 1) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
System.out.println(new FirstNotRepeatingChar_50().FirstNotRepeatingChar("google"));
System.out.println(new FirstNotRepeatingChar_50().FirstNotRepeatingChar("NXWtnzyoHoBhUJaPauJaAitLWNMlkKwDYbbigdMMaYfkVPhGZcrEwp"));
char c=0;
System.out.println(c);
}
}
50-2 字符流中第一個只出現一次的字符
public class CharStatistics_50_2 {
//occurrence,下標表示字符的ascii碼,值表示字符出現的位置。
public int[] occurrence = new int[256];
//插入的字符個數
public int index;
//構造函數
public CharStatistics_50_2() {
index = 0;
for (int i = 0; i < occurrence.length; i++) {
occurrence[i] = -1;
}
}
//插入
public void insert(char ch) {
if (occurrence[ch] == -1) {
occurrence[ch] = index;
} else {
occurrence[ch] = -2;
}
index++;
}
//得到第一個只出現一次的字符
public char firstAppearingOnce() {
char ch = 0;
int minIndex = Integer.MAX_VALUE;
for (int i = 0; i < 256; i++) {
if (occurrence[i] >= 0 && occurrence[i] < minIndex) {
ch = (char) i;
minIndex = occurrence[i];
}
}
return ch;
}
}
51 數組中的逆序對
題目描述
在數組中的兩個數字,如果前面一個數字大於后面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數。
public class InversePairs_51 {
// O(n^2)
public int InversePairs(int[] array) {
if (array == null || array.length < 2) {
return 0;
}
long res = 0;
for (int i = 0; i < array.length - 1; i++) {
for (int j = i + 1; j < array.length; j++) {
if (array[i] > array[j]) {
res++;
}
}
}
return (int) (res % 1000000007);
}
//輔助數組
private int[] help;
// O(nlogn)
public int InversePairsByMergeSort(int[] array) {
if (array == null || array.length < 2) {
return 0;
}
return (int) (mergeSort(array, 0, array.length - 1) % 1000000007);
}
// 歸並排序
private long mergeSort(int[] array, int left, int right) {
if (left == right) {
return 0;
}
int mid = left + ((right - left) >> 1);
return mergeSort(array, left, mid) + mergeSort(array, mid + 1, right) + merge(array, left, mid, right);
}
private long merge(int[] array, int left, int mid, int right) {
help = new int[right - left + 1];
int i = 0;
int p1 = left;
int p2 = mid + 1;
long res = 0;
// 關鍵代碼
// 每次合並的時候,統計逆序對的個數,合並完成之后,局部有序。使用歸並排序,可以減少重復的比較次數,從而縮短時間復雜度。
while (p1 <= mid && p2 <= right) {
if (array[p1] > array[p2]) {
for (int j = p2; j <= right; j++) {
res++;
}
}
help[i++] = array[p1] > array[p2] ? array[p1++] : array[p2++];
}
while (p1 <= mid) {
help[i++] = array[p1++];
}
while (p2 <= right) {
help[i++] = array[p2++];
}
for (int j = 0; j < help.length; j++) {
array[left + j] = help[j];
}
return res;
}
public static void main(String[] args) {
int[] arr = {7, 5, 6, 4};
// System.out.println(new InversePairs_51().InversePairs(arr));
System.out.println(new InversePairs_51().InversePairsByMergeSort(arr));
}
}
52 兩個鏈表的第一個公共結點
import java.util.HashSet;
public class FindFirstCommonNode_52 {
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
// 使用HashSet
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
HashSet<ListNode> hashSet = new HashSet<>();
ListNode head1 = pHead1;
ListNode head2 = pHead2;
while (head1 != null) {
hashSet.add(head1);
head1 = head1.next;
}
while (head2 != null) {
if (hashSet.contains(head2)) {
return head2;
}
head2 = head2.next;
}
return null;
}
// 先遍歷兩個鏈表,獲得鏈表的長度,讓較長的鏈表頭指針先移動,使得兩個鏈表剩余的節點個數相同,之后,兩個鏈表的頭指針同時開始移動,
// 出現第一個相同值的節點,即為第一個公共節點。
public ListNode FindFirstCommonNode_2(ListNode pHead1, ListNode pHead2) {
int len1 = 0, len2 = 0;
ListNode head1 = pHead1;
ListNode head2 = pHead2;
while (head1 != null) {
len1++;
head1 = head1.next;
}
while (head2 != null) {
len2++;
head2 = head2.next;
}
int max = Math.max(len1, len2);
int move1 = max - len1;
int move2 = max - len2;
head1 = pHead1;
head2 = pHead2;
//下面兩個循環只會執行一個
while (move2 != 0) {
head1 = head1.next;
move2--;
}
while (move1 != 0) {
head2 = head2.next;
move1--;
}
while (head1 != null && head2 != null) {
if (head1.val == head2.val) {
return head1;
}
head1 = head1.next;
head2 = head2.next;
}
return null;
}
}
溝通能力和學習能力、知識遷移能力
53-1 數字在排序數組中出現的次數
題目描述
Input:
nums = 1, 2, 3, 3, 3, 3, 4, 6
K = 3
Output:
4
public class GetNumberOfK_53 {
public int getNumberOfK(int[] array, int k) {
// 二分查找,時間復雜度O(logn),logn是while循環的次數。
int first = getFirst(array, k);
int last = getLast(array, k);
// System.out.println(first + " " + last);
return last - first + 1;
}
//獲取k第一次出現的下標
int getFirst(int[] data, int k) {
int start = 0, end = data.length - 1;
int mid = (start + end) / 2;
while (start <= end) {
if (data[mid] < k) {
start = mid + 1;
} else {
end = mid - 1;
}
mid = (start + end) / 2;
}
// start指向第一個小於k的數的下標,while循環結束,返回的start表示k第一次出現的下標。
// 如果k不存在,則返回第一個大於k的數的下標。
return start;
}
//獲取k最后一次出現的下標
int getLast(int[] data, int k) {
int start = 0, end = data.length - 1;
int mid = (start + end) / 2;
while (start <= end) {
if (data[mid] <= k) {
start = mid + 1;
} else {
end = mid - 1;
}
mid = (start + end) / 2;
}
// end指向第一個大於k的數的下標,while循環結束,返回的end表示k最后一次出現的下標。
// 如果k不存在,則返回第一個小於k的數的下標。
return end;
}
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 3, 3, 3, 4, 6};
int[] arr2 = {3, 3, 3, 3, 4, 5};
int[] arr3 = {1, 2, 3, 4, 6};
System.out.println(new GetNumberOfK_53().getNumberOfK(arr1, 3));
System.out.println(new GetNumberOfK_53().getNumberOfK(arr2, 3));
System.out.println(new GetNumberOfK_53().getNumberOfK(arr3, 5));
}
}
53-2 0~n-1中缺失的數字
public class MissingNumber_53_2 {
//有序數組,使用二分查找
public int getMissingNumber(int[] arr, int len) {
//邊界處理
if (arr == null || len <= 0) {
return -1;
}
int left = 0;
int right = len - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (arr[mid] != mid) {
if (mid == 0 || arr[mid - 1] == mid - 1) {
return mid;
}
right = mid - 1;
} else {
left = mid + 1;
}
}
if (left == len) {
return left;
}
return -1;
}
}
53-3 數組中數值和下標相等的元素
public class NumberEqualIndex_53_3 {
public int getNumberEqualIndex(int[] arr) {
if (arr == null || arr.length == 0) {
return -1;
}
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (arr[mid] == mid) {
return mid;
} else if (arr[mid] > mid) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
}
54 二叉查找樹的第 K 個結點
二叉搜索樹的中序遍歷是一個遞增序列
public class TheKthNode_54 {
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
private int cnt = 0;
private TreeNode res = null;
TreeNode KthNode(TreeNode pRoot, int k) {
InOrder(pRoot, k);
return res;
}
public void InOrder(TreeNode pRoot, int k) {
if (pRoot == null || cnt > k) {
return;
}
InOrder(pRoot.left, k);
cnt++;
if (cnt == k) {
res = pRoot;
}
InOrder(pRoot.right, k);
}
}
55-1 二叉樹的深度
遞歸遍歷,根節點的深度是左子樹和右子樹中較大的深度+1.
public class BinaryDepth_55 {
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
public int TreeDepth(TreeNode root) {
if (root == null) {
return 0;
}
return 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right));
}
}
55-2 平衡二叉樹
題目描述:
public class IsBalanced_55_2 {
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
public boolean IsBalanced_Solution(TreeNode root) {
return getHeight(root) != -1;
}
/*
* -1:表示非平衡二叉樹
* 平衡二叉樹左右子樹高度之差不超過1,
* 因此,可以遞歸遍歷左右子樹的高度,只要左右子樹高度之差大於1,就立即返回-1
* */
private int getHeight(TreeNode root) {
if (root == null) {
return 0;
}
int left = getHeight(root.left);
if (left == -1) {
return -1;
}
int right = getHeight(root.right);
if (right == -1) {
return -1;
}
return Math.abs(left - right) > 1 ? -1 : Math.max(left, right) + 1;
}
}
56-1 數組中只出現一次的兩個數字
一個整型數組里除了兩個數字只出現一次之外,其他的數字都出現了兩次,找出這兩個只出現一次的數字。
public class AppearOnceInArray_56 {
public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
/*
位運算中異或的性質:兩個相同數字異或為0,一個數和 0 異或還是它本身。
因此,array數組中數字異或的結果二進制中的1,表示的是兩個只出現一次數字的不同的位。接下來,以結果中最右邊的1所在的位數,來分組,
從而,可以將兩個不同的數字划分到不同的分組中。
例如,{2,4,3,6,3,2,5,5},異或的結果是0010,以最右邊1所在的位置划分的結果是{2,3,6,3,2}和{4,5,5}。
diff保存的是array數組中數字異或的結果.
*/
int diff = 0;
for (int num : array) {
diff ^= num;
}
//得到diff最右側1的位置
diff = diff & (-diff);
//划分數組成兩部分
for (int num : array) {
if ((num & diff) == 0)
num1[0] ^= num;
else
num2[0] ^= num;
}
}
}
56-2 數組中唯一只出現一次的數字
一個整型數組里除了1個數字只出現一次之外,其他的數字都出現了3次,找出只出現一次的數字。
public class AppearOnceInArray_56_2 {
public static int FindNumsAppearOnce(int[] array) throws Exception {
if (array == null || array.length == 0) {
throw new Exception("input error!");
}
//bitArray保存每個二進制位數組中元素相加的和
int[] bitArray = new int[32];
int bitMask = 1;
for (int i = 31; i >= 0; i--) {
for (int j = 0; j < array.length; j++) {
int bit = bitMask & array[j];
if (bit != 0) {
bitArray[i] += 1;
}
}
bitMask = bitMask << 1;
}
int res = 0;
//將res向右移1位,然后對每個二進制位上的數字對3求余。
for (int i = 0; i < 32; i++) {
res = res << 1;
res += bitArray[i] % 3;
}
return res;
}
public static void main(String[] args) throws Exception {
int[] arr = {1, 3, 3, 3, 2, 2, 2, 1, 1, 4, 4, 4, 9};
int res = FindNumsAppearOnce(arr);
System.out.println(res);
}
}
57-1 和為s的數字
牛客
題目描述
輸入一個遞增排序的數組和一個數字 S,在數組中查找兩個數,使得他們的和正好是 S,如果有多對數字的和等於 S,輸出兩個數的乘積最小的。
輸出描述
對應每個測試案例,輸出兩個數,小的先輸出。
題解
使用兩個指針,分別指向數組的第一個元素和最后一個元素。然后開始遍歷,根據條件來移動指針。
import java.util.ArrayList;
public class SumToS_57_1 {
public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
int left = 0;
int right = array.length - 1;
int min = Integer.MAX_VALUE;
int res1 = 0;
int res2 = 0;
ArrayList arrayList = new ArrayList();
while (left < right) {
int curSum = array[left] + array[right];
if (curSum == sum) {
if (curSum < min) {
res1 = array[left];
res2 = array[right];
min = array[left] * array[right];
}
left++;
right--;
} else if (curSum > sum) {
right--;
} else {
left++;
}
}
if (min != Integer.MAX_VALUE) {
arrayList.add(res1);
arrayList.add(res2);
}
return arrayList;
}
public static void main(String[] args) {
SumToS_57_1 test = new SumToS_57_1();
int[] array = {1, 2, 3, 4, 5};
ArrayList<Integer> arrayList = test.FindNumbersWithSum(array, 10);
System.out.println(arrayList.toString());
}
}
57-2 和為s的連續正數序列
和57-1類似,這里使用small和big來表示序列的最大值和最小值,通過移動small和big的位置來
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
public class SequenceSumToS_57_2 {
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
int small = 1;
int big = 2;
ArrayList<ArrayList<Integer>> arrayLists = new ArrayList<>();
while (small <= (1 + sum) / 2) {
int curSum = getCurSum(small, big);
if (curSum == sum) {
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = small; i <= big; i++) {
arrayList.add(i);
}
arrayLists.add(arrayList);
big++;
} else if (curSum < sum) {
big++;
} else {
small++;
}
}
return arrayLists;
}
private int getCurSum(int small, int big) {
int curSum = (big - small + 1) * (small + big) / 2;
return curSum;
}
public static void main(String[] args) {
SequenceSumToS_57_2 test=new SequenceSumToS_57_2();
ArrayList<ArrayList<Integer>> arrayLists= test.FindContinuousSequence(90);
for (ArrayList<Integer> arrayList:arrayLists) {
System.out.println(arrayList.toString());
}
}
}
58-1 翻轉單詞順序列
題目描述
Input:
"I am a student."
Output:
"student. a am I
題解
public class ReverseString_58_1 {
public String ReverseSentence(String str) {
if (str != null && str.length() != 0) {
char[] chars = str.toCharArray();
reverse(chars, 0, chars.length - 1);
int begin = 0;
int end = 0;
while (begin < chars.length) {
//划分單詞,對每一個單詞進行翻轉
if (chars[end] == ' ' || end == chars.length - 1) {
int tmp_end = end;
if (chars[end] == ' ') {
tmp_end = end - 1;
}
reverse(chars, begin, tmp_end);
end++;
begin = end;
} else {
end++;
}
}
return String.valueOf(chars);
}
return str;
}
// 翻轉字符串
public void reverse(char[] chars, int begin, int end) {
while (begin < end) {
char tmp = chars[begin];
chars[begin] = chars[end];
chars[end] = tmp;
begin++;
end--;
}
}
public static void main(String[] args) {
ReverseString_58_1 test = new ReverseString_58_1();
System.out.println(test.ReverseSentence("I am a student."));
// String str=null;
// String str1="";
// System.out.println(str.length());
// System.out.println(str1.length());
}
}
58-2 左旋轉字符串
翻轉三次,根據n把字符串分成兩部分,然后分別對這兩部分進行翻轉,最后再對整個字符串進行翻轉。
public class RotateLeft_58_2 {
public String LeftRotateString(String str, int n) {
//邊界條件判斷
if (str != null && str.length() != 0) {
if (n > 0 && n < str.length()) {
char[] chars = str.toCharArray();
reverse(chars, 0, n - 1);
reverse(chars, n, chars.length - 1);
reverse(chars, 0, chars.length - 1);
return String.valueOf(chars);
}
}
return str;
}
// 翻轉字符串
public void reverse(char[] chars, int begin, int end) {
while (begin < end) {
char tmp = chars[begin];
chars[begin] = chars[end];
chars[end] = tmp;
begin++;
end--;
}
}
public static void main(String[] args) {
RotateLeft_58_2 test = new RotateLeft_58_2();
System.out.println(test.LeftRotateString("abcdefg", 2));
}
}
59-1 滑動窗口的最大值
import java.util.ArrayDeque;
import java.util.ArrayList;
public class MaxInWindows_59_1 {
public ArrayList<Integer> maxInWindows(int[] num, int size) {
ArrayList<Integer> res = new ArrayList<>();
if (num != null && num.length != 0) {
if (size > 0 && size <= num.length) {
int index = 0;
while (index + size <= num.length) {
int max = Integer.MIN_VALUE;
for (int i = index; i < size + index; i++) {
max = Math.max(max, num[i]);
}
res.add(max);
index++;
}
}
}
return res;
}
public ArrayList<Integer> maxInWindows_2(int[] num, int size) {
// 用一個雙端隊列,隊列第一個位置保存當前窗口的最大值,每當窗口滑動一次,進行下面的判斷:
// 1.判斷當前最大值是否過期
// 2.新增加的值從隊尾開始比較,把所有小於等於它的值從隊列中移除,然后再添加新增的值
ArrayList<Integer> res = new ArrayList<>();
if (num != null && num.length != 0) {
if (size > 0 && size <= num.length) {
ArrayDeque<Integer> deque = new ArrayDeque<>();
int begin = 0;
for (int i = 0; i < num.length; i++) {
//begin記錄滑動窗口的起始位置
begin = i - size + 1;
if (deque.isEmpty()) {
deque.addLast(i);
}
// 判斷當前最大值是否過期
else if (begin > deque.peekFirst()) {
deque.pollFirst();
}
// 新增加的值從隊尾開始比較,把所有小於等於它的值從隊列中移除,然后再添加新增的值
while (!deque.isEmpty() && num[deque.peekLast()] <= num[i]) {
deque.pollLast();
}
deque.addLast(i);
// 添加當前滑動窗口的最大值
if (begin >= 0) {
res.add(num[deque.peekFirst()]);
}
}
}
}
return res;
}
}
59-2 隊列的最大值
定義兩個雙端隊列data和maxData,分別用來存儲隊列的元素和當前隊列里的最大值。函數的實現和59-1類似。
import java.util.ArrayDeque;
public class MaxQueue_59_2 {
public class MaxQueue {
class InternalData {
int number;
int index;
public InternalData(int number, int index) {
this.number = number;
this.index = index;
}
}
ArrayDeque<InternalData> data;
ArrayDeque<InternalData> maxData;
int curIndex;
public MaxQueue() {
curIndex = 0;
}
public int max() throws Exception {
if (maxData.isEmpty()) {
throw new Exception("maxData is empty");
}
return maxData.pollFirst().number;
}
public void push_back(int number) {
while (!maxData.isEmpty() && maxData.peekLast().number <= number) {
maxData.pollLast();
}
InternalData internalData = new InternalData(number, curIndex);
data.addLast(internalData);
maxData.addLast(internalData);
curIndex++;
}
public void pop_front() throws Exception {
if (maxData.isEmpty()) {
throw new Exception("maxData is empty");
}
if (maxData.peekFirst() == data.peekFirst()) {
maxData.pollFirst();
}
data.pollFirst();
}
}
}
60 n個骰子的點數
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class DicesSum_60 {
//動態規划
public List<Map.Entry<Integer, Double>> dicesSum(int n) {
List<Map.Entry<Integer, Double>> res = new ArrayList<>();
// dp[i][j]表示前i個骰子產生點數j的次數
long[][] dp = new long[n + 1][6 * n + 1];
// 設置初始條件
for (int i = 1; i <= 6; i++) {
dp[1][i] = 1;
}
for (int i = 2; i <= n; i++) {
for (int j = 1 * i; j <= 6 * n; j++) {
for (int k = 1; k <= 6 && k <= j; k++) {
dp[i][j] += dp[i - 1][j - k];
}
}
}
double totalNum = Math.pow(6, n);
double p = 0;
for (int i = n; i <= 6 * n; i++) {
p = dp[n][i] / totalNum;
p = Double.valueOf(String.format("%.2f", p));
res.add(new AbstractMap.SimpleEntry<>(i, p));
}
return res;
}
//遞歸,時間復雜度較高
public List<Map.Entry<Integer, Double>> dicesSumByDiGui(int n) {
List<Map.Entry<Integer, Double>> res = new ArrayList<>();
double totalNum = Math.pow(6, n);
double p = 0;
long count = 0;
for (int i = n; i <= 6 * n; i++) {
count = getCount(n, i);
p = count / totalNum;
// 保留兩位小數
p = Double.valueOf(String.format("%.2f", p));
res.add(new AbstractMap.SimpleEntry<>(i, p));
}
return res;
}
public long getCount(int num, int sum) {
if (num < 1 || sum > 6 * num || sum < num) {
return 0;
}
if (num == 1) {
return 1;
}
long count = getCount(num - 1, sum - 1) + getCount(num - 1, sum - 2)
+ getCount(num - 1, sum - 3) + getCount(num - 1, sum - 4)
+ getCount(num - 1, sum - 5) + getCount(num - 1, sum - 6);
return count;
}
public static void main(String[] args) {
DicesSum_60 test = new DicesSum_60();
List<Map.Entry<Integer, Double>> res = test.dicesSum(5);
for (Map.Entry<Integer, Double> map : res) {
System.out.println(map.toString());
}
}
}
61 撲克牌中的順子
import java.util.Arrays;
public class ContinousCards_61 {
public boolean isContinuous(int[] numbers) {
if (numbers.length < 5) {
return false;
}
Arrays.sort(numbers);
//統計0的個數
int countZero = 0;
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] == 0)
countZero++;
}
for (int i = countZero; i < numbers.length - 1; i++) {
//判斷對子
if (numbers[i] == numbers[i + 1]) {
return false;
}
//用0填充缺失的數字
countZero = countZero - (numbers[i + 1] - numbers[i] - 1);
}
//最后只需判斷0的個數是否大於等於0,小於0則說明缺失的數字用0去填補(或者數組中沒有0)是不夠的,因此是不連續的.
return countZero >= 0;
}
}
62 圓圈中最后剩下的數字
約瑟夫環問題:https://blog.csdn.net/u011500062/article/details/72855826
public class LastRemaining_62 {
//使用數組來模擬這個過程,這可以使用鏈表來模擬
public int LastRemaining_Solution(int n, int m) {
if (n == 0 || m == 0) {
return -1;
}
int[] arr = new int[n];
int count = n;
int index = 0;
while (count != 1) {
//找到第m個數
for (int i = 1; i < m; index++) {
if (index == n) {
index = 0;
}
while (arr[index] == 1) {
index++;
if (index == n) {
index = 0;
}
}
i++;
}
if (index == n) {
index = 0;
}
for (int i = index; i <= n; i++) {
if (i == n) {
i = 0;
}
if (arr[i] == 0) {
arr[i] = 1;
index = i;
break;
}
}
count--;
// System.out.print(index + " ");
}
System.out.println();
for (int i = 0; i < n; i++) {
if (arr[i] != 1) {
return i;
}
}
return -1;
}
// 約瑟夫環問題
// 遞推公式:f(n,m)=(f(n-1,m)+m)%n
// 理解這個遞推式的核心在於關注勝利者的下標位置是怎么變的。每殺掉一個人,其實就是把這個數組向前移動了 m 位。然后逆過來,就可以得到這個遞推式。
public int LastRemaining_Solution_2(int n, int m) {
if (n == 0 || m == 0) {
return -1;
}
if (n == 1) {
return 0;
}
return (LastRemaining_Solution_2(n - 1, m) + m) % n;
}
public static void main(String[] args) {
LastRemaining_62 test = new LastRemaining_62();
System.out.println(test.LastRemaining_Solution(5, 3));
}
}
63 股票的最大利潤
public class MaxProfit_63 {
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) {
return 0;
}
// min表示前i只股票價格的最小值,maxProfile表示前i只股票獲得的最大利潤
int min = prices[0];
int maxProfit = prices[1] - prices[0];
for (int i = 1; i < prices.length; i++) {
maxProfit = maxProfit > prices[i] - min ? maxProfit : prices[i] - min;
if (prices[i] < min) {
min = prices[i];
}
}
if (maxProfit < 0) {
return 0;
}
return maxProfit;
}
public static void main(String[] args) {
MaxProfit_63 test = new MaxProfit_63();
int[] prices = {7, 1, 5, 3, 6, 4};
System.out.println(test.maxProfit(prices));
}
}
買賣股票的最佳時機 II
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你可以盡可能地完成更多的交易(多次買賣一支股票)。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
隨后,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。
示例 2:
輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,之后再將它們賣出。
因為這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。
示例 3:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。
計算連續遞增的子序列之和,時間復雜度O(n)
public class MaxProfit_63_2 {
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) {
return 0;
}
int maxProfit=0;
for (int i = 1; i < prices.length; i++) {
if(prices[i]>prices[i-1]){
maxProfit+=prices[i]-prices[i-1];
}
}
return maxProfit;
}
}
64 求 1+2+3+...+n
題目描述
要求不能使用乘除法、for、while、if、else、switch、case 等關鍵字及條件判斷語句 A ? B : C。
65. 不用加減乘除做加法public class Sum_64 {
public int Sum_Solution(int n) {
int sum = n;
boolean b = (n > 0) && ((sum += Sum_Solution(n - 1)) > 0);
return sum;
}
}
65 不用加減乘除做加法
題目描述
寫一個函數,求兩個整數之和,要求不得使用 +、-、*、/ 四則運算符號。
解題思路
a ^ b 表示沒有考慮進位的情況下兩數的和,(a & b) << 1 就是進位。
遞歸會終止的原因是 (a & b) << 1 最右邊會多一個 0,那么繼續遞歸,進位最右邊的 0 會慢慢增多,最后進位會變為0,遞歸終止。
public class Add_65 {
/*
鏈接:https://www.nowcoder.com/questionTerminal/59ac416b4b944300b617d4f7f111b215?f=discussion
來源:牛客網
首先看十進制是如何做的: 5+7=12,三步走
第一步:相加各位的值,不算進位,得到2。
第二步:計算進位值,得到10. 如果這一步的進位值為0,那么第一步得到的值就是最終結果。
第三步:重復上述兩步,只是相加的值變成上述兩步的得到的結果2和10,得到12。
同樣我們可以用三步走的方式計算二進制值相加: 5-101,7-111 第一步:相加各位的值,不算進位,得到010,二進制每位相加就相當於各位做異或操作,101^111。
第二步:計算進位值,得到1010,相當於各位做與操作得到101,再向左移一位得到1010,(101&111)<<1。
第三步重復上述兩步, 各位相加 010^1010=1000,進位值為100=(010&1010)<<1。
繼續重復上述兩步:1000^100 = 1100,進位值為0,跳出循環,1100為最終結果。
*/
public int Add(int num1, int num2) {
int res=num1^num2;
int flag=(num1&num2)<<1;
int tmp;
while(flag!=0){
tmp=res;
res=tmp^flag;
flag=(tmp&flag)<<1;
}
return res;
}
//遞歸
public int Add_1(int a, int b) {
return b == 0 ? a : Add(a ^ b, (a & b) << 1);
}
public static void main(String[] args) {
Add_65 test =new Add_65();
System.out.println(test.Add_1(111,899));
}
}
66 構建乘積數組
題目描述
給定一個數組 A[0, 1,..., n-1],請構建一個數組 B[0, 1,..., n-1],其中 B 中的元素 B[i]=A[0]A[1]...A[i-1]A[i+1]...A[n-1]。要求不能使用除法。
題解
public class Multiply_66 {
public int[] multiply(int[] A) {
if (A == null || A.length == 0) {
return A;
}
int[] B = new int[A.length];
int[] C = new int[A.length];
int[] D = new int[A.length];
C[0] = 1;
D[D.length - 1] = 1;
for (int i = 1; i < C.length; i++) {
C[i] = C[i - 1] * A[i - 1];
}
for (int i = D.length - 2; i >= 0; i--) {
D[i] = D[i + 1] * A[i + 1];
}
for (int i = 0; i < B.length; i++) {
B[i] = C[i] * D[i];
}
return B;
}
}
67 把字符串轉換成整數
題目描述
將一個字符串轉換成一個整數,字符串不是一個合法的數值則返回 0,要求不能使用字符串轉換整數的庫函數。
Iuput:
+2147483647
1a33
Output:
2147483647
0
注意邊界條件的判斷,數字不能超出Integer類型所能表示值的范圍
public class StrToInt_67 {
public int StrToInt(String str) {
if (str == null || str.length() == 0) {
return 0;
}
char[] chars = str.toCharArray();
long res = 0;
boolean isNegative = str.charAt(0) == '-';
for (int i = 0; i < chars.length; i++) {
if (i == 0 && (chars[i] == '+' || chars[i] == '-')) {
continue;
}
if (chars[i] >= '0' && chars[i] <= '9') {
res += Math.pow(10, chars.length - i - 1) * (chars[i] - '0');
} else {
return 0;
}
}
res = isNegative ? -res : res;
if (res < Integer.MIN_VALUE || res > Integer.MAX_VALUE) {
return 0;
}
return (int) res;
}
public static void main(String[] args) {
StrToInt_67 test = new StrToInt_67();
System.out.println(test.StrToInt("-2147483648"));
// System.out.println(Integer.MAX_VALUE);
// System.out.println(Integer.MIN_VALUE);
}
}
68 二叉搜索樹的最近公共祖先
給定一個二叉搜索樹,找到該樹中兩個指定節點的最近公共祖先。
百度百科中最近公共祖先的定義為:“對於有根樹 T 的兩個結點 p、q,最近公共祖先表示為一個結點 x,滿足 x 是 p、q 的祖先且 x 的深度盡可能大(一個節點也可以是它自己的祖先)。”
例如,給定如下二叉搜索樹: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
輸出: 6
解釋: 節點 2 和節點 8 的最近公共祖先是 6。
示例 2:
輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
輸出: 2
解釋: 節點 2 和節點 4 的最近公共祖先是 2, 因為根據定義最近公共祖先節點可以為節點本身。
說明:
所有節點的值都是唯一的。
p、q 為不同節點且均存在於給定的二叉搜索樹中。
算法:
- 從根節點開始遍歷樹
- 如果節點 p和節點 q 都在右子樹上,那么以右孩子為根節點繼續 1 的操作
- 如果節點 p 和節點 q 都在左子樹上,那么以左孩子為根節點繼續 1 的操作
- 如果條件 2 和條件 3 都不成立,這就意味着我們已經找到節 p 和節點 q 的 LCA 了。
public class LowestCommonAncestor_68 {
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null){
return root;
}
if (root.val > p.val && root.val > q.val) {
return lowestCommonAncestor(root.left,p,q);
}
if(root.val<p.val&&root.val<q.val){
return lowestCommonAncestor(root.right,p,q);
}
return root;
}
}
}