说明:题目来源网络,由于本人水平有限,代码可能存在错误,欢迎指正
文章目录
- 笔试题
- 1. 排序算法
- 2. LeetCode
- 2.1 字符串
- 2.2 双指针
- :star:通配符匹配
- 2.3 排序
- 2.4 贪心
- 2.5 滑动窗口
- 2.6 二分查找
- 2.7 BFS
- 2.8 DFS
- 2.9 Backtracking
- 3.0 动态规划
- 3.1 数学
- 3.2 链表
- 3.3 栈
- 3.4 队列
- 3.5 数组
- 3.6 位运算
- 3.7 二叉树
- :star:验证二叉搜索树
- :star:判断对称二叉树
- :star:翻转二叉树
- :star:二叉树的层次遍历
- :star:二叉树的锯齿形打印
- :star:二叉树的最近公共祖先
- :star:二叉树的中序遍历
- :star:先序遍历非递归
- :star:后续遍历非递归
- :star:二叉树的最大深度
- :star:判断平衡二叉树
- :star:二叉树的最大路径和
- :star:路径总和2
- :star:从前序遍历和中序遍历构造二叉树
- :star:二叉树的直径
- :star:[求根节点到叶节点数字之和](https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/)
- :star:路径总和2
- :star:从前序遍历和中序遍历构造二叉树
- :star:二叉树的直径
- :star:[求根节点到叶节点数字之和](https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/)
- 推荐1:简历制作神器
- 推荐2:保姆级的Java教程和项目
笔试题
字节:链接
存在n+1个房间,每个房间依次为房间1 2 3…i,每个房间都存在一个传送门,i房间的传送门可以把人传送到房间pi(1<=pi<=i),现在路人甲从房间1开始出发(当前房间1即第一次访问),每次移动他有两种移动策略:
A. 如果访问过当前房间 i 偶数次,那么下一次移动到房间i+1;
B. 如果访问过当前房间 i 奇数次,那么移动到房间pi;
现在路人甲想知道移动到房间n+1一共需要多少次移动;
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] strategy = new int[n + 1];
for(int i = 1; i <= n; i++){
strategy[i] = scanner.nextInt();
}
System.out.println(step(n, strategy));
}
private static long step(int n, int[] strategy){
//思路:到达i时,i前面的所有房间必定访问了偶数次
long[] dp = new long[n + 2];//存储第一次到房间i所需步数
dp[1] = 0;
if(n == 1)return 1;
long mod = 1000000007;
for(int i = 2; i <= n + 1; i++){
//dp[i] = dp[i-1] + dp[i-1] + 1 - dp[p[i-1]] + 1
dp[i] = (2*dp[i - 1])%mod - dp[strategy[i - 1]] + 2;
}
return dp[n + 1]%mod;
}
}
1. 排序算法
冒泡排序
沉石法,大的往后交换。
优化:外循环每次判断上次是否发生交换,如果没交换可以提前结束排序。
public class BubbleSort {
public static void main(String[] args) {
int[] arr = new int[]{2, 5, 3, 9, 1};
sort(arr);
for (int i : arr) {
System.out.println(i);
}
}
public static void sort(int[] arr){
int len = arr.length;
for (int i = 0; i < len; i++) {
//0-4
for(int j = 0; j < len - i - 1; j++){//注意这里的len - i - 1
if(arr[j] > arr[j + 1]){
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
选择排序
**从数组中选择最小元素,将它与数组的第一个元素交换位置。再从数组剩下的元素中选择出最小的元素,将它与数组 的第二个元素交换位置。**不断进行这样的操作,直到将整个数组排序。 选择排序需要 ~N2 /2 次比较和 ~N 次交换,它的运行时间与输入无关,这个特点使得它对一个已经排序的数组也需要 这么多的比较和交换操作
public class SelectSort {
public static void main(String[] args) {
int[] arr = new int[]{2, 5, 3, 9, 1};
sort(arr);
for (int i : arr) {
System.out.println(i);
}
}
private static void sort(int[] arr){
int len = arr.length;
for(int i = 0; i < len; i++){
int min = i;
for(int j = i + 1; j < len; j++){
if(arr[min] > arr[j]){
min = j;
}
}
swap(arr, min, i);
}
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
插入排序
每次都将当前元素插入到左侧已经排序的数组中,使得插入之后左侧数组依然有序。对于数组 {3, 5, 2, 4, 1},它具有以下逆序:(3, 2), (3, 1), (5, 2), (5, 4), (5, 1), (2, 1), (4, 1),插入排序每次只能交换相邻 元素,令逆序数量减少 1,因此插入排序需要交换的次数为逆序数量。
public class InserSort {
public static void main(String[] args) {
int[] arr = new int[]{2, 5, 3, 9, 1};
sort(arr);
for (int i : arr) {
System.out.println(i);
}
}
private static void sort(int[] arr){
int len = arr.length;
for(int i = 1; i < len; i++){//从第1个往前插
for(int j = i; j > 0; j--){
if(arr[j] < arr[j - 1]){
swap(arr, j, j - 1);
}else{
break;
}
}
}
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
归并排序
- 思路:使用递归分别排好左右两侧顺序,再使用merge合并,将排好序的help数组拷贝进原数组对应位置
- 思想:分治
public class MergeSort {
public static void main(String[] args) {
int[] arr = new int[]{2, 5, 3, 9, 1};
sort(arr, 0, arr.length - 1);
for (int i : arr) {
System.out.println(i);
}
}
private static void sort(int[] arr, int low, int high){
if(low == high) return;
int mid = low + (high - low)/2;
sort(arr, low, mid);
sort(arr, mid + 1, high);
merge(arr, low, mid, high);
}
private static void merge(int[] arr, int left, int mid, int right){
int[] temp = new int[right - left + 1];
int p1 = left;//左侧数组起点
int p2 = mid + 1;//右侧数组起点
int i = 0; //temp下标
//简单的双指针遍历
while(p1 <= mid && p2 <= right){
temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
//剩余部分:两个之一
while(p1 <= mid){
temp[i++] = arr[p1++];
}
while(p2 <= right){
temp[i++] = arr[p2++];
}
//拷贝回去
for(int j = 0; j < temp.length; j++){
arr[left + j] = temp[j];
}
}
}
快速排序
- 经典快排
public class ClassicQuickSort {
public static void main(String[] args) {
int[] arr = new int[]{2, 5, 3, 9, 1};
sort(arr, 0, arr.length - 1);
for (int i : arr) {
System.out.println(i);
}
}
private static void sort(int[] arr, int l, int r){
if(l < r){
int p = partition(arr, l, r);
sort(arr, l, p - 1);
sort(arr, p + 1, r);
}
}
private static int partition(int[] arr, int l, int r){
int less = l - 1;
while(l < r){
if(arr[l] < arr[r]){
swap(arr, ++less, l++);
}else{
l++;
}
}
swap(arr, less + 1, r);
return less + 1;
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
- 随机快排 + 荷兰国旗三向切分partition优化
public class QuickSort {
public static void main(String[] args) {
int[] arr = new int[]{2, 5, 3, 9, 1};
sort(arr, 0, arr.length - 1);
for (int i : arr) {
System.out.println(i);
}
}
private static void sort(int[] arr, int left, int right){
if(left >= right) return;//靠,这里很关键,必须判断大小,不然下面返回的less、more可能会越界
int x = left + (int) (Math.random() * (right - left + 1));
swap(arr, x, right);
int[] p = partition(arr, left, right);
sort(arr, left, p[0]);
sort(arr, p[1], right);
}
private static int[] partition(int[] arr, int left, int right){
int less = left - 1;
int more = right;//为什么是right,因为right是不遍历的下标,最后要交换
while(left < more){
if(arr[left] < arr[right]){
swap(arr, ++less, left++);
}else if( arr[left] > arr[right]){
swap(arr, --more, left);
}else{
left++;
}
}
swap(arr, more, right);
return new int[]{less, more + 1};
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
堆排序
Min-heap: 父节点的值小于或等于子节点的值;
Max-heap: 父节点的值大于或等于子节点的值;
- 创建一个堆 H[0……n-1];
- 创建一个大根堆
- 堆顶和堆尾交换:即每次把最大值放在最后
- 重复第三步骤
多种实现方式:个人觉得下面方式最为简单
注意:建立大根堆、小根堆有两种方式,第一种叫heapInsert,称为上浮,时间复杂度为o(nlogn),第二种为heapify,即下沉,时间复杂度为O(n),建议用heapify,因为
public class HeapSort {
public static void main(String[] args) {
int[] arr = new int[]{2, 5, 3, 9, 1};
buildMaxHeap(arr);
for(int i = arr.length - 1; i > 0; i--){
swap(arr, i, 0);
heapify(arr, 0, i);//注意这里的heapSize
}
for (int i : arr) {
System.out.println(i);
}
}
private static void buildMaxHeap(int[] arr){
// int len = arr.length;
// for(int i = 0; i < len; i++){//heapInsert:向上调整
// while(arr[i] > arr[(i - 1) / 2]){
// swap(arr, i, (i - 1) / 2);
// i = (i - 1) / 2;
// }
// }
for(int i = arr.length/2; i >= 0; i--){
heapify(arr, i, arr.length);
}
}
private static void heapify(int[] arr, int i, int heapSize){
// int l = i * 2 + 1;
// while(l < heapSize){//非递归实现
// int large = l + 1 <= heapSize && arr[l] > arr[l + 1] ? l : l + 1;
// large = arr[large] > arr[i] ? large : i;
// if(large == i) break;//自己最大,退出
// swap(arr, large, i);
// i = large; //新node
// l = i * 2 + 1; //新左儿子
// }
int l = i * 2 + 1, r = i * 2 + 2, largest = i; //l:左孩子,r:右孩子,i:父节点
if (l < heapSize && arr[l] > arr[largest]) {
largest = l; //先与左孩子比较
}
if (r < heapSize && arr[r] > arr[largest]) {
largest = r;//再与右孩子比较
}
if (largest != i) {//如果孩子节点更大,交换
swap(arr, i, largest);
heapify(arr, largest, heapSize); //递归调整下一个子树
}
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
2. LeetCode
2.1 字符串
⭐️比较版本号
给你两个版本号 version1 和 version2 ,请你比较它们。
版本号由一个或多个修订号组成,各修订号由一个 ‘.’ 连接。每个修订号由 多位数字 组成,可能包含 前导零 。每个版本号至少包含一个字符。修订号从左到右编号,下标从 0 开始,最左边的修订号下标为 0 ,下一个修订号下标为 1 ,以此类推。例如,2.5.33 和 0.1 都是有效的版本号。
比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较 忽略任何前导零后的整数值 。也就是说,修订号 1 和修订号 001 相等 。如果版本号没有指定某个下标处的修订号,则该修订号视为 0 。例如,版本 1.0 小于版本 1.1 ,因为它们下标为 0 的修订号相同,而下标为 1 的修订号分别为 0 和 1 ,0 < 1 。
返回规则如下:
如果 version1 > version2 返回 1,
如果 version1 < version2 返回 -1,
除此之外返回 0。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/compare-version-numbers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
法一:借助split方法
class Solution {
public int compareVersion(String version1, String version2) {
String[] v1 = version1.split("\\.");
String[] v2 = version2.split("\\.");
int len1 = v1.length;
int len2 = v2.length;
int i = 0;
while(i < len1 && i < len2){
int num1 = Integer.parseInt(v1[i]);
int num2 = Integer.parseInt(v2[i]);
if(num1 == num2){
i++;
}else if(num1 > num2){
return 1;
}else{
return -1;
}
}
while(i < len2){
if(Integer.parseInt(v2[i]) != 0){
return -1;
}
i++;
}
while(i < len1){
if(Integer.parseInt(v1[i]) != 0){
return 1;
}
i++;
}
return 0;
}
}
法二:不使用split
class Solution {
public int compareVersion(String version1, String version2) {
int i = 0, j = 0;
int v1Len = version1.length(), v2Len = version2.length();
while (i < v1Len || j < v2Len) {
int a = 0, b = 0;
while (i < v1Len && version1.charAt(i) != '.') {
a = a * 10 + version1.charAt(i) - '0';
i++;
}
while (j < v2Len && version2.charAt(j) != '.') {
b = b * 10 + version2.charAt(j) - '0';
j++;
}
if (a > b) {
return 1;
} else if (a < b) {
return -1;
}
i++;
j++;
}
return 0;
}
}
2.2 双指针
⭐️通配符匹配
class Solution {
public boolean isMatch(String s, String p) {
if (p==null||p.isEmpty())return s==null||s.isEmpty();
int i=0,j=0, istart=-1, jstart=-1,slen=s.length(),plen=p.length();
//判断s的所有字符是否匹配
while (i<slen){
//三种匹配成功情况以及匹配失败返回false
if (j<plen&&(s.charAt(i)==p.charAt(j)||p.charAt(j)=='?')){
i++;
j++;
}else if (j<plen&&p.charAt(j)=='*'){
//记录下使用*去匹配的两字符串起始位置,并先让j++,因为可能有多个连续的*
istart=i;
jstart=j;
j++;
}else if (istart>-1){
//不匹配且有*时,用*去抵消,并记录下一个*的匹配位置
istart++;
i=istart;
j=jstart+1;
}else {
return false;
}
}
//s中的字符都判断完毕,此时需要p为空或者p中只剩下星号的时候,才能成功匹配。
//如果p中剩余的都是*,则可以移除剩余的*
//说明:在将s匹配完的情况下,如果j能够到达末尾,说明匹配是成功的
while (j<plen&&p.charAt(j)=='*')j++;
return j==plen;
}
}
有序数组的 Two Sum
- 题目描述:在有序数组中找出两个数,使它们的和为 target。
public class TwoSum {
//题目描述:在有序数组中找出两个数,使它们的和为 target。
public static void main(String[] args) {
int[] arr = new int[]{1, 3, 5, 6};
int[] ans = solution(arr, 8);
for (int i : ans) {
System.out.println(arr[i]);
}
}
private static int[] solution(int[] arr, int target){
int len = arr.length;
int l = 0, r = len - 1;
while(l < r){
if(arr[l] + arr[r] > target){
r--;
}else if(arr[l] + arr[r] < target){
l++;
}else{
break;
}
}
return new int[]{l, r};
}
}
两数平方和
- 题目描述:判断一个数是否为两个数的平方和。
public class SumOfSquare{
// 题目描述:判断一个数是否为两个数的平方和。
public static void main(String[] args) {
int a = 4;
int b = 5;
int c = 3;
System.out.println(solution(a));
System.out.println(solution(b));
System.out.println(solution(c));
}
private static boolean solution(int num){
int i = 0;
// int j = num/2;
int j = (int) Math.sqrt(num);
while(i <= j){
if(i * i + j * j == num){
return true;
}else if(i * i + j * j > num){
j--;
}else{
i++;
}
}
return false;
}
}
反转字符串中的元音
- 使用双指针指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
public class ReverseVowelOfString {
//编写一个函数,该函数将字符串作为输入,并且仅反转字符串的元音。
public static void main(String[] args) {
String str1 = solution("hello");
String str2 = solution("leetcode");
System.out.println(str1);
System.out.println(str2);
}
public static String solution(String str){
Set<Character> set = new HashSet<>(
Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U')
);
int l = 0;
int r = str.length() - 1;
char[] ans = new char[str.length()];
while(l <= r){
Character L = Character.valueOf(str.charAt(l));
Character R = Character.valueOf(str.charAt(r));
if(!set.contains(L)){
ans[l++] = L;
}else if(!set.contains(R)){
ans[r--] = R;
}else{ //(set.contains(L) && set.contains(R))
ans[l++] = R;
ans[r--] = L;
}
}
return new String(ans);
}
}
回文串判断
- 题目描述:可以删除一个字符,判断是否能构成回文字符串。
public class ValidPalindrome {
//题目描述:可以删除一个字符,判断是否能构成回文字符串。
public static void main(String[] args) {
String str1 = "abba";
String str2 = "abab";
String str3 = "abbc";
System.out.println(check1(str1));
System.out.println(check1(str2));
System.out.println(check1(str3));
}
//可以删除情况
public static boolean check1(String str) {
int l = 0, r = str.length() - 1;
while( l < r){
if(str.charAt(l) != str.charAt(r)){
return check2(str, l + 1, r) || check2(str, l, r - 1);
}
l++;
r--;
}
return true;
}
//不能删除情况
private static boolean check2(String str, int l, int r){
while(l < r){
if(str.charAt(l) != str.charAt(r)){
return false;
}
l++;
r--;
}
return true;
}
}
⭐️最长回文子串
- 理解题意
- 题目给定一个字符串
- 需要我们找到这个字符串中的最长回文串
- 回文串:正着读和反着读都一样的字符串
- 整体思路
- 我根据回文串的一个核心思想来解题:从中心点往两边扩散,来寻找回文串,这种方向相当于穷举每一个点为中心点
- 如果回文串是奇数时,比如 “bab” ,它的中心点就只有一个 “a”,所以从 “a” 开始向两边扩散
- 如果回文串是偶数时,比如 “baab”,它的中心点有两个 “aa”,所以从 “aa” 开始向两边扩散
- 编写一个辅助函数来寻找回文串,当中心点确定了之后调用辅助函数,直接返回找到的回文串
- 将每次找到的回文串与之前的做对比,谁长就留谁
public class LongestPalindrome {
public static void main(String[] args) {
String str = "abbbaccabba";
System.out.println(solution(str));//bbaccabb
}
private static String solution(String str){
if(str == null || "".equals(str))return "";
String res = "";
for(int i = 0; i < str.length(); i++){
String s1 = find(str, i, i);//奇数
String s2 = find(str, i, i + 1);//偶数
res = res.length() > s1.length() ? res : s1;
res = res.length() > s2.length() ? res : s2;
}
return res;
}
// 查找回文串
private static String find(String s, int l, int r){
while(l >= 0 && r < s.length()){
if(s.charAt(l) == s.charAt(r)){
l--;
r++;
}else{
break;
}
}
return s.substring(l + 1, r);//注意substring是小写
}
}
归并两个有序数组
- 题目描述:把归并结果存到第一个数组上。
public class MergeSortedArray {
//归并有序数组,把归并结果存到第一个数组上
//注意:这题要求不使用额外空间,为了方便,从后面开始遍历,不需要交换
public static void main(String[] args) {
int[] arr1 = new int[]{1, 3 ,5, 6, 0, 0};
int[] arr2 = new int[]{2, 4};
solution(arr1, arr2);
for (int i : arr1) {
System.out.println(i);
}
}
private static void solution(int[] arr1, int[] arr2){
int i = arr1.length - 1;
int j = arr2.length - 1;
int k = i - j - 1; //arr1的真实size - 1
while(j >= 0 && k >= 0){
if(arr1[k] < arr2[j]){
arr1[i--] = arr2[j--];
}else{
arr1[i--] = arr1[k--];
}
}
}
}
判断链表是否有环
public class LinkedListCycle{
//题目:判断链表是否存在环
//思路:看是否存在环
static class Node{
Integer val;
Node next;
Node(Integer val){
this.val = val;
}
}
//快慢指针
public static void main(String[] args) {
int[] arr = new int[]{1, 2 ,3, 4, 5, 6};
Node head = new Node(1);
Node p = head;
Node p2 = new Node(0);
Node tail = new Node(0);
for(int i = 1; i < arr.length; i++){//尾插法
Node temp = new Node(arr[i]);
p.next = temp;
p = p.next;
if(arr[i] == 2){
p2 = temp;
}
if(arr[i] == 6){
tail = temp;
}
}
//tail.next = p2; //加环
System.out.println(check(head));
}
private static boolean check(Node head){
Node p1 = head;
Node p2 = p1.next;
while(p2 != null && p2.next != null){
if(p1.val == p2.val){
return true;
}else{
p1 = p1.next;
p2 = p2.next.next;
}
}
return false;
}
}
⭐️最长子序列
- 题目描述:删除 s 中的一些字符,使得它构成字符串列表 d 中的一个字符串,找出能构成的最长字符串。如果有多个 相同长度的结果,返回字典序的最小字符串。
所谓的子序列就是在原来序列中找出一部分组成的序列。如:dabdbf的最长递增子序列是:abdf
public class FindLongestWord {
//题目描述:删除 s 中的一些字符,
//使得它构成字符串列表 d 中的一个字符串,找出能构成的最长字符串。如果有多个
//相同长度的结果,返回字典序的最小字符串。
//题解:通过删除字符串 s 中的一个字符能得到字符串 t,
//可以认为 t 是 s 的子序列,我们可以使用双指针来判断一个字符串
//是否为另一个字符串的子序列。
public static void main(String[] args) {
String s = "abpcpleaf";
String[] d = new String[]{"ale", "apple", "bleaf", "monkey", "plea"};
System.out.println(solution(s, d));
}
private static String solution(String s, String[] d){
int len = d.length;
String longMinSub = ""; //最长最小字典序子串
for(int i = 0; i < len; i++){
if(isSubStr(s, d[i])){
String temp = d[i];
int len1 = longMinSub.length();
int len2 = temp.length();
if(len1 < len2){
longMinSub = temp;
}else if(len1 == len2){
if(longMinSub.compareTo(temp) > 0){
longMinSub = temp;
}
}
}
}
return longMinSub;
}
private static boolean isSubStr(String s, String target){
//双指针法:判断 target 是否是 s 的子序列
int i = 0, j = 0;
while(i < s.length() && j < target.length()){
if(s.charAt(i) == target.charAt(j)){
j++; //j匹配上才++
}
i++; //i不管是否匹配都要++
}
return j == target.length();
}
}
⭐️三数之和
题目描述:找数组中三个数相加等于零的
时间复杂度O(n^2)
import java.util.*;
public class ThreeAdd {
// 三数相加
public static void main(String[] args) {
int[] arr = {-1,0,1,2,-1,4};
List<List<Integer>> ans = solution(arr);
Iterator<List<Integer>> iterator = ans.iterator();
while(iterator.hasNext()){
List<Integer> list = iterator.next();
Iterator<Integer> temp = list.iterator();
while(temp.hasNext()){
System.out.print(temp.next() + " ");
}
System.err.println();
}
}
private static List<List<Integer>> solution(int[] arr){
// 算法:排序+双指针,O(n^2)
if(arr == null || arr.length == 0)return null;
int len = arr.length;
Arrays.sort(arr);
List<List<Integer>> ans = new ArrayList<>();
for(int i = 0; i < len - 2; i++){ //注意 len - 2即可
if(i > 0 && arr[i] == arr[i - 1]) continue; //相等的元素跳过
int target = - arr[i]; // a + b == target
int left = i + 1, right = len - 1;
while(left < right){
if(arr[left] + arr[right] == target){
ans.add(Arrays.asList(arr[i], arr[left], arr[right]));
left++;
right--;
while(left < right && arr[left] == arr[left - 1])left++;//相等的元素跳过
while(left < right && arr[right] == arr[right + 1])right--;//相等的元素跳过
}else if(arr[left] + arr[right] > target){
right--;
}else{
left++;
}
}
}
return ans;
}
}
//-1 -1 2
//-1 0 1
⭐️ 调整数组顺序使奇数位于偶数前面
class Solution {
public int[] exchange(int[] nums) {
int left = 0; //left遇到偶数停下来
int right = nums.length - 1; //right遇到奇数停下来
while(left < right){
if(nums[left] % 2 == 1){
left++;
continue;//继续
}
if(nums[right] % 2 == 0){
right--;
continue;//继续
}
swap(nums, left++, right--);//交换
}
return nums;
}
public void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
2.3 排序
⭐️topk问题
- 题目描述:找到第 k 大的元素。
堆排法,时间复杂度 O(NlogK),空间复杂度 O(K)。
- topk (前k大)用小根堆,维护堆大小不超过 k 即可。每次压入堆前和堆顶元素比较,如果比堆顶元素还小,直接扔掉,否则压入堆。检查堆大小是否超过 k,如果超过,弹出堆顶。复杂度是 nlogk
- 避免使用大根堆,因为你得把所有元素压入堆,复杂度是 nlogn,而且还浪费内存。如果是海量元素,那就挂了。
法一:大根堆实现(不建议)
public class FindTopK {
public static void main(String[] args) {
int[] arr = new int[]{3, 2, 1, 5 ,6 ,4};
int k = 2;
System.out.println(find(arr, k));
}
private static int find(int[] arr, int k){
//法1:用大根堆实现topk
int len = arr.length;
buildMaxHeap(arr);
while(k-- > 1){
swap(arr, 0, len - 1);
heapify(arr, 0, len - 1);
len--;
}
return arr[0];
}
private static void buildMaxHeap(int[] arr){
int len = arr.length;
for(int i = len/2; i >= 0; i--){
heapify(arr, i, len);
}
}
private static void heapify(int[] arr, int index, int heapSize){
int l = 2 * index + 1;
int largeIdx = index;
if(l < heapSize && arr[largeIdx] < arr[l]){
largeIdx = l;
}
if(l + 1 < heapSize && arr[largeIdx] < arr[l + 1]){
largeIdx = l + 1;
}
if(largeIdx != index){//需要交换才递归
swap(arr, largeIdx, index);
heapify(arr, largeIdx, heapSize);
}
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
法二:小根堆实现
package com.tosang.java;
import java.util.Arrays;
/**
* TopK元素
*
* @Author: tosang
* @Date: 2021/7/31 0:02
*/
public class TopK {
public static void main(String[] args) {
int[] arr = new int[]{3,2,7,8,10,4};
System.out.println(solution(arr, 2));
}
public static int solution(int[] arr, int k){
//小根堆:大小为K
//1. 先对k个元素建立小根堆
for(int i = k / 2; i >= 0; i--){
heapify(arr, i, k);
}
//2.判断后续元素与堆顶的大小关系
for(int i = k; i < arr.length; i++){
if(arr[i] > arr[0]){
//3. 与堆顶交换
swap(arr, 0, i);
heapify(arr, 0, k);
}
}
return arr[0];
}
/**
* 小根堆
*
* @param arr
* @param index
* @param heapSize
*/
public static void heapify(int[] arr, int index, int heapSize){
int left = 2 * index + 1;
int right = 2 * index + 2;
int min = index;
if(left < heapSize && arr[index] > arr[left]){
min = left;
}
if(right < heapSize && arr[min] > arr[right]){
min = right;
}
if(min != index){
swap(arr, min, index);
heapify(arr, min, heapSize);
}
}
public static void swap(int[] arr, int a, int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
法三:快排快速选择法,时间复杂度 O(N),空间复杂度 O(1)
public class FindTopK2 {
//随机快排+三向切分partition版本
public static void main(String[] args) {
int[] arr = new int[]{3, 2, 1, 5 ,6 ,4};
System.out.println(quickSort(arr, 0, arr.length - 1, 1));
System.out.println(quickSort(arr, 0, arr.length - 1, 2));
System.out.println(quickSort(arr, 0, arr.length - 1, 3));
System.out.println(quickSort(arr, 0, arr.length - 1, 4));
System.out.println(quickSort(arr, 0, arr.length - 1, 5));
System.out.println(quickSort(arr, 0, arr.length - 1, 6));
}
private static int quickSort(int[] arr, int l, int r, int k){
int idx = l + (int)Math.random() * (r - l + 1);
swap(arr, idx, r);
int index = partition(arr, l, r);
if(index < arr.length - k){
return quickSort(arr, index + 1, r, k);
}else if(index > arr.length - k){
return quickSort(arr, l, index - 1, k);
}else{
return arr[index];
}
}
private static int partition(int[] arr, int l, int r){
int less = l - 1;
int more = r;
while(l < more){
if(arr[l] < arr[r]){
swap(arr, ++less, l++);
}else if(arr[l] > arr[r]){
swap(arr, --more, l);
}else{
l++;
}
}
swap(arr, more, r);
return more;
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
出现频率最多的k个元素
- 思路:和topk原理一样,只不过多一个hash表来将频率转为个数。
注意:
- topk (前k大)用小根堆,维护堆大小不超过 k 即可。每次压入堆前和堆顶元素比较,如果比堆顶元素还小,直接扔掉,否则压入堆。检查堆大小是否超过 k,如果超过,弹出堆顶。复杂度是 nlogk
- 避免使用大根堆,因为你得把所有元素压入堆,复杂度是 nlogn,而且还浪费内存。如果是海量元素,那就挂了。
[注意]
- 求前 k 大,用小根堆,求前 k 小,用大根堆。
import java.util.*;
public class TopKFrequent {//小根堆
public static void main(String[] args) {
int[] nums = new int[]{1, 1, 2, 2, 3};
int k = 2;
int[] ans = topKFrequent(nums, k);
for (int i : ans) {
System.out.println(i);
}
}
public static int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
for (int num : nums) {
occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
}
//技巧:int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] m, int[] n) {
return m[1] - n[1];
}
});
for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
int num = entry.getKey(), count = entry.getValue();
if (queue.size() == k) {
if (queue.peek()[1] < count) {
queue.poll();
queue.offer(new int[]{num, count});
}
} else {
queue.offer(new int[]{num, count});
}
}
int[] ret = new int[k];
for (int i = 0; i < k; ++i) {
ret[i] = queue.poll()[0];
}
return ret;
}
}
按颜色进行排序
- 题目描述:只有 0/1/2 三种颜色。
public class SortColor {
//荷兰国旗问题
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 2, 1, 0, 0};
sort(arr);
for (int i : arr) {
System.out.println(i);
}
}
private static void sort(int[] arr){
int less = -1;
int more = arr.length;
int index = 0;
while(index < more){
if(arr[index] == 0){
swap(arr, ++less, index++);
}else if(arr[index] == 2){
swap(arr, --more, index);
}else{
index++;
}
}
}
private static void swap(int[] arr, int i ,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
2.4 贪心
分配饼干
- 题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于等于一个孩子的满足度,该孩子 才会获得满足。求解最多可以获得满足的孩子数量。
import java.util.*;
public class FindContentChildren {
public static void main(String[] args) {
int[] g = new int[]{1, 2};
int[] s = new int[]{1, 2, 3};
System.out.println(solution(g, s));
}
public static int solution(int[] g, int[] s) {
//贪心法,先排序后从小到大比较
Arrays.sort(g);
Arrays.sort(s);
int i = 0, j = 0;
int count = 0;
while(i < g.length && j < s.length){
if(s[j] >= g[i]){//满足
i++;
j++;
count++;
}else{
j++; //不满足,饼干++
}
}
return count;
}
}
不重叠的区间
- 给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
- 注意:这题也可以按照左边界排序,不过遍历时必须从右往左。
import java.util.Arrays;
import java.util.Comparator;
public class EraseOverlapIntervals {
//题目描述:计算让一组区间不重叠所需要移除的区间个数。
public static void main(String[] args) {
int[][] arr = {{1, 2}, {2, 3}, {3, 4}, {1, 3}};
int[][] arr1 = {{1, 2}, {1, 2}, {1, 2}};
System.out.println(eraseOverlapIntervals(arr));
System.out.println(eraseOverlapIntervals(arr1));
}
public static int eraseOverlapIntervals(int[][] intervals) {
//按区间的结尾进行排序,每次选择结尾最小,并且和前一个区间不重叠的区间。
if (intervals.length == 0) {
return 0;
}
Arrays.sort(intervals, new Comparator<int [] >(){
public int compare(int [] a1,int [] a2) {
return a1[1] - a2[1]; //右边界升序排列
}
});
int count = 1; //注意:目标是找到最多能组成的不重叠区间个数
int end = intervals[0][1];
for (int i = 0; i < intervals.length; i++) {
if (intervals[i][0] < end) {//每次选择最小的右边界和下一个左边界比较
continue;
}
end = intervals[i][1]; //不重叠,获得一个新的区间右边界
count++; //区间++
}
return intervals.length - count;
}
}
投飞镖刺破气球
思路:
- 先对右边界排序。
- 每次选择上一个重合区间的最小右边界和下一个区间的左边界比较,因为两个区间重合意味着一定是最小的右边界重合,故end每次选最小右边界以保证留给后面的区间的空间越大,那么后面能够选择的区间 个数也就越大。
import java.util.Arrays;
import java.util.Comparator;
public class FindMinArrowShots {
//题目描述:用最少数量的箭引爆气球
//寻找不重叠区间个数
public static void main(String[] args) {
int[][] points = new int[][]{{1, 2}, {2, 3}, {3, 4}, {4 ,5}};
System.out.println(solution(points));
}
private static int solution(int[][] points){
if(points.length == 0){
return 0;
}
int len = points.length;
Arrays.sort(points, new Comparator<int[]>(){
@Override
public int compare(int[] arr1, int[] arr2){
return arr1[1] - arr2[1];
}
});
int count = 1;
int end = points[0][1];
for(int i = 1; i < len; i++){
if(points[i][0] <= end){
continue;
}
count++;
end = points[i][1];
}
return count;
}
}
根据身高和序号重组队列
- 题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样 高。
- 思路:在高的人前面插入矮的人不会影响原队列,但如果插入更高的人会影响原队列,故需要按身高降序,按k升序。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class QueueReconstruction {
// 题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。
public static void main(String[] args) {
int[][] arr = new int[][]{{7, 0}, {4, 4}, {7, 1}, {5, 0}, {6, 1}, {5, 2}};
int[][] ans = solution(arr);
for (int[] is : ans) {
System.out.println("[" + is[0] + "," + is[1] + "]");
}
}
private static int[][] solution(int[][] arr){
if(arr == null || arr.length == 0){
return new int[0][0];
}
Arrays.sort(arr, (a, b) -> a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]);
List<int[]> queue = new ArrayList<>();
for(int[] i : arr){
queue.add(i[1], i);
}
return queue.toArray(new int[queue.size()][]);
}
}
⭐️买卖股票最大的收益
-
题目描述:一次股票交易包含买入和卖出,只进行一次交易,求最大收益。
-
这题既可以用贪心,也可以用DP,思路一致,dp只不过用dp数组替换下面的max即可
public class MaxProfit {
//买卖股票的最佳时机
public static void main(String[] args) {
int[] arr = new int[]{7, 1, 5, 3, 6, 4};
int[] arr1 = new int[]{7, 6, 4, 3, 2, 1};
System.out.println(solution(arr));
System.out.println(solution(arr1));
}
private static int solution(int[] arr){
if(arr == null || arr.length <= 1){
return 0;
}
int min = arr[0];
int max = 0;
for(int i = 1; i < arr.length; i++){
if(arr[i] < min){
min = arr[i];
}else{
max = max > arr[i] - min ? max : arr[i] - min;
}
}
return max;
}
}
⭐️买卖股票的最大收益2
-
题目:允许多次买卖股票,求最大收益
-
题解:可以用贪心、dp,贪心每次买跌买涨,dp需要维护两个dp数组,分别为持有股票的收益和不持有股票的收益。
public class MaxProfit2 {
public static void main(String[] args) {
int[] prices = new int[]{7, 1, 5, 3, 6, 4};
int[] prices1 = new int[]{1, 2, 3, 4, 5};
System.out.println(solution1(prices));
System.out.println(solution1(prices1));
System.out.println(solution2(prices));
System.out.println(solution2(prices1));
}
private static int solution1(int[] arr){
// 贪心算法:买跌卖涨
int ans = 0;
if(arr == null || arr.length <= 1){
return 0;
}
for(int i = 1; i < arr.length; i++){
if(arr[i] > arr[i - 1]){//可以买入
ans += arr[i] - arr[i - 1];
}
}
return ans;
}
private static int solution2(int[] arr){
// 动态规划
if(arr == null || arr.length <= 1){
return 0;
}
int[][] dp = new int[arr.length][2];
dp[0][0] = 0; //不持股票
dp[0][1] = -arr[0]; //持有股票
for(int i = 1; i < arr.length; i++){
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + arr[i]);//max(昨天也没持有,昨天持有+今天卖了)
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - arr[i]);
}
return dp[arr.length - 1][0];
}
}
⭐️判断是否为子序列
public class IsSubSequence {
public static void main(String[] args) {
String s = "abc";
String t = "ahbgdc";
System.out.println(check(s, t));
}
private static boolean check(String s, String t){
int slen = s.length();
int tlen = t.length();
int i = 0, j = 0;
while(i < slen && j < tlen){
if(s.charAt(i) == t.charAt(j)){
i++;
j++;
}else{
j++;
}
}
return i == slen;
}
}
2.5 滑动窗口
⭐️无重复字符的最长子串
- 注意时间复杂度是 O(n)
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s == null || s.length() == 0)return 0;
int len = s.length();
Set<Character> set = new HashSet<>();
int l = 0, r = 0; //滑动窗口区间
int ans = 0;
while(r < len){
while(set.contains(s.charAt(r))){//
set.remove(s.charAt(l++));
}
ans = Math.max(ans, r - l + 1);
set.add(s.charAt(r++));
}
return ans;
}
}
⭐️最小覆盖子串
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
示例:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
class Solution {
public String minWindow(String s, String t) {
Map<Character,Integer> window = new HashMap(); // 用来记录窗口中的字符和数量
Map<Character,Integer> need = new HashMap(); // 需要凑齐的字符和数量
// 构建need字符集
for (int i = 0; i < t.length(); i++) {
char needChar = t.charAt(i);
need.put(needChar,need.getOrDefault(needChar,0)+1);
}
int left = 0,right = 0,valid = 0;
// valid是用来记录窗口中满足need要求的字符和数量的数目,比如need中要求字符a数量为2,如果window中的a字符的数量等于了2,valid就+1,反之-1
int len = Integer.MAX_VALUE; // 记录最小字串的长度
int start = 0; // 记录最小字串的起始位置
while(right < s.length()){
char addChar = s.charAt(right); // 即将要加入window的字符
window.put(addChar,window.getOrDefault(addChar,0) + 1);
right++;
// 如果加入的字符是need中要求的字符,并且数量已经达到了need要求的数量,则valid+1
// 这里和下面都有个坑,window.get(addChar)和need.get(addChar)返回的都是对象,最好用.equals()方法比较大小
if(need.containsKey(addChar) && window.get(addChar).equals(need.get(addChar))){
valid++;
}
// 当window中记录的字符和数量满足了need中要求的字符和数量,考虑缩窗口
while(valid == need.size()){
// 先判断当前的最小覆盖字串是否比之前的最小覆盖字串短
if(right - left < len){ // 注意,这里上面已经对right实施了++操作,所以这里的长度不是right - left + 1
len = right - left ;
start = left; // 如果最短,则记录下该最小覆盖字串的起始位置
}
char removeChar = s.charAt(left);
// 开始缩减窗口,left右移,如果要从window删除的字符正好是need中需要的并且,数目也等于need中需要的数目,则删减后,该该字符要求的数量
// 显然不满足need要求的数量,所以valid要-1;
if(need.containsKey(removeChar) && window.get(removeChar).equals(need.get(removeChar))){
valid--;
}
window.put(removeChar,window.get(removeChar) - 1);
left++;
}
}
// 如果最小覆盖字串的长度相对于定义时没变,则t不包含s中所有的字符,返回"",如果长度改变过,说明存在这样的最小覆盖字串,直接输出。
return len == Integer.MAX_VALUE?"":s.substring(start,start+len);
}
}
2.6 二分查找
讲解:https://blog.csdn.net/zxzxzx0119/article/details/82670761
二分法一定要注意边界问题!!
- l < r:此时,循环退出条件为 l == r ,而由于 mid =(r + l)/ 2取的中的靠左位置,即mid可能 == l,而如果 l此时的更新方式为:l = mid将导致死循环。
- 解决办法为mid取中点靠右:mid = (r + l + 1) / 2
- 注意1:当 l 更新方式为 l = mid + 1时,不会死循环。
- 注意2:这种写法,退出时 l == r
- l <= r:退出条件为 l > r,即实际上退出时 r != l。
⭐️实现sqrt
- 注意事项:
- 使用除法,避免大数运算。
- 返回r,因为 l <= r 循环退出时,l > r,即r是小的那个,符合sqrt取整的要求。
public class Sqrt {
// 二分法实现sqrt
public static void main(String[] args) {
System.out.println(solution(4));
System.out.println(solution(8));
}
private static int solution(int num){
if(num <= 1){
return num;
}
int l = 1, r = num;
while(l <= r){
int mid = l + (r - l) / 2;
int temp = num / mid;
if(temp == mid){
return mid;
}else if(mid < temp){
l = mid + 1;
}else{
r = mid - 1;
}
}
return r;
}
}
大于给定元素的最小元素
- 题 目描述:给定一个有序的字符数组 letters 和一个字符 target,要求找出 letters 中大于 target 的最小字符,如果 找不到就返回第 1 个字符。
public class FindSmallestLetter {
public static void main(String[] args) {
char[] ch = new char[]{'c', 'f', 'j'};
char target1 = 'd';
char target2 = 'k';
System.out.println(solution(ch, target1));
System.out.println(solution(ch, target2));
}
private static char solution(char[] letters, char target){
int len = letters.length;
int l = 0, r = len - 1;
while(l <= r){
int mid = l + (r - l) / 2;
if(letters[mid] > target){
r = mid - 1;
}else{
l = mid + 1;
}
}
return l < len ? letters[l] : letters[0];
}
}
//法二:
class Solution {
public char nextGreatestLetter(char[] letters, char target) {
int lo = 0, hi = letters.length;
while (lo < hi) {
int mi = lo + (hi - lo) / 2;
if (letters[mi] <= target) lo = mi + 1;
else hi = mi;
}
return letters[lo % letters.length];
}
}
有序数组中的单一元素
- 题目:给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
public class SingleNonDuplicate {
public static void main(String[] args) {
int[] arr = new int[]{1, 1, 2, 3, 3, 4, 4, 8, 8};
int[] arr1 = new int[]{3, 3, 7, 7, 10, 11, 11};
System.out.println(solution(arr));
System.out.println(solution(arr1));
}
private static int solution(int[] arr){
if(arr == null || arr.length == 0){
return -1;
}
int l = 0, r = arr.length - 1;
while(l < r){ //注意不能==,因为r=mid赋值可能导致死循环
int mid = l + (r - l) / 2;
if(mid % 2 == 1){//如果为奇数,则改为判断前一个(偶数)
mid--;
}
if(arr[mid] == arr[mid + 1]){
l = mid + 2;
}else{
r = mid;
}
}
return arr[l]; // l和r都可以
}
}
⭐️搜索旋转排序数组
class Solution {
public int search(int[] nums, int target) {
//要求logn,必然二分
if(nums == null || nums.length == 0)return -1;
int l = 0;
int r = nums.length - 1;
while(l <= r){
int mid = l + (r - l) / 2;
if(nums[mid] == target) return mid;
if(nums[l] < nums[mid]){//左侧有序
if(target < nums[l] || target > nums[mid]){//小于最小边界,或大于最大边界
l = mid + 1;
}else{
r = mid - 1;
}
}else{//右侧有序
if(target < nums[mid] || target > nums[r]){
//小于最小边界,或大于最大边界
r = mid - 1;
}else{
l = mid + 1;
}
}
}
return -1;
}
}
⭐️寻找旋转数组的最小数字
https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/
public class FindRotateMin {
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 3, 4, 5};
int[] arr1 = new int[]{3, 4, 5, 1, 2};
System.out.println(solution(arr));
System.out.println(solution(arr1));
}
// 必然是二分法:最小值必然在右侧区域
//因此每次比较mid和最右边的大小确定mid当前处于哪个区间。
private static int solution(int[] arr){
int l = 0, r = arr.length - 1;
while(l < r){
int mid = l + (r - l) / 2;
if(arr[mid] <= arr[r]){
r = mid;
}else{
l = mid + 1;
}
}
return arr[r];
}
}
⭐️在排序数组中查找元素的第一个和最后一个位置
- 题目描述:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。
public class SearchRange {
public static void main(String[] args) {
int[] arr = new int[]{5, 7, 7, 8, 8, 10};
System.out.println(solution(arr, 8));
// System.out.println(solution(arr, 6));
}
private static int[] solution(int[] arr, int target){
int[] ans = new int[]{-1, -1};
if(arr == null || arr.length == 0){
return ans;
}
int L = binarySearch(arr, target);
int R = binarySearch(arr, target + 1) - 1;
if(L == -1){
return ans;
}else{
ans[0] = L;
ans[1] = R;
return ans;
}
}
private static int binarySearch(int[] arr, int target){
int l = 0, r = arr.length - 1;
while(l < r){
int mid = l + (r - l) / 2;
if(arr[mid] < target){
l = mid + 1;
}else{
r = mid;
}
}
System.out.println(l);
return arr[l] == target ? l : -1;
}
}
法二:分情况讨论法
class Solution {
public int[] searchRange(int[] nums, int target) {
// 二分查找
int[] arr = new int[2];
arr[0] = binarySearchLeft(nums, target);
arr[1] = binarySearchRight(nums, target);
return arr;
}
private int binarySearchLeft(int[] nums, int target){
int left = 0;
int 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(mid == 0 || nums[mid - 1] != target){
return mid;
}else{
right = mid - 1;
}
}
}
return -1;
}
private int binarySearchRight(int[] nums, int target){
int left = 0;
int 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(mid == nums.length - 1 || nums[mid + 1] != target){
return mid;
}else{
left = mid + 1;
}
}
}
return -1;
}
}
2.7 BFS
每一层遍历的节点都与根节点距离相同。设 di 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di <= dj。利用这个结论,可以求解最短路径等 最优解 问题:第一次遍历到目的节点,其所经 过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径,无权图是指从一个节点到另一个节点 的代价都记为 1。
在程序实现 BFS 时需要考虑以下问题:
- 队列:用来存储每一轮遍历得到的节点;
- 标记:对于遍历过的节点,应该将它标记,防止重复遍历。
计算在网络中从原点到特定点的最短路径
- 题目描述:1 表示可以经过某个位置,求解从 (0, 0) 位置到 (tr, tc) 位置的最短路径长度。
import java.util.ArrayDeque;
import java.util.Queue;
public class MinPathLength {
// bfs典型题
public static void main(String[] args) {
int[][] arr = {{1, 1, 0, 1}, {1, 0, 1, 0}, {1, 1, 1, 1}, {1, 0, 1, 1}};
System.out.println(solution(arr, 3, 3));
}
private static int solution(int[][] arr, int ti, int tj){
int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int row = arr.length;
int col = arr[0].length;
int len = 0;
boolean[][] visited = new boolean[row][col];
Queue<int[]> queue = new ArrayDeque<>();
queue.offer(new int[]{0, 0});
while(!queue.isEmpty()){
int size = queue.size(); //注意queue每次存放的是下一次可以遍历的相同距离的点
len++; //queue不为空可以++
while(size-- > 0){
int[] temp = queue.poll();
int i = temp[0], j = temp[1];
visited[i][j] = true;
for(int[] d : direction){
int ii = i + d[0];
int jj = j + d[1];
if(ii < 0 || ii >= row || jj < 0 || jj >= col || arr[ii][jj] == 0 ||visited[ii][jj]){
continue;
}
if(ii == ti && jj == tj){
return len;
}
queue.offer(new int[]{ii, jj});
}
}
}
return -1;
}
}
2.8 DFS
广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。 而深度优先搜索在得到一个新节点时立即对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对 新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。 从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 可达性 问题。
在程序实现 DFS 时需要考虑以下问题:
- 栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。
- 标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。
查找最大连通面积
public class MaxAreaOfIsland {
// 查找最大连通面积
public static void main(String[] args) {
}
private static int solution(int[][] arr){
if(arr == null || arr.length == 0){
return 0;
}
int maxArea = 0;
int row = arr.length;
int col = arr[0].length;
for(int i = 0; i < row; i++){
for(int j = 0; j < col; j++){
maxArea = Math.max(maxArea, dfs(arr, i, j, row, col));
}
}
}
private static int dfs(int[][] arr, int i, int j, int row, int col){
if(i < 0 || i>= row || j < 0 || j >= col || arr[i][j] == 0){
return 0;
}
arr[i][j] = 0;
return 1 + dfs(arr, i - 1, j, row, col)
+ dfs(arr, i + 1, j, row, col)
+ dfs(arr, i, j - 1, row, col)
+ dfs(arr, i, j + 1, row, col);
}
}
⭐️矩阵中的连通区域分量数目/岛屿数量
public class NumOfIsland {
// 连通分量数目
public static void main(String[] args) {
int[][] arr = {
{1,1,0,0,0},
{1,1,0,0,0},
{0,0,1,0,0},
{0,0,0,1,1}
};
System.out.println(solution(arr));
}
private static int solution(int[][] arr){
// 把遍历过的区域标记为0,下次直接跳过
if(arr == null || arr.length == 0) return 0;
int row = arr.length;
int col = arr[0].length;
int count = 0;
for(int i = 0; i < row; i++){
for(int j = 0; j < col; j++){
if(arr[i][j] != 0){
count++;
dfs(arr, i, j, row, col);
}
}
}
return count;
}
private static void dfs(int[][] arr, int i, int j, int row, int col){
if(i < 0 || i >= row || j < 0 || j >= col || arr[i][j] <= 0){
return ;
}
arr[i][j] = 0;
dfs(arr, i - 1, j, row, col);
dfs(arr, i + 1, j, row, col);
dfs(arr, i, j - 1, row, col);
dfs(arr, i, j + 1, row, col);
}
}
2.9 Backtracking
Backtracking(回溯)属于 DFS。
- 普通 DFS 主要用在 可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
- 而 Backtracking 主要用于求解 排列组合 问题,例如有 { ‘a’,‘b’,‘c’ } 三个字符,求解所有由这三个字符排列得到 的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。
因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
- 在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复 访问该元素;
- 但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访 问已经访问过但是不在当前递归链中的元素。
技巧:回溯法的模板很好记,但是何时用start变量,何时用visited数组呢? 当为组合问题,即[a, b]与[b, a]等价,用start变量,当为排列问题,即[a, b]与[b, a]不等价,用visited数组
⭐️复原IP地址
给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效 IP 地址。
class Solution {
static final int SEG_COUNT = 4; //分4段
List<String> ans = new ArrayList<String>();
int[] segments = new int[SEG_COUNT]; //存放4个整数
public List<String> restoreIpAddresses(String s) {
segments = new int[SEG_COUNT];
dfs(s, 0, 0);
return ans;
}
// segId代表第几段,segStart代表每段的开始下标
public void dfs(String s, int segId, int segStart) {
// 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案
if (segId == SEG_COUNT) {
if (segStart == s.length()) {
StringBuffer ipAddr = new StringBuffer();
for (int i = 0; i < SEG_COUNT; ++i) {
ipAddr.append(segments[i]);
if (i != SEG_COUNT - 1) { //中间加点
ipAddr.append('.');
}
}
ans.add(ipAddr.toString());
}
return;
}
// 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯
if (segStart == s.length()) {
return;
}
// 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0,即0必须单独划分区间
if (s.charAt(segStart) == '0') {
segments[segId] = 0;
dfs(s, segId + 1, segStart + 1);
}
// 一般情况,枚举每一种可能性并递归
int addr = 0;
for (int segEnd = segStart; segEnd < s.length(); ++segEnd) {
addr = addr * 10 + (s.charAt(segEnd) - '0');
if (addr > 0 && addr <= 0xFF) { //0xFF == 255
segments[segId] = addr;
dfs(s, segId + 1, segEnd + 1);
} else {
break;
}
}
}
}
数字的排列组合
题目描述:给定一个数组,给出数组元素的所有排列组合
package com.tosang.java;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: tosang
* @Date: 2021/4/5 14:27
*/
public class Permute {
public static void main(String[] args) {
int[] arr = {1,2,3};
List<List<Integer>> solution = solution(arr);
solution.forEach((List<Integer> temp) -> {
temp.forEach(System.out::print);
System.out.println();
});
}
public static List<List<Integer>> solution(int[] nums){
List<List<Integer>> ans = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
boolean[] visited = new boolean[nums.length];
dfs(nums, temp, ans, visited);
return ans;
}
// 回溯法
public static void dfs(int[] nums, List<Integer> temp, List<List<Integer>> ans, boolean[] visited){
if(temp.size() == nums.length){
ans.add(new ArrayList<>(temp));
return;
}
for(int i = 0; i < nums.length; i++){
if(!visited[i]){
temp.add(nums[i]);
visited[i] = true;
dfs(nums, temp, ans, visited);
// 还原现场
visited[i] = false;
temp.remove(temp.size() - 1);
}
}
}
}
⭐️组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
技巧说明:回溯法的关键是for循环里面的内容,这里列举两种情况:
- 求全排列:意味着数组每次都要从0下标遍历,故for(int i = 0)
- 求和为目标值:从当前下标继续走,故for(int i = index)
此外,理解回溯的含义:当前dfs遍历完成了,不管其是否满足条件,我需要回到上一步以跳过上一步,尝试下一步dfs。
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(candidates);
//System.out.println(candidates);
backtrack(candidates, target, res, 0, new ArrayList<Integer>());
return res;
}
private void backtrack(int[] candidates, int target, List<List<Integer>> res, int i, ArrayList<Integer> tmp_list) {
if (target < 0) return;
if (target == 0) {
res.add(new ArrayList<>(tmp_list));
return;
}
for (int start = i; start < candidates.length; start++) {
tmp_list.add(candidates[start]);
backtrack(candidates, target - candidates[start], res, start, tmp_list);
tmp_list.remove(tmp_list.size() - 1);
}
}
}
⭐️ 子集
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
class Solution {
public List<List<Integer>> subsets(int[] nums) {
// 回溯法
List<List<Integer>> ans = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
dfs(nums, 0, temp, ans);
return ans;
}
private void dfs(int[] nums, int index, List<Integer> temp, List<List<Integer>> ans){
ans.add(new ArrayList<>(temp));
for(int i = index; i < nums.length; i++){
temp.add(nums[i]);
dfs(nums, i + 1, temp, ans);
temp.remove(temp.size() - 1);
}
}
}
3.0 动态规划
⭐️最长重复子数组
给两个整数数组 A
和 B
,返回两个数组中公共的、长度最长的子数组的长度。
class Solution {
public int findLength(int[] nums1, int[] nums2) {
//dp[i][j]代表以下标 i-1, j-1结尾的公共子数组。
int[][] dp = new int[nums1.length + 1][nums2.length + 1];
int res = 0;
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < dp[i].length; j++) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
res = Math.max(res, dp[i][j]);
} else {
dp[i][j] = 0;
}
}
}
return res;
}
}
爬楼梯
public class ClimbingStairs {
public static void main(String[] args) {
System.out.println(solution(1));
System.out.println(solution(2));
System.out.println(solution(3));
System.out.println(solution(4));
System.out.println(solution(5));
}
private static int solution(int n){
if(n == 1)return 1;
int[] dp = new int[n];
dp[0] = 1;
dp[1] = 2;
for(int i = 2; i < n; i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n - 1];
}
}
强盗抢劫
- 题目描述:抢劫一排住户,但是不能抢邻近的住户,求最大抢劫量。
public class Rob {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 1};
int[] arr2 = {2, 7, 9, 3, 1};
System.out.println(solution(arr1));
System.out.println(solution(arr2));
}
private static int solution(int[] arr){
if(arr == null || arr.length == 0)return 0;
if(arr.length == 1)return arr[0];
int[] dp = new int[arr.length];
dp[0] = arr[0];
dp[1] = Math.max(dp[0], dp[1]);
for(int i = 2; i < arr.length; i++){
dp[i] = Math.max(dp[i - 1], dp[i - 2] + arr[i]);
}
return dp[arr.length - 1];
}
}
矩阵的最小路径和
public class MinPathSum {
public static void main(String[] args) {
int[][] arr = {
{1,3,1}
,{1,5,1}
,{4,2,1}};
System.out.println(solution(arr));
}
private static int solution(int[][] arr){
if(arr == null || arr.length == 0) return 0;
int row = arr.length;
int col = arr[0].length;
int[][] dp = new int[row][col];
for(int i = 0; i < row; i++){
for(int j = 0; j < row; j++){
if(i == 0 && j== 0){
dp[0][0] = arr[0][0];
}
else if(i == 0 && j != 0){
dp[i][j] = dp[i][j - 1] + arr[i][j];
}
else if(j == 0 && i != 0){
dp[i][j] = dp[i - 1][j] + arr[i][j];
}else{
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + arr[i][j];
}
}
}
return dp[row - 1][col - 1];
}
}
矩阵的路径数
public class UniquePath {
public static void main(String[] args) {
System.out.println(solution(3, 3));
System.out.println(solution(2, 2));
System.out.println(solution(1, 1));
}
private static int solution(int row, int col){
if(row <= 0 || col <= 0)return 0;
int[][] dp = new int[row][col];
for(int i = 0; i < row; i++){
for(int j = 0; j < col; j++){
if(i == 0 && j == 0){
dp[i][j] = 1;
}else if(i == 0 && j != 0){
dp[i][j] = dp[i][j - 1];
}else if(j == 0 && i !=0){
dp[i][j] = dp[i - 1][j];
}else{
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
}
return dp[row - 1][col - 1];
}
}
⭐️最大子序和
题目描述:给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
class Solution {
public int maxSubArray(int[] nums) {
int max = nums[0];
int len = nums.length;
if(len == 1)return nums[0];
int[] dp = new int[len]; //代表包含当前下标i的最大子序列和
dp[0] = nums[0];
for(int i = 1; i < len; i++){
dp[i] = Math.max(dp[i- 1] + nums[i], nums[i]);
max = dp[i] > max ? dp[i] : max;
}
return max;
}
}
⭐️最长公共子序列
注意区别子序列和子串
对于两个子序列 S1 和 S2,找出它们最长的公共子序列。
public class LengthOfLCS {
public static void main(String[] args) {
int[] s1 = {1, 3, 4, 5, 6, 7, 7, 8};
int[] s2 = {3, 5, 7, 4, 8, 6, 7, 8, 2};
int[] s3 = {3, 5};
int[] s4 = {3};
System.out.println(solution(s1, s2));
System.out.println(solution(s1, s3));
System.out.println(solution(s1, s4));
}
private static int solution(int[] s1, int[] s2){
if(s1.length == 0 || s2.length == 0)return 0;
int[][] dp = new int[s1.length][s2.length]; //dp[i][j]对应s1的i,s2的j
for(int i = 0; i < s1.length; i++){
for(int j = 0; j < s2.length; j++){
if(i == 0 && j == 0){
dp[i][j] = s1[i] == s2[j] ? 1 : 0;
}else if(i == 0 && j != 0){
dp[i][j] = s1[i] == s2[j] ? 1 : dp[i][j - 1];
}else if(j == 0 && i != 0){
dp[i][j] = s1[i] == s2[j] ? 1 : dp[i - 1][j];
}else{
dp[i][j] = s1[i] == s2[j] ? dp[i - 1][j - 1] + 1 : Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[s1.length - 1][s2.length - 1];
}
}
⭐️最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
public class LengthOfLIS {
public static void main(String[] args) {
int[] arr1 = {10, 9, 2, 5, 3, 7, 101, 18};
int[] arr2 = {0, 1, 0, 3, 2, 3};
int[] arr3 = {7, 7, 7, 7, 7, 7, 7};
System.out.println(solution(arr1));
System.out.println(solution(arr2));
System.out.println(solution(arr3));
}
private static int solution(int[] arr){
if(arr == null || arr.length == 0)return 0;
int len = arr.length;
int[] dp = new int[len];
// dp[j] 代表 nums[0…j] 中以j结尾的最长上升子序列,所以不一定是最大值
int ans = 0;
dp[0] = 1;
for(int i = 1; i < len; i++){
dp[i] = 1;
for(int j = 0; j < i; j++){
if(arr[i] > arr[j]){
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
ans = Math.max(ans, dp[i]);
}
return ans;
}
}
划分数组为和相等的两部分
class Solution {
public boolean canPartition(int[] nums) {
// 01背包问题:
// 可选数量为N = nums.length,背包最大容量为:w = sum / 2,
// 这题和一般01背包的区别是,这里不要求价值最大,而是恰好装满,因此dp数组存放背包的当前体积即可。
int sum = 0;
for(int num : nums){ // 计算和
sum += num;
}
if(sum % 2 != 0){
return false;//除不尽,说明总和一定不能分成两份
}
int len = nums.length;
int W = sum / 2;
int[][] dp = new int[len + 1][W + 1]; //dp[i][j],i是当前背包的件数,j是当前背包的体积
for(int i = 1; i <= len; i++){ //注意题目说只包含正整数,所以j从1开始
int w = nums[i - 1];
for(int j = 1; j <= W; j++){
if(j >= w){ //背包容量装得下当前数字
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w] + w);
}else{
dp[i][j] = dp[i - 1][j];
}
if(dp[i][j] == W){
return true;
}
}
}
return false;
}
}
删除字符串的字符是他们相等
public class MinDIstance {
public static void mian(String[] args){
String word1 = "sea";
String word2 = "eat";
System.out.println(solution(word1, word2));
}
private static int solution(String word1, String word2){
int len1 = word1.length();
int len2 = word2.length();
int[][] dp = new int[len1 + 1][len2 + 1];
for(int i = 1; i <= len1; i++){
for(int j = 1; j <= len2; j++){
if(word1.charAt(i - 1) == word2.charAt(j - 1)){
dp[i][j] = dp[i - 1][j - 1] + 1;
}else{
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return len1 + len2 - 2 * dp[len1][len2];
}
}
⭐️接雨水
首先用两个数组,max_left [i] 代表第 i 列左边最高的墙的高度,max_right[i] 代表第 i 列右边最高的墙的高度。(一定要注意下,第 i 列左(右)边最高的墙,是不包括自身的,和 leetcode 上边的讲的有些不同)
对于 max_left我们其实可以这样求。
max_left [i] = Max(max_left [i-1],height[i-1])。它前边的墙的左边的最高高度和它前边的墙的高度选一个较大的,就是当前列左边最高的墙了。
对于 max_right我们可以这样求。
max_right[i] = Max(max_right[i+1],height[i+1]) 。它后边的墙的右边的最高高度和它后边的墙的高度选一个较大的,就是当前列右边最高的墙了。
这样,我们再利用解法二的算法,就不用在 for 循环里每次重新遍历一次求 max_left 和 max_right 了。
作者:windliang
链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-8/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
public class Trap {
public static void main(String[] args) {
int[] arr = {4,2,0,3,2,5};
System.out.println(solution(arr));//9
}
private static int solution(int[] arr){
if(arr == null || arr.length == 0)return 0;
int len = arr.length;
int[] lMax = new int[len];//存储i左侧最大值
int[] rMax = new int[len];//存储i右侧最大值
lMax[0] = 0;
rMax[len - 1] = 0;
for(int i = 1; i < len; i++){
lMax[i] = Math.max(lMax[i - 1], arr[i - 1]);
}
for(int i = len - 2; i >= 0; i--){
rMax[i] = Math.max(rMax[i + 1], arr[i + 1]);
}
int count = 0;
for(int i = 1; i < len - 1; i++){
// 因为0和len - 1必然没有水,故可以跳过
int min = Math.min(lMax[i], rMax[i]);
if(arr[i] < min){
count += min - arr[i];
}
}
return count;
}
}
⭐️硬币兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];// dp[i] 代表总金额为i时,最少的硬币数
Arrays.fill(dp, amount + 1);// 初始填充最大值
dp[0] = 0; //一定要初始化,因为0比较特殊,金额为0,不需要硬币
for(int i = 1; i <= amount; i++){
for(int j = 0; j < coins.length; j++){
int value = coins[j];// 面值
if(i >= value){
dp[i] = Math.min(dp[i], dp[i - value] + 1);//遍历所有面值,找最小值
}
}
}
return dp[amount] != amount + 1 ? dp[amount] : -1;
}
}
3.1 数学
⭐️用rand7()实现rand10()
只要记得这个公式:if a in [0,x], 那么 a * Y + randY() 能等概率的生成 1到X*Y之间的随机数
原理:
a * y = [0, 6] * 7 ,即{0,7,14,21,28,35,42}
randy = {1,2,3,4,5,6,7}
所以有:
p{ a * Y + randY() == 1}=1/49
p{ a * Y + randY() == 2}=1/49
…
p{ a * Y + randY() == 10}=1/49
p{ a * Y + randY() == 49}=1/49
方法一:
根据上面的算法,我们可以得到以下最简单的实现,即不断生成随机数,直到数字是【1,10】区间以内的,返回即可
class Solution extends SolBase {
public int rand10() {
int num = 0;
do{
num = (rand7() - 1)*7 + rand7();
}while(num > 10);
return num;
}
}
方法二:
注意到上面的算法存在一个问题:我们的函数会得到 1~49之间的数,而我们只想得到 1~10之间的数,这一部分占的比例太少了,简而言之,这样效率太低,太慢,可能要 while循环很多次。我们最后完全可以利用 1~40 之间的数来得到 1~10之间的数。
class Solution extends SolBase {
public int rand10() {
int num = 0;
while(true){
num = (rand7() - 1)*7 + rand7();
if(num > 40){
continue;
}
return num % 10 + 1;
}
}
}
二进制相加
import jdk.internal.jshell.tool.resources.l10n;
public class AddBinary {
public static void main(String[] args) {
String num1 = "11";
String num2 = "1";
System.out.println(solution(num1, num2));
}
private static String solution(String num1, String num2){
int m = num1.length() - 1, n = num2.length() - 1;
int carry = 0; //表示进位
StringBuilder sb = new StringBuilder();
while(carry == 1 || m >= 0 || n >= 0){ //注意一定要判断 == 1,因为最高位可能会进1
if(m >= 0 && num1.charAt(m--) == '1'){
carry++;
}
if(n >= 0 && num2.charAt(n--) == '1'){
carry++;
}
sb.append(carry % 2); //保留余数
carry /= 2; //进位
}
return sb.reverse().toString(); }
}
⭐️字符串相加
public class AddString {
public static void main(String[] args) {
String num1 = "111111";
String num2 = "999999";
System.out.println(solution(num1, num2));
}
private static String solution(String num1, String num2){
int m = num1.length() - 1;
int n = num2.length() - 1;
int carry = 0;//进位
StringBuilder sb = new StringBuilder();
while(carry >= 1 || m >= 0 || n >= 0){
int x = 0, y = 0;
if(m >= 0){
x = num1.charAt(m--) - '0';
}
if(n >= 0){
y = num2.charAt(n--) - '0';
}
sb.append((x + y + carry) % 10);
carry = (x + y + carry) / 10;
}
return sb.reverse().toString();
}
}
注意上面两题的思路基本一致
数组中出现次数多于一半的元素
public class MajorElement {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 2, 2, 2, 2};
int[] arr1 = {3, 3, 3, 3, 3, 2, 2, 2, 2};
System.out.println(solution(arr));
System.out.println(solution(arr1));
}
private static int solution(int[] arr){
// 摩尔投票法
int len = arr.length;
if(len <= 0)return -1;
if(len == 1)return arr[0];
int cnt = 0;
int ans = arr[0];
for(int i = 0; i < len; i++){
if(cnt == 0){
// ans被抵消了
ans = arr[i];
}
cnt = ans == arr[i] ? cnt + 1 : cnt - 1;
}
return ans;
}
}
3.2 链表
⭐️删除链表重复元素
遍历法:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head == null)return head;
ListNode p1 = head;
while(p1 != null && p1.next != null){
if(p1.next.val == p1.val){
ListNode temp = p1.next;
p1.next = p1.next.next;
temp = null;
}else{
p1 = p1.next;
}
}
return head;
}
}
递归法:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return null;
}
if (head.next == null) {
return head;
}
if (head.val == head.next.val) {
head = deleteDuplicates(head.next);
} else {
head.next = deleteDuplicates(head.next);
}
return head;
}
}
⭐️删除链表重复元素2
与1区别:这题要求删除重复元素,并且不保留重复元素
迭代法:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummy = new ListNode(0, head);
ListNode prev = dummy, curr, next;
while ((curr = prev.next) != null && (next = curr.next) != null) {
if (next.val != curr.val) {
prev = prev.next;
continue;
}
while (next != null && next.val == curr.val) next = next.next;
prev.next = next;
}
return dummy.next;
}
}
递归法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
//baseCase
if (head == null || head.next == null) {
return head;
}
ListNode next = head.next;
//如果是这种情况
// 1 --> 1 --> 1 --> 2 --> 3
// head next
//1.则需要移动next直到出现与当前head.value不相等的情况(含null)
//2.并且此时的head已经不能要了,因为已经head是重复的节点
//--------------else-------------
// 1 --> 2 --> 3
// head next
//3.如果没有出现1的情况,则递归返回的节点就作为head的子节点
if (head.val == next.val) {
//1
while (next != null && head.val == next.val) {
next = next.next;
}
//2
head = deleteDuplicates(next);
} else {
//3
head.next = deleteDuplicates(next);
}
return head;
}
}
找出两个链表的交点
注意使用静态内部类创建链表。
public class GetInterSectionNode {
static class Node{
int val;
Node next;
Node(int val){
this.val = val;
}
Node(int val, Node next){
this.val = val;
this.next = next;
}
}
public static void main(String[] args) {
Node list1 = new Node(4);
Node list2 = new Node(5);
int[] arr = {1, 8, 4, 5};
int[] arr1 = {0, 1, 2};
Node p1 = list1;
Node p2 = list2;
Node pp = list1;
for(int num : arr){
Node temp = new Node(num);
p1.next = temp;
p1 = p1.next;
if(num == 8){
pp = p1;
}
}
for(int num : arr1){
Node temp = new Node(num);
p2.next = temp;
p2 = p2.next;
}
p2.next = pp;
System.out.println(solution(list1, list2).val);
}
private static Node solution(Node list1, Node list2){
Node p1 = list1;
Node p2 = list2;
while(p1.next != p2.next){
if(p1.next == null){
p1 = list2;
p2 = p2.next;
}
if(p2.next == null){
p2 = list1;
p1 = p1.next;
}else{
p1 = p1.next;
p2 = p2.next;
}
}
return p1.next;
}
}
链表反转
- 头插法
- 递归法
讲解:https://leetcode-cn.com/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode-solution-d1k2/
public class ReverseLinkedList {
static class Node{
int val;
Node next;
Node(int val){
this.val = val;
}
Node(int val, Node next){
this.val = val;
this.next = next;
}
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
Node list = new Node(0);
Node p = list;
for(int n : arr){
Node temp = new Node(n);
p.next = temp;
p = p.next;
}
// Node ans = solution(list);
Node ans = solution2(list);
while(ans != null){
System.out.print(ans.val + " ");
ans = ans.next;
}
}
private static Node solution(Node list){
// 头插法
Node head = new Node(-1);
Node p = list;
while(p != null){
Node temp = p.next; //注意记录下一个节点
p.next = head.next;
head.next = p;
p = temp;
}
return head.next;
}
private static Node solution2(Node list){
// 递归法
if(list == null || list.next == null){
return list;
}
Node temp = list.next;
Node newHead = solution2(list.next);
temp.next = list;
list.next = null;
return newHead;
}
}
⭐️归并两个有序链表
public class MergeTwoList {
static class ListNode{
int val;
ListNode next;
ListNode(int val){
this.val = val;
}
}
public static void main(String[] args) {
int[] arr1 = {1,2,3,4};
int[] arr2 = {2,3,4,5};
ListNode l1 = new ListNode(0);
ListNode l2 = new ListNode(0);
ListNode p1 = l1;
ListNode p2 = l2;
for(int num : arr1){
ListNode temp = new ListNode(num);
p1.next = temp;
p1 = p1.next;
}
for(int num : arr2){
ListNode temp = new ListNode(num);
p2.next = temp;
p2 = p2.next;
}
ListNode ans = solution(l1.next, l2.next);
while(ans != null){
System.out.println(ans.val);
ans = ans.next;
}
}
private static ListNode solution(ListNode l1, ListNode l2){
ListNode p1 = l1;
ListNode p2 = l2;
ListNode head = new ListNode(0);
ListNode curr = head;
while(p1 != null && p2 != null){
if(p1.val <= p2.val){
curr.next = p1;
p1 = p1.next;
}else{
curr.next = p2;
p2 = p2.next;
}
curr = curr.next;
}
curr.next = p1 == null ? p2 : p1;
return head.next;
}
}
注意这题有一个递归写法:
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
ListNode res = l1.val < l2.val ? l1 : l2;
res.next = mergeTwoLists(res.next, l1.val >= l2.val ? l1 : l2);
return res;
}
链表求和
Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) Output: 7 -> 8 -> 0 -> 7
题目要求:不能修改原始链表。
import java.util.*;
public class AddTwoNumber{
static class Node{
int val;
Node next;
Node(int val){
this.val = val;
}
}
public static void main(String[] args) {
int[] arr1 = {7 , 2, 4, 3};
int[] arr2 = {5 , 6, 4};
Node list1 = new Node(7);
Node list2 = new Node(5);
Node p1 = list1;
Node p2 = list2;
for(int i = 1; i < arr1.length; i++){
Node temp = new Node(arr1[i]);
p1.next = temp;
p1 = p1.next;
}
for(int i = 1; i < arr2.length; i++){
Node temp = new Node(arr2[i]);
p2.next = temp;
p2 = p2.next;
}
Node ans = add(list1, list2);
while(ans != null){
System.out.println(ans.val);
ans = ans.next;
}
}
private static Node add(Node list1, Node list2){
Stack<Integer> stack1 = buildStack(list1);
Stack<Integer> stack2 = buildStack(list2);
Node head = new Node(-1);
int carry = 0; //进位
while(!stack1.isEmpty() || !stack2.isEmpty() || carry != 0){
int x = stack1.isEmpty() ? 0 : stack1.pop();
int y = stack2.isEmpty() ? 0 : stack2.pop();
int sum = x + y + carry;
Node temp = new Node(sum % 10);
temp.next = head.next;
head.next = temp;
carry = sum / 10;
}
return head.next;
}
private static Stack<Integer> buildStack(Node list){
Stack<Integer> stack = new Stack<>();
while(list != null){
stack.push(list.val);
list = list.next;
}
return stack;
}
}
⭐️K个一组翻转链表
-
题目描述:给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序
-
注意:翻转一个链表并不难,过程可以参考「206. 反转链表」。但是对于一个子链表,除了翻转其本身之外,还需要将子链表的头部与上一个子链表连接,以及子链表的尾部与下一个子链表连接。
注意下面最关键的链表逆转方式
public class ReverseKGroup {
static class ListNode{
int val;
ListNode next;
ListNode(int val){
this.val = val;
}
}
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7,8,9,10,11};
int k = 3;
ListNode head = new ListNode(0);
ListNode p = head;
for(int i = 0; i < arr.length; i++){
ListNode temp = new ListNode(arr[i]);
p.next = temp;
p = p.next;
}
ListNode ans = solution(head.next, k);
while(ans != null){
System.out.println(ans.val);
ans = ans.next;
}
}
private static ListNode solution(ListNode head, int k){
ListNode newHead = new ListNode(0); //头指针
ListNode pre = newHead; //前驱
ListNode curr = head; //当前
newHead.next = head;
int len = 0;
while(head != null){
head = head.next;
len++;
}
head = newHead.next; //复位
for(int i = 0; i < len / k; i++){
for(int j = 0; j < k - 1; j++){
// 循环 k - 1次即可
//注意:这里的链表逆转方式类似于插入排序,每次把curr后面节点的插入到最前面,这样保证curr的位置始终没变,方便
ListNode next = curr.next;
curr.next = next.next;
next.next = pre.next;
pre.next = next;
}
pre = curr;
curr = pre.next;
}
return newHead.next;
}
}
⭐️环形链表2
题目描述:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
思路:
-
首先根据快慢指针判断有无环。
-
对于有环的情况,设head -到环入口距离 == a, 环的长度 == b:
fast 走的步数是slow步数的 2 倍,即f=2s;(解析: fast 每轮走 2 步)
fast 比 slow多走了n 个环的长度,即 f = s + nb ( 解析: 双指针都走过 aa 步,然后在环内绕圈直到重合,重合时 fast 比 slow 多走 环的长度整数倍 );
以上两式相减得:s = nb即slow再走 a步长就可以到达环的入口。
public class DetectCycle {
static class ListNode{
int val;
ListNode next;
ListNode(int val){
this.val = val;
}
}
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7,8,9};
ListNode h = new ListNode(0);
ListNode p = h, cycle = new ListNode(-1);
for(int num : arr){
ListNode temp = new ListNode(num);
p.next = temp;
p = p.next;
if(num == 5){
cycle = temp;
}
}
p.next = cycle; //建环
System.out.println(solution(h.next).val);
}
private static ListNode solution(ListNode head){
boolean hasCycle = false;
ListNode p1 = head, p2 = head; //快慢指针
while(p1 != null && p2.next != null){
p1 = p1.next;
p2 = p2.next.next;
if(p1.val == p2.val){
hasCycle = true;
break;
}
}
if(hasCycle){
ListNode p3 = head;
while(p1.val != p3.val){
p1 = p1.next;
p3 = p3.next;
}
return p3;
}else{
return null;
}
}
}
⭐️ 链表的倒数第k个节点
双指针法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode p1 = head;
ListNode p2 = head;
while(k > 0 && p2 != null){
p2 = p2.next;
k--;
}
if(k > 0){
return null;
}
while(p2 != null){
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
}
⭐️ 反转链表2
题目:给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-linked-list-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
for(int i = 1; i < m; i++){
pre = pre.next;
}
head = pre.next;
for(int i = m; i < n; i++){
ListNode nex = head.next;
head.next = nex.next;
nex.next = pre.next;
pre.next = nex;
}
return dummy.next;
}
}
⭐️合并K个有序链表
有两种算法:分治和最小堆,二者的时间复杂度都是 n*KlogK,n是一条链表的长度,K是链表的数量
分治法:
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists, 0, lists.length - 1);
}
public ListNode merge(ListNode[] lists, int l, int r) {
//二路归并
if (l == r) {
return lists[l];
}
if (l > r) {
return null;
}
int mid = (l + r) >> 1;
ListNode l1 = merge(lists, l, mid);
ListNode l2 = merge(lists, mid + 1, r);
return mergeTwoLists(l1, l2);
}
public ListNode mergeTwoLists(ListNode a, ListNode b) {
if (a == null || b == null) {
return a != null ? a : b;
}
ListNode head = new ListNode(0);
ListNode tail = head, aPtr = a, bPtr = b;
while (aPtr != null && bPtr != null) {
if (aPtr.val < bPtr.val) {
tail.next = aPtr;
aPtr = aPtr.next;
} else {
tail.next = bPtr;
bPtr = bPtr.next;
}
tail = tail.next;
}
tail.next = (aPtr != null ? aPtr : bPtr);
return head.next;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/merge-k-sorted-lists/solution/he-bing-kge-pai-xu-lian-biao-by-leetcode-solutio-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
最小堆:
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
ListNode dummyHead = new ListNode(0);
ListNode curr = dummyHead;
PriorityQueue<ListNode> pq = new PriorityQueue<>(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val - o2.val;
}
});
for (ListNode list : lists) {
if (list == null) {
continue;
}
pq.add(list);
}
while (!pq.isEmpty()) {
ListNode nextNode = pq.poll();
curr.next = nextNode;
curr = curr.next;
if (nextNode.next != null) {
pq.add(nextNode.next);
}
}
return dummyHead.next;
}
}
⭐️判断回文链表
这题要求时间复杂度 o(n),空间复杂度 o(1),用栈可能不满足要求
关于反转链表的递归解法讲解参看:https://leetcode-cn.com/problems/reverse-linked-list/solution/shi-pin-jiang-jie-die-dai-he-di-gui-hen-hswxy/
方法一:栈
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
LinkedList<Integer> stack = new LinkedList<Integer>();
ListNode tmp = head;
while(tmp != null){
stack.push(tmp.val);
tmp = tmp.next;
}
ListNode tmp2 = head;
while(tmp2 != null){
if(tmp2.val != stack.pop()){
return false;
}
tmp2 = tmp2.next;
}
return true;
}
}
作者:LeetcodeFrom20210207
链接:https://leetcode-cn.com/problems/palindrome-linked-list/solution/234-hui-wen-lian-biao-by-leetcodefrom202-xorb/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
方法二:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
// 要实现 O(n) 的时间复杂度和 O(1) 的空间复杂度,需要翻转后半部分
if (head == null || head.next == null) {
return true;
}
ListNode fast = head;
ListNode slow = head;
// 根据快慢指针,找到链表的中点
while(fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
slow = reverse(slow.next);
while(slow != null) {
if (head.val != slow.val) {
return false;
}
head = head.next;
slow = slow.next;
}
return true;
}
private ListNode reverse(ListNode head){
if (head == null || head.next == null) {//注意这里的推出条件,一定要写head.next == null
return head;
}
ListNode newHead = reverse(head.next);//这里将head.next后面的元素都反转
head.next.next = head;//形成环
head.next = null;//反转成功
return newHead;//返回新的头节点
}
}
⭐️重排链表
定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reorder-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public void reorderList(ListNode head) {
ListNode p1 = head;
ListNode fast = p1;
ListNode slow = fast;
if(fast == null)return;
//快慢指针找到中点
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
//递归翻转后半部分
ListNode p2 = reverse(slow);
//将翻转后的后半部分插入原链表
while(p2 != null){
ListNode temp = p2.next;
p2.next = p1.next;
p1.next = p2;
p1 = p1.next.next;
p2 = temp;
}
//断链
p1.next = null;
}
public ListNode reverse(ListNode head){
if(head == null || head.next == null){
return head;
}
ListNode temp = head.next;
ListNode newHead = reverse(head.next);
temp.next = head;
head.next = null;
return newHead;
}
}
3.3 栈
实现计算器
用栈实现队列
- 入栈只进stack1
- 出栈只出stack2,若stack2为空,先从stack1全部拿到stack2,再出栈一个stack2
import java.util.ArrayDeque;
import java.util.Deque;
public class StackToQueue {
Deque<Integer> in = new ArrayDeque<>();
Deque<Integer> out = new ArrayDeque<>();
public static void main(String[] args) {
StackToQueue queue = new StackToQueue();
queue.add(1);
queue.add(2);
queue.add(3);
System.out.println(queue.get());
System.out.println(queue.get());
System.out.println(queue.get());
}
private void add(int val){
in.push(val);
}
private int get(){
inToOut();
return out.pop();
}
private void inToOut(){
if(out.isEmpty()){
while(!in.isEmpty()){
out.push(in.pop());
}
}
}
}
用队列实现栈
- 加入元素时,把新元素之前的元素放到队尾
import java.util.ArrayDeque;
import java.util.Queue;
public class QueueToStack {
Queue<Integer> queue= new ArrayDeque<>();
public static void main(String[] args) {
QueueToStack stack = new QueueToStack();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
public void push(int val){
queue.offer(val);
int size = queue.size();
while(size-- > 1){
queue.offer(queue.poll());
}
}
public int pop(){
return queue.poll();
}
}
⭐️括号匹配
补充:为什么要用Deque替代Stack
Vector不行是因为效率不太行,很多方法都用了synchronized修饰,虽然线程安,因此继承Vector的Stack也存在效率问题,故不推荐使用。
再一个原因是Deque双端队列可以实现多种数据结构,完全可以模拟成栈的结构。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bpNgPAyO-1638356641861)(D:\MD笔记\typora-pic\2020053121015811.png)]
class Solution {
public boolean isValid(String s) {
if(s.isEmpty())
return true;
Deque<Character> stack=new ArrayDeque<>();
for(char c:s.toCharArray()){
if(c=='(')
stack.push(')');
else if(c=='{')
stack.push('}');
else if(c=='[')
stack.push(']');
else if(stack.empty()||c!=stack.pop())
return false;
}
return stack.empty();
}
}
⭐️基本计算器
给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
class Solution {
public int calculate(String s) {
Stack<Integer> stack = new Stack<Integer>();
// sign 代表正负
int sign = 1, res = 0;
int length = s.length();
for (int i = 0; i < length; i++) {
char ch = s.charAt(i);
if (Character.isDigit(ch)) {
int cur = ch - '0';
while (i + 1 < length && Character.isDigit(s.charAt(i + 1)))
cur = cur * 10 + s.charAt(++i) - '0';
res = res + sign * cur;
} else if (ch == '+') {
sign = 1;
} else if (ch == '-') {
sign = -1;
} else if (ch == '(') {
stack.push(res);
res = 0;
stack.push(sign);
sign = 1;
} else if (ch == ')') {
res = stack.pop() * res + stack.pop();
}
}
return res;
}
}
3.4 队列
⭐️滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值【即每个窗口的最大值组成的数组】。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int len = nums.length;
if (len == 0) {
return nums;
}
int[] arr = new int[len - k + 1];
int arr_index = 0;
//我们需要维护一个单调递增的双向队列
Deque<Integer> deque = new LinkedList<>();
//先将第一个窗口的值按照规则入队
for (int i = 0; i < k; i++) {
while (!deque.isEmpty() && deque.peekLast() < nums[i]) {
deque.removeLast();
}
deque.offerLast(nums[i]);
}
//存到数组里,队头元素
arr[arr_index++] = deque.peekFirst();
//移动窗口
for (int j = k; j < len; j++) {
//对应咱们的红色情况,则是窗口的前一个元素等于队头元素
if (nums[j - k] == deque.peekFirst()) {
deque.removeFirst();
}
while (!deque.isEmpty() && deque.peekLast() < nums[j]) {
deque.removeLast();
}
deque.offerLast(nums[j]);
arr[arr_index++] = deque.peekFirst();
}
return arr;
}
}
3.5 数组
⭐️缺失的第一个正数
给你一个未排序的整数数组 nums
,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n)
并且只使用常数级别额外空间的解决方案。
public class Solution {
public int firstMissingPositive(int[] nums) {
int len = nums.length;
for (int i = 0; i < len; i++) {
while (nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]) {
//自建哈希表i-->i+1
// 满足在指定范围内、并且没有放在正确的位置上,才交换
// 例如:数值 3 应该放在索引 2 的位置上
// 理解nums[nums[i] - 1] != nums[i]:相当于i下标的元素和待交换下标 nums[i] - 1的元素如果一样,那已经满足哈希函数规则了,不用交换。
swap(nums, nums[i] - 1, i);
}
}
// [1, -1, 3, 4]
for (int i = 0; i < len; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}
// 都正确则返回数组长度 + 1
return len + 1;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
⭐️搜索二维矩阵 II
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-a-2d-matrix-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
// 时间复杂度 O(m + n)
// 算法:把左上角或者右下角看作一个二叉查找树。
if(matrix == null || matrix.length == 0)return false;
int m = 0, n = matrix[0].length - 1;
while(m < matrix.length && n >= 0){
if(matrix[m][n] == target){
return true;
}else if(matrix[m][n] < target){
m = m + 1;
}else{
n = n - 1;
}
}
return false;
}
}
⭐️下一个排列
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-permutation
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public void nextPermutation(int[] nums) {
//字典序算法:https://www.cnblogs.com/darklights/p/5285598.html
//其算法思路其实很简单:
//1.从后往前遍历,从这个序列中从右至左找第一个左邻小于右邻的数
//2.此时必然要从后面换一个比当前数大的最小数到当前位置(再次从右往左)
//3.换好以后,新的序列还不是最小的大值,要对之后的数升序排序。
//--------------------------------查找
int small = -1; //要交换的较小值下标
int len = nums.length;
for(int i = len - 1; i > 0; i--){
// 如果找不到,则所有排列求解完成,如果找得到则说明排列未完成。
if(nums[i] > nums[i - 1]){
small = i - 1;
break;
}
}
int big = 0; //要交换的较大值下标
for(int i = len - 1; i >= 0 && small >= 0; i--){
if(nums[i] > nums[small]){
big = i;
break;
}
}
if(small == -1) {
//说明已经最大了,不能再找了
quickSort(nums, small + 1, len - 1);
}else{
//---------------------------------交换
int temp = nums[small];
nums[small] = nums[big];
nums[big] = temp;
//----------------------------------交换后,重新排序,使用随机快排
quickSort(nums, small + 1, len - 1);
}
}
void quickSort(int[] arr, int L, int R){
if(L < R){
swap(arr, L + (int)(Math.random()*(R - L + 1)), R); //这里有一点疑惑,为什么随机数包含R?
int[] p = partion(arr, L, R);
quickSort(arr, L, p[0] - 1);
quickSort(arr, p[1] + 1, R);
}
}
int[] partion(int[] arr, int L, int R){
int less = L - 1;
int more = R; //注意为什么是R,因为R不参与partion,它存储的是基准元素
while(L < more){
if(arr[L] < arr[R]) swap(arr, ++less, L++);// 注意为什么是L++,其实因为L必然是==R的数了,不需要判断,画个图描述: <<<<less=======L 未知 未知 more >>>>>>> R
else if(arr[L] > arr[R]) swap(arr, --more, L);//注意这里为什么是L,因为交换后的数不一定是等于R的需要再判断一次,L到R之间是未知的
else L++;
}
swap(arr, more , R);
return new int[]{less + 1, more}; //[less + 1, more]是中间的等于R的数
}
void swap(int[] arr, int a, int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
⭐️多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/majority-element
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
//摩尔投票法
int res = 0, count = 0;
for(int i = 0; i < nums.length; i ++){
if(count == 0){
//票数为0更换目标
res = nums[i];
count++;
}else{
if(res == nums[i]){
count++;
}else{
count--;
}
}
}
return res;
}
}
}
⭐️合并区间
题目:以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/merge-intervals
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals.length == 0) {
return new int[0][2];
}
Arrays.sort(intervals, new Comparator<int[]>() {//按照区间左边界排序
public int compare(int[] interval1, int[] interval2) {
return interval1[0] - interval2[0];
}
});
List<int[]> merged = new ArrayList<int[]>();
for (int i = 0; i < intervals.length; ++i) {
int L = intervals[i][0], R = intervals[i][1];
if (merged.size() == 0 || merged.get(merged.size() - 1)[1] < L) {
merged.add(new int[]{L, R});//mergedL小于当前L
} else {
merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], R);
}
}
//注意:这里的用new int[0]只是为了指定函数的形参数,最终返回的int[]的长度是由你的list存 //储内容的长度决定了。new int[0]就是起一个模板的作用,指定了返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型
return merged.toArray(new int[0][]);
}
}
⭐️移动字符串的 全部*号到左侧
public class MoveStar {
public static void main(String[] args) {
String str = "*abcd*efg*h";
System.out.println(solution(str));
}
private static String solution(String str){
char[] ch = str.toCharArray();
int idx = ch.length - 1;
for(int i = ch.length - 1; i >=0; i--){
if(ch[i] != '*'){
ch[idx--] = ch[i];
}
}
while(idx >= 0){//注意--不能加在while循环上,因为while循环进入循环体时,idx已经--,会越界
ch[idx--] = '*';
}
return new String(ch);
}
}
⭐️螺旋矩阵
题目:给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
思路:
- 按照每一层遍历,注意先完整遍历一行和一列,保证特殊情况:只有一行或一列时能够遍历。
- 再判断是否有两层及其以上,来添加剩下两边。
package com.tosang.java;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: tosang
* @Date: 2021/4/5 13:55
*/
public class spiralOrder {
public static void main(String[] args) {
int[][] arr = {
{1,2,3},
{4,5,6},
{7,8,9}
};
List<Integer> solution = solution(arr);
solution.forEach(System.out::print);
}
public static List<Integer> solution(int[][] matrix){
ArrayList<Integer> list = new ArrayList<>();
if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
return list;
}
int row = matrix.length;
int col = matrix[0].length;
int top = 0, bottom = row - 1;
int left = 0, right = col - 1;
while(top <= bottom && left <= right){
for(int i = left; i <= right; i++){
list.add(matrix[top][i]);
}
for (int j = top + 1; j <= bottom; j++){
list.add(matrix[j][right]);
}
// 如果有两行两列以上。
if(left < right && top < bottom){
for(int i = right - 1; i >= left + 1; i--){
list.add(matrix[bottom][i]);
}
for (int j = bottom; j >= top + 1; j--){
list.add(matrix[j][left]);
}
}
left++;
right--;
top++;
bottom--;
}
return list;
}
}
⭐️旋转图像
给定一个 n × n 的二维矩阵 matrix
表示一个图像。请你将图像顺时针旋转 90 度。要求原地旋转
class Solution {
public void rotate(int[][] matrix) {
// 先转置,然后镜像对称
for(int i = 0; i < matrix.length; i++){
for(int j = 0; j < i; j++){ // 注意为啥是 j < i, 因为求的是转置
// 转置
int temp = matrix[j][i];
matrix[j][i] = matrix[i][j];
matrix[i][j] = temp;
}
}
for(int i = 0; i < matrix.length; i++){
// 镜像对称
for(int j = 0; j < matrix[0].length / 2; j++){
int temp = matrix[i][j];
matrix[i][j] = matrix[i][matrix[0].length - j - 1];
matrix[i][matrix[0].length - j - 1] = temp;
}
}
}
}
3.6 位运算
现在有一个数组,里面有一个数出现了奇数次,剩下的数出现偶数次,怎么找出这个数?
public class OddAndEven {
// 有1个出现奇数和n-1个出现偶数次的数,找出出现奇数次的那个数
public static void main(String[] args) {
int[] arr = {1,1,2,2,3,3,3};
System.out.println(solution(arr));
}
private static int solution(int[] arr){
int ans = 0;
for(int num : arr){
ans ^= num;
}
return ans;
}
}
3.7 二叉树
⭐️验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/validate-binary-search-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
long last = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
// 中序遍历优化,可以提前退出
return inOrder(root);
}
public boolean inOrder(TreeNode root){
// 中序遍历,看是否为升序
if(root == null) return true;
if(!inOrder(root.left)){
return false;
};
if(last >= root.val){
return false;
}
last = root.val;
if(!inOrder(root.right)){
return false;
};
return true;
}
}
⭐️判断对称二叉树
递归法
/**
* 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) {
if(root == null)return true;
return compare(root.left, root.right);
}
boolean compare(TreeNode t1, TreeNode t2){
if(t1 == null && t2 == null) return true;
if(t1 == null || t2 == null || t1.val != t2.val) return false;
return compare(t1.left, t2.right) && compare(t1.right, t2.left);
}
}
迭代法
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root.left);
queue.offer(root.right);
while (!queue.isEmpty()) {
TreeNode node1 = queue.poll();
TreeNode node2 = queue.poll();
if (node1 == null && node2 == null) {
continue;
}
if (node1 == null || node2 == null || node1.val != node2.val) {
return false;
}
queue.offer(node1.left);
queue.offer(node2.right);
queue.offer(node1.right);
queue.offer(node2.left);
}
return true;
}
}
⭐️翻转二叉树
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null){
return root;
}
TreeNode right = root.right;
root.right = invertTree(root.left);
root.left = invertTree(right);
return root;
}
}
⭐️二叉树的层次遍历
- 注意包含两个步骤:用数组建立二叉树 + 二叉树层次遍历。
import java.util.*;
public class levelOrder {
static class TreeNode{
int val;
TreeNode left;
TreeNode right;
TreeNode(int val){
this.val = val;
}
}
public static void main(String[] args) {
int[] arr = {3, 9, 20, -1, -1, 15, 7};//-1代表null
TreeNode root = buildTree(arr, 0);
List<List<Integer>> ans = solution(root);
Iterator<List<Integer>> iterator = ans.iterator();
while(iterator.hasNext()){
Iterator<Integer> ite = iterator.next().iterator();
while(ite.hasNext()){
System.out.print(ite.next() + " ");
}
System.out.println();
}
}
private static TreeNode buildTree(int[] arr, int index){
// 根据数组建立二叉树
if(index >= arr.length){
return null;
}
if(arr[index] == -1){
return null;
}
TreeNode ans = new TreeNode(arr[index]);
ans.left = buildTree(arr, index * 2 + 1);
ans.right = buildTree(arr, index * 2 + 2);
return ans;
}
private static List<List<Integer>> solution(TreeNode root){
// 层次遍历
List<List<Integer>> ans = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
List<Integer> temp = new ArrayList<>();
while(size > 0){
TreeNode tempNode = queue.poll();
temp.add(tempNode.val);
if(tempNode.left != null){
queue.offer(tempNode.left);
}
if(tempNode.right != null){
queue.offer(tempNode.right);
}
size--;
}
ans.add(temp);
}
return ans;
}
}
⭐️二叉树的锯齿形打印
思路:和层次遍历类似,不过多一个flag来控制list的添加顺序。
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
boolean flag = true;
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
LinkedList<Integer> list = new LinkedList<>();
while (size > 0) {
TreeNode node = queue.poll();
if (flag) {
list.add(node.val);
} else {
list.addFirst(node.val);
}
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
size--;
}
res.add(list);
flag = !flag;
}
return res;
}
}
⭐️二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。
思路:采用后序遍历
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left == null && right == null) return null; // 1.情况1:子树找不到p/q
if(left == null) return right; // 3.情况3,左子树没有p/q,右子树有
if(right == null) return left; // 4.情况4,右子树没有p/q,左子树有
return root; // 2. if(left != null and right != null)
}
}
⭐️二叉树的中序遍历
递归 + 非递归
注意:以下三种树的遍历采用同一种模板,思路清晰。
class Solution {
//递归法
List<Integer> list = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root != null){
if(root.left != null)inorderTraversal(root.left);
list.add(root.val);
if(root.right != null)inorderTraversal(root.right);
}
return list;
}
}
class Solution {
public List<Integer> inOrderTraversal(TreeNode root) {
//非递归算法
Deque<TreeNode> stack = new ArrayDeque<>();
List<Integer> list = new ArrayList<>();
while (root != null || !stack.isEmpty()) {
if (root!=null){
//先往左走到头
stack.push(root);
root = root.left;
}else {
//出栈,保存数据,往右走
root = stack.pop();
list.add(root.val);//后添加
root = root.right;
}
}
return list;
}
}
⭐️先序遍历非递归
class Solution {
public List<Integer> preOrderTraversal(TreeNode root) {
//非递归算法
Deque<TreeNode> stack = new ArrayDeque<>();
List<Integer> list = new ArrayList<>();
while (root != null || !stack.isEmpty()) {
if (root!=null){
//先往左走到头
list.add(root.val) //先添加
stack.push(root);
root = root.left;
}else {
//出栈,保存数据,往右走
root = stack.pop();
root = root.right;
}
}
return list;
}
}
⭐️后续遍历非递归
思路:后续遍历非递归直接写比较困难,但是可以有下面的骚操作实现
- 根据先序遍历是:根 - 左 - 右 的顺序,将list的添加顺序逆转(从头添加,而不是尾部),那么list包含的顺序必然是:右 - 左 - 根。
- 在1的基础上,我们只需要改变下遍历顺序即可,即先遍历右走到头,再去遍历左,便有了:左 - 右 - 根。
class Solution {
public List<Integer> postOrderTraversal(TreeNode root) {
//非递归算法
Deque<TreeNode> stack = new ArrayDeque<>();
List<Integer> list = new ArrayList<>();
while (root != null || !stack.isEmpty()) {
if (root!=null){
//先往右走到头
list.addFirst(root.val)//注意加到表头,不是表尾
stack.push(root);
root = root.right;
}else {
//出栈,保存数据,往右走
root = stack.pop();
root = root.left;
}
}
return list;
}
}
⭐️二叉树的最大深度
class Solution {
public int maxDepth(TreeNode root) {
//考虑递归
if(root == null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
⭐️判断平衡二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isBalanced(TreeNode root) {
return height(root) != -1;
}
public int height(TreeNode root){
// 递归退出条件
if(root == null){
return 0;
}
// 单步操作
int l = height(root.left); //为正数或0时表示树高,为负数是表示不平衡
int r = height(root.right);
//返回值
if(l >= 0 && r >= 0 && Math.abs(l - r) <= 1){
return Math.max(l, r) + 1;
}else{
return -1;
}
}
}
⭐️二叉树的最大路径和
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-tree-maximum-path-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return maxSum;
}
private int dfs(TreeNode root){
// 有点像后序遍历
if(root == null) return 0;
int left = dfs(root.left);
int right = dfs(root.right);
int oneSidePath = root.val + Math.max(left, right); //单边最大路径
maxSum = Math.max(maxSum, left + root.val + right);
// 由于root.val可能为负值,所以要判断
return oneSidePath > 0 ? oneSidePath : 0;
}
}
⭐️路径总和2
题目:给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<List<Integer>> list = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
dfs(list, temp, root, targetSum, 0);
return list;
}
private void dfs(List list, List temp, TreeNode root, int targetSum, int sum){
// 回溯法
if(root == null){
return ;
}
if(root.left == null && root.right == null && root.val + sum == targetSum){
temp.add(root.val);
list.add(new ArrayList<>(temp));
temp.remove(temp.size() - 1);
return;
}
// 需要注意:这里不能剪枝,即判断root.val + sum > targetSum就直接停止
//因为:节点值可能为负数,后面还可能等于targetSum
temp.add(root.val);
dfs(list, temp, root.left, targetSum, sum + root.val);
dfs(list, temp, root.right, targetSum, sum + root.val);
temp.remove(temp.size() - 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 buildTree(int[] preorder, int[] inorder) {
return buildTree(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
}
//l1,r1;l2,r2分别表示在两个数组的左右边界
public TreeNode buildTree(int[] preorder, int l1, int r1, int[] inorder, int l2, int r2){
if(l1 > r1)return null;
int rootVal = preorder[l1]; //根节点
TreeNode root = new TreeNode(rootVal);//加入
int index = l2; //使用根节点在中序遍历中划分左右子树,即中序遍历root的下标
while(index < r2 && inorder[index] != rootVal){
// 遍历,寻找inorder中根节点的下标
index++; //
}
root.left = buildTree(preorder, l1 + 1, l1 + (index - l2), inorder, l2, index - 1);
root.right = buildTree(preorder, l1 + (index -l2) + 1, r1, inorder, index + 1, r2);
return root;
}
}
⭐️二叉树的直径
题目:给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
if(root == null){
return 0;
}
dfs(root);
return max;
}
private int dfs(TreeNode root){
if(root.left == null && root.right == null){
return 0;
}
int left = root.left == null ? 0 : dfs(root.left) + 1;
int right = root.right == null ? 0 : dfs(root.right) + 1;
max = Math.max(max, left + right);
return Math.max(left, right);
}
}
⭐️求根节点到叶节点数字之和
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:
例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。
计算从根节点到叶节点生成的 所有数字之和 。
叶节点 是指没有子节点的节点。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
public int sumNumbers(TreeNode root) {
return helper(root, 0);
}
public int helper(TreeNode root, int i){
if (root == null) return 0;
int temp = i * 10 + root.val;
if (root.left == null && root.right == null)
return temp;
return helper(root.left, temp) + helper(root.right, temp);
}
⭐️路径总和2
题目:给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<List<Integer>> list = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
dfs(list, temp, root, targetSum, 0);
return list;
}
private void dfs(List list, List temp, TreeNode root, int targetSum, int sum){
// 回溯法
if(root == null){
return ;
}
if(root.left == null && root.right == null && root.val + sum == targetSum){
temp.add(root.val);
list.add(new ArrayList<>(temp));
temp.remove(temp.size() - 1);
return;
}
// 需要注意:这里不能剪枝,即判断root.val + sum > targetSum就直接停止
//因为:节点值可能为负数,后面还可能等于targetSum
temp.add(root.val);
dfs(list, temp, root.left, targetSum, sum + root.val);
dfs(list, temp, root.right, targetSum, sum + root.val);
temp.remove(temp.size() - 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 buildTree(int[] preorder, int[] inorder) {
return buildTree(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
}
//l1,r1;l2,r2分别表示在两个数组的左右边界
public TreeNode buildTree(int[] preorder, int l1, int r1, int[] inorder, int l2, int r2){
if(l1 > r1)return null;
int rootVal = preorder[l1]; //根节点
TreeNode root = new TreeNode(rootVal);//加入
int index = l2; //使用根节点在中序遍历中划分左右子树,即中序遍历root的下标
while(index < r2 && inorder[index] != rootVal){
// 遍历,寻找inorder中根节点的下标
index++; //
}
root.left = buildTree(preorder, l1 + 1, l1 + (index - l2), inorder, l2, index - 1);
root.right = buildTree(preorder, l1 + (index -l2) + 1, r1, inorder, index + 1, r2);
return root;
}
}
⭐️二叉树的直径
题目:给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
if(root == null){
return 0;
}
dfs(root);
return max;
}
private int dfs(TreeNode root){
if(root.left == null && root.right == null){
return 0;
}
int left = root.left == null ? 0 : dfs(root.left) + 1;
int right = root.right == null ? 0 : dfs(root.right) + 1;
max = Math.max(max, left + right);
return Math.max(left, right);
}
}
⭐️求根节点到叶节点数字之和
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:
例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。
计算从根节点到叶节点生成的 所有数字之和 。
叶节点 是指没有子节点的节点。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
public int sumNumbers(TreeNode root) {
return helper(root, 0);
}
public int helper(TreeNode root, int i){
if (root == null) return 0;
int temp = i * 10 + root.val;
if (root.left == null && root.right == null)
return temp;
return helper(root.left, temp) + helper(root.right, temp);
}
推荐1:简历制作神器
说明:个人用过,帮我斩获各大厂offer,体验很不错,价格也比较良心,真心推荐一波。