
判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
TreeNode(int val) {
this.val = val;
}
}
public static boolean isSum = false;
public static boolean hasPathSum(TreeNode root, int sum) {
if (root == null) {
return false;
}
isSum = false; //先把全局公共变量改false
process(root, 0, sum);
return isSum;
}
public static void process(TreeNode x, int preSum, int sum) {//之前已经形成的和是preSum,目标是sum
//把base case不定成空节点,而是叶节点
if (x.left == null && x.right == null) {
if (x.val + preSum == sum) {
isSum = true;
}
return;
}
// x是非叶节点
preSum += x.val;
//保证传入process里的x不能有null的时刻
if (x.left != null) {
process(x.left, preSum, sum);
}
if (x.right != null) {
process(x.right, preSum, sum);
}
}
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有从根节点到叶子节点路径总和等于给定目标和的路径。
注意:达标后返回上层要清理现场。

public static List<List<Integer>> pathSum(TreeNode root, int sum) {//收集所有路径:list套list
List<List<Integer>> ans = new ArrayList<>();
if (root == null) {
return ans;
}
ArrayList<Integer> path = new ArrayList<>();
process(root, path, 0, sum, ans);
return ans;
}
//五个参数:当前到达的某个节点、存之前经过节点的list、之前的累加和、目标数、达标路径存储list
public static void process(TreeNode x, List<Integer> path, int preSum, int sum, List<List<Integer>> ans) {
// base case还是叶节点
if (x.left == null && x.right == null) {
if (preSum + x.val == sum) {
path.add(x.val);
ans.add(copy(path));
//经典的清理现场的过程:把叶子节点值从list中去掉
path.remove(path.size() - 1);
}
return;
}
// x 非叶节点
path.add(x.val); //生成路径list
preSum += x.val; //累加和是值传递进递归故不用恢复现场,path是按引用传递
if (x.left != null) {
process(x.left, path, preSum, sum, ans);
}
if (x.right != null) {
process(x.right, path, preSum, sum, ans);
}
//该结点也要向上返回了,把值从list中去掉
path.remove(path.size() - 1);
}
public static List<Integer> copy(List<Integer> path) {
//copy出一个跟path里值完全一样的list
List<Integer> ans = new ArrayList<>();
for (Integer num : path) {
ans.add(num);
}
return ans;
}
归并排序
归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
时间复杂度:O(n * log n)
发明者:约翰·冯·诺伊曼
递归
// 递归方法实现
public static void mergeSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
// arr[L...R]范围上,请让这个范围上的数,有序!
public static void process(int[] arr, int L, int R) {
if (L == R) { //只有一个数
return;
}
// int mid = (L + R) / 2
// 若下标L+R很大可能发生越界,所以换成以下安全写法
int mid = L + ((R - L) >> 1);
process(arr, L, mid); //左边有序
process(arr, mid + 1, R); //右边有序
merge(arr, L, mid, R); //一起有序
}
public static void merge(int[] arr, int L, int M, int R) {
int[] help = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
while (p1 <= M && p2 <= R) { //有一个越界就跳出:其中一个已经全部放入help
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
// 要么p1越界,要么p2越界
// 不可能出现:共同越界
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
}
非递归:
步长递增:1->2->4->8->等等 ,然后每步进行两两归并。
注意:
步长>=总长度时,左组有且已经有序,右边没有,停止。
对最后一组,右组有多少算多少,进行merge。
找mid时,如果左组能凑够个数则mid=L+step-1,凑不够应该为mid=N-1。
但是mid=L+step-1可能发生值越界,计算:N-1-L+1=N-L个数。故N - L >= step时,mid=L+step-1;否则M = N - 1(此时没有右组了)。
若有右组,计算N-1-(M+1)+1=N-M-1个数。当N-M-1>=step,则R=M+step;否则R=N-1。
步长*=2可能发生溢出,若step > N / 2则应该break。一种情况;如果step = N / 2,此时若有17个数,17/2然后向下取整为8,步长为8时break明显会出错。
public static void mergeSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int step = 1;
int N = arr.length;
while (step < N) { //步长要小于总长度,等于总长度代表左边长度=步长,右边没有则不用继续
int L = 0;
while (L < N) { //每次改变步长都划分左右组
int M = 0;
//最后的左组可能凑不够,此时不用merge
//1.可凑满,来到L+step-1
//2.凑不满,来到N-1就可以了
//3.逻辑上是Math.min(L+step-1,N-1)
//4.但是L+step-1可能溢出
/**
* 来到一个具体的L,算L->N-1能否凑够step个数,凑不够M来到N-1,
* 能凑够来到L+step-1
*/
if (N - L >= step) { //N-1-L+1>=step 能凑满
M = L + step - 1;
} else {
M = N - 1;
}
//L...M 此时代表没有右组了
if (M == N - 1) {
break;
}
//有右组
int R = 0;
//R1...R
if (N - 1 - M >= step) { //N-1-(M+1)+1=N-M-1
R = M + step;
} else {
R = N - 1;
}
//左组:L...M 右组:M+1...R
merge(arr, L, M, R);
if (R == N - 1) {
break;
} else {
L = R + 1;
}
}
if (step > N / 2) { //为了防止数过大溢出
break;
}
step *= 2;
}
}
非递归经典写法:
public static void mergeSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int N = arr.length;
int mergeSize = 1;
while (mergeSize < N) {
int L = 0;
while (L < N) {
if (mergeSize >= N - L) {
break;
}
int M = L + mergeSize - 1;
int R = M + Math.min(mergeSize, N - M - 1);
merge(arr, L, M, R);
L = R + 1;
}
if (mergeSize > N / 2) {
break;
}
mergeSize <<= 1;
}
}
快速排序
单趟代码:
public static void splitNum1(int[] arr) {
int lessEqualR = -1; //<=区域的右边界,左边是<=区域
int index = 0;
int N = arr.length;
while (index < N) {
if (arr[index] <= arr[N - 1]) {
//当前数<=指定数,当前数和<=区下一个数做交换,<=区右扩,当前数也跳下一个
swap(arr, ++lessEqualR, index++);
} else { //当前数>指定数,当前数直接下一个
index++;
}
}
}
单趟代码优化:
拿最后一个数P做划分;小于区初始为-1;大于区初始为最后一个数。
当前数<P,当前数和<区下一个数做交换,<区右扩,当前数跳下一个;
当前数>P,当前数和>区前一个数做交换,>区左扩,当前数不动;
当前数=P,直接跳下一个;
最后P和>区最左边数交换
public static void splitNum2(int[] arr) { //优化
int N = arr.length;
int lessR = -1; //小于区初始为-1
int moreL = N - 1; //大于区初始为最后一个数
int index = 0; //当前数
// arr[N-1] --> 作为划分值
while (index < moreL) { //index没有撞上大于区的左边界时继续
if (arr[index] < arr[N - 1]) { //当前数<P,当前数和<区下一个数做交换,<区右扩,当前数跳下一个
swap(arr, ++lessR, index++);
} else if (arr[index] > arr[N - 1]) { //当前数>P,当前数和>区前一个数做交换,>区左扩,当前数不动
swap(arr, --moreL, index);
} else {
index++; //当前数=P,直接跳下一个
}
}
swap(arr, moreL, N - 1); //最后P和>区最左边数交换
}
递归实现快排:

// arr[L...R]范围上,拿arr[R]做划分值,
// L....R < = >
public static int[] partition(int[] arr, int L, int R) {
int lessR = L - 1; //小于区右边界
int moreL = R; //大于区左边界
int index = L; //当前数
while (index < moreL) {
if (arr[index] < arr[R]) { //index没有撞上大于区的左边界时继续
swap(arr, ++lessR, index++); //当前数<P,当前数和<区下一个数做交换,<区右扩,当前数跳下一个
} else if (arr[index] > arr[R]) { //当前数>P,当前数和>区前一个数做交换,>区左扩,当前数不动
swap(arr, --moreL, index);
} else {
index++; //当前数=P,直接跳下一个
}
}
swap(arr, moreL, R); //最后P和>区最左边数交换
//返回等于区的边界
return new int[] { lessR + 1, moreL }; //小于区的下一个即等于区的第一个,moreL为等于区最后一个
}
public static void quickSort1(int[] arr) { //递归快排
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
public static void process(int[] arr, int L, int R) {
if (L >= R) {
return;
}
//equalE[0]->等于区域第一个数 equalE[1]->等于区域第二个数
int[] equalE = partition(arr, L, R);
process(arr, L, equalE[0] - 1); //左边排序
process(arr, equalE[1] + 1, R); //右边排序
}
非递归快排:
明确排序的任务:

// arr[L...R]范围上,拿arr[R]做划分值,
// L....R < = >
public static int[] partition(int[] arr, int L, int R) {
int lessR = L - 1; //小于区右边界
int moreL = R; //大于区左边界
int index = L; //当前数
while (index < moreL) {
if (arr[index] < arr[R]) { //index没有撞上大于区的左边界时继续
swap(arr, ++lessR, index++); //当前数<P,当前数和<区下一个数做交换,<区右扩,当前数跳下一个
} else if (arr[index] > arr[R]) { //当前数>P,当前数和>区前一个数做交换,>区左扩,当前数不动
swap(arr, --moreL, index);
} else {
index++; //当前数=P,直接跳下一个
}
}
swap(arr, moreL, R); //最后P和>区最左边数交换
//返回等于区的边界
return new int[] { lessR + 1, moreL }; //小于区的下一个即等于区的第一个,moreL为等于区最后一个
}
public static class Job { //任务类:由一个左边界和一个右边界组成一个任务
public int L;
public int R;
public Job(int left, int right) {
L = left;
R = right;
}
}
public static void quickSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
Stack<Job> stack = new Stack<>(); //将要做的任务放到栈里去
stack.push(new Job(0, arr.length - 1));
while (!stack.isEmpty()) {
Job cur = stack.pop();
int[] equals = partition(arr, cur.L, cur.R); //依然返回等于区的位置
if (equals[0] > cur.L) { // 有 < 区域
stack.push(new Job(cur.L, equals[0] - 1)); //到等于区域的前一个位置
}
if (equals[1] < cur.R) { // 有 > 区域
stack.push(new Job(equals[1] + 1, cur.R)); //从等于区域的下一个位置
}
}
}
测试代码:
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
// int[] arr = { 7, 1, 3, 5, 4, 5, 1, 4, 2, 4, 2, 4 };
//
// splitNum2(arr);
// for (int i = 0; i < arr.length; i++) {
// System.out.print(arr[i] + " ");
// }
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
System.out.println("test begin");
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
// int[] arr3 = copyArray(arr1);
quickSort1(arr1);
quickSort2(arr2);
// quickSort3(arr3);
if (!isEqual(arr1, arr2) /*|| !isEqual(arr1, arr3)*/) {
System.out.println("Oops!");
succeed = false;
break;
}
}
System.out.println("test end");
System.out.println(succeed ? "Nice!" : "Oops!");
}