算法
动态规划算法
关键点
(求最值,有重叠子问题(计算过的结果用dp保存),最优子结构(最后的解包含上一步的解))
状态和选择
# 初始化 base case
dp[0][0][...] = base
# 进行状态转移
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...] = 求最值(选择1,选择2...)
- 坐标型动态规划
概念:
举例:
-
// 状态:当前的坐标(i, j),dp[i][j]代表走到当前位置的礼物的最大值 // 选择:从(i - 1, j)或者(i, j - 1)到当前的坐标 // 二维数组解法,转移方程:dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1]) + grid[i][j]; class Solution { public int maxValue(int[][] grid) { int rowLength = grid.length, columnLength = grid[0].length; int[][] dp = new int[rowLength + 1][columnLength + 1]; // 基础状态:(0, ...)(..., 0)都为0,此时不需要手动初始化 for (int i = 0; i < rowLength; i++) { for (int j = 0; j < columnLength; j ++) { dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1]) + grid[i][j]; } } return dp[rowLength][columnLength]; } } // 因为坐标(i, j)只与(i - 1, j)或者(i, j - 1)有关,所以此时可以对dp数组进行降维 // 一维数组解法,转移方程:dp[j + 1] = Math.max(dp[j], dp[j + 1]) + grid[i][j]; class Solution { public int maxValue(int[][] grid) { int rowLength = grid.length, columnLength = grid[0].length; int[] dp = new int[columnLength + 1]; // 基础状态:dp(0 ~ rowLength)都为0,所以不需要手动初始化 for (int i = 0; i < rowLength; i++) { for (int j = 0; j < columnLength; j ++) { // 第一个(j + 1)是当前的位置,第二个(j)是当前位置的左边一个位置,第三个(j + 1)是当前位置的上一个位置 dp[j + 1] = Math.max(dp[j], dp[j + 1]) + grid[i][j]; } } return dp[columnLength]; } }
-
// 状态:(i, j)代表triangle(i,j) // 选择: class Solution { public int minimumTotal(List<List<Integer>> triangle) { int[] dp = new int[triangle.get(triangle.size() - 1).size() + 1]; // 从下往上遍历 for (int i = triangle.size() - 1; i >= 0; i--) { for (int j = 0; j < triangle.get(i).size(); j++) { dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j); } } return dp[0]; } }
2. 单序列型动态规划
概念:题目中给你一个序列,这个序列中可以跳过某些元素,(这里区别于坐标型dp,因为它的路径必须一步一步的走)或者前/第i个。
举例:
-
// 状态:当前的位置i,dp[i]代表以nums[i]结尾的最长递增子序列的长度 // 选择:dp[i]需要从0...i中选取一个最长的子序列(以j结尾),并且该序列满足nums[i] > nums[j] class Solution { public int lengthOfLIS(int[] nums) { int maxLength = 0, numsLength = nums.length; int[] dp = new int[numsLength]; // 因为没有找到满足条件的序列时应该赋值为1,所以提前进行初始化 Arrays.fill(dp, 1); for (int i = 0; i < numsLength; i++) { for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } } for (int num : dp) { maxLength = Math.max(maxLength, num); } return maxLength; } }
-
// 状态:dp[i][j]代表在子数组arr(i~j)中的最长回文子序列, a b x a b y b // 选择:① 当arr[i] == arr[j],dp[i][j] = dp[i + 1][j - 1] + 2 // ② 否则dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]) // 二维数组解法 class Solution { public int longestPalindromeSubseq(String s) { int length = s.length(); int[][] dp = new int[length][length]; // 当i > j时,长度为0,当i == j时,长度为1 for (int i = 0; i < length; i++) { dp[i][i] = 1; } // 因为最终的结果是dp[0][length - 1],所以从下到上,从左到右求值 for (int i = length - 2; i >= 0; i--) { for (int j = i + 1; j < length; j++) { if (s.charAt(i) == s.charAt(j)) { dp[i][j] = dp[i + 1][j - 1] + 2; } else { dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]); } } } return dp[0][length - 1]; } } // 一维数组解法 // dp[i][j] 只与 dp[i + 1][j - 1],dp[i][j - 1],dp[i + 1][j]有关,所以可以进行降维 // 而一维的数组只能存储两个值,所以此时还需要一个变量存储dp[i + 1][j - 1], class Solution { public int longestPalindromeSubseq(String s) { int length = s.length(); int[] dp = new int[length]; Arrays.fill(dp, 1); for (int i = length - 2; i >= 0; i--) { int preLength = 0; for (int j = i + 1; j < length; j++) { int nextPreLengthBackup = dp[j]; if (s.charAt(i) == s.charAt(j)) { dp[j] = preLength + 2; } else { dp[j] = Math.max(dp[j - 1], dp[j]); } preLength = nextPreLengthBackup; } } return dp[length - 1]; } }
3. 双序列型动态规划
概念:
举例:
-
// 状态: // 选择: class Solution { public int minDistance(String word1, String word2) { } }
-
// 状态:i, j分别代表text1和text2的索引,dp[i][j] 代表text1(0 ~ i) 和 text2(0 ~ j)的最长公共子序列的长度 // 选择:①当 text1[i] == text2[j]时,dp[i][j] = dp[i - 1][j - 1] + 1, // ①否则,dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) // 二维数组 class Solution { public int longestCommonSubsequence(String text1, String text2) { int aLength = text1.length(), bLength = text2.length(); int[][] dp = new int[aLength + 1][bLength + 1]; for (int i = 0; i < aLength; i++) { for (int j = 0; j < bLength; j++) { if (text1.charAt(i) == text2.charAt(j)) { dp[i + 1][j + 1] = dp[i][j] + 1; } else { dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1]); } } } return dp[aLength][bLength]; } } // 一维数组 class Solution { public int longestCommonSubsequence(String text1, String text2) { int aLength = text1.length(), bLength = text2.length(); int[] dp = new int[bLength + 1]; for (int i = 0; i < aLength; i++) { int leftTopValue = 0; for (int j = 0; j < bLength; j++) { int nextLeftTopValueBackup = dp[j + 1]; if (text1.charAt(i) == text2.charAt(j)) { dp[j + 1] = leftTopValue + 1; } else { dp[j + 1] = Math.max(dp[j], dp[j + 1]); } leftTopValue = nextLeftTopValueBackup; } } return dp[bLength]; } }
-
import java.util.*; public class Solution { public String LCS(String str1, String str2) { if (str1.length() == 0 || str2.length() == 0) { return "-1"; } char[] s1 = str1.toCharArray(); char[] s2 = str2.toCharArray(); int start1 = -1; int start2 = -1; int[][] dp = new int[s1.length][s2.length]; int max = 0; for (int i = 0; i < s1.length; i++) { dp[i][0] = (s1[i] == s2[0] ? 1 : 0); for (int j = 0; j < s2.length; j++) { dp[0][j] = (s1[0] == s2[j] ? 1 : 0); if (i > 0 && j > 0) { if (s1[i] == s2[j]) { dp[i][j] = dp[i - 1][j - 1] + 1; } } if (max < dp[i][j]) { max = dp[i][j]; start1 = i + 1 - max; start2 = j + 1 - max; } } } if (max == 0) return "-1"; return str1.substring(start1, max + start1); } }
4. 划分型动态规划
5. 区间型动态规划
6. 背包型动态规划
概念:
举例:
0-1背包:
-
0-1背包(物品每种只有一个),给你一个可装载重量为W的背包和N个物品,每个物品有重量和价值两个属性。其中第i个物品的种类为wt[i],价值为val[i],现在让你用这个背包装物品,最多能装的价值是多少?N = 3, W = 4,wt = [2, 1, 3],val = [4, 2, 3],应该返回6。因为此时物品不能被分割,要么装进包里,要么不装。
-
// 状态:dp[i][j]代表对于前i个物品,j容量是否可以刚好消耗完 // 选择:当j < nums[i - 1],即当前剩余容量小于所需要的容量,dp[i][j] = dp[i - 1][j]; // 否则,dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]] class Solution { public boolean canPartition(int[] nums) { int sum = 0; for (int num : nums) { sum += num; } if (sum % 2 == 1) { return false; } sum = sum / 2; int rowLength = nums.length, columnLength = sum; boolean[][] dp = new boolean[rowLength + 1][columnLength + 1]; for (int i = 0; i <= rowLength; i++) { dp[i][0] = true; } for (int i = 1; i <= rowLength; i++) { for (int j = 1; j <= columnLength; j++) { if (j < nums[i - 1]) { dp[i][j] = dp[i - 1][j]; } else { dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]; } } } return dp[rowLength][columnLength]; } } class Solution { public boolean canPartition(int[] nums) { int sum = 0; for (int num : nums) { sum += num; } if (sum % 2 == 1) { return false; } sum = sum / 2; int rowLength = nums.length, columnLength = sum; boolean[] dp = new boolean[columnLength + 1]; dp[0] = true; for (int i = 1; i <= rowLength; i++) { for (int j = columnLength; j >= 0; j--) { if (j >= nums[i - 1]) { dp[j] = dp[j] || dp[j - nums[i - 1]]; } } } return dp[columnLength]; } } class Solution { public boolean canPartition(int[] nums) { int sum = 0, half = 0; for (int num : nums) { sum += num; } if (sum % 2 == 1) { return false; } boolean[] dp = new boolean[sum / 2 + 1]; dp[0] = true; return dfs(nums, sum / 2, 0, 0, dp); } private boolean dfs(int[] nums, int target, int sum, int idx, boolean[] dp) { if (idx >= nums.length) { return false; } if (sum + nums[idx] == target) { return true; } else if (sum + nums[idx] < target) { if (!dp[sum + nums[idx]]) { dp[sum + nums[idx]] = true; boolean res = dfs(nums, target, sum + nums[idx], idx + 1, dp); if (res) { return true; } } } return dfs(nums, target, sum, idx + 1, dp); } }
-
// 状态:i,j分别代表前i个物品,和剩余j的空间,dp[i][j]代表对前i个物品空间j能获得的最大价值 // 选择:求dp[i][j]时,如果当前物品的体积大于当前剩余体积,则dp[i][j] == dp[i - 1][j] // 否则,可以将当前的物品放入,也可以不放入,取两者的最大值 import java.util.*; public class Solution { public int knapsack (int V, int n, int[][] wv) { int[][] dp = new int[n + 1][V + 1]; for (int i = 1; i <= n; i++) { for (int j = 1; j <= V; j++) { if (j < wv[i - 1][0]) { dp[i][j] = dp[i - 1][j]; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - wv[i - 1][0]] + wv[i - 1][1]); } } } return dp[n][V]; } // 一维数组解法 public int knapsack (int V, int n, int[][] wv) { int[] dp = new int[V + 1]; for (int i = 1; i <= n; i++) { // 因为从左到右遍历时,dp[j - 1]会覆盖上一层的值,所以反向遍历 for (int j = V; j >= 0; j--) { if (j >= wv[i - 1][0]) { dp[j] = Math.max(dp[j], dp[j - wv[i - 1][0]] + wv[i - 1][1]); } } } return dp[V]; } }
完全背包:
-
// 状态: // 选择: class Solution { public int change(int amount, int[] coins) { int[] dp = new int[amount + 1]; dp[0] = 1; for (int i = 0; i < coins.length; i++) { for (int j = 1; j <= amount; j++) { if (j - coins[i] >= 0) { dp[j] = dp[j] + dp[j - coins[i]]; } } } return dp[amount]; } }
8. 博弈型动态规划
概念:游戏的结果一开始就被决定了,结果由游戏的状态集合、游戏初始状态以及玩家的先后手完全确定。
举例:
例题
-
/* 题目: 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。 /* /* 思路: maxValue[i] = Math.max(nums[i], currentMaxValue + nums[i], maxValue[i - 1]) 可优化成 maxValue = Math.max(Math.max(nums[i], currentMaxValue + nums[i]), maxValue) 因为由转移方程可知当前最大值只与 之前的最大值 和 当前值 或 连续到当前的序列 有关, 题目中的数组是一维,但此时设置两个变量即可,不需要设置数组 */ class Solution { public int maxSubArray(int[] nums) { int maxValue = Integer.MIN_VALUE, currentMaxValue = 0, length = nums.length; for(int i = 0; i < length; i++) { currentMaxValue = Math.max(nums[i], currentMaxValue + nums[i]); maxValue = Math.max(maxValue, currentMaxValue); } return maxValue; } }
-
/* 题目: 1 3 1 1 5 1 4 2 1 */ /* 思路: 从路线来看,要到达grid[i][j], 必然是从grid[i][j-1](上面)或者grid[i-1][j](左边)过来的, 此时 dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]) + grid[i][j] 题目中的数组是二维,此时只需要设置一维的数组即可。 */ class Solution { public int maxValue(int[][] grid) { if(grid == null || grid.length == 0 || grid[0].length == 0) { return 0; } int rows = grid.length, columns = grid[0].length, i, j; int[] dp = new int[columns]; for(i = 0; i < rows; i++) { for(j = 0; j < columns; j++) { if(j == 0) { dp[j] = dp[j] + grid[i][j]; } else { dp[j] = Math.max(dp[j], dp[j - 1]) + grid[i][j]; } } } return dp[columns - 1]; } }
-
/* 题目: 给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。 一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。 */ /* 思路: 从后往前面遍历,如果前两个数组能构成新字符串,dp[i] = dp[i-1] + dp[i-2] 如果不能 dp[i] = dp[i-1] */ class Solution { public int translateNum(int num) { int dp_1 = 1, dp_2 = 1, count = 1; while (num != 0) { if ((num % 100) >= 10 && (num % 100) < 26) { count = dp_1 + dp_2; } dp_2 = dp_1; dp_1 = count; num /= 10; } return count; } }
递归算法
例题
- 翻转链表
public class Solution { public ListNode ReverseList(ListNode head) { if (head == null || head.next == null) { return head; } else { ListNode newHead = ReverseList(head.next); ListNode nextNode = head.next; nextNode.next = head; head.next = null; return newHead; } } }
- 翻转链表的前K个节点
回溯算法
解题方法
result = []
def backtrack(路径, 选择列表)
if 满足结束条件 :
result.add(路径)
return
for 选择 in 选择列表
做选择
backtrack(路径, 选择列表)
撤销选择
例题
-
/* 题目: 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始, 每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。 例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径。 [ ["a","b","c","e"], ["s","f","c","s"], ["a","d","e","e"] ] 但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。 */ /* 思路: 因为每一个点都可能是字符串匹配的起点,所以每一个节点都要遍历。当字符串中有重复的字符时可能会出现重复匹配的情况。 所以搜索过的节点需要替换成字符串中不可能出现的字符,搜索过后需要替换回来。 */ class Solution { public boolean exist(char[][] board, String word) { char[] words = word.toCharArray(); for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { if (dfs(board, words, i, j, 0)) return true; } } return false; } /** * * @param i 当前匹配到的行 * @param j 当前匹配到的列 * @param k 表示匹配到哪一位了 * @return */ boolean dfs(char[][] board, char[] word, int i, int j, int k) { if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) { return false; } if (k == word.length - 1) { return true; } char tmp = board[i][j]; board[i][j] = '/'; boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i, j - 1, k + 1); board[i][j] = tmp; return res; } }
import java.util.*;
public class Solution {
public ArrayList<String> restoreIpAddresses (String s) {
ArrayList<String> result = new ArrayList<>();
backtrack(s, result, 3, 0);
return result;
}
private void backtrack(String tempStr, ArrayList<String> result, int dotCount, int position) {
if (dotCount == 0) {
String[] strs = tempStr.split("\\.");
for (String str : strs) {
if (str.length() == 0 || (str.length() > 1 && str.charAt(0) == '0') || Integer.parseInt(str) > 255) {
return ;
}
}
result.add(tempStr);
return;
}
if (position + 1 < tempStr.length()) {
backtrack(tempStr.substring(0, position + 1) + '.' + tempStr.substring(position + 1), result, dotCount - 1, position + 2);
}
if (position + 2 < tempStr.length()) {
backtrack(tempStr.substring(0, position + 2) + '.' + tempStr.substring(position + 2), result, dotCount - 1, position + 3);
}
if (position + 3 < tempStr.length()) {
backtrack(tempStr.substring(0, position + 3) + '.' + tempStr.substring(position + 3), result, dotCount - 1, position + 4);
}
}
}
-
import java.util.*; public class Solution { public ArrayList<String> generateParenthesis (int n) { ArrayList<String> result = new ArrayList<>(); backtrack("", result, n, n); return result; } private void backtrack(String str, ArrayList<String> result, int openCount, int closeCount) { if (closeCount == 0) { result.add(str); } if (openCount > 0) { backtrack(str + '(', result, openCount - 1, closeCount); } if (closeCount > openCount) { backtrack(str + ')', result, openCount, closeCount - 1); } } }
-
import java.util.*; public class Solution { public ArrayList<ArrayList<Integer>> permute(int[] nums) { ArrayList<ArrayList<Integer>> result = new ArrayList<>(); ArrayList<Integer> list = new ArrayList<>(); for (int num : nums) { list.add(num); } backtrack(result, list, 0); return result; } private void backtrack(ArrayList<ArrayList<Integer>> result, ArrayList<Integer> list, int currentIndex) { if (currentIndex == list.size()) { result.add(new ArrayList(list)); } for (int i = currentIndex; i < list.size(); i++) { Collections.swap(list, currentIndex, i); backtrack(result, list, currentIndex + 1); Collections.swap(list, currentIndex, i); } } }
-
class Solution { public List<List<Integer>> permuteUnique(int[] nums) { List<List<Integer>> result = new ArrayList<>(); List<Integer> list = new ArrayList<>(); for (int num : nums) { list.add(num); } backtrack(result, list, 0); return result; } private void backtrack(List<List<Integer>> result, List<Integer> list, int startIndex) { if (startIndex == list.size()) { result.add(new ArrayList(list)); } Set<Integer> set = new HashSet<>(); for (int i = startIndex; i < list.size(); i++) { if (set.contains(list.get(i))) { continue; } set.add(list.get(i)); Collections.swap(list, startIndex, i); backtrack(result, list, startIndex + 1); Collections.swap(list, startIndex, i); } } }
-
import java.util.*; public class Solution { public ArrayList<ArrayList<Integer>> combinationSum2(int[] num, int target) { Arrays.sort(num); ArrayList<ArrayList<Integer>> result = new ArrayList<>(); ArrayList<Integer> tempList = new ArrayList<>(); backTracing(result, tempList, num, 0, target); return result; } private void backTracing(ArrayList<ArrayList<Integer>> result, ArrayList<Integer> tempList, int[] num, int startIndex, int target) { if (target == 0) { result.add(new ArrayList(tempList)); } if (target > 0) { for (int i = startIndex; i < num.length; i++) { if (i > startIndex && num[i] == num[i - 1]) { continue; } tempList.add(num[i]); backTracing(result, tempList, num, i + 1, target - num[i]); tempList.remove(tempList.size() - 1); } } } }
-
import java.util.*; public class Solution { public ArrayList<ArrayList<Integer>> subsets(int[] nums) { ArrayList<ArrayList<Integer>> result = new ArrayList<>(); ArrayList<Integer> list = new ArrayList<>(); backtrack(result, list, nums, 0); Collections.sort(result, (a, b) -> { if (a.size() != b.size()) { return a.size() - b.size(); } else { int index = 0; while (index < a.size() && a.get(index) == b.get(index)) { index++; } return (index == a.size() ? 0 :a.get(index) - b.get(index)); } }); return result; } private void backtrack(ArrayList<ArrayList<Integer>> result, ArrayList<Integer> list, int[] nums, int startIndex) { result.add(new ArrayList(list)); for (int i = startIndex; i < nums.length; i++) { list.add(nums[i]); backtrack(result, list, nums, i + 1); list.remove(list.size() - 1); } } }
搜索算法
解题方法
例题
/*
题目:
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,
每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。
例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径。
[
["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]
]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
*/
/*
思路:
因为每一个点都可能是字符串匹配的起点,所以每一个节点都要遍历。当字符串中有重复的字符时可能会出现重复匹配的情况。
所以搜索过的节点需要替换成字符串中不可能出现的字符,搜索过后需要替换回来。
深度优先搜索,如果(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k])
不返回false,则一直dfs(board, word, i + 1, j, k + 1),直到(k == word.length - 1),即匹配成功。
*/
class Solution {
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (dfs(board, words, i, j, 0)) return true;
}
}
return false;
}
/**
*
* @param i 当前匹配到的行
* @param j 当前匹配到的列
* @param k 表示匹配到哪一位了
* @return
*/
boolean dfs(char[][] board, char[] word, int i, int j, int k) {
if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) {
return false;
}
if (k == word.length - 1) {
return true;
}
char tmp = board[i][j];
board[i][j] = '/';
boolean res = dfs(board, word, i + 1, j, k + 1) ||
dfs(board, word, i - 1, j, k + 1) ||
dfs(board, word, i, j + 1, k + 1) ||
dfs(board, word, i, j - 1, k + 1);
board[i][j] = tmp;
return res;
}
}
-
/* 题目: 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。 5 / \ 4 8 / / \ 11 13 4 / \ / \ 7 2 5 1 */ /* 思路: */ class Solution { List<List<Integer>> result = new ArrayList<>(); List<Integer> col = new ArrayList<>(); public List<List<Integer>> pathSum(TreeNode root, int sum) { if (root == null) { return new ArrayList<>(); } dfs(root, sum); return result; } private void dfs(TreeNode node, int num) { if (node == null) return; num -= node.val; col.add(node.val); if (num == 0 && node.left == null && node.right == null) { result.add(new ArrayList<Integer>(col)); } dfs(node.left, num); dfs(node.right, num); col.remove(col.size() - 1); } }
## 贪心算法
**解题方法**
**例题**
1. [分发饼干](https://leetcode-cn.com/problems/assign-cookies/)
```java
/*
题目:
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,
都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。
你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
*/
/*
思路:
优先满足胃口小的孩子,当所有胃口小的孩子都被满足时即是最优解。
*/
class Solution {
public int findContentChildren(int[] children, int[] cookies) {
Arrays.sort(children);
Arrays.sort(cookies);
int child = 0, cookie = 0;
//一个循环即可完成任务
while (child < children.length && cookie < cookies.length) {
if (children[child] <= cookies[cookie]) child++;
cookie++;
}
return child;
}
}
-
/* 题目: 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 示例: 输入: [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 */ /* 思路: 1. 初始化 定义一个当前和curSum,为负数的时候就清零从新累计,初始值为0; 定义一个最大和maxSum,每当curSum求出之后都要拿来比较一下,进行更新,初始值为Integer.MIN_VALUE,保证计算第一个元素的时候maxSum就更新为curSum; 2. 遍历,对每一个元素进行如下操作: 计算当前和curSum; 更新最大和maxSum; 更新当前和curSum,若为负数则清零 3.返回 */ class Solution { public int maxSubArray(int[] nums) { //特判 if(nums == null || nums.length == 0) return 0; //初始化 int curSum = 0; int maxSum = Integer.MIN_VALUE; //遍历 int len = nums.length; for(int i = 0; i < len; i++){ curSum += nums[i]; //计算curSum maxSum = Math.max(maxSum,curSum); //更新maxSum if(curSum < 0){ //更新curSum curSum = 0; } } return maxSum; } }
分治算法
解题方法
例题
-
/* 题目: 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 示例: 输入: [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 */ /* 思路: 我们定义一个操作 get(a, l, r) 表示查询 aa 序列 [l, r][l,r] 区间内的最大子段和,那么最终我们要求的答案就 是 get(nums, 0, nums.size() - 1)。 如何分治实现这个操作呢?对于一个区间 [l, r][l,r],我们取 m = (l + r) / 2 ,对区 间 [l, m][l,m] 和 [m + 1, r][m+1,r] 分治求解。当递归逐层深入直到区间长度缩小为 11 的时候, 递归「开始回升」。这个时候我们考虑如何通过 [l, m][l,m] 区间的信息和 [m + 1, r][m+1,r] 区间的信息合并成区间 [l, r][l,r] 的信息。 最关键的两个问题是: 我们要维护区间的哪些信息呢? 我们如何合并这些信息呢? 对于一个区间 [l, r][l,r],我们可以维护四个量: lSum 表示 [l, r][l,r] 内以 ll 为左端点的最大子段和 rSum 表示 [l, r][l,r] 内以 rr 为右端点的最大子段和 mSum 表示 [l, r][l,r] 内的最大子段和 iSum 表示 [l, r][l,r] 的区间和 以下简称 [l, m][l,m] 为 [l, r][l,r] 的「左子区间」,[m + 1, r][m+1,r] 为 [l, r][l,r] 的「右子区间」。 我们考虑如何维护这些量呢(如何通过左右子区间的信息合并得到 [l, r][l,r] 的信息)?对于长度为 11 的区间 [i, i][i,i], 四个量的值都和 a[i]相等。对于长度大于 11 的区间: 首先最好维护的是 iSum,区间 [l, r][l,r] 的 iSum 就等于「左子区间」的 iSum 加上「右子区间」的 iSum。 对于 [l, r][l,r] 的 lSum,存在两种可能,它要么等于「左子区间」的 lSum,要么等于「左子区间」的 iSum 加 上「右子区间」的 lSum,二者取大。对于 [l, r][l,r] 的 rSum,同理,它要么等于「右子区间」的 rSum,要么 等于「右子区间」的 iSum 加上「左子区间」的 rSum,二者取大。 当计算好上面的三个量之后,就很好计算 [l, r][l,r] 的 mSum 了。我们可以考虑 [l, r][l,r] 的 mSum 对应的区间是否 跨越 mm——它可能不跨越 mm,也就是说 [l, r][l,r] 的 mSum 可能是「左子区间」的 mSum 和 「右子区间」的 mSum 中的一个;它也可能跨越 mm,可能是「左子区间」的 rSum 和 「右子区间」的 lSum 求和。 三者取大。这样问题就得到了解决。 */ class Solution { public class Status { public int lSum, rSum, mSum, iSum; public Status(int lSum, int rSum, int mSum, int iSum) { this.lSum = lSum; this.rSum = rSum; this.mSum = mSum; this.iSum = iSum; } } public int maxSubArray(int[] nums) { return getInfo(nums, 0, nums.length - 1).mSum; } public Status getInfo(int[] a, int l, int r) { if (l == r) { return new Status(a[l], a[l], a[l], a[l]); } int m = (l + r) >> 1; Status lSub = getInfo(a, l, m); Status rSub = getInfo(a, m + 1, r); return pushUp(lSub, rSub); } public Status pushUp(Status l, Status r) { int iSum = l.iSum + r.iSum; int lSum = Math.max(l.lSum, l.iSum + r.lSum); int rSum = Math.max(r.rSum, r.iSum + l.rSum); int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum); return new Status(lSum, rSum, mSum, iSum); } }
与树有关
解题方法
例题
-
import java.util.*; public class Solution { public int[][] threeOrders (TreeNode root) { return new int[][]{preNonRecursion(root), inNonRecursion(root), postNonRecursion(root)}; } // 非递归前序遍历 private int[] preNonRecursion(TreeNode root) { if (root == null) { return new int[]{}; } ArrayList<Integer> list = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode tempNode = stack.pop(); list.add(tempNode.val); if (tempNode.right != null) { stack.push(tempNode.right); } if (tempNode.left != null) { stack.push(tempNode.left); } } return list.stream().mapToInt(Integer::intValue).toArray(); } // 非递归中序遍历 private int[] inNonRecursion(TreeNode root) { if (root == null) { return new int[]{}; } ArrayList<Integer> list = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode nextNode = root; while (!stack.isEmpty() || nextNode != null) { while (nextNode != null) { stack.push(nextNode); nextNode = nextNode.left; } TreeNode tempNode = stack.pop(); list.add(tempNode.val); if (tempNode.right != null) { nextNode = tempNode.right; } } return list.stream().mapToInt(Integer::intValue).toArray(); } // 非递归后序遍历 private int[] postNonRecursion(TreeNode root) { if (root == null) { return new int[]{}; } ArrayList<Integer> list = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode preNode = root; stack.push(root); while (!stack.isEmpty()) { TreeNode peekNode = stack.peek(); if (peekNode.left != null && peekNode.left != preNode && peekNode.right != preNode) { stack.push(peekNode.left); } else if (peekNode.right != null && peekNode.right != preNode) { stack.push(peekNode.right); } else { preNode = stack.pop(); list.add(preNode.val); } } return list.stream().mapToInt(Integer::intValue).toArray(); } }
-
import java.util.*; public class Solution { public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) { ArrayList<ArrayList<Integer>> result = new ArrayList<>(); if (root == null) { return result; } LinkedList<TreeNode> queue = new LinkedList<>(); queue.addLast(root); while (!queue.isEmpty()) { ArrayList<Integer> list = new ArrayList<>(); int currentNodeCount = queue.size(); for (int i = 0; i < currentNodeCount; i++) { TreeNode tempNode = queue.removeFirst(); list.add(tempNode.val); if (tempNode.left != null) { queue.addLast(tempNode.left); } if (tempNode.right != null) { queue.addLast(tempNode.right); } } result.add(list); } return result; } }
-
// 递归解法 class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode o1, TreeNode o2) { if (root == null || root == o1 || root == o2) { return root; } TreeNode left = lowestCommonAncestor(root.left, o1, o2); TreeNode right = lowestCommonAncestor(root.right, o1, o2); if (left != null && right != null) { return root; } else { return (left == null ? right : left); } } } // 非递归解法 class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { //记录遍历到的每个节点的父节点。 Map<TreeNode, TreeNode> parent = new HashMap<>(); Queue<TreeNode> queue = new LinkedList<>(); parent.put(root, null);//根节点没有父节点,所以为空 queue.add(root); //直到两个节点都找到为止。 while (!parent.containsKey(p) || !parent.containsKey(q)) { //队列是一边进一边出,这里poll方法是出队, TreeNode node = queue.poll(); if (node.left != null) { parent.put(node.left, node); queue.add(node.left); } if (node.right != null) { parent.put(node.right, node); queue.add(node.right); } } Set<TreeNode> ancestors = new HashSet<>(); //记录下p和他的祖先节点,从p节点开始一直到根节点。 while (p != null) { ancestors.add(p); p = parent.get(p); } //查看p和他的祖先节点是否包含q节点,如果不包含再看是否包含q的父节点…… while (!ancestors.contains(q)) { q = parent.get(q); } return q; } }
-
public class Solution { public boolean IsBalanced_Solution(TreeNode root) { if (root == null) { return true; } return Math.abs(isBalancedHelper(root.left) - isBalancedHelper(root.right)) <= 1; } private int isBalancedHelper(TreeNode root) { if (root == null) { return 0; } return Math.max(isBalancedHelper(root.left), isBalancedHelper(root.right)) + 1; } } public class Solution { public boolean IsBalanced_Solution(TreeNode root) { return isBalancedHelper(root) != -1; } private int isBalancedHelper(TreeNode root) { if (root == null) { return 0; } int leftDepth = isBalancedHelper(root.left); if (leftDepth == -1) { return -1; } int rightDepth = isBalancedHelper(root.right); if (rightDepth == -1) { return -1; } return (Math.abs(leftDepth - rightDepth) <= 1 ? Math.max(leftDepth, rightDepth) + 1 : -1); } }
-
public class Solution { public void Mirror(TreeNode root) { if (root != null) { TreeNode tempNode = root.left; root.left = root.right; root.right = tempNode; Mirror(root.left); Mirror(root.right); } } } public class Solution { public void Mirror(TreeNode root) { if (root == null) { return; } Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode tempNode = stack.pop(); swap(tempNode); if (tempNode.left != null) { stack.push(tempNode.left); } if (tempNode.right != null) { stack.push(tempNode.right); } } } private void swap(TreeNode root) { TreeNode tempNode = root.left; root.left = root.right; root.right = tempNode; } }
-
-
-
/* 题目: 输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构) B是A的子结构, 即 A中有出现和B相同的结构和节点值。 例如: 给定的树 A: 3 / \ 4 5 / \ 1 2 给定的树 B: 4 / 1 返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。 */ /* 思路: 要解该题需要完成两个任务: ①先序遍历每一个节点,即先判断A节点是和B相等的结点 ②判断每个节点是否是B子树的根节点 */ class Solution { public boolean isSubStructure(TreeNode A, TreeNode B) { return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B)); } boolean recur(TreeNode A, TreeNode B) { if(B == null) return true; if(A == null || A.val != B.val) return false; return recur(A.left, B.left) && recur(A.right, B.right); } }
-
/* 题目: 请完成一个函数,输入一个二叉树,该函数输出它的镜像。 例如输入: 4 / \ 2 7 / \ / \ 1 3 6 9 镜像输出: 4 / \ 7 2 / \ / \ 9 6 3 1 */ /* 思路: 先序遍历每一个节点,交换其左孩子和右孩子。 */ class Solution { public TreeNode mirrorTree(TreeNode root) { if(root != null){ TreeNode temp = root.left; root.left = root.right; root.right = temp; mirrorTree(root.left); mirrorTree(root.right); } return root; } }
-
/* 题目: 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 1 / \ 2 2 / \ / \ 3 4 4 3 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 1 / \ 2 2 \ \ 3 3 */ /* 思路: 借助一个辅助函数来判断两个节点是否对称。 当两个节点都为 null 时返回true 当两个节点有一个为空,另一个不为空,或者值不相等时返回false 否则继续往下判断,return (a.left, b.right) && (a.right, b.left) */ class Solution { public boolean isSymmetric(TreeNode root) { return root == null ? true : recur(root.left, root.right); } boolean recur(TreeNode leftNode, TreeNode rightNode) { if (leftNode == null && rightNode == null){ return true; } if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) { return false; } return recur(leftNode.left, rightNode.right) && recur(leftNode.right, rightNode.left); } }
-
/* 题目: 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。 示例: 给定如下二叉树,以及目标和 sum = 22, 5 / \ 4 8 / / \ 11 13 4 / \ / \ 7 2 5 1 返回: [ [5,4,11,2], [5,8,4,5] ] */ /* 思路:设置两个实例变量(一个存储结果集,一个临时存放当前序列)存储结果。 设置一个辅助函数用来先序遍历,将值添加到临时序列,tar -= val 如果tar == 0,说明此时已经找到路径。 否则继续遍历 最后将值弹出临时序列。 */ class Solution { LinkedList<List<Integer>> res = new LinkedList<>(); LinkedList<Integer> path = new LinkedList<>(); public List<List<Integer>> pathSum(TreeNode root, int sum) { recur(root, sum); return res; } void recur(TreeNode root, int tar) { if(root == null) return; path.add(root.val); tar -= root.val; if(tar == 0 && root.left == null && root.right == null) res.add(new LinkedList(path)); recur(root.left, tar); recur(root.right, tar); path.removeLast(); } }
-
/* 题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。 */ /* 思路:二叉搜索数的中序遍历的结果为从小到大排序。 设置两个实例变量(pre存储上一次的结果,head存储头结点) 当节点为null时return 否则进行中序遍历,处理第一个节点时,head和pre都为空, 此时只需要判断pre是否为空,如果为空,说明遍历到了值最小的节点 */ class Solution { Node head, pre; public Node treeToDoublyList(Node root) { if (root == null){ return null; } dfs(root); pre.right = head; head.left = pre; return head; } public void dfs(Node cur) { if (cur == null){ return; } dfs(cur.left); if (pre == null){ head = cur; } else { pre.right = cur; } cur.left = pre; pre = cur; dfs(cur.right); } }
-
/* 题目:给定一棵二叉搜索树,请找出其中第k大的节点。 示例 1: 输入: root = [3,1,4,null,2], k = 1 3 / \ 1 4 \ 2 输出: 4 示例 2: 输入: root = [5,3,6,2,4,null,null,1], k = 3 5 / \ 3 6 / \ 2 4 / 1 输出: 4 */ /* 思路: 定义两个实例变量,存储k和需要返回的值。 中序遍历二叉搜索树。一直左递归,在处理逻辑处判断和减一,然后右递归。 */ class Solution { int res, k; public int kthLargest(TreeNode root, int k) { this.k = k; dfs(root); return res; } void dfs(TreeNode root) { if(root == null) return; dfs(root.right); if(k == 0) return; if(--k == 0) res = root.val; dfs(root.left); } }
-
/* 题目: 输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。 例如: 给定二叉树 [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 返回它的最大深度 3 。 */ /* 思路: 根节点的深度为 max(root.left, root.right) + 1,所以遍历一遍即可完成任务。 */ class Solution { public int maxDepth(TreeNode root) { if(root == null) return 0; return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; } }
-
/* 题目: 输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。 示例 1: 给定二叉树 [3,9,20,null,null,15,7] 3 / \ 9 20 / \ 15 7 返回 true 示例 2: 给定二叉树 [1,2,2,3,3,null,null,4,4] 1 / \ 2 2 / \ 3 3 / \ 4 4 返回 false */ /* 思路: 设置一个辅助函数,返回的是深度或者-1(-1表示不是平衡树)。 在辅助函数中,如果root为null,则返回0 求左孩子的深度,如果左孩子深度为-1,表示左孩子不是平衡二叉树,则返回-1 求右孩子的深度,如果右孩子深度为-1,表示右孩子不是平衡二叉树,则返回-1 如果左右孩子深度之差的绝对值大于2,说明root为根节点的树不是二叉树,此时也返回-1; 否则返回二叉树的max(root.left, root.right) + 1 */ class Solution { public boolean isBalanced(TreeNode root) { return recur(root) != -1; } private int recur(TreeNode root) { if (root == null) { return 0; } int left = recur(root.left); if(left == -1) { return -1; } int right = recur(root.right); if(right == -1) { return -1; } return (Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1); } }
-
/* 题目: 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x, 满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” */ /* 思路: 当 root.val < p.val && root.val < q.val 时,表示p 和 q在右子树中 当 root.val > p.val && root.val > q.val 时,表示p 和 q在左子树中 否则root就是两个节点的最近公共节点。 */ class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { while(root != null) { if(root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中 root = root.right; // 遍历至右子节点 else if(root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中 root = root.left; // 遍历至左子节点 else break; } return root; } } // 保证 p.val < q.val可减少循环次数。 class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if(p.val > q.val) { // 保证 p.val < q.val TreeNode tmp = p; p = q; q = tmp; } while(root != null) { if(root.val < p.val) // p,q 都在 root 的右子树中 root = root.right; // 遍历至右子节点 else if(root.val > q.val) // p,q 都在 root 的左子树中 root = root.left; // 遍历至左子节点 else break; } return root; } }
-
/* 题目: 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x, 满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4] */ /* 思路: */
位运算
解题方法
巧妙的利用 &
, |
, ^
去解决一些问题
例题
-
/* 题目: 请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。 */ /* 思路: n &= n - 1 会去除数字n的最低位上的1,一直循环即可完成任务。 */ public class Solution { public int hammingWeight(int n) { int res = 0; while(n != 0) { res++; n &= n - 1; } return res; } }
-
BitSet
// 在一个没有负数的数组中,有一个数出现了两次,其他数都只出现了一次,找出这个重复的数,如果数不存在,则返回-1 public static int getRepetiveNumber(int[] nums) { int maxNum = Integer.MIN_VALUE, index_1, index_2; for (int num : nums) { maxNum = Math.max(num, maxNum); } int[] bitMap = new int[1 + maxNum / 32]; for (int i = 0; i < nums.length; i++) { index_1 = nums[i] / 32; index_2 = 32 - nums[i] % 32; if ( (bitMap[index_1] & (1 << index_2) ) != 0) { return nums[i]; } else { bitMap[index_1] += 1 << index_2; } } return -1; }
二分查找
解题方法
模板
-
普通二分查找
public int binarySearch(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { return mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid - 1; } } return -1; }
-
左边界二分查找
public int left_bound (int[] nums, int target) { if (nums.length == 0) { return -1; } int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { right = mid - 1; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid - 1; } } return (left >= nums.length || nums[left] != target) ? -1 : left; }
-
右边界二分查找
public int right_bound(int[] nums, int target) { if (nums.length == 0) { return -1; } int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { left = mid + 1; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid - 1; } } return (right < 0 || nums[right] != target) ? -1 : right; }
例题
-
/* 题目: 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转, 输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。 示例 1: 输入:[3,4,5,1,2] 输出:1 示例 2: 输入:[2,2,2,0,1] 输出:0 */ /* 思路: 有序或者部分有序的数组的查找,考虑用二分查找。 位置 0 1 2 3 4 5 1 2 3 4 5 6 5 6 1 2 3 4 4 5 6 1 2 3 1 1 1 1 0 1 1 0 1 1 1 1 i = left, j = right, m = (i + j) / 2 如果nums[m] < nums[j] 有1 2 两种情况,此时最小值在左半边,此时令 j = m(最小值可能就是nums[m]) 如果nums[m] > nums[j] 只有3 一种情况,此时最小值在右半边,此时令 i = m + 1 如果nums[m] == nums[j] 有4 5两种情况,此时无法判断最小值的位置,此时直接搜索(i, j)(开区间) */ class Solution { public int minArray(int[] numbers) { int i = 0, j = numbers.length - 1; while (i < j) { int m = (i + j) / 2; if (numbers[m] > numbers[j]) i = m + 1; else if (numbers[m] < numbers[j]) j = m; else { int x = i; for(int k = i + 1; k < j; k++) { if(numbers[k] < numbers[x]) x = k; } return numbers[x]; } } return numbers[i]; } }
单调栈
特点:下一个比当前值大或者小的值
-
// 循环寻找下一个比当前元素大的值,如果不存在,则为-1,否则填入那个大的数 class Solution { public int[] nextGreaterElements(int[] nums) { int len = nums.length; int[] res = new int[len]; Stack<Integer> stack = new Stack<>(); for (int i = 2 * len - 1; i >= 0; i--) { // 从后往前遍历,如果当前值比栈顶部的要大,则将栈内元素出栈 while (!stack.isEmpty() && stack.peek() <= nums[i % len]) { stack.pop(); } res[i % len] = stack.isEmpty() ? -1 : stack.peek(); stack.push(nums[i % len]); } return res; } }
-
一个月有多少天
// 给定一个数组,存储的是每天的天气,对于每一天,你还要至少等多久才能等到一个温度更高的气温,如果等不到则填0. // input : [73, 74, 75, 71, 69, 76] // output : [1, 1, 3, 2, 1, 0] class Solution { public int[] nextWarmerDay(int[] nums) { int len = nums.length; int[] res = new int[len]; // 栈中存放索引值,因为返回数组中存的是要等待的天数 Stack<Integer> stack = new Stack<>(); for (int i = len; i >= 0; i--) { // 从后往前遍历,如果当前值比栈顶部索引对应的值的要大,则将栈内元素出栈 while (!stack.isEmpty() && nums[stack.peek()] <= nums[i]) { stack.pop(); } // 计算出天数 res[i] = stack.isEmpty() ? 0 : (stack.peek() - i); stack.push(i); } return res; } }
单调队列
-
import java.util.*; public class Solution { LinkedList<Integer> queue = new LinkedList<>(); public ArrayList<Integer> maxInWindows(int [] nums, int size) { if (size == 0 || size > nums.length) { return new ArrayList<Integer>(); } ArrayList<Integer> res = new ArrayList<>(); for (int i = 0; i < nums.length; i++) { // 窗口还没满 if (i < size - 1) { push(nums[i]); } else { push(nums[i]); res.add(queue.getFirst()); pop(nums[i - size + 1]); } } return res; } public void push(int value) { // 将队列尾部小于value的元素全部出队 while (!queue.isEmpty() && queue.getLast() < value) { queue.pollLast(); } queue.addLast(value); } public void pop(int value) { // 如果队列头部元素是value,则将队列头出队 if (value == queue.getFirst()) { queue.pollFirst(); } } }
滑动窗口
-
/* 给出两个字符串 SS 和 TT,要求在O(n)O(n)的时间复杂度内在 SS 中找出最短的包含 TT 中所有字符的子串。 例如: S ="XDOYEZODEYXNZ"S="XDOYEZODEYXNZ" T ="XYZ"T="XYZ" 找出的最短子串为"YXNZ""YXNZ". 注意: 如果 SS 中没有包含 TT 中所有字符的子串,返回空字符串 “”; 满足条件的子串可能有很多,但是题目保证满足条件的最短的子串唯一。 */ public class Solution { /* 新建一个needs[255] 用来统计t中每个字符出现次数,新建一个 window[255]用 来统计滑动窗口中每个字符出现次数。首先统计出T中每个字母出现的次数. 新建 两个变量left和right分别用来表示滑动窗口的左边和右边。 新建一个变量count来表示目前窗口中已经找到了多少个字符。 */ public String minWindow (String s, String t) { if (s == null || s == "" || t == null || t == "" || s.length() < t.length()) { return ""; } // 用来统计t中每个字符出现次数 int[] needs = new int[128]; // 用来统计滑动窗口中每个字符出现次数 int[] window = new int[128]; for (int i = 0; i < t.length(); i++) { needs[t.charAt(i)]++; } int left = 0, right = 0; String res = ""; // 目前有多少个字符 int count = 0; // 用来记录最短需要多少个字符。 int minLength = s.length() + 1; while (right < s.length()) { char ch = s.charAt(right); window[ch]++; if (needs[ch] > 0 && needs[ch] >= window[ch]) { count++; } // 移动到不满足条件为止 while (count == t.length()) { ch = s.charAt(left); if (needs[ch] > 0 && needs[ch] >= window[ch]) { count--; } if (right - left + 1 < minLength) { minLength = right - left + 1; res = s.substring(left, right + 1); } window[ch]--; left++; } right++; } return res; } }
并查集
随机化算法
双指针
解题方法
- 前后指针
- 快慢指针
例题
-
/* 题目: 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。 输入:nums = [1,2,3,4] 输出:[1,3,2,4] 注:[3,1,2,4] 也是正确的答案之一。 */ /* 思路: 前后指针解法:设置两个变量 left、right,当left < right时,从左边选一个偶数,从右边选一个奇数,交换位置。 快慢指针:设置两个变量 slow、fast,当fast < length时,当 nums[fast] 为奇数时,交换 nums[slow] 和 nums[fast], 并且slow++,且不论 nums[fast] 是否为奇数,fast都执行自增操作。 */ public void swap(int[] nums,int a,int b){ int temp = nums[a]; nums[a] = nums[b]; nums[b] =temp; return; } //前后指针 class Solution { public int[] exchange(int[] nums) { int i = 0, j = nums.length - 1, tmp; while(i < j) { while(i < j && (nums[i] & 1) == 1) i++; while(i < j && (nums[j] & 1) == 0) j--; swap(nums, i, j); } return nums; } } //快慢指针 class Solution { public int[] exchange(int[] nums) { int slow = 0,fast = 0; while(fast<nums.length){ if((nums[fast] & 1) == 1) { swap(nums,slow,fast); slow++; } fast++; } return nums; } }
-
/* 题目: 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL */ /* 思路: 画一个链表,即可看出,要完成任务需要三个变量(head可复用)。 */ /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode reverseList(ListNode head) { ListNode left = null, temporyNode; while(head != null){ temporyNode = head.next; head.next = left; left = head; head = temporyNode; } return left; } }
-
/* 题目: 输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。 例如输入字符串"I am a student. ",则输出"student. a am I"。 首尾或者中间可能有多个空格。 */ /* 思路: 先去掉首尾的空格,然后从末端开始处理字符串。left = right。 */ class Solution { public String reverseWords(String s) { s = s.trim(); // 删除首尾空格 int right = s.length() - 1, left = right; StringBuilder res = new StringBuilder(); while(left >= 0) { while(left >= 0 && s.charAt(left) != ' ') left--; // 搜索首个空格 res.append(s.substring(left + 1, right + 1) + " "); // 添加单词 while(left >= 0 && s.charAt(left) == ' ') left--; // 跳过单词间空格 right = left; // j 指向下个单词的尾字符 } return res.toString().trim(); // 转化为字符串并返回 } }
-
public boolean hasCycle(ListNode head) { ListNode fast = head, slow = head; while(fast != null && fast.next != null){ fast = fast.next.next; slow = slow.next; if(fast == slow) return true; } return false; }
排序算法
复杂度
解题方法
例题
-
选择排序
public static void selectSort(int[] nums) { if (nums == null || nums.length == 0) { return; } int length = nums.length, maxNumIndex = 0, i, j; for (i = 0; i < length - 1; i++) { for (j = 1; j < length - i; j++) { if (nums[j] > nums[maxNumIndex]) { maxNumIndex = j; } } exchange(nums, maxNumIndex, j - 1); maxNumIndex = 0; } }
-
冒泡排序
public static void bubbleSort(int[] nums) { if (nums == null || nums.length == 0) { return; } int length = nums.length; for (int i = 0; i < length - 1; i++) { for (int j = 0; j < length - i - 1; j++) { if (nums[j] > nums[j + 1]) { exchange(nums, j, j + 1); } } } }
-
插入排序
public static void insertSort(int[] nums) { if (nums == null || nums.length == 0) { return; } int length = nums.length; for (int i = 1; i < length; i++) { for (int j = i; j > 0; j--) { if (nums[j] >= nums[j - 1]) { break; } exchange(nums, j, j - 1); } } }
-
希尔排序
public static void shellSort(int[] nums) { if (nums == null || nums.length == 0) { return; } int length = nums.length; int h = 1; while (h < length / 3) { h = 3 * h + 1; } while (h >= 1) { for (int i = h; i < length; i++) { for (int j = i; j >= h; j -= h) { if (nums[j] >= nums[j - h]) { break; } exchange(nums, j, j - h); } } h /= 3; } }
-
归并排序
public static void mergeSort_Recursion(int[] nums) { if (nums == null || nums.length == 0) { return; } int length = nums.length; int[] tempArray = new int[length]; mergeSort_Recursion(nums, tempArray, 0, length - 1); } private static void mergeSort_Recursion(int[] nums, int[] tempArray, int low, int high) { if (low >= high) { return; } int middle = (low + high) / 2; mergeSort_Recursion(nums, tempArray, low, middle); mergeSort_Recursion(nums, tempArray, middle + 1, high); //最后一行才调用递归,任意时刻只有一个方法在使用临时数组,所以不会出现临时数组混乱的情况 //当前一个有序数组的最后一个元素大于后一个有序数组时才需要排序 if (nums[middle] > nums[middle + 1]) { merge(nums, tempArray, low, middle, high); } } private static void merge(int[] nums, int[] tempArray, int low, int middle, int high) { int i = low, j = middle + 1; for (int k = low; k <= high; k++) { tempArray[k] = nums[k]; } for (int k = low; k <= high; k++) { if ((j > high || tempArray[i] < tempArray[j]) && i <= middle) { nums[k] = tempArray[i++]; } else { nums[k] = tempArray[j++]; } } } //非递归的归并排序 public static void mergeSort_NonRecursion(int[] nums) { if (nums == null || nums.length == 0) { return; } int length = nums.length; int[] tempArray = new int[length]; for (int sz = 1; sz < length; sz = sz + sz) { for (int lo = 0; lo < length - sz; lo += sz + sz) { merge(nums, tempArray, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, length - 1)); } } }
-
快速排序
public static void quickSort(int[] nums) { if (nums == null || nums.length == 0) { return; } int length = nums.length; quickSort(nums, 0, length - 1); } private static void quickSort(int[] nums, int low, int high) { if (low >= high) { return; } int cursor = partition(nums, low, high); quickSort(nums, low, cursor - 1); quickSort(nums, cursor + 1, high); } private static int partition(int[] nums, int low, int high) { int i = low + 1, j = high; while (i < j) { while (i < j && nums[j] >= nums[low]) { j--; } while (i < j && nums[i] < nums[low]) { i++; } exchange(nums, i, j); } if (nums[low] > nums[j]) { exchange(nums, low, j); return j; } else { return low; } }
-
随机化快速排序
private void quickSort(int[] arr,int low,int high){ if(low < high){ int i = low, j = high; int randomIndex = new Random().nextInt(high - low + 1) + low; swap(arr, low, randomIndex); int key = arr[low]; while(i < j){ // 需要找一个比基准值小的 while(i < j && arr[j] >= key){ j--; } if(i < j){ arr[i] = arr[j]; } // 需要找一个比基准值大的 while(i < j && arr[i] <= key){ i++; } if(i < j){ arr[j] = arr[i]; } } arr[i] = key; quickSort(arr, low, i - 1); quickSort(arr, i + 1, high); } } private void swap(int[] arr, int index1, int index2) { int temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; }
-
最小的K个数(快速排序解法)
private void quickSort(int[] arr,int low,int high, int k){ if(low < high){ int i = low, j = high; int randomIndex = new Random().nextInt(high - low + 1) + low; swap(arr, low, randomIndex); int key = arr[low]; while(i < j){ // 需要找一个比基准值小的 while(i < j && arr[j] >= key){ j--; } if(i < j){ arr[i] = arr[j]; } // 需要找一个比基准值大的 while(i < j && arr[i] <= key){ i++; } if(i < j){ arr[j] = arr[i]; } } arr[i] = key; if (i == k - 1) { return; } else if (i > k - 1) { quickSort(arr, low, i - 1, k); } else if (i < k - 1) { quickSort(arr, i + 1, high, k); } } } private void swap(int[] arr, int index1, int index2) { int temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; }
-
堆排序(利用优先队列)
public ArrayList<Integer> getLeastNumbers(int[] nums, int k) { ArrayList<Integer> result = new ArrayList<>(); if (nums.length == 0 || k == 0 || k > nums.length) { return result; } PriorityQueue<Integer> queue = new PriorityQueue<Integer>((a, b) -> (b - a)); for (int i = 0; i < k; i++) { queue.offer(nums[i]); } for (int i = k; i < nums.length; ++i) { if (queue.peek() > nums[i]) { queue.poll(); queue.offer(nums[i]); } } for (int i = 0; i < k; i++) { result.add(queue.poll()); } return result; }
-
public int findKth(int[] arr, int n, int k) { return quickSort(arr, 0, n - 1, k); } private int quickSort(int[] arr,int low,int high, int k){ if(low < high){ int i = low, j = high; int randomIndex = new Random().nextInt(high - low + 1) + low; swap(arr, low, randomIndex); int key = arr[low]; while(i < j){ // 需要找一个比基准值小的 while(i < j && arr[j] >= key){ j--; } if(i < j){ arr[i] = arr[j]; } // 需要找一个比基准值大的 while(i < j && arr[i] <= key){ i++; } if(i < j){ arr[j] = arr[i]; } } arr[i] = key; if (i == k - 1) { return arr[i]; } else if (i > k - 1) { return quickSort(arr, low, i - 1, k); } else if (i < k - 1) { return quickSort(arr, i + 1, high, k); } } return -1; } private void swap(int[] arr, int index1, int index2) { int temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; }
-
堆排序
import java.util.*; public class Solution { public int[] MySort (int[] arr) { heapSort(arr); return arr; } public void heapSort(int[] arr){ if(arr == null || arr.length < 2){ return; } // 构造堆 for(int i = 0; i < arr.length; i++){ swim(arr, i); } int size = arr.length; swap(arr, 0, --size); while(size > 0){ sink(arr, 0, size); // 最后一个数与根交换 swap(arr, 0, --size); } } //判断该结点与父结点的大小,大结点一直往,建立大根堆 public void swim(int[] arr, int index){ while(arr[index] > arr[(index - 1) / 2]){ swap(arr, index, (index - 1) / 2); index = (index - 1) / 2; } } //一个值变小往下沉的过程 public void sink(int[] arr, int index, int size){ int left = index * 2 + 1; while(left < size){ int largest = (left + 1 < size && arr[left + 1] > arr[left]) ? left + 1 : left; largest = (arr[largest] > arr[index]) ? largest : index; if(largest == index){ break; } swap(arr, largest, index); index = largest; left = index * 2 + 1; } } //交换函数 public void swap(int[] arr, int i, int j){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
-
归并排序
import java.util.*; public class Solution { public int[] MySort (int[] arr) { int[] helper = new int[arr.length]; mergeSort(arr, helper, 0, arr.length - 1); return arr; } public void mergeSort(int[] arr, int[] helper, int left, int right){ if (left != right) { int mid = left + ((right - left) / 2); //中点位置,即(l+r)/2 mergeSort(arr, helper, left, mid); mergeSort(arr, helper, mid + 1, right); merge(arr, helper, left, mid, right); } } public void merge(int[] arr, int[] helper, int left, int mid, int right){ int i = 0; int p1 = left; //左半数组的起点下标 int p2 = mid + 1; //右半数组的起点下标 //判断是否越界 while(p1 <= mid && p2 <= right){ helper[i++] = (arr[p1] < arr[p2]) ? arr[p1++] : arr[p2++]; } //p1没有越界,说明p2越界了,将左边剩余元素拷贝到辅助数组 while(p1 <= mid){ helper[i++] = arr[p1++]; } //p2没有越界,说明p1越界了 while(p2 <= right){ helper[i++] = arr[p2++]; } //将辅助数组元素拷贝回原数组 for(int j = 0; j < i; j++){ arr[left + j] = helper[j]; } } }
-
// 用HashMap + PriorityQueue解题 import java.util.*; public class Solution { public String[][] topKstrings (String[] strings, int k) { // 统计字符串频数 HashMap<String, Integer> stringCount = new HashMap<>(); for (int i = 0;i < strings.length;i++) { stringCount.put(strings[i], stringCount.getOrDefault(strings[i], 0)+1); } // 建立大小为k的小顶堆 PriorityQueue<String> heap = new PriorityQueue<String>( (w1, w2) -> Objects.equals(stringCount.get(w1), stringCount.get(w2)) ? w2.compareTo(w1) : stringCount.get(w1).compareTo(stringCount.get(w2)) ); for (String word: stringCount.keySet()) { heap.offer(word); if (heap.size() > k) { heap.poll(); } } String[][] res = new String[k][2]; int j = k - 1; while (!heap.isEmpty()) { String tmp = heap.poll(); res[j][0] = tmp; res[j][1] = stringCount.get(tmp) + ""; j--; } return res; } }
// 用TreeMap + Collections.sort解题 import java.util.*; public class Solution { public String[][] topKstrings (String[] strings, int k) { if (k == 0) { return new String[][]{}; } String[][] res = new String[k][2]; TreeMap<String, Integer> tmap = new TreeMap<>(); for (int i = 0; i < strings.length; i++) { String s = strings[i]; if (!tmap.containsKey(s)) { tmap.put(s, 1); } else { tmap.put(s, tmap.get(s) + 1); } } // 先比较值是否相同,相同按键升序进行比较,不相同按值降序比较 ArrayList<Map.Entry<String, Integer>> list = new ArrayList<>(tmap.entrySet()); Collections.sort(list, (o1, o2) -> (Objects.equals(o1.getValue(), o2.getValue()) ? o1.getKey().compareTo(o2.getKey()) : o2.getValue().compareTo(o1.getValue()))); for (int i = 0; i < k; i++) { res[i][0] = list.get(i).getKey(); res[i][1] = String.valueOf(list.get(i).getValue()); } return res; } }
-
import java.util.*; public class Solution { public ListNode sortInList (ListNode head) { // write code here if(head == null || head.next == null) return head; //使用快慢指针找到中点 ListNode slow = head, fast = head.next; while(fast!=null && fast.next !=null){ slow = slow.next; fast = fast.next.next; } //分割为两个链表 ListNode newList = slow.next; slow.next = null; //将两个链表继续分割 ListNode left = sortInList(head); ListNode right = sortInList(newList); ListNode lhead = new ListNode(-1); ListNode res = lhead; //归并排序 while(left != null && right != null){ if(left.val < right.val) { lhead.next = left; left = left.next; } else { lhead.next = right; right = right.next; } lhead = lhead.next; } //判断左右链表是否为空 lhead.next = left!=null?left:right; return res.next; } }
-
k路归并排序(外部排序)
胜者树与败者树
模拟
解题方法
例题
-
public int[] LRU(int[][] operators, int k) { LinkedHashMap<Integer, Integer> lruMap = new LinkedHashMap<Integer, Integer>(); ArrayList<Integer> result = new ArrayList(); for (int[] opat : operators) { int key = opat[1]; switch (opat[0]) { case 1: int value = opat[2]; if (lruMap.size() < k) { lruMap.put(key, value); } else { Iterator ot = lruMap.keySet().iterator(); lruMap.remove(ot.next()); lruMap.put(key, value); } break; case 2: if (lruMap.containsKey(key)) { int val = lruMap.get(key); result.add(val); lruMap.remove(key); lruMap.put(key, val); } else { result.add(-1); } break; default: } } return result.stream().mapToInt(Integer::valueOf).toArray(); }
数学
解题方法
例题
数据结构
位图
二叉树
-
数据结构
public static class TreeNode { TreeNode left; TreeNode right; Integer data; public TreeNode(TreeNode left, TreeNode right, Integer data) { this.left = left; this.right = right; this.data = data; } }
-
生成完全二叉树
public void initTree(Integer[] nums) { int size = nums.length; List<TreeNode> nodes = new ArrayList(); for (Integer num : nums) { if (num == null) nodes.add(null); else nodes.add(new TreeNode(null, null, num)); } for (int i = 0; i < size / 2 + 1; i++) { if (nodes.get(i) != null) { if (2 * i + 1 < size) nodes.get(i).left = nodes.get(2 * i + 1); if (2 * i + 2 < size) nodes.get(i).right = nodes.get(2 * i + 2); } } head = nodes.get(0); }
-
/* 题目: 输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7] 3 / \ 9 20 / \ 15 7 */ /* 思路: 前序遍历的顺序为:中 左 右 中序遍历的顺序为:左 中 右 将前序遍历的顺序存起来,中序遍历的结果放在HashMap中(方便取值) 按照前序遍历的顺序生成二叉树,先生成根节点,然后生成根节点的左节点,并开始递归根节点的左节点。 再递归根节点的右节点,根节点的右节点的起始节点在先序遍历的排序为 根节点 + 左子树的长度 */ /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { //利用原理,先序遍历的第一个节点就是根。在中序遍历中通过根 区分哪些是左子树的,哪些是右子树的 //左右子树,递归 HashMap<Integer, Integer> map = new HashMap<>();//标记中序遍历 int[] preorder;//保留的先序遍历 public TreeNode buildTree(int[] preorder, int[] inorder) { this.preorder = preorder; for (int i = 0; i < preorder.length; i++) { map.put(inorder[i], i); } return recursive(0, 0, inorder.length - 1); } /** * @param pre_root_idx 先序遍历的索引,生成树的根节点 * @param in_left_idx 中序遍历的索引,生成树的左子树的左边界 * @param in_right_idx 中序遍历的索引,生成树的右子树的右边界 */ public TreeNode recursive(int pre_root_idx, int in_left_idx, int in_right_idx) { //相等就是自己 if (in_left_idx > in_right_idx) { return null; } //root_idx是在先序里面的 TreeNode root = new TreeNode(preorder[pre_root_idx]); // 有了先序的,再根据先序的,在中序中获 当前根的索引 int idx = map.get(preorder[pre_root_idx]); //左子树的根节点就是 左子树的(前序遍历)第一个,就是+1,左边边界就是left,右边边界是中间区分的idx-1 root.left = recursive(pre_root_idx + 1, in_left_idx, idx - 1); //由根节点在中序遍历的idx 区分成2段,idx 就是根 //右子树的根,就是右子树(前序遍历)的第一个,就是当前根节点 加上左子树的数量 // pre_root_idx 当前的根 左子树的长度 = 左子树的左边-右边 (idx-1 - in_left_idx +1) 。最后+1就是右子树的根了 root.right = recursive(pre_root_idx + (idx - 1 - in_left_idx + 1) + 1, idx + 1, in_right_idx); return root; } }
-
/* 题目: 请完成一个函数,输入一个二叉树,该函数输出它的镜像。 例如输入: 4 / \ 2 7 / \ / \ 1 3 6 9 镜像输出: 4 / \ 7 2 / \ / \ 9 6 3 1 */ /* 思路:先序遍历二叉树,交换每个节点的左孩子和右孩子 */ /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public TreeNode mirrorTree(TreeNode root) { if(root != null){ TreeNode temp = root.left; root.left = root.right; root.right = temp; mirrorTree(root.left); mirrorTree(root.right); } return root; } }
-
/* 题目: 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 1 / \ 2 2 / \ / \ 3 4 4 3 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 1 / \ 2 2 \ \ 3 3 */ /* 思路: 该题需要判断每个节点的 left.left.value 和 right.right.value 以及 left.right.value 和 right.left.value的关系, 当两个节点均为null,则返回true 当两个节点不同时为null,或两个节点的值不相等,则返回false 否则继续递归 recur(L.left, R.right) && recur(L.right, R.left) */ /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public boolean isSymmetric(TreeNode root) { return root == null ? true : recur(root.left, root.right); } boolean recur(TreeNode L, TreeNode R) { if(L == null && R == null) return true; if(L == null || R == null || L.val != R.val) return false; return recur(L.left, R.right) && recur(L.right, R.left); } }
-
先序遍历
//递归版本 public void preTraverseTree_Recursion(TreeNode head) { if (head != null) { System.out.print(head.data + " "); preTraverseTree_Recursion(head.left); preTraverseTree_Recursion(head.right); } } //非递归版本 /* 思路: 迭代先序遍历树 先将head进栈,当栈不为空时 {将栈顶元素tempNode弹出,tempNode的右孩子左孩子依次进栈(不为空时)} */ public void preTraverseTree_Iteration(TreeNode head) { if (head == null) { return; } Deque<TreeNode> stack = new ArrayDeque(); stack.push(head); while (!stack.isEmpty()) { TreeNode tempNode = stack.pop(); System.out.print(tempNode.data + " "); if (tempNode.right != null) stack.push(tempNode.right); if (tempNode.left != null) stack.push(tempNode.left); } }
-
中序遍历
//递归版本 public void inTraverseTree_Recursion(TreeNode head) { if (head != null) { inTraverseTree_Recursion(head.left); System.out.print(head.data + " "); inTraverseTree_Recursion(head.right); } } //非递归版本 /* 思路: 迭代中序遍历树 cur表示是否右孩子需要进栈,当栈不为空或者cur不为null时 {左孩子一直进栈,直到cur为null,出栈tempNode,如果tempNode右孩子不为空,则进栈并令cur = tempNode.right} */ public void inTraverseTree_Iteration(TreeNode head) { if (head == null) return; Deque<TreeNode> stack = new ArrayDeque(); TreeNode cur = head; while (!stack.isEmpty() || cur != null) { while (cur != null) { stack.push(cur); cur = cur.left; } TreeNode tempNode = stack.pop(); System.out.print(tempNode.data + " "); if (tempNode.right != null) { cur = tempNode.right; } } }
-
后序遍历
//递归版本 public void postTraverseTree_Recursion(TreeNode head) { if (head != null) { postTraverseTree_Recursion(head.left); postTraverseTree_Recursion(head.right); System.out.print(head.data + " "); } } //非递归版本 /* 思路: 迭代后序遍历树,cur表示上一次输出的节点,当栈不为空时 当栈顶节点的左孩子不为空,并且左右孩子都没有输出过时,将左孩子入栈(因为右孩子都输出过了则左孩子一定输出过了), 否则,当栈顶节点的右孩子不为空,并且右孩子没有输出过时,将右孩子入栈 否则,栈顶节点出栈,并改变lastNode */ public void postTraverseTree_Iteration(TreeNode head) { if (head == null) return; //cur表示上一次出栈的节点 TreeNode lastNode = head; Deque<TreeNode> stack = new ArrayDeque(); stack.push(head); while (!stack.isEmpty()) { TreeNode peek = stack.peek(); //当右孩子都输出了则左孩子一定已经输出过了 if (peek.left != null && peek.left != lastNode && peek.right != lastNode) { stack.push(peek.left); } else if (peek.right != null && peek.right != lastNode) { stack.push(peek.right); } else { lastNode = stack.pop(); System.out.print(lastNode.data + " "); } } }
-
层序遍历
//不打印层次结构 public void levelTraverseTree_Normal(TreeNode head) { Queue<TreeNode> queue = new LinkedList(); queue.add(head); while (!queue.isEmpty()) { TreeNode temp = queue.poll(); System.out.print(temp.data + " "); if (temp.left != null) queue.add(temp.left); if (temp.right != null) queue.add(temp.right); } } //打印层次结构 public void levelTraverseTree_Special(TreeNode head) { if (head == null) return; Queue<TreeNode> queue = new LinkedList<>(); queue.add(head); while (!queue.isEmpty()) { int size = queue.size(); for (int i = 0; i < size; i++) { TreeNode tempNode = queue.poll(); System.out.print(tempNode.data + " "); if (tempNode.left != null) queue.add(tempNode.left); if (tempNode.right != null) queue.add(tempNode.right); } System.out.println(); } }
链表
链表节点每K个一组翻转
用栈解决
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
public ListNode reverseKGroup (ListNode head, int k) {
Stack<ListNode> stack=new Stack<ListNode>();
ListNode ret = new ListNode(0);
ListNode p = ret;
while (true){
int count = 0;
ListNode tmp = head;
while (tmp != null && count < k){
stack.push(tmp);
tmp = tmp.next;
count++;
}
if (count != k){
p.next = head;
break;
}
while (!stack.isEmpty()){
p.next = stack.pop();
p = p.next;
}
p.next = tmp;
head = tmp;
}
return ret.next;
}
}
用递归解决
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null) {
return head;
}
ListNode tail = head;
for (int i = 0; i < k; i++) {
// 少于k个,不需要翻转了,直接返回
if (tail == null) {
return head;
}
tail = tail.next;
}
ListNode newHead = reverse(head, tail);
head.next = reverseKGroup(tail, k);
return newHead;
}
private ListNode reverse(ListNode head, ListNode tail) {
ListNode pre = null, next = null;
while (head != tail) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
}
图论
-
最小生成树
/* 一个有n户人家的村庄,有m条路连接着。村里现在要修路,每条路都有一个代价, 现在请你帮忙计算下,最少需要花费多少的代价,就能让这n户人家连接起来。 input : 3,3,[[1,3,3],[1,2,1],[2,3,1]] output : 2 */ public class Solution { public int miniSpanningTree (int n, int m, int[][] cost) { // 父节点数组,father[i]表示第i个节点的父节点索引,如果相等,则表示父节点是自己 int[] father = new int[n + 1]; for(int i = 0; i < father.length; i++){ father[i] = i; } // 小顶堆 PriorityQueue<Node> queue = new PriorityQueue<>((a,b)-> a.cost - b.cost); for(int[] c : cost){ queue.add(new Node(c[2], c[1], c[0])); } int ans = 0; while(!queue.isEmpty()){ // Node cur = queue.poll(); if(find(father, cur.start) != find(father, cur.end)){ ans += cur.cost; union(father, cur.start, cur.end); } } return ans; } static class Node{ int cost; int start; int end; public Node(int c, int s, int e){ this.start = s; this.end = e; this.cost = c; } } // 找到a的最终父节点 public int find(int[] f, int a){ if(f[a] == a){ return a; } return f[a] = find(f, f[a]); } // 找到a和b的父节点,将a的父节点指向b的父节点 public void union(int[] f, int a ,int b){ int fa = find(f, a); int fb = find(f, b); f[fa] = fb; } }
public class Solution { public int miniSpanningTree(int n, int m, int[][] cost) { //构建图 int[][] graph = new int[n + 1][n + 1]; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { graph[i][j] = Integer.MAX_VALUE; } } for (int[] edge : cost) { //边已经存在 if (graph[edge[0]][edge[1]] < Integer.MAX_VALUE) { if (graph[edge[0]][edge[1]] > edge[2]) { graph[edge[0]][edge[1]] = edge[2]; graph[edge[1]][edge[0]] = edge[2]; } } else { graph[edge[0]][edge[1]] = edge[2]; graph[edge[1]][edge[0]] = edge[2]; } } boolean[] isVisited = new boolean[n + 1]; isVisited[1] = true; int[] dis = new int[n + 1]; for (int i = 1; i <= n; i++) { dis[i] = graph[1][i]; } int next = -1; int res = 0; for (int i = 1; i <= n - 1; i++) { int min = Integer.MAX_VALUE; for (int j = 1; j <= n; j++) { if (!isVisited[j] && dis[j] < min) { min = dis[j]; next = j; } } res += min; isVisited[next] = true; for (int j = 1; j <= n; j++) { if (!isVisited[j] && dis[j] > graph[next][j]) { dis[j] = graph[next][j]; } } } return res; } }
-
单源最短路
/* 在一个有向无环图中,已知每条边长,求出1到n的最短路径,返回1到n的最短路径值。如果1无法到n,输出-1 input: 5,5,[[1,2,2],[1,4,5],[2,3,3],[3,5,4],[4,5,5]] output: 9 */ public class Solution { public int findShortestPath(int n, int m, int[][] graph) { int[] dp = new int[n + 1]; dp[1] = 0; for (int i = 2; i < n + 1; i++) { int min = Integer.MAX_VALUE; for (int j = 0; j < m; j++) { if (graph[j][1] == i && dp[graph[j][0]] != -1) { min = Math.min(min, graph[j][2] + dp[graph[j][0]]); } } dp[i] = (min == Integer.MAX_VALUE) ? -1 : min; } return dp[n]; } }
正则匹配
-
public class Solution { public boolean match(String str, String pattern) { //1.初始化 int m = str.length(); int n = pattern.length(); boolean dp[][] = new boolean[m + 1][n + 1]; dp[0][0] = true; for (int j = 1; j <= n; j++) { if (pattern.charAt(j - 1) == '*' && dp[0][j - 2]) { dp[0][j] = true; } } for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (str.charAt(i - 1) == pattern.charAt(j - 1) || pattern.charAt(j - 1) == '.') { dp[i][j] = dp[i - 1][j - 1]; } else if (pattern.charAt(j - 1) == '*') { if (dp[i][j - 2] == true) { dp[i][j] = dp[i][j - 2]; } else { char pre = pattern.charAt(j - 2); if (pre == str.charAt(i - 1) || pre == '.') { dp[i][j] = dp[i - 1][j]; } } } } } return dp[m][n]; } }
表达式求值
-
public class Solution { public int solve(String s) { Stack<Integer> stack = new Stack<>(); int sum = 0, number = 0; char sign = '+'; char[] charArray = s.toCharArray(); for (int i = 0, n = charArray.length; i < n; i++) { char c = charArray[i]; if (c == '(') { int j = i + 1; int counterPar = 1; while (counterPar > 0) { if (charArray[j] == '(') { counterPar++; } if (charArray[j] == ')') { counterPar--; } j++; } number = solve(s.substring(i + 1, j - 1)); i = j - 1; } if (Character.isDigit(c)) { number = number * 10 + c - '0'; } if (!Character.isDigit(c) || i == n - 1) { if (sign == '+') { stack.push(number); } else if (sign == '-') { stack.push(-1 * number); } else if (sign == '*') { stack.push(stack.pop() * number); } else if (sign == '/') { stack.push(stack.pop() / number); } number = 0; sign = c; } } while (!stack.isEmpty()) { sum += stack.pop(); } return sum; } }
-
/* 计算逆波兰式(后缀表达式)的值 运算符仅包含"+","-","*"和"/",被操作数可能是整数或其他表达式 例如: ["20", "10", "+", "30", "*"] -> ((20 + 10) * 30) -> 900 ["40", "130", "50", "/", "+"] -> (40 + (130 / 50)) -> 42 */ public class Solution { public int evalRPN (String[] tokens) { Stack<Integer> stack = new Stack<Integer>(); for(String s :tokens){ if(s.equals("+") ||s.equals("-")||s.equals("*")||s.equals("/")){ int b = stack.pop(); int a = stack.pop(); int c = get(a,b,s); stack.push(c); }else { int num = Integer.parseInt(s); stack.push(num); } } return stack.pop(); } public int get(int a ,int b,String s){ switch(s){ case "+" : return a+b; case "-" : return a-b; case "*" : return a*b; case "/" : return a/b; default : return 0; } } // write code here }
模板
/*
题目:
*/
/*
思路:
*/
```xxxxxxxxxx /* 题目:*//* 思路:*/java